@deksden-com/dd-flow-cli 0.9.0-beta.96 → 0.9.0-beta.97

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.96",
4
- "cli_commit": "cb6c669ff3f24115ac0a4d2c04c8877eb8683bc6",
5
- "built_at": "2026-09-22T12:33:21.451Z",
3
+ "cli_version": "0.9.0-beta.97",
4
+ "cli_commit": "4d478c7dd9549a5b81ed5cd77d4f04befebf6f1c",
5
+ "built_at": "2026-09-22T15:35:15.503Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
@@ -3,8 +3,8 @@ import { durableDaemonStart, inspectDaemonOperation, interruptDaemonOperation, s
3
3
  import { cancelSession, createSession, doctor, inspectSession, promptFromFile, promptSession, startSession } from "../lib/dd-codex.mjs";
4
4
  import { callDaemon, serveDaemon, startDaemon, stopDaemon } from "../lib/dd-codex-daemon.mjs";
5
5
 
6
- function parse(argv) { const positional = []; const options = {}; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (!token.startsWith("--")) { positional.push(token); continue; } const key = token.slice(2); if (["json", "cancel-tree"].includes(key)) { options[key] = true; continue; } const value = argv[++i]; if (value === undefined) throw new Error(`--${key} requires a value`); options[key] = value; } return { positional, options }; }
7
- function common(options) { return { bin: options["codex-bin"], journal: options.journal, stateDir: options["state-dir"], cwd: options.cwd, sessionId: options["session-id"], turnId: options["turn-id"], model: options.model, reasoning: options.reasoning, ddFlowBin: options["dd-flow-bin"], ddFlowHome: options["dd-flow-home"], projectRoot: options["project-root"], timeoutMs: options.timeout ? Number(options.timeout) * 1000 : undefined }; }
6
+ function parse(argv) { const positional = []; const options = {}; for (let i = 0; i < argv.length; i += 1) { const token = argv[i]; if (!token.startsWith("--")) { positional.push(token); continue; } const key = token.slice(2); if (["json", "cancel-tree", "no-flow"].includes(key)) { options[key] = true; continue; } const value = argv[++i]; if (value === undefined) throw new Error(`--${key} requires a value`); options[key] = value; } return { positional, options }; }
7
+ function common(options) { return { bin: options["codex-bin"], journal: options.journal, stateDir: options["state-dir"], cwd: options.cwd, sessionId: options["session-id"], turnId: options["turn-id"], model: options.model, reasoning: options.reasoning, ddFlowBin: options["dd-flow-bin"], ddFlowHome: options["dd-flow-home"], projectRoot: options["project-root"], noFlow: options["no-flow"] === true, timeoutMs: options.timeout ? Number(options.timeout) * 1000 : undefined }; }
8
8
  try {
9
9
  const { positional, options } = parse(process.argv.slice(2)); const [family, command, modifier] = positional; const shared = common(options); let result;
10
10
  if (family === "doctor") result = await doctor(shared);
@@ -27,7 +27,7 @@ try {
27
27
  else if (family === "daemon" && command === "operation") result = await inspectDaemonOperation(config.stateDir, options["operation-id"]);
28
28
  else if (family === "daemon" && command === "stop") result = await stopDaemon({ stateDir: config.stateDir, cancelTree: options["cancel-tree"] });
29
29
  else if (family === "hook" && command === "handle") { const payload = JSON.parse(await input()); const eventId = randomUUID(); result = await callDaemon(config.stateDir, "hook.observe", { payload, eventId }, 25_000, eventId); }
30
- else if (family === "session") { if (command === "fork") throw new Error("Droid fork is not qualified for this adapter"); const prompt = options["prompt-file"] ? await readFile(options["prompt-file"], "utf8") : options.prompt; result = await callDaemon(config.stateDir, `session.${command === "status" ? "inspect" : command}`, { sessionId: options["session-id"], ...(prompt ? { prompt } : {}) }); }
30
+ else if (family === "session") { if (command === "fork") throw new Error("Droid fork is not qualified for this adapter"); const prompt = options["prompt-file"] ? await readFile(options["prompt-file"], "utf8") : options.prompt; result = await callDaemon(config.stateDir, `session.${command === "status" ? "inspect" : command}`, { sessionId: options["session-id"], ...(config.noFlow ? { noFlow: true } : {}), ...(prompt ? { prompt } : {}) }); }
31
31
  else throw new Error("usage: dd-droid doctor | daemon start|status|operation|stop | session create|resume|prompt|inspect|cancel");
32
32
  process.stdout.write(`${JSON.stringify(family === "hook" ? { ...(result.hookSpecificOutput ? { hookSpecificOutput: result.hookSpecificOutput } : {}), ...(typeof result.continue === "boolean" ? { continue: result.continue } : {}), ...(result.stopReason ? { stopReason: result.stopReason } : {}) } : { ok: true, ...result })}\n`);
33
33
  } catch (error) { process.stderr.write(`${JSON.stringify({ ok: false, error: errorRecord(error) })}\n`); process.exitCode = process.argv[2] === "hook" ? 2 : 1; }
@@ -9,7 +9,7 @@ function parse(argv) {
9
9
  for (let index = 0; index < argv.length; index += 1) {
10
10
  const token = argv[index];
11
11
  if (!token.startsWith("--")) { positional.push(token); continue; }
12
- const key = token.slice(2); if (["json", "cancel-tree"].includes(key)) { options[key] = true; continue; }
12
+ const key = token.slice(2); if (["json", "cancel-tree", "no-flow"].includes(key)) { options[key] = true; continue; }
13
13
  const value = argv[++index]; if (value === undefined) throw new Error(`--${key} requires a value`); options[key] = value;
14
14
  }
15
15
  return { positional, options };
@@ -29,7 +29,7 @@ function common(options) {
29
29
  childSessionId: options["child-session-id"],
30
30
  model: options.model, reasoning: options.reasoning, mode: options.mode,
31
31
  permission: options.permission ?? "deny", ddFlowBin: options["dd-flow-bin"],
32
- ddFlowHome: options["dd-flow-home"], projectRoot: options["project-root"],
32
+ ddFlowHome: options["dd-flow-home"], projectRoot: options["project-root"], noFlow: options["no-flow"] === true,
33
33
  env: options["dd-flow-home"] ? { DD_FLOW_HOME: options["dd-flow-home"] } : undefined,
34
34
  timeoutMs: seconds("timeout"),
35
35
  livenessTimeoutMs: seconds("liveness-timeout"),
@@ -63,7 +63,7 @@ export async function serveDaemon(stateDir) {
63
63
  const params = request.params ?? {}, operation = request.operation;
64
64
  if (operation === "daemon.status") { let observationError; try { await runtime.refreshTopology(); } catch (error) { observationError = errorRecord(error); } return { observation_error: observationError ?? null, daemon_id: state.daemon_id, pid: process.pid, config: state.config, shutdown_state: state.shutdown_state, provider_ready: Boolean(runtime.child && !runtime.closed), active_operation: busy, active_tree: Boolean(runtime.active || runtime.workingState !== "idle" || runtime.topologyIncomplete || [...runtime.topology.values()].some(child => !["completed", "cancelled", "failed"].includes(child.status))), sessions: runtime.rootId ? [runtime.rootId] : [], provider_pid: runtime.closed ? null : runtime.child?.pid }; }
65
65
  if (operation === "hook.observe") return await runtime.observeHook(params.payload, params.eventId);
66
- if (operation === "session.inspect") return await runtime.inspect(params.sessionId);
66
+ if (operation === "session.inspect") return await runtime.inspect(params.sessionId, { ingestUsage: params.noFlow !== true });
67
67
  if (operation === "session.cancel") {
68
68
  runtime.requireIdentity(params.sessionId); const cleanup = await runtime.closeTree(true); let inspected;
69
69
  try { inspected = await runtime.inspect(params.sessionId, { ignoreActive: true }); }
@@ -372,13 +372,13 @@ export class DroidRuntime {
372
372
  }
373
373
  return [...this.topology.values()];
374
374
  }
375
- async snapshotUsage(id) {
375
+ async snapshotUsage(id, { ingest = true } = {}) {
376
376
  const native = await readDroidSession(this.paths.factory, id); if (!native) return { status: "unavailable", provider_session_id: id };
377
377
  this.journaledTools ??= new Map();
378
378
  journalToolObservations(this.config.journal, [...native.records.flatMap(record => droidToolObservations(record, id)), ...native.tool_calls.reasons.map(reason => ({ reason }))], this.journaledTools);
379
379
  const measured = native.usage;
380
380
  const payload = { provider_session_id: id, daemon_id: this.config.daemonId, observed_at: new Date().toISOString(), usage_scope: "physical_session", completeness: native.complete && measured ? "complete" : "partial", usage: measured, tool_calls: native.tool_calls, source: { path: native.transcript_path, sha256: native.source_sha256 } };
381
- if (measured && this.config.ddFlowBin) await this.flow("usage", "ingest", payload);
381
+ if (ingest && measured && this.config.ddFlowBin) await this.flow("usage", "ingest", payload);
382
382
  return { status: measured ? "measured" : "unavailable", ...payload };
383
383
  }
384
384
  async flow(family, command, payload) {
@@ -390,9 +390,9 @@ export class DroidRuntime {
390
390
  child.on("error", error => { clearTimeout(timer); reject(error); }); child.on("close", code => { clearTimeout(timer); if (code !== 0) return reject(new DroidError("droid_flow_rejected", stderr || "dd-flow rejected Droid evidence")); try { resolve(JSON.parse(stdout)); } catch (error) { reject(error); } }); child.stdin.end(`${JSON.stringify(payload)}\n`);
391
391
  });
392
392
  }
393
- async inspect(id, { ignoreActive = false } = {}) {
393
+ async inspect(id, { ignoreActive = false, ingestUsage = true } = {}) {
394
394
  this.requireIdentity(id); await this.captureProcesses(); const children = await this.refreshTopology(); const source = await readDroidSession(this.paths.factory, id);
395
- const usage = []; for (const sessionId of [id, ...children.map(child => child.provider_session_id)]) usage.push(await this.snapshotUsage(sessionId));
395
+ const usage = []; for (const sessionId of [id, ...children.map(child => child.provider_session_id)]) usage.push(await this.snapshotUsage(sessionId, { ingest: ingestUsage }));
396
396
  const settled = !this.topologyIncomplete && (!this.active || ignoreActive) && (this.closed || this.workingState === "idle") && children.every(child => terminal(child.status));
397
397
  return { harness: "droid-cli", runtime_family: "droid", session: { harness_id: "droid-cli", session_id: id }, provider_session_id: id, adapter_session_id: id, cwd: this.config.cwd, observed_profile: source?.settings ? await this.observeSettings(id, source.settings, { path: source.transcript_path, sha256: source.source_sha256 }) : this.observedProfile, observed_runtime: { droid: this.config.version, factory_protocol: this.protocol ?? DROID_PROTOCOL, dd_harness_contract: DROID_CONTRACT }, status: settled ? "idle" : "running", settled, descendants: children, transcript_path: source?.transcript_path ?? null, usage, result: this.lastResult, provider_pid: this.closed ? null : this.child?.pid ?? null };
398
398
  }
@@ -82,7 +82,7 @@ async function inspect(bridge, sessionId, options) {
82
82
  }
83
83
 
84
84
  async function forwardUsage(options, providerSessionId, usage, toolCalls) {
85
- if (!options.ddFlowBin || !usage || usage.unavailable) return;
85
+ if (options.noFlow || !options.ddFlowBin || !usage || usage.unavailable) return;
86
86
  const measured = usage.usage && typeof usage.usage === "object" ? usage.usage : usage;
87
87
  const input = JSON.stringify({ provider_session_id: providerSessionId, daemon_id: options.daemonId ?? null, observed_at: new Date().toISOString(), usage: measured, tool_calls: toolCalls, usage_scope: usageScope(options, providerSessionId), completeness: measured.usageIsIncomplete ? "partial" : "complete" });
88
88
  const target = executable(options.ddFlowBin, ["grok", "usage", "ingest", "--project-root", absolute(options.projectRoot, "--project-root"), "--json"]);
@@ -743,9 +743,10 @@ export function handleAgyEvent(context, input) {
743
743
  throw new AppError("agy_session_identity_invalid", "Antigravity event is missing trusted physical identity", 1);
744
744
  const rawInput = objectRecord(event.input);
745
745
  const command = stringValue(rawInput.command) ?? stringValue(rawInput.cmd) ?? stringValue(rawInput.CommandLine);
746
- if (event.phase === "before" && command && (lifecycleFacts(command) || continuationCommand(command))
747
- && (!Number.isInteger(event.execution_num) || !Number.isInteger(event.step_index))) {
748
- throw new AppError("agy_native_call_identity_missing", "Participating Antigravity lifecycle calls require native execution and step identity", 1, { phase: "prepare", effect: "no_effect", recoverable: false });
746
+ const invalidStep = !Number.isSafeInteger(event.step_index) || Number(event.step_index) < 0;
747
+ const invalidExecution = event.execution_num !== undefined && (!Number.isSafeInteger(event.execution_num) || Number(event.execution_num) < 0);
748
+ if (event.phase === "before" && command && (lifecycleFacts(command) || continuationCommand(command)) && (invalidStep || invalidExecution)) {
749
+ throw new AppError("agy_native_call_identity_missing", "Participating Antigravity lifecycle calls require a native step identity and a valid execution identity when provided", 1, { phase: "prepare", effect: "no_effect", recoverable: false });
749
750
  }
750
751
  const translated = {
751
752
  schema_id: "dd-flow/opencode-tool-event@1", source_harness: "antigravity-cli", phase: event.phase, event_id: eventId,
@@ -29,7 +29,7 @@ export function assertNativeCancellationReceipt(value, sessionId) {
29
29
  /** Native observation is shared by RUN and non-Work EVAL owners. */
30
30
  export async function inspectControlledNativeSession(input) {
31
31
  input.assertCurrent();
32
- const receipt = await runHarnessAdapter({ executable: input.executable, args: ["session", "inspect", "--session-id", input.sessionId, "--state-dir", input.stateDir, "--json"], env: input.env, operationId: input.operationId, timeoutMs: 30_000 });
32
+ const receipt = await runHarnessAdapter({ executable: input.executable, args: ["session", "inspect", "--session-id", input.sessionId, "--state-dir", input.stateDir, ...(input.settlementObservation ? ["--no-flow"] : []), "--json"], env: input.env, operationId: input.operationId, timeoutMs: 30_000 });
33
33
  if (adapterSessionId(receipt) !== input.sessionId)
34
34
  throw new AppError("controller_operation_mismatch", "Session observation belongs to another target", 1);
35
35
  if (input.harness && receipt.harness !== undefined && receipt.harness !== input.harness)
@@ -474,7 +474,7 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
474
474
  : { session_id: session.id, state_dir: session.state_dir, harness, status: "pending", reason: "shutdown_session_unproven" };
475
475
  if (!interrupt) {
476
476
  try {
477
- const receipt = await inspectControlledNativeSession({ executable, sessionId: session.id, stateDir: session.state_dir, harness, env, operationId: `${observationId}:inspect:${crypto.randomUUID()}`, assertCurrent });
477
+ const receipt = await inspectControlledNativeSession({ executable, sessionId: session.id, stateDir: session.state_dir, harness, env, operationId: `${observationId}:inspect:${crypto.randomUUID()}`, assertCurrent, settlementObservation: true });
478
478
  return { session_id: session.id, state_dir: session.state_dir, harness: harness ?? (typeof receipt.harness === "string" ? receipt.harness : null), status: receipt.settled === true ? "completed" : "pending", receipt };
479
479
  }
480
480
  catch (error) {
@@ -511,7 +511,7 @@ async function reconcileOwnedSessions(context, input, interrupt, boundary) {
511
511
  return { operation_id: operationId, session_id: session.id, status: "outcome_unknown", error: cancellationError };
512
512
  }
513
513
  try {
514
- const observed = await inspectControlledNativeSession({ executable, sessionId: session.id, stateDir: session.state_dir, harness, env, operationId: `${observationId}:inspect:${crypto.randomUUID()}`, assertCurrent });
514
+ const observed = await inspectControlledNativeSession({ executable, sessionId: session.id, stateDir: session.state_dir, harness, env, operationId: `${observationId}:inspect:${crypto.randomUUID()}`, assertCurrent, settlementObservation: true });
515
515
  return { operation_id: operationId, session_id: session.id, status: observed.settled === true ? "completed" : "pending", receipt: observed, cancellation, reused: !inserted.changes, ...(cancellationError ? { cancellation_error: cancellationError } : {}) };
516
516
  }
517
517
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.96",
3
+ "version": "0.9.0-beta.97",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {