@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 +6 -0
- package/README.md +21 -0
- package/dist/build-info.json +5 -5
- package/dist/cli/help.js +12 -0
- package/dist/cli/run-cli.js +13 -3
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/services/cli-operation-classifier.js +2 -0
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +36 -16
- package/dist/services/usage.js +13 -3
- package/package.json +1 -1
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:
|
package/dist/build-info.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
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.18",
|
|
4
|
+
"cli_commit": "5624d92e8e96732db6a81f1cf86fb5342f38401f",
|
|
5
|
+
"built_at": "2026-09-06T03:05:30.379Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
|
-
"version": "4.0.
|
|
8
|
-
"commit": "
|
|
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",
|
package/dist/cli/run-cli.js
CHANGED
|
@@ -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
|
|
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];
|
package/dist/services/hooks.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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(
|
|
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 ${
|
|
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",
|
|
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 };
|
package/dist/services/usage.js
CHANGED
|
@@ -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);
|