@gajae-code/coding-agent 0.2.5 → 0.3.1
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 +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +23 -29
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +198 -14
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +372 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +600 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +98 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +197 -64
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +2 -0
- package/src/prompts/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +423 -79
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-session control endpoint — a Unix domain socket served by the RuntimeOwner so
|
|
3
|
+
* stateless `gjc harness` CLI calls can route owner-routed primitives (submit, observe,
|
|
4
|
+
* recover, retire) to the live owner. One JSON request line in, one JSON response line out.
|
|
5
|
+
*
|
|
6
|
+
* The owner is the only listener; clients connect per call. When no socket is reachable
|
|
7
|
+
* the caller falls back to the no-owner behavior (read-only observe, owner-not-live submit).
|
|
8
|
+
*
|
|
9
|
+
* FIFO fallback (for platforms/paths where AF_UNIX is unavailable or path-length limited)
|
|
10
|
+
* is a documented seam tracked as an ADR follow-up.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as fs from "node:fs/promises";
|
|
14
|
+
import * as net from "node:net";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
import { MAX_UNIX_SOCKET_PATH_BYTES } from "./storage";
|
|
17
|
+
|
|
18
|
+
export interface EndpointRequest {
|
|
19
|
+
verb: string;
|
|
20
|
+
input: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type EndpointHandler = (req: EndpointRequest) => Promise<unknown>;
|
|
24
|
+
|
|
25
|
+
function frame(value: unknown): string {
|
|
26
|
+
return `${JSON.stringify(value)}\n`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class ControlServer {
|
|
30
|
+
#server: net.Server | null = null;
|
|
31
|
+
constructor(
|
|
32
|
+
readonly socketPath: string,
|
|
33
|
+
private readonly handler: EndpointHandler,
|
|
34
|
+
) {}
|
|
35
|
+
|
|
36
|
+
async listen(): Promise<void> {
|
|
37
|
+
await fs.mkdir(path.dirname(this.socketPath), { recursive: true });
|
|
38
|
+
if (Buffer.byteLength(this.socketPath) > MAX_UNIX_SOCKET_PATH_BYTES) {
|
|
39
|
+
throw new Error(`socket_path_too_long:${this.socketPath}`);
|
|
40
|
+
}
|
|
41
|
+
await fs.rm(this.socketPath, { force: true });
|
|
42
|
+
await new Promise<void>((resolve, reject) => {
|
|
43
|
+
const server = net.createServer(socket => this.#onConnection(socket));
|
|
44
|
+
server.once("error", reject);
|
|
45
|
+
server.listen(this.socketPath, () => {
|
|
46
|
+
server.removeListener("error", reject);
|
|
47
|
+
this.#server = server;
|
|
48
|
+
resolve();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#onConnection(socket: net.Socket): void {
|
|
54
|
+
socket.setEncoding("utf8");
|
|
55
|
+
let buffer = "";
|
|
56
|
+
let handled = false;
|
|
57
|
+
socket.on("data", (chunk: string) => {
|
|
58
|
+
if (handled) return;
|
|
59
|
+
buffer += chunk;
|
|
60
|
+
const idx = buffer.indexOf("\n");
|
|
61
|
+
if (idx < 0) return;
|
|
62
|
+
handled = true;
|
|
63
|
+
const line = buffer.slice(0, idx).trim();
|
|
64
|
+
void this.#dispatch(line)
|
|
65
|
+
.then(response => {
|
|
66
|
+
socket.end(frame(response));
|
|
67
|
+
})
|
|
68
|
+
.catch((error: unknown) => {
|
|
69
|
+
socket.end(frame({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async #dispatch(line: string): Promise<unknown> {
|
|
75
|
+
const req = JSON.parse(line) as EndpointRequest;
|
|
76
|
+
if (!req || typeof req.verb !== "string") throw new Error("bad_request");
|
|
77
|
+
return this.handler({ verb: req.verb, input: req.input ?? {} });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async close(): Promise<void> {
|
|
81
|
+
const server = this.#server;
|
|
82
|
+
this.#server = null;
|
|
83
|
+
if (server) {
|
|
84
|
+
await new Promise<void>(resolve => server.close(() => resolve()));
|
|
85
|
+
}
|
|
86
|
+
await fs.rm(this.socketPath, { force: true });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class EndpointUnreachableError extends Error {
|
|
91
|
+
constructor(
|
|
92
|
+
readonly socketPath: string,
|
|
93
|
+
readonly reason = "unreachable",
|
|
94
|
+
) {
|
|
95
|
+
super(`endpoint_${reason}:${socketPath}`);
|
|
96
|
+
this.name = "EndpointUnreachableError";
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Call the owner's control endpoint. Rejects with {@link EndpointUnreachableError} when no owner listens or responds. */
|
|
101
|
+
export function callEndpoint(socketPath: string, req: EndpointRequest, timeoutMs = 5_000): Promise<unknown> {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const socket = net.connect(socketPath);
|
|
104
|
+
let buffer = "";
|
|
105
|
+
let settled = false;
|
|
106
|
+
const done = (fn: () => void): void => {
|
|
107
|
+
if (settled) return;
|
|
108
|
+
settled = true;
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
socket.destroy();
|
|
111
|
+
fn();
|
|
112
|
+
};
|
|
113
|
+
const timer = setTimeout(
|
|
114
|
+
() => done(() => reject(new EndpointUnreachableError(socketPath, "timeout"))),
|
|
115
|
+
timeoutMs,
|
|
116
|
+
);
|
|
117
|
+
socket.setEncoding("utf8");
|
|
118
|
+
socket.on("connect", () => socket.write(frame(req)));
|
|
119
|
+
socket.on("data", (chunk: string) => {
|
|
120
|
+
buffer += chunk;
|
|
121
|
+
const idx = buffer.indexOf("\n");
|
|
122
|
+
if (idx >= 0) {
|
|
123
|
+
const line = buffer.slice(0, idx).trim();
|
|
124
|
+
done(() => {
|
|
125
|
+
try {
|
|
126
|
+
resolve(JSON.parse(line));
|
|
127
|
+
} catch {
|
|
128
|
+
reject(new EndpointUnreachableError(socketPath, "bad_frame"));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
socket.on("error", (error: NodeJS.ErrnoException) => {
|
|
134
|
+
done(() => {
|
|
135
|
+
if (
|
|
136
|
+
error.code === "ENOENT" ||
|
|
137
|
+
error.code === "ECONNREFUSED" ||
|
|
138
|
+
error.code === "ECONNRESET" ||
|
|
139
|
+
error.code === "EPIPE"
|
|
140
|
+
) {
|
|
141
|
+
reject(new EndpointUnreachableError(socketPath, error.code.toLowerCase()));
|
|
142
|
+
} else {
|
|
143
|
+
reject(error);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence-gated finalizer (M8).
|
|
3
|
+
*
|
|
4
|
+
* `completed: true` ONLY when: every required validation receipt is valid for the commit
|
|
5
|
+
* under test, the final commit exists on the branch, a PR/issue artifact exists, and the
|
|
6
|
+
* completion receipt validates with no blockers. Never "the agent said done".
|
|
7
|
+
*
|
|
8
|
+
* External effects (running validation commands, git, gh) are injected via {@link FinalizeChecks}
|
|
9
|
+
* so the gate predicate is unit-testable; {@link defaultFinalizeChecks} provides the real
|
|
10
|
+
* implementation exercised by the M10 e2e suite.
|
|
11
|
+
*/
|
|
12
|
+
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { randomBytes } from "node:crypto";
|
|
14
|
+
import { readFile } from "node:fs/promises";
|
|
15
|
+
import {
|
|
16
|
+
buildReceipt,
|
|
17
|
+
type CompletionEvidence,
|
|
18
|
+
type ReceiptEnvelope,
|
|
19
|
+
type ReceiptSubject,
|
|
20
|
+
type ValidationEvidence,
|
|
21
|
+
validateReceipt,
|
|
22
|
+
} from "./receipts";
|
|
23
|
+
import { readReceiptIndex, writeReceiptImmutable } from "./storage";
|
|
24
|
+
|
|
25
|
+
export interface ValidationCommandSpec {
|
|
26
|
+
name: string;
|
|
27
|
+
command: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ValidationRun {
|
|
31
|
+
exactCommand: string;
|
|
32
|
+
cwd: string;
|
|
33
|
+
exitStatus: number;
|
|
34
|
+
pass: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FinalizeChecks {
|
|
38
|
+
runValidation(spec: ValidationCommandSpec): Promise<ValidationRun>;
|
|
39
|
+
resolveCommit(): Promise<string | null>;
|
|
40
|
+
commitOnBranch(commit: string, branch: string): Promise<boolean>;
|
|
41
|
+
prOrIssue(): Promise<{ prUrl: string | null; issueArtifact: string | null }>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface FinalizeOptions {
|
|
45
|
+
root: string;
|
|
46
|
+
sessionId: string;
|
|
47
|
+
workspace: string;
|
|
48
|
+
branch: string;
|
|
49
|
+
requireTests?: boolean;
|
|
50
|
+
requireCommit?: boolean;
|
|
51
|
+
requirePr?: boolean;
|
|
52
|
+
validationCommands?: ValidationCommandSpec[];
|
|
53
|
+
checks: FinalizeChecks;
|
|
54
|
+
clock?: () => number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface FinalizeResult {
|
|
58
|
+
completed: boolean;
|
|
59
|
+
receiptPath: string | null;
|
|
60
|
+
validation: { name: string; valid: boolean; exitStatus: number }[];
|
|
61
|
+
commitHash: string | null;
|
|
62
|
+
prUrl: string | null;
|
|
63
|
+
issueArtifact: string | null;
|
|
64
|
+
blockers: string[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function receiptId(prefix: string): string {
|
|
68
|
+
return `${prefix}-${Date.now()}-${randomBytes(4).toString("hex")}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function runFinalize(opts: FinalizeOptions): Promise<FinalizeResult> {
|
|
72
|
+
const now = () => new Date(opts.clock ? opts.clock() : Date.now()).toISOString();
|
|
73
|
+
const blockers: string[] = [];
|
|
74
|
+
const validation: FinalizeResult["validation"] = [];
|
|
75
|
+
const validationReceiptIds: string[] = [];
|
|
76
|
+
|
|
77
|
+
const commit = await opts.checks.resolveCommit();
|
|
78
|
+
const subject: ReceiptSubject = { workspace: opts.workspace, branch: opts.branch, head: commit, commit };
|
|
79
|
+
|
|
80
|
+
// 1. Validation receipts.
|
|
81
|
+
for (const spec of opts.validationCommands ?? []) {
|
|
82
|
+
const run = await opts.checks.runValidation(spec);
|
|
83
|
+
const evidence: ValidationEvidence = {
|
|
84
|
+
command: spec.name,
|
|
85
|
+
exactCommand: run.exactCommand,
|
|
86
|
+
cwd: run.cwd,
|
|
87
|
+
exitStatus: run.exitStatus,
|
|
88
|
+
pass: run.pass,
|
|
89
|
+
commitUnderTest: commit,
|
|
90
|
+
};
|
|
91
|
+
const receipt = buildReceipt<ValidationEvidence>({
|
|
92
|
+
receiptId: receiptId("val"),
|
|
93
|
+
sessionId: opts.sessionId,
|
|
94
|
+
family: "validation",
|
|
95
|
+
source: "finalizer",
|
|
96
|
+
subject,
|
|
97
|
+
evidence,
|
|
98
|
+
createdAt: now(),
|
|
99
|
+
valid: run.pass,
|
|
100
|
+
});
|
|
101
|
+
await writeReceiptImmutable(opts.root, opts.sessionId, "validation", receipt.receiptId, receipt);
|
|
102
|
+
const outcome = validateReceipt(receipt);
|
|
103
|
+
validation.push({ name: spec.name, valid: outcome.valid, exitStatus: run.exitStatus });
|
|
104
|
+
validationReceiptIds.push(receipt.receiptId);
|
|
105
|
+
if (opts.requireTests && !outcome.valid) blockers.push(`validation-failed:${spec.name}`);
|
|
106
|
+
}
|
|
107
|
+
if (opts.requireTests && (opts.validationCommands?.length ?? 0) === 0) {
|
|
108
|
+
blockers.push("validation-required-but-none-run");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2. Commit on branch.
|
|
112
|
+
if (opts.requireCommit) {
|
|
113
|
+
if (!commit) blockers.push("missing-commit");
|
|
114
|
+
else if (!(await opts.checks.commitOnBranch(commit, opts.branch))) blockers.push("commit-not-on-branch");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 3. PR / issue artifact.
|
|
118
|
+
const artifact = await opts.checks.prOrIssue();
|
|
119
|
+
if (opts.requirePr && !artifact.prUrl && !artifact.issueArtifact) blockers.push("missing-pr-or-issue");
|
|
120
|
+
|
|
121
|
+
// B4: cross-validate the persisted validation receipts (validity + commit freshness) before completion.
|
|
122
|
+
if (blockers.length === 0) {
|
|
123
|
+
const persisted = await readReceiptIndex(opts.root, opts.sessionId, "validation");
|
|
124
|
+
for (const id of validationReceiptIds) {
|
|
125
|
+
const entry = persisted.find(e => e.receiptId === id);
|
|
126
|
+
if (!entry) {
|
|
127
|
+
blockers.push(`missing-validation-receipt:${id}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const receipt = JSON.parse(await readFile(entry.path, "utf8")) as ReceiptEnvelope<ValidationEvidence>;
|
|
131
|
+
if (!validateReceipt(receipt).valid) blockers.push(`validation-receipt-invalid:${id}`);
|
|
132
|
+
else if (commit && receipt.evidence.commitUnderTest !== commit) blockers.push(`validation-stale-commit:${id}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (blockers.length > 0) {
|
|
137
|
+
return {
|
|
138
|
+
completed: false,
|
|
139
|
+
receiptPath: null,
|
|
140
|
+
validation,
|
|
141
|
+
commitHash: commit,
|
|
142
|
+
prUrl: artifact.prUrl,
|
|
143
|
+
issueArtifact: artifact.issueArtifact,
|
|
144
|
+
blockers,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 4. Completion receipt + predicate.
|
|
149
|
+
const completion: CompletionEvidence = {
|
|
150
|
+
finalCommit: commit ?? "",
|
|
151
|
+
branch: opts.branch,
|
|
152
|
+
prUrl: artifact.prUrl,
|
|
153
|
+
issueArtifact: artifact.issueArtifact,
|
|
154
|
+
requiredValidationReceiptIds: validationReceiptIds,
|
|
155
|
+
finalLifecycle: "completed",
|
|
156
|
+
finalizedAt: now(),
|
|
157
|
+
blockers: [],
|
|
158
|
+
};
|
|
159
|
+
const receipt = buildReceipt<CompletionEvidence>({
|
|
160
|
+
receiptId: receiptId("done"),
|
|
161
|
+
sessionId: opts.sessionId,
|
|
162
|
+
family: "completion",
|
|
163
|
+
source: "finalizer",
|
|
164
|
+
subject,
|
|
165
|
+
evidence: completion,
|
|
166
|
+
createdAt: now(),
|
|
167
|
+
});
|
|
168
|
+
const outcome = validateReceipt(receipt);
|
|
169
|
+
const entry = await writeReceiptImmutable(opts.root, opts.sessionId, "completion", receipt.receiptId, receipt);
|
|
170
|
+
return {
|
|
171
|
+
completed: outcome.valid,
|
|
172
|
+
receiptPath: entry.path,
|
|
173
|
+
validation,
|
|
174
|
+
commitHash: commit,
|
|
175
|
+
prUrl: artifact.prUrl,
|
|
176
|
+
issueArtifact: artifact.issueArtifact,
|
|
177
|
+
blockers: outcome.valid ? [] : outcome.reasons,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function git(workspace: string, args: string[]): string | null {
|
|
182
|
+
try {
|
|
183
|
+
return execFileSync("git", args, {
|
|
184
|
+
cwd: workspace,
|
|
185
|
+
encoding: "utf8",
|
|
186
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
187
|
+
}).trim();
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Real checks: git for commit/branch, gh for PR, Bun.spawn for validation commands. */
|
|
194
|
+
export function defaultFinalizeChecks(workspace: string): FinalizeChecks {
|
|
195
|
+
return {
|
|
196
|
+
async runValidation(spec) {
|
|
197
|
+
const proc = Bun.spawnSync(["bash", "-lc", spec.command], { cwd: workspace, stdout: "pipe", stderr: "pipe" });
|
|
198
|
+
const exitStatus = proc.exitCode ?? 1;
|
|
199
|
+
return { exactCommand: spec.command, cwd: workspace, exitStatus, pass: exitStatus === 0 };
|
|
200
|
+
},
|
|
201
|
+
async resolveCommit() {
|
|
202
|
+
return git(workspace, ["rev-parse", "HEAD"]);
|
|
203
|
+
},
|
|
204
|
+
async commitOnBranch(commit, branch) {
|
|
205
|
+
const merged = git(workspace, ["branch", "--contains", commit, "--format=%(refname:short)"]);
|
|
206
|
+
if (!merged) return false;
|
|
207
|
+
return merged.split("\n").some(b => b.trim() === branch);
|
|
208
|
+
},
|
|
209
|
+
async prOrIssue() {
|
|
210
|
+
try {
|
|
211
|
+
const out = execFileSync("gh", ["pr", "view", "--json", "url", "-q", ".url"], {
|
|
212
|
+
cwd: workspace,
|
|
213
|
+
encoding: "utf8",
|
|
214
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
215
|
+
}).trim();
|
|
216
|
+
return { prUrl: out || null, issueArtifact: null };
|
|
217
|
+
} catch {
|
|
218
|
+
return { prUrl: null, issueArtifact: null };
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure mapping from `gjc --mode rpc` event frames (docs/rpc.md) to bounded owner event kinds
|
|
3
|
+
* and {@link ObservedSignal}s. The owner feeds raw frames through this mapper and emits the
|
|
4
|
+
* result via its single-writer #emit — the mapper itself performs NO IO and NO appends.
|
|
5
|
+
*
|
|
6
|
+
* Hard rule: evidence is BOUNDED — only ids, names, categories, statuses, cursors, timestamps,
|
|
7
|
+
* and short codes/messages. Never assistant text, message deltas, command output, or raw args.
|
|
8
|
+
*/
|
|
9
|
+
import type { ObservedSignal } from "./types";
|
|
10
|
+
|
|
11
|
+
export interface MappedFrame {
|
|
12
|
+
/** Owner event kind (rpc_*). */
|
|
13
|
+
kind: string;
|
|
14
|
+
/** Bounded observed signal, or null when the frame carries no user-facing signal. */
|
|
15
|
+
signal: ObservedSignal | null;
|
|
16
|
+
/** Bounded evidence — ids/names/statuses/cursors/timestamps/short codes only. */
|
|
17
|
+
evidence: Record<string, unknown>;
|
|
18
|
+
/** Severity for the emitted event. */
|
|
19
|
+
severity: "info" | "warn" | "critical";
|
|
20
|
+
/** Never-drop frames (must be enqueued in order, never coalesced away). */
|
|
21
|
+
semantic: boolean;
|
|
22
|
+
/** Coalescing key for high-frequency non-semantic frames (message id / tool id); null otherwise. */
|
|
23
|
+
coalesceKey: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const TEST_RE = /\b(bun test|npm test|yarn test|pnpm test|jest|vitest|pytest|go test|cargo test|mocha|ava)\b/i;
|
|
27
|
+
const TOOL_STATUS_CODES = new Set([
|
|
28
|
+
"aborted",
|
|
29
|
+
"blocked",
|
|
30
|
+
"cancelled",
|
|
31
|
+
"complete",
|
|
32
|
+
"completed",
|
|
33
|
+
"error",
|
|
34
|
+
"failed",
|
|
35
|
+
"ok",
|
|
36
|
+
"pending",
|
|
37
|
+
"running",
|
|
38
|
+
"skipped",
|
|
39
|
+
"success",
|
|
40
|
+
"timeout",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
export function isTestRunnerTool(toolName?: unknown, command?: unknown): boolean {
|
|
44
|
+
const name = typeof toolName === "string" ? toolName : "";
|
|
45
|
+
const cmd = typeof command === "string" ? command : "";
|
|
46
|
+
if (/test/i.test(name) && name !== "edit" && name !== "read") return true;
|
|
47
|
+
return TEST_RE.test(cmd);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function str(v: unknown): string | undefined {
|
|
51
|
+
return typeof v === "string" ? v : undefined;
|
|
52
|
+
}
|
|
53
|
+
function num(v: unknown): number | undefined {
|
|
54
|
+
return typeof v === "number" ? v : undefined;
|
|
55
|
+
}
|
|
56
|
+
function boundedMessage(v: unknown): string | undefined {
|
|
57
|
+
const s = typeof v === "string" ? v : undefined;
|
|
58
|
+
return s === undefined ? undefined : s.slice(0, 200);
|
|
59
|
+
}
|
|
60
|
+
function boundedStatus(v: unknown): string | undefined {
|
|
61
|
+
if (typeof v !== "string") return undefined;
|
|
62
|
+
const status = v.trim().toLowerCase();
|
|
63
|
+
return TOOL_STATUS_CODES.has(status) ? status : undefined;
|
|
64
|
+
}
|
|
65
|
+
function recordObject(v: unknown): Record<string, unknown> | undefined {
|
|
66
|
+
return v && typeof v === "object" && !Array.isArray(v) ? (v as Record<string, unknown>) : undefined;
|
|
67
|
+
}
|
|
68
|
+
/** Extract a tool command from real AgentSessionEvent `args` or a flat fixture frame. Bounded use only — never persisted. */
|
|
69
|
+
function toolCommand(frame: Record<string, unknown>): string | undefined {
|
|
70
|
+
const args = recordObject(frame.args);
|
|
71
|
+
const c = args?.command ?? args?.cmd ?? args?.commandLine;
|
|
72
|
+
if (typeof c === "string") return c;
|
|
73
|
+
return str(frame.command) ?? str(frame.commandLine);
|
|
74
|
+
}
|
|
75
|
+
/** Derive a tool status, honoring real `isError` booleans as well as bounded status strings. */
|
|
76
|
+
function toolStatus(frame: Record<string, unknown>): string | undefined {
|
|
77
|
+
if (frame.isError === true) return "error";
|
|
78
|
+
const flatStatus = boundedStatus(frame.status);
|
|
79
|
+
if (flatStatus) return flatStatus;
|
|
80
|
+
for (const candidate of [frame.result, frame.partialResult]) {
|
|
81
|
+
const result = recordObject(candidate);
|
|
82
|
+
if (!result) continue;
|
|
83
|
+
if (result.isError === true) return "error";
|
|
84
|
+
const status = boundedStatus(result.status) ?? boundedStatus(recordObject(result.details)?.status);
|
|
85
|
+
if (status) return status;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Map a single RPC frame. Returns null for frames that carry no observability value
|
|
92
|
+
* (or that the adapter handles itself: `ready`, `response`).
|
|
93
|
+
*/
|
|
94
|
+
export function mapRpcFrame(frame: Record<string, unknown>): MappedFrame | null {
|
|
95
|
+
const type = str(frame.type);
|
|
96
|
+
if (!type || type === "ready" || type === "response") return null;
|
|
97
|
+
|
|
98
|
+
switch (type) {
|
|
99
|
+
case "agent_start":
|
|
100
|
+
return {
|
|
101
|
+
kind: "rpc_agent_started",
|
|
102
|
+
signal: "SessionStart",
|
|
103
|
+
evidence: {},
|
|
104
|
+
severity: "info",
|
|
105
|
+
semantic: true,
|
|
106
|
+
coalesceKey: null,
|
|
107
|
+
};
|
|
108
|
+
case "turn_start":
|
|
109
|
+
return {
|
|
110
|
+
kind: "rpc_turn_started",
|
|
111
|
+
signal: "prompt-accepted",
|
|
112
|
+
evidence: {},
|
|
113
|
+
severity: "info",
|
|
114
|
+
semantic: true,
|
|
115
|
+
coalesceKey: null,
|
|
116
|
+
};
|
|
117
|
+
case "turn_end":
|
|
118
|
+
return {
|
|
119
|
+
kind: "rpc_turn_ended",
|
|
120
|
+
signal: null,
|
|
121
|
+
evidence: {},
|
|
122
|
+
severity: "info",
|
|
123
|
+
semantic: false,
|
|
124
|
+
coalesceKey: null,
|
|
125
|
+
};
|
|
126
|
+
case "message_start":
|
|
127
|
+
case "message_update":
|
|
128
|
+
case "message_end":
|
|
129
|
+
return {
|
|
130
|
+
kind: "rpc_message_activity",
|
|
131
|
+
signal: null,
|
|
132
|
+
evidence: { phase: type, messageId: str(frame.messageId) ?? null },
|
|
133
|
+
severity: "info",
|
|
134
|
+
semantic: false,
|
|
135
|
+
coalesceKey: `message:${str(frame.messageId) ?? "msg"}`,
|
|
136
|
+
};
|
|
137
|
+
case "tool_execution_start": {
|
|
138
|
+
const toolName = str(frame.toolName);
|
|
139
|
+
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
140
|
+
return {
|
|
141
|
+
kind: "rpc_tool_started",
|
|
142
|
+
signal: test ? "test-running" : "tool-call",
|
|
143
|
+
evidence: { toolId: str(frame.toolCallId) ?? null, toolName: toolName ?? null },
|
|
144
|
+
severity: "info",
|
|
145
|
+
semantic: true,
|
|
146
|
+
coalesceKey: null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
case "tool_execution_update": {
|
|
150
|
+
const toolName = str(frame.toolName);
|
|
151
|
+
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
152
|
+
return {
|
|
153
|
+
kind: "rpc_tool_updated",
|
|
154
|
+
signal: test ? "test-running" : null,
|
|
155
|
+
evidence: { toolId: str(frame.toolCallId) ?? null, status: toolStatus(frame) ?? null },
|
|
156
|
+
severity: "info",
|
|
157
|
+
semantic: false,
|
|
158
|
+
coalesceKey: `tool:${str(frame.toolCallId) ?? "tool"}`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
case "tool_execution_end": {
|
|
162
|
+
const toolName = str(frame.toolName);
|
|
163
|
+
const test = isTestRunnerTool(toolName, toolCommand(frame));
|
|
164
|
+
const status = toolStatus(frame);
|
|
165
|
+
return {
|
|
166
|
+
kind: "rpc_tool_ended",
|
|
167
|
+
signal: test ? "test-running" : "tool-call",
|
|
168
|
+
evidence: {
|
|
169
|
+
toolId: str(frame.toolCallId) ?? null,
|
|
170
|
+
toolName: toolName ?? null,
|
|
171
|
+
status: status ?? null,
|
|
172
|
+
exitCode: num(frame.exitCode) ?? null,
|
|
173
|
+
},
|
|
174
|
+
severity: status === "error" ? "warn" : "info",
|
|
175
|
+
semantic: true,
|
|
176
|
+
coalesceKey: null,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
case "host_tool_call":
|
|
180
|
+
case "host_tool_cancel":
|
|
181
|
+
return {
|
|
182
|
+
kind: "rpc_host_tool",
|
|
183
|
+
signal: "tool-call",
|
|
184
|
+
evidence: { toolName: str(frame.toolName) ?? null },
|
|
185
|
+
severity: "info",
|
|
186
|
+
semantic: false,
|
|
187
|
+
coalesceKey: null,
|
|
188
|
+
};
|
|
189
|
+
case "host_uri_request":
|
|
190
|
+
case "host_uri_cancel":
|
|
191
|
+
return {
|
|
192
|
+
kind: "rpc_host_uri",
|
|
193
|
+
signal: "tool-call",
|
|
194
|
+
evidence: { operation: str(frame.operation) ?? null },
|
|
195
|
+
severity: "info",
|
|
196
|
+
semantic: false,
|
|
197
|
+
coalesceKey: null,
|
|
198
|
+
};
|
|
199
|
+
case "auto_compaction_start":
|
|
200
|
+
case "auto_compaction_end":
|
|
201
|
+
return {
|
|
202
|
+
kind: "rpc_compaction",
|
|
203
|
+
signal: null,
|
|
204
|
+
evidence: { phase: type },
|
|
205
|
+
severity: "info",
|
|
206
|
+
semantic: false,
|
|
207
|
+
coalesceKey: null,
|
|
208
|
+
};
|
|
209
|
+
case "auto_retry_start":
|
|
210
|
+
case "auto_retry_end":
|
|
211
|
+
return {
|
|
212
|
+
kind: "rpc_retry",
|
|
213
|
+
signal: null,
|
|
214
|
+
evidence: { phase: type, reason: boundedMessage(frame.reason) ?? null },
|
|
215
|
+
severity: "warn",
|
|
216
|
+
semantic: false,
|
|
217
|
+
coalesceKey: null,
|
|
218
|
+
};
|
|
219
|
+
case "ttsr_triggered":
|
|
220
|
+
return {
|
|
221
|
+
kind: "rpc_ttsr",
|
|
222
|
+
signal: "error",
|
|
223
|
+
evidence: { reason: boundedMessage(frame.reason) ?? null },
|
|
224
|
+
severity: "warn",
|
|
225
|
+
semantic: true,
|
|
226
|
+
coalesceKey: null,
|
|
227
|
+
};
|
|
228
|
+
case "todo_reminder":
|
|
229
|
+
case "todo_auto_clear":
|
|
230
|
+
return {
|
|
231
|
+
kind: "rpc_todo",
|
|
232
|
+
signal: null,
|
|
233
|
+
evidence: { phase: type },
|
|
234
|
+
severity: "info",
|
|
235
|
+
semantic: false,
|
|
236
|
+
coalesceKey: null,
|
|
237
|
+
};
|
|
238
|
+
case "extension_ui_request":
|
|
239
|
+
return {
|
|
240
|
+
kind: "rpc_extension_request",
|
|
241
|
+
signal: "tool-call",
|
|
242
|
+
evidence: { method: str(frame.method) ?? null },
|
|
243
|
+
severity: "info",
|
|
244
|
+
semantic: false,
|
|
245
|
+
coalesceKey: null,
|
|
246
|
+
};
|
|
247
|
+
case "extension_error":
|
|
248
|
+
return {
|
|
249
|
+
kind: "rpc_extension_error",
|
|
250
|
+
signal: "error",
|
|
251
|
+
evidence: {
|
|
252
|
+
code: str(frame.error) ? boundedMessage(frame.error) : null,
|
|
253
|
+
extensionPath: str(frame.extensionPath) ?? null,
|
|
254
|
+
},
|
|
255
|
+
severity: "critical",
|
|
256
|
+
semantic: true,
|
|
257
|
+
coalesceKey: null,
|
|
258
|
+
};
|
|
259
|
+
case "agent_end": {
|
|
260
|
+
const failed =
|
|
261
|
+
Boolean(frame.error) ||
|
|
262
|
+
frame.aborted === true ||
|
|
263
|
+
str(frame.outcome) === "failed" ||
|
|
264
|
+
str(frame.outcome) === "aborted";
|
|
265
|
+
return failed
|
|
266
|
+
? {
|
|
267
|
+
kind: "rpc_agent_failed",
|
|
268
|
+
signal: "error",
|
|
269
|
+
evidence: { outcome: str(frame.outcome) ?? "failed" },
|
|
270
|
+
severity: "critical",
|
|
271
|
+
semantic: true,
|
|
272
|
+
coalesceKey: null,
|
|
273
|
+
}
|
|
274
|
+
: {
|
|
275
|
+
kind: "rpc_agent_completed",
|
|
276
|
+
signal: "completed",
|
|
277
|
+
evidence: { outcome: "completed" },
|
|
278
|
+
severity: "info",
|
|
279
|
+
semantic: true,
|
|
280
|
+
coalesceKey: null,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
default:
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
}
|