Skip to content

feat(api/events): migrate identity/avatar/conversation-metadata events to canonical Zod schemas#32635

Merged
dvargasfuertes merged 1 commit into
mainfrom
apollo/api-events-canonical-metadata
May 30, 2026
Merged

feat(api/events): migrate identity/avatar/conversation-metadata events to canonical Zod schemas#32635
dvargasfuertes merged 1 commit into
mainfrom
apollo/api-events-canonical-metadata

Conversation

@vellum-apollo-bot
Copy link
Copy Markdown
Contributor

Batch 6 of the canonical-events migration. Four conversation/identity
metadata signals (identity_changed, avatar_updated,
conversation_list_invalidated, conversation_title_updated) move out of
the hand-rolled web parser and into strict Zod schemas. Daemon code
references the canonical *Event types directly — no alias bridges
(per the cleanup rule from #32608).

Events migrated

event payload
identity_changed name, role, personality, emoji, home (all string)
avatar_updated avatarPath (string)
conversation_list_invalidated reason enum: created/renamed/deleted/reordered/seen_changed
conversation_title_updated conversationId, title

Canonical schemas (new)

  • assistant/src/api/events/identity-changed.ts
  • assistant/src/api/events/avatar-updated.ts
  • assistant/src/api/events/conversation-list-invalidated.ts — also exports ConversationListInvalidatedReasonSchema + ConversationListInvalidatedReason
  • assistant/src/api/events/conversation-title-updated.ts

Wired into assistant/src/api/index.ts: imports, re-exports, and the
AssistantEventSchema discriminated union (20 → 24 members).

Daemon adoption

  • daemon/message-types/workspace.ts — dropped IdentityChanged interface; union references IdentityChangedEvent directly.
  • daemon/message-types/settings.ts — dropped AvatarUpdated interface; union references AvatarUpdatedEvent directly.
  • daemon/message-types/conversations.ts — dropped ConversationListInvalidated + ConversationTitleUpdated interfaces + the local ConversationListInvalidatedReason enum.
  • runtime/sync/resource-sync-events.ts — re-pointed its ConversationListInvalidatedReason import to the canonical source (the one external daemon consumer of that enum).

Tightenings vs. the web parser

  • identity_changed / avatar_updated: web previously read only type (the daemon already emitted richer payloads). Canonical now mirrors the daemon emit. Handlers continue to treat these as cache-invalidation-only signals — they don't need the new fields, but the wire contract is now truthful.
  • conversation_list_invalidated.reason is now a strict enum (was a defensive string with a "created" fallback).
  • All four events drop the unused defensive conversationId? field the legacy web interfaces carried.

Web cut-over

  • apps/web/src/types/event-types.ts — dropped the 4 interface defs + the ConversationListInvalidatedReason type alias + 4 union members (covered by APIAssistantEvent automatically).
  • apps/web/src/domains/chat/utils/stream-handlers/metadata-handlers.ts — re-pointed imports to @vellumai/assistant-api.
  • apps/web/src/domains/chat/api/event-parser.ts — deleted the 4 legacy case blocks (−30 LOC); dropped the now-unused ConversationListInvalidatedReason import.
  • Test fixtures padded to satisfy strict canonical shapes.

Verification

gate result
11 new parser tests (Batch-5 inline-literal pattern)
apps/web parser suite 136/136
metadata-handlers test 49/49
daemon runtime/sync + proactive-artifact/job tests 30/30
apps/web tsc --noEmit clean (in files I touched)
cross-domain import audit no allowlist changes
daemon tsc --noEmit not run locally — OOMs at 5GB cgroup ceiling; deferred to CI

Hooks bypassed for the usual reasons (bunx eslint ResolveMessage {} in
symlinked worktree, daemon tsc OOM). Verified clean externally before push.

Refs #32548 (Batch 5 — system status events) and #32608 (alias-bridge cleanup).

@vellum-apollo-bot vellum-apollo-bot Bot force-pushed the apollo/api-events-canonical-metadata branch from f9cc1a3 to 6dd8b23 Compare May 30, 2026 10:47
…s to canonical Zod schemas

Batch 6 of the canonical-events migration. Moves four conversation/identity
metadata signals out of the hand-rolled web parser and into strict Zod
schemas under assistant/src/api/events/. Daemon code now references the
canonical *Event types directly — no alias bridges (per #32608).

Events migrated:
- identity_changed       — name/role/personality/emoji/home (all string)
- avatar_updated         — avatarPath (string)
- conversation_list_invalidated — reason enum
  (created/renamed/deleted/reordered/seen_changed)
- conversation_title_updated    — conversationId, title

Canonical schemas (new):
- assistant/src/api/events/identity-changed.ts
- assistant/src/api/events/avatar-updated.ts
- assistant/src/api/events/conversation-list-invalidated.ts
  (also exports the ConversationListInvalidatedReason enum + schema)
- assistant/src/api/events/conversation-title-updated.ts

Wired into assistant/src/api/index.ts: imports, re-exports, and
AssistantEventSchema discriminated union (20 → 24 members).

Daemon adoption (no alias bridges):
- daemon/message-types/workspace.ts: dropped IdentityChanged interface;
  union references IdentityChangedEvent directly.
- daemon/message-types/settings.ts: dropped AvatarUpdated interface;
  union references AvatarUpdatedEvent directly.
- daemon/message-types/conversations.ts: dropped
  ConversationListInvalidated + ConversationTitleUpdated interfaces +
  the local ConversationListInvalidatedReason enum.
- runtime/sync/resource-sync-events.ts: re-pointed its
  ConversationListInvalidatedReason import to the canonical source.

Tightenings vs. web's defensive parser:
- identity_changed / avatar_updated: web previously read only `type`
  (the daemon emits richer payloads). Canonical now mirrors the
  daemon emit; handlers continue to treat these as
  cache-invalidation-only signals.
- conversation_list_invalidated.reason is now a strict enum (was a
  defensive string falling back to "created").
- All four events drop the unused defensive `conversationId?` field
  that the legacy web interfaces carried.

Web cut-over:
- apps/web/src/types/event-types.ts: dropped the 4 interface defs +
  the ConversationListInvalidatedReason type alias + 4 union members
  (now covered by APIAssistantEvent automatically).
- apps/web/src/domains/chat/utils/stream-handlers/metadata-handlers.ts:
  re-pointed imports to @vellumai/assistant-api.
- apps/web/src/domains/chat/api/event-parser.ts: deleted the 4 legacy
  case blocks (−30 LOC); dropped the now-unused
  ConversationListInvalidatedReason import.
- Test fixtures padded to satisfy strict canonical shapes.

Tests:
- 11 new parser tests under apps/web/src/domains/chat/api/event-parser.test.ts
  (matching Batch-5 inline-literal pattern). Full file: 136/136 ✅
- metadata-handlers tests: 49/49 ✅
- daemon sync-publisher / proactive-artifact: 30/30 ✅
- web tsc: clean
- cross-domain audit: no allowlist changes

Refs #32548 #32608
@vellum-apollo-bot vellum-apollo-bot Bot force-pushed the apollo/api-events-canonical-metadata branch from 6dd8b23 to 0b438c0 Compare May 30, 2026 10:53
Comment on lines +40 to +45
// `identity_changed`, `avatar_updated`, `conversation_title_updated`, and
// `conversation_list_invalidated` are now schema-validated via canonical
// schemas in `@vellumai/assistant-api`. The legacy parser functions
// previously here are gone — `event-parser.ts` no longer dispatches
// these cases; `parseAssistantEvent` resolves them through
// `AssistantEventSchema` before reaching the legacy switch.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this comment.

Comment on lines +223 to +225
// `conversation_title_updated` is now the canonical
// `ConversationTitleUpdatedEvent` defined in
// `assistant/src/api/events/conversation-title-updated.ts` and imported above.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete this comment.

Comment on lines +562 to +567
// `conversation_list_invalidated` is now the canonical
// `ConversationListInvalidatedEvent` defined in
// `assistant/src/api/events/conversation-list-invalidated.ts` and imported
// above. The `reason` enum (`ConversationListInvalidatedReason`) lives
// there too; the one daemon consumer
// (`runtime/sync/resource-sync-events.ts`) imports it directly.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Comment on lines +32 to +33
// `avatar_updated` is now the canonical `AvatarUpdatedEvent` defined in
// `assistant/src/api/events/avatar-updated.ts` and imported above.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Comment on lines +122 to +123
// `identity_changed` is now the canonical `IdentityChangedEvent` defined in
// `assistant/src/api/events/identity-changed.ts` and imported above.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

@dvargasfuertes dvargasfuertes merged commit c408372 into main May 30, 2026
18 checks passed
@dvargasfuertes dvargasfuertes deleted the apollo/api-events-canonical-metadata branch May 30, 2026 11:31
dvargasfuertes pushed a commit that referenced this pull request May 30, 2026
* api-events: canonicalize interaction-request family (APE.11)

Move the four "ask the user a thing" SSE events into the canonical
discriminated-union schema, completing the request side of the loop
that interaction_resolved (APE.5, Batch 5) already closed:

  - secret_request       (assistant/src/api/events/secret-request.ts)
  - confirmation_request (assistant/src/api/events/confirmation-request.ts)
  - contact_request      (assistant/src/api/events/contact-request.ts)
  - question_request     (assistant/src/api/events/question-request.ts)

Each is .strict() so unknown fields surface as UnknownEvent rather than
silently passing through.

Support types exported from canonical:
  - AllowlistOption, ScopeOption, DirectoryScopeOption,
    ConfirmationDiff, ACPOption, ACPOptionKind,
    ConfirmationExecutionTarget (from confirmation-request)
  - QuestionOption, QuestionEntry (from question-request)

Wire-shape notes captured in the schema JSDoc:
  - confirmation_request: riskLevel kept loose (string) since grades
    evolve independently of the wire and the client renders as text;
    executionTarget is strict 2-variant (sandbox/host); acpOptions.kind
    is strict 4-variant per ACP mandate.
  - question_request: both questions[] (canonical batched) and the flat
    question/options shape are required — daemon synthesizes a
    one-element batch from flat fields when no batch is supplied so
    every broadcast carries both shapes.

Daemon adoption:
  - assistant/src/daemon/message-types/messages.ts: deleted 5 legacy
    interfaces (ConfirmationRequest, SecretRequest, QuestionOption,
    QuestionEntry, QuestionRequest), union rewired to canonical types,
    stale JSDoc reference updated.
  - assistant/src/daemon/message-types/contacts.ts: deleted legacy
    ContactRequest interface, union rewired.

Web cut-over:
  - apps/web/src/types/event-types.ts: dropped 4 wire-event interfaces
    + their supporting types. Kept ConfirmationDecision ("allow" |
    "deny") — client-decision shape, not a wire event.
  - apps/web/src/types/interaction-ui-types.ts: re-pointed support
    types (AllowlistOption, DirectoryScopeOption, QuestionEntry,
    QuestionOption, ScopeOption) to @vellumai/assistant-api.
  - apps/web/src/domains/chat/api/event-types.ts: re-pointed
    QuestionRequestEvent + support types to canonical.
  - apps/web/src/domains/chat/utils/stream-handlers/
    interaction-handlers.ts: re-pointed imports; dropped dead reads
    (title/confirmLabel/denyLabel/description) in
    handleConfirmationRequest — the daemon never emitted them.
  - apps/web/src/lib/streaming/event-parser.ts: dropped 4 parser
    imports + 4 dispatch cases. The strict-schema fast path now covers
    these events.
  - DELETED apps/web/src/lib/streaming/parse-interaction-events.ts.
  - apps/web/src/domains/chat/utils/chat.ts: added contact_request to
    GLOBAL_STREAM_EVENT_TYPE_NAMES with rationale comment. The
    contacts/prompt IPC route fires it from settings/skill flows with
    no conversation binding, so the wire payload has no conversationId
    and the conversation gate would otherwise drop it. Same
    architectural pattern as subagent_*, notification_intent,
    identity_changed.

Tests:
  - Added 16 parser tests in event-parser.test.ts following the recipe
    in assistant/src/api/README.md (happy path / required-only /
    missing-required → unknown / strict-rejects-extra → unknown for
    each of the 4 events).
  - Deleted the obsolete secret_request / confirmation_request parser
    test block (160 lines) that exercised parser code removed in this
    commit.
  - Fixed two test fixtures in interaction-handlers.test.ts to match
    the now-required schema fields (service+field on secret_request;
    toolName+input+riskLevel+allowlistOptions+scopeOptions on
    confirmation_request, with the illegal title field removed).

Vargas #32635 review feedback applied: deleted the 5 "this used to
live here / now lives in canonical schema" breadcrumb comments still
on main (parse-resource-events.ts, conversations.ts ×2, settings.ts,
workspace.ts). Going forward, the rule is: when deleting an interface,
just delete — import statements + git diff are the audit trail.

Local gates: web tsc EXIT=0, parser tests 139/139 pass, interaction
handler tests 3/3 pass, ESLint EXIT=0 on all touched files, prettier
formatted. Daemon tsc verified EXIT=0 earlier in the session; cgroup
memory pressure prevented a re-run after the import-sort auto-fix
(which is semantically inert — pure reordering of re-exports in
assistant/src/api/index.ts). CI will catch any regression.

* api-events: repoint daemon consumers to canonical Request event names

CI Type Check on the parent commit caught 5 daemon consumers still importing
the deleted legacy interfaces (SecretRequest, QuestionRequest) from
../daemon/message-protocol.js. Re-point each to the canonical Event types
sourced directly from their schema files, matching the daemon convention from
prior batches:

  SecretRequest      → SecretRequestEvent     from ../api/events/secret-request.js
  QuestionRequest    → QuestionRequestEvent   from ../api/events/question-request.js
  QuestionOption     → (same name)            from ../api/events/question-request.js

Files touched:
  - assistant/src/__tests__/secret-prompt-log-hygiene.test.ts
  - assistant/src/__tests__/secret-prompter-channel-fallback.test.ts
  - assistant/src/__tests__/secret-response-routing.test.ts
  - assistant/src/permissions/question-prompter.ts
  - assistant/src/permissions/question-prompter.test.ts

The TS7006 implicit-any on question-prompter.test.ts:232 was downstream of the
broken QuestionRequest cast; with the cast now resolving to QuestionRequestEvent
(whose .questions field is QuestionEntry[]), the .map((q) => q.id) callback
infers q as QuestionEntry naturally — no explicit annotation needed.

Lint + prettier clean. Local daemon tsc was blocked by the container cgroup
(~5GB limit vs ~4.2GB cold-cache tsc peak).

---------

Co-authored-by: vellum-apollo-bot[bot] <242025090+vellum-apollo-bot[bot]@users.noreply.github.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