@deksden-com/dd-flow-cli 0.9.0-beta.117 → 0.9.0-beta.119
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 +12 -0
- package/dist/build-info.json +3 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +2 -2
- package/dist/harness-runtime/lib/dd-agy.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +8 -3
- package/dist/harness-runtime/lib/dd-zcode.mjs +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.119
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Give Grok's native ACP startup a bounded 60-second initialize window and a 120-second daemon-ready window. ZCode keeps its 30-second initialize bound.
|
|
8
|
+
|
|
9
|
+
## 0.9.0-beta.118
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Admit the tested ZCode bridge fix for managed turns that remain active after a frozen event watermark, and allow 30 seconds for Antigravity's version check during doctor and daemon startup.
|
|
14
|
+
|
|
3
15
|
## 0.9.0-beta.117
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.119",
|
|
4
|
+
"cli_commit": "9bf32901c2f926dd5819e3695dbab4fae1869724",
|
|
5
|
+
"built_at": "2026-09-28T13:03:08.769Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
7
|
"version": "4.1.1",
|
|
8
8
|
"commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
|
|
@@ -11,7 +11,7 @@ import { agyWorkspaceHooks } from "./agy-hooks.mjs";
|
|
|
11
11
|
import { durableDaemonDispatch } from "./daemon-operations.mjs";
|
|
12
12
|
import path from "node:path";
|
|
13
13
|
import { observeModel } from "./model-observations.mjs";
|
|
14
|
-
import { AgyError, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
14
|
+
import { AGY_VERSION_TIMEOUT_MS, AgyError, assertProfile, executable, observedProfile, runAgy, usageSnapshot } from "./dd-agy.mjs";
|
|
15
15
|
import { prepareRuntimeOwner, cleanupFailedStart, cleanupFailedDaemonStart, confirmDaemonProcess, confirmDaemonStopped, finishDaemonProcess, scheduleDaemonProcessHeartbeat, registerDaemonProcess, stopProcessGroup } from "./managed-daemon.mjs";
|
|
16
16
|
|
|
17
17
|
const REQUEST_SCHEMA = "dd-agy/daemon-request@1", RESPONSE_SCHEMA = "dd-agy/daemon-response@1", STATE_SCHEMA = "dd-agy/daemon-state@1";
|
|
@@ -189,7 +189,7 @@ export async function startDaemon(options) {
|
|
|
189
189
|
if (previous?.shutdown_state === "clean") await authorizeRetainedDaemonResume(paths.dir, previous, options.sessionId, config);
|
|
190
190
|
if (previous?.shutdown_state === "running" && previous.active_tree) throw new DaemonError("invalid_harness_crash", "previous Antigravity daemon died with an active or unproven tree");
|
|
191
191
|
await removeSocket(paths); await prepare(paths, config);
|
|
192
|
-
const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs:
|
|
192
|
+
const version = (await runAgy(config.bin, ["--version"], { cwd: config.cwd, env: sanitizedEnv(config), timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
|
|
193
193
|
const resourceProcess = await registerDaemonProcess(config, { kind: "agy-daemon", owner: `agy:${path.basename(paths.dir)}`, operation: `agy-daemon:${path.basename(paths.dir)}`, stdout: paths.log, stderr: paths.log });
|
|
194
194
|
const retained = retainedAgyState(previous, config.resumeSessionId);
|
|
195
195
|
await writeJson(paths.state, { schema_id: STATE_SCHEMA, daemon_id: config.daemonId, shutdown_state: "starting", active_tree: false, unclaimed_activity: retained.unclaimed_activity ?? false, retained_tree_settlement: retained.retained_tree_settlement ?? null, versions: { agy: version }, config, sessions: [], session_observations: retained.session_observations ?? [], descendants: retained.descendants ?? [], tool_calls: retained.tool_calls ?? [], completed_tool_steps: retained.completed_tool_steps ?? [], tool_identity_complete: retained.tool_identity_complete !== false, turn_generation: retained.turn_generation ?? 0, last_result: retained.last_result ?? null, resource_process: resourceProcess });
|
|
@@ -6,6 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import { checkObservedProfile } from "./model-observations.mjs";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_MODEL = "gemini-3.1-pro-high";
|
|
9
|
+
export const AGY_VERSION_TIMEOUT_MS = 30_000;
|
|
9
10
|
|
|
10
11
|
export class AgyError extends Error {
|
|
11
12
|
constructor(code, message, retryable = false, details) { super(message); this.code = code; this.retryable = retryable; this.details = details; }
|
|
@@ -50,7 +51,7 @@ export function assertProfile(requested, observed) { return checkObservedProfile
|
|
|
50
51
|
export async function doctor(options = {}) {
|
|
51
52
|
const bin = options.bin ?? process.env.DD_AGY_BIN ?? "agy", temporary = await mkdtemp(path.join(os.tmpdir(), "dd-agy-doctor-"));
|
|
52
53
|
try {
|
|
53
|
-
const version = (await runAgy(bin, ["--version"], { timeoutMs:
|
|
54
|
+
const version = (await runAgy(bin, ["--version"], { timeoutMs: AGY_VERSION_TIMEOUT_MS })).stdout.trim();
|
|
54
55
|
const models = await runAgy(bin, [`--gemini_dir=${temporary}`, "--app_data_dir=runtime", "models"], { timeoutMs: options.timeoutMs ?? 30_000 });
|
|
55
56
|
const modelIds = models.stdout.split(/\r?\n/).map(line => line.split(/\s+/)[0]).filter(Boolean);
|
|
56
57
|
if (!modelIds.includes(options.model ?? DEFAULT_MODEL)) throw new AgyError("agy_model_unavailable", `required Antigravity model is unavailable: ${options.model ?? DEFAULT_MODEL}`);
|
|
@@ -159,6 +159,11 @@ export async function callDaemon(stateDir, operation, params = {}, timeoutMs = 3
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
function sameConfig(state, config) { return ["cwd", "journal", "bin", "authSource", "model", "reasoning", "mode", "ddFlowBin", "ddFlowHome", "projectRoot", "sessionArchive", "resourceHome", "noFlow"].every((key) => (state.config?.[key] ?? null) === (config[key] ?? null)); }
|
|
162
|
+
export function grokStartupFailure(state) {
|
|
163
|
+
if (state?.shutdown_state !== "failed" || !state.startup_error) return null;
|
|
164
|
+
const failure = state.startup_error;
|
|
165
|
+
return new DaemonError(failure.code ?? "daemon_start_failed", failure.message ?? "Grok daemon failed before becoming ready", failure.retryable === true, failure.details);
|
|
166
|
+
}
|
|
162
167
|
export async function startDaemon(options) {
|
|
163
168
|
const paths = locations(options.stateDir); const archive = options.sessionArchive ? await readSessionArchiveManifest(options.sessionArchive) : null; const archiveCwd = archive ? absolute(archive.manifest.source_cwd, "Grok Build archive source cwd") : null; const cwd = options.cwd ? absolute(options.cwd, "--cwd") : archiveCwd; if (!cwd) throw new DaemonError("invalid_path", "--cwd must be an absolute path when no session archive is supplied"); if (archiveCwd && cwd !== archiveCwd) throw new DaemonError("session_archive_cwd_mismatch", "Grok Build session archives must start from their recorded source cwd", false, { archive_cwd: archiveCwd, requested_cwd: cwd }); const noFlow = options.noFlow === true; const authSource = options.authPath ? absolute(options.authPath, "--auth-path") : path.join(os.homedir(), ".grok", "auth.json"); const config = { cwd, journal: absolute(options.journal, "--journal"), bin: options.bin ?? process.env.DD_GROK_BIN ?? "grok", authSource, authPath: path.join(paths.home, "auth.json"), model: String(options.model ?? ""), reasoning: String(options.reasoning ?? ""), mode: options.mode ?? "bypassPermissions", ddFlowBin: noFlow ? null : absolute(options.ddFlowBin, "--dd-flow-bin"), ddFlowHome: noFlow ? null : absolute(options.ddFlowHome, "--dd-flow-home"), projectRoot: noFlow ? cwd : absolute(options.projectRoot, "--project-root"), resourceHome: noFlow ? null : (process.env.DD_FLOW_RESOURCE_HOME ? absolute(process.env.DD_FLOW_RESOURCE_HOME, "DD_FLOW_RESOURCE_HOME") : null), env: noFlow ? {} : { DD_FLOW_HOME: absolute(options.ddFlowHome, "--dd-flow-home") }, sessionArchive: archive?.source ?? null, grokHome: paths.home, noFlow };
|
|
164
169
|
await prepareRuntimeOwner(config, paths.dir);
|
|
@@ -171,7 +176,7 @@ export async function startDaemon(options) {
|
|
|
171
176
|
const daemonId = randomUUID();
|
|
172
177
|
const state = { descendants: previous?.descendants ?? [], schema_id: STATE_SCHEMA, daemon_id: daemonId, pid: null, socket: paths.socket, started_at: new Date().toISOString(), shutdown_state: "starting", active_tree: false, recovery_status: previous?.shutdown_state === "running" ? "recovered_idle" : "clean_start", auth_status: copiedAuth ? "copied" : "absent", ...(importedSession ? { imported_session: importedSession } : {}), versions: versions.versions, config_isolation: configIsolation, config: { ...config, daemonId }, sessions: importedSession ? [{ provider_session_id: importedSession.provider_session_id, adapter_session_id: importedSession.provider_session_id, parent_provider_session_id: null, root_provider_session_id: importedSession.provider_session_id, cwd: importedSession.source_cwd }] : (previous?.sessions ?? []), resource_process: resourceProcess };
|
|
173
178
|
await writeState(paths.state, state); const log = await open(paths.log, "a", 0o600); const child = spawn(process.execPath, [absolute(options.entryPath, "dd-grok entry"), "daemon", "serve", "--state-dir", paths.dir], { cwd: config.cwd, env: isolatedEnv(state.config), detached: true, stdio: ["ignore", log.fd, log.fd] }); child.unref(); await log.close();
|
|
174
|
-
try { await confirmDaemonProcess(config, resourceProcess, child); const deadline = Date.now() +
|
|
179
|
+
try { await confirmDaemonProcess(config, resourceProcess, child); const deadline = Date.now() + 120_000; while (Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 100)); const failure = grokStartupFailure(await readState(paths.dir)); if (failure) throw failure; try { const status = await callDaemon(paths.dir, "daemon.ready", {}, 5000); if (status.ready) return status; } catch (error) { if (error.code !== "daemon_not_running" && error.code !== "daemon_timeout") throw error; } } throw new DaemonError("daemon_start_failed", "daemon did not become ready", true); }
|
|
175
180
|
catch (error) { await cleanupFailedDaemonStart(config, resourceProcess, child, error, () => readState(paths.dir), daemonId); throw error; }
|
|
176
181
|
}
|
|
177
182
|
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); }
|
|
@@ -382,7 +387,7 @@ export class Runtime {
|
|
|
382
387
|
|
|
383
388
|
export async function serveDaemon(stateDir) {
|
|
384
389
|
const paths = locations(stateDir); const state = await readState(paths.dir); if (!state || state.schema_id !== STATE_SCHEMA) throw new DaemonError("daemon_state_missing", "daemon state is missing or incompatible"); let runtime;
|
|
385
|
-
const bridge = new AcpBridge({ ...state.config, commandArgs: ["agent", "--no-leader", "--always-approve", "--model", state.config.model, "--reasoning-effort", state.config.reasoning, "stdio"], env: isolatedEnv(state.config), clientInfo: { name: "dd-grok", version: "0.1.0" }, permission: "allow", onNotification: async (message) => {
|
|
390
|
+
const bridge = new AcpBridge({ ...state.config, initializeTimeoutMs: 60_000, commandArgs: ["agent", "--no-leader", "--always-approve", "--model", state.config.model, "--reasoning-effort", state.config.reasoning, "stdio"], env: isolatedEnv(state.config), clientInfo: { name: "dd-grok", version: "0.1.0" }, permission: "allow", onNotification: async (message) => {
|
|
386
391
|
const sessionId = message?.params?.sessionId;
|
|
387
392
|
if (!runtime || !sessionId) return;
|
|
388
393
|
try {
|
|
@@ -403,7 +408,7 @@ export async function serveDaemon(stateDir) {
|
|
|
403
408
|
const session = runtime.sessions.get(sessionId);
|
|
404
409
|
const rootProviderSessionId = session?.root_provider_session_id ?? sessionId;
|
|
405
410
|
await observeAcpToolCall(state.config, { daemonId: state.daemon_id, rootProviderSessionId, parentProviderSessionId: session?.parent_provider_session_id ?? null, observedProfile: bridge.modelProfiles?.get(sessionId) ?? {} }, message);
|
|
406
|
-
} });
|
|
411
|
+
} }); let initialized; try { initialized = await startManagedBridge({ config: state.config, state, bridge, kind: "grok-provider", publish: snapshot => writeState(paths.state, snapshot) }); } catch (error) { await writeState(paths.state, { ...state, shutdown_state: "failed", startup_error: errorPayload(error) }); throw error; } runtime = new Runtime(paths, state, bridge, initialized);
|
|
407
412
|
for (const fact of await readModelObservations(state.config.journal)) {
|
|
408
413
|
if (fact.harness === "grok-acp" && fact.source?.channel === "x.ai/subagent_spawned" && runtime.sessions.has(fact.session_id)
|
|
409
414
|
&& (runtime.descendants.get(fact.session_id)?.attempt_id ?? null) === (fact.source.attempt_id ?? null) && fact.observed?.model) {
|
|
@@ -193,7 +193,7 @@ export class AcpBridge {
|
|
|
193
193
|
protocolVersion: 1,
|
|
194
194
|
clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, elicitation: { form: {} } },
|
|
195
195
|
clientInfo: this.options.clientInfo ?? { name: "dd-zcode", version: "0.1.0" },
|
|
196
|
-
});
|
|
196
|
+
}, this.options.initializeTimeoutMs ?? 30_000);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
199
|
receive(line) {
|
|
@@ -805,6 +805,7 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
|
|
|
805
805
|
["5a8c89ab716a4b2ccc50df81cb4d204ff5c06368", "dd-zcode-harness@2"],
|
|
806
806
|
["91a1470650f5fd6105f9863fd31d1cad7b90a109", "dd-zcode-harness@2"],
|
|
807
807
|
["b9e0f5106dddb439b76186278bd9be5f45dbf3ef", "dd-zcode-harness@2"],
|
|
808
|
+
["636b14181d5c9a95476055079836fd6b58169f58", "dd-zcode-harness@2"],
|
|
808
809
|
]);
|
|
809
810
|
const qualified = qualifiedBridgeCommits.get(bridgeCommit) === harnessContract;
|
|
810
811
|
return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
|