@rosthq/cli 0.7.95 → 0.7.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-manifest.d.ts","sourceRoot":"","sources":["../../src/generated/command-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,gBAAgB,EAAE,SAAS,oBAAoB,
|
|
1
|
+
{"version":3,"file":"command-manifest.d.ts","sourceRoot":"","sources":["../../src/generated/command-manifest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAE3E,eAAO,MAAM,gBAAgB,EAAE,SAAS,oBAAoB,EAsR3D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -41569,6 +41569,7 @@ var compassImportOutputSchema = external_exports.object({
|
|
|
41569
41569
|
citations: external_exports.number().int().nonnegative()
|
|
41570
41570
|
}).strict();
|
|
41571
41571
|
var goalStatusSchema = external_exports.enum(["on", "off", "done", "dropped"]);
|
|
41572
|
+
var goalStatusSourceSchema = external_exports.enum(["human", "agent", "rollup", "signal"]);
|
|
41572
41573
|
var goalListInputSchema = external_exports.object({
|
|
41573
41574
|
cycle_id: uuidSchema9.optional(),
|
|
41574
41575
|
seat_id: uuidSchema9.optional()
|
|
@@ -41584,7 +41585,7 @@ var goalSummarySchema = external_exports.object({
|
|
|
41584
41585
|
parent_goal_id: uuidSchema9.nullable(),
|
|
41585
41586
|
title: external_exports.string(),
|
|
41586
41587
|
status: goalStatusSchema,
|
|
41587
|
-
status_source:
|
|
41588
|
+
status_source: goalStatusSourceSchema
|
|
41588
41589
|
}).strict();
|
|
41589
41590
|
var goalListOutputSchema = external_exports.object({
|
|
41590
41591
|
goals: external_exports.array(goalSummarySchema)
|
|
@@ -41604,7 +41605,7 @@ var goalMutationOutputSchema = external_exports.object({
|
|
|
41604
41605
|
parent_goal_id: uuidSchema9.nullable(),
|
|
41605
41606
|
title: external_exports.string(),
|
|
41606
41607
|
status: goalStatusSchema,
|
|
41607
|
-
status_source:
|
|
41608
|
+
status_source: goalStatusSourceSchema
|
|
41608
41609
|
}).strict();
|
|
41609
41610
|
var goalUpdateInputSchema = external_exports.object({
|
|
41610
41611
|
goal_id: uuidSchema9,
|
|
@@ -41655,7 +41656,6 @@ var goalListAtRiskOutputSchema = external_exports.object({
|
|
|
41655
41656
|
goals: external_exports.array(goalAtRiskSchema)
|
|
41656
41657
|
}).strict();
|
|
41657
41658
|
var goalSignalStateSchema = external_exports.enum(["ok", "risk", "crit", "pending"]);
|
|
41658
|
-
var goalStatusSourceSchema = external_exports.enum(["human", "agent", "rollup", "signal"]);
|
|
41659
41659
|
var goalMeasurableRoleSchema = external_exports.enum(["drives_status", "informs"]);
|
|
41660
41660
|
var goalDriverAnchorSchema = external_exports.object({
|
|
41661
41661
|
// The closed period being anchored on (an ISO date / period label — free-form so a
|
|
@@ -42746,6 +42746,20 @@ var confirmationSummarySchema = external_exports.object({
|
|
|
42746
42746
|
var confirmationListOutputSchema = external_exports.object({
|
|
42747
42747
|
confirmations: external_exports.array(confirmationSummarySchema)
|
|
42748
42748
|
}).strict();
|
|
42749
|
+
var confirmationDismissInputSchema = external_exports.object({
|
|
42750
|
+
confirmation_id: uuidSchema11
|
|
42751
|
+
}).strict();
|
|
42752
|
+
var confirmationDismissOutputSchema = external_exports.object({
|
|
42753
|
+
dismissed: external_exports.object({
|
|
42754
|
+
id: uuidSchema11,
|
|
42755
|
+
status: external_exports.literal("dismissed")
|
|
42756
|
+
}).strict()
|
|
42757
|
+
}).strict();
|
|
42758
|
+
var confirmationDismissStaleInputSchema = external_exports.object({}).strict();
|
|
42759
|
+
var confirmationDismissStaleOutputSchema = external_exports.object({
|
|
42760
|
+
dismissed_count: external_exports.number().int().min(0),
|
|
42761
|
+
dismissed_ids: external_exports.array(uuidSchema11)
|
|
42762
|
+
}).strict();
|
|
42749
42763
|
var credentialIngressInputSchema = external_exports.object({
|
|
42750
42764
|
seat_id: uuidSchema11.optional(),
|
|
42751
42765
|
provider: external_exports.string().trim().min(1),
|
|
@@ -43438,7 +43452,11 @@ var fleetDigestAgentSchema = fleetOverviewRowSchema.extend({
|
|
|
43438
43452
|
spend_7d_usd: external_exports.string(),
|
|
43439
43453
|
spend_30d_usd: external_exports.string(),
|
|
43440
43454
|
cost_drift_percent: external_exports.number().int().nullable(),
|
|
43441
|
-
|
|
43455
|
+
// DER-1905: "idle" added — cadence-aware grading now distinguishes an
|
|
43456
|
+
// on-demand agent with nothing pending (idle, neutral) from a scheduled
|
|
43457
|
+
// agent that missed its cron (stale, alarming). Purely additive to this
|
|
43458
|
+
// enum; no other field on this schema changed.
|
|
43459
|
+
health_status: external_exports.enum(["needs_review", "stale", "idle", "setup_gap", "awaiting_data", "awaiting_data_overdue", "healthy"]),
|
|
43442
43460
|
health_reasons: external_exports.array(external_exports.string()),
|
|
43443
43461
|
recent_failed_runs: external_exports.array(fleetDigestRunSchema),
|
|
43444
43462
|
unresolved_errors: external_exports.array(fleetDigestErrorSchema),
|
|
@@ -43509,8 +43527,11 @@ var seatRunDetailSchema = seatRunSchema.extend({
|
|
|
43509
43527
|
task_id: uuidSchema14.nullable(),
|
|
43510
43528
|
work_order_id: uuidSchema14.nullable(),
|
|
43511
43529
|
transcript_ref: external_exports.string(),
|
|
43512
|
-
|
|
43513
|
-
|
|
43530
|
+
// DER-1913: null = usage UNKNOWN — a Codex / read-status / timeout / spawn-error
|
|
43531
|
+
// runner turn reports no model-usage envelope, so the run records no token count.
|
|
43532
|
+
// Never coerced to a fabricated 0; surfaces (CLI/MCP/UI) render it as unknown.
|
|
43533
|
+
input_tokens: external_exports.number().int().nonnegative().nullable(),
|
|
43534
|
+
output_tokens: external_exports.number().int().nonnegative().nullable(),
|
|
43514
43535
|
outcome: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
43515
43536
|
error_logs: external_exports.array(seatRunErrorLogSchema),
|
|
43516
43537
|
skill_activations: external_exports.array(external_exports.object({
|
|
@@ -50113,11 +50134,11 @@ Keep agent scope tight at first. Approve more autonomy only after evidence. Use
|
|
|
50113
50134
|
order: 71,
|
|
50114
50135
|
title: "Confirmations and human gates guide",
|
|
50115
50136
|
summary: "How {{brand}} routes authority-changing work through human confirmation, and why agents never approve their own requests.",
|
|
50116
|
-
version: "2026-07-
|
|
50137
|
+
version: "2026-07-17.1",
|
|
50117
50138
|
public: true,
|
|
50118
50139
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
50119
50140
|
stages: ["graph_design", "charter_design", "staffing", "operating_rhythm"],
|
|
50120
|
-
relatedCommandIds: ["confirmation.approve", "confirmation.list", "confirmation.reject", "confirmation.remint", "charter.approve", "agent.go_live", "credential.ingress", "mcp_token.create", "staffing.assign_user", "goal.reparent", "friction.resolve", "task.confirm_proposal", "task.decline_proposal"],
|
|
50141
|
+
relatedCommandIds: ["confirmation.approve", "confirmation.dismiss", "confirmation.dismiss_stale", "confirmation.list", "confirmation.reject", "confirmation.remint", "charter.approve", "agent.go_live", "credential.ingress", "mcp_token.create", "staffing.assign_user", "goal.reparent", "friction.resolve", "task.confirm_proposal", "task.decline_proposal"],
|
|
50121
50142
|
legal: { publicRisk: "low", notes: ["{{brand}}-native human-gate guidance."] },
|
|
50122
50143
|
sources: [
|
|
50123
50144
|
{
|
|
@@ -50165,6 +50186,10 @@ A held tool call actuates the same way regardless of which command backs it \u20
|
|
|
50165
50186
|
|
|
50166
50187
|
The \`/approvals\` page is one place a human clears every pending decision across the company. It unions still-pending confirmations, open steward escalations, and agent-proposed draft tasks into a single queue, each item shown with its risk or proposal type, the source or proposing seat, and redacted arguments \u2014 secret material is never displayed. You see only items for seats you have authority over: an owner sees all of them; a member sees confirmations for the seats they occupy, escalations routed to their steward chain, and task proposals where they occupy or steward the target seat. Confirming a task proposal runs \`task.confirm_proposal\` and moves the draft to Offered; declining runs \`task.decline_proposal\` and records a human reason. Neither action accepts the task, schedules a run, or executes agent work. Approve and reject route through the same \`confirmation.approve\` / \`confirmation.reject\` and escalation decision commands the CLI and per-seat surfaces use, so the audit trail and the agents-recommend-humans-decide rule are identical wherever you act. The queue also unions pending Forge software-credential requests (owner-scoped, read-only, deep-linking to \`/forge\`) \u2014 it surfaces the ask and links to the vault-backed fulfillment flow, never collecting the secret itself. A confirmation that fails on approval shows the actual execution error inline as a \`failed\` card instead of a dead generic banner, and its Retry re-runs \`confirmation.approve\` against current state; re-minting the same request supersedes the older card instead of leaving a duplicate behind.
|
|
50167
50188
|
|
|
50189
|
+
## Clearing expired confirmations
|
|
50190
|
+
|
|
50191
|
+
A confirmation that expires before a human acts on it is dead \u2014 it can no longer be approved. Re-mint revives one; dismiss is the opposite gesture for a card you do *not* want to revive. \`confirmation.dismiss\` clears one obsolete expired card from the queue, and \`confirmation.dismiss_stale\` clears every currently-expired card in your authority scope at once (bulk "dismiss all stale"). Dismiss is human-only queue hygiene: it never re-executes the original command and never records a decision \u2014 it just marks the dead card \`dismissed\`, notes who dismissed it, and writes an audit event for each row (\`dismiss_stale\` writes one per card). Only an already-expired card is dismissable; a still-live pending confirmation is refused, because its decision is approve or reject, not dismiss. You can only dismiss cards you already have authority over \u2014 an owner over any, a member over the seats they occupy.
|
|
50192
|
+
|
|
50168
50193
|
## The rule for agents
|
|
50169
50194
|
|
|
50170
50195
|
An agent never approves or rejects its own request. \`decisions.decided_by\` is always a human. The steward decision commands (\`escalation.resolve\`, \`escalation.reject\`) and proposal decision commands (\`task.confirm_proposal\`, \`task.decline_proposal\`) are not exposed over MCP. When an agent hits a gate, it prepares the evidence and the recommended action, returns the approve link, and waits for a human. Draft task proposals follow the same rule: the agent can propose the commitment, but a target-side human confirms or declines it unless a bounded Trusted grant applies.
|
|
@@ -52525,6 +52550,8 @@ var COMMAND_MANIFEST = [
|
|
|
52525
52550
|
{ "id": "compass.show_markdown", "namespace": "compass", "action": "show_markdown", "title": "Show Compass as markdown", "description": "Compose the current Compass and its open gaps into a clean, human-skimmable markdown card for review.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Render the current Compass and its open gaps as a clean markdown card to show your human a quick review." },
|
|
52526
52551
|
{ "id": "compass.update_draft", "namespace": "compass", "action": "update_draft", "title": "Update Compass draft", "description": "Replace a draft Compass document.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "draft_id", "flag": "draft-id", "type": "string", "required": true }], "hasComplexInput": true, "help": "Pass the compass_version_id returned by compass.draft as draft_id; update_draft replaces only an unpublished draft in this tenant." },
|
|
52527
52552
|
{ "id": "confirmation.approve", "namespace": "confirmation", "action": "approve", "title": "Approve pending confirmation", "description": "Approve an in-flight confirmation and execute the original command as the approving human.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }, { "name": "reviewed", "flag": "reviewed", "type": "boolean", "required": false }], "hasComplexInput": false, "help": "Approve pending confirmations only when the requested durable change is clear to the human owner." },
|
|
52553
|
+
{ "id": "confirmation.dismiss", "namespace": "confirmation", "action": "dismiss", "title": "Dismiss an expired confirmation", "description": "Clear a single expired, unactioned confirmation from the queue without re-executing the original command.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Clear a single expired, unactioned confirmation from the queue. Dismiss is human-only queue hygiene \u2014 it never re-executes the original command." },
|
|
52554
|
+
{ "id": "confirmation.dismiss_stale", "namespace": "confirmation", "action": "dismiss_stale", "title": "Dismiss all expired confirmations", "description": "Clear every expired, unactioned confirmation in your authority scope from the queue without re-executing any command.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [], "hasComplexInput": false, "help": "Clear every expired, unactioned confirmation in your authority scope at once. Each dismissal is audited; none re-executes the original command." },
|
|
52528
52555
|
{ "id": "confirmation.list", "namespace": "confirmation", "action": "list", "title": "List pending confirmations", "description": "List in-flight confirmations awaiting a human decision, scoped to the caller's authority (owner sees all; a member sees seats they occupy; an agent sees its own seat).", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "limit", "flag": "limit", "type": "integer", "required": false }], "hasComplexInput": false, "help": "List pending confirmations awaiting a human decision before approving or rejecting them." },
|
|
52529
52556
|
{ "id": "confirmation.reject", "namespace": "confirmation", "action": "reject", "title": "Reject pending confirmation", "description": "Reject an in-flight confirmation without executing the original command.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }, { "name": "reason", "flag": "reason", "type": "string", "required": false }], "hasComplexInput": false, "help": "Reject pending confirmations when authority, evidence, or human intent is unclear." },
|
|
52530
52557
|
{ "id": "confirmation.remint", "namespace": "confirmation", "action": "remint", "title": "Re-mint an expired confirmation", "description": "Stage a fresh confirmation for the same command as an expired one, re-running the normal minting authorization checks.", "requiredScope": "tenant", "confirmation": "none", "exposeOverMcp": true, "fields": [{ "name": "confirmation_id", "flag": "confirmation-id", "type": "string", "required": true }], "hasComplexInput": false, "help": "Re-mint an expired confirmation into a fresh one before approving, instead of re-running the original command from scratch." },
|
|
@@ -56383,7 +56410,8 @@ function parseSemver2(value) {
|
|
|
56383
56410
|
// src/runner-serve.ts
|
|
56384
56411
|
import { execFile as execFileCallback5, spawn } from "node:child_process";
|
|
56385
56412
|
import { createHash as createHash2 } from "node:crypto";
|
|
56386
|
-
import {
|
|
56413
|
+
import { constants as fsConstants2 } from "node:fs";
|
|
56414
|
+
import { access as access3, chmod as chmod2, mkdir as mkdir5, readFile as readFile5, rename, rm as rm5, statfs, writeFile as writeFile5 } from "node:fs/promises";
|
|
56387
56415
|
import { cpus, homedir as homedir4, hostname as hostname3, platform, totalmem, tmpdir as tmpdir2 } from "node:os";
|
|
56388
56416
|
import path7 from "node:path";
|
|
56389
56417
|
import { setTimeout as sleep3 } from "node:timers/promises";
|
|
@@ -57699,11 +57727,43 @@ function resolveSandboxPosture(env, homeDir) {
|
|
|
57699
57727
|
function resolveSandboxNetworkFlag(env) {
|
|
57700
57728
|
return parseSandboxNetwork(env.RUNNER_SANDBOX_NETWORK).allowNetwork ? "allow" : "deny";
|
|
57701
57729
|
}
|
|
57730
|
+
var CAPABILITY_PROBE_TIMEOUT_MS = 1e4;
|
|
57731
|
+
function classifyProbeError(err) {
|
|
57732
|
+
if (!err || typeof err !== "object") {
|
|
57733
|
+
return "absent";
|
|
57734
|
+
}
|
|
57735
|
+
const candidate = err;
|
|
57736
|
+
const message = typeof candidate.message === "string" ? candidate.message.toLowerCase() : "";
|
|
57737
|
+
if (candidate.code === "ENOENT" || message.includes("not found")) {
|
|
57738
|
+
return "absent";
|
|
57739
|
+
}
|
|
57740
|
+
if (candidate.killed === true || candidate.code === "ETIMEDOUT" || candidate.signal === "SIGTERM") {
|
|
57741
|
+
return "present_slow";
|
|
57742
|
+
}
|
|
57743
|
+
return "absent";
|
|
57744
|
+
}
|
|
57745
|
+
async function isResolvableOnPath(command) {
|
|
57746
|
+
const dirs = (process.env.PATH ?? "").split(path7.delimiter).filter((dir) => dir.length > 0);
|
|
57747
|
+
const suffixes = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter((ext) => ext.length > 0) : [""];
|
|
57748
|
+
for (const dir of dirs) {
|
|
57749
|
+
for (const suffix of suffixes) {
|
|
57750
|
+
try {
|
|
57751
|
+
await access3(path7.join(dir, `${command}${suffix}`), fsConstants2.X_OK);
|
|
57752
|
+
return true;
|
|
57753
|
+
} catch {
|
|
57754
|
+
}
|
|
57755
|
+
}
|
|
57756
|
+
}
|
|
57757
|
+
return false;
|
|
57758
|
+
}
|
|
57702
57759
|
async function probe(command) {
|
|
57703
57760
|
try {
|
|
57704
|
-
const { stdout, stderr } = await execFile5(command, ["--version"], { timeout:
|
|
57761
|
+
const { stdout, stderr } = await execFile5(command, ["--version"], { timeout: CAPABILITY_PROBE_TIMEOUT_MS });
|
|
57705
57762
|
return { installed: true, version: (stdout || stderr).trim() || null, auth_state: "ready" };
|
|
57706
|
-
} catch {
|
|
57763
|
+
} catch (err) {
|
|
57764
|
+
if (classifyProbeError(err) === "present_slow" && await isResolvableOnPath(command)) {
|
|
57765
|
+
return { installed: true, version: null, auth_state: "unknown" };
|
|
57766
|
+
}
|
|
57707
57767
|
return { installed: false, version: null, auth_state: "unknown" };
|
|
57708
57768
|
}
|
|
57709
57769
|
}
|
|
@@ -57739,7 +57799,7 @@ async function probeClaude() {
|
|
|
57739
57799
|
}
|
|
57740
57800
|
async function probeClaudeStreamJsonInterface() {
|
|
57741
57801
|
try {
|
|
57742
|
-
const { stdout, stderr } = await execFile5("claude", ["--help"], { timeout:
|
|
57802
|
+
const { stdout, stderr } = await execFile5("claude", ["--help"], { timeout: CAPABILITY_PROBE_TIMEOUT_MS, maxBuffer: 1024 * 1024 });
|
|
57743
57803
|
return supportsClaudeStreamJsonInterface(`${stdout ?? ""}
|
|
57744
57804
|
${stderr ?? ""}`);
|
|
57745
57805
|
} catch {
|
|
@@ -57751,7 +57811,7 @@ function supportsClaudeStreamJsonInterface(helpText2) {
|
|
|
57751
57811
|
}
|
|
57752
57812
|
async function probeGit() {
|
|
57753
57813
|
try {
|
|
57754
|
-
const { stdout, stderr } = await execFile5("git", ["--version"], { timeout:
|
|
57814
|
+
const { stdout, stderr } = await execFile5("git", ["--version"], { timeout: CAPABILITY_PROBE_TIMEOUT_MS });
|
|
57755
57815
|
const raw = (stdout || stderr).trim();
|
|
57756
57816
|
return { installed: true, version: /(\d+\.\d+(?:\.\d+)?)/.exec(raw)?.[1] ?? (raw || null) };
|
|
57757
57817
|
} catch {
|
|
@@ -57760,7 +57820,7 @@ async function probeGit() {
|
|
|
57760
57820
|
}
|
|
57761
57821
|
async function probeGh() {
|
|
57762
57822
|
try {
|
|
57763
|
-
const { stdout, stderr } = await execFile5("gh", ["--version"], { timeout:
|
|
57823
|
+
const { stdout, stderr } = await execFile5("gh", ["--version"], { timeout: CAPABILITY_PROBE_TIMEOUT_MS });
|
|
57764
57824
|
const raw = (stdout || stderr).trim().split("\n")[0]?.trim() ?? "";
|
|
57765
57825
|
const authenticated = await probeGhAuth();
|
|
57766
57826
|
return { installed: true, version: /(\d+\.\d+\.\d+)/.exec(raw)?.[1] ?? (raw || null), authenticated };
|
|
@@ -57886,6 +57946,11 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtim
|
|
|
57886
57946
|
summary: capResultSummary(result.summary),
|
|
57887
57947
|
log_ref: input.logRef(id),
|
|
57888
57948
|
model: (turnRuntime ?? runtime) === "codex" ? "codex-cli" : "claude-cli",
|
|
57949
|
+
// DER-1915: token usage lifted from the claude JSON envelope (extractClaudeUsage), sent for
|
|
57950
|
+
// BOTH kinds when present — never defaulted to 0 (see RunnerTurnResult.inputTokens/outputTokens).
|
|
57951
|
+
// Both result routes (work-orders and turn-executions) accept these snake_case fields.
|
|
57952
|
+
...result.inputTokens != null ? { input_tokens: result.inputTokens } : {},
|
|
57953
|
+
...result.outputTokens != null ? { output_tokens: result.outputTokens } : {},
|
|
57889
57954
|
// Artifact-bearing result fields (T1.5) — only sent when the harness produced them, so
|
|
57890
57955
|
// the result route's `.strict()` schema still accepts a plain read-status report.
|
|
57891
57956
|
...result.prUrl ? { pr_url: result.prUrl } : {},
|
|
@@ -58019,9 +58084,14 @@ function resolveTurnPlan(input) {
|
|
|
58019
58084
|
],
|
|
58020
58085
|
codexSandbox: execution.local_tools === "workspace_write" ? "workspace-write" : "read-only",
|
|
58021
58086
|
// Forge build/review turns emit a structured JSON envelope (reusing the AICOS
|
|
58022
|
-
// --output-format json pattern) so their summary is a real contract, not a stdout
|
|
58023
|
-
//
|
|
58024
|
-
|
|
58087
|
+
// --output-format json pattern) so their summary is a real contract, not a stdout slice.
|
|
58088
|
+
// DER-1915: seat_work ALSO uses the JSON envelope now, purely so its usage block
|
|
58089
|
+
// (extractClaudeUsage) is reportable — the DER-1333 intent (a free-form status summary,
|
|
58090
|
+
// not a structured contract) is unchanged, because `extractRunnerSummary` unwraps the
|
|
58091
|
+
// envelope down to the model's own `result` text either way; json just adds parseable
|
|
58092
|
+
// metadata (usage, session_id) alongside it. Only read_status (a read-only status stub,
|
|
58093
|
+
// not a real MCP turn) still uses plain text.
|
|
58094
|
+
outputFormat: execution.profile === "forge_build" || execution.profile === "forge_review" || execution.profile === "seat_work" ? "json" : "text",
|
|
58025
58095
|
// Only a Forge workspace grants a repo cwd; a needs_repo contract with no prepared
|
|
58026
58096
|
// workspace (broker/clone failed upstream) falls back to tmpdir rather than a bad cwd.
|
|
58027
58097
|
repoDir: execution.needs_repo ? input.repoDir : null,
|
|
@@ -58702,11 +58772,21 @@ function runModelProcess(input) {
|
|
|
58702
58772
|
let err = "";
|
|
58703
58773
|
const timer = setTimeout(() => {
|
|
58704
58774
|
killProcessTree(child);
|
|
58775
|
+
const seconds = Math.round(input.timeoutMs / 1e3);
|
|
58705
58776
|
const partial2 = scrubTranscriptSessionIds(redactForLog((out || err || "").trim()));
|
|
58777
|
+
let transcriptBody = partial2;
|
|
58778
|
+
if (transcriptBody.length === 0) {
|
|
58779
|
+
const redactedErr = scrubTranscriptSessionIds(redactForLog(err.trim()));
|
|
58780
|
+
transcriptBody = `turn timed out after ${seconds}s; no model output was captured before the process was killed.`;
|
|
58781
|
+
if (redactedErr.length > 0) {
|
|
58782
|
+
transcriptBody += `
|
|
58783
|
+
${redactedErr}`;
|
|
58784
|
+
}
|
|
58785
|
+
}
|
|
58706
58786
|
finish({
|
|
58707
58787
|
ok: false,
|
|
58708
|
-
summary: `turn timed out after ${
|
|
58709
|
-
|
|
58788
|
+
summary: `turn timed out after ${seconds}s`,
|
|
58789
|
+
transcript: transcriptBody.slice(0, MAX_TRANSCRIPT_CHARS)
|
|
58710
58790
|
});
|
|
58711
58791
|
}, input.timeoutMs);
|
|
58712
58792
|
child.stdout.on("data", (data) => {
|
|
@@ -58721,18 +58801,30 @@ function runModelProcess(input) {
|
|
|
58721
58801
|
const redacted = redactForLog(rawOutput);
|
|
58722
58802
|
const evidence = input.collectEvidence && code === 0 ? extractRunnerEvidence(rawOutput) : [];
|
|
58723
58803
|
const planArtifact = input.collectEvidence && code === 0 ? extractForgePlanArtifact(rawOutput) : void 0;
|
|
58804
|
+
const transcriptSource = !input.collectEvidence && redacted.trim().startsWith("{") ? extractRunnerSummary(redacted) : redacted;
|
|
58724
58805
|
finish({
|
|
58725
58806
|
ok: code === 0,
|
|
58726
58807
|
summary: extractRunnerSummary(redacted).slice(0, 12e3),
|
|
58727
|
-
transcript: scrubTranscriptSessionIds(
|
|
58808
|
+
transcript: scrubTranscriptSessionIds(transcriptSource).slice(0, MAX_TRANSCRIPT_CHARS),
|
|
58728
58809
|
...evidence.length > 0 ? { evidence } : {},
|
|
58729
58810
|
...input.expectSessionEnvelope ? extractClaudeSessionId(rawOutput) : {},
|
|
58730
|
-
...planArtifact ? { planArtifact } : {}
|
|
58811
|
+
...planArtifact ? { planArtifact } : {},
|
|
58812
|
+
// DER-1915: attempt the token-usage extraction for BOTH work_order and turn_execution
|
|
58813
|
+
// kinds (unlike the session id, which is scoped to expectSessionEnvelope/resume) — a
|
|
58814
|
+
// Forge build/review work order runs claude with the same JSON envelope and its usage
|
|
58815
|
+
// is just as reportable. Safe to attempt unconditionally: non-JSON/no-usage output
|
|
58816
|
+
// yields {}, never a fabricated zero.
|
|
58817
|
+
...extractClaudeUsage(rawOutput)
|
|
58731
58818
|
});
|
|
58732
58819
|
});
|
|
58733
58820
|
child.on("error", (error51) => {
|
|
58734
58821
|
clearTimeout(timer);
|
|
58735
|
-
|
|
58822
|
+
const message = redactForLog(error51.message);
|
|
58823
|
+
finish({
|
|
58824
|
+
ok: false,
|
|
58825
|
+
summary: `spawn error: ${message}`,
|
|
58826
|
+
transcript: `spawn error: ${message}`.slice(0, MAX_TRANSCRIPT_CHARS)
|
|
58827
|
+
});
|
|
58736
58828
|
});
|
|
58737
58829
|
});
|
|
58738
58830
|
}
|
|
@@ -58852,6 +58944,30 @@ function extractClaudeSessionId(output) {
|
|
|
58852
58944
|
return {};
|
|
58853
58945
|
}
|
|
58854
58946
|
}
|
|
58947
|
+
function extractClaudeUsage(output) {
|
|
58948
|
+
const trimmed = output.trim();
|
|
58949
|
+
if (!trimmed.startsWith("{")) {
|
|
58950
|
+
return {};
|
|
58951
|
+
}
|
|
58952
|
+
try {
|
|
58953
|
+
const parsed = JSON.parse(trimmed);
|
|
58954
|
+
const usage2 = parsed.usage;
|
|
58955
|
+
if (!usage2 || typeof usage2 !== "object" || Array.isArray(usage2)) {
|
|
58956
|
+
return {};
|
|
58957
|
+
}
|
|
58958
|
+
const record2 = usage2;
|
|
58959
|
+
const result = {};
|
|
58960
|
+
if (typeof record2.input_tokens === "number" && Number.isFinite(record2.input_tokens) && record2.input_tokens >= 0) {
|
|
58961
|
+
result.inputTokens = Math.floor(record2.input_tokens);
|
|
58962
|
+
}
|
|
58963
|
+
if (typeof record2.output_tokens === "number" && Number.isFinite(record2.output_tokens) && record2.output_tokens >= 0) {
|
|
58964
|
+
result.outputTokens = Math.floor(record2.output_tokens);
|
|
58965
|
+
}
|
|
58966
|
+
return result;
|
|
58967
|
+
} catch {
|
|
58968
|
+
return {};
|
|
58969
|
+
}
|
|
58970
|
+
}
|
|
58855
58971
|
function runtimeSessionKey(state, workOrder) {
|
|
58856
58972
|
const seatId = extractSeatId(workOrder);
|
|
58857
58973
|
const userId = extractTurnUserId(workOrder);
|