+Give every event route a canonical, per-family `canonical.payload` beside the raw `native` envelope (`AgentEventRouteProps<E>`): the fields at least two hosts report — `toolName`, `toolInput`, `toolUseId`, `toolResponse`, `sessionId` (Claude and Codex `session_id`, Cursor `conversation_id`), `transcriptPath`, `cwd`, `model`, `permissionMode`, `agentId`/`agentType`, `agentTranscriptPath`, `prompt`, `reason`, `source`, `trigger`, `error`/`isInterrupt`, `lastAssistantMessage`, and `reentry` (Claude and Codex `stop_hook_active`, Cursor `loop_count > 0`) — each delivered as `{ value, nativeKey }` naming the host key it was read from, and absent when the host did not send it, never fabricated. Cursor's `tool_output` JSON string is parsed into `toolResponse` (kept as the string when it is not valid JSON); the Claude-only `model-switch/before` and `model-switch/after` families admitted by the 2.1.260 re-pin carry `fromModel`, `toModel`, `requestedModel`, and `source`. Where a family is also a config hook handler event, `canonical.payload` uses the same field names as `HookEvent<E>` (`reentry` is the one renaming of `stopHookActive`). Type a route to its family (`AgentEventRouteProps<'tool/after'>`) and `payload` narrows to that family's fields, in the route and in the generated `.agent-bundle/routes.d.ts` that `renderRoute` reads; `AgentEventCanonicalIdentity<E>` gains the same parameter. The per-family table ships as `agentEventPayloadFields`, the per-host key table as `agentEventPayloadNativeKeys` (with `AgentEventPayload`, `AgentEventPayloadField`, `AgentEventPayloadFieldName`, `AgentEventPayloadNativeKey`, and `agentEventPayloadFieldKinds`), and each pinned capability table mirrors its host's mapping under `hooks.eventRoutes.<event>.payload`, so the generated events reference documents field × host → native key per family. `agent-bundle/test` gains `createEventRouteInput(event, native, { host })`, which validates a host envelope and builds the `{ canonical, native }` input the harness takes, payload included; the Workbench Lifecycles view lists the mapped payload beside the canonical identity. `native` is unchanged, `idempotencyKey` still hashes only the envelope, and a route typed with the bare `AgentEventRouteProps` keeps working with every field optional. Breaking for one shape of consumer code: `payload` is a required property of `AgentEventCanonicalIdentity`, so a test or harness that constructs the identity by hand no longer compiles until it adds one — build the input with `createEventRouteInput` instead. (#466)
0 commit comments