Skip to content

Event routes: expose a canonical per-family payload (tool name/input/response, session id, stop re-entry) beside the raw native envelope #466

Description

@ScriptedAlchemy

Symptom in cargo-hauler

cargo-hauler's four event routes (src/events/**) cannot use the payload the framework hands them without a host-specific bridge. ScriptedAlchemy/cargo-hauler#43 (framework-mode audit, finding 7) records it as a framework gap, not plugin drift: AgentEventRouteProps.native is the raw host envelope and no canonical payload accessor is exported, so the plugin keeps src/lib/event-support.ts to paper over:

  • session id: session_id (Claude/Codex) vs conversation_id (Cursor) — event-support.ts:56, :99
  • tool result: tool_response object (Claude/Codex) vs tool_output JSON string (Cursor) — event-support.ts:69-77
  • stop re-entry: stop_hook_active boolean vs Cursor loop_count number — event-support.ts:87-95
  • tool_name, tool_input, tool_use_id, cwd read by key from nativeevent-support.ts:55-59

Every cross-host plugin with a tool/before, tool/after, or stop route has to rediscover and re-implement the same table.

Root cause in agent-bundle (main 10a98a0fb)

  • packages/agent-bundle/src/routes/public.ts:53AgentEventNativePayload = Readonly<Record<string, unknown>>; :64-68AgentEventRouteProps is { canonical, native, signal } where canonical (:44-50) carries only identity (event, idempotencyKey, observedAt, provenance, sequence), never payload.
  • The framework already knows every per-host field: the wrapper validates the envelope per host and per event (website/docs/en/guide/authoring/hooks.mdx:229 "validates it per host and per event"), the pinned capability tables enumerate the input fields (capabilities/codex-0.147.0.json:812-815, capabilities/cursor-2026-08-28.json:593), and hook-contract.ts:148-149 builds tool_input/tool_name when encoding playground input. Only the read side is left to the route.
  • await agent() gives host/session/actor/workspace as Observed axes, so session identity is partly covered; tool name/input/response and stop re-entry are not.

Workaround currently in cargo-hauler

src/lib/event-support.ts (whole module): beforeShellEventFrom, afterShellEventFrom, stopHoldEventFrom, hookContextFrom, consumed by src/events/tool/before.tsx, src/events/tool/after.tsx, src/events/stop.tsx, src/events/session/start.tsx.

Proposed fix

Add a typed, per-family canonical payload next to native (keep native for anything not modelled):

interface AgentEventRouteProps<F extends CanonicalAgentEvent = CanonicalAgentEvent> {
  readonly canonical: AgentEventCanonicalIdentity;
  readonly payload: CanonicalEventPayload[F];   // new
  readonly native: AgentEventNativePayload;
  readonly signal: AbortSignal;
}
// tool/before: { toolName, toolInput: JsonValue, toolUseId?, cwd? }
// tool/after:  tool/before + { toolResponse: JsonValue }  (Cursor's string parsed when it is JSON, else kept as string)
// stop / agent/stop: { reentry: boolean }  (stop_hook_active || loop_count > 0)
// session/start: { source? }
  • Derived in the generated wrapper from the already-validated envelope, using the per-host tables the adapters own; fields a host does not report are typed Observed/optional, never fabricated (same rule as canonical).
  • Surfaced in the Workbench playground and agent-bundle/test runEventRoute so fixtures exercise payload, not raw keys.
  • Docs: website/docs/{en,zh}/guide/authoring/hooks.mdx route props section; generated events reference unchanged.

Acceptance

  • A tool/before route reading only payload.toolName / payload.toolInput behaves identically under the checked-in Claude, Codex, and Cursor native fixtures.
  • A stop route reading payload.reentry sees true for Claude/Codex stop_hook_active: true and Cursor loop_count: 1.
  • cargo-hauler can delete src/lib/event-support.ts (except decisionValue) once pinned to the fix.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthost-integrationAgent host capabilities, projection, discovery, and lifecycle

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions