@deksden-com/dd-flow-cli 0.9.0-beta.7 → 0.9.0-beta.74
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 +396 -0
- package/README.md +65 -0
- package/dist/build-info.json +10 -10
- package/dist/cli/help.js +81 -9
- package/dist/cli/run-cli.js +442 -46
- package/dist/harness-runtime/bin/dd-agy.mjs +37 -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 +423 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +141 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +438 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +470 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -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 +910 -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 +260 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -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/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-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/cleanup.js +8 -5
- package/dist/services/cli-operation-classifier.js +11 -1
- package/dist/services/code-checks.js +543 -96
- package/dist/services/controller-fanout.js +120 -0
- package/dist/services/dashboard.js +9 -0
- package/dist/services/engines.js +50 -19
- package/dist/services/eval-snapshots.js +639 -60
- package/dist/services/execution-policy.js +177 -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 +413 -168
- package/dist/services/lanes.js +1 -1
- package/dist/services/lifecycle-command.js +74 -6
- package/dist/services/lifecycle-invocations.js +637 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +138 -22
- package/dist/services/merge-queue.js +6 -6
- package/dist/services/merge-server.js +75 -29
- package/dist/services/migrations.js +1 -1
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/projects.js +7 -3
- package/dist/services/prompts.js +4 -2
- package/dist/services/protocols.js +11 -2
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -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 +211 -0
- package/dist/services/run-controller-capture.js +134 -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 +31 -0
- package/dist/services/run-controller.js +733 -0
- package/dist/services/run-engine-bindings.js +20 -62
- package/dist/services/run-fork.js +124 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +327 -0
- package/dist/services/runs.js +280 -40
- package/dist/services/runtime-budget.js +362 -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 +543 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +210 -0
- package/dist/services/runtime-service.js +96 -0
- package/dist/services/schema-validation.js +12 -12
- package/dist/services/stage-context.js +2 -2
- package/dist/services/stage-lifecycle.js +32 -10
- package/dist/services/stage-pause.js +25 -13
- package/dist/services/usage.js +37 -93
- package/dist/services/vnext-code-review.js +149 -89
- package/dist/services/vnext-code.js +223 -215
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +135 -21
- package/dist/services/vnext-merge.js +258 -89
- package/dist/services/vnext-plan-review.js +75 -64
- package/dist/services/vnext-plan.js +77 -29
- package/dist/services/vnext-protocolize.js +21 -14
- package/dist/services/vnext-specify.js +26 -16
- package/dist/services/work-registry.js +592 -154
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +341 -31
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +20 -13
- package/tools/repair-paused-run-status.mjs +59 -0
|
@@ -2,6 +2,8 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { quote } from "shell-quote";
|
|
6
|
+
import { lifecycleRetryCommands, managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
5
7
|
import { AppError } from "../shared/errors.js";
|
|
6
8
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
7
9
|
import { findRecentMatchingHookEvent, claimStageResumeHookEvent, stageResumeMatchKey } from "./hooks.js";
|
|
@@ -25,7 +27,7 @@ export function pauseStageForUser(context, input) {
|
|
|
25
27
|
fs.mkdirSync(pauseRoot, { recursive: true });
|
|
26
28
|
fs.writeFileSync(questionPath, question);
|
|
27
29
|
const now = context.now();
|
|
28
|
-
context.db.
|
|
30
|
+
context.db.beginWriteTransaction();
|
|
29
31
|
try {
|
|
30
32
|
const paused = context.db.run("UPDATE works SET status = 'paused', updated_at = ? WHERE work_id = ? AND status = 'running'", [now, work.work_id]);
|
|
31
33
|
if (paused.changes !== 1)
|
|
@@ -71,16 +73,22 @@ export function resumeStageAfterUser(context, input) {
|
|
|
71
73
|
errorCode: "trusted_stage_resume_required",
|
|
72
74
|
operation: "stage resume"
|
|
73
75
|
}).eventKey;
|
|
74
|
-
const answerPath = path.join(path.dirname(stage.pause.question_path), "answer.md");
|
|
75
|
-
fs.writeFileSync(answerPath, input.answer);
|
|
76
76
|
const now = context.now();
|
|
77
77
|
const workSession = context.db.get("SELECT id, session_id, prompt_path, result_path, status FROM work_sessions WHERE work_id = ? AND status = 'running' ORDER BY created_at DESC LIMIT 1", [work.work_id]);
|
|
78
78
|
if (!workSession)
|
|
79
79
|
throw new AppError("runtime_missing", "Paused Work has no open Session link", 1, { work_id: work.work_id });
|
|
80
|
-
|
|
80
|
+
const answerPath = path.join(path.dirname(stage.pause.question_path), `answer-${crypto.createHash("sha256").update(`${stage.pause.id}\0${input.answer}`).digest("hex")}.md`);
|
|
81
|
+
context.db.beginWriteTransaction();
|
|
81
82
|
let identity;
|
|
82
83
|
try {
|
|
83
84
|
identity = claimStageResumeHookEvent(context, { projectId: project.id, runId: run.id, stage: stage.stage, workId: work.work_id, projectRoot, eventKey: hookEventId });
|
|
85
|
+
try {
|
|
86
|
+
fs.writeFileSync(answerPath, input.answer, { flag: "wx", mode: 0o600 });
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== "EEXIST" || fs.readFileSync(answerPath, "utf8") !== input.answer)
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
84
92
|
if (workSession.session_id !== identity.sessionId) {
|
|
85
93
|
bindSessionForResume(context, work.work_id, identity, now);
|
|
86
94
|
context.db.run("UPDATE work_sessions SET status = 'completed', updated_at = ?, completed_at = ? WHERE id = ?", [now, now, workSession.id]);
|
|
@@ -99,6 +107,7 @@ export function resumeStageAfterUser(context, input) {
|
|
|
99
107
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
100
108
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "stage_resume_session_bound", stage: stage.stage, work_id: work.work_id, session_id: identity.sessionId });
|
|
101
109
|
const prompt = fs.readFileSync(workSession.prompt_path, "utf8");
|
|
110
|
+
const retryCommands = lifecycleRetryCommands(context, stage.stage);
|
|
102
111
|
const continuation = [
|
|
103
112
|
prompt.trimEnd(),
|
|
104
113
|
"",
|
|
@@ -107,6 +116,7 @@ export function resumeStageAfterUser(context, input) {
|
|
|
107
116
|
`- Question source: ${stage.pause.question_path}`,
|
|
108
117
|
`- Answer source: ${answerPath}`,
|
|
109
118
|
"- Continue the same stage, Work and attempt. Do not call stage start or repeat completed preparation.",
|
|
119
|
+
...(retryCommands.length ? ["- A prior no-effect lifecycle rejection now has these authoritative replacement commands; use only these replacements, never invent an invocation id:", ...retryCommands.map(command => ` ${command}`)] : []),
|
|
110
120
|
"",
|
|
111
121
|
"<user_question>",
|
|
112
122
|
fs.readFileSync(stage.pause.question_path, "utf8"),
|
|
@@ -121,7 +131,7 @@ export function resumeStageAfterUser(context, input) {
|
|
|
121
131
|
return { ok: true, outcome: "resumed", run_id: run.id, work_id: work.work_id, stage: stage.stage, pause_id: stage.pause.id, question_path: stage.pause.question_path, answer_path: answerPath, prompt_path: workSession.prompt_path, worker_prompt_markdown: continuation, next_action: `continue_${stage.stage}` };
|
|
122
132
|
}
|
|
123
133
|
export function stagePauseCommand(context, input) {
|
|
124
|
-
return `${flowCommand(context)} stage pause ${input.runId} --stage ${input.stage} --work ${input.workId} --question-stdin --project-root ${JSON.stringify(input.projectRoot)} --json
|
|
134
|
+
return managedLifecycleCommand(context, `${flowCommand(context)} stage pause ${input.runId} --stage ${input.stage} --work ${input.workId} --question-stdin --project-root ${JSON.stringify(input.projectRoot)} --json`);
|
|
125
135
|
}
|
|
126
136
|
/**
|
|
127
137
|
* The only shell form allowed for an agent-owned HITL pause. Supplying a
|
|
@@ -147,9 +157,11 @@ Effect on scope or acceptance:
|
|
|
147
157
|
USER_QUESTION`;
|
|
148
158
|
}
|
|
149
159
|
function stageResumeCommand(context, input) {
|
|
150
|
-
return `${flowCommand(context)} stage resume ${input.runId} --stage ${input.stage} --work ${input.workId} --answer-stdin --project-root ${JSON.stringify(input.projectRoot)} --json
|
|
160
|
+
return managedLifecycleCommand(context, `${flowCommand(context)} stage resume ${input.runId} --stage ${input.stage} --work ${input.workId} --answer-stdin --project-root ${JSON.stringify(input.projectRoot)} --json`);
|
|
151
161
|
}
|
|
152
162
|
export function flowCommand(context) {
|
|
163
|
+
const scope = context.env.DD_FLOW_INVOCATION_SCOPE;
|
|
164
|
+
const prefix = scope ? `DD_FLOW_INVOCATION_SCOPE=${quote([scope])} ` : "";
|
|
153
165
|
const defaultHome = path.resolve(path.join(os.homedir(), ".dd-flow"));
|
|
154
166
|
// Desktop/ACP tool processes are not required to inherit the environment of
|
|
155
167
|
// the adapter that created their Session. In an isolated runtime, a bare
|
|
@@ -158,16 +170,16 @@ export function flowCommand(context) {
|
|
|
158
170
|
// make every generated lifecycle command self-contained.
|
|
159
171
|
const executable = context.env.DD_FLOW_BIN;
|
|
160
172
|
if (executable && path.isAbsolute(executable)) {
|
|
161
|
-
return
|
|
173
|
+
return `${prefix}DD_FLOW_HOME=${JSON.stringify(context.ddFlowHome)} ${JSON.stringify(executable)}`;
|
|
162
174
|
}
|
|
163
175
|
const isolatedExecutable = path.join(context.ddFlowHome, "bin", "dd-flow");
|
|
164
176
|
if (context.ddFlowHome !== defaultHome && fs.existsSync(isolatedExecutable)) {
|
|
165
|
-
return
|
|
177
|
+
return `${prefix}DD_FLOW_HOME=${JSON.stringify(context.ddFlowHome)} ${JSON.stringify(isolatedExecutable)}`;
|
|
166
178
|
}
|
|
167
|
-
return context.ddFlowHome === defaultHome ?
|
|
179
|
+
return context.ddFlowHome === defaultHome ? `${prefix}dd-flow` : `${prefix}DD_FLOW_HOME=${JSON.stringify(context.ddFlowHome)} dd-flow`;
|
|
168
180
|
}
|
|
169
181
|
function requireRun(context, projectId, id) {
|
|
170
|
-
const rows = context.db.all("SELECT id, short_id, project_id, project_root,
|
|
182
|
+
const rows = context.db.all("SELECT id, short_id, project_id, project_root, run_root, index_json FROM runs WHERE project_id = ? AND (id = ? OR short_id = ?)", [projectId, id, id]);
|
|
171
183
|
if (rows.length !== 1)
|
|
172
184
|
throw new AppError(rows.length ? "ambiguous_alias" : "not_found", rows.length ? "RUN alias is ambiguous" : "RUN is not registered", 1, { run_id: id });
|
|
173
185
|
return rows[0];
|
|
@@ -186,9 +198,9 @@ function requireStage(run, stage, status) {
|
|
|
186
198
|
return found;
|
|
187
199
|
}
|
|
188
200
|
function requireRunHome(run) {
|
|
189
|
-
if (!run.
|
|
190
|
-
throw new AppError("runtime_missing", "RUN has no artifact
|
|
191
|
-
return run.
|
|
201
|
+
if (!run.run_root)
|
|
202
|
+
throw new AppError("runtime_missing", "RUN has no artifact root", 1, { run_id: run.id });
|
|
203
|
+
return run.run_root;
|
|
192
204
|
}
|
|
193
205
|
function nextPauseId(runHome) {
|
|
194
206
|
const root = path.join(runHome, "intake", "hitl");
|
package/dist/services/usage.js
CHANGED
|
@@ -4,6 +4,8 @@ import { parseJsonObject } from "../shared/json.js";
|
|
|
4
4
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
5
5
|
import { registerProject, requireProjectByRoot } from "./projects.js";
|
|
6
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";
|
|
7
9
|
export function ingestZcodeUsage(context, input) {
|
|
8
10
|
return ingestHarnessUsage(context, input, { harness: "zcode-acp", label: "ZCode", idPrefix: "ZUSG", sourceKind: "zcode_session_usage_v1" });
|
|
9
11
|
}
|
|
@@ -19,6 +21,14 @@ export function ingestOpenCodeUsage(context, input) {
|
|
|
19
21
|
export function ingestAgyUsage(context, input) {
|
|
20
22
|
return ingestHarnessUsage(context, input, { harness: "antigravity-cli", label: "Antigravity", idPrefix: "AUSG", sourceKind: "antigravity_cli_session_usage_v1" });
|
|
21
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
|
+
}
|
|
22
32
|
function ingestHarnessUsage(context, input, harness) {
|
|
23
33
|
const payload = parseJsonObject(input.stdin || "{}", `${harness.label} usage snapshot`);
|
|
24
34
|
const usage = object(payload.usage);
|
|
@@ -157,7 +167,7 @@ export function recalculateRunUsage(context, input) {
|
|
|
157
167
|
const usageFinal = final && unsettledSessions.length === 0;
|
|
158
168
|
const persistRunProjection = !input.sessionId && !input.stage;
|
|
159
169
|
if (persistRunProjection) {
|
|
160
|
-
context.db.exec("
|
|
170
|
+
context.db.exec("SAVEPOINT run_usage");
|
|
161
171
|
try {
|
|
162
172
|
context.db.run("DELETE FROM usage WHERE project_id = ? AND run_id = ?", [input.projectId, input.runId]);
|
|
163
173
|
for (const row of rows) {
|
|
@@ -177,13 +187,16 @@ export function recalculateRunUsage(context, input) {
|
|
|
177
187
|
AND status = 'idle'
|
|
178
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]);
|
|
179
189
|
}
|
|
180
|
-
context.db.exec("
|
|
190
|
+
context.db.exec("RELEASE run_usage");
|
|
181
191
|
}
|
|
182
192
|
catch (error) {
|
|
183
|
-
context.db.exec("ROLLBACK");
|
|
193
|
+
context.db.exec("ROLLBACK TO run_usage");
|
|
194
|
+
context.db.exec("RELEASE run_usage");
|
|
184
195
|
throw error;
|
|
185
196
|
}
|
|
186
197
|
}
|
|
198
|
+
const observations = persistRunProjection ? runObservations(context, input.projectId, input.runId) : null;
|
|
199
|
+
const legacyTools = toolCallsForLinks(context, links, accountingAt);
|
|
187
200
|
return {
|
|
188
201
|
ok: true,
|
|
189
202
|
schema_id: "dd-flow/run-usage@2",
|
|
@@ -198,7 +211,8 @@ export function recalculateRunUsage(context, input) {
|
|
|
198
211
|
stopped: publicSessionIdentities(context, input.projectId, settledSessions.map((session) => session.session_id)),
|
|
199
212
|
unsettled: publicSessionIdentities(context, input.projectId, unsettledSessions)
|
|
200
213
|
},
|
|
201
|
-
tool_calls:
|
|
214
|
+
tool_calls: observations?.tools.observed_sessions ? observations.tools : legacyTools,
|
|
215
|
+
...(observations ? { observations, legacy_tool_calls: legacyTools } : {}),
|
|
202
216
|
coverage: rows.reduce((coverage, row) => ({ ...coverage, [row.status]: (coverage[row.status] ?? 0) + 1 }), {})
|
|
203
217
|
};
|
|
204
218
|
}
|
|
@@ -241,6 +255,7 @@ function publicSessionIdentities(context, projectId, storageIds) {
|
|
|
241
255
|
}
|
|
242
256
|
function toolCallsForLinks(context, links, observedAt) {
|
|
243
257
|
const result = { status: "unavailable", total: 0, failures: 0, by_tool: {}, observed_sessions: 0, unavailable_sessions: [] };
|
|
258
|
+
let incomplete = false;
|
|
244
259
|
const sessions = new Map();
|
|
245
260
|
for (const link of links)
|
|
246
261
|
sessions.set(link.session_id, [...(sessions.get(link.session_id) ?? []), link]);
|
|
@@ -253,18 +268,20 @@ function toolCallsForLinks(context, links, observedAt) {
|
|
|
253
268
|
result.unavailable_sessions.push(sessionId);
|
|
254
269
|
continue;
|
|
255
270
|
}
|
|
271
|
+
if ("completeness" in parsed && parsed.completeness !== "complete")
|
|
272
|
+
incomplete = true;
|
|
256
273
|
result.observed_sessions += 1;
|
|
257
274
|
result.total += parsed.total;
|
|
258
275
|
result.failures += parsed.failures;
|
|
259
276
|
for (const [name, count] of Object.entries(parsed.by_tool))
|
|
260
277
|
result.by_tool[name] = (result.by_tool[name] ?? 0) + count;
|
|
261
278
|
}
|
|
262
|
-
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";
|
|
263
280
|
return result;
|
|
264
281
|
}
|
|
265
|
-
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"; }
|
|
266
283
|
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";
|
|
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";
|
|
268
285
|
}
|
|
269
286
|
function readHarnessToolCallsWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
|
|
270
287
|
if (!providerSessionId)
|
|
@@ -302,9 +319,7 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
302
319
|
return null;
|
|
303
320
|
try {
|
|
304
321
|
let sessionId;
|
|
305
|
-
|
|
306
|
-
let failures = 0;
|
|
307
|
-
const byTool = {};
|
|
322
|
+
const tools = new ToolObservations();
|
|
308
323
|
for (const line of fs.readFileSync(transcriptPath, "utf8").split(/\r?\n/)) {
|
|
309
324
|
if (!line.trim())
|
|
310
325
|
continue;
|
|
@@ -313,6 +328,7 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
313
328
|
event = JSON.parse(line);
|
|
314
329
|
}
|
|
315
330
|
catch {
|
|
331
|
+
tools.gaps.add("journal_invalid_record");
|
|
316
332
|
continue;
|
|
317
333
|
}
|
|
318
334
|
const payload = object(event.payload);
|
|
@@ -321,20 +337,13 @@ function readToolCallsForWindows(transcriptPath, expectedSessionId, windows, obs
|
|
|
321
337
|
if (event.type !== "response_item" || !payload)
|
|
322
338
|
continue;
|
|
323
339
|
const eventAt = stringValue(event.timestamp);
|
|
324
|
-
if (eventAt && !windows.some((window) => eventInsideWindow(eventAt, window, observedAt)))
|
|
340
|
+
if (eventAt && windows.length && !windows.some((window) => eventInsideWindow(eventAt, window, observedAt)))
|
|
325
341
|
continue;
|
|
326
|
-
|
|
327
|
-
if (type === "function_call" || type === "custom_tool_call") {
|
|
328
|
-
const name = stringValue(payload.name) ?? "unknown";
|
|
329
|
-
byTool[name] = (byTool[name] ?? 0) + 1;
|
|
330
|
-
total += 1;
|
|
331
|
-
}
|
|
332
|
-
if ((type === "function_call_output" || type === "custom_tool_call_output") && outputIndicatesFailure(payload))
|
|
333
|
-
failures += 1;
|
|
342
|
+
tools.add(codexToolObservation(event, expectedSessionId));
|
|
334
343
|
}
|
|
335
344
|
if (sessionId && sessionId !== expectedSessionId)
|
|
336
345
|
return null;
|
|
337
|
-
return
|
|
346
|
+
return tools.summary();
|
|
338
347
|
}
|
|
339
348
|
catch {
|
|
340
349
|
return null;
|
|
@@ -346,37 +355,6 @@ function eventInsideWindow(eventAt, window, observedAt) {
|
|
|
346
355
|
const end = Date.parse(window.completed_at ?? observedAt);
|
|
347
356
|
return Number.isFinite(at) && (!Number.isFinite(start) || at >= start) && (!Number.isFinite(end) || at <= end);
|
|
348
357
|
}
|
|
349
|
-
function outputIndicatesFailure(payload) {
|
|
350
|
-
if (payload.error === true || typeof payload.error === "string")
|
|
351
|
-
return true;
|
|
352
|
-
return containsFailure(payload.output);
|
|
353
|
-
}
|
|
354
|
-
function containsFailure(value) {
|
|
355
|
-
if (Array.isArray(value))
|
|
356
|
-
return value.some(containsFailure);
|
|
357
|
-
if (typeof value === "string") {
|
|
358
|
-
if (/(?:^|\n)exit=(?:[1-9]\d*)\b|Process exited with code [1-9]\d*/.test(value))
|
|
359
|
-
return true;
|
|
360
|
-
const trimmed = value.trim();
|
|
361
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
362
|
-
try {
|
|
363
|
-
return containsFailure(JSON.parse(trimmed));
|
|
364
|
-
}
|
|
365
|
-
catch {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
const item = object(value);
|
|
372
|
-
if (!item)
|
|
373
|
-
return false;
|
|
374
|
-
if (item.ok === false || item.is_error === true || item.isError === true || item.error === true || typeof item.error === "string")
|
|
375
|
-
return true;
|
|
376
|
-
if (typeof item.exit_code === "number" && item.exit_code !== 0)
|
|
377
|
-
return true;
|
|
378
|
-
return Object.values(item).some(containsFailure);
|
|
379
|
-
}
|
|
380
358
|
/** RUN accounting belongs to the physical provider Session, not to every Work link. */
|
|
381
359
|
function usageRowsForPhysicalSessions(context, links, observedAt, stage) {
|
|
382
360
|
const sessions = new Map();
|
|
@@ -425,7 +403,7 @@ function hasInclusiveUsageSnapshot(context, projectId, harness, providerSessionI
|
|
|
425
403
|
function readHarnessUsageWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
|
|
426
404
|
if (!providerSessionId)
|
|
427
405
|
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
|
|
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
|
|
429
407
|
FROM harness_usage_snapshots
|
|
430
408
|
WHERE project_id = ? AND harness = ? AND provider_session_id = ? AND observed_at <= ?
|
|
431
409
|
ORDER BY observed_at, id`, [projectId, harness, providerSessionId, observedAt]);
|
|
@@ -434,6 +412,8 @@ function readHarnessUsageWindow(context, harness, projectId, providerSessionId,
|
|
|
434
412
|
const before = snapshots.filter((snapshot) => Date.parse(snapshot.observed_at) <= Date.parse(startedAt)).at(-1);
|
|
435
413
|
if (!last || !before || last === before)
|
|
436
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" };
|
|
437
417
|
const delta = (value, baseline) => value === null || baseline === null ? null : Math.max(0, value - baseline);
|
|
438
418
|
const input = delta(last.input_tokens, before.input_tokens);
|
|
439
419
|
const cacheRead = delta(last.cache_read_input_tokens, before.cache_read_input_tokens);
|
|
@@ -575,62 +555,26 @@ function usageReport(context, input, refresh) {
|
|
|
575
555
|
}
|
|
576
556
|
function toolCallsForSessions(sessions) {
|
|
577
557
|
const result = { status: "unavailable", total: 0, failures: 0, by_tool: {}, observed_sessions: 0, unavailable_sessions: [] };
|
|
558
|
+
let incomplete = false;
|
|
578
559
|
for (const session of sessions) {
|
|
579
560
|
const parsed = readToolCalls(session.transcript_path, nativeSessionId(session));
|
|
580
561
|
if (!parsed) {
|
|
581
562
|
result.unavailable_sessions.push(session.session_id);
|
|
582
563
|
continue;
|
|
583
564
|
}
|
|
565
|
+
if (parsed.completeness !== "complete")
|
|
566
|
+
incomplete = true;
|
|
584
567
|
result.observed_sessions += 1;
|
|
585
568
|
result.total += parsed.total;
|
|
586
569
|
result.failures += parsed.failures;
|
|
587
570
|
for (const [name, count] of Object.entries(parsed.by_tool))
|
|
588
571
|
result.by_tool[name] = (result.by_tool[name] ?? 0) + count;
|
|
589
572
|
}
|
|
590
|
-
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";
|
|
591
574
|
return result;
|
|
592
575
|
}
|
|
593
576
|
function readToolCalls(transcriptPath, expectedSessionId) {
|
|
594
|
-
|
|
595
|
-
return null;
|
|
596
|
-
try {
|
|
597
|
-
let sessionId;
|
|
598
|
-
const byTool = {};
|
|
599
|
-
let total = 0;
|
|
600
|
-
let failures = 0;
|
|
601
|
-
for (const line of fs.readFileSync(transcriptPath, "utf8").split(/\r?\n/)) {
|
|
602
|
-
if (!line.trim())
|
|
603
|
-
continue;
|
|
604
|
-
let event;
|
|
605
|
-
try {
|
|
606
|
-
event = JSON.parse(line);
|
|
607
|
-
}
|
|
608
|
-
catch {
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
const payload = object(event.payload);
|
|
612
|
-
if (!payload)
|
|
613
|
-
continue;
|
|
614
|
-
if (event.type === "session_meta")
|
|
615
|
-
sessionId = stringValue(payload.id) ?? sessionId;
|
|
616
|
-
if (event.type !== "response_item")
|
|
617
|
-
continue;
|
|
618
|
-
const type = stringValue(payload.type);
|
|
619
|
-
if (type === "function_call" || type === "custom_tool_call") {
|
|
620
|
-
const name = stringValue(payload.name) ?? "unknown";
|
|
621
|
-
byTool[name] = (byTool[name] ?? 0) + 1;
|
|
622
|
-
total += 1;
|
|
623
|
-
}
|
|
624
|
-
if ((type === "function_call_output" || type === "custom_tool_call_output") && outputIndicatesFailure(payload))
|
|
625
|
-
failures += 1;
|
|
626
|
-
}
|
|
627
|
-
if (sessionId && sessionId !== expectedSessionId)
|
|
628
|
-
return null;
|
|
629
|
-
return { total, failures, by_tool: Object.fromEntries(Object.entries(byTool).sort(([left], [right]) => left.localeCompare(right))) };
|
|
630
|
-
}
|
|
631
|
-
catch {
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
577
|
+
return readToolCallsForWindows(transcriptPath, expectedSessionId, [], new Date().toISOString());
|
|
634
578
|
}
|
|
635
579
|
function segmentUsage(context, session, segment) {
|
|
636
580
|
if (!session)
|