@gajae-code/coding-agent 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/types/async/job-manager.d.ts +44 -1
  3. package/dist/types/cli/setup-cli.d.ts +14 -1
  4. package/dist/types/commands/coordinator.d.ts +19 -0
  5. package/dist/types/commands/mcp-serve.d.ts +24 -0
  6. package/dist/types/commands/setup.d.ts +41 -0
  7. package/dist/types/commit/model-selection.d.ts +1 -1
  8. package/dist/types/config/model-registry.d.ts +3 -1
  9. package/dist/types/config/model-resolver.d.ts +1 -19
  10. package/dist/types/config/models-config-schema.d.ts +12 -0
  11. package/dist/types/config/settings-schema.d.ts +15 -1
  12. package/dist/types/coordinator/contract.d.ts +4 -0
  13. package/dist/types/coordinator-mcp/policy.d.ts +24 -0
  14. package/dist/types/coordinator-mcp/safety.d.ts +26 -0
  15. package/dist/types/coordinator-mcp/server.d.ts +52 -0
  16. package/dist/types/extensibility/extensions/types.d.ts +13 -0
  17. package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
  18. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
  19. package/dist/types/harness-control-plane/types.d.ts +7 -2
  20. package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +7 -0
  22. package/dist/types/modes/components/hook-selector.d.ts +11 -0
  23. package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
  24. package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
  25. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
  26. package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
  27. package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
  28. package/dist/types/session/agent-session.d.ts +12 -1
  29. package/dist/types/session/session-manager.d.ts +1 -1
  30. package/dist/types/setup/hermes-setup.d.ts +71 -0
  31. package/dist/types/task/render.d.ts +7 -1
  32. package/dist/types/tools/bash.d.ts +2 -0
  33. package/dist/types/tools/browser/actions.d.ts +54 -0
  34. package/dist/types/tools/browser.d.ts +80 -0
  35. package/dist/types/tools/image-gen.d.ts +1 -0
  36. package/dist/types/tools/index.d.ts +3 -1
  37. package/dist/types/tools/job.d.ts +1 -1
  38. package/dist/types/tools/subagent-render.d.ts +25 -0
  39. package/dist/types/tools/subagent.d.ts +5 -1
  40. package/package.json +7 -7
  41. package/src/async/job-manager.ts +163 -2
  42. package/src/cli/setup-cli.ts +86 -2
  43. package/src/cli.ts +2 -0
  44. package/src/commands/coordinator.ts +70 -0
  45. package/src/commands/mcp-serve.ts +62 -0
  46. package/src/commands/setup.ts +30 -1
  47. package/src/commands/ultragoal.ts +7 -1
  48. package/src/commit/agentic/index.ts +2 -2
  49. package/src/commit/model-selection.ts +7 -22
  50. package/src/commit/pipeline.ts +2 -2
  51. package/src/config/model-registry.ts +17 -9
  52. package/src/config/model-resolver.ts +14 -84
  53. package/src/config/models-config-schema.ts +2 -0
  54. package/src/config/settings-schema.ts +14 -1
  55. package/src/coordinator/contract.ts +20 -0
  56. package/src/coordinator-mcp/policy.ts +160 -0
  57. package/src/coordinator-mcp/safety.ts +80 -0
  58. package/src/coordinator-mcp/server.ts +1316 -0
  59. package/src/extensibility/extensions/types.ts +13 -0
  60. package/src/gjc-runtime/goal-mode-request.ts +21 -1
  61. package/src/gjc-runtime/session-state-sidecar.ts +79 -0
  62. package/src/harness-control-plane/owner.ts +3 -3
  63. package/src/harness-control-plane/rpc-adapter.ts +7 -1
  64. package/src/harness-control-plane/types.ts +8 -11
  65. package/src/internal-urls/docs-index.generated.ts +6 -5
  66. package/src/memories/index.ts +1 -1
  67. package/src/modes/acp/acp-agent.ts +17 -9
  68. package/src/modes/acp/acp-event-mapper.ts +33 -1
  69. package/src/modes/components/custom-editor.ts +19 -3
  70. package/src/modes/components/hook-selector.ts +109 -5
  71. package/src/modes/controllers/extension-ui-controller.ts +16 -1
  72. package/src/modes/controllers/input-controller.ts +27 -7
  73. package/src/modes/controllers/selector-controller.ts +7 -1
  74. package/src/modes/interactive-mode.ts +3 -1
  75. package/src/modes/rpc/rpc-client.ts +16 -3
  76. package/src/modes/rpc/rpc-mode.ts +5 -2
  77. package/src/modes/shared/agent-wire/command-contract.ts +18 -0
  78. package/src/modes/shared/agent-wire/event-contract.ts +147 -0
  79. package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
  80. package/src/modes/shared/agent-wire/event-observation.ts +397 -0
  81. package/src/modes/shared/agent-wire/protocol.ts +24 -81
  82. package/src/modes/utils/context-usage.ts +2 -2
  83. package/src/prompts/agents/architect.md +6 -0
  84. package/src/prompts/agents/critic.md +6 -0
  85. package/src/prompts/agents/explore.md +1 -1
  86. package/src/prompts/agents/plan.md +1 -1
  87. package/src/prompts/agents/planner.md +8 -1
  88. package/src/prompts/agents/reviewer.md +1 -1
  89. package/src/prompts/tools/browser.md +3 -2
  90. package/src/runtime-mcp/manager.ts +15 -2
  91. package/src/sdk.ts +3 -1
  92. package/src/session/agent-session.ts +66 -4
  93. package/src/session/session-manager.ts +1 -1
  94. package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
  95. package/src/setup/hermes-setup.ts +429 -0
  96. package/src/task/agents.ts +1 -1
  97. package/src/task/index.ts +2 -0
  98. package/src/task/render.ts +14 -0
  99. package/src/tools/ask.ts +30 -10
  100. package/src/tools/bash.ts +6 -1
  101. package/src/tools/browser/actions.ts +189 -0
  102. package/src/tools/browser.ts +91 -1
  103. package/src/tools/image-gen.ts +42 -15
  104. package/src/tools/index.ts +7 -1
  105. package/src/tools/inspect-image.ts +10 -8
  106. package/src/tools/job.ts +12 -2
  107. package/src/tools/monitor.ts +98 -17
  108. package/src/tools/renderers.ts +2 -0
  109. package/src/tools/subagent-render.ts +160 -0
  110. package/src/tools/subagent.ts +49 -7
  111. package/src/utils/commit-message-generator.ts +6 -13
  112. package/src/utils/title-generator.ts +1 -1
  113. package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
  114. package/src/harness-control-plane/frame-mapper.ts +0 -286
  115. package/src/priority.json +0 -37
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Canonical agent-wire contract: the single transport-neutral source of truth
3
+ * for AgentSession events and bounded owner observations.
4
+ *
5
+ * Two distinct consumer-facing shapes, deliberately NOT collapsed into one:
6
+ * - `AgentWireEventPayload`: rich, full `AgentSessionEvent` for renderers
7
+ * (ACP SDK notifications, RPC/Bridge event frames).
8
+ * - `AgentWireOwnerObservation`: bounded/redacted owner evidence for control
9
+ * planes (Harness). Never carries assistant text, message deltas, raw tool
10
+ * args, raw command output, raw tool results, answers, or oversize strings.
11
+ *
12
+ * The exhaustive `AGENT_SESSION_EVENT_TYPE_REGISTRY` lives here so that adding
13
+ * an `AgentSessionEvent` variant fails typecheck until it is registered, and so
14
+ * conformance tests can assert fixture coverage equals the registry exactly.
15
+ */
16
+ import type { AgentSessionEvent } from "../../../session/agent-session";
17
+
18
+ /** Wire protocol version. Bump on breaking envelope/semantic changes. */
19
+ export const AGENT_WIRE_PROTOCOL_VERSION = 2 as const;
20
+ export type AgentWireProtocolVersion = typeof AGENT_WIRE_PROTOCOL_VERSION;
21
+
22
+ /** The discriminant of every `AgentSessionEvent` the agent can emit. */
23
+ export type AgentWireEventType = AgentSessionEvent["type"];
24
+
25
+ /**
26
+ * Compile-time exhaustive registry of every `AgentSessionEvent` variant. The
27
+ * `Record<AgentWireEventType, true>` shape forces every member to be present:
28
+ * a new union variant is a type error until added here, and a removed variant
29
+ * is a type error until deleted.
30
+ */
31
+ const AGENT_SESSION_EVENT_TYPE_REGISTRY: Record<AgentWireEventType, true> = {
32
+ agent_start: true,
33
+ agent_end: true,
34
+ turn_start: true,
35
+ turn_end: true,
36
+ message_start: true,
37
+ message_update: true,
38
+ message_end: true,
39
+ tool_execution_start: true,
40
+ tool_execution_update: true,
41
+ tool_execution_end: true,
42
+ auto_compaction_start: true,
43
+ auto_compaction_end: true,
44
+ auto_retry_start: true,
45
+ auto_retry_end: true,
46
+ retry_fallback_applied: true,
47
+ retry_fallback_succeeded: true,
48
+ ttsr_triggered: true,
49
+ todo_reminder: true,
50
+ todo_auto_clear: true,
51
+ irc_message: true,
52
+ notice: true,
53
+ thinking_level_changed: true,
54
+ goal_updated: true,
55
+ };
56
+
57
+ /** Every agent-session event type, derived from the exhaustive registry. */
58
+ export const AGENT_WIRE_EVENT_TYPES: readonly AgentWireEventType[] = Object.keys(
59
+ AGENT_SESSION_EVENT_TYPE_REGISTRY,
60
+ ) as AgentWireEventType[];
61
+
62
+ /**
63
+ * Rich event payload. Carries the full `AgentSessionEvent` so renderers (ACP,
64
+ * RPC, Bridge) can present message content, tool args/results, todo state, etc.
65
+ */
66
+ export interface AgentWireEventPayload {
67
+ event_type: AgentWireEventType;
68
+ event: AgentSessionEvent;
69
+ }
70
+
71
+ /**
72
+ * Bounded observed-signal vocabulary surfaced to owner control planes. Mirrors
73
+ * the Harness `ObservedSignal` set; the Harness type aliases this in a later
74
+ * step so there is a single source of truth.
75
+ */
76
+ export type AgentWireObservedSignal =
77
+ | "SessionStart"
78
+ | "prompt-accepted"
79
+ | "tool-call"
80
+ | "test-running"
81
+ | "commit-created"
82
+ | "completed"
83
+ | "error"
84
+ | "streaming"
85
+ | "idle";
86
+
87
+ export type AgentWireSeverity = "info" | "warn" | "critical";
88
+
89
+ /**
90
+ * Bounded, redacted owner observation. Evidence may include ids, names,
91
+ * categories, statuses, cursors, timestamps, short codes, and bounded short
92
+ * messages ONLY. It must never carry assistant text, message deltas, raw tool
93
+ * args, raw command output, raw tool result content, answers, or oversize
94
+ * strings.
95
+ */
96
+ export interface AgentWireOwnerObservation {
97
+ /** Set when this observation derives from an `AgentSessionEvent`. */
98
+ eventType?: AgentWireEventType;
99
+ /** Set when this observation derives from a non-event wire frame. */
100
+ frameType?: string;
101
+ /** Owner event kind (e.g. `rpc_tool_started`). */
102
+ kind: string;
103
+ /** Bounded observed signal, or null when the frame carries no signal. */
104
+ signal: AgentWireObservedSignal | null;
105
+ /** Bounded evidence — ids/names/statuses/cursors/timestamps/short codes only. */
106
+ evidence: Record<string, unknown>;
107
+ /** Severity for the emitted event. */
108
+ severity: AgentWireSeverity;
109
+ /** Never-drop observations: must be enqueued in order, never coalesced away. */
110
+ semantic: boolean;
111
+ /** Coalescing key for high-frequency non-semantic frames; null otherwise. */
112
+ coalesceKey: string | null;
113
+ }
114
+
115
+ /** Top-level frame categories carried over any agent-wire transport. */
116
+ export type AgentWireFrameType =
117
+ | "ready"
118
+ | "event"
119
+ | "response"
120
+ | "ui_request"
121
+ | "permission_request"
122
+ | "host_tool_call"
123
+ | "host_uri_request"
124
+ | "reset"
125
+ | "workflow_gate"
126
+ | "error";
127
+
128
+ /**
129
+ * Universal frame envelope. Every frame on every transport carries these
130
+ * fields so clients can order (`seq`), resume (`seq` cursor), and correlate
131
+ * request/response pairs (`correlation_id`).
132
+ */
133
+ export interface AgentWireFrameEnvelope<TType extends AgentWireFrameType = AgentWireFrameType, TPayload = unknown> {
134
+ protocol_version: AgentWireProtocolVersion;
135
+ session_id: string;
136
+ /** Monotonic per-session sequence number, starting at 1. */
137
+ seq: number;
138
+ /** Unique id for this frame. */
139
+ frame_id: string;
140
+ /** Ties a request frame to its response frame, when applicable. */
141
+ correlation_id?: string;
142
+ type: TType;
143
+ payload: TPayload;
144
+ }
145
+
146
+ /** An `AgentSessionEvent` serialized into a versioned wire frame. */
147
+ export type AgentWireEventFrame = AgentWireFrameEnvelope<"event", AgentWireEventPayload>;
@@ -1,19 +1,23 @@
1
1
  /**
2
- * Serialize `AgentSessionEvent`s into versioned bridge wire frames.
2
+ * Serialize `AgentSessionEvent`s into versioned agent-wire frames.
3
3
  *
4
4
  * The mapping is intentionally exhaustive: `agentSessionEventType` switches over
5
5
  * every variant of the event union and calls `assertNever` in the default arm,
6
6
  * so a newly added event variant fails to compile until it is handled here.
7
+ *
8
+ * The canonical sequencer + frame builders live here; the historical `Bridge*`
9
+ * names are retained as thin aliases for callers that have not yet migrated.
7
10
  */
