@deksden-com/dd-flow-cli 0.9.0-beta.17 → 0.9.0-beta.18

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,11 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.18
4
+
5
+ ### Patch Changes
6
+
7
+ - Add Factory Droid harness routing, native lifecycle receipts and physical Session usage ingestion. Preserve child identity and reject conflicting hook replays or inclusive Droid usage to prevent incorrect Work binding and double counting.
8
+
3
9
  ## 0.9.0-beta.17
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -17,6 +17,27 @@ Never use a broad `pkill`/`killall`. A failed owned stop retains resources.
17
17
 
18
18
  It does not replace Memory Bank prompts and does not make product, design, merge, or verification judgments. Prompts own intent, route selection, planning depth, evidence meaning, and semantic readiness. The CLI owns explicit local state: projects, protocols, Codex session bindings, transitions, worktree records, lanes, locks, merge queue jobs, hook records, and audit events.
19
19
 
20
+ ## Factory Droid harness
21
+
22
+ Configure `droid-cli` in `~/.dd-flow/harnesses.json` with absolute
23
+ `adapter_command` (`dd-droid`) and `runtime_command` (`droid`) paths. Agent
24
+ profiles use the `droid` alias; the adapter receives `--droid-bin`.
25
+
26
+ The controlled adapter sends `dd-flow/droid-tool-event@1` JSON to
27
+ `dd-flow droid event handle --project-root <root> --json`. Events include a
28
+ stable `event_id`, `daemon_id`, `phase` (`before` or `after`), native Session
29
+ identity and parent, absolute Session directory, `Execute` input, observed
30
+ profile and transcript path. Before a lifecycle invocation, the returned
31
+ `hookSpecificOutput.updatedInput` adds its one-use `--hook-event-id` receipt.
32
+ The event ID belongs to the native hook invocation and is reused only for
33
+ transport retries. Session success is established by the Work contract.
34
+
35
+ `dd-flow droid usage ingest --project-root <root> --json` accepts cumulative
36
+ per-Session counters with explicit `usage_scope: physical_session` and
37
+ `completeness: complete|partial`. Inclusive root counters are rejected;
38
+ partial windows remain unavailable in RUN totals until complete evidence
39
+ arrives. Keep inclusive provider totals in adapter evidence for reconciliation.
40
+
20
41
  ## Runtime Model
21
42
 
