@deksden-com/dd-flow-cli 0.9.0-beta.9 → 0.9.0-beta.91
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 +467 -0
- package/README.md +65 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +305 -0
- package/dist/cli/help.js +89 -13
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/run-cli.js +1421 -384
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +57 -13
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +543 -96
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +132 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +121 -76
- package/dist/services/eval-snapshots.js +750 -73
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +587 -299
- package/dist/services/lanes.js +61 -53
- package/dist/services/lifecycle-command.js +103 -17
- package/dist/services/lifecycle-contract.js +30 -0
- package/dist/services/lifecycle-invocations.js +1122 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +283 -27
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +73 -28
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +23 -16
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +134 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +793 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +368 -74
- package/dist/services/runtime-budget.js +434 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +99 -102
- package/dist/services/stage-pause.js +84 -55
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +37 -93
- package/dist/services/vnext-code-review.js +195 -88
- package/dist/services/vnext-code.js +285 -252
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +160 -17
- package/dist/services/vnext-merge.js +291 -104
- package/dist/services/vnext-plan-review.js +139 -81
- package/dist/services/vnext-plan.js +138 -69
- package/dist/services/vnext-protocolize.js +129 -56
- package/dist/services/vnext-specify.js +88 -65
- package/dist/services/work-registry.js +716 -168
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +459 -32
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +24 -13
- package/tools/repair-paused-run-status.mjs +59 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { captureDispatchGuard, cancelPendingDispatch } from "./dispatch-fence.mjs";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { chmod, lstat, mkdir, open, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import net from "node:net";
|
|
5
|
+
import { durableDaemonDispatch } from "./daemon-operations.mjs";
|
|
6
|
+
import { observedTimeout } from "./observation-clock.mjs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
9
|
+
import { waitForSettlement } from "./session-settlement.mjs";
|
|
10
|
+
import { errorRecord } from "./operation-errors.mjs";
|
|
11
|
+
import { CodexBridge, cancelSessionWithBridge, createSessionWithBridge, inspectSessionWithBridge, promptSessionWithBridge, startSessionWithBridge } from "./dd-codex.mjs";
|
|
12
|
+
import { prepareRuntimeOwner, cleanupFailedStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, heartbeatDaemonProcess, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
13
|
+
|
|
14
|
+
const requestSchema = "dd-codex/daemon-request@1";
|
|
15
|
+
const responseSchema = "dd-codex/daemon-response@1";
|
|
16
|
+
const stateSchema = "dd-codex/daemon-state@1";
|
|
17
|
+
const absolute = (value, label) => { if (!value || !path.isAbsolute(value)) throw Object.assign(new Error(`${label} must be an absolute path`), { code: "invalid_path" }); return path.resolve(value); };
|
|
18
|
+
function locations(stateDir) { const dir = absolute(stateDir, "--state-dir"); const local = path.join(dir, "daemon.sock"); return { dir, socket: Buffer.byteLength(local) < 100 ? local : `/tmp/dd-codex-${createHash("sha256").update(dir).digest("hex").slice(0, 24)}.sock`, state: path.join(dir, "daemon.json"), log: path.join(dir, "daemon.log") }; }
|
|
19
|
+
async function readState(file) { try { return JSON.parse(await readFile(file, "utf8")); } catch (error) { if (error.code === "ENOENT") return null; throw error; } }
|
|
20
|
+
async function writeState(file, value) { const temp = `${file}.${process.pid}.tmp`; await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 }); await rename(temp, file); }
|
|
21
|
+
async function removeSocket(socket) { try { const info = await lstat(socket); if (!info.isSocket()) throw Object.assign(new Error("refusing to remove non-socket daemon path"), { code: "unsafe_socket" }); await unlink(socket); } catch (error) { if (error.code !== "ENOENT") throw error; } }
|
|
22
|
+
const errorPayload = errorRecord;
|
|
23
|
+
const terminalTurn = turn => ["completed", "interrupted", "failed", "cancelled"].includes(turn?.status);
|
|
24
|
+
export function settledCodexSession(status, latestTurn, active) {
|
|
25
|
+
if (status === "idle") return true;
|
|
26
|
+
if (status === "notLoaded") return !latestTurn || terminalTurn(latestTurn);
|
|
27
|
+
// `systemError` alone is not evidence that the provider stopped. A current
|
|
28
|
+
// terminal Turn plus no daemon operation is the narrow settled case.
|
|
29
|
+
return status === "systemError" && !active && terminalTurn(latestTurn);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function observeCodexSessions(bridge, options, sessions, active, remember = () => {}) {
|
|
33
|
+
// Expand trusted native topology before profile/history diagnostics: an
|
|
34
|
+
// unavailable root must not hide its already discovered siblings/children.
|
|
35
|
+
for (const id of sessions) for (const child of bridge.directChildren?.(id) ?? []) {
|
|
36
|
+
if (typeof child.provider_session_id !== "string" || !child.provider_session_id.trim()) throw Object.assign(new Error("Codex child has no native identity"), { code: "invalid_session_topology" });
|
|
37
|
+
sessions.add(child.provider_session_id); remember(child.provider_session_id);
|
|
38
|
+
}
|
|
39
|
+
const unsettled = [], observations = [];
|
|
40
|
+
for (const sessionId of sessions) {
|
|
41
|
+
const observed = await inspectSessionWithBridge(bridge, { ...options, sessionId });
|
|
42
|
+
for (const child of observed.descendants ?? []) { sessions.add(child.provider_session_id); remember(child.provider_session_id); }
|
|
43
|
+
const status = observed.status?.type ?? observed.status;
|
|
44
|
+
const stored = ["notLoaded", "systemError"].includes(status) ? await bridge.request("thread/turns/list", { threadId: sessionId, limit: 1, sortDirection: "desc", itemsView: "summary" }, 5_000) : null;
|
|
45
|
+
const settled = settledCodexSession(status, stored?.data?.[0] ?? null, active());
|
|
46
|
+
if (!settled) unsettled.push(sessionId);
|
|
47
|
+
observations.push({ ...observed, settled });
|
|
48
|
+
}
|
|
49
|
+
return { sessions: unsettled.reverse(), active: Boolean(active()), observations };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function inspectCodexTree(bridge, options, { remember = () => {}, active = () => null } = {}) {
|
|
53
|
+
const sessions = new Set([options.sessionId]);
|
|
54
|
+
const observed = await observeCodexSessions(bridge, options, sessions, active, remember);
|
|
55
|
+
return { ...observed.observations[0], settled: !observed.active && !observed.sessions.length, unsettled_sessions: observed.sessions, tree_session_ids: [...sessions], tree_observations: observed.observations.map(item => ({ provider_session_id: item.provider_session_id, status: item.status, settled: item.settled })) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function cancelCodexTree(bridge, options, { remember = () => {}, active = () => null, timeoutMs = 5_000 } = {}) {
|
|
59
|
+
const sessions = new Set([options.sessionId]);
|
|
60
|
+
const settlement = await waitForSettlement({
|
|
61
|
+
timeoutMs, knownSessions: () => [...sessions],
|
|
62
|
+
cancel: async sessionId => {
|
|
63
|
+
try { await cancelSessionWithBridge(bridge, { ...options, sessionId, turnId: null }); }
|
|
64
|
+
catch (error) { if (error.code !== "turn_not_active") throw error; }
|
|
65
|
+
},
|
|
66
|
+
observe: () => observeCodexSessions(bridge, options, sessions, active, remember)
|
|
67
|
+
});
|
|
68
|
+
return { harness: "codex-desktop", provider_session_id: options.sessionId, adapter_session_id: options.sessionId, cancelled: true, settled: true, tree_session_ids: [...sessions], cleanup_errors: settlement.cleanup_errors, tree_observations: settlement.observations.map(item => ({ provider_session_id: item.provider_session_id, status: item.status, settled: item.settled })) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 30_000) {
|
|
72
|
+
const target = locations(stateDir); const request = { schema_id: requestSchema, id: process.env.DD_EVAL_OPERATION_ID ?? randomUUID(), operation, params };
|
|
73
|
+
return await new Promise((resolve, reject) => {
|
|
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
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function startDaemon(options) {
|
|
84
|
+
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
|
+
await prepareRuntimeOwner(config, paths.dir);
|
|
86
|
+
await mkdir(paths.dir, { recursive: true, mode: 0o700 }); await chmod(paths.dir, 0o700);
|
|
87
|
+
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
|
+
const previous = await readState(paths.state); 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
|
+
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config);
|
|
90
|
+
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
|
+
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 deadline = Date.now() + 15_000; while (Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 100)); try { return await callDaemon(paths.dir, "daemon.status", {}, 1_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
|
+
catch (error) { await cleanupFailedStart(config, resourceProcess, child, error); throw error; }
|
|
94
|
+
}
|
|
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); }
|
|
96
|
+
|
|
97
|
+
export async function serveDaemon(stateDir) {
|
|
98
|
+
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
|
+
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 bridge.start(); const sessions = new Set(state.sessions ?? []); let active = null; let server; let persist = Promise.resolve();
|
|
100
|
+
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)).then(() => heartbeatDaemonProcess(snapshot.config, snapshot.resource_process).catch(() => {})); return persist; };
|
|
101
|
+
const dispatch = async (operation, params) => {
|
|
102
|
+
const assertDispatch = captureDispatchGuard(bridge);
|
|
103
|
+
if (operation === "hook.verify") {
|
|
104
|
+
if (params.daemonId !== state.daemon_id) throw Object.assign(new Error("Hook daemon identity differs"), { code: "native_hook_unproven" });
|
|
105
|
+
return { ...bridge.verifyNativeHook(params), daemonId: state.daemon_id, ddFlowHome: state.config.ddFlowHome, projectRoot: state.config.projectRoot };
|
|
106
|
+
}
|
|
107
|
+
if (operation === "daemon.status") return { daemon_id: state.daemon_id, pid: process.pid, cwd: state.cwd, journal: state.journal, config: state.config, sessions: [...sessions], active_operation: active, shutdown_state: state.shutdown_state };
|
|
108
|
+
// The runner may need to retire an orphaned daemon after its caller was
|
|
109
|
+
// interrupted. `daemon.stop` is the one control operation that remains
|
|
110
|
+
// available while a request is marked active; callers must first prove the
|
|
111
|
+
// provider Session itself is idle.
|
|
112
|
+
if (operation === "daemon.stop") {
|
|
113
|
+
if (params.cancelTree) cancelPendingDispatch(bridge);
|
|
114
|
+
if (active && !params.cancelTree) throw Object.assign(new Error("productive operation is running"), { code: "tree_not_settled" });
|
|
115
|
+
await waitForSettlement({
|
|
116
|
+
knownSessions: () => [...sessions],
|
|
117
|
+
observe: () => observeCodexSessions(bridge, state.config, sessions, () => active),
|
|
118
|
+
...(params.cancelTree ? { cancel: async sessionId => { try { await cancelSessionWithBridge(bridge, { ...state.config, sessionId }); } catch (error) { if (error.code !== "turn_not_active") throw error; } } } : {})
|
|
119
|
+
});
|
|
120
|
+
await save({ active_tree: false });
|
|
121
|
+
return { stopped: true, clean: true, _shutdown: true };
|
|
122
|
+
}
|
|
123
|
+
if (active && !["session.inspect", "session.cancel"].includes(operation)) throw Object.assign(new Error(`${active} is in progress`), { code: "operation_busy", retryable: true });
|
|
124
|
+
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 }); } }
|
|
125
|
+
if (operation === "session.prompt") { sessions.add(params.sessionId); active = operation; try { await save({ active_operation: active }); return await promptSessionWithBridge(bridge, { ...state.config, ...params, assertDispatch }); } finally { active = null; await save({ active_operation: null }); } }
|
|
126
|
+
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 }); } }
|
|
127
|
+
if (operation === "session.inspect" || operation === "session.resume") {
|
|
128
|
+
const owned = new Set(sessions);
|
|
129
|
+
for (const id of owned) for (const child of bridge.directChildren?.(id) ?? []) owned.add(child.provider_session_id);
|
|
130
|
+
if (!owned.has(params.sessionId)) throw Object.assign(new Error("inspection Session is not owned by this daemon"), { code: "session_identity_mismatch" });
|
|
131
|
+
bridge.assertHookHealthy(params.sessionId);
|
|
132
|
+
try { return await inspectCodexTree(bridge, { ...state.config, ...params }, { remember: id => sessions.add(id), active: () => active }); }
|
|
133
|
+
finally { await save(); }
|
|
134
|
+
}
|
|
135
|
+
if (operation === "session.cancel") {
|
|
136
|
+
if (!sessions.has(params.sessionId)) throw Object.assign(new Error("cancellation Session is not owned by this daemon"), { code: "session_identity_mismatch" });
|
|
137
|
+
cancelPendingDispatch(bridge);
|
|
138
|
+
try { return await cancelCodexTree(bridge, { ...state.config, ...params }, { remember: id => sessions.add(id), active: () => active }); }
|
|
139
|
+
finally { await save(); }
|
|
140
|
+
}
|
|
141
|
+
throw Object.assign(new Error(`unknown operation: ${operation}`), { code: "unknown_operation" });
|
|
142
|
+
};
|
|
143
|
+
await removeSocket(paths.socket); server = net.createServer((connection) => { connection.setEncoding("utf8"); let buffer = ""; connection.on("data", (chunk) => { buffer += chunk; const at = buffer.indexOf("\n"); if (at < 0) return; const line = buffer.slice(0, at); buffer = buffer.slice(at + 1); void (async () => { let request; try { request = JSON.parse(line); if (request.schema_id !== requestSchema || !request.id || !request.operation) throw Object.assign(new Error("invalid daemon request"), { code: "daemon_protocol" }); const result = await durableDaemonDispatch(paths.dir, request, () => dispatch(request.operation, request.params ?? {}), () => dispatch("session.inspect", request.params ?? {})); connection.end(`${JSON.stringify({ schema_id: responseSchema, id: request.id, ok: true, result: { ...result, _shutdown: undefined } })}\n`); if (result._shutdown) { await bridge.close(); await save({ shutdown_state: "clean", stopped_at: new Date().toISOString() }); await finishDaemonProcess(state.config, state.resource_process); server.close(() => process.exit(0)); await removeSocket(paths.socket); } } catch (error) { if (connection.writableEnded) { await save({ shutdown_state: "cleanup_failed", cleanup_error: errorPayload(error) }); return; } connection.end(`${JSON.stringify({ schema_id: responseSchema, id: request?.id ?? null, ok: false, error: errorPayload(error) })}\n`); } })(); }); });
|
|
144
|
+
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(() => {});
|
|
145
|
+
}
|
|
146
|
+
import { authorizeRetainedDaemonResume } from "./driver-recovery.mjs";
|