@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 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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.117",
4
- "cli_commit": "bf6fd6c47695578a4b0ee99e4267913d39328048",
5
- "built_at": "2026-09-28T08:45:26.913Z",
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: 10_000 })).stdout.trim();
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: 10_000 })).stdout.trim();
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() + 30_000; while (Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 100)); 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); }
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
- } }); const initialized = await startManagedBridge({ config: state.config, state, bridge, kind: "grok-provider", publish: snapshot => writeState(paths.state, snapshot) }); runtime = new Runtime(paths, state, bridge, initialized);
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.117",
3
+ "version": "0.9.0-beta.119",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {