@deksden-com/dd-flow-cli 0.9.0-beta.1 → 0.9.0-beta.100
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 +561 -0
- package/README.md +68 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +305 -0
- package/dist/cli/help.js +91 -15
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/run-cli.js +1506 -334
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/DD-ZCODE.md +99 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +39 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +449 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +468 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +300 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +217 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +941 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +267 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/harness-runtime/lib/zcode-usage.mjs +54 -0
- package/dist/runtime/context.js +3 -1
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/harness-config.schema.json +23 -0
- package/dist/schemas/plan-review-decision.schema.json +1 -1
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +75 -21
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +676 -79
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +134 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +125 -80
- package/dist/services/eval-snapshots.js +838 -74
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +68 -0
- package/dist/services/hooks.js +609 -310
- package/dist/services/lanes.js +62 -53
- package/dist/services/lifecycle-command.js +103 -17
- package/dist/services/lifecycle-contract.js +30 -0
- package/dist/services/lifecycle-invocations.js +1128 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +425 -0
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +78 -27
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/portable-refs.js +57 -0
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +27 -18
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +136 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +803 -0
- package/dist/services/run-engine-bindings.js +20 -62
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-projection.js +10 -8
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +437 -81
- package/dist/services/runtime-budget.js +439 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +21 -19
- package/dist/services/session-identity.js +19 -0
- package/dist/services/sessions.js +61 -80
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +112 -108
- package/dist/services/stage-pause.js +117 -73
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +108 -132
- package/dist/services/vnext-code-review.js +245 -98
- package/dist/services/vnext-code.js +355 -257
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +166 -26
- package/dist/services/vnext-merge.js +413 -147
- package/dist/services/vnext-plan-review.js +182 -109
- package/dist/services/vnext-plan.js +199 -82
- package/dist/services/vnext-protocolize.js +133 -60
- package/dist/services/vnext-specify.js +94 -71
- package/dist/services/work-registry.js +813 -160
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +569 -16
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +24 -13
- package/tools/audit-runtime-fix-boundaries.mjs +96 -0
- package/tools/repair-paused-run-status.mjs +59 -0
package/dist/services/usage.js
CHANGED
|
@@ -3,6 +3,9 @@ import fs from "node:fs";
|
|
|
3
3
|
import { parseJsonObject } from "../shared/json.js";
|
|
4
4
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
5
|
import { registerProject, requireProjectByRoot } from "./projects.js";
|
|
6
|
+
import { publicSessionIdentity } from "./session-identity.js";
|
|
7
|
+
import { runObservations } from "./run-observations.js";
|
|
8
|
+
import { ToolObservations, codexToolObservation } from "../harness-runtime/lib/tool-observations.mjs";
|
|
6
9
|
export function ingestZcodeUsage(context, input) {
|
|
7
10
|
return ingestHarnessUsage(context, input, { harness: "zcode-acp", label: "ZCode", idPrefix: "ZUSG", sourceKind: "zcode_session_usage_v1" });
|
|
8
11
|
}
|
|
@@ -18,6 +21,14 @@ export function ingestOpenCodeUsage(context, input) {
|
|
|
18
21
|
export function ingestAgyUsage(context, input) {
|
|
19
22
|
return ingestHarnessUsage(context, input, { harness: "antigravity-cli", label: "Antigravity", idPrefix: "AUSG", sourceKind: "antigravity_cli_session_usage_v1" });
|
|
20
23
|
}
|
|
24
|
+
/** Droid counters must describe one physical Session; inclusive roots double-count children. */
|
|
25
|
+
export function ingestDroidUsage(context, input) {
|
|
26
|
+
const payload = parseJsonObject(input.stdin || "{}", "Droid usage snapshot");
|
|
27
|
+
if (payload.usage_scope !== "physical_session" || !["complete", "partial"].includes(String(payload.completeness))) {
|
|
28
|
+
throw new Error("Droid usage requires physical_session scope and complete or partial completeness");
|
|
29
|
+
}
|
|
30
|
+
return ingestHarnessUsage(context, input, { harness: "droid-cli", label: "Droid", idPrefix: "DUSG", sourceKind: "droid_session_usage_v1" });
|
|
31
|
+
}
|
|
21
32
|
function ingestHarnessUsage(context, input, harness) {
|
|
22
33
|
const payload = parseJsonObject(input.stdin || "{}", `${harness.label} usage snapshot`);
|
|
23
34
|
const usage = object(payload.usage);
|
|
@@ -55,11 +66,11 @@ function ingestHarnessUsage(context, input, harness) {
|
|
|
55
66
|
values.input, values.cacheRead, values.cacheWrite, values.output, values.reasoning, values.requests, values.errors,
|
|
56
67
|
values.toolCalls, values.toolFailures, values.toolByName ? JSON.stringify(values.toolByName) : null, harness.sourceKind,
|
|
57
68
|
stringValue(payload.usage_scope) ?? "physical_session", stringValue(payload.completeness) ?? "complete", context.now()]);
|
|
58
|
-
return { ok: true, observed: inserted.changes === 1, duplicate: inserted.changes === 0, snapshot_id: id,
|
|
69
|
+
return { ok: true, observed: inserted.changes === 1, duplicate: inserted.changes === 0, snapshot_id: id, session: publicSessionIdentity({ harness: harness.harness, provider_session_id: providerSessionId, session_id: providerSessionId }) };
|
|
59
70
|
}
|
|
60
71
|
export function checkpointSessionUsage(context, session, input) {
|
|
61
72
|
const observedAt = context.now();
|
|
62
|
-
const result = readCodexTranscript(session.transcript_path, session
|
|
73
|
+
const result = readCodexTranscript(session.transcript_path, nativeSessionId(session));
|
|
63
74
|
const source = sourceFacts(session.transcript_path);
|
|
64
75
|
const snapshot = {
|
|
65
76
|
id: `USG-${crypto.randomUUID()}`,
|
|
@@ -114,7 +125,7 @@ export function checkpointSessionUsage(context, session, input) {
|
|
|
114
125
|
return snapshotForOutput(snapshot);
|
|
115
126
|
}
|
|
116
127
|
export function checkpointRunUsage(context, input) {
|
|
117
|
-
const sessions = context.db.all(`SELECT session_id, project_id, run_id, transcript_path, parent_session_id, role, aspect_id, plan_item_id, session_kind
|
|
128
|
+
const sessions = context.db.all(`SELECT session_id, project_id, run_id, transcript_path, provider_session_id, parent_session_id, role, aspect_id, plan_item_id, session_kind
|
|
118
129
|
FROM sessions WHERE project_id = ? AND run_id = ?`, [input.projectId, input.runId]);
|
|
119
130
|
return sessions.map((session) => checkpointSessionUsage(context, session, input));
|
|
120
131
|
}
|
|
@@ -136,7 +147,7 @@ export function recalculateRunUsage(context, input) {
|
|
|
136
147
|
// after `work finish` but before the agent's actual terminal event.
|
|
137
148
|
const accountingAt = observedAt;
|
|
138
149
|
const links = context.db.all(`SELECT ws.id, ws.work_id, ws.session_id, ws.created_at, ws.completed_at,
|
|
139
|
-
w.project_id, s.harness, s.transcript_path, s.parent_session_id, s.agent_id, s.provider_session_id
|
|
150
|
+
w.project_id, s.harness, s.transcript_path, s.parent_session_id, s.provider_parent_session_id, s.agent_id, s.provider_session_id
|
|
140
151
|
FROM work_sessions ws
|
|
141
152
|
JOIN works w ON w.work_id = ws.work_id
|
|
142
153
|
LEFT JOIN sessions s ON s.project_id = w.project_id AND s.session_id = ws.session_id
|
|
@@ -154,32 +165,38 @@ export function recalculateRunUsage(context, input) {
|
|
|
154
165
|
const settledSessions = settledRunSessions(context, links, observedAt);
|
|
155
166
|
const unsettledSessions = [...new Set(links.map((link) => link.session_id))].filter((sessionId) => !settledSessions.some((session) => session.session_id === sessionId));
|
|
156
167
|
const usageFinal = final && unsettledSessions.length === 0;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
context.db.
|
|
160
|
-
|
|
161
|
-
context.db.run(
|
|
168
|
+
const persistRunProjection = !input.sessionId && !input.stage;
|
|
169
|
+
if (persistRunProjection) {
|
|
170
|
+
context.db.exec("SAVEPOINT run_usage");
|
|
171
|
+
try {
|
|
172
|
+
context.db.run("DELETE FROM usage WHERE project_id = ? AND run_id = ?", [input.projectId, input.runId]);
|
|
173
|
+
for (const row of rows) {
|
|
174
|
+
context.db.run(`INSERT INTO usage (id, project_id, run_id, session_id, checkpoint, stage, stage_attempt, observed_at, total_tokens, input_tokens, cached_input_tokens, cache_read_input_tokens, cache_write_input_tokens, uncached_input_tokens, output_tokens, reasoning_output_tokens, source_kind, source_locator, source_sha256, source_size_bytes, source_mtime_ms, token_event_at, turn_id, turn_attribution, parser_version, extraction_status, diagnostic_code, created_at)
|
|
162
175
|
VALUES (?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL, 'windowed_work_session', 1, ?, ?, ?)`, [
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
176
|
+
`USG-${crypto.randomUUID()}`, input.projectId, input.runId, row.session_id, usageFinal ? "final" : "provisional", input.stage?.name ?? null, row.observed_at,
|
|
177
|
+
row.tokens?.total_tokens ?? null, row.tokens?.input_tokens ?? null, row.tokens?.cache_read_input_tokens ?? null,
|
|
178
|
+
row.tokens?.cache_read_input_tokens ?? null, row.tokens?.cache_write_input_tokens ?? null, row.tokens?.uncached_input_tokens ?? null,
|
|
179
|
+
row.tokens?.output_tokens ?? null, row.tokens?.reasoning_output_tokens ?? null, row.source_kind, row.source.locator, row.source.sha256, row.source.size, row.source.mtime,
|
|
180
|
+
row.status, row.diagnostic, observedAt
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
for (const session of settledSessions) {
|
|
184
|
+
context.db.run(`UPDATE sessions
|
|
172
185
|
SET status = 'stopped', stop_reason = 'provider_turn_complete', updated_at = ?, stopped_at = ?
|
|
173
186
|
WHERE project_id = ? AND run_id = ? AND session_id = ?
|
|
174
187
|
AND status = 'idle'
|
|
175
188
|
AND NOT EXISTS (SELECT 1 FROM work_sessions WHERE session_id = ? AND status = 'running')`, [observedAt, session.completed_at, input.projectId, input.runId, session.session_id, session.session_id]);
|
|
189
|
+
}
|
|
190
|
+
context.db.exec("RELEASE run_usage");
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
context.db.exec("ROLLBACK TO run_usage");
|
|
194
|
+
context.db.exec("RELEASE run_usage");
|
|
195
|
+
throw error;
|
|
176
196
|
}
|
|
177
|
-
context.db.exec("COMMIT");
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
context.db.exec("ROLLBACK");
|
|
181
|
-
throw error;
|
|
182
197
|
}
|
|
198
|
+
const observations = persistRunProjection ? runObservations(context, input.projectId, input.runId) : null;
|
|
199
|
+
const legacyTools = toolCallsForLinks(context, links, accountingAt);
|
|
183
200
|
return {
|
|
184
201
|
ok: true,
|
|
185
202
|
schema_id: "dd-flow/run-usage@2",
|
|
@@ -187,16 +204,29 @@ export function recalculateRunUsage(context, input) {
|
|
|
187
204
|
status: usageFinal ? "final" : "provisional",
|
|
188
205
|
scope: input.stage ? { kind: "stage", stage: input.stage.name, started_at: input.stage.startedAt, completed_at: input.stage.completedAt } : { kind: "run" },
|
|
189
206
|
observed_at: observedAt,
|
|
207
|
+
persisted: persistRunProjection,
|
|
190
208
|
totals: outputUsageTotals(totals),
|
|
191
|
-
sessions: rows,
|
|
209
|
+
sessions: publicUsageRows(context, input.projectId, rows),
|
|
192
210
|
session_reconciliation: {
|
|
193
|
-
stopped: settledSessions.map((session) => session.session_id),
|
|
194
|
-
unsettled: unsettledSessions
|
|
211
|
+
stopped: publicSessionIdentities(context, input.projectId, settledSessions.map((session) => session.session_id)),
|
|
212
|
+
unsettled: publicSessionIdentities(context, input.projectId, unsettledSessions)
|
|
195
213
|
},
|
|
196
|
-
tool_calls:
|
|
214
|
+
tool_calls: observations?.tools.observed_sessions ? observations.tools : legacyTools,
|
|
215
|
+
...(observations ? { observations, legacy_tool_calls: legacyTools } : {}),
|
|
197
216
|
coverage: rows.reduce((coverage, row) => ({ ...coverage, [row.status]: (coverage[row.status] ?? 0) + 1 }), {})
|
|
198
217
|
};
|
|
199
218
|
}
|
|
219
|
+
function publicUsageRows(context, projectId, rows) {
|
|
220
|
+
const identityFor = (storageId) => {
|
|
221
|
+
const row = context.db.get("SELECT harness, provider_session_id FROM sessions WHERE project_id = ? AND session_id = ?", [projectId, storageId]);
|
|
222
|
+
return row ? publicSessionIdentity({ harness: row.harness, provider_session_id: row.provider_session_id, session_id: storageId }) : null;
|
|
223
|
+
};
|
|
224
|
+
return rows.map(({ session_id, provider_session_id: _providerSessionId, parent_session_id, ...row }) => ({
|
|
225
|
+
...row,
|
|
226
|
+
session: identityFor(session_id),
|
|
227
|
+
...(parent_session_id ? { parent_session: identityFor(parent_session_id) } : {})
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
200
230
|
function overlapsStage(link, stage, observedAt) {
|
|
201
231
|
const linkStart = Date.parse(link.created_at);
|
|
202
232
|
const linkEnd = Date.parse(link.completed_at ?? observedAt);
|
|
@@ -212,8 +242,20 @@ function clipToStage(link, stage, observedAt) {
|
|
|
212
242
|
const completedAt = Date.parse(naturalEnd) > Date.parse(stageEnd) ? stageEnd : naturalEnd;
|
|
213
243
|
return { ...link, created_at: startedAt, completed_at: completedAt };
|
|
214
244
|
}
|
|
245
|
+
function nativeSessionId(session) {
|
|
246
|
+
return session.provider_session_id ?? session.session_id;
|
|
247
|
+
}
|
|
248
|
+
function publicSessionIdentities(context, projectId, storageIds) {
|
|
249
|
+
if (storageIds.length === 0)
|
|
250
|
+
return [];
|
|
251
|
+
const marks = storageIds.map(() => "?").join(", ");
|
|
252
|
+
const rows = context.db.all(`SELECT session_id, harness, provider_session_id FROM sessions WHERE project_id = ? AND session_id IN (${marks})`, [projectId, ...storageIds]);
|
|
253
|
+
const byStorage = new Map(rows.map((row) => [row.session_id, publicSessionIdentity(row)]));
|
|
254
|
+
return storageIds.flatMap((id) => byStorage.get(id) ?? []);
|
|
255
|
+
}
|
|
215
256
|
function toolCallsForLinks(context, links, observedAt) {
|
|
216
257
|
const result = { status: "unavailable", total: 0, failures: 0, by_tool: {}, observed_sessions: 0, unavailable_sessions: [] };
|
|
258
|
+
let incomplete = false;
|
|
217
259
|
const sessions = new Map();
|
|
218
260
|
for (const link of links)
|
|
219
261
|
sessions.set(link.session_id, [...(sessions.get(link.session_id) ?? []), link]);
|
|
@@ -221,23 +263,25 @@ function toolCallsForLinks(context, links, observedAt) {
|
|
|
221
263
|
const first = windows[0];
|
|
222
264
|
const parsed = isExternalHarness(first.harness)
|
|
223
265
|
? readHarnessToolCallsWindow(context, first.harness, first.project_id, first.provider_session_id, windows.map((item) => item.created_at).sort()[0], windows.map((item) => item.completed_at ?? observedAt).sort().at(-1), observedAt)
|
|
224
|
-
: readToolCallsForWindows(first.transcript_path,
|
|
266
|
+
: readToolCallsForWindows(first.transcript_path, nativeSessionId(first), windows, observedAt);
|
|
225
267
|
if (!parsed) {
|
|
226
268
|
result.unavailable_sessions.push(sessionId);
|
|
227
269
|
continue;
|
|
228
270
|
}
|
|
271
|
+
if ("completeness" in parsed && parsed.completeness !== "complete")
|
|
272
|
+
incomplete = true;
|
|
229
273
|
result.observed_sessions += 1;
|
|
230
274
|
result.total += parsed.total;
|
|
231
275
|
result.failures += parsed.failures;
|
|
232
276
|
for (const [name, count] of Object.entries(parsed.by_tool))
|
|
233
277
|
result.by_tool[name] = (result.by_tool[name] ?? 0) + count;
|
|
234
278
|
}
|
|
235
|
-
result.status = result.observed_sessions === 0 ? "unavailable" : result.unavailable_sessions.length ? "partial" : "measured";
|
|
279
|
+
result.status = result.observed_sessions === 0 ? "unavailable" : result.unavailable_sessions.length || incomplete ? "partial" : "measured";
|
|
236
280
|
return result;
|
|
237
281
|
}
|
|
238
|
-
function isExternalHarness(harness) { return harness === "zcode-acp" || harness === "grok-acp" || harness === "opencode-server" || harness === "antigravity-cli"; }
|
|
282
|
+
function isExternalHarness(harness) { return harness === "droid-cli" || harness === "zcode-acp" || harness === "grok-acp" || harness === "opencode-server" || harness === "antigravity-cli"; }
|
|
239
283
|
function harnessSourceKind(harness) {
|
|
240
|
-
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";
|
|
284
|
+
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";
|
|
241
285
|
}
|
|
242
286
|
function readHarnessToolCallsWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
|
|
243
287
|
if (!providerSessionId)
|
|
@@ -275,9 +319,7 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
275
319
|
return null;
|
|
276
320
|
try {
|
|
277
321
|
let sessionId;
|
|
278
|
-
|
|
279
|
-
let failures = 0;
|
|
280
|
-
const byTool = {};
|
|
322
|
+
const tools = new ToolObservations();
|
|
281
323
|
for (const line of fs.readFileSync(transcriptPath, "utf8").split(/\r?\n/)) {
|
|
282
324
|
if (!line.trim())
|
|
283
325
|
continue;
|
|
@@ -286,6 +328,7 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
286
328
|
event = JSON.parse(line);
|
|
287
329
|
}
|
|
288
330
|
catch {
|
|
331
|
+
tools.gaps.add("journal_invalid_record");
|
|
289
332
|
continue;
|
|
290
333
|
}
|
|
291
334
|
const payload = object(event.payload);
|
|
@@ -294,20 +337,13 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
294
337
|
if (event.type !== "response_item" || !payload)
|
|
295
338
|
continue;
|
|
296
339
|
const eventAt = stringValue(event.timestamp);
|
|
297
|
-
if (eventAt && !windows.some((window) => eventInsideWindow(eventAt, window, observedAt)))
|
|
340
|
+
if (eventAt && windows.length && !windows.some((window) => eventInsideWindow(eventAt, window, observedAt)))
|
|
298
341
|
continue;
|
|
299
|
-
|
|
300
|
-
if (type === "function_call" || type === "custom_tool_call") {
|
|
301
|
-
const name = stringValue(payload.name) ?? "unknown";
|
|
302
|
-
byTool[name] = (byTool[name] ?? 0) + 1;
|
|
303
|
-
total += 1;
|
|
304
|
-
}
|
|
305
|
-
if ((type === "function_call_output" || type === "custom_tool_call_output") && outputIndicatesFailure(payload))
|
|
306
|
-
failures += 1;
|
|
342
|
+
tools.add(codexToolObservation(event, expectedSessionId));
|
|
307
343
|
}
|
|
308
344
|
if (sessionId && sessionId !== expectedSessionId)
|
|
309
345
|
return null;
|
|
310
|
-
return
|
|
346
|
+
return tools.summary();
|
|
311
347
|
}
|
|
312
348
|
catch {
|
|
313
349
|
return null;
|
|
@@ -319,37 +355,6 @@ function eventInsideWindow(eventAt, window, observedAt) {
|
|
|
319
355
|
const end = Date.parse(window.completed_at ?? observedAt);
|
|
320
356
|
return Number.isFinite(at) && (!Number.isFinite(start) || at >= start) && (!Number.isFinite(end) || at <= end);
|
|
321
357
|
}
|
|
322
|
-
function outputIndicatesFailure(payload) {
|
|
323
|
-
if (payload.error === true || typeof payload.error === "string")
|
|
324
|
-
return true;
|
|
325
|
-
return containsFailure(payload.output);
|
|
326
|
-
}
|
|
327
|
-
function containsFailure(value) {
|
|
328
|
-
if (Array.isArray(value))
|
|
329
|
-
return value.some(containsFailure);
|
|
330
|
-
if (typeof value === "string") {
|
|
331
|
-
if (/(?:^|\n)exit=(?:[1-9]\d*)\b|Process exited with code [1-9]\d*/.test(value))
|
|
332
|
-
return true;
|
|
333
|
-
const trimmed = value.trim();
|
|
334
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
335
|
-
try {
|
|
336
|
-
return containsFailure(JSON.parse(trimmed));
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
return false;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
const item = object(value);
|
|
345
|
-
if (!item)
|
|
346
|
-
return false;
|
|
347
|
-
if (item.ok === false || item.is_error === true || item.isError === true || item.error === true || typeof item.error === "string")
|
|
348
|
-
return true;
|
|
349
|
-
if (typeof item.exit_code === "number" && item.exit_code !== 0)
|
|
350
|
-
return true;
|
|
351
|
-
return Object.values(item).some(containsFailure);
|
|
352
|
-
}
|
|
353
358
|
/** RUN accounting belongs to the physical provider Session, not to every Work link. */
|
|
354
359
|
function usageRowsForPhysicalSessions(context, links, observedAt, stage) {
|
|
355
360
|
const sessions = new Map();
|
|
@@ -357,15 +362,15 @@ function usageRowsForPhysicalSessions(context, links, observedAt, stage) {
|
|
|
357
362
|
sessions.set(link.session_id, [...(sessions.get(link.session_id) ?? []), link]);
|
|
358
363
|
const physicalSessions = [...sessions.values()].filter((sessionLinks) => {
|
|
359
364
|
const link = sessionLinks[0];
|
|
360
|
-
if (!link.
|
|
365
|
+
if (!link.provider_parent_session_id)
|
|
361
366
|
return true;
|
|
362
|
-
const parent = links.find((candidate) => candidate.session_id === link.
|
|
367
|
+
const parent = links.find((candidate) => candidate.session_id === link.provider_parent_session_id);
|
|
363
368
|
return !parent || !hasInclusiveUsageSnapshot(context, parent.project_id, parent.harness, parent.provider_session_id, observedAt);
|
|
364
369
|
});
|
|
365
370
|
return physicalSessions.map((sessionLinks) => {
|
|
366
371
|
const link = sessionLinks[0];
|
|
367
372
|
const startedAt = stage?.startedAt ?? sessionLinks.map((item) => item.created_at).sort()[0];
|
|
368
|
-
const completedTurn = stage ? null : completedProviderTurn(link.transcript_path, link
|
|
373
|
+
const completedTurn = stage ? null : completedProviderTurn(link.transcript_path, nativeSessionId(link));
|
|
369
374
|
const naturalEnd = isExternalHarness(link.harness)
|
|
370
375
|
? observedAt
|
|
371
376
|
: completedTurn?.completed_at ?? sessionLinks.map((item) => item.completed_at ?? observedAt).sort().at(-1);
|
|
@@ -373,7 +378,7 @@ function usageRowsForPhysicalSessions(context, links, observedAt, stage) {
|
|
|
373
378
|
const harnessUsage = isExternalHarness(link.harness)
|
|
374
379
|
? readHarnessUsageWindow(context, link.harness, link.project_id, link.provider_session_id, startedAt, endedAt, observedAt)
|
|
375
380
|
: null;
|
|
376
|
-
const parsed = harnessUsage ?? readCodexTranscriptWindow(link.transcript_path, link
|
|
381
|
+
const parsed = harnessUsage ?? readCodexTranscriptWindow(link.transcript_path, nativeSessionId(link), startedAt, endedAt);
|
|
377
382
|
return {
|
|
378
383
|
work_session_id: link.id,
|
|
379
384
|
work_id: link.work_id,
|
|
@@ -398,7 +403,7 @@ function hasInclusiveUsageSnapshot(context, projectId, harness, providerSessionI
|
|
|
398
403
|
function readHarnessUsageWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
|
|
399
404
|
if (!providerSessionId)
|
|
400
405
|
return { status: "not_observable", diagnostic: "provider_session_id_missing" };
|
|
401
|
-
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
|
|
406
|
+
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
|
|
402
407
|
FROM harness_usage_snapshots
|
|
403
408
|
WHERE project_id = ? AND harness = ? AND provider_session_id = ? AND observed_at <= ?
|
|
404
409
|
ORDER BY observed_at, id`, [projectId, harness, providerSessionId, observedAt]);
|
|
@@ -407,6 +412,8 @@ function readHarnessUsageWindow(context, harness, projectId, providerSessionId,
|
|
|
407
412
|
const before = snapshots.filter((snapshot) => Date.parse(snapshot.observed_at) <= Date.parse(startedAt)).at(-1);
|
|
408
413
|
if (!last || !before || last === before)
|
|
409
414
|
return { status: "not_yet_emitted", diagnostic: `${harness}_usage_window_incomplete` };
|
|
415
|
+
if (harness === "droid-cli" && (last.completeness !== "complete" || before.completeness !== "complete"))
|
|
416
|
+
return { status: "not_observable", diagnostic: "droid-cli_usage_window_partial" };
|
|
410
417
|
const delta = (value, baseline) => value === null || baseline === null ? null : Math.max(0, value - baseline);
|
|
411
418
|
const input = delta(last.input_tokens, before.input_tokens);
|
|
412
419
|
const cacheRead = delta(last.cache_read_input_tokens, before.cache_read_input_tokens);
|
|
@@ -437,15 +444,16 @@ function settledRunSessions(context, links, observedAt) {
|
|
|
437
444
|
return [];
|
|
438
445
|
const link = sessionLinks[0];
|
|
439
446
|
const sessionStartedAt = sessionLinks.map((item) => item.created_at).sort()[0];
|
|
447
|
+
const workCompletedAt = sessionLinks.map((item) => item.completed_at).sort().at(-1);
|
|
440
448
|
if (isExternalHarness(link.harness) && link.provider_session_id) {
|
|
441
449
|
const snapshot = context.db.get(`SELECT observed_at FROM harness_usage_snapshots
|
|
442
450
|
WHERE project_id = ? AND harness = ? AND provider_session_id = ?
|
|
443
451
|
AND completeness = 'complete' AND observed_at >= ? AND observed_at <= ?
|
|
444
|
-
ORDER BY observed_at DESC, id DESC LIMIT 1`, [link.project_id, link.harness, link.provider_session_id,
|
|
452
|
+
ORDER BY observed_at DESC, id DESC LIMIT 1`, [link.project_id, link.harness, link.provider_session_id, workCompletedAt, observedAt]);
|
|
445
453
|
return snapshot ? [{ session_id: sessionId, completed_at: snapshot.observed_at }] : [];
|
|
446
454
|
}
|
|
447
|
-
const completed = completedProviderTurn(sessionLinks[0].transcript_path,
|
|
448
|
-
if (!completed || Date.parse(completed.completed_at) < Date.parse(sessionStartedAt))
|
|
455
|
+
const completed = completedProviderTurn(sessionLinks[0].transcript_path, nativeSessionId(sessionLinks[0]));
|
|
456
|
+
if (!completed || Date.parse(completed.completed_at) < Date.parse(workCompletedAt) || Date.parse(completed.completed_at) < Date.parse(sessionStartedAt))
|
|
449
457
|
return [];
|
|
450
458
|
return [{ session_id: sessionId, completed_at: completed.completed_at }];
|
|
451
459
|
});
|
|
@@ -484,10 +492,10 @@ function completedProviderTurn(transcriptPath, expectedSessionId) {
|
|
|
484
492
|
}
|
|
485
493
|
}
|
|
486
494
|
function usageReport(context, input, refresh) {
|
|
487
|
-
const sessions = context.db.all(`SELECT DISTINCT session_id, project_id, ? AS run_id, transcript_path, parent_session_id, role, aspect_id, plan_item_id, session_kind
|
|
495
|
+
const sessions = context.db.all(`SELECT DISTINCT session_id, project_id, ? AS run_id, transcript_path, harness, provider_session_id, parent_session_id, role, aspect_id, plan_item_id, session_kind
|
|
488
496
|
FROM sessions WHERE project_id = ? AND run_id = ?
|
|
489
497
|
UNION
|
|
490
|
-
SELECT DISTINCT f.session_id, f.project_id, ? AS run_id, f.transcript_path, f.parent_session_id, f.role, f.aspect_id, f.plan_item_id, f.session_kind
|
|
498
|
+
SELECT DISTINCT f.session_id, f.project_id, ? AS run_id, f.transcript_path, f.harness, f.provider_session_id, f.parent_session_id, f.role, f.aspect_id, f.plan_item_id, f.session_kind
|
|
491
499
|
FROM flow_session_segments s JOIN sessions f ON f.project_id = s.project_id AND f.session_id = s.session_id
|
|
492
500
|
WHERE s.project_id = ? AND s.run_id = ?`, [input.runId, input.projectId, input.runId, input.runId, input.projectId, input.runId]);
|
|
493
501
|
const selectedSessions = input.sessionId ? sessions.filter((session) => session.session_id === input.sessionId) : sessions;
|
|
@@ -500,13 +508,15 @@ function usageReport(context, input, refresh) {
|
|
|
500
508
|
LEFT JOIN sessions f ON f.project_id = s.project_id AND f.session_id = s.session_id
|
|
501
509
|
WHERE s.project_id = ? AND s.run_id = ? ORDER BY s.session_id, s.observed_at, s.id`, [input.projectId, input.runId]);
|
|
502
510
|
const selectedRows = input.sessionId ? rows.filter((row) => row.session_id === input.sessionId) : rows;
|
|
503
|
-
const
|
|
511
|
+
const publicSessions = new Map(sessions.map((session) => [session.session_id, publicSessionIdentity({ harness: session.harness ?? "codex-desktop", provider_session_id: session.provider_session_id ?? null, session_id: session.session_id })]));
|
|
512
|
+
const rawDeltas = usageDeltas(selectedRows);
|
|
513
|
+
const deltas = rawDeltas.map(({ session_id, ...delta }) => ({ ...delta, session: publicSessions.get(session_id) ?? null }));
|
|
504
514
|
const segments = context.db.all(`SELECT id, session_id, run_id, protocol_id, started_at, ended_at, cwd, tool_name
|
|
505
515
|
FROM flow_session_segments WHERE project_id = ? AND run_id = ? ORDER BY started_at, id`, [input.projectId, input.runId]);
|
|
506
516
|
const supported = new Set(["session", "role", "aspect", "plan-item", "stage", "protocol"]);
|
|
507
517
|
const groupBy = supported.has(input.groupBy) ? input.groupBy : "session";
|
|
508
518
|
const groups = new Map();
|
|
509
|
-
for (const delta of
|
|
519
|
+
for (const delta of rawDeltas) {
|
|
510
520
|
const key = groupKey(delta, groupBy);
|
|
511
521
|
const group = groups.get(key) ?? { tokens: emptyUsage(), snapshots: 0, statuses: {}, transition_buckets: 0 };
|
|
512
522
|
group.snapshots += 1;
|
|
@@ -523,7 +533,9 @@ function usageReport(context, input, refresh) {
|
|
|
523
533
|
run_id: input.runId,
|
|
524
534
|
source: { kind: "codex_transcript_v1", parser_version: 1, refreshed_sessions: refreshed.length, checkpoint_source: refresh ? "refreshed" : "stored" },
|
|
525
535
|
sessions: selectedSessions.map((session) => ({
|
|
526
|
-
|
|
536
|
+
session: publicSessionIdentity({ harness: session.harness ?? "codex-desktop", provider_session_id: session.provider_session_id ?? null, session_id: session.session_id }),
|
|
537
|
+
...(session.parent_session_id ? { parent_session: publicSessions.get(session.parent_session_id) ?? null } : {}),
|
|
538
|
+
role: session.role ?? null,
|
|
527
539
|
aspect_id: session.aspect_id ?? null, plan_item_id: session.plan_item_id ?? null, session_kind: session.session_kind ?? null
|
|
528
540
|
})),
|
|
529
541
|
groups: [...groups.entries()].map(([key, group]) => ({ key, ...group, tokens: outputUsageTotals(group.tokens) })),
|
|
@@ -531,7 +543,7 @@ function usageReport(context, input, refresh) {
|
|
|
531
543
|
tool_calls: toolCallsForSessions(sessions),
|
|
532
544
|
segments: segments.map((segment) => ({
|
|
533
545
|
segment_id: segment.id,
|
|
534
|
-
|
|
546
|
+
session: publicSessions.get(segment.session_id) ?? null,
|
|
535
547
|
run_id: segment.run_id,
|
|
536
548
|
protocol_id: segment.protocol_id,
|
|
537
549
|
started_at: segment.started_at,
|
|
@@ -543,67 +555,31 @@ function usageReport(context, input, refresh) {
|
|
|
543
555
|
}
|
|
544
556
|
function toolCallsForSessions(sessions) {
|
|
545
557
|
const result = { status: "unavailable", total: 0, failures: 0, by_tool: {}, observed_sessions: 0, unavailable_sessions: [] };
|
|
558
|
+
let incomplete = false;
|
|
546
559
|
for (const session of sessions) {
|
|
547
|
-
const parsed = readToolCalls(session.transcript_path, session
|
|
560
|
+
const parsed = readToolCalls(session.transcript_path, nativeSessionId(session));
|
|
548
561
|
if (!parsed) {
|
|
549
562
|
result.unavailable_sessions.push(session.session_id);
|
|
550
563
|
continue;
|
|
551
564
|
}
|
|
565
|
+
if (parsed.completeness !== "complete")
|
|
566
|
+
incomplete = true;
|
|
552
567
|
result.observed_sessions += 1;
|
|
553
568
|
result.total += parsed.total;
|
|
554
569
|
result.failures += parsed.failures;
|
|
555
570
|
for (const [name, count] of Object.entries(parsed.by_tool))
|
|
556
571
|
result.by_tool[name] = (result.by_tool[name] ?? 0) + count;
|
|
557
572
|
}
|
|
558
|
-
result.status = result.observed_sessions === 0 ? "unavailable" : result.unavailable_sessions.length
|
|
573
|
+
result.status = result.observed_sessions === 0 ? "unavailable" : result.unavailable_sessions.length || incomplete ? "partial" : "measured";
|
|
559
574
|
return result;
|
|
560
575
|
}
|
|
561
576
|
function readToolCalls(transcriptPath, expectedSessionId) {
|
|
562
|
-
|
|
563
|
-
return null;
|
|
564
|
-
try {
|
|
565
|
-
let sessionId;
|
|
566
|
-
const byTool = {};
|
|
567
|
-
let total = 0;
|
|
568
|
-
let failures = 0;
|
|
569
|
-
for (const line of fs.readFileSync(transcriptPath, "utf8").split(/\r?\n/)) {
|
|
570
|
-
if (!line.trim())
|
|
571
|
-
continue;
|
|
572
|
-
let event;
|
|
573
|
-
try {
|
|
574
|
-
event = JSON.parse(line);
|
|
575
|
-
}
|
|
576
|
-
catch {
|
|
577
|
-
continue;
|
|
578
|
-
}
|
|
579
|
-
const payload = object(event.payload);
|
|
580
|
-
if (!payload)
|
|
581
|
-
continue;
|
|
582
|
-
if (event.type === "session_meta")
|
|
583
|
-
sessionId = stringValue(payload.id) ?? sessionId;
|
|
584
|
-
if (event.type !== "response_item")
|
|
585
|
-
continue;
|
|
586
|
-
const type = stringValue(payload.type);
|
|
587
|
-
if (type === "function_call" || type === "custom_tool_call") {
|
|
588
|
-
const name = stringValue(payload.name) ?? "unknown";
|
|
589
|
-
byTool[name] = (byTool[name] ?? 0) + 1;
|
|
590
|
-
total += 1;
|
|
591
|
-
}
|
|
592
|
-
if ((type === "function_call_output" || type === "custom_tool_call_output") && outputIndicatesFailure(payload))
|
|
593
|
-
failures += 1;
|
|
594
|
-
}
|
|
595
|
-
if (sessionId && sessionId !== expectedSessionId)
|
|
596
|
-
return null;
|
|
597
|
-
return { total, failures, by_tool: Object.fromEntries(Object.entries(byTool).sort(([left], [right]) => left.localeCompare(right))) };
|
|
598
|
-
}
|
|
599
|
-
catch {
|
|
600
|
-
return null;
|
|
601
|
-
}
|
|
577
|
+
return readToolCallsForWindows(transcriptPath, expectedSessionId, [], new Date().toISOString());
|
|
602
578
|
}
|
|
603
579
|
function segmentUsage(context, session, segment) {
|
|
604
580
|
if (!session)
|
|
605
581
|
return { status: "unavailable", diagnostic: "session_record_missing" };
|
|
606
|
-
const result = readCodexTranscriptWindow(session.transcript_path, session
|
|
582
|
+
const result = readCodexTranscriptWindow(session.transcript_path, nativeSessionId(session), segment.started_at, segment.ended_at ?? context.now());
|
|
607
583
|
return result.counter ? { status: result.status, tokens: result.counter.usage, token_event_at: result.counter.token_event_at } : { status: result.status, diagnostic: result.diagnostic ?? null };
|
|
608
584
|
}
|
|
609
585
|
function readCodexTranscript(transcriptPath, expectedSessionId) {
|