8
11
  import { randomUUID } from "node:crypto";
9
12
  import type { AgentSessionEvent } from "../../../session/agent-session";
10
13
  import {
11
- type AgentSessionEventType,
12
- BRIDGE_PROTOCOL_VERSION,
13
- type BridgeEventFrame,
14
- type BridgeFrameEnvelope,
15
- type BridgeFrameType,
16
- } from "./protocol";
14
+ AGENT_WIRE_PROTOCOL_VERSION,
15
+ type AgentWireEventFrame,
16
+ type AgentWireEventPayload,
17
+ type AgentWireEventType,
18
+ type AgentWireFrameEnvelope,
19
+ type AgentWireFrameType,
20
+ } from "./event-contract";
17
21
 
18
22
  function assertNever(value: never): never {
19
23
  throw new Error(`Unhandled AgentSessionEvent variant: ${JSON.stringify(value)}`);
@@ -24,7 +28,7 @@ function assertNever(value: never): never {
24
28
  *
25
29
  * Exhaustive over the union; adding a variant without a case is a type error.
26
30
  */
27
- export function agentSessionEventType(event: AgentSessionEvent): AgentSessionEventType {
31
+ export function agentSessionEventType(event: AgentSessionEvent): AgentWireEventType {
28
32
  switch (event.type) {
29
33
  case "agent_start":
30
34
  case "agent_end":
@@ -59,7 +63,7 @@ export function agentSessionEventType(event: AgentSessionEvent): AgentSessionEve
59
63
  * Per-session monotonic frame builder. One instance per active session; `seq`
60
64
  * starts at 1 and increments per frame so clients can order and resume.
61
65
  */
62
- export class BridgeFrameSequencer {
66
+ export class AgentWireFrameSequencer {
63
67
  readonly #sessionId: string;
64
68
  #seq = 0;
65
69
 
@@ -78,14 +82,14 @@ export class BridgeFrameSequencer {
78
82
  }
79
83
 
80
84
  /** Build the next envelope of the given type with a fresh seq + frame id. */
81
- next<TType extends BridgeFrameType, TPayload>(
85
+ next<TType extends AgentWireFrameType, TPayload>(
82
86
  type: TType,
83
87
  payload: TPayload,
84
88
  correlationId?: string,
85
- ): BridgeFrameEnvelope<TType, TPayload> {
89
+ ): AgentWireFrameEnvelope<TType, TPayload> {
86
90
  this.#seq += 1;
87
- const frame: BridgeFrameEnvelope<TType, TPayload> = {
88
- protocol_version: BRIDGE_PROTOCOL_VERSION,
91
+ const frame: AgentWireFrameEnvelope<TType, TPayload> = {
92
+ protocol_version: AGENT_WIRE_PROTOCOL_VERSION,
89
93
  session_id: this.#sessionId,
90
94
  seq: this.#seq,
91
95
  frame_id: randomUUID(),
@@ -99,14 +103,29 @@ export class BridgeFrameSequencer {
99
103
  }
100
104
  }
101
105
 
102
- /** Serialize a single `AgentSessionEvent` into an `event` wire frame. */
103
- export function toBridgeEventFrame(event: AgentSessionEvent, sequencer: BridgeFrameSequencer): BridgeEventFrame {
106
+ /** Back-compat alias for {@link AgentWireFrameSequencer}. */
107
+ export const BridgeFrameSequencer = AgentWireFrameSequencer;
108
+ export type BridgeFrameSequencer = AgentWireFrameSequencer;
109
+
110
+ /** Serialize a single `AgentSessionEvent` into a canonical `event` wire frame. */
111
+ export function toAgentWireEventFrame(
112
+ event: AgentSessionEvent,
113
+ sequencer: AgentWireFrameSequencer,
114
+ ): AgentWireEventFrame {
104
115
  return sequencer.next("event", {
105
116
  event_type: agentSessionEventType(event),
106
117
  event,
107
118
  });
108
119
  }
109
120
 
121
+ /** Build the rich event payload (renderer-facing) for an `AgentSessionEvent`. */
122
+ export function toAgentWireEventPayload(event: AgentSessionEvent): AgentWireEventPayload {
123
+ return { event_type: agentSessionEventType(event), event };
124
+ }
125
+
126
+ /** Back-compat alias for {@link toAgentWireEventFrame}. */
127
+ export const toBridgeEventFrame = toAgentWireEventFrame;
128
+
110
129
  /**
111
130
  * Serialize a `workflow_gate` event into a sequenced wire frame (#321). The
112
131
  * gate_id is stamped as the correlation id so the answer (posted to the
@@ -115,7 +134,7 @@ export function toBridgeEventFrame(event: AgentSessionEvent, sequencer: BridgeFr
115
134
  */
116
135
  export function toBridgeWorkflowGateFrame(
117
136
  gate: import("../../rpc/rpc-types").RpcWorkflowGate,
118
- sequencer: BridgeFrameSequencer,
137
+ sequencer: AgentWireFrameSequencer,
119
138
  ): import("./protocol").BridgeWorkflowGateFrame {
120
139
  return sequencer.next("workflow_gate", gate, gate.gate_id);
121
140
  }
@@ -0,0 +1,397 @@
1
+ /**
2
+ * Canonical AgentSession event observation: the single semantic mapping from
3
+ * `AgentSessionEvent` (and non-event wire frames) to bounded owner observations.
4
+ *
5
+ * This is the one place that derives `AgentWireOwnerObservation`s. Harness (and
6
+ * any other owner control plane) consumes these instead of re-parsing the wire
7
+ * protocol with private knowledge.
8
+ *
9
+ * Hard rule: evidence is BOUNDED — only ids, names, categories, statuses,
10
+ * cursors, timestamps, and short codes/messages. Never assistant text, message
11
+ * deltas, command output, raw args, or raw tool results.
12
+ */
13
+ import type { AgentSessionEvent } from "../../../session/agent-session";
14
+ import type { AgentWireEventPayload, AgentWireOwnerObservation } from "./event-contract";
15
+ import { toAgentWireEventPayload } from "./event-envelope";
16
+
17
+ const TEST_RE = /\b(bun test|npm test|yarn test|pnpm test|jest|vitest|pytest|go test|cargo test|mocha|ava)\b/i;
18
+ const TOOL_STATUS_CODES = new Set([
19
+ "aborted",
20
+ "blocked",
21
+ "cancelled",
22
+ "complete",
23
+ "completed",
24
+ "error",
25
+ "failed",
26
+ "ok",
27
+ "pending",
28
+ "running",
29
+ "skipped",
30
+ "success",
31
+ "timeout",
32
+ ]);
33
+
34
+ /** True when a tool name or command indicates a test-runner invocation. */
35
+ export function isTestRunnerTool(toolName?: unknown, command?: unknown): boolean {
36
+ const name = typeof toolName === "string" ? toolName : "";
37
+ const cmd = typeof command === "string" ? command : "";
38
+ if (/test/i.test(name) && name !== "edit" && name !== "read") return true;
39
+ return TEST_RE.test(cmd);
40
+ }
41
+
42
+ function str(v: unknown): string | undefined {
43
+ return typeof v === "string" ? v : undefined;
44
+ }
45
+ function num(v: unknown): number | undefined {
46
+ return typeof v === "number" ? v : undefined;
47
+ }
48
+ /** Only accept a known closed-vocabulary tool status; reject arbitrary strings. */
49
+ export function boundedStatus(v: unknown): string | undefined {
50
+ if (typeof v !== "string") return undefined;
51
+ const status = v.trim().toLowerCase();
52
+ return TOOL_STATUS_CODES.has(status) ? status : undefined;
53
+ }
54
+ /** Accept only identifier-shaped tokens (e.g. RPC command names); reject free text. */
55
+ export function boundedToken(v: unknown): string | undefined {
56
+ if (typeof v !== "string") return undefined;
57
+ return /^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(v) ? v : undefined;
58
+ }
59
+ function recordObject(v: unknown): Record<string, unknown> | undefined {
60
+ return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
61
+ }
62
+ function idOf(v: unknown): string | null {
63
+ const record = recordObject(v);
64
+ return str(record?.id) ?? null;
65
+ }
66
+ /** Extract a bounded tool command for test detection only — never persisted. */
67
+ function toolCommand(args: unknown): string | undefined {
68
+ const record = recordObject(args);
69
+ const c = record?.command ?? record?.cmd ?? record?.commandLine;
70
+ return typeof c === "string" ? c : undefined;
71
+ }
72
+ /** Derive a bounded tool status from a result/partialResult/isError shape. */
73
+ function resultStatus(result: unknown, isError?: boolean): string | undefined {
74
+ if (isError === true) return "error";
75
+ const record = recordObject(result);
76
+ if (!record) return undefined;
77
+ if (record.isError === true) return "error";
78
+ return boundedStatus(record.status) ?? boundedStatus(recordObject(record.details)?.status);
79
+ }
80
+
81
+ function obs(
82
+ event: AgentSessionEvent,
83
+ partial: Omit<AgentWireOwnerObservation, "eventType">,
84
+ ): AgentWireOwnerObservation {
85
+ return { eventType: event.type, ...partial };
86
+ }
87
+
88
+ /**
89
+ * Map a single `AgentSessionEvent` to its bounded owner observation, or null
90
+ * when the event carries no owner-facing signal.
91
+ */
92
+ export function observeAgentSessionEvent(event: AgentSessionEvent): AgentWireOwnerObservation | null {
93
+ switch (event.type) {
94
+ case "agent_start":
95
+ return obs(event, {
96
+ kind: "rpc_agent_started",
97
+ signal: "SessionStart",
98
+ evidence: {},
99
+ severity: "info",
100
+ semantic: true,
101
+ coalesceKey: null,
102
+ });
103
+ case "turn_start":
104
+ return obs(event, {
105
+ kind: "rpc_turn_started",
106
+ signal: "prompt-accepted",
107
+ evidence: {},
108
+ severity: "info",
109
+ semantic: true,
110
+ coalesceKey: null,
111
+ });
112
+ case "turn_end":
113
+ return obs(event, {
114
+ kind: "rpc_turn_ended",
115
+ signal: null,
116
+ evidence: {},
117
+ severity: "info",
118
+ semantic: false,
119
+ coalesceKey: null,
120
+ });
121
+ case "message_start":
122
+ case "message_update":
123
+ case "message_end": {
124
+ const messageId = idOf(event.message);
125
+ return obs(event, {
126
+ kind: "rpc_message_activity",
127
+ signal: null,
128
+ evidence: { phase: event.type, messageId },
129
+ severity: "info",
130
+ semantic: false,
131
+ coalesceKey: `message:${messageId ?? "msg"}`,
132
+ });
133
+ }
134
+ case "tool_execution_start": {
135
+ const test = isTestRunnerTool(event.toolName, toolCommand(event.args));
136
+ return obs(event, {
137
+ kind: "rpc_tool_started",
138
+ signal: test ? "test-running" : "tool-call",
139
+ evidence: { toolId: str(event.toolCallId) ?? null, toolName: str(event.toolName) ?? null },
140
+ severity: "info",
141
+ semantic: true,
142
+ coalesceKey: null,
143
+ });
144
+ }
145
+ case "tool_execution_update": {
146
+ const test = isTestRunnerTool(event.toolName, toolCommand(event.args));
147
+ return obs(event, {
148
+ kind: "rpc_tool_updated",
149
+ signal: test ? "test-running" : null,
150
+ evidence: { toolId: str(event.toolCallId) ?? null, status: resultStatus(event.partialResult) ?? null },
151
+ severity: "info",
152
+ semantic: false,
153
+ coalesceKey: `tool:${str(event.toolCallId) ?? "tool"}`,
154
+ });
155
+ }
156
+ case "tool_execution_end": {
157
+ const test = isTestRunnerTool(event.toolName);
158
+ const status = resultStatus(event.result, event.isError);
159
+ return obs(event, {
160
+ kind: "rpc_tool_ended",
161
+ signal: test ? "test-running" : "tool-call",
162
+ evidence: {
163
+ toolId: str(event.toolCallId) ?? null,
164
+ toolName: str(event.toolName) ?? null,
165
+ status: status ?? null,
166
+ },
167
+ severity: status === "error" ? "warn" : "info",
168
+ semantic: true,
169
+ coalesceKey: null,
170
+ });
171
+ }
172
+ case "auto_compaction_start":
173
+ case "auto_compaction_end":
174
+ return obs(event, {
175
+ kind: "rpc_compaction",
176
+ signal: null,
177
+ evidence: { phase: event.type },
178
+ severity: "info",
179
+ semantic: false,
180
+ coalesceKey: null,
181
+ });
182
+ case "auto_retry_start":
183
+ return obs(event, {
184
+ kind: "rpc_retry",
185
+ signal: null,
186
+ evidence: { phase: event.type, attempt: num(event.attempt) ?? null },
187
+ severity: "warn",
188
+ semantic: false,
189
+ coalesceKey: null,
190
+ });
191
+ case "auto_retry_end":
192
+ return obs(event, {
193
+ kind: "rpc_retry",
194
+ signal: null,
195
+ evidence: { phase: event.type, success: event.success === true },
196
+ severity: "warn",
197
+ semantic: false,
198
+ coalesceKey: null,
199
+ });
200
+ case "retry_fallback_applied":
201
+ case "retry_fallback_succeeded":
202
+ return obs(event, {
203
+ kind: "rpc_retry_fallback",
204
+ signal: null,
205
+ evidence: { phase: event.type, role: str(event.role) ?? null },
206
+ severity: "warn",
207
+ semantic: false,
208
+ coalesceKey: null,
209
+ });
210
+ case "ttsr_triggered":
211
+ return obs(event, {
212
+ kind: "rpc_ttsr",
213
+ signal: "error",
214
+ evidence: { ruleCount: Array.isArray(event.rules) ? event.rules.length : 0 },
215
+ severity: "warn",
216
+ semantic: true,
217
+ coalesceKey: null,
218
+ });
219
+ case "todo_reminder":
220
+ case "todo_auto_clear":
221
+ return obs(event, {
222
+ kind: "rpc_todo",
223
+ signal: null,
224
+ evidence: { phase: event.type },
225
+ severity: "info",
226
+ semantic: false,
227
+ coalesceKey: null,
228
+ });
229
+ case "irc_message":
230
+ return obs(event, {
231
+ kind: "rpc_irc",
232
+ signal: null,
233
+ evidence: {},
234
+ severity: "info",
235
+ semantic: false,
236
+ coalesceKey: null,
237
+ });
238
+ case "notice": {
239
+ const level = event.level;
240
+ return obs(event, {
241
+ kind: "rpc_notice",
242
+ signal: level === "error" ? "error" : null,
243
+ evidence: { level },
244
+ severity: level === "info" ? "info" : "warn",
245
+ semantic: level === "error",
246
+ coalesceKey: null,
247
+ });
248
+ }
249
+ case "thinking_level_changed":
250
+ return obs(event, {
251
+ kind: "rpc_thinking",
252
+ signal: null,
253
+ evidence: { thinkingLevel: str(event.thinkingLevel) ?? null },
254
+ severity: "info",
255
+ semantic: false,
256
+ coalesceKey: null,
257
+ });
258
+ case "goal_updated":
259
+ return obs(event, {
260
+ kind: "rpc_goal",
261
+ signal: null,
262
+ evidence: { hasGoal: event.goal != null },
263
+ severity: "info",
264
+ semantic: false,
265
+ coalesceKey: null,
266
+ });
267
+ case "agent_end":
268
+ return obs(event, {
269
+ kind: "rpc_agent_completed",
270
+ signal: "completed",
271
+ evidence: { stopReason: str(event.stopReason) ?? "completed" },
272
+ severity: "info",
273
+ semantic: true,
274
+ coalesceKey: null,
275
+ });
276
+ default:
277
+ return assertNeverEvent(event);
278
+ }
279
+ }
280
+
281
+ function assertNeverEvent(event: never): null {
282
+ void (event as AgentSessionEvent);
283
+ return null;
284
+ }
285
+
286
+ /** Build the rich event payload (renderer-facing) for an `AgentSessionEvent`. */
287
+ export { toAgentWireEventPayload };
288
+
289
+ /** Observe the bounded owner signal carried by a rich event payload. */
290
+ export function observeAgentWireEventPayload(payload: AgentWireEventPayload): AgentWireOwnerObservation | null {
291
+ return observeAgentSessionEvent(payload.event);
292
+ }
293
+
294
+ function ownerFrame(
295
+ frameType: string,
296
+ partial: Omit<AgentWireOwnerObservation, "frameType">,
297
+ ): AgentWireOwnerObservation {
298
+ return { frameType, ...partial };
299
+ }
300
+
301
+ /**
302
+ * Map a single outbound RPC wire frame (docs/rpc.md) to a bounded owner
303
+ * observation, or null when the frame carries no owner-facing signal. Event
304
+ * frames delegate to {@link observeAgentWireEventPayload}; non-event frames are
305
+ * mapped here so owners never re-parse protocol semantics privately.
306
+ */
307
+ export function observeRpcOutboundFrame(frame: Record<string, unknown>): AgentWireOwnerObservation | null {
308
+ const type = str(frame.type);
309
+ if (!type || type === "ready") return null;
310
+
311
+ switch (type) {
312
+ case "response": {
313
+ if (frame.success === false) {
314
+ const error = recordObject(frame.error);
315
+ return ownerFrame(type, {
316
+ kind: "rpc_response_failed",
317
+ signal: "error",
318
+ evidence: {
319
+ command: boundedToken(frame.command) ?? null,
320
+ id: boundedToken(frame.id) ?? null,
321
+ code: boundedToken(error?.code) ?? null,
322
+ },
323
+ severity: "warn",
324
+ semantic: false,
325
+ coalesceKey: null,
326
+ });
327
+ }
328
+ return null;
329
+ }
330
+ case "event": {
331
+ const payload = recordObject(frame.payload);
332
+ const event = recordObject(payload?.event);
333
+ if (!event) return null;
334
+ return observeAgentSessionEvent(event as unknown as AgentSessionEvent);
335
+ }
336
+ case "workflow_gate":
337
+ return ownerFrame(type, {
338
+ kind: "rpc_workflow_gate",
339
+ signal: null,
340
+ evidence: {
341
+ gate_id: str(frame.gate_id) ?? null,
342
+ kind: str(frame.kind) ?? null,
343
+ stage: str(frame.stage) ?? null,
344
+ },
345
+ severity: "info",
346
+ semantic: true,
347
+ coalesceKey: null,
348
+ });
349
+ case "extension_ui_request":
350
+ return ownerFrame(type, {
351
+ kind: "rpc_extension_request",
352
+ signal: "tool-call",
353
+ evidence: { id: str(frame.id) ?? null, method: str(frame.method) ?? null },
354
+ severity: "info",
355
+ semantic: false,
356
+ coalesceKey: null,
357
+ });
358
+ case "extension_error":
359
+ return ownerFrame(type, {
360
+ kind: "rpc_extension_error",
361
+ signal: "error",
362
+ evidence: {
363
+ extensionPath: str(frame.extensionPath) ?? null,
364
+ event: boundedToken(frame.event) ?? null,
365
+ },
366
+ severity: "critical",
367
+ semantic: true,
368
+ coalesceKey: null,
369
+ });
370
+ case "host_tool_call":
371
+ case "host_tool_cancel":
372
+ return ownerFrame(type, {
373
+ kind: type === "host_tool_cancel" ? "rpc_host_tool_cancel" : "rpc_host_tool_call",
374
+ signal: "tool-call",
375
+ evidence: { id: str(frame.id) ?? null, toolName: str(frame.toolName) ?? null },
376
+ severity: "info",
377
+ semantic: false,
378
+ coalesceKey: null,
379
+ });
380
+ case "host_uri_request":
381
+ case "host_uri_cancel":
382
+ return ownerFrame(type, {
383
+ kind: type === "host_uri_cancel" ? "rpc_host_uri_cancel" : "rpc_host_uri_request",
384
+ signal: "tool-call",
385
+ evidence: {
386
+ id: str(frame.id) ?? null,
387
+ operation: str(frame.operation) ?? null,
388
+ scheme: str(frame.scheme) ?? null,
389
+ },
390
+ severity: "info",
391
+ semantic: false,
392
+ coalesceKey: null,
393
+ });
394
+ default:
395
+ return null;
396
+ }
397
+ }