@gajae-code/coding-agent 0.3.0 → 0.3.2
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 +32 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +3 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +8 -0
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +47 -0
- package/dist/types/config/settings-schema.d.ts +14 -4
- 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 +1 -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/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-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- 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/main.d.ts +10 -1
- 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/custom-provider-wizard.d.ts +10 -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/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- 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 +9 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -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 +3 -1
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +1 -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 +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +6 -4
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +19 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +10 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +19 -0
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +36 -0
- package/src/config/settings-schema.ts +16 -3
- 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 +6 -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/ultragoal/SKILL.md +39 -3
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- 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/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +231 -38
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- 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 +8 -10
- 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 +88 -6
- 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/custom-editor.ts +101 -0
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- 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/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +86 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -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/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +5 -1
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +9 -1
- package/src/task/executor.ts +31 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +259 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- 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/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +6 -18
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +4 -3
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
|
@@ -13,10 +13,16 @@ declare const subagentSchema: z.ZodObject<{
|
|
|
13
13
|
steer: "steer";
|
|
14
14
|
}>;
|
|
15
15
|
ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
16
|
+
id: z.ZodOptional<z.ZodString>;
|
|
16
17
|
message: z.ZodOptional<z.ZodString>;
|
|
17
18
|
pause: z.ZodOptional<z.ZodBoolean>;
|
|
18
19
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
19
20
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
21
|
+
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
22
|
+
full: "full";
|
|
23
|
+
preview: "preview";
|
|
24
|
+
receipt: "receipt";
|
|
25
|
+
}>>;
|
|
20
26
|
}, z.core.$strip>;
|
|
21
27
|
type SubagentParams = z.infer<typeof subagentSchema>;
|
|
22
28
|
type SubagentStatus = "running" | "paused" | "queued" | "completed" | "failed" | "cancelled" | "not_found" | "already_completed";
|
|
@@ -32,6 +38,9 @@ export interface SubagentSnapshot {
|
|
|
32
38
|
durationMs: number;
|
|
33
39
|
resultText?: string;
|
|
34
40
|
errorText?: string;
|
|
41
|
+
resultPreview?: string;
|
|
42
|
+
outputRef?: string;
|
|
43
|
+
truncated?: boolean;
|
|
35
44
|
guidance?: string;
|
|
36
45
|
}
|
|
37
46
|
export interface SubagentToolDetails {
|
|
@@ -55,10 +64,16 @@ export declare class SubagentTool implements AgentTool<typeof subagentSchema, Su
|
|
|
55
64
|
steer: "steer";
|
|
56
65
|
}>;
|
|
57
66
|
ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
67
|
+
id: z.ZodOptional<z.ZodString>;
|
|
58
68
|
message: z.ZodOptional<z.ZodString>;
|
|
59
69
|
pause: z.ZodOptional<z.ZodBoolean>;
|
|
60
70
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
61
71
|
limit: z.ZodOptional<z.ZodNumber>;
|
|
72
|
+
verbosity: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
full: "full";
|
|
74
|
+
preview: "preview";
|
|
75
|
+
receipt: "receipt";
|
|
76
|
+
}>>;
|
|
62
77
|
}, z.core.$strip>;
|
|
63
78
|
readonly strict = true;
|
|
64
79
|
readonly loadMode = "discoverable";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.2",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
49
49
|
"@babel/parser": "^7.29.3",
|
|
50
50
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@gajae-code/stats": "0.3.
|
|
52
|
-
"@gajae-code/agent-core": "0.3.
|
|
53
|
-
"@gajae-code/ai": "0.3.
|
|
54
|
-
"@gajae-code/natives": "0.3.
|
|
55
|
-
"@gajae-code/tui": "0.3.
|
|
56
|
-
"@gajae-code/utils": "0.3.
|
|
51
|
+
"@gajae-code/stats": "0.3.2",
|
|
52
|
+
"@gajae-code/agent-core": "0.3.2",
|
|
53
|
+
"@gajae-code/ai": "0.3.2",
|
|
54
|
+
"@gajae-code/natives": "0.3.2",
|
|
55
|
+
"@gajae-code/tui": "0.3.2",
|
|
56
|
+
"@gajae-code/utils": "0.3.2",
|
|
57
57
|
"@puppeteer/browsers": "^2.13.0",
|
|
58
58
|
"@types/turndown": "5.0.6",
|
|
59
59
|
"@xterm/headless": "^6.0.0",
|
package/scripts/build-binary.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
|
|
5
5
|
const packageDir = path.join(import.meta.dir, "..");
|
|
6
6
|
const outputPath = path.join(packageDir, "dist", "gjc");
|
|
7
|
+
const nativeDir = path.join(packageDir, "..", "natives", "native");
|
|
7
8
|
|
|
8
9
|
function shouldAdhocSignDarwinBinary(): boolean {
|
|
9
10
|
return process.platform === "darwin";
|
|
@@ -21,6 +22,11 @@ async function runCommand(command: string[], env: NodeJS.ProcessEnv = Bun.env):
|
|
|
21
22
|
throw new Error(`Command failed with exit code ${exitCode}: ${command.join(" ")}`);
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
async function stageWorkspaceNativeAddons(): Promise<void> {
|
|
26
|
+
await Array.fromAsync(new Bun.Glob("pi_natives.*.node").scan({ cwd: nativeDir }), async filename => {
|
|
27
|
+
await Bun.write(path.join(packageDir, "dist", filename), Bun.file(path.join(nativeDir, filename)));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
async function main(): Promise<void> {
|
|
26
32
|
await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
|
|
@@ -62,6 +68,7 @@ async function main(): Promise<void> {
|
|
|
62
68
|
buildEnv,
|
|
63
69
|
);
|
|
64
70
|
|
|
71
|
+
await stageWorkspaceNativeAddons();
|
|
65
72
|
// Bun 1.3.12 emits a truncated Mach-O signature on darwin builds.
|
|
66
73
|
if (shouldAdhocSignDarwinBinary()) {
|
|
67
74
|
await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
|
package/src/async/job-manager.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface AsyncJobMetadata {
|
|
|
35
35
|
description?: string;
|
|
36
36
|
assignment?: string;
|
|
37
37
|
};
|
|
38
|
+
/** True when this bash job was started by the `monitor` tool (vs plain async bash). */
|
|
39
|
+
monitor?: boolean;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
/**
|
|
@@ -224,6 +226,12 @@ export class AsyncJobManager {
|
|
|
224
226
|
#resumeSeq = 0;
|
|
225
227
|
#resumeRunner?: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined;
|
|
226
228
|
readonly #resumeDescriptors = new Map<string, ResumeDescriptor>();
|
|
229
|
+
/**
|
|
230
|
+
* Change listeners notified on any mutation that can alter the live job set
|
|
231
|
+
* (register, terminal/eviction transitions, dispose). Used by the status-line
|
|
232
|
+
* jobs widget / overlay to refresh event-driven without polling.
|
|
233
|
+
*/
|
|
234
|
+
readonly #changeListeners = new Set<() => void>();
|
|
227
235
|
|
|
228
236
|
#filterJobs(jobs: Iterable<AsyncJob>, filter?: AsyncJobFilter): AsyncJob[] {
|
|
229
237
|
const ownerId = filter?.ownerId;
|
|
@@ -241,6 +249,29 @@ export class AsyncJobManager {
|
|
|
241
249
|
this.#retentionMs = Math.max(0, Math.floor(options.retentionMs ?? DEFAULT_RETENTION_MS));
|
|
242
250
|
}
|
|
243
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Subscribe to live-job-set change events. Returns an unsubscribe function.
|
|
254
|
+
* Listener errors are isolated so one bad subscriber cannot break others.
|
|
255
|
+
*/
|
|
256
|
+
onChange(cb: () => void): () => void {
|
|
257
|
+
this.#changeListeners.add(cb);
|
|
258
|
+
return () => {
|
|
259
|
+
this.#changeListeners.delete(cb);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
#notifyChange(): void {
|
|
264
|
+
for (const cb of this.#changeListeners) {
|
|
265
|
+
try {
|
|
266
|
+
cb();
|
|
267
|
+
} catch (error) {
|
|
268
|
+
logger.warn("Async job change listener failed", {
|
|
269
|
+
error: error instanceof Error ? error.message : String(error),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
244
275
|
register(
|
|
245
276
|
type: "bash" | "task",
|
|
246
277
|
label: string,
|
|
@@ -336,6 +367,7 @@ export class AsyncJobManager {
|
|
|
336
367
|
})();
|
|
337
368
|
|
|
338
369
|
this.#jobs.set(id, job);
|
|
370
|
+
this.#notifyChange();
|
|
339
371
|
return id;
|
|
340
372
|
}
|
|
341
373
|
|
|
@@ -880,6 +912,8 @@ export class AsyncJobManager {
|
|
|
880
912
|
this.#liveHandles.clear();
|
|
881
913
|
this.#resumeDescriptors.clear();
|
|
882
914
|
this.#resumeQueue.length = 0;
|
|
915
|
+
this.#notifyChange();
|
|
916
|
+
this.#changeListeners.clear();
|
|
883
917
|
return drained;
|
|
884
918
|
}
|
|
885
919
|
|
|
@@ -909,6 +943,7 @@ export class AsyncJobManager {
|
|
|
909
943
|
}
|
|
910
944
|
|
|
911
945
|
#scheduleEviction(jobId: string): void {
|
|
946
|
+
this.#notifyChange();
|
|
912
947
|
if (this.#retentionMs <= 0) {
|
|
913
948
|
this.#jobs.delete(jobId);
|
|
914
949
|
this.#suppressedDeliveries.delete(jobId);
|
|
@@ -926,6 +961,7 @@ export class AsyncJobManager {
|
|
|
926
961
|
this.#suppressedDeliveries.delete(jobId);
|
|
927
962
|
this.#watchedJobs.delete(jobId);
|
|
928
963
|
this.#outputState.delete(jobId);
|
|
964
|
+
this.#notifyChange();
|
|
929
965
|
}, this.#retentionMs);
|
|
930
966
|
timer.unref();
|
|
931
967
|
this.#evictionTimers.set(jobId, timer);
|
package/src/cli/args.ts
CHANGED
|
@@ -7,7 +7,7 @@ import chalk from "chalk";
|
|
|
7
7
|
import { parseEffort } from "../thinking";
|
|
8
8
|
import { BUILTIN_TOOLS } from "../tools";
|
|
9
9
|
|
|
10
|
-
export type Mode = "text" | "json" | "rpc" | "acp" | "rpc-ui";
|
|
10
|
+
export type Mode = "text" | "json" | "rpc" | "acp" | "rpc-ui" | "bridge";
|
|
11
11
|
|
|
12
12
|
export interface Args {
|
|
13
13
|
cwd?: string;
|
|
@@ -17,6 +17,8 @@ export interface Args {
|
|
|
17
17
|
smol?: string;
|
|
18
18
|
slow?: string;
|
|
19
19
|
plan?: string;
|
|
20
|
+
mpreset?: string;
|
|
21
|
+
default?: boolean;
|
|
20
22
|
apiKey?: string;
|
|
21
23
|
systemPrompt?: string;
|
|
22
24
|
appendSystemPrompt?: string;
|
|
@@ -96,7 +98,14 @@ export function parseArgs(args: string[]): Args {
|
|
|
96
98
|
result.allowHome = true;
|
|
97
99
|
} else if (arg === "--mode" && i + 1 < args.length) {
|
|
98
100
|
const mode = args[++i];
|
|
99
|
-
if (
|
|
101
|
+
if (
|
|
102
|
+
mode === "text" ||
|
|
103
|
+
mode === "json" ||
|
|
104
|
+
mode === "rpc" ||
|
|
105
|
+
mode === "acp" ||
|
|
106
|
+
mode === "rpc-ui" ||
|
|
107
|
+
mode === "bridge"
|
|
108
|
+
) {
|
|
100
109
|
result.mode = mode;
|
|
101
110
|
}
|
|
102
111
|
} else if (arg === "--continue" || arg === "-c") {
|
|
@@ -120,6 +129,10 @@ export function parseArgs(args: string[]): Args {
|
|
|
120
129
|
result.slow = args[++i];
|
|
121
130
|
} else if (arg === "--plan" && i + 1 < args.length) {
|
|
122
131
|
result.plan = args[++i];
|
|
132
|
+
} else if (arg === "--mpreset" && i + 1 < args.length) {
|
|
133
|
+
result.mpreset = args[++i];
|
|
134
|
+
} else if (arg === "--default") {
|
|
135
|
+
result.default = true;
|
|
123
136
|
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
124
137
|
result.apiKey = args[++i];
|
|
125
138
|
} else if (arg === "--system-prompt" && i + 1 < args.length) {
|
|
@@ -192,6 +205,10 @@ export function parseArgs(args: string[]): Args {
|
|
|
192
205
|
}
|
|
193
206
|
}
|
|
194
207
|
|
|
208
|
+
if (result.default && !result.mpreset) {
|
|
209
|
+
throw new Error("--default requires --mpreset <name>");
|
|
210
|
+
}
|
|
211
|
+
|
|
195
212
|
return result;
|
|
196
213
|
}
|
|
197
214
|
|
|
@@ -21,6 +21,7 @@ export default class DeepInterview extends Command {
|
|
|
21
21
|
deliberate: Flags.boolean({
|
|
22
22
|
description: "Shortcut for --write handoff to ralplan in deliberate consensus mode",
|
|
23
23
|
}),
|
|
24
|
+
force: Flags.boolean({ description: "Overwrite corrupt existing deep-interview state during --write" }),
|
|
24
25
|
json: Flags.boolean({ description: "Output JSON" }),
|
|
25
26
|
};
|
|
26
27
|
static examples = [
|
package/src/commands/harness.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../harness-control-plane/storage";
|
|
28
28
|
import {
|
|
29
29
|
DEFAULT_RETRY_BUDGET,
|
|
30
|
+
type EventEnvelope,
|
|
30
31
|
type GitDelta,
|
|
31
32
|
type Harness as HarnessKind,
|
|
32
33
|
type Observation,
|
|
@@ -76,27 +77,191 @@ function gitDeltaFor(workspace: string): { gitDelta: GitDelta; branch: string |
|
|
|
76
77
|
return { gitDelta: "unknown", branch, deleted: false };
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
interface HarnessPreflight {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
blockers: string[];
|
|
83
|
+
workspace: string;
|
|
84
|
+
actualBranch: string | null;
|
|
85
|
+
declaredBranch: string | null;
|
|
86
|
+
normalizedIssueOrPr: string | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeIssueOrPr(value: unknown): string | null {
|
|
90
|
+
if (value === undefined || value === null) return null;
|
|
91
|
+
if (typeof value === "number") {
|
|
92
|
+
if (Number.isSafeInteger(value) && value > 0) return String(value);
|
|
93
|
+
throw new Error(`invalid_issue_or_pr:${value}`);
|
|
94
|
+
}
|
|
95
|
+
if (typeof value !== "string") throw new Error("invalid_issue_or_pr:not-string-or-number");
|
|
96
|
+
const trimmed = value.trim();
|
|
97
|
+
if (!trimmed) return null;
|
|
98
|
+
const patterns = [
|
|
99
|
+
/^#?(\d+)$/i,
|
|
100
|
+
/^(?:pr|pull|issue)[-_#]?(\d+)$/i,
|
|
101
|
+
/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+#(\d+)$/,
|
|
102
|
+
/^(?:https?:\/\/github\.com\/)?[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/(?:pull|issues)\/(\d+)\/?$/i,
|
|
103
|
+
];
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
const match = trimmed.match(pattern);
|
|
106
|
+
if (match?.[1]) return match[1];
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`invalid_issue_or_pr:${trimmed}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function gitOutput(workspace: string, args: string[]): string | null {
|
|
112
|
+
try {
|
|
113
|
+
return execFileSync("git", args, {
|
|
114
|
+
cwd: workspace,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
117
|
+
}).trim();
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildPreflight(input: Record<string, unknown>): HarnessPreflight {
|
|
124
|
+
const workspace = typeof input.workspace === "string" ? input.workspace : process.cwd();
|
|
125
|
+
const declaredBranch = typeof input.branch === "string" && input.branch.trim() ? input.branch.trim() : null;
|
|
126
|
+
const blockers: string[] = [];
|
|
127
|
+
const gitRoot = gitOutput(workspace, ["rev-parse", "--show-toplevel"]);
|
|
128
|
+
const actualBranch = gitRoot ? gitOutput(workspace, ["rev-parse", "--abbrev-ref", "HEAD"]) : null;
|
|
129
|
+
let normalizedIssueOrPr: string | null = null;
|
|
130
|
+
|
|
131
|
+
if (!gitRoot) blockers.push("workspace-not-git-repo");
|
|
132
|
+
if (gitRoot && actualBranch === "HEAD") blockers.push("detached-head");
|
|
133
|
+
if (declaredBranch && actualBranch && actualBranch !== "HEAD" && declaredBranch !== actualBranch) {
|
|
134
|
+
blockers.push("branch-mismatch");
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
normalizedIssueOrPr = normalizeIssueOrPr(input.issueOrPr ?? input.pr ?? input.issue);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
ok: blockers.length === 0,
|
|
144
|
+
blockers,
|
|
145
|
+
workspace,
|
|
146
|
+
actualBranch: actualBranch === "HEAD" ? null : actualBranch,
|
|
147
|
+
declaredBranch,
|
|
148
|
+
normalizedIssueOrPr,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
79
151
|
|
|
80
|
-
|
|
152
|
+
function startFatalPreflightBlockers(input: Record<string, unknown>, preflight: HarnessPreflight): string[] {
|
|
153
|
+
const strict = input.strictPreflight === true || typeof input.branch === "string";
|
|
154
|
+
return preflight.blockers.filter(blocker => {
|
|
155
|
+
if (blocker === "branch-mismatch") return true;
|
|
156
|
+
if (blocker.startsWith("invalid_issue_or_pr:")) return true;
|
|
157
|
+
if (strict && (blocker === "workspace-not-git-repo" || blocker === "detached-head")) return true;
|
|
158
|
+
return false;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Fallback liveness after owner routing failed: no reachable owner handled this CLI call. */
|
|
81
163
|
function ownerLiveFor(_state: SessionState): boolean {
|
|
82
164
|
return false;
|
|
83
165
|
}
|
|
84
166
|
|
|
85
|
-
function
|
|
167
|
+
function pushUnique(out: string[], value: unknown): void {
|
|
168
|
+
if (typeof value === "string" && !out.includes(value)) out.push(value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface CompletedTerminalEvent {
|
|
172
|
+
cursor: number;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
kind: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function completedTerminalEvent(events: EventEnvelope[]): CompletedTerminalEvent | null {
|
|
178
|
+
for (const event of [...events].reverse()) {
|
|
179
|
+
const signal = (event.evidence as { signal?: unknown } | undefined)?.signal;
|
|
180
|
+
if (event.kind === "rpc_agent_completed" || signal === "completed") {
|
|
181
|
+
return { cursor: event.cursor, createdAt: event.createdAt, kind: event.kind };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function buildObservation(
|
|
188
|
+
root: string,
|
|
189
|
+
state: SessionState,
|
|
190
|
+
ownerLive: boolean,
|
|
191
|
+
): Promise<{
|
|
192
|
+
observation: Observation;
|
|
193
|
+
completedTerminalEvent: CompletedTerminalEvent | null;
|
|
194
|
+
}> {
|
|
86
195
|
const workspace = state.handle.workspace;
|
|
87
196
|
const { gitDelta, branch, deleted } = gitDeltaFor(workspace);
|
|
197
|
+
const events = await readEvents(root, state.sessionId, 0);
|
|
198
|
+
const observedSignals = ["SessionStart"];
|
|
199
|
+
for (const event of events.slice(-200)) {
|
|
200
|
+
pushUnique(observedSignals, (event.evidence as { signal?: unknown } | undefined)?.signal);
|
|
201
|
+
}
|
|
202
|
+
const terminalEvent = completedTerminalEvent(events);
|
|
203
|
+
const lastEventAt = events.at(-1)?.createdAt;
|
|
88
204
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
205
|
+
observation: {
|
|
206
|
+
lifecycle: state.lifecycle,
|
|
207
|
+
ownerLive,
|
|
208
|
+
cwd: workspace,
|
|
209
|
+
branch: branch ?? state.handle.branch,
|
|
210
|
+
gitDelta,
|
|
211
|
+
lastActivityAt: lastEventAt ?? state.updatedAt,
|
|
212
|
+
observedSignals,
|
|
213
|
+
risk: deleted ? "deleted-worktree" : !ownerLive && gitDelta === "dirty" ? "vanished-dirty" : "normal",
|
|
214
|
+
},
|
|
215
|
+
completedTerminalEvent: terminalEvent,
|
|
97
216
|
};
|
|
98
217
|
}
|
|
99
218
|
|
|
219
|
+
function isOwnerLivenessBlocker(blocker: string): boolean {
|
|
220
|
+
return blocker === "detached-owner-not-live" || blocker.startsWith("owner-vanished:");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function reconcileCompletedOwnerExited(
|
|
224
|
+
root: string,
|
|
225
|
+
state: SessionState,
|
|
226
|
+
observation: Observation,
|
|
227
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
228
|
+
): Promise<SessionState> {
|
|
229
|
+
if (!completedTerminal || observation.ownerLive || observation.gitDelta !== "clean") return state;
|
|
230
|
+
if (state.lifecycle === "completed" || state.lifecycle === "retired") return state;
|
|
231
|
+
state.lifecycle = "completed";
|
|
232
|
+
state.blockers = state.blockers.filter(blocker => !isOwnerLivenessBlocker(blocker));
|
|
233
|
+
state.updatedAt = nowIso();
|
|
234
|
+
await writeSessionState(root, state);
|
|
235
|
+
return state;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function needsVanishedOwnerBlock(
|
|
239
|
+
state: SessionState,
|
|
240
|
+
observation: Observation,
|
|
241
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
242
|
+
): boolean {
|
|
243
|
+
if (observation.ownerLive || state.lifecycle !== "observing") return false;
|
|
244
|
+
if (completedTerminal || observation.observedSignals.includes("completed")) return false;
|
|
245
|
+
return observation.observedSignals.some(
|
|
246
|
+
signal => signal === "prompt-accepted" || signal === "tool-call" || signal === "streaming",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function markVanishedOwnerBlocked(
|
|
251
|
+
root: string,
|
|
252
|
+
state: SessionState,
|
|
253
|
+
observation: Observation,
|
|
254
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
255
|
+
): Promise<SessionState> {
|
|
256
|
+
if (!needsVanishedOwnerBlock(state, observation, completedTerminal)) return state;
|
|
257
|
+
const blocker = `owner-vanished:${observation.gitDelta}`;
|
|
258
|
+
state.lifecycle = "blocked";
|
|
259
|
+
state.blockers = state.blockers.includes(blocker) ? state.blockers : [...state.blockers, blocker];
|
|
260
|
+
state.updatedAt = nowIso();
|
|
261
|
+
await writeSessionState(root, state);
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
264
|
+
|
|
100
265
|
function resolveRetryBudget(input: Record<string, unknown>): RetryBudget {
|
|
101
266
|
const supplied = input.retryBudget;
|
|
102
267
|
if (supplied && typeof supplied === "object" && !Array.isArray(supplied)) {
|
|
@@ -139,7 +304,7 @@ export default class Harness extends Command {
|
|
|
139
304
|
|
|
140
305
|
static args = {
|
|
141
306
|
verb: Args.string({
|
|
142
|
-
description: "start|submit|observe|classify|recover|validate|finalize|retire|events|monitor|operate",
|
|
307
|
+
description: "start|preflight|submit|observe|classify|recover|validate|finalize|retire|events|monitor|operate",
|
|
143
308
|
required: true,
|
|
144
309
|
}),
|
|
145
310
|
};
|
|
@@ -168,6 +333,8 @@ export default class Harness extends Command {
|
|
|
168
333
|
switch (verb) {
|
|
169
334
|
case "start":
|
|
170
335
|
return await this.#start(root, input);
|
|
336
|
+
case "preflight":
|
|
337
|
+
return this.#preflight(input);
|
|
171
338
|
case "observe":
|
|
172
339
|
return await this.#observe(root, input, flags.session);
|
|
173
340
|
case "classify":
|
|
@@ -196,6 +363,20 @@ export default class Harness extends Command {
|
|
|
196
363
|
}
|
|
197
364
|
}
|
|
198
365
|
|
|
366
|
+
#preflight(input: Record<string, unknown>): void {
|
|
367
|
+
const preflight = buildPreflight(input);
|
|
368
|
+
writeJson({
|
|
369
|
+
ok: preflight.ok,
|
|
370
|
+
evidence: {
|
|
371
|
+
preflight,
|
|
372
|
+
guidance: preflight.ok
|
|
373
|
+
? "workspace metadata is normalized"
|
|
374
|
+
: "fix blockers before gjc harness start; branch must match the actual checkout and issueOrPr must be numeric or a recognized PR/issue form",
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
if (!preflight.ok) process.exitCode = 1;
|
|
378
|
+
}
|
|
379
|
+
|
|
199
380
|
async #finalizeVerb(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
200
381
|
const sessionId = requireSessionId(input, flagSession);
|
|
201
382
|
if (await this.#tryOwnerRoute(root, sessionId, "finalize", { ...input, sessionId })) return;
|
|
@@ -214,6 +395,7 @@ export default class Harness extends Command {
|
|
|
214
395
|
): Promise<void> {
|
|
215
396
|
const sessionId = flagSession ?? (typeof input.sessionId === "string" ? input.sessionId : undefined);
|
|
216
397
|
if (sessionId && (await this.#tryOwnerRoute(root, sessionId, verb, { ...input, sessionId }))) return;
|
|
398
|
+
if (verb === "recover" && sessionId) return this.#recoverWithoutOwner(root, sessionId, input);
|
|
217
399
|
return this.#pending(root, verb, input, flagSession);
|
|
218
400
|
}
|
|
219
401
|
|
|
@@ -343,6 +525,21 @@ export default class Harness extends Command {
|
|
|
343
525
|
process.exitCode = 1;
|
|
344
526
|
return;
|
|
345
527
|
}
|
|
528
|
+
const preflight = buildPreflight(input);
|
|
529
|
+
const fatalBlockers = startFatalPreflightBlockers(input, preflight);
|
|
530
|
+
if (fatalBlockers.length > 0) {
|
|
531
|
+
writeJson({
|
|
532
|
+
ok: false,
|
|
533
|
+
error: "harness_preflight_failed",
|
|
534
|
+
evidence: {
|
|
535
|
+
preflight: { ...preflight, blockers: fatalBlockers, ok: false },
|
|
536
|
+
guidance:
|
|
537
|
+
"fix blockers before start; run gjc harness preflight with the same input for branch and issue/PR diagnostics",
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
process.exitCode = 1;
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
346
543
|
const workspace = typeof input.workspace === "string" ? input.workspace : process.cwd();
|
|
347
544
|
const sessionId = typeof input.sessionId === "string" ? input.sessionId : generateSessionId();
|
|
348
545
|
const eventsPath = `${root}/sessions/${sessionId}/events.jsonl`;
|
|
@@ -353,9 +550,9 @@ export default class Harness extends Command {
|
|
|
353
550
|
harness,
|
|
354
551
|
repo: typeof input.repo === "string" ? input.repo : null,
|
|
355
552
|
workspace,
|
|
356
|
-
branch:
|
|
553
|
+
branch: preflight.declaredBranch ?? preflight.actualBranch,
|
|
357
554
|
base: typeof input.base === "string" ? input.base : null,
|
|
358
|
-
issueOrPr:
|
|
555
|
+
issueOrPr: preflight.normalizedIssueOrPr,
|
|
359
556
|
processHandle: { kind: "runtime-owner", ownerId: null, pid: null },
|
|
360
557
|
rpcHandle: { kind: "rpc-subprocess", pid: null, sessionDir: `${root}/sessions/${sessionId}/gjc-session` },
|
|
361
558
|
ownerHandle: { leasePath, endpoint: null, heartbeatAt: null },
|
|
@@ -407,6 +604,25 @@ export default class Harness extends Command {
|
|
|
407
604
|
await writeSessionState(root, state);
|
|
408
605
|
}
|
|
409
606
|
}
|
|
607
|
+
if (ownerBlockerReason) {
|
|
608
|
+
const resolved = await resolveOwner(root, sessionId);
|
|
609
|
+
if (resolved.live && resolved.socketPath) {
|
|
610
|
+
ownerLive = true;
|
|
611
|
+
ownerBlockerReason = null;
|
|
612
|
+
handle.processHandle = {
|
|
613
|
+
kind: "runtime-owner",
|
|
614
|
+
ownerId: resolved.lease?.ownerId ?? null,
|
|
615
|
+
pid: resolved.lease?.pid ?? null,
|
|
616
|
+
};
|
|
617
|
+
handle.ownerHandle = {
|
|
618
|
+
leasePath,
|
|
619
|
+
endpoint: resolved.socketPath,
|
|
620
|
+
heartbeatAt: resolved.lease?.heartbeatAt ?? null,
|
|
621
|
+
};
|
|
622
|
+
state.handle = handle;
|
|
623
|
+
await writeSessionState(root, state);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
410
626
|
if (ownerBlockerReason) {
|
|
411
627
|
state.lifecycle = "blocked";
|
|
412
628
|
state.blockers = [...state.blockers, ownerBlockerReason];
|
|
@@ -421,6 +637,7 @@ export default class Harness extends Command {
|
|
|
421
637
|
{
|
|
422
638
|
handle,
|
|
423
639
|
ownerRuntime,
|
|
640
|
+
preflight,
|
|
424
641
|
...(ownerFallbackReason ? { ownerFallbackReason } : {}),
|
|
425
642
|
...(ownerBlockerReason ? { reason: ownerBlockerReason } : {}),
|
|
426
643
|
},
|
|
@@ -453,10 +670,24 @@ export default class Harness extends Command {
|
|
|
453
670
|
async #observe(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
454
671
|
const sessionId = requireSessionId(input, flagSession);
|
|
455
672
|
if (await this.#tryOwnerRoute(root, sessionId, "observe", { ...input, sessionId })) return;
|
|
456
|
-
|
|
673
|
+
let state = await loadState(root, sessionId);
|
|
457
674
|
const ownerLive = ownerLiveFor(state);
|
|
458
|
-
const observation = buildObservation(state, ownerLive);
|
|
459
|
-
|
|
675
|
+
const { observation, completedTerminalEvent } = await buildObservation(root, state, ownerLive);
|
|
676
|
+
state = await reconcileCompletedOwnerExited(root, state, observation, completedTerminalEvent);
|
|
677
|
+
const vanishedOwnerBlock = needsVanishedOwnerBlock(state, observation, completedTerminalEvent);
|
|
678
|
+
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
679
|
+
writeJson(
|
|
680
|
+
buildResponse(state, ownerLive, {
|
|
681
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
682
|
+
readOnly: !ownerLive,
|
|
683
|
+
...(vanishedOwnerBlock
|
|
684
|
+
? { ownerVanished: true, blockerReason: `owner-vanished:${observation.gitDelta}` }
|
|
685
|
+
: {}),
|
|
686
|
+
...(completedTerminalEvent && !ownerLive
|
|
687
|
+
? { completedOwnerExited: true, terminalResult: completedTerminalEvent }
|
|
688
|
+
: {}),
|
|
689
|
+
}),
|
|
690
|
+
);
|
|
460
691
|
}
|
|
461
692
|
|
|
462
693
|
async #classify(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
@@ -466,7 +697,16 @@ export default class Harness extends Command {
|
|
|
466
697
|
const sessionId = flagSession ?? (typeof input.sessionId === "string" ? input.sessionId : undefined);
|
|
467
698
|
if (sessionId) {
|
|
468
699
|
stateView = await loadState(root, sessionId);
|
|
469
|
-
if (!observation)
|
|
700
|
+
if (!observation) {
|
|
701
|
+
const built = await buildObservation(root, stateView, ownerLiveFor(stateView));
|
|
702
|
+
observation = built.observation;
|
|
703
|
+
stateView = await markVanishedOwnerBlocked(
|
|
704
|
+
root,
|
|
705
|
+
stateView,
|
|
706
|
+
built.observation,
|
|
707
|
+
built.completedTerminalEvent,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
470
710
|
}
|
|
471
711
|
if (!observation) throw new Error("classify_requires_observation_or_session");
|
|
472
712
|
const full: Observation = {
|
|
@@ -481,7 +721,12 @@ export default class Harness extends Command {
|
|
|
481
721
|
};
|
|
482
722
|
const decision = classifyRecovery({ observation: full, retryBudget: budget });
|
|
483
723
|
if (stateView) {
|
|
484
|
-
writeJson(
|
|
724
|
+
writeJson(
|
|
725
|
+
buildResponse(stateView, ownerLiveFor(stateView), {
|
|
726
|
+
decision,
|
|
727
|
+
observation: { ...full, lifecycle: stateView.lifecycle },
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
485
730
|
return;
|
|
486
731
|
}
|
|
487
732
|
// Pure classify without a session: synthesize a minimal state view.
|
|
@@ -531,7 +776,7 @@ export default class Harness extends Command {
|
|
|
531
776
|
const sessionId = requireSessionId(input, flagSession);
|
|
532
777
|
if (await this.#tryOwnerRoute(root, sessionId, "retire", { ...input, sessionId })) return;
|
|
533
778
|
const state = await loadState(root, sessionId);
|
|
534
|
-
const observation = buildObservation(state, ownerLiveFor(state));
|
|
779
|
+
const { observation } = await buildObservation(root, state, ownerLiveFor(state));
|
|
535
780
|
if (observation.gitDelta === "dirty" || observation.gitDelta === "unknown") {
|
|
536
781
|
writeJson(
|
|
537
782
|
buildResponse(
|
|
@@ -554,6 +799,31 @@ export default class Harness extends Command {
|
|
|
554
799
|
writeJson(buildResponse(state, false, { retired: true }));
|
|
555
800
|
}
|
|
556
801
|
|
|
802
|
+
async #recoverWithoutOwner(root: string, sessionId: string, input: Record<string, unknown>): Promise<void> {
|
|
803
|
+
const budget = resolveRetryBudget(input);
|
|
804
|
+
let state = await loadState(root, sessionId);
|
|
805
|
+
const { observation, completedTerminalEvent } = await buildObservation(root, state, false);
|
|
806
|
+
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
807
|
+
const decision = classifyRecovery({
|
|
808
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
809
|
+
retryBudget: budget,
|
|
810
|
+
});
|
|
811
|
+
writeJson(
|
|
812
|
+
buildResponse(
|
|
813
|
+
state,
|
|
814
|
+
false,
|
|
815
|
+
{
|
|
816
|
+
pending: false,
|
|
817
|
+
reason: "owner-not-live",
|
|
818
|
+
decision,
|
|
819
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
820
|
+
},
|
|
821
|
+
false,
|
|
822
|
+
),
|
|
823
|
+
);
|
|
824
|
+
process.exitCode = 1;
|
|
825
|
+
}
|
|
826
|
+
|
|
557
827
|
async #pending(
|
|
558
828
|
root: string,
|
|
559
829
|
verb: string,
|