22
43
  Root local database:
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.17",
4
- "cli_commit": "162dd994d19c0d454a2ad6b7b62abd70c3e848d6",
5
- "built_at": "2026-09-06T00:16:19.328Z",
3
+ "cli_version": "0.9.0-beta.18",
4
+ "cli_commit": "5624d92e8e96732db6a81f1cf86fb5342f38401f",
5
+ "built_at": "2026-09-06T03:05:30.379Z",
6
6
  "built_with_canon": {
7
- "version": "4.0.4",
8
- "commit": "ced59c19d0a4062146a1dc4ccb977e10284a2fe4",
7
+ "version": "4.0.5",
8
+ "commit": "272579e9035b88772a0f017c9bec09f877c17552",
9
9
  "flow_contract": "dd-flow-canonical-2026-08",
10
10
  "repo_root": "/Users/deksden/Documents/_Projects/dd-memorybank",
11
11
  "memorybank_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank",
package/dist/cli/help.js CHANGED
@@ -38,6 +38,7 @@ Core commands:
38
38
  codex home/hooks/hook Manage Codex homes, hooks, and hook events.
39
39
  zcode event/usage Ingest trusted zcode-acp lifecycle and usage evidence.
40
40
  grok event/usage Ingest trusted Grok Build lifecycle and usage evidence.
41
+ droid event/usage Ingest trusted Factory Droid lifecycle and physical usage evidence.
41
42
  opencode event/usage Ingest trusted OpenCode lifecycle and usage evidence.
42
43
  agy event/usage Ingest trusted Antigravity CLI lifecycle and usage evidence.
43
44
  worktree plan/create/status Record Worktrunk-backed feature worktrees.
@@ -549,6 +550,17 @@ Usage:
549
550
 
550
551
  Both payloads are read from stdin. The event adapter retains lifecycle identity;
551
552
  the usage adapter retains cumulative provider counters for RUN accounting.`
553
+ ],
554
+ [
555
+ "droid event",
556
+ `dd-flow droid event - ingest a controlled Factory Droid Execute hook
557
+
558
+ Usage:
559
+ dd-flow droid event handle --project-root <root> --json
560
+ dd-flow droid usage ingest --project-root <root> --json
561
+
562
+ Reads trusted adapter JSON from stdin. Usage requires physical_session scope and explicit completeness.
563
+ `
552
564
  ],
553
565
  [
554
566
  "opencode event",
@@ -10,7 +10,7 @@ import { blockPlanItem, completePlanItem, getPlanStatus, skipPlanItem, startPlan
10
10
  import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
11
11
  import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
12
12
  import { cleanupApply, cleanupScan } from "../services/cleanup.js";
13
- import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
13
+ import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleDroidEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
14
14
  import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
15
15
  import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
16
16
  import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
@@ -26,7 +26,7 @@ import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunSe
26
26
  import { previewNextEntityId } from "../services/ids.js";
27
27
  import { validateSchema } from "../services/schema-validation.js";
28
28
  import { preflightMemoryPermissions } from "../services/memory-permissions.js";
29
- import { ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
29
+ import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
30
30
  import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, verifyMigrationReport } from "../services/migrations.js";
31
31
  import { publishProjectSummary } from "../services/project-summary.js";
32
32
  import { requireProjectByRoot } from "../services/projects.js";
@@ -1070,6 +1070,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1070
1070
  throw new AppError("usage", "Unknown opencode usage action", 2);
1071
1071
  return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1072
1072
  }
1073
+ if (family === "droid" && command === "event") {
1074
+ if (requiredPosition(parsed, 0, "droid event action") !== "handle")
1075
+ throw new AppError("usage", "Unknown droid event action", 2);
1076
+ return handleDroidEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1077
+ }
1078
+ if (family === "droid" && command === "usage") {
1079
+ if (requiredPosition(parsed, 0, "droid usage action") !== "ingest")
1080
+ throw new AppError("usage", "Unknown droid usage action", 2);
1081
+ return ingestDroidUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1082
+ }
1073
1083
  if (family === "agy" && command === "event")
1074
1084
  return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
1075
1085
  if (family === "agy" && command === "usage") {
@@ -2372,7 +2382,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
2372
2382
  if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
2373
2383
  return requiredOption(parsed, "project-root");
2374
2384
  }
2375
- if (family === "agy" && ((command === "event" && requiredPosition(parsed, 0, "agy event action") === "handle") || command === "usage"))
2385
+ if (["agy", "droid"].includes(family ?? "") && ((command === "event" && requiredPosition(parsed, 0, `${family} event action`) === "handle") || command === "usage"))
2376
2386
  return requiredOption(parsed, "project-root");
2377
2387
  if (family === "codex" && command === "hooks") {
2378
2388
  const action = requiredPosition(parsed, 0, "codex hooks action");
@@ -7,7 +7,7 @@
7
7
  "properties": {
8
8
  "schema_id": {"const": "dd-flow/agent-profile@1"},
9
9
  "id": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"},
10
- "harness": {"enum": ["codex", "zcode", "agy", "opencode", "grok"]},
10
+ "harness": {"enum": ["codex", "zcode", "agy", "opencode", "grok", "droid"]},
11
11
  "provider": {"type": "string", "minLength": 1},
12
12
  "model": {"type": "string", "minLength": 1},
13
13
  "reasoning": {"type": "string", "minLength": 1},
@@ -106,6 +106,8 @@ function isRouterNative(args) {
106
106
  || (first === "zcode" && args[1] === "usage" && args[2] === "ingest")
107
107
  || (first === "grok" && args[1] === "event" && args[2] === "handle")
108
108
  || (first === "grok" && args[1] === "usage" && args[2] === "ingest")
109
+ || (first === "droid" && args[1] === "event" && args[2] === "handle")
110
+ || (first === "droid" && args[1] === "usage" && args[2] === "ingest")
109
111
  || (first === "opencode" && args[1] === "event" && args[2] === "handle")
110
112
  || (first === "opencode" && args[1] === "usage" && args[2] === "ingest")
111
113
  || (first === "agy" && args[1] === "event" && args[2] === "handle")
@@ -10,6 +10,7 @@ export function harnessKey(value) {
10
10
  zcode: "zcode-acp", "zcode-acp": "zcode-acp",
11
11
  grok: "grok-acp", "grok-acp": "grok-acp",
12
12
  agy: "antigravity-cli", "antigravity-cli": "antigravity-cli",
13
+ droid: "droid-cli", "droid-cli": "droid-cli",
13
14
  opencode: "opencode-server", "opencode-server": "opencode-server"
14
15
  };
15
16
  const resolved = keys[value];
@@ -60,6 +61,7 @@ export function harnessRuntimeArguments(ddFlowHome, harness) {
60
61
  "zcode-acp": "--zcode-acp-bin",
61
62
  "grok-acp": "--grok-bin",
62
63
  "antigravity-cli": "--agy-bin",
64
+ "droid-cli": "--droid-bin",
63
65
  "opencode-server": "--opencode-bin"
64
66
  };
65
67
  return [option[key], runtime];
@@ -577,13 +577,25 @@ export function handleGrokEvent(context, input) {
577
577
  /** Convert a controlled OpenCode tool hook into the shared lifecycle receipt. */
578
578
  export function handleOpenCodeEvent(context, input) {
579
579
  const event = parseJsonObject(input.stdin || "{}", "OpenCode tool event");
580
- const agy = event.source_harness === "antigravity-cli";
581
- const harness = agy ? "antigravity-cli" : "opencode-server";
582
580
  if (event.schema_id !== "dd-flow/opencode-tool-event@1")
583
581
  throw new AppError("unsupported_opencode_event_schema", "Unsupported OpenCode event schema", 1);
582
+ return handleControlledToolEvent(context, input, event, event.source_harness === "antigravity-cli" ? "antigravity-cli" : "opencode-server");
583
+ }
584
+ /** Accept adapter-observed Factory identity, never a model-supplied Session ID. */
585
+ export function handleDroidEvent(context, input) {
586
+ const event = parseJsonObject(input.stdin || "{}", "Droid tool event");
587
+ if (event.schema_id !== "dd-flow/droid-tool-event@1")
588
+ throw new AppError("unsupported_droid_event_schema", "Unsupported Droid event schema", 1);
589
+ return handleControlledToolEvent(context, input, event, "droid-cli");
590
+ }
591
+ function handleControlledToolEvent(context, input, event, harness) {
592
+ const agy = harness === "antigravity-cli";
593
+ const droid = harness === "droid-cli";
594
+ const label = droid ? "Droid" : agy ? "Antigravity" : "OpenCode";
595
+ const errorPrefix = droid ? "droid" : agy ? "agy" : "opencode";
584
596
  const phase = stringValue(event.phase);
585
597
  if (phase !== "before" && phase !== "after")
586
- throw new AppError("unsupported_opencode_event_schema", "OpenCode event phase must be before or after", 1);
598
+ throw new AppError(`unsupported_${errorPrefix}_event_schema`, `${label} event phase must be before or after`, 1);
587
599
  const session = objectRecord(event.session);
588
600
  const profile = objectRecord(event.profile);
589
601
  const rawInput = objectRecord(event.input);
@@ -591,16 +603,18 @@ export function handleOpenCodeEvent(context, input) {
591
603
  const nativeParentId = stringValue(session.parent_provider_session_id);
592
604
  const daemonId = stringValue(event.daemon_id);
593
605
  const toolCallId = stringValue(event.tool_call_id);
594
- const eventId = stringValue(event.event_id) ?? toolCallId;
606
+ const eventId = stringValue(event.event_id) ?? (droid ? undefined : toolCallId);
595
607
  const toolName = stringValue(event.tool);
596
608
  const directory = stringValue(session.directory);
597
- if (!providerSessionId || !daemonId || !toolCallId || !eventId || !toolName || !directory || !path.isAbsolute(directory)) {
598
- throw new AppError("opencode_session_identity_invalid", "OpenCode event is missing trusted physical identity", 1);
609
+ if (!providerSessionId || !daemonId || (!droid && !toolCallId) || !eventId || !toolName || !directory || !path.isAbsolute(directory)) {
610
+ throw new AppError(`${errorPrefix}_session_identity_invalid`, `${label} event is missing trusted physical identity`, 1);
599
611
  }
600
612
  const expectedRoot = resolveProjectRoot(input.projectRoot);
601
613
  if (resolveProjectRoot(directory) !== expectedRoot) {
602
- throw new AppError(agy ? "agy_directory_mismatch" : "opencode_directory_mismatch", `${agy ? "Antigravity" : "OpenCode"} Session directory does not match the controlled workspace`, 1, { expected: expectedRoot, actual: directory });
614
+ throw new AppError(`${errorPrefix}_directory_mismatch`, `${label} Session directory does not match the controlled workspace`, 1, { expected: expectedRoot, actual: directory });
603
615
  }
616
+ if (droid && nativeParentId === providerSessionId)
617
+ throw new AppError("droid_session_identity_invalid", "A Droid Session cannot parent itself", 1);
604
618
  const sessionId = storageSessionId(nativeSessionIdentity(harness, providerSessionId));
605
619
  const parentSessionId = nativeParentId ? storageSessionId(nativeSessionIdentity(harness, nativeParentId)) : null;
606
620
  const command = stringValue(rawInput.command) ?? stringValue(rawInput.cmd) ?? stringValue(rawInput.CommandLine);
@@ -611,43 +625,49 @@ export function handleOpenCodeEvent(context, input) {
611
625
  projectId: project.id, protocolId: null, harness, providerSessionId, parentSessionId,
612
626
  daemonId, sessionId, agentId: stringValue(session.agent_id) ?? null, turnId: stringValue(event.message_id) ?? null,
613
627
  eventName: "PostToolUse", toolName, status: "observed", payload: { session_id: providerSessionId, cwd: expectedRoot, tool_name: toolName, status: objectRecord(event.outcome).status },
614
- eventKey: baseKey, matchKey: null, transcriptPath: null, cwd: expectedRoot
628
+ eventKey: baseKey, matchKey: null, transcriptPath: droid ? stringValue(event.transcript_path) ?? null : null, cwd: expectedRoot
615
629
  });
616
630
  return { ok: true, observed: inserted, duplicate: !inserted, event_key: baseKey, phase, harness, session: nativeSessionIdentity(harness, providerSessionId), ...(nativeParentId ? { parent_session: nativeSessionIdentity(harness, nativeParentId) } : {}) };
617
631
  }
618
- if (!command || !["bash", "Bash", "run_command", "run_terminal_command", "RunTerminalCommand"].includes(toolName)) {
632
+ if (!command || !(droid ? ["Execute"] : ["bash", "Bash", "run_command", "run_terminal_command", "RunTerminalCommand"]).includes(toolName)) {
619
633
  return { ok: true, observed: false, reason: command ? "non_bash_tool" : "event_not_participating" };
620
634
  }
621
635
  const lifecycle = lifecycleFacts(command);
622
636
  if (!lifecycle)
623
637
  return { ok: true, observed: false, reason: "event_not_participating" };
624
638
  if (lifecycle.analysis.kind === "compound") {
625
- throw new AppError("compound_lifecycle_command", `dd-flow lifecycle commands must be a standalone ${agy ? "Antigravity" : "OpenCode"} shell tool call`, 1, { standalone_command: lifecycle.invocation.command });
639
+ throw new AppError("compound_lifecycle_command", `dd-flow lifecycle commands must be a standalone ${label} shell tool call`, 1, { standalone_command: lifecycle.invocation.command });
626
640
  }
627
641
  const { flowPayload, bootstrapStageStart, commandProjectRoot } = lifecycle;
628
642
  if (commandProjectRoot && resolveProjectRoot(commandProjectRoot) !== expectedRoot) {
629
- throw new AppError("project_mismatch", "OpenCode lifecycle command project root does not match the controlled workspace", 1, { expected: expectedRoot, actual: commandProjectRoot });
643
+ throw new AppError("project_mismatch", `${label} lifecycle command project root does not match the controlled workspace`, 1, { expected: expectedRoot, actual: commandProjectRoot });
630
644
  }
631
645
  const matchKey = lifecycleMatchKey(lifecycle, expectedRoot);
632
646
  if (bootstrapStageStart)
633
647
  registerProject(context, { root: expectedRoot });
634
648
  const project = requireProjectByRoot(context, expectedRoot);
649
+ if (droid) {
650
+ const existing = context.db.get("SELECT harness, provider_session_id, parent_session_id, daemon_id, match_key, tool_name FROM hook_events WHERE project_id = ? AND event_key = ?", [project.id, baseKey]);
651
+ if (existing && (existing.harness !== harness || existing.provider_session_id !== providerSessionId || existing.parent_session_id !== parentSessionId || existing.daemon_id !== daemonId || existing.match_key !== matchKey || existing.tool_name !== toolName)) {
652
+ throw new AppError("droid_event_replay_mismatch", "Droid event ID was already observed with different lifecycle identity", 1);
653
+ }
654
+ }
635
655
  const agentId = stringValue(session.agent_id);
636
656
  const observedSession = flowPayload ? bindObservedFlowSession(context, project, {
637
657
  ...flowPayload, harness, provider_session_id: providerSessionId, agent_id: agentId ?? null,
638
658
  parent_session_id: parentSessionId, provider: stringValue(profile.provider) ?? null, model: stringValue(profile.model) ?? null,
639
- reasoning: stringValue(profile.variant) ?? null, mode: stringValue(profile.agent) ?? null, agent_type: stringValue(profile.agent) ?? null,
640
- metadata: { opencode_version: stringValue(event.server_version) ?? null, api_fingerprint: stringValue(event.api_fingerprint) ?? null, plugin_sha256: stringValue(event.plugin_sha256) ?? null, native_project_id: stringValue(session.project_id) ?? null }
659
+ reasoning: stringValue(droid ? profile.reasoning : profile.variant) ?? null, mode: stringValue(droid ? profile.mode : profile.agent) ?? null, agent_type: stringValue(droid ? profile.agent_type : profile.agent) ?? null,
660
+ metadata: droid ? { droid_version: stringValue(event.runtime_version) ?? null } : { opencode_version: stringValue(event.server_version) ?? null, api_fingerprint: stringValue(event.api_fingerprint) ?? null, plugin_sha256: stringValue(event.plugin_sha256) ?? null, native_project_id: stringValue(session.project_id) ?? null }
641
661
  }, sessionId) : undefined;
642
662
  const payload = {
643
663
  session_id: providerSessionId, agent_id: agentId, cwd: expectedRoot, tool_name: toolName, tool_input: rawInput, command,
644
- provider: stringValue(profile.provider), model: stringValue(profile.model), reasoning: stringValue(profile.variant),
645
- mode: stringValue(profile.agent), agent_type: stringValue(profile.agent)
664
+ provider: stringValue(profile.provider), model: stringValue(profile.model), reasoning: stringValue(droid ? profile.reasoning : profile.variant),
665
+ mode: stringValue(droid ? profile.mode : profile.agent), agent_type: stringValue(droid ? profile.agent_type : profile.agent)
646
666
  };
647
667
  const inserted = recordHookEvent(context, {
648
668
  projectId: project.id, protocolId: observedSession?.protocol_id ?? null, harness, providerSessionId, parentSessionId,
649
669
  daemonId, sessionId, agentId: agentId ?? null, turnId: stringValue(event.message_id) ?? null, eventName: "PreToolUse", toolName,
650
- status: "observed", payload, eventKey: baseKey, matchKey, transcriptPath: null, cwd: expectedRoot
670
+ status: "observed", payload, eventKey: baseKey, matchKey, transcriptPath: droid ? stringValue(event.transcript_path) ?? null : null, cwd: expectedRoot
651
671
  });
652
672
  recordFlowSessionObservation(context, { projectId: project.id, sessionId, runId: observedSession?.run_id ?? null, protocolId: observedSession?.protocol_id ?? null, cwd: expectedRoot, toolName, eventKey: baseKey });
653
673
  const result = { ok: true, observed: inserted, duplicate: !inserted, event_key: baseKey, phase, harness, session: nativeSessionIdentity(harness, providerSessionId), ...(nativeParentId ? { parent_session: nativeSessionIdentity(harness, nativeParentId) } : {}), daemon_id: daemonId };
@@ -19,6 +19,14 @@ export function ingestOpenCodeUsage(context, input) {
19
19
  export function ingestAgyUsage(context, input) {
20
20
  return ingestHarnessUsage(context, input, { harness: "antigravity-cli", label: "Antigravity", idPrefix: "AUSG", sourceKind: "antigravity_cli_session_usage_v1" });
21
21
  }
22
+ /** Droid counters must describe one physical Session; inclusive roots double-count children. */
23
+ export function ingestDroidUsage(context, input) {
24
+ const payload = parseJsonObject(input.stdin || "{}", "Droid usage snapshot");
25
+ if (payload.usage_scope !== "physical_session" || !["complete", "partial"].includes(String(payload.completeness))) {
26
+ throw new Error("Droid usage requires physical_session scope and complete or partial completeness");
27
+ }
28
+ return ingestHarnessUsage(context, input, { harness: "droid-cli", label: "Droid", idPrefix: "DUSG", sourceKind: "droid_session_usage_v1" });
29
+ }
22
30
  function ingestHarnessUsage(context, input, harness) {
23
31
  const payload = parseJsonObject(input.stdin || "{}", `${harness.label} usage snapshot`);
24
32
  const usage = object(payload.usage);
@@ -262,9 +270,9 @@ function toolCallsForLinks(context, links, observedAt) {
262
270
  result.status = result.observed_sessions === 0 ? "unavailable" : result.unavailable_sessions.length ? "partial" : "measured";
263
271
  return result;
264
272
  }
265
- function isExternalHarness(harness) { return harness === "zcode-acp" || harness === "grok-acp" || harness === "opencode-server" || harness === "antigravity-cli"; }
273
+ function isExternalHarness(harness) { return harness === "droid-cli" || harness === "zcode-acp" || harness === "grok-acp" || harness === "opencode-server" || harness === "antigravity-cli"; }
266
274
  function harnessSourceKind(harness) {
267
- return harness === "grok-acp" ? "grok_session_usage_v1" : harness === "zcode-acp" ? "zcode_session_usage_v1" : harness === "antigravity-cli" ? "antigravity_cli_session_usage_v1" : "opencode_session_usage_v1";
275
+ return harness === "droid-cli" ? "droid_session_usage_v1" : harness === "grok-acp" ? "grok_session_usage_v1" : harness === "zcode-acp" ? "zcode_session_usage_v1" : harness === "antigravity-cli" ? "antigravity_cli_session_usage_v1" : "opencode_session_usage_v1";
268
276
  }
269
277
  function readHarnessToolCallsWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
270
278
  if (!providerSessionId)
@@ -425,7 +433,7 @@ function hasInclusiveUsageSnapshot(context, projectId, harness, providerSessionI
425
433
  function readHarnessUsageWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
426
434
  if (!providerSessionId)
427
435
  return { status: "not_observable", diagnostic: "provider_session_id_missing" };
428
- const snapshots = context.db.all(`SELECT observed_at, total_tokens, input_tokens, cache_read_input_tokens, cache_write_input_tokens, output_tokens, reasoning_output_tokens
436
+ const snapshots = context.db.all(`SELECT completeness, observed_at, total_tokens, input_tokens, cache_read_input_tokens, cache_write_input_tokens, output_tokens, reasoning_output_tokens
429
437
  FROM harness_usage_snapshots
430
438
  WHERE project_id = ? AND harness = ? AND provider_session_id = ? AND observed_at <= ?
431
439
  ORDER BY observed_at, id`, [projectId, harness, providerSessionId, observedAt]);
@@ -434,6 +442,8 @@ function readHarnessUsageWindow(context, harness, projectId, providerSessionId,
434
442
  const before = snapshots.filter((snapshot) => Date.parse(snapshot.observed_at) <= Date.parse(startedAt)).at(-1);
435
443
  if (!last || !before || last === before)
436
444
  return { status: "not_yet_emitted", diagnostic: `${harness}_usage_window_incomplete` };
445
+ if (harness === "droid-cli" && (last.completeness !== "complete" || before.completeness !== "complete"))
446
+ return { status: "not_observable", diagnostic: "droid-cli_usage_window_partial" };
437
447
  const delta = (value, baseline) => value === null || baseline === null ? null : Math.max(0, value - baseline);
438
448
  const input = delta(last.input_tokens, before.input_tokens);
439
449
  const cacheRead = delta(last.cache_read_input_tokens, before.cache_read_input_tokens);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.17",
3
+ "version": "0.9.0-beta.18",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {