@deksden-com/dd-flow-cli 0.9.0-beta.123 → 0.9.0-beta.127
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 +26 -0
- package/README.md +33 -1
- package/dist/build-info.json +7 -7
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +4 -3
- package/dist/cli/help.js +1 -0
- package/dist/cli/hook-ingress.js +1 -1
- package/dist/cli/run-cli.js +126 -193
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +51 -0
- package/dist/cli/runtime-process.js +128 -0
- package/dist/cli.js +8 -5
- package/dist/domain/stage-catalog.js +1 -1
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
- package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
- package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
- package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
- package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +159 -15
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +215 -86
- package/dist/harness-runtime/lib/dd-agy.mjs +11 -13
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +47 -23
- package/dist/harness-runtime/lib/dd-codex.mjs +257 -99
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +32 -30
- package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +96 -38
- package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +54 -20
- package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +81 -76
- package/dist/harness-runtime/lib/dd-zcode.mjs +204 -116
- package/dist/harness-runtime/lib/driver-recovery.mjs +12 -2
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
- package/dist/harness-runtime/lib/managed-daemon.d.mts +11 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +321 -65
- package/dist/harness-runtime/lib/native-children.d.mts +13 -0
- package/dist/harness-runtime/lib/native-children.mjs +78 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
- package/dist/harness-runtime/lib/process-json.mjs +136 -38
- package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
- package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/schemas/plan-review-decision.schema.json +3 -3
- package/dist/schemas/run-control-receipt.schema.json +19 -0
- package/dist/services/code-checks.js +93 -27
- package/dist/services/codex-capacity.js +251 -74
- package/dist/services/codex-hook-delivery.js +2 -2
- package/dist/services/compatibility-preflight.js +4 -1
- package/dist/services/controller-fanout.js +9 -75
- package/dist/services/engines.js +70 -20
- package/dist/services/eval-snapshots.js +179 -40
- package/dist/services/external-work-launch.js +21 -9
- package/dist/services/harness-adapter.js +195 -25
- package/dist/services/hooks.js +65 -21
- package/dist/services/lifecycle-invocations.js +404 -32
- package/dist/services/managed-lease-monitor.js +128 -0
- package/dist/services/managed-processes.js +117 -53
- package/dist/services/merge-server.js +66 -23
- package/dist/services/recovery-observation-budget.js +67 -7
- package/dist/services/recovery-snapshot-database.js +7 -4
- package/dist/services/review-deferral.js +23 -0
- package/dist/services/run-control-receipt.js +1 -1
- package/dist/services/run-control-worker.js +177 -118
- package/dist/services/run-control.js +99 -8
- package/dist/services/run-controller-adapter.js +177 -51
- package/dist/services/run-controller-capture.js +14 -2
- package/dist/services/run-controller-process.js +11 -4
- package/dist/services/run-controller-recovery.js +17 -0
- package/dist/services/run-controller-state.js +68 -7
- package/dist/services/run-controller.js +207 -107
- package/dist/services/run-fork.js +8 -3
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +21 -30
- package/dist/services/run-recovery.js +17 -13
- package/dist/services/runs.js +40 -7
- package/dist/services/runtime-budget.js +32 -15
- package/dist/services/runtime-scope-control.js +8 -0
- package/dist/services/runtime-scope-resume.js +30 -8
- package/dist/services/runtime-scope-worker.js +118 -73
- package/dist/services/runtime-service.js +6 -7
- package/dist/services/scope-writer-cleanup.js +3 -2
- package/dist/services/stage-context.js +135 -0
- package/dist/services/stage-lifecycle.js +3 -15
- package/dist/services/stage-pause.js +3 -2
- package/dist/services/usage.js +14 -3
- package/dist/services/verification-matrix.js +30 -1
- package/dist/services/vnext-code-review.js +26 -13
- package/dist/services/vnext-code.js +10 -8
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +75 -2
- package/dist/services/vnext-fanout.js +5 -0
- package/dist/services/vnext-merge.js +30 -11
- package/dist/services/vnext-plan-review.js +102 -35
- package/dist/services/vnext-plan.js +9 -7
- package/dist/services/vnext-protocolize.js +15 -9
- package/dist/services/vnext-specify.js +6 -6
- package/dist/services/work-registry.js +43 -6
- package/dist/services/workspace-bootstrap.js +14 -3
- package/dist/shared/errors.js +11 -6
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +80 -21
- package/package.json +7 -4
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
3
2
|
import os from "node:os";
|
|
4
3
|
import path from "node:path";
|
|
@@ -12,23 +11,21 @@ export class AgyError extends Error {
|
|
|
12
11
|
constructor(code, message, retryable = false, details) { super(message); this.code = code; this.retryable = retryable; this.details = details; }
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
export function agyTerminalFailure(result, metadata = {}) {
|
|
15
|
+
const message = String(result.error ?? "Antigravity returned ERROR"), normalized = message.toLowerCase();
|
|
16
|
+
const code = result.status === "CANCELLED" ? "agy_turn_cancelled" : /quota|insufficient.?credit|billing|usage limit/.test(normalized) ? "agy_provider_quota_exhausted"
|
|
17
|
+
: /rate.?limit|too many requests/.test(normalized) ? "agy_provider_rate_limited" : /\b429\b/.test(normalized) ? "agy_provider_limit_unknown"
|
|
18
|
+
: /auth|unauthori[sz]ed|forbidden|account|eligib/.test(normalized) ? "agy_provider_account" : "agy_provider_failed";
|
|
19
|
+
return new AgyError(code, message, code === "agy_provider_rate_limited", { provider_result: result, ...metadata });
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
export function executable(bin, args = []) {
|
|
16
23
|
return /\.[cm]?js$/.test(bin) ? { command: process.execPath, args: [bin, ...args] } : { command: bin, args };
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
export async function runAgy(bin, args, options = {}) {
|
|
20
|
-
const
|
|
21
|
-
return
|
|
22
|
-
const child = spawn(target.command, target.args, { cwd: options.cwd, env: options.env ?? process.env, stdio: ["pipe", "pipe", "pipe"] });
|
|
23
|
-
let stdout = "", stderr = "", settled = false;
|
|
24
|
-
const timer = setTimeout(() => { child.kill("SIGTERM"); finish(new AgyError("agy_timeout", `${args[0] ?? "agy"} timed out`, true)); }, options.timeoutMs ?? 30_000);
|
|
25
|
-
const finish = (error, value) => { if (settled) return; settled = true; clearTimeout(timer); error ? reject(error) : resolve(value); };
|
|
26
|
-
child.stdout.setEncoding("utf8").on("data", chunk => stdout += chunk);
|
|
27
|
-
child.stderr.setEncoding("utf8").on("data", chunk => stderr += chunk);
|
|
28
|
-
child.on("error", error => finish(error));
|
|
29
|
-
child.on("close", code => code === 0 ? finish(null, { stdout, stderr, code }) : finish(new AgyError("agy_cli_failed", stderr.trim() || stdout.trim() || `agy exited ${code}`, false, { exit_code: code })));
|
|
30
|
-
child.stdin.end(options.stdin ?? "");
|
|
31
|
-
});
|
|
27
|
+
const stdout = await commandText(bin, args, { cwd: options.cwd, env: options.env, input: options.stdin ?? "", timeoutMs: options.timeoutMs, phase: "native-doctor" });
|
|
28
|
+
return { stdout, stderr: "", code: 0 };
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
export function usageSnapshot(result = {}, toolCalls = {}) {
|
|
@@ -58,3 +55,4 @@ export async function doctor(options = {}) {
|
|
|
58
55
|
return { compatible: true, observed_runtime: { agy: version, dd_harness_contract: "dd-agy-harness@1" }, isolation: { gemini_dir: "verified", app_data_dir: "verified" }, auth: "available", models: modelIds };
|
|
59
56
|
} finally { await rm(temporary, { recursive: true, force: true }); }
|
|
60
57
|
}
|
|
58
|
+
import { commandText } from "./process-json.mjs";
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { callDaemonObserved } from './daemon-observation.mjs';
|
|
2
|
+
import { admitNativeRequestBuffer } from "./native-streams.mjs";
|
|
1
3
|
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
2
4
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
5
|
import { chmod, lstat, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
6
|
import net from "node:net";
|
|
5
|
-
import { durableDaemonDispatch } from "./daemon-operations.mjs";
|
|
6
|
-
import {
|
|
7
|
+
import { durableDaemonDispatch, completeDaemonShutdown, assertDaemonDispatch, assertDaemonRestartable, trackDaemonConnections, retireDaemonEndpoint, retireFailedDaemon } from "./daemon-operations.mjs";
|
|
8
|
+
import { ObservationClock } from "./observation-clock.mjs";
|
|
9
|
+
import { parseCapacityOperation } from "./codex-capacity-policy.mjs";
|
|
7
10
|
import path from "node:path";
|
|
8
11
|
import { spawn } from "node:child_process";
|
|
9
12
|
import { waitForSettlement } from "./session-settlement.mjs";
|
|
@@ -69,38 +72,31 @@ export async function cancelCodexTree(bridge, options, { remember = () => {}, ac
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
72
|
-
const
|
|
73
|
-
return await
|
|
74
|
-
const client = net.createConnection(target.socket); let buffer = ""; let settled = false; const timer = operation === "session.prompt" ? null : observedTimeout(() => finish(Object.assign(new Error(`${operation} timed out`), { code: "daemon_timeout", retryable: true })), timeoutMs);
|
|
75
|
-
const finish = (error, value) => { if (settled) return; settled = true; clearTimeout(timer); client.destroy(); error ? reject(error) : resolve(value); };
|
|
76
|
-
client.setEncoding("utf8"); client.on("connect", () => client.write(`${JSON.stringify(request)}\n`)); client.on("error", (error) => finish(Object.assign(error, { code: "daemon_not_running", retryable: true })));
|
|
77
|
-
client.on("end", () => finish(Object.assign(new Error("daemon closed the request without a response"), { code: "daemon_connection_closed", retryable: true })));
|
|
78
|
-
client.on("close", () => finish(Object.assign(new Error("daemon connection closed without a response"), { code: "daemon_connection_closed", retryable: true })));
|
|
79
|
-
client.on("data", (chunk) => { buffer += chunk; const at = buffer.indexOf("\n"); if (at < 0) return; try { const response = JSON.parse(buffer.slice(0, at)); if (response.schema_id !== responseSchema || response.id !== request.id) throw new Error("invalid daemon response"); response.ok ? finish(null, response.result) : finish(Object.assign(new Error(response.error?.message ?? "daemon error"), response.error)); } catch (error) { finish(Object.assign(error, { code: "daemon_protocol" })); } });
|
|
80
|
-
});
|
|
75
|
+
const request = { schema_id: requestSchema, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
76
|
+
return await callDaemonObserved({ stateDir, socket: locations(stateDir).socket, request, responseSchema: responseSchema, harness: 'codex', timeoutMs });
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
export async function startDaemon(options) {
|
|
84
80
|
const paths = locations(options.stateDir); const cwd = absolute(options.cwd, "--cwd"); const config = { cwd, journal: absolute(options.journal, "--journal"), bin: options.bin ?? null, ddFlowBin: options.ddFlowBin ? absolute(options.ddFlowBin, "--dd-flow-bin") : null, ddFlowHome: options.ddFlowHome ? absolute(options.ddFlowHome, "--dd-flow-home") : null, projectRoot: options.projectRoot ? absolute(options.projectRoot, "--project-root") : cwd, resourceHome: process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null, env: options.ddFlowHome ? { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home"), ...(options.ddFlowBin ? { DD_FLOW_BIN: absolute(options.ddFlowBin, "--dd-flow-bin") } : {}) } : {} };
|
|
85
81
|
await prepareRuntimeOwner(config, paths.dir);
|
|
86
82
|
await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
83
|
+
let previous = await readState(paths.state); assertDaemonRestartable(previous);
|
|
87
84
|
try { const status = await callDaemon(paths.dir, "daemon.status", {}, 1_000); if (JSON.stringify(status.config) !== JSON.stringify(config)) throw Object.assign(new Error("running daemon has a different configuration"), { code: "daemon_config_mismatch" }); return { ...status, already_running: true }; } catch (error) { if (!["daemon_not_running", "daemon_timeout"].includes(error.code)) throw error; }
|
|
88
|
-
|
|
85
|
+
previous = await readState(paths.state); assertDaemonRestartable(previous);
|
|
86
|
+
if (previous?.shutdown_state === "running") throw Object.assign(new Error("previous dd-codex daemon died; do not reuse its state directory"), { code: "invalid_harness_crash" });
|
|
89
87
|
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config);
|
|
90
88
|
await removeSocket(paths.socket); const resourceProcess = await registerDaemonProcess(config, { kind: "codex-daemon", owner: `codex:${path.basename(paths.dir)}`, operation: `codex-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log }); const state = { schema_id: stateSchema, daemon_id: randomUUID(), pid: null, cwd: config.cwd, journal: config.journal, config, socket: paths.socket, started_at: new Date().toISOString(), shutdown_state: "starting", sessions: previous?.sessions ?? [], resource_process: resourceProcess }; await writeState(paths.state, state);
|
|
91
89
|
const log = await open(paths.log, "a", 0o600); const child = spawn(process.execPath, [options.entryPath, "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
|
|
92
|
-
try { await confirmDaemonProcess(config, resourceProcess, child); const
|
|
90
|
+
try { await confirmDaemonProcess(config, resourceProcess, child); const startupClock = new ObservationClock({ timeoutMs: 60_000 }); while (!startupClock.sample()) { await new Promise((resolve) => setTimeout(resolve, 100)); try { return await callDaemon(paths.dir, "daemon.status", {}, 5_000); } catch (error) { if (!["daemon_not_running", "daemon_timeout"].includes(error.code)) throw error; } } throw Object.assign(new Error("daemon did not become ready"), { code: "daemon_start_failed" }); }
|
|
93
91
|
catch (error) { await cleanupFailedDaemonStart(config, resourceProcess, child, error, () => readState(paths.state), state.daemon_id); throw error; }
|
|
94
92
|
}
|
|
95
|
-
export async function stopDaemon(options) { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs); }
|
|
93
|
+
export async function stopDaemon(options) { const timeoutMs = options.timeoutMs ?? 30_000; return await confirmDaemonStopped(() => callDaemon(options.stateDir, "daemon.stop", { cancelTree: options.cancelTree === true }, timeoutMs), locations(options.stateDir).socket, timeoutMs, { readState: () => readState(locations(options.stateDir).state) }); }
|
|
96
94
|
|
|
97
95
|
export async function serveDaemon(stateDir) {
|
|
98
96
|
const paths = locations(stateDir); const state = await readState(paths.state); if (!state || state.schema_id !== stateSchema) throw Object.assign(new Error("daemon state missing"), { code: "daemon_state_missing" });
|
|
99
97
|
const bridge = new CodexBridge({ ...state.config, env: { ...state.config.env, CODEX_HOME: process.env.CODEX_HOME, DD_FLOW_DAEMON_ID: state.daemon_id, DD_FLOW_CODEX_STATE_DIR: paths.dir } }); await startManagedBridge({ config: state.config, state, bridge, kind: "codex-provider", publish: snapshot => writeState(paths.state, snapshot) }); const sessions = new Set(state.sessions ?? []); let active = null; let server; let persist = Promise.resolve();
|
|
100
|
-
const closeBridge = bridge.close.bind(bridge);
|
|
101
|
-
bridge.close = async () => { await closeBridge(); await finishDaemonProcess(state.config, state.provider_process, "stopped", "daemon_stopped"); };
|
|
102
98
|
scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
|
|
103
|
-
const save = (patch = {}) => { Object.assign(state, patch, { pid: process.pid, updated_at: new Date().toISOString(), sessions: [...sessions] }); const snapshot = structuredClone(state); persist = persist.then(() => writeState(paths.state, snapshot)); return persist; };
|
|
99
|
+
const save = (patch = {}) => { Object.assign(state, patch, { pid: process.pid, updated_at: new Date().toISOString(), sessions: [...sessions] }); const snapshot = structuredClone(state); persist = persist.catch(() => {}).then(() => writeState(paths.state, snapshot)); return persist; };
|
|
104
100
|
const dispatch = async (operation, params, operationId) => {
|
|
105
101
|
const assertDispatch = captureDispatchGuard(bridge);
|
|
106
102
|
if (operation === "hook.verify") {
|
|
@@ -113,6 +109,7 @@ export async function serveDaemon(stateDir) {
|
|
|
113
109
|
// available while a request is marked active; callers must first prove the
|
|
114
110
|
// provider Session itself is idle.
|
|
115
111
|
if (operation === "daemon.stop") {
|
|
112
|
+
return await completeDaemonShutdown(paths.dir, state, save, async () => {
|
|
116
113
|
if (params.cancelTree) cancelPendingDispatch(bridge);
|
|
117
114
|
if (active && !params.cancelTree) throw Object.assign(new Error("productive operation is running"), { code: "tree_not_settled" });
|
|
118
115
|
await waitForSettlement({
|
|
@@ -120,12 +117,18 @@ export async function serveDaemon(stateDir) {
|
|
|
120
117
|
observe: () => observeCodexSessions(bridge, state.config, sessions, () => active),
|
|
121
118
|
...(params.cancelTree ? { cancel: async sessionId => { try { await cancelSessionWithBridge(bridge, { ...state.config, sessionId }); } catch (error) { if (error.code !== "turn_not_active") throw error; } } } : {})
|
|
122
119
|
});
|
|
123
|
-
|
|
124
|
-
|
|
120
|
+
return { clean: true };
|
|
121
|
+
}, [
|
|
122
|
+
["provider", () => bridge.close()],
|
|
123
|
+
["provider_resource", () => finishDaemonProcess(state.config, state.provider_process)],
|
|
124
|
+
["daemon_resource", () => finishDaemonProcess(state.config, state.resource_process)]
|
|
125
|
+
]);
|
|
125
126
|
}
|
|
127
|
+
if (["session.create", "session.prompt", "session.start", "session.fork", "session.resume"].includes(operation)) assertDaemonDispatch(paths.dir);
|
|
126
128
|
if (active && !["session.inspect", "session.cancel"].includes(operation)) throw Object.assign(new Error(`${active} is in progress`), { code: "operation_busy", retryable: true });
|
|
127
129
|
if (operation === "session.create") { active = operation; try { await save({ active_operation: active }); const result = await createSessionWithBridge(bridge, { ...state.config, ...params }); sessions.add(result.provider_session_id); return result; } finally { active = null; await save({ active_operation: null }); } }
|
|
128
130
|
if (operation === "session.prompt") {
|
|
131
|
+
parseCapacityOperation(operationId);
|
|
129
132
|
// Admission can wait for a permit. Observe again after it returns, not
|
|
130
133
|
// just before the caller's backoff, so no child Turn races this dispatch.
|
|
131
134
|
// Reserve before the first await: distinct operation IDs are independent
|
|
@@ -135,14 +138,14 @@ export async function serveDaemon(stateDir) {
|
|
|
135
138
|
const tree = await inspectCodexTree(bridge, { ...state.config, ...params });
|
|
136
139
|
assertDispatch();
|
|
137
140
|
if (tree.settled !== true) throw Object.assign(new Error("native Session tree changed while waiting for admission"), { code: "tree_not_settled", details: { provider_session_id: params.sessionId, unsettled_sessions: tree.unsettled_sessions } });
|
|
138
|
-
if (/:capacity:[
|
|
141
|
+
if (/:capacity:[1-9]\d*:session\.prompt$|:child:[a-f0-9]+:followup:[1-9]\d*$/.test(operationId ?? ""))
|
|
139
142
|
await assertDaemonOwnership(paths.dir, { "operation-id": operationId, operation, "native-session-id": params.sessionId, "prompt-sha256": createHash("sha256").update(params.prompt ?? "").digest("hex") }, assertDispatch);
|
|
140
143
|
sessions.add(params.sessionId);
|
|
141
|
-
await save({ active_operation: active }); return await promptSessionWithBridge(bridge, { ...state.config, ...params, assertDispatch });
|
|
144
|
+
await save({ active_operation: active }); return await promptSessionWithBridge(bridge, { ...state.config, ...params, operationId, assertDispatch });
|
|
142
145
|
}
|
|
143
146
|
finally { active = null; await save({ active_operation: null }); }
|
|
144
147
|
}
|
|
145
|
-
if (operation === "session.start") { sessions.add(params.sessionId); active = operation; try { await save({ active_operation: active }); return await startSessionWithBridge(bridge, { ...state.config, ...params, assertDispatch }); } finally { active = null; await save({ active_operation: null }); } }
|
|
148
|
+
if (operation === "session.start") { sessions.add(params.sessionId); active = operation; try { await save({ active_operation: active }); return await startSessionWithBridge(bridge, { ...state.config, ...params, assertDispatch, operationId }); } finally { active = null; await save({ active_operation: null }); } }
|
|
146
149
|
if (operation === "session.inspect" || operation === "session.resume") {
|
|
147
150
|
const owned = new Set(sessions);
|
|
148
151
|
for (const id of owned) for (const child of bridge.directChildren?.(id) ?? []) owned.add(child.provider_session_id);
|
|
@@ -159,7 +162,28 @@ export async function serveDaemon(stateDir) {
|
|
|
159
162
|
}
|
|
160
163
|
throw Object.assign(new Error(`unknown operation: ${operation}`), { code: "unknown_operation" });
|
|
161
164
|
};
|
|
162
|
-
await removeSocket(paths.socket);
|
|
165
|
+
await removeSocket(paths.socket);
|
|
166
|
+
server = net.createServer(connection => {
|
|
167
|
+
connection.setEncoding("utf8"); let buffer = "", consumed = false;
|
|
168
|
+
connection.on("data", chunk => {
|
|
169
|
+
if (consumed) return;
|
|
170
|
+
buffer += chunk; if (!admitNativeRequestBuffer(buffer, connection, responseSchema)) { consumed = true; return; } const at = buffer.indexOf("\n"); if (at < 0) return; consumed = true;
|
|
171
|
+
void (async () => {
|
|
172
|
+
let request;
|
|
173
|
+
try {
|
|
174
|
+
request = JSON.parse(buffer.slice(0, at));
|
|
175
|
+
if (request.schema_id !== requestSchema || !request.id || !request.operation) throw Object.assign(new Error("invalid daemon request"), { code: "daemon_protocol" });
|
|
176
|
+
const result = await durableDaemonDispatch(paths.dir, request, () => dispatch(request.operation, request.params ?? {}, request.id), () => dispatch("session.inspect", request.params ?? {}));
|
|
177
|
+
if (result._shutdown) retireDaemonEndpoint(server);
|
|
178
|
+
connection.end(`${JSON.stringify({ schema_id: responseSchema, id: request.id, ok: true, result: { ...result, _shutdown: undefined } })}\n`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
await retireFailedDaemon(server, state, error, save);
|
|
181
|
+
connection.end(`${JSON.stringify({ schema_id: responseSchema, id: request?.id ?? null, ok: false, error: errorPayload(error) })}\n`);
|
|
182
|
+
}
|
|
183
|
+
})();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
trackDaemonConnections(server);
|
|
163
187
|
await new Promise((resolve, reject) => { server.once("error", reject); server.listen(paths.socket, resolve); }); await chmod(paths.socket, 0o600); await save({ shutdown_state: "running", active_operation: null }); return await new Promise(() => {});
|
|
164
188
|
}
|
|
165
189
|
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|