@rosthq/cli 0.7.101 → 0.7.102
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/dist/index.js +138 -91
- package/dist/index.js.map +2 -2
- package/dist/runner-serve.d.ts +6 -0
- package/dist/runner-serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -57536,15 +57536,17 @@ ${usage()}
|
|
|
57536
57536
|
let heartbeatSucceeded = false;
|
|
57537
57537
|
let failed = false;
|
|
57538
57538
|
let activeSessions = 0;
|
|
57539
|
+
const activeTurnExecutionIds = /* @__PURE__ */ new Set();
|
|
57539
57540
|
do {
|
|
57540
57541
|
const detectedCapabilities = await detectCapabilities(cliVersion, env, homeDir);
|
|
57541
57542
|
const capabilities = capabilitiesForConfiguredRuntime(detectedCapabilities, config2.runtime);
|
|
57542
57543
|
const localRuntime = config2.execute ? effectiveExecutionRuntime(config2.runtime, capabilities) : null;
|
|
57543
57544
|
try {
|
|
57544
|
-
const heartbeat = await post2(fetchImpl, options.appUrl, "/api/runner/heartbeat", {
|
|
57545
|
+
const heartbeat = await post2(fetchImpl, options.appUrl, "/api/runner/heartbeat", buildHeartbeatBody({
|
|
57545
57546
|
capabilities,
|
|
57546
|
-
telemetry: detectTelemetry(capabilities, env, activeSessions, cliVersion)
|
|
57547
|
-
|
|
57547
|
+
telemetry: detectTelemetry(capabilities, env, activeSessions, cliVersion),
|
|
57548
|
+
activeTurnExecutionIds: [...activeTurnExecutionIds]
|
|
57549
|
+
}), state.runner_secret);
|
|
57548
57550
|
if (heartbeat.status !== 200) {
|
|
57549
57551
|
options.io.stderr.write(`heartbeat ${heartbeat.status}: ${redactForLog(JSON.stringify(heartbeat.json))}
|
|
57550
57552
|
`);
|
|
@@ -57563,10 +57565,11 @@ ${usage()}
|
|
|
57563
57565
|
intervalMs: config2.heartbeatMs,
|
|
57564
57566
|
capabilities,
|
|
57565
57567
|
io: options.io,
|
|
57566
|
-
telemetry: () => detectTelemetry(capabilities, env, activeSessions, cliVersion)
|
|
57568
|
+
telemetry: () => detectTelemetry(capabilities, env, activeSessions, cliVersion),
|
|
57569
|
+
activeTurnIds: () => [...activeTurnExecutionIds]
|
|
57567
57570
|
});
|
|
57568
57571
|
try {
|
|
57569
|
-
await claimAndExecute(fetchImpl, options.appUrl, state, config2, options.io, localRuntime);
|
|
57572
|
+
await claimAndExecute(fetchImpl, options.appUrl, state, config2, options.io, localRuntime, activeTurnExecutionIds);
|
|
57570
57573
|
} finally {
|
|
57571
57574
|
stopTurnHeartbeat();
|
|
57572
57575
|
activeSessions = Math.max(0, activeSessions - 1);
|
|
@@ -57579,27 +57582,61 @@ ${usage()}
|
|
|
57579
57582
|
}
|
|
57580
57583
|
beat += 1;
|
|
57581
57584
|
if (!config2.once) {
|
|
57582
|
-
await waitForNextHeartbeat(
|
|
57585
|
+
await waitForNextHeartbeat(
|
|
57586
|
+
fetchImpl,
|
|
57587
|
+
options.appUrl,
|
|
57588
|
+
state,
|
|
57589
|
+
config2,
|
|
57590
|
+
options.io,
|
|
57591
|
+
localRuntime,
|
|
57592
|
+
capabilities,
|
|
57593
|
+
() => detectTelemetry(capabilities, env, activeSessions, cliVersion),
|
|
57594
|
+
activeTurnExecutionIds
|
|
57595
|
+
);
|
|
57583
57596
|
}
|
|
57584
57597
|
} while (!config2.once);
|
|
57585
57598
|
return heartbeatSucceeded && !failed ? 0 : 1;
|
|
57586
57599
|
}
|
|
57587
|
-
async function waitForNextHeartbeat(fetchImpl, appUrl2, state, config2, io, runtime) {
|
|
57600
|
+
async function waitForNextHeartbeat(fetchImpl, appUrl2, state, config2, io, runtime, capabilities, telemetry, activeTurnExecutionIds) {
|
|
57588
57601
|
const deadline = Date.now() + config2.heartbeatMs;
|
|
57589
|
-
|
|
57590
|
-
|
|
57591
|
-
|
|
57592
|
-
|
|
57593
|
-
|
|
57594
|
-
|
|
57595
|
-
|
|
57596
|
-
|
|
57597
|
-
|
|
57598
|
-
|
|
57602
|
+
const stopWait = runtime ? startTurnHeartbeat({
|
|
57603
|
+
fetchImpl,
|
|
57604
|
+
appUrl: appUrl2,
|
|
57605
|
+
secret: state.runner_secret,
|
|
57606
|
+
intervalMs: config2.heartbeatMs,
|
|
57607
|
+
capabilities,
|
|
57608
|
+
io,
|
|
57609
|
+
telemetry,
|
|
57610
|
+
activeTurnIds: () => [...activeTurnExecutionIds]
|
|
57611
|
+
}) : null;
|
|
57612
|
+
try {
|
|
57613
|
+
while (Date.now() < deadline) {
|
|
57614
|
+
const intervalMs = Math.min(config2.interactiveClaimMs, deadline - Date.now());
|
|
57615
|
+
await sleep3(intervalMs);
|
|
57616
|
+
if (!runtime || Date.now() >= deadline) {
|
|
57617
|
+
continue;
|
|
57618
|
+
}
|
|
57619
|
+
try {
|
|
57620
|
+
await claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime, activeTurnExecutionIds, { interactiveOnly: true });
|
|
57621
|
+
} catch (error51) {
|
|
57622
|
+
io.stderr.write(`transient runner error (continuing): ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
|
|
57599
57623
|
`);
|
|
57624
|
+
}
|
|
57600
57625
|
}
|
|
57626
|
+
} finally {
|
|
57627
|
+
stopWait?.();
|
|
57601
57628
|
}
|
|
57602
57629
|
}
|
|
57630
|
+
function buildHeartbeatBody(input) {
|
|
57631
|
+
const body = {
|
|
57632
|
+
capabilities: input.capabilities,
|
|
57633
|
+
telemetry: input.telemetry
|
|
57634
|
+
};
|
|
57635
|
+
if (input.activeTurnExecutionIds.length > 0) {
|
|
57636
|
+
body.active_turn_execution_ids = input.activeTurnExecutionIds;
|
|
57637
|
+
}
|
|
57638
|
+
return body;
|
|
57639
|
+
}
|
|
57603
57640
|
function startTurnHeartbeat(deps) {
|
|
57604
57641
|
let stopped = false;
|
|
57605
57642
|
let inFlight = false;
|
|
@@ -57613,10 +57650,11 @@ function startTurnHeartbeat(deps) {
|
|
|
57613
57650
|
timeout.unref?.();
|
|
57614
57651
|
void (async () => {
|
|
57615
57652
|
try {
|
|
57616
|
-
const response = await post2(deps.fetchImpl, deps.appUrl, "/api/runner/heartbeat", {
|
|
57653
|
+
const response = await post2(deps.fetchImpl, deps.appUrl, "/api/runner/heartbeat", buildHeartbeatBody({
|
|
57617
57654
|
capabilities: deps.capabilities,
|
|
57618
|
-
telemetry: deps.telemetry()
|
|
57619
|
-
|
|
57655
|
+
telemetry: deps.telemetry(),
|
|
57656
|
+
activeTurnExecutionIds: deps.activeTurnIds()
|
|
57657
|
+
}), deps.secret, controller.signal);
|
|
57620
57658
|
if (!stopped && response.status !== 200) {
|
|
57621
57659
|
deps.io.stderr.write(`turn heartbeat ${response.status}: ${redactForLog(JSON.stringify(response.json))}
|
|
57622
57660
|
`);
|
|
@@ -58077,7 +58115,7 @@ async function post2(fetchImpl, appUrl2, pathName, body, token, signal) {
|
|
|
58077
58115
|
}
|
|
58078
58116
|
return { status: response.status, json: json2 };
|
|
58079
58117
|
}
|
|
58080
|
-
async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime, options = {}) {
|
|
58118
|
+
async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime, activeTurnIds, options = {}) {
|
|
58081
58119
|
const claimStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
58082
58120
|
const claimed = await post2(
|
|
58083
58121
|
fetchImpl,
|
|
@@ -58097,7 +58135,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime,
|
|
|
58097
58135
|
});
|
|
58098
58136
|
const turnExecution = claimed.json.turn_execution;
|
|
58099
58137
|
if (claimed.status === 200 && turnExecution && typeof turnExecution === "object") {
|
|
58100
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, {
|
|
58138
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, activeTurnIds, {
|
|
58101
58139
|
kind: "turn_execution",
|
|
58102
58140
|
idField: "turn_execution_id",
|
|
58103
58141
|
item: turnExecution,
|
|
@@ -58111,7 +58149,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime,
|
|
|
58111
58149
|
if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
|
|
58112
58150
|
return;
|
|
58113
58151
|
}
|
|
58114
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, {
|
|
58152
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, activeTurnIds, {
|
|
58115
58153
|
kind: "work_order",
|
|
58116
58154
|
idField: "work_order_id",
|
|
58117
58155
|
item: workOrder,
|
|
@@ -58120,7 +58158,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime,
|
|
|
58120
58158
|
logRef: (id) => `runner-local:${id}`
|
|
58121
58159
|
});
|
|
58122
58160
|
}
|
|
58123
|
-
async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, input) {
|
|
58161
|
+
async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, ledger, activeTurnIds, input) {
|
|
58124
58162
|
const id = input.item.id;
|
|
58125
58163
|
if (typeof id !== "string") {
|
|
58126
58164
|
return;
|
|
@@ -58129,79 +58167,88 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtim
|
|
|
58129
58167
|
if (started.status !== 200) {
|
|
58130
58168
|
throw new Error(`${claimedUnitLabel(input.kind)} start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
|
|
58131
58169
|
}
|
|
58132
|
-
|
|
58133
|
-
|
|
58134
|
-
|
|
58135
|
-
|
|
58136
|
-
|
|
58137
|
-
|
|
58138
|
-
|
|
58139
|
-
|
|
58140
|
-
|
|
58141
|
-
|
|
58142
|
-
|
|
58143
|
-
|
|
58144
|
-
|
|
58145
|
-
|
|
58170
|
+
if (input.kind === "turn_execution") {
|
|
58171
|
+
activeTurnIds.add(id);
|
|
58172
|
+
}
|
|
58173
|
+
try {
|
|
58174
|
+
const turnRuntime = runtimeForClaimedUnit(input.item, runtime, input.kind);
|
|
58175
|
+
const result = turnRuntime ? await runLocalTurn({ fetchImpl, appUrl: appUrl2, state, config: config2, io, ledger }, input.item, turnRuntime, input.kind) : {
|
|
58176
|
+
ok: false,
|
|
58177
|
+
summary: `Runner runtime ${runtime} cannot execute requested ${String(input.item.brain ?? "unknown")} turn.`
|
|
58178
|
+
};
|
|
58179
|
+
if (input.kind === "turn_execution" && turnRuntime === "claude") {
|
|
58180
|
+
try {
|
|
58181
|
+
if (result.ok && result.claudeSessionId) {
|
|
58182
|
+
await persistClaudeSessionId(config2.stateFile, state, input.item, result.claudeSessionId);
|
|
58183
|
+
} else if (!result.ok && result.resumedFromSessionId) {
|
|
58184
|
+
await clearClaudeSessionId(config2.stateFile, state, input.item);
|
|
58185
|
+
}
|
|
58186
|
+
} catch (error51) {
|
|
58187
|
+
io.stderr.write(`resume cache update failed (continuing): ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
|
|
58146
58188
|
`);
|
|
58189
|
+
}
|
|
58147
58190
|
}
|
|
58148
|
-
|
|
58149
|
-
|
|
58150
|
-
|
|
58151
|
-
|
|
58152
|
-
|
|
58153
|
-
|
|
58154
|
-
|
|
58155
|
-
|
|
58156
|
-
|
|
58157
|
-
|
|
58158
|
-
|
|
58159
|
-
|
|
58160
|
-
|
|
58161
|
-
|
|
58162
|
-
|
|
58163
|
-
|
|
58164
|
-
|
|
58165
|
-
|
|
58166
|
-
|
|
58167
|
-
|
|
58168
|
-
|
|
58169
|
-
|
|
58170
|
-
|
|
58171
|
-
|
|
58172
|
-
|
|
58173
|
-
|
|
58174
|
-
|
|
58175
|
-
|
|
58176
|
-
|
|
58177
|
-
|
|
58178
|
-
|
|
58179
|
-
|
|
58180
|
-
|
|
58181
|
-
|
|
58182
|
-
|
|
58183
|
-
|
|
58184
|
-
|
|
58185
|
-
|
|
58186
|
-
|
|
58187
|
-
|
|
58188
|
-
|
|
58189
|
-
|
|
58190
|
-
|
|
58191
|
-
|
|
58192
|
-
|
|
58193
|
-
|
|
58194
|
-
|
|
58195
|
-
|
|
58196
|
-
io.stdout.write(`${input.idField}=${id} status=failed (result rejected ${reported.status}; reported terminal failure)
|
|
58191
|
+
const reportBody = {
|
|
58192
|
+
status: result.ok ? "succeeded" : "failed",
|
|
58193
|
+
// Cap to the result route's summary limit (see capResultSummary / RESULT_SUMMARY_MAX). A
|
|
58194
|
+
// real turn summary can exceed it; without the cap the server's Zod `.max()` 400s the
|
|
58195
|
+
// report, and because the loop treats a throw as "transient (continuing)" the phase
|
|
58196
|
+
// zombies in `running` until its lease grace. Truncating keeps a successful turn a success.
|
|
58197
|
+
summary: capResultSummary(result.summary),
|
|
58198
|
+
log_ref: input.logRef(id),
|
|
58199
|
+
model: (turnRuntime ?? runtime) === "codex" ? "codex-cli" : "claude-cli",
|
|
58200
|
+
// DER-1915: token usage lifted from the claude JSON envelope (extractClaudeUsage), sent for
|
|
58201
|
+
// BOTH kinds when present — never defaulted to 0 (see RunnerTurnResult.inputTokens/outputTokens).
|
|
58202
|
+
// Both result routes (work-orders and turn-executions) accept these snake_case fields.
|
|
58203
|
+
...result.inputTokens != null ? { input_tokens: result.inputTokens } : {},
|
|
58204
|
+
...result.outputTokens != null ? { output_tokens: result.outputTokens } : {},
|
|
58205
|
+
// Artifact-bearing result fields (T1.5) — only sent when the harness produced them, so
|
|
58206
|
+
// the result route's `.strict()` schema still accepts a plain read-status report.
|
|
58207
|
+
...result.prUrl ? { pr_url: result.prUrl } : {},
|
|
58208
|
+
...result.branch ? { branch: result.branch } : {},
|
|
58209
|
+
...result.commitSha ? { commit_sha: result.commitSha } : {},
|
|
58210
|
+
...result.evidence && result.evidence.length > 0 ? { evidence: result.evidence } : {},
|
|
58211
|
+
...result.changedPaths && result.changedPaths.length > 0 ? { changed_paths: result.changedPaths } : {},
|
|
58212
|
+
// DER-1352 (T1.8): the merge-turn postcondition. Only sent when the harness GET-verified the PR
|
|
58213
|
+
// merged on GitHub — the driver's completion writer marks the changeset merged + writes the
|
|
58214
|
+
// decisions row ONLY on this. Gated on result.ok so a failed turn can never carry it.
|
|
58215
|
+
...result.ok && result.merged === true ? { merged: true } : {},
|
|
58216
|
+
...result.ok && result.merged === true && result.mergedCommitSha ? { merged_commit_sha: result.mergedCommitSha } : {},
|
|
58217
|
+
// DER-1331 (T1.9): the step ledger + the model's (already redacted, bounded) transcript.
|
|
58218
|
+
// Only the work-orders result route accepts these today (turn-executions is a separate,
|
|
58219
|
+
// narrower endpoint — T1.9 scope); its Zod schema strips unrecognized keys on a non-strict
|
|
58220
|
+
// parse either way, so sending them there is harmless, just unused.
|
|
58221
|
+
...input.kind === "work_order" ? { step_ledger: ledger.entries() } : {},
|
|
58222
|
+
...input.kind === "work_order" && result.transcript ? { transcript: result.transcript } : {},
|
|
58223
|
+
// DER-1371: the planner's structured decomposition — only sent when the model produced one.
|
|
58224
|
+
...result.planArtifact ? { plan_artifact: result.planArtifact } : {}
|
|
58225
|
+
};
|
|
58226
|
+
const reported = await postResultWithRetries(fetchImpl, appUrl2, input.resultPath(id), reportBody, state.runner_secret);
|
|
58227
|
+
if (reported.status !== 200) {
|
|
58228
|
+
if (reported.status >= 400 && reported.status < 500) {
|
|
58229
|
+
const fallback = await post2(fetchImpl, appUrl2, input.resultPath(id), {
|
|
58230
|
+
status: "failed",
|
|
58231
|
+
summary: capResultSummary(
|
|
58232
|
+
`Runner result rejected by server (${reported.status}). Original outcome: ${result.ok ? "succeeded" : "failed"}. ${redactForLog(JSON.stringify(reported.json))}`
|
|
58233
|
+
),
|
|
58234
|
+
log_ref: input.logRef(id),
|
|
58235
|
+
model: (turnRuntime ?? runtime) === "codex" ? "codex-cli" : "claude-cli"
|
|
58236
|
+
}, state.runner_secret);
|
|
58237
|
+
if (fallback.status === 200) {
|
|
58238
|
+
io.stdout.write(`${input.idField}=${id} status=failed (result rejected ${reported.status}; reported terminal failure)
|
|
58197
58239
|
`);
|
|
58198
|
-
|
|
58240
|
+
return;
|
|
58241
|
+
}
|
|
58199
58242
|
}
|
|
58243
|
+
throw new Error(`${claimedUnitLabel(input.kind)} result failed ${reported.status}: ${redactForLog(JSON.stringify(reported.json))}`);
|
|
58200
58244
|
}
|
|
58201
|
-
|
|
58202
|
-
}
|
|
58203
|
-
io.stdout.write(`${input.idField}=${id} status=${result.ok ? "succeeded" : "failed"}
|
|
58245
|
+
io.stdout.write(`${input.idField}=${id} status=${result.ok ? "succeeded" : "failed"}
|
|
58204
58246
|
`);
|
|
58247
|
+
} finally {
|
|
58248
|
+
if (input.kind === "turn_execution") {
|
|
58249
|
+
activeTurnIds.delete(id);
|
|
58250
|
+
}
|
|
58251
|
+
}
|
|
58205
58252
|
}
|
|
58206
58253
|
var RESULT_REPORT_RETRY_ATTEMPTS = 4;
|
|
58207
58254
|
var RESULT_REPORT_RETRY_BASE_DELAY_MS = 500;
|