@rosthq/cli 0.7.25 → 0.7.26
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 +217 -45
- package/dist/index.js.map +3 -3
- package/dist/runner-serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -42478,6 +42478,10 @@ var softwareBuildRequestSummarySchema = external_exports.object({
|
|
|
42478
42478
|
current_phase: softwarePhaseSchema,
|
|
42479
42479
|
inferred_risk: softwareRiskLevelSchema,
|
|
42480
42480
|
automation_mode: softwareAutomationModeSchema,
|
|
42481
|
+
max_iterations: external_exports.number().int().nullable(),
|
|
42482
|
+
max_wall_clock_seconds: external_exports.number().int().nullable(),
|
|
42483
|
+
cost_budget_usd: external_exports.string().nullable(),
|
|
42484
|
+
iterations_used: external_exports.number().int(),
|
|
42481
42485
|
created_at: external_exports.string()
|
|
42482
42486
|
});
|
|
42483
42487
|
var softwarePhaseRunSummarySchema = external_exports.object({
|
|
@@ -42531,6 +42535,21 @@ var softwareTaskSummarySchema = external_exports.object({
|
|
|
42531
42535
|
completed_at: external_exports.string().nullable(),
|
|
42532
42536
|
created_at: external_exports.string()
|
|
42533
42537
|
});
|
|
42538
|
+
var softwarePlanVersionSummarySchema = external_exports.object({
|
|
42539
|
+
id: uuid10,
|
|
42540
|
+
build_request_id: uuid10,
|
|
42541
|
+
version_no: external_exports.number().int(),
|
|
42542
|
+
status: softwarePlanVersionStatusSchema,
|
|
42543
|
+
summary: external_exports.string().nullable(),
|
|
42544
|
+
confidence_score: external_exports.number().int().nullable(),
|
|
42545
|
+
clarifying_questions: external_exports.array(external_exports.string()).default([]),
|
|
42546
|
+
clarification_answers: external_exports.array(external_exports.object({
|
|
42547
|
+
question: external_exports.string(),
|
|
42548
|
+
answer: external_exports.string()
|
|
42549
|
+
})).default([]),
|
|
42550
|
+
created_by_seat_id: uuid10.nullable(),
|
|
42551
|
+
created_at: external_exports.string()
|
|
42552
|
+
});
|
|
42534
42553
|
var softwarePlanConformanceFindingSummarySchema = external_exports.object({
|
|
42535
42554
|
id: uuid10,
|
|
42536
42555
|
build_request_id: uuid10,
|
|
@@ -42628,7 +42647,10 @@ var softwareRequestShowOutputSchema = external_exports.object({
|
|
|
42628
42647
|
request: softwareBuildRequestSummarySchema,
|
|
42629
42648
|
current_phase: softwarePhaseSchema,
|
|
42630
42649
|
phase_runs: external_exports.array(softwarePhaseRunSummarySchema),
|
|
42631
|
-
gates: external_exports.array(softwareGateSummarySchema)
|
|
42650
|
+
gates: external_exports.array(softwareGateSummarySchema),
|
|
42651
|
+
plan_versions: external_exports.array(softwarePlanVersionSummarySchema),
|
|
42652
|
+
changesets: external_exports.array(softwareChangesetSummarySchema),
|
|
42653
|
+
tasks: external_exports.array(softwareTaskSummarySchema)
|
|
42632
42654
|
});
|
|
42633
42655
|
var softwarePhaseAdvanceInputSchema = external_exports.object({
|
|
42634
42656
|
build_request_id: uuid10,
|
|
@@ -44388,7 +44410,7 @@ No orphan agents. No raw secrets in prompts, logs, or tool arguments. No durable
|
|
|
44388
44410
|
order: 41,
|
|
44389
44411
|
title: "Add agents to your Responsibility Graph",
|
|
44390
44412
|
summary: "The visual journey for adding an agent seat: where to start, choosing a mode, placing the seat, naming a Steward, setup, the safety gates, and go-live.",
|
|
44391
|
-
version: "2026-
|
|
44413
|
+
version: "2026-07-04.1",
|
|
44392
44414
|
public: true,
|
|
44393
44415
|
audiences: ["human", "in_app_agent"],
|
|
44394
44416
|
stages: ["staffing"],
|
|
@@ -44437,8 +44459,8 @@ The wizard builds a teammate, not a prompt:
|
|
|
44437
44459
|
|
|
44438
44460
|
1. **Responsibilities and authority** \u2014 what the seat owns, what it may do alone, what needs approval, and what must escalate. Every clause is editable before sign-off.
|
|
44439
44461
|
2. **Signals** \u2014 the measurables the seat is judged on.
|
|
44440
|
-
3. **Skills** \u2014 pin published, immutable Skill versions that the agent should carry. Skills are procedures, not authority: selecting one creates a proposed assignment and surfaces required/optional tool dependencies before
|
|
44441
|
-
4. **
|
|
44462
|
+
3. **Skills** \u2014 pin published, immutable Skill versions that the agent should carry. Skills are procedures, not authority: selecting one creates a proposed assignment and surfaces required/optional tool dependencies before access approval. The builder shows the provider and Access level each Skill proposes so the human sees the tool bundle before it enters the draft manifest.
|
|
44463
|
+
4. **Access** \u2014 connect or decline each proposed tool. Declining a tool updates the permission manifest and the dry-run task so the agent stays safe. Secrets go in through write-only credential ingress and are stored as vault references, never shown again. Missing required Skill tools block access approval, dry-run, and go-live until resolved; missing optional Skill tools warn without blocking.
|
|
44442
44464
|
5. **Schedule and lane** \u2014 when the agent runs and on which lane. These stay advanced unless the mode needs them.
|
|
44443
44465
|
|
|
44444
44466
|
You can save and resume a draft setup at any point. Returning to the flow restores the parent, Steward, mode, answers, Skill selections, tool decisions, and dry-run result.
|
|
@@ -44448,7 +44470,7 @@ You can save and resume a draft setup at any point. Returning to the flow restor
|
|
|
44448
44470
|
Every mode ends at the same conservative gates, shown at each step:
|
|
44449
44471
|
|
|
44450
44472
|
1. **Draft Charter** \u2014 a draft, not an active job, until a human approves it.
|
|
44451
|
-
2. **
|
|
44473
|
+
2. **Approve access** \u2014 a human approves the permission manifest before any tool is live. The command remains \`charter.sign_manifest\`; the UI names the human decision, not the storage primitive.
|
|
44452
44474
|
3. **Sandbox dry run** \u2014 the agent rehearses a representative task and is expected to escalate where the Charter says it must. A failed dry run keeps the draft and shows the reason; you edit and rerun.
|
|
44453
44475
|
4. **Human go-live** \u2014 a person promotes the agent live. An agent never approves its own setup, signs its own manifest, or goes itself live.
|
|
44454
44476
|
|
|
@@ -44464,7 +44486,7 @@ Reopening the builder for a seat whose agent is already live shows its live stat
|
|
|
44464
44486
|
|
|
44465
44487
|
In read-only or demo mode the **Add agent** affordance never starts a write. The public demo instead replays the add-an-agent journey end to end \u2014 describe the role, watch the draft Charter assemble, see the four safety gates light, and watch a sandbox dry run reach the must-escalate boundary and stop \u2014 then routes go-live to sign-up, because going live is a human decision.
|
|
44466
44488
|
|
|
44467
|
-
For CLI and MCP setup, read agent-skill-setup-guide before adding or assigning Skills. List existing company Skills and the {{brand}} catalog first, read the full Skill file before proposing it, and run the dependency check before
|
|
44489
|
+
For CLI and MCP setup, read agent-skill-setup-guide before adding or assigning Skills. List existing company Skills and the {{brand}} catalog first, read the full Skill file before proposing it, and run the dependency check before access approval. A missing required Skill tool is fixed by updating the Charter permission manifest through tool configuration; it is never fixed by treating the Skill as authorization.`
|
|
44468
44490
|
},
|
|
44469
44491
|
{
|
|
44470
44492
|
slug: "custom-agents-guide",
|
|
@@ -44881,7 +44903,7 @@ External connectors are being rolled out provider by provider, conservatively (r
|
|
|
44881
44903
|
order: 48,
|
|
44882
44904
|
title: "CLI and MCP installation guide",
|
|
44883
44905
|
summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
|
|
44884
|
-
version: "2026-07-04.
|
|
44906
|
+
version: "2026-07-04.17",
|
|
44885
44907
|
public: true,
|
|
44886
44908
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44887
44909
|
stages: ["company_setup", "staffing"],
|
|
@@ -45624,7 +45646,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
45624
45646
|
| \`rost_list_forge_github_installations\` | \`software_factory.github.installation.list\` | List connected GitHub App installations and each repository's bind state (connected or active) with the Forge project it maps to. Read-only. | Tenant | Call with \`{}\`; a \`connected\` repository is verified but not yet bound to a project. |
|
|
45625
45647
|
| \`rost_list_forge_runner_capacity\` | \`software_factory.capacity.list\` | List Forge runner capacity observations (advisory scheduling input only, never an authority input). Read-only; requires the Forge add-on. | Tenant | Call with \`{}\` or \`{"runner_id":"<runner-id>"}\`. |
|
|
45626
45648
|
|
|
45627
|
-
The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, runner capacity, configuration status, and Developer Team install. The install button deliberately stages a pending confirmation before durable seats, Skills, authority grants, or live agent activation are applied; the install page states plainly that approving activates the 6 agents at a pull-request-only, observe-first posture and surfaces the company autonomy ceiling.
|
|
45649
|
+
The web Forge control room at \`/forge\` uses the same command path for project selection, build request creation/list/detail reads, runner capacity, configuration status, and Developer Team install. A request detail page shows phase history, open gates, plan progress, loop/time/cost ceilings, and latest plan-review questions from \`software_factory.request.show\`; answering a clarification, approving a plan, or rejecting it calls the same \`software_factory.request.answer_clarification\`, \`software_factory.plan.approve\`, and \`software_factory.plan.reject\` commands exposed over CLI/MCP. The stop control is visible only as a non-executing placeholder until pause/cancel commands are wired, so the UI does not imply a capability that is not live. The install button deliberately stages a pending confirmation before durable seats, Skills, authority grants, or live agent activation are applied; the install page states plainly that approving activates the 6 agents at a pull-request-only, observe-first posture and surfaces the company autonomy ceiling.
|
|
45628
45650
|
|
|
45629
45651
|
#### Forge GitHub App access
|
|
45630
45652
|
|
|
@@ -46640,7 +46662,7 @@ Do not claim that a live charge happened unless Stripe test/live evidence proves
|
|
|
46640
46662
|
order: 72,
|
|
46641
46663
|
title: "Settings guide",
|
|
46642
46664
|
summary: "How to use Settings as the control plane for company access, channels, providers, tokens, and operating defaults.",
|
|
46643
|
-
version: "2026-07-
|
|
46665
|
+
version: "2026-07-04.1",
|
|
46644
46666
|
public: true,
|
|
46645
46667
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46646
46668
|
stages: ["company_setup", "staffing"],
|
|
@@ -46663,7 +46685,9 @@ Start with members and invites, then provider and channel connections, then MCP
|
|
|
46663
46685
|
|
|
46664
46686
|
## Navigating Settings
|
|
46665
46687
|
|
|
46666
|
-
Settings is a left-rail master\u2013detail layout: a grouped rail (General, Team & Access, Billing, AI & Agents, Connections) selects one section at a time, shown in the detail pane. The active section is a URL search param, so \`/settings?section=<id>\` is linkable and survives a refresh.
|
|
46688
|
+
Settings is a left-rail master\u2013detail layout: a grouped rail (General, Team & Access, Billing, AI & Agents, Connections) selects one section at a time, shown in the detail pane. The active section is a URL search param, so \`/settings?section=<id>\` is linkable and survives a refresh. The Connections section is the workspace-level provider surface: Google, Slack, QuickBooks, local runners, seat-scoped agent tokens, and stored secrets appear as provider cards with status, used-for chips, actions, and the shared Access language (**Off / Read / Draft / Act with approval**). Detailed management forms for tokens, channels, runners, provider links, and stored vault entries remain reachable from those cards.
|
|
46689
|
+
|
|
46690
|
+
Legacy hash deep-links still resolve \u2014 \`/settings#integrations\`, \`#channels\`, \`#runners\`, and \`#vault\` redirect to \`?section=connections\` while preserving any status params; other hashes such as \`#members\` and \`#guardrails\` redirect to their matching section. Documented shortcut links resolve to the right section instead of a dead end: \`/settings/integrations\` and \`/readiness\` open the Connections section (connector readiness lives inside it, not on a standalone page), and \`/settings/policy\` opens the Product learning section. After you save a change, the app keeps you on the section you edited rather than returning to the top.
|
|
46667
46691
|
|
|
46668
46692
|
## What belongs in Settings
|
|
46669
46693
|
|
|
@@ -46820,7 +46844,7 @@ Every notification should include the seat, cause, evidence, and requested decis
|
|
|
46820
46844
|
order: 75,
|
|
46821
46845
|
title: "Local runner guide",
|
|
46822
46846
|
summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
|
|
46823
|
-
version: "2026-07-04.
|
|
46847
|
+
version: "2026-07-04.4",
|
|
46824
46848
|
public: true,
|
|
46825
46849
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46826
46850
|
stages: ["staffing", "operating_rhythm"],
|
|
@@ -46864,6 +46888,8 @@ Headless and service accounts are supported first-class: when no usable OS keych
|
|
|
46864
46888
|
|
|
46865
46889
|
Choose the local runtime with \`--runtime auto|claude|codex\` (default \`auto\`). The same short-lived seat-scoped MCP token and server-side guard apply regardless of runtime; results are tagged \`claude-cli\` or \`codex-cli\`.
|
|
46866
46890
|
|
|
46891
|
+
For always-on execution, \`runner serve --execute\` heartbeats on its normal interval and also checks for claimable work between heartbeats. The default interactive claim cadence is about one second; set \`RUNNER_INTERACTIVE_CLAIM_MS\` or \`--interactive-claim-ms\` to tune it for a host. Claude-backed turns reuse a per-seat Claude Code resume session when Claude reports a session id, so later turns can run with \`claude --resume <session-id>\` under the local subscription account. The runner stores only the opaque resume id in its owner-only state file; it does not use API-billed managed sessions to make subscription runner turns faster. A resident long-lived Claude/Codex process is still prototype-gated, so do not assume a fixed latency SLA from this guide alone.
|
|
46892
|
+
|
|
46867
46893
|
The runner bearer secret is stored locally and is never displayed. Each claimed turn receives a short-lived seat-scoped MCP token from {{brand}}, and server-side guards still decide what tools the Seat may use.
|
|
46868
46894
|
|
|
46869
46895
|
## Interactive AICOS runner turns
|
|
@@ -51387,7 +51413,7 @@ function parseSemver2(value) {
|
|
|
51387
51413
|
// src/runner-serve.ts
|
|
51388
51414
|
import { execFile as execFileCallback3, spawn } from "node:child_process";
|
|
51389
51415
|
import { createHash } from "node:crypto";
|
|
51390
|
-
import { chmod, mkdir as mkdir2, readFile as readFile4, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
51416
|
+
import { chmod, mkdir as mkdir2, readFile as readFile4, rename, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
51391
51417
|
import { cpus, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
|
|
51392
51418
|
import path3 from "node:path";
|
|
51393
51419
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
@@ -51432,6 +51458,7 @@ ${usage()}
|
|
|
51432
51458
|
do {
|
|
51433
51459
|
const detectedCapabilities = await detectCapabilities();
|
|
51434
51460
|
const capabilities = capabilitiesForConfiguredRuntime(detectedCapabilities, config2.runtime);
|
|
51461
|
+
const localRuntime = config2.execute ? effectiveExecutionRuntime(config2.runtime, capabilities) : null;
|
|
51435
51462
|
try {
|
|
51436
51463
|
const heartbeat = await post2(fetchImpl, options.appUrl, "/api/runner/heartbeat", {
|
|
51437
51464
|
capabilities,
|
|
@@ -51446,15 +51473,12 @@ ${usage()}
|
|
|
51446
51473
|
options.io.stdout.write(`heartbeat ok #${beat} runner_id=${state.runner_id} claude=${capabilities.claude.installed} codex=${capabilities.codex.installed}
|
|
51447
51474
|
`);
|
|
51448
51475
|
}
|
|
51449
|
-
if (
|
|
51450
|
-
|
|
51451
|
-
|
|
51452
|
-
|
|
51453
|
-
|
|
51454
|
-
|
|
51455
|
-
} finally {
|
|
51456
|
-
activeSessions = Math.max(0, activeSessions - 1);
|
|
51457
|
-
}
|
|
51476
|
+
if (localRuntime) {
|
|
51477
|
+
activeSessions += 1;
|
|
51478
|
+
try {
|
|
51479
|
+
await claimAndExecute(fetchImpl, options.appUrl, state, config2, options.io, localRuntime);
|
|
51480
|
+
} finally {
|
|
51481
|
+
activeSessions = Math.max(0, activeSessions - 1);
|
|
51458
51482
|
}
|
|
51459
51483
|
}
|
|
51460
51484
|
} catch (error51) {
|
|
@@ -51464,11 +51488,27 @@ ${usage()}
|
|
|
51464
51488
|
}
|
|
51465
51489
|
beat += 1;
|
|
51466
51490
|
if (!config2.once) {
|
|
51467
|
-
await
|
|
51491
|
+
await waitForNextHeartbeat(fetchImpl, options.appUrl, state, config2, options.io, localRuntime);
|
|
51468
51492
|
}
|
|
51469
51493
|
} while (!config2.once);
|
|
51470
51494
|
return heartbeatSucceeded && !failed ? 0 : 1;
|
|
51471
51495
|
}
|
|
51496
|
+
async function waitForNextHeartbeat(fetchImpl, appUrl2, state, config2, io, runtime) {
|
|
51497
|
+
const deadline = Date.now() + config2.heartbeatMs;
|
|
51498
|
+
while (Date.now() < deadline) {
|
|
51499
|
+
const intervalMs = Math.min(config2.interactiveClaimMs, deadline - Date.now());
|
|
51500
|
+
await sleep2(intervalMs);
|
|
51501
|
+
if (!runtime || Date.now() >= deadline) {
|
|
51502
|
+
continue;
|
|
51503
|
+
}
|
|
51504
|
+
try {
|
|
51505
|
+
await claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime, { interactiveOnly: true });
|
|
51506
|
+
} catch (error51) {
|
|
51507
|
+
io.stderr.write(`transient runner error (continuing): ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
|
|
51508
|
+
`);
|
|
51509
|
+
}
|
|
51510
|
+
}
|
|
51511
|
+
}
|
|
51472
51512
|
function detectTelemetry(capabilities, env, activeSessions) {
|
|
51473
51513
|
const accountAlias = (env.RUNNER_MODEL_ACCOUNT_ALIAS ?? env.ROST_RUNNER_MODEL_ACCOUNT_ALIAS ?? "").trim() || "default";
|
|
51474
51514
|
const usageState = env.RUNNER_MODEL_ACCOUNT_STATE ?? env.ROST_RUNNER_MODEL_ACCOUNT_STATE ?? "unknown";
|
|
@@ -51490,6 +51530,7 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51490
51530
|
let name = env.RUNNER_NAME ?? `headless-${hostname3()}`;
|
|
51491
51531
|
let stateFile = env.RUNNER_STATE_FILE ?? path3.join(tmpdir(), `rost-runner-${createHash("sha1").update(appUrl2).digest("hex").slice(0, 8)}.json`);
|
|
51492
51532
|
let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
|
|
51533
|
+
let interactiveClaimMs = Number(env.RUNNER_INTERACTIVE_CLAIM_MS ?? 1e3);
|
|
51493
51534
|
let execute2 = env.RUNNER_EXECUTE === "1";
|
|
51494
51535
|
let once = false;
|
|
51495
51536
|
let runtime = parseRuntime(env.RUNNER_RUNTIME ?? "auto");
|
|
@@ -51505,6 +51546,9 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51505
51546
|
} else if (token === "--heartbeat-ms") {
|
|
51506
51547
|
heartbeatMs = Number(requiredValue(args, index));
|
|
51507
51548
|
index += 1;
|
|
51549
|
+
} else if (token === "--interactive-claim-ms") {
|
|
51550
|
+
interactiveClaimMs = Number(requiredValue(args, index));
|
|
51551
|
+
index += 1;
|
|
51508
51552
|
} else if (token === "--user-code") {
|
|
51509
51553
|
userCode = requiredValue(args, index);
|
|
51510
51554
|
index += 1;
|
|
@@ -51522,7 +51566,11 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51522
51566
|
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3) {
|
|
51523
51567
|
throw new Error("--heartbeat-ms must be an integer >= 1000.");
|
|
51524
51568
|
}
|
|
51525
|
-
|
|
51569
|
+
if (!Number.isSafeInteger(interactiveClaimMs) || interactiveClaimMs < 250) {
|
|
51570
|
+
throw new Error("--interactive-claim-ms must be an integer >= 250.");
|
|
51571
|
+
}
|
|
51572
|
+
interactiveClaimMs = Math.min(interactiveClaimMs, heartbeatMs);
|
|
51573
|
+
return { name, stateFile, heartbeatMs, interactiveClaimMs, execute: execute2, once, runtime, ...userCode ? { userCode } : {} };
|
|
51526
51574
|
}
|
|
51527
51575
|
function parseRuntime(value) {
|
|
51528
51576
|
if (value === "auto" || value === "claude" || value === "codex") {
|
|
@@ -51604,10 +51652,7 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
|
|
|
51604
51652
|
...typeof tenantId === "string" ? { tenant_id: tenantId } : config2.expectedTenantId ? { tenant_id: config2.expectedTenantId } : {},
|
|
51605
51653
|
name: typeof claim.json.name === "string" ? claim.json.name : config2.name
|
|
51606
51654
|
};
|
|
51607
|
-
await
|
|
51608
|
-
await writeFile2(config2.stateFile, `${JSON.stringify(state)}
|
|
51609
|
-
`, { mode: 384 });
|
|
51610
|
-
await chmod(config2.stateFile, 384);
|
|
51655
|
+
await saveState(config2.stateFile, state);
|
|
51611
51656
|
io.stdout.write(`paired runner_id=${runnerId} state_file=${config2.stateFile}
|
|
51612
51657
|
`);
|
|
51613
51658
|
return state;
|
|
@@ -51623,7 +51668,8 @@ async function loadState(filePath, expectedTenantId) {
|
|
|
51623
51668
|
runner_id: parsed.runner_id,
|
|
51624
51669
|
runner_secret: parsed.runner_secret,
|
|
51625
51670
|
...typeof parsed.tenant_id === "string" ? { tenant_id: parsed.tenant_id } : {},
|
|
51626
|
-
...typeof parsed.name === "string" ? { name: parsed.name } : {}
|
|
51671
|
+
...typeof parsed.name === "string" ? { name: parsed.name } : {},
|
|
51672
|
+
...isRuntimeSessionMap(parsed.runtime_sessions) ? { runtime_sessions: parsed.runtime_sessions } : {}
|
|
51627
51673
|
};
|
|
51628
51674
|
}
|
|
51629
51675
|
} catch {
|
|
@@ -51631,6 +51677,31 @@ async function loadState(filePath, expectedTenantId) {
|
|
|
51631
51677
|
}
|
|
51632
51678
|
return null;
|
|
51633
51679
|
}
|
|
51680
|
+
function isRuntimeSessionMap(value) {
|
|
51681
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
51682
|
+
return false;
|
|
51683
|
+
}
|
|
51684
|
+
return Object.values(value).every((entry) => {
|
|
51685
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
51686
|
+
return false;
|
|
51687
|
+
}
|
|
51688
|
+
const record2 = entry;
|
|
51689
|
+
return (record2.claude_session_id === void 0 || typeof record2.claude_session_id === "string") && (record2.updated_at === void 0 || typeof record2.updated_at === "string");
|
|
51690
|
+
});
|
|
51691
|
+
}
|
|
51692
|
+
async function saveState(filePath, state) {
|
|
51693
|
+
await mkdir2(path3.dirname(filePath), { recursive: true, mode: 448 });
|
|
51694
|
+
const tempPath = `${filePath}.${process.pid}.${createHash("sha1").update(`${Date.now()}:${Math.random()}`).digest("hex").slice(0, 8)}.tmp`;
|
|
51695
|
+
try {
|
|
51696
|
+
await writeFile2(tempPath, `${JSON.stringify(state)}
|
|
51697
|
+
`, { mode: 384 });
|
|
51698
|
+
await chmod(tempPath, 384);
|
|
51699
|
+
await rename(tempPath, filePath);
|
|
51700
|
+
} catch (error51) {
|
|
51701
|
+
await rm2(tempPath, { force: true });
|
|
51702
|
+
throw error51;
|
|
51703
|
+
}
|
|
51704
|
+
}
|
|
51634
51705
|
async function startPairing(client, config2) {
|
|
51635
51706
|
const response = await client.execute("runner.pairing.start", { name: config2.name, platform: process.platform });
|
|
51636
51707
|
const userCode = response.output && typeof response.output === "object" ? response.output.user_code : void 0;
|
|
@@ -51670,11 +51741,17 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
|
|
|
51670
51741
|
}
|
|
51671
51742
|
return { status: response.status, json: json2 };
|
|
51672
51743
|
}
|
|
51673
|
-
async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
51674
|
-
const claimed = await post2(
|
|
51744
|
+
async function claimAndExecute(fetchImpl, appUrl2, state, config2, io, runtime, options = {}) {
|
|
51745
|
+
const claimed = await post2(
|
|
51746
|
+
fetchImpl,
|
|
51747
|
+
appUrl2,
|
|
51748
|
+
"/api/runner/work-orders/claim",
|
|
51749
|
+
options.interactiveOnly ? { interactive_only: true } : {},
|
|
51750
|
+
state.runner_secret
|
|
51751
|
+
);
|
|
51675
51752
|
const turnExecution = claimed.json.turn_execution;
|
|
51676
51753
|
if (claimed.status === 200 && turnExecution && typeof turnExecution === "object") {
|
|
51677
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, {
|
|
51754
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, {
|
|
51678
51755
|
kind: "turn_execution",
|
|
51679
51756
|
idField: "turn_execution_id",
|
|
51680
51757
|
item: turnExecution,
|
|
@@ -51688,7 +51765,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
|
51688
51765
|
if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
|
|
51689
51766
|
return;
|
|
51690
51767
|
}
|
|
51691
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, {
|
|
51768
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, {
|
|
51692
51769
|
kind: "work_order",
|
|
51693
51770
|
idField: "work_order_id",
|
|
51694
51771
|
item: workOrder,
|
|
@@ -51697,7 +51774,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
|
51697
51774
|
logRef: (id) => `runner-local:${id}`
|
|
51698
51775
|
});
|
|
51699
51776
|
}
|
|
51700
|
-
async function executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, input) {
|
|
51777
|
+
async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, input) {
|
|
51701
51778
|
const id = input.item.id;
|
|
51702
51779
|
if (typeof id !== "string") {
|
|
51703
51780
|
return;
|
|
@@ -51707,10 +51784,22 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, input)
|
|
|
51707
51784
|
throw new Error(`${claimedUnitLabel(input.kind)} start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
|
|
51708
51785
|
}
|
|
51709
51786
|
const turnRuntime = runtimeForClaimedUnit(input.item, runtime, input.kind);
|
|
51710
|
-
const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind) : {
|
|
51787
|
+
const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind, state) : {
|
|
51711
51788
|
ok: false,
|
|
51712
51789
|
summary: `Runner runtime ${runtime} cannot execute requested ${String(input.item.brain ?? "unknown")} turn.`
|
|
51713
51790
|
};
|
|
51791
|
+
if (input.kind === "turn_execution" && turnRuntime === "claude") {
|
|
51792
|
+
try {
|
|
51793
|
+
if (result.ok && result.claudeSessionId) {
|
|
51794
|
+
await persistClaudeSessionId(config2.stateFile, state, input.item, result.claudeSessionId);
|
|
51795
|
+
} else if (!result.ok && result.resumedFromSessionId) {
|
|
51796
|
+
await clearClaudeSessionId(config2.stateFile, state, input.item);
|
|
51797
|
+
}
|
|
51798
|
+
} catch (error51) {
|
|
51799
|
+
io.stderr.write(`resume cache update failed (continuing): ${redactForLog(error51 instanceof Error ? error51.message : String(error51))}
|
|
51800
|
+
`);
|
|
51801
|
+
}
|
|
51802
|
+
}
|
|
51714
51803
|
const reported = await post2(fetchImpl, appUrl2, input.resultPath(id), {
|
|
51715
51804
|
status: result.ok ? "succeeded" : "failed",
|
|
51716
51805
|
summary: result.summary,
|
|
@@ -51735,16 +51824,16 @@ function runtimeForClaimedUnit(item, fallback, kind) {
|
|
|
51735
51824
|
}
|
|
51736
51825
|
return fallback === "claude" ? "claude" : null;
|
|
51737
51826
|
}
|
|
51738
|
-
function runLocalTurn(appUrl2, workOrder, runtime, kind) {
|
|
51739
|
-
return runtime === "codex" ? runCodexTurn(appUrl2, workOrder, kind) : runClaudeTurn(appUrl2, workOrder, kind);
|
|
51827
|
+
function runLocalTurn(appUrl2, workOrder, runtime, kind, state) {
|
|
51828
|
+
return runtime === "codex" ? runCodexTurn(appUrl2, workOrder, kind) : runClaudeTurn(appUrl2, workOrder, kind, state);
|
|
51740
51829
|
}
|
|
51741
|
-
function runClaudeTurn(appUrl2, workOrder, kind) {
|
|
51742
|
-
return spawnRunnerTurn(appUrl2, workOrder, "claude", kind);
|
|
51830
|
+
function runClaudeTurn(appUrl2, workOrder, kind, state) {
|
|
51831
|
+
return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, state);
|
|
51743
51832
|
}
|
|
51744
51833
|
function runCodexTurn(appUrl2, workOrder, kind) {
|
|
51745
51834
|
return spawnRunnerTurn(appUrl2, workOrder, "codex", kind);
|
|
51746
51835
|
}
|
|
51747
|
-
function buildTurnCommand(runtime, prompt, configPath, kind) {
|
|
51836
|
+
function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
|
|
51748
51837
|
if (runtime === "codex") {
|
|
51749
51838
|
return {
|
|
51750
51839
|
command: "codex",
|
|
@@ -51762,11 +51851,12 @@ function buildTurnCommand(runtime, prompt, configPath, kind) {
|
|
|
51762
51851
|
"--allowedTools",
|
|
51763
51852
|
(kind === "turn_execution" ? RUNNER_AICOS_TURN_TOOLS : RUNNER_WORK_ORDER_TOOLS).join(","),
|
|
51764
51853
|
"--output-format",
|
|
51765
|
-
kind === "turn_execution" ? "json" : "text"
|
|
51854
|
+
kind === "turn_execution" ? "json" : "text",
|
|
51855
|
+
...resumeSessionId ? ["--resume", resumeSessionId] : []
|
|
51766
51856
|
]
|
|
51767
51857
|
};
|
|
51768
51858
|
}
|
|
51769
|
-
async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
51859
|
+
async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
|
|
51770
51860
|
const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
|
|
51771
51861
|
const token = typeof mcp.token === "string" ? mcp.token : "";
|
|
51772
51862
|
const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
|
|
@@ -51800,7 +51890,9 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51800
51890
|
await writeFile2(configPath, `${mcpConfig}
|
|
51801
51891
|
`, { mode: 384 });
|
|
51802
51892
|
await chmod(configPath, 384);
|
|
51803
|
-
const
|
|
51893
|
+
const resumeSessionId = runtime === "claude" ? getClaudeResumeSessionId(state, workOrder) : void 0;
|
|
51894
|
+
const { command, args } = buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId);
|
|
51895
|
+
const expectSessionEnvelope = runtime === "claude" && kind === "turn_execution";
|
|
51804
51896
|
return new Promise((resolve2) => {
|
|
51805
51897
|
let settled = false;
|
|
51806
51898
|
const finish = (result) => {
|
|
@@ -51809,7 +51901,7 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51809
51901
|
}
|
|
51810
51902
|
settled = true;
|
|
51811
51903
|
void rm2(configPath, { force: true });
|
|
51812
|
-
resolve2(result);
|
|
51904
|
+
resolve2(resumeSessionId ? { ...result, resumedFromSessionId: resumeSessionId } : result);
|
|
51813
51905
|
};
|
|
51814
51906
|
const child = spawn(command, args, {
|
|
51815
51907
|
cwd: tmpdir(),
|
|
@@ -51829,7 +51921,12 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51829
51921
|
});
|
|
51830
51922
|
child.on("close", (code) => {
|
|
51831
51923
|
clearTimeout(timer);
|
|
51832
|
-
|
|
51924
|
+
const rawOutput = (out || err || "completed").trim();
|
|
51925
|
+
finish({
|
|
51926
|
+
ok: code === 0,
|
|
51927
|
+
summary: extractRunnerSummary(redactForLog(rawOutput)).slice(0, 12e3),
|
|
51928
|
+
...expectSessionEnvelope ? extractClaudeSessionId(rawOutput) : {}
|
|
51929
|
+
});
|
|
51833
51930
|
});
|
|
51834
51931
|
child.on("error", (error51) => {
|
|
51835
51932
|
clearTimeout(timer);
|
|
@@ -51855,8 +51952,83 @@ function extractRunnerSummary(output) {
|
|
|
51855
51952
|
}
|
|
51856
51953
|
return trimmed;
|
|
51857
51954
|
}
|
|
51955
|
+
function extractClaudeSessionId(output) {
|
|
51956
|
+
const trimmed = output.trim();
|
|
51957
|
+
if (!trimmed.startsWith("{")) {
|
|
51958
|
+
return {};
|
|
51959
|
+
}
|
|
51960
|
+
try {
|
|
51961
|
+
const parsed = JSON.parse(trimmed);
|
|
51962
|
+
const sessionId = parsed.session_id ?? parsed.sessionId;
|
|
51963
|
+
return typeof sessionId === "string" && sessionId.trim().length > 0 ? { claudeSessionId: sessionId.trim() } : {};
|
|
51964
|
+
} catch {
|
|
51965
|
+
return {};
|
|
51966
|
+
}
|
|
51967
|
+
}
|
|
51968
|
+
function runtimeSessionKey(state, workOrder) {
|
|
51969
|
+
const seatId = extractSeatId(workOrder);
|
|
51970
|
+
const userId = extractTurnUserId(workOrder);
|
|
51971
|
+
const sessionId = extractTurnSessionId(workOrder);
|
|
51972
|
+
if (!state?.tenant_id || !seatId || !userId || !sessionId) {
|
|
51973
|
+
return null;
|
|
51974
|
+
}
|
|
51975
|
+
return `${state.tenant_id}:seat:${seatId}:user:${userId}:session:${sessionId}:claude`;
|
|
51976
|
+
}
|
|
51977
|
+
function extractTurnUserId(workOrder) {
|
|
51978
|
+
const userId = workOrder.user_id;
|
|
51979
|
+
return typeof userId === "string" && userId.length > 0 ? userId : null;
|
|
51980
|
+
}
|
|
51981
|
+
function extractTurnSessionId(workOrder) {
|
|
51982
|
+
const topLevel = workOrder.session_id;
|
|
51983
|
+
if (typeof topLevel === "string" && topLevel.length > 0) {
|
|
51984
|
+
return topLevel;
|
|
51985
|
+
}
|
|
51986
|
+
const aicos = typeof workOrder.aicos === "object" && workOrder.aicos !== null ? workOrder.aicos : null;
|
|
51987
|
+
const sessionId = aicos?.session_id;
|
|
51988
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
|
|
51989
|
+
}
|
|
51990
|
+
function extractSeatId(workOrder) {
|
|
51991
|
+
const seat = typeof workOrder.seat === "object" && workOrder.seat !== null ? workOrder.seat : null;
|
|
51992
|
+
if (typeof seat?.id === "string" && seat.id.length > 0) {
|
|
51993
|
+
return seat.id;
|
|
51994
|
+
}
|
|
51995
|
+
const charterRef = typeof workOrder.charter_ref === "object" && workOrder.charter_ref !== null ? workOrder.charter_ref : null;
|
|
51996
|
+
return typeof charterRef?.seat_id === "string" && charterRef.seat_id.length > 0 ? charterRef.seat_id : null;
|
|
51997
|
+
}
|
|
51998
|
+
function getClaudeResumeSessionId(state, workOrder) {
|
|
51999
|
+
const key = runtimeSessionKey(state, workOrder);
|
|
52000
|
+
if (!key) {
|
|
52001
|
+
return void 0;
|
|
52002
|
+
}
|
|
52003
|
+
const sessionId = state?.runtime_sessions?.[key]?.claude_session_id;
|
|
52004
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
|
|
52005
|
+
}
|
|
52006
|
+
async function persistClaudeSessionId(stateFile, state, workOrder, claudeSessionId) {
|
|
52007
|
+
const key = runtimeSessionKey(state, workOrder);
|
|
52008
|
+
if (!key) {
|
|
52009
|
+
return;
|
|
52010
|
+
}
|
|
52011
|
+
state.runtime_sessions = {
|
|
52012
|
+
...state.runtime_sessions ?? {},
|
|
52013
|
+
[key]: {
|
|
52014
|
+
claude_session_id: claudeSessionId,
|
|
52015
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
52016
|
+
}
|
|
52017
|
+
};
|
|
52018
|
+
await saveState(stateFile, state);
|
|
52019
|
+
}
|
|
52020
|
+
async function clearClaudeSessionId(stateFile, state, workOrder) {
|
|
52021
|
+
const key = runtimeSessionKey(state, workOrder);
|
|
52022
|
+
if (!key || !state.runtime_sessions?.[key]) {
|
|
52023
|
+
return;
|
|
52024
|
+
}
|
|
52025
|
+
const nextSessions = { ...state.runtime_sessions };
|
|
52026
|
+
delete nextSessions[key];
|
|
52027
|
+
state.runtime_sessions = nextSessions;
|
|
52028
|
+
await saveState(stateFile, state);
|
|
52029
|
+
}
|
|
51858
52030
|
function usage() {
|
|
51859
|
-
return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--user-code <code>] [--runtime auto|claude|codex] [--once] [--execute]";
|
|
52031
|
+
return "Usage: rost runner serve [--name <text>] [--state-file <path>] [--heartbeat-ms <n>] [--interactive-claim-ms <n>] [--user-code <code>] [--runtime auto|claude|codex] [--once] [--execute]";
|
|
51860
52032
|
}
|
|
51861
52033
|
|
|
51862
52034
|
// src/runner-service.ts
|