@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.
- package/CHANGELOG.md +13 -0
- package/dist/types/async/job-manager.d.ts +44 -1
- package/dist/types/cli/setup-cli.d.ts +14 -1
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/setup.d.ts +41 -0
- package/dist/types/commit/model-selection.d.ts +1 -1
- package/dist/types/config/model-registry.d.ts +3 -1
- package/dist/types/config/model-resolver.d.ts +1 -19
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/settings-schema.d.ts +15 -1
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/policy.d.ts +24 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +52 -0
- package/dist/types/extensibility/extensions/types.d.ts +13 -0
- package/dist/types/gjc-runtime/goal-mode-request.d.ts +8 -1
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +13 -0
- package/dist/types/harness-control-plane/types.d.ts +7 -2
- package/dist/types/modes/acp/acp-event-mapper.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +7 -0
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +84 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +14 -7
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +13 -34
- package/dist/types/session/agent-session.d.ts +12 -1
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/setup/hermes-setup.d.ts +71 -0
- package/dist/types/task/render.d.ts +7 -1
- package/dist/types/tools/bash.d.ts +2 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser.d.ts +80 -0
- package/dist/types/tools/image-gen.d.ts +1 -0
- package/dist/types/tools/index.d.ts +3 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/subagent-render.d.ts +25 -0
- package/dist/types/tools/subagent.d.ts +5 -1
- package/package.json +7 -7
- package/src/async/job-manager.ts +163 -2
- package/src/cli/setup-cli.ts +86 -2
- package/src/cli.ts +2 -0
- package/src/commands/coordinator.ts +70 -0
- package/src/commands/mcp-serve.ts +62 -0
- package/src/commands/setup.ts +30 -1
- package/src/commands/ultragoal.ts +7 -1
- package/src/commit/agentic/index.ts +2 -2
- package/src/commit/model-selection.ts +7 -22
- package/src/commit/pipeline.ts +2 -2
- package/src/config/model-registry.ts +17 -9
- package/src/config/model-resolver.ts +14 -84
- package/src/config/models-config-schema.ts +2 -0
- package/src/config/settings-schema.ts +14 -1
- package/src/coordinator/contract.ts +20 -0
- package/src/coordinator-mcp/policy.ts +160 -0
- package/src/coordinator-mcp/safety.ts +80 -0
- package/src/coordinator-mcp/server.ts +1316 -0
- package/src/extensibility/extensions/types.ts +13 -0
- package/src/gjc-runtime/goal-mode-request.ts +21 -1
- package/src/gjc-runtime/session-state-sidecar.ts +79 -0
- package/src/harness-control-plane/owner.ts +3 -3
- package/src/harness-control-plane/rpc-adapter.ts +7 -1
- package/src/harness-control-plane/types.ts +8 -11
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-agent.ts +17 -9
- package/src/modes/acp/acp-event-mapper.ts +33 -1
- package/src/modes/components/custom-editor.ts +19 -3
- package/src/modes/components/hook-selector.ts +109 -5
- package/src/modes/controllers/extension-ui-controller.ts +16 -1
- package/src/modes/controllers/input-controller.ts +27 -7
- package/src/modes/controllers/selector-controller.ts +7 -1
- package/src/modes/interactive-mode.ts +3 -1
- package/src/modes/rpc/rpc-client.ts +16 -3
- package/src/modes/rpc/rpc-mode.ts +5 -2
- package/src/modes/shared/agent-wire/command-contract.ts +18 -0
- package/src/modes/shared/agent-wire/event-contract.ts +147 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +35 -16
- package/src/modes/shared/agent-wire/event-observation.ts +397 -0
- package/src/modes/shared/agent-wire/protocol.ts +24 -81
- package/src/modes/utils/context-usage.ts +2 -2
- package/src/prompts/agents/architect.md +6 -0
- package/src/prompts/agents/critic.md +6 -0
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/plan.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/tools/browser.md +3 -2
- package/src/runtime-mcp/manager.ts +15 -2
- package/src/sdk.ts +3 -1
- package/src/session/agent-session.ts +66 -4
- package/src/session/session-manager.ts +1 -1
- package/src/setup/hermes/templates/operator-instructions.v1.md +29 -0
- package/src/setup/hermes-setup.ts +429 -0
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +2 -0
- package/src/task/render.ts +14 -0
- package/src/tools/ask.ts +30 -10
- package/src/tools/bash.ts +6 -1
- package/src/tools/browser/actions.ts +189 -0
- package/src/tools/browser.ts +91 -1
- package/src/tools/image-gen.ts +42 -15
- package/src/tools/index.ts +7 -1
- package/src/tools/inspect-image.ts +10 -8
- package/src/tools/job.ts +12 -2
- package/src/tools/monitor.ts +98 -17
- package/src/tools/renderers.ts +2 -0
- package/src/tools/subagent-render.ts +160 -0
- package/src/tools/subagent.ts +49 -7
- package/src/utils/commit-message-generator.ts +6 -13
- package/src/utils/title-generator.ts +1 -1
- package/dist/types/harness-control-plane/frame-mapper.d.ts +0 -29
- package/src/harness-control-plane/frame-mapper.ts +0 -286
- 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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
|
|
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):
|
|
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
|
|
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
|
|
85
|
+
next<TType extends AgentWireFrameType, TPayload>(
|
|
82
86
|
type: TType,
|
|
83
87
|
payload: TPayload,
|
|
84
88
|
correlationId?: string,
|
|
85
|
-
):
|
|
89
|
+
): AgentWireFrameEnvelope<TType, TPayload> {
|
|
86
90
|
this.#seq += 1;
|
|
87
|
-
const frame:
|
|
88
|
-
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
|
-
/**
|
|
103
|
-
export
|
|
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:
|
|
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
|
+
}
|