Skip to content

feat(phase8 #77 D8.4b): FE message-parts renderer (text/tool/source/citation/activity)#1703

Merged
earayu merged 4 commits into
mainfrom
fe/d8.4b-parts-renderer
Apr 25, 2026
Merged

feat(phase8 #77 D8.4b): FE message-parts renderer (text/tool/source/citation/activity)#1703
earayu merged 4 commits into
mainfrom
fe/d8.4b-parts-renderer

Conversation

@earayu
Copy link
Copy Markdown
Collaborator

@earayu earayu commented Apr 25, 2026

Summary

D8.4b first-cut. Replaces the legacy `AgentTurnCard` + `legacy-snapshot-shim` projection with a renderer that consumes the new `useAgentTurnStream` seam (D8.4a, merge `63a9d522`) directly. Each `AgentMessagePart` is rendered by type; transient `data-activity` is surfaced through a separate inline indicator and never persisted. Slot props `ConsentSlot` / `ElicitationSlot` carry the only handoff into #78.

What lands

File Change
`web/src/components/chat/agent-turn-renderer.tsx` NEW ~700 LOC. Activity stream + answer + references + slots all derive from the `parts` array.
`web/src/components/chat/agent-turn-card.tsx` DELETE (1279 LOC).
`web/src/features/agent-runtime/legacy-snapshot-shim.ts` DELETE; index re-exports updated.
`web/src/components/chat/chat-messages.tsx` `AgentTurnStreamCard` now feeds the hook output directly into `AgentTurnRenderer`; `projectToLegacySnapshot` projection layer is gone.
`web/src/i18n/{zh-CN,en-US}/page_chat.json` (+ aggregated catalogs) Adds `activity_stream.tool.{title,state.}`, `activity_stream.transient.`, `activity_stream.consent.placeholder_*`, `activity_stream.elicitation.placeholder_state`, `activity_stream.{completed_empty,pending_empty}`, `answer_section.completed_empty`.

Slot props (the only seam crossing into #78 territory)

```ts
type ConsentSlotProps = {
chatId: string;
turnId: string;
part: AgentToolConsentPart;
};
type ElicitationSlotProps = {
chatId: string;
turnId: string;
part: AgentElicitationPart;
};

type AgentTurnRendererProps = {
// ... part stream + status from useAgentTurnStream
ConsentSlot?: React.ComponentType;
ElicitationSlot?: React.ComponentType;
};
```

#78 chenyexuan implements `consent-prompt.tsx` + `elicitation-form.tsx` that conform to these prop signatures; both call `decideToolConsent` / `submitElicitation` from the agent-runtime API client landed in D8.4a. The slot props are optional by design — the placeholder fallback (`` / ``) keeps the parts visible even before #78 lands.

Visual baseline preserved

The L1 design and existing card affordance is intact:

  • avatar + status badge + timestamp header,
  • activity stream Collapsible (now: tool calls + consent/elicitation slots + transient activity indicator),
  • answer Card / streaming markdown,
  • debug Collapsible (turn id / request id / status / error code+message),
  • references Sheet (now sourced from `source-url` / `source-document` / `data-citation` parts),
  • feedback + copy.

Hook seam contract — unchanged

`useAgentTurnStream(...) → { parts, transientActivity, status, errorText, lastSequence, abort }` is consumed verbatim. No transport-layer changes; this PR is purely the rendering layer.

Verification

  • `yarn lint` clean.
  • `tsc --noEmit` clean for the touched files (the four pre-existing errors in `chat-input.tsx` are unrelated and untouched here).
  • `yarn dev` boots in 2.8s on port 3012; `GET /`, `/auth/signin`, `/workspace/collections`, `/workspace` all return 200.

Out of scope

Test plan

  • Symphony architect canonical drift quick check on the parts-to-render mapping (esp. tool state lifecycle + citation/source aggregation rules)
  • Weston minimal CR — confirm renderer does not regress hook contract / transport boundary
  • dongdong scoped FE seam review — confirm slot props interface is stable for chore: fix embedding timeout and scale the embedding workers #78 to plug into without renderer changes
  • Manual visual smoke once a fresh tenant is available (run an agent turn → confirm activity stream / streaming answer / references all render from `parts`)
  • Confirm consent / elicitation placeholder fallback renders when chore: fix embedding timeout and scale the embedding workers #78 has not landed yet

Hand-off seam for #78

  • Branch `fe/d8.4b-parts-renderer` head `b532abcd`; chenyexuan can base off this commit and add `consent-prompt.tsx` + `elicitation-form.tsx` that match `ConsentSlotProps` / `ElicitationSlotProps`.
  • Wiring is a one-line change in `chat-messages.tsx`'s `AgentTurnStreamCard` (`<AgentTurnRenderer ... ConsentSlot={ConsentPrompt} ElicitationSlot={ElicitationForm} />`).

🤖 Generated with Claude Code

earayu and others added 2 commits April 25, 2026 23:12
…itation/activity)

D8.4b first-cut. Replaces the legacy `AgentTurnCard` + `legacy-snapshot-shim`
projection with a renderer that consumes the new `useAgentTurnStream`
seam (D8.4a, merge `63a9d522`) directly. Each `AgentMessagePart` is
rendered by type; transient `data-activity` is surfaced through a
separate inline indicator and never persisted.

## What lands

* **NEW** `web/src/components/chat/agent-turn-renderer.tsx` — rebuilds
  the activity card from the `parts` stream. Keeps the L1 visual
  baseline (avatar + status badge + activity stream Collapsible +
  answer Card + debug Collapsible + references Sheet + feedback +
  copy) so non-technical users see the same affordance.
  * `<ToolActivityItem>` — one entry per `tool-${SafeToolName}` part;
    state-aware label / icon / debug-expand previews of input + output
    (or errorText on `output-error`).
  * `<ActivityIndicator>` — transient `data-activity` rendered inline
    above the activity stream entries; replaced on each new frame and
    never persisted.
  * `<ConsentPlaceholder>` / `<ElicitationPlaceholder>` — fallback
    rendering for `data-tool-consent` / `data-elicitation` parts when
    no interactive slot is provided. **#78 chenyexuan** plugs in
    concrete components via the new `ConsentSlot` / `ElicitationSlot`
    props on `AgentTurnRendererProps`.
  * References sheet now sources from `source-url` / `source-document`
    parts + `data-citation` content, replacing the old
    `reference_bundle` artifact path.

* `chat-messages.tsx` — `AgentTurnStreamCard` now feeds the hook
  output directly into `AgentTurnRenderer`; the
  `projectToLegacySnapshot` projection layer is gone.

* **DELETE** `web/src/components/chat/agent-turn-card.tsx`
  (1279 LOC) — replaced by the new renderer end-to-end.
* **DELETE** `web/src/features/agent-runtime/legacy-snapshot-shim.ts`
  — its only caller (`AgentTurnStreamCard`) no longer needs the
  projection. `getRunningToolName` / `projectToLegacySnapshot` /
  `LegacySnapshotShim` are dropped from the feature module
  re-exports.

## Slot props (the only seam crossing into #78 territory)

```ts
type ConsentSlotProps = {
  chatId: string;
  turnId: string;
  part: AgentToolConsentPart;
};
type ElicitationSlotProps = {
  chatId: string;
  turnId: string;
  part: AgentElicitationPart;
};

type AgentTurnRendererProps = {
  // ... part stream + status from useAgentTurnStream
  ConsentSlot?: React.ComponentType<ConsentSlotProps>;
  ElicitationSlot?: React.ComponentType<ElicitationSlotProps>;
};
```

#78 chenyexuan implements `consent-prompt.tsx` + `elicitation-form.tsx`
that conform to these prop signatures; both call
`decideToolConsent` / `submitElicitation` from the agent-runtime API
client landed in D8.4a. Optional by design — the placeholder
fallback keeps the parts visible even if a slot is not yet wired.

## i18n

Adds to `page_chat.json` (zh-CN + en-US):
* `activity_stream.tool.title` + `activity_stream.tool.state.{input-streaming|input-available|output-available|output-error}`
* `activity_stream.transient.{thinking|searching_knowledge|reading_source|comparing_results|writing_answer|waiting|completed|error}`
* `activity_stream.consent.placeholder_{title,state}`
* `activity_stream.elicitation.placeholder_state`
* `activity_stream.{completed_empty,pending_empty}`
* `answer_section.completed_empty`

## Verification

* `yarn lint` clean.
* `tsc --noEmit` clean for the touched files (the four pre-existing
  errors in `chat-input.tsx` are unrelated and untouched here).
* `yarn dev` boots in 2.8s on port 3012; `GET /`, `/auth/signin`,
  `/workspace/collections`, `/workspace` all return 200.

## Notes

* The EOF-before-terminal regression test follow-up that Weston
  flagged on D8.4a (msg=b7ae3bfd) is not bundled here — there is no
  FE test infra in the repo today, and adding `vitest` is its own
  scope. The behavior is documented at the relevant code paths in
  `stream-client.ts` + `reducer.ts`; recommend adding a dedicated
  test-infra PR after `#77/#78` land.
* No hook contract changes; `useAgentTurnStream` and the
  `AgentMessagePart` typed union are exactly as merged in
  `63a9d522`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…storical reload

Addresses dongdong msg=97336fb9 — terminal historical AI turns reloaded
through `seedFromSnapshot()` were rendering as empty `idle` cards
because `useAgentTurnStream({ streamUrl: null })` keeps `parts: []`
and `status: 'idle'`, and the new renderer no longer reads
`baselineSnapshot.timeline / .artifacts` directly.

Fix scope: read-only synthesis of `AgentMessagePart[]` from the legacy
snapshot's artifacts (answer text → one `text` part; reference bundle
items → `source-url` + `data-citation` parts) when the hook is
dormant for a terminal turn. Backend status is mapped back to the
stream-side enum so the renderer's status branching stays consistent.

Files:
* **NEW** `web/src/features/agent-runtime/snapshot-fallback.ts` —
  `synthesizePartsFromSnapshot()` + `mapBackendTurnStatus()` +
  `isTerminalBackendStatus()` helpers. Read-only, never feeds the
  live reducer; deletes wholesale once the BE snapshot endpoint
  returns UIMessages.
* `chat-messages.tsx` — `AgentTurnStreamCard` falls back to
  synthesized parts + mapped status when `streamUrl == null` and the
  live stream has not produced anything. Live turns are unaffected.
* `features/agent-runtime/index.ts` — re-exports the fallback helpers.

Tool call timeline is intentionally NOT replayed for historical turns —
matches the legacy `agent-turn-card` behaviour, which also did not
show tool-call activity stream once the answer artifact had landed.

Verified: `yarn lint` clean; `tsc --noEmit` clean for touched files;
`yarn dev` boots in 2.6s on port 3013; `GET /`, `/auth/signin`,
`/workspace/collections`, `/workspace` all return 200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
earayu and others added 2 commits April 25, 2026 23:33
…summary handling

Per architect msg=711f8c2f review of the prior `2effca4a` fix:

* File header now explicitly references task **#90 (D8.4d)** as the
  removal trigger — `Bryce` claimed #90 (msg=00230183) to migrate
  the snapshot endpoint to canonical UIMessage parts, after which
  this whole module deletes wholesale.
* Adds `extractErrorTextFromSnapshot()` covering the
  `error_summary` artifact, mapping its payload (`message` /
  `text` / `summary` / artifact-level summary) back into the
  renderer's `errorText` channel. The wire/at-rest contract treats
  `error` as a lifecycle marker (status + errorText), not a part,
  so this stays out of `AgentMessagePart[]`.
* `chat-messages.tsx` `AgentTurnStreamCard` chains
  `extractErrorTextFromSnapshot` ahead of `envelope.error_message`
  in the fallback path so historical FAILED turns surface the
  richer artifact text when present.

Verified: `yarn lint` clean; `tsc --noEmit` clean for the touched
files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…erer rename

CI lint-and-unit failed because `tests/unit_test/test_web_typed_api_contract.py`
hardcoded a path to `web/src/components/chat/agent-turn-card.tsx`,
which #77 deleted in favor of the new `agent-turn-renderer.tsx`.
Swap the path; the same `@/api` / legacy-SDK / FeedbackTagEnum
ban-list applies to the new renderer (which only reaches
`@/features/agent-runtime` + `@/features/bot/types`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@earayu earayu merged commit f035166 into main Apr 25, 2026
4 checks passed
@earayu earayu deleted the fe/d8.4b-parts-renderer branch April 25, 2026 15:55
earayu added a commit that referenced this pull request Apr 25, 2026
* feat(phase8 #77 D8.4b): FE message-parts renderer (text/tool/source/citation/activity)

D8.4b first-cut. Replaces the legacy `AgentTurnCard` + `legacy-snapshot-shim`
projection with a renderer that consumes the new `useAgentTurnStream`
seam (D8.4a, merge `63a9d522`) directly. Each `AgentMessagePart` is
rendered by type; transient `data-activity` is surfaced through a
separate inline indicator and never persisted.

## What lands

* **NEW** `web/src/components/chat/agent-turn-renderer.tsx` — rebuilds
  the activity card from the `parts` stream. Keeps the L1 visual
  baseline (avatar + status badge + activity stream Collapsible +
  answer Card + debug Collapsible + references Sheet + feedback +
  copy) so non-technical users see the same affordance.
  * `<ToolActivityItem>` — one entry per `tool-${SafeToolName}` part;
    state-aware label / icon / debug-expand previews of input + output
    (or errorText on `output-error`).
  * `<ActivityIndicator>` — transient `data-activity` rendered inline
    above the activity stream entries; replaced on each new frame and
    never persisted.
  * `<ConsentPlaceholder>` / `<ElicitationPlaceholder>` — fallback
    rendering for `data-tool-consent` / `data-elicitation` parts when
    no interactive slot is provided. **#78 chenyexuan** plugs in
    concrete components via the new `ConsentSlot` / `ElicitationSlot`
    props on `AgentTurnRendererProps`.
  * References sheet now sources from `source-url` / `source-document`
    parts + `data-citation` content, replacing the old
    `reference_bundle` artifact path.

* `chat-messages.tsx` — `AgentTurnStreamCard` now feeds the hook
  output directly into `AgentTurnRenderer`; the
  `projectToLegacySnapshot` projection layer is gone.

* **DELETE** `web/src/components/chat/agent-turn-card.tsx`
  (1279 LOC) — replaced by the new renderer end-to-end.
* **DELETE** `web/src/features/agent-runtime/legacy-snapshot-shim.ts`
  — its only caller (`AgentTurnStreamCard`) no longer needs the
  projection. `getRunningToolName` / `projectToLegacySnapshot` /
  `LegacySnapshotShim` are dropped from the feature module
  re-exports.

## Slot props (the only seam crossing into #78 territory)

```ts
type ConsentSlotProps = {
  chatId: string;
  turnId: string;
  part: AgentToolConsentPart;
};
type ElicitationSlotProps = {
  chatId: string;
  turnId: string;
  part: AgentElicitationPart;
};

type AgentTurnRendererProps = {
  // ... part stream + status from useAgentTurnStream
  ConsentSlot?: React.ComponentType<ConsentSlotProps>;
  ElicitationSlot?: React.ComponentType<ElicitationSlotProps>;
};
```

#78 chenyexuan implements `consent-prompt.tsx` + `elicitation-form.tsx`
that conform to these prop signatures; both call
`decideToolConsent` / `submitElicitation` from the agent-runtime API
client landed in D8.4a. Optional by design — the placeholder
fallback keeps the parts visible even if a slot is not yet wired.

## i18n

Adds to `page_chat.json` (zh-CN + en-US):
* `activity_stream.tool.title` + `activity_stream.tool.state.{input-streaming|input-available|output-available|output-error}`
* `activity_stream.transient.{thinking|searching_knowledge|reading_source|comparing_results|writing_answer|waiting|completed|error}`
* `activity_stream.consent.placeholder_{title,state}`
* `activity_stream.elicitation.placeholder_state`
* `activity_stream.{completed_empty,pending_empty}`
* `answer_section.completed_empty`

## Verification

* `yarn lint` clean.
* `tsc --noEmit` clean for the touched files (the four pre-existing
  errors in `chat-input.tsx` are unrelated and untouched here).
* `yarn dev` boots in 2.8s on port 3012; `GET /`, `/auth/signin`,
  `/workspace/collections`, `/workspace` all return 200.

## Notes

* The EOF-before-terminal regression test follow-up that Weston
  flagged on D8.4a (msg=b7ae3bfd) is not bundled here — there is no
  FE test infra in the repo today, and adding `vitest` is its own
  scope. The behavior is documented at the relevant code paths in
  `stream-client.ts` + `reducer.ts`; recommend adding a dedicated
  test-infra PR after `#77/#78` land.
* No hook contract changes; `useAgentTurnStream` and the
  `AgentMessagePart` typed union are exactly as merged in
  `63a9d522`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(phase8 #77 D8.4b): synthesize parts from snapshot for terminal historical reload

Addresses dongdong msg=97336fb9 — terminal historical AI turns reloaded
through `seedFromSnapshot()` were rendering as empty `idle` cards
because `useAgentTurnStream({ streamUrl: null })` keeps `parts: []`
and `status: 'idle'`, and the new renderer no longer reads
`baselineSnapshot.timeline / .artifacts` directly.

Fix scope: read-only synthesis of `AgentMessagePart[]` from the legacy
snapshot's artifacts (answer text → one `text` part; reference bundle
items → `source-url` + `data-citation` parts) when the hook is
dormant for a terminal turn. Backend status is mapped back to the
stream-side enum so the renderer's status branching stays consistent.

Files:
* **NEW** `web/src/features/agent-runtime/snapshot-fallback.ts` —
  `synthesizePartsFromSnapshot()` + `mapBackendTurnStatus()` +
  `isTerminalBackendStatus()` helpers. Read-only, never feeds the
  live reducer; deletes wholesale once the BE snapshot endpoint
  returns UIMessages.
* `chat-messages.tsx` — `AgentTurnStreamCard` falls back to
  synthesized parts + mapped status when `streamUrl == null` and the
  live stream has not produced anything. Live turns are unaffected.
* `features/agent-runtime/index.ts` — re-exports the fallback helpers.

Tool call timeline is intentionally NOT replayed for historical turns —
matches the legacy `agent-turn-card` behaviour, which also did not
show tool-call activity stream once the answer artifact had landed.

Verified: `yarn lint` clean; `tsc --noEmit` clean for touched files;
`yarn dev` boots in 2.6s on port 3013; `GET /`, `/auth/signin`,
`/workspace/collections`, `/workspace` all return 200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(phase8 #78 D8.4c): FE interactive consent + elicitation UI

Body components for the `<ConsentSlot>` / `<ElicitationSlot>`
placeholders that #77 (huangheng, PR #1703 head `b532abcd`) reserved
on the parts renderer. Consumes the SDK-compatible slot props
(`{ chatId, turnId, part }`) and wires the user's decision back via
`decideToolConsent()` / `submitElicitation()` from the AI SDK-
compatible client API landed by #76.

## Write set (3 files)

- NEW `web/src/components/chat/consent-prompt.tsx` -- renders one
  `AgentToolConsentPart`. Surfaces only `toolName + argsPreview +
  risk badge` (raw args never reach the FE per #75 backend
  redaction), short fingerprint of `argsHash`, plus Approve / Deny
  buttons. State machine is server-driven: clicking the button calls
  `decideToolConsent(...)` and we wait for the next streamed
  `data-tool-consent` part to flip the visible state -- no local
  optimism. Resolved (approved/denied/expired) parts render a
  compact status row.
- NEW `web/src/components/chat/elicitation-form.tsx` -- renders one
  `AgentElicitationPart`. Generates form fields from the JSON-Schema
  fragment (`string` / `number` / `integer` / `boolean` / `enum`,
  `format: textarea` for multi-line, `default` for initial state,
  `required` for FE-side gating). On submit calls
  `submitElicitation(...)` with coerced payload; on validation error
  we leave the form populated for retry. Resolved (answered /
  cancelled) renders a compact status row.
- MOD `web/src/components/chat/chat-messages.tsx` -- imports both
  components and passes them to `AgentTurnRenderer` as `ConsentSlot`
  / `ElicitationSlot`. Renderer shell + transport hook contract
  untouched.

## D9 §3.1 / §5.1 contract (renderer-side verification)

- consent UI shows only `toolName + argsPreview + risk` -- raw args
  never reach the FE wire (BE-side `args_preview()` redaction per
  #75 + `argsPreview` field on `ToolConsentData` per #74 wrapped
  shape).
- consent decisions go to chat-scoped path
  `/agent/chats/{chat_id}/turns/{turn_id}/consent/{tool_call_id}`
  -- HTTP-layer ownership pre-check + service-layer
  `ConsentOwnershipError` defense-in-depth still apply (per #75).
- elicitation form is schema-validated FE-side as a UX accelerator;
  the BE remains source of truth (`tools/elicitation.py`
  `_required_fields_validator` per #75).
- pending -> approved | denied | expired (consent) and pending ->
  answered | cancelled (elicitation) state transitions are picked
  up from the next streamed part; the visible UI is server-driven.
- Error handling: 403 (ownership) / 404 (not found) / 409 (already
  resolved) / 422 (validation) all surface via `toast.error(...)`;
  the form / prompt stays mounted so the user can retry.

## Boundary discipline

- Does NOT change `transport / hook contract` (per PM lock
  msg=6e521597) -- consumes `useAgentTurnStream` shape unchanged.
- Does NOT change renderer shell (per PM lock msg=4adbf669) --
  only fills the slot bodies via `ConsentSlot` / `ElicitationSlot`
  props.
- Does NOT change schema main design (#74 final shape).

## Built on

- #73 D8.1 wire emitter (cuiwenbo, `51137301`)
- #74 D8.2 at-rest UIMessage storage (Bryce, `e290488b`)
- #75 D8.3 backend tool lifecycle + consent/elicit endpoints + 7-point
  contract enforcement (chenyexuan, `bd4052d5`)
- #76 D8.4a SDK-compatible stream transport + `useAgentTurnStream`
  hook + client API (huangheng, `63a9d522`)
- #77 D8.4b parts renderer + `<ConsentSlot>` / `<ElicitationSlot>`
  seam (huangheng, PR #1703 head `b532abcd`) -- this PR is chained
  on top of `#1703` per PM split-write-set lock msg=4adbf669.

## Gates

- `yarn tsc --noEmit` on changed files: clean (8 pre-existing
  errors on main are unrelated to this diff -- all in
  `chat-input.tsx` / `collection-form.tsx` / `collection-provider.tsx`
  / `app/page.tsx`).
- `yarn lint --quiet`: clean (no warnings/errors).

* refine(phase8 #77 D8.4b): pin TODO(#90) on snapshot-fallback + error_summary handling

Per architect msg=711f8c2f review of the prior `2effca4a` fix:

* File header now explicitly references task **#90 (D8.4d)** as the
  removal trigger — `Bryce` claimed #90 (msg=00230183) to migrate
  the snapshot endpoint to canonical UIMessage parts, after which
  this whole module deletes wholesale.
* Adds `extractErrorTextFromSnapshot()` covering the
  `error_summary` artifact, mapping its payload (`message` /
  `text` / `summary` / artifact-level summary) back into the
  renderer's `errorText` channel. The wire/at-rest contract treats
  `error` as a lifecycle marker (status + errorText), not a part,
  so this stays out of `AgentMessagePart[]`.
* `chat-messages.tsx` `AgentTurnStreamCard` chains
  `extractErrorTextFromSnapshot` ahead of `envelope.error_message`
  in the fallback path so historical FAILED turns surface the
  richer artifact text when present.

Verified: `yarn lint` clean; `tsc --noEmit` clean for the touched
files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(phase8 #78 D8.4c): wire consent + elicitation UI through i18n catalog

Address dongdong B1 blocker on PR #1704 (msg=e86a774b): the new
consent prompt + elicitation form rendered hardcoded English strings
(button labels, toast messages, risk badges, state labels), breaking
the zh-CN visual baseline that #77 already established for the
renderer placeholders.

## What changed

- `web/src/components/chat/consent-prompt.tsx` -- replace hardcoded
  English with `useTranslations('page_chat')`. Risk label looks up
  `activity_stream.consent.risk.{key}`; resolved-state label looks up
  `activity_stream.consent.state_label.{state}`; toast falls back to
  `activity_stream.consent.decision_failed` when the API rejection
  carries no message. Dynamic identifiers (`toolName`, `argsPreview`,
  `argsHash`) stay verbatim per dongdong's guidance.
- `web/src/components/chat/elicitation-form.tsx` -- same pattern:
  `submit` / `submitting` / `submit_failed` / `from_server` /
  `no_schema_fields` / `missing_required` / `invalid_value` /
  `select_placeholder` / `state_label` all routed through i18n.
  Schema field `title` / `description` and the prompt itself stay
  verbatim (BE-controlled identifiers).

## Catalog updates (en-US + zh-CN, both split + merged forms)

Added under `activity_stream.consent` and `activity_stream.elicitation`:

- consent: `approve` / `deny` / `approving` / `denying` /
  `args_fingerprint` / `decision_failed` / `resolved_status` /
  `risk.{writes_user_data, calls_external_api, modifies_system,
  admin_only}` / `state_label.{approved, denied, expired}`
- elicitation: `submit` / `submitting` / `submit_failed` /
  `from_server` / `no_schema_fields` / `missing_required` /
  `invalid_value` / `select_placeholder` / `resolved_status` /
  `state_label.{answered, cancelled}`

The merged `web/src/i18n/{en-US, zh-CN}.json` catalogs and the per-page
`web/src/i18n/{en-US, zh-CN}/page_chat.json` files both got the same
additions so `yarn i18n:sync` regenerates `en-US.d.json.ts` typed
catalog with the new keys.

## Boundary unchanged

- Slot props (`ConsentSlotProps` / `ElicitationSlotProps`) untouched.
- No transport / hook / renderer-shell changes.
- BE contract surface (decideToolConsent / submitElicitation /
  ToolConsentData / ElicitationData) untouched.

## Gates

- `yarn i18n:sync` regenerated typed catalog.
- `yarn tsc --noEmit` on changed files: clean (0 errors in #78
  files; 8 pre-existing main errors unrelated).
- `yarn lint --quiet`: clean (no warnings/errors).

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant