@rosthq/cli 0.7.24 → 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/generator/render.d.ts.map +1 -1
- package/dist/index.js +282 -51
- 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
|
@@ -37718,6 +37718,19 @@ var dryRunToolActionSchema = external_exports.object({
|
|
|
37718
37718
|
// The underlying manifest guard decision that produced `status`.
|
|
37719
37719
|
guard: dryRunGuardResultSchema
|
|
37720
37720
|
}).strict();
|
|
37721
|
+
var dryRunLaneSchema = external_exports.enum(["cloud", "mcp_session", "runner"]);
|
|
37722
|
+
var dryRunToolExecutabilitySchema = external_exports.enum([
|
|
37723
|
+
"executable",
|
|
37724
|
+
"unimplemented_in_lane",
|
|
37725
|
+
"held_for_steward"
|
|
37726
|
+
]);
|
|
37727
|
+
var dryRunGrantedToolSchema = external_exports.object({
|
|
37728
|
+
// The granted tool (manifest id).
|
|
37729
|
+
tool_name: external_exports.string().min(1),
|
|
37730
|
+
// Its lane-aware executability, derived from the agent's lane + the runtime
|
|
37731
|
+
// handler registry (never an id string pattern).
|
|
37732
|
+
executability: dryRunToolExecutabilitySchema
|
|
37733
|
+
}).strict();
|
|
37721
37734
|
var dryRunTranscriptSchema = external_exports.object({
|
|
37722
37735
|
// Whether the rehearsal passed (the must-escalate clause was escalated and no
|
|
37723
37736
|
// external side effect ran) or failed (it acted without escalating, or the run
|
|
@@ -37736,6 +37749,14 @@ var dryRunTranscriptSchema = external_exports.object({
|
|
|
37736
37749
|
// handlers. Optional so the field is additive — a transcript without it (the
|
|
37737
37750
|
// static demo replay, an older record) still validates.
|
|
37738
37751
|
tool_preview: external_exports.array(dryRunToolActionSchema).optional(),
|
|
37752
|
+
// DER-1294: the runtime lane this rehearsal reflects. Optional/additive —
|
|
37753
|
+
// transcripts recorded before DER-1294 (and the static public-demo replay)
|
|
37754
|
+
// omit it, so they still validate.
|
|
37755
|
+
lane: dryRunLaneSchema.optional(),
|
|
37756
|
+
// DER-1294: every GRANTED tool (manifest allow|escalate) with its lane-aware
|
|
37757
|
+
// executability, so an operator sees which grants actually run vs. silently
|
|
37758
|
+
// no-op in this lane. Optional/additive so older transcripts still validate.
|
|
37759
|
+
granted_tools: external_exports.array(dryRunGrantedToolSchema).optional(),
|
|
37739
37760
|
// The underlying run id (present once the run is recorded).
|
|
37740
37761
|
run_id: external_exports.string().min(1).optional()
|
|
37741
37762
|
}).strict();
|
|
@@ -42457,6 +42478,10 @@ var softwareBuildRequestSummarySchema = external_exports.object({
|
|
|
42457
42478
|
current_phase: softwarePhaseSchema,
|
|
42458
42479
|
inferred_risk: softwareRiskLevelSchema,
|
|
42459
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(),
|
|
42460
42485
|
created_at: external_exports.string()
|
|
42461
42486
|
});
|
|
42462
42487
|
var softwarePhaseRunSummarySchema = external_exports.object({
|
|
@@ -42510,6 +42535,21 @@ var softwareTaskSummarySchema = external_exports.object({
|
|
|
42510
42535
|
completed_at: external_exports.string().nullable(),
|
|
42511
42536
|
created_at: external_exports.string()
|
|
42512
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
|
+
});
|
|
42513
42553
|
var softwarePlanConformanceFindingSummarySchema = external_exports.object({
|
|
42514
42554
|
id: uuid10,
|
|
42515
42555
|
build_request_id: uuid10,
|
|
@@ -42607,7 +42647,10 @@ var softwareRequestShowOutputSchema = external_exports.object({
|
|
|
42607
42647
|
request: softwareBuildRequestSummarySchema,
|
|
42608
42648
|
current_phase: softwarePhaseSchema,
|
|
42609
42649
|
phase_runs: external_exports.array(softwarePhaseRunSummarySchema),
|
|
42610
|
-
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)
|
|
42611
42654
|
});
|
|
42612
42655
|
var softwarePhaseAdvanceInputSchema = external_exports.object({
|
|
42613
42656
|
build_request_id: uuid10,
|
|
@@ -44367,7 +44410,7 @@ No orphan agents. No raw secrets in prompts, logs, or tool arguments. No durable
|
|
|
44367
44410
|
order: 41,
|
|
44368
44411
|
title: "Add agents to your Responsibility Graph",
|
|
44369
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.",
|
|
44370
|
-
version: "2026-
|
|
44413
|
+
version: "2026-07-04.1",
|
|
44371
44414
|
public: true,
|
|
44372
44415
|
audiences: ["human", "in_app_agent"],
|
|
44373
44416
|
stages: ["staffing"],
|
|
@@ -44416,8 +44459,8 @@ The wizard builds a teammate, not a prompt:
|
|
|
44416
44459
|
|
|
44417
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.
|
|
44418
44461
|
2. **Signals** \u2014 the measurables the seat is judged on.
|
|
44419
|
-
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
|
|
44420
|
-
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.
|
|
44421
44464
|
5. **Schedule and lane** \u2014 when the agent runs and on which lane. These stay advanced unless the mode needs them.
|
|
44422
44465
|
|
|
44423
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.
|
|
@@ -44427,7 +44470,7 @@ You can save and resume a draft setup at any point. Returning to the flow restor
|
|
|
44427
44470
|
Every mode ends at the same conservative gates, shown at each step:
|
|
44428
44471
|
|
|
44429
44472
|
1. **Draft Charter** \u2014 a draft, not an active job, until a human approves it.
|
|
44430
|
-
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.
|
|
44431
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.
|
|
44432
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.
|
|
44433
44476
|
|
|
@@ -44443,14 +44486,14 @@ Reopening the builder for a seat whose agent is already live shows its live stat
|
|
|
44443
44486
|
|
|
44444
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.
|
|
44445
44488
|
|
|
44446
|
-
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.`
|
|
44447
44490
|
},
|
|
44448
44491
|
{
|
|
44449
44492
|
slug: "custom-agents-guide",
|
|
44450
44493
|
order: 42,
|
|
44451
44494
|
title: "Design a custom agent",
|
|
44452
44495
|
summary: "How to build a custom agent from operational questions through the Charter Builder, tools, dry run, and go-live without writing prompts.",
|
|
44453
|
-
version: "2026-
|
|
44496
|
+
version: "2026-07-04.1",
|
|
44454
44497
|
public: true,
|
|
44455
44498
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44456
44499
|
stages: ["staffing", "charter_design"],
|
|
@@ -44517,7 +44560,7 @@ The same path is command-backed:
|
|
|
44517
44560
|
|
|
44518
44561
|
## Dry run and go-live
|
|
44519
44562
|
|
|
44520
|
-
The dry run is a real sandbox rehearsal, not a stamp. It executes a mock-provider run derived from the Charter \u2014 the agent works against sandbox data only and is expected to escalate where the Charter's must-escalate clause requires it. The same rehearsal works on all three lanes: cloud, external MCP, and local Runner. External MCP dry runs require an active seat-scoped MCP token; Runner dry runs require a paired Runner. Missing substrate returns a typed precondition error, not a generic failure. The recorded run keeps the agent's actual lane, so the evidence you review matches the lane you selected. The result is earned: a run that escalates the must-escalate boundary passes; a run that acts on that boundary instead of escalating fails. A failed dry run keeps the draft and shows the reason so you can edit and rerun. The rehearsal returns a transcript \u2014 the steps the agent took and the escalation it raised \u2014 shown step by step in the builder and printed by the CLI, so you can see the governance model working before anything goes live. When the dry run passes, a human promotes the agent live. The dry run rehearses the specific model tier you chose, so once it passes the model is locked \u2014 changing the model requires re-running the dry run on the new model before go-live.
|
|
44563
|
+
The dry run is a real sandbox rehearsal, not a stamp. It executes a mock-provider run derived from the Charter \u2014 the agent works against sandbox data only and is expected to escalate where the Charter's must-escalate clause requires it. The same rehearsal works on all three lanes: cloud, external MCP, and local Runner. External MCP dry runs require an active seat-scoped MCP token; Runner dry runs require a paired Runner. Missing substrate returns a typed precondition error, not a generic failure. The recorded run keeps the agent's actual lane, so the evidence you review matches the lane you selected. The result is earned: a run that escalates the must-escalate boundary passes; a run that acts on that boundary instead of escalating fails. A failed dry run keeps the draft and shows the reason so you can edit and rerun. The rehearsal returns a transcript \u2014 the steps the agent took and the escalation it raised \u2014 shown step by step in the builder and printed by the CLI, so you can see the governance model working before anything goes live. The transcript is also lane-honest: each granted tool is marked executable, held for the steward, or granted but unimplemented in this lane, with a plain-count verdict such as "2 of 6 granted tools don't execute in the cloud lane yet" \u2014 so a grant that would silently no-op in the cloud runtime is visible before go-live instead of passing green. Runner and MCP-session agents run those tools through their own local skills, so their grants are not flagged as unimplemented. When the dry run passes, a human promotes the agent live. The dry run rehearses the specific model tier you chose, so once it passes the model is locked \u2014 changing the model requires re-running the dry run on the new model before go-live.
|
|
44521
44564
|
|
|
44522
44565
|
## When to stop for confirmation
|
|
44523
44566
|
|
|
@@ -44860,7 +44903,7 @@ External connectors are being rolled out provider by provider, conservatively (r
|
|
|
44860
44903
|
order: 48,
|
|
44861
44904
|
title: "CLI and MCP installation guide",
|
|
44862
44905
|
summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
|
|
44863
|
-
version: "2026-07-04.
|
|
44906
|
+
version: "2026-07-04.17",
|
|
44864
44907
|
public: true,
|
|
44865
44908
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
44866
44909
|
stages: ["company_setup", "staffing"],
|
|
@@ -45603,7 +45646,7 @@ Several rows here are seat-operating commands (\`task.create\`, the \`signal.*\`
|
|
|
45603
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. |
|
|
45604
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>"}\`. |
|
|
45605
45648
|
|
|
45606
|
-
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.
|
|
45607
45650
|
|
|
45608
45651
|
#### Forge GitHub App access
|
|
45609
45652
|
|
|
@@ -46089,7 +46132,7 @@ Retrieve the narrowest relevant guide before making a setup recommendation. Pref
|
|
|
46089
46132
|
order: 60,
|
|
46090
46133
|
title: "Cascade guide",
|
|
46091
46134
|
summary: "How to connect company goals to seat-level work without turning {{brand}} into a project-management tool.",
|
|
46092
|
-
version: "2026-07-04.
|
|
46135
|
+
version: "2026-07-04.2",
|
|
46093
46136
|
public: true,
|
|
46094
46137
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46095
46138
|
stages: ["operating_rhythm"],
|
|
@@ -46157,7 +46200,7 @@ Cascade warnings are advisory, not blockers. They point to setup that should be
|
|
|
46157
46200
|
- Agent goal missing human accountability parent: an agent-owned goal has no human or hybrid goal in its ancestry.
|
|
46158
46201
|
- Orphaned goal branch: the owner seat or parent chain no longer reaches an active Compass objective.
|
|
46159
46202
|
|
|
46160
|
-
|
|
46203
|
+
\`/cascade\` leads with one computed line \u2014 how many goals are off pace this cycle, or an all-clear when none are. Off-pace and off-track goals stay fully visible with their owner seat and pace context; goals confirmed on pace collapse into a per-branch count you can expand. Creating or editing a goal opens the same drawer from a single **New goal** action or a goal's own edit control \u2014 there is no separate persistent form.
|
|
46161
46204
|
|
|
46162
46205
|
## Goal progress and at-risk goals
|
|
46163
46206
|
|
|
@@ -46304,7 +46347,7 @@ Agents may record readings when the Charter allows it. Agent-reported readings s
|
|
|
46304
46347
|
order: 62,
|
|
46305
46348
|
title: "Friction guide",
|
|
46306
46349
|
summary: "How to capture issues with evidence, rank them, and resolve them without losing ownership.",
|
|
46307
|
-
version: "2026-07-
|
|
46350
|
+
version: "2026-07-04.1",
|
|
46308
46351
|
public: true,
|
|
46309
46352
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46310
46353
|
stages: ["operating_rhythm"],
|
|
@@ -46356,6 +46399,8 @@ Agents should file Friction when a measurable breaks, a tool fails, a repeated e
|
|
|
46356
46399
|
|
|
46357
46400
|
In the app, operational evidence is summarized for humans instead of shown as the primary interface. Incident fields, proposed tool calls, schedules, payload labels, and source links render as readable fields with raw details tucked behind supporting context when useful. Secret-shaped values stay redacted.
|
|
46358
46401
|
|
|
46402
|
+
The Friction page opens with one computed line \u2014 how many issues are open, the oldest one's age, and how many are escalated \u2014 followed by the open issues themselves as incident-ticket rows (filer, one-line cause, an escalation chip naming the steward when severity is critical). Resolved issues collapse into a single count that expands into a quiet history list. Filing a new issue and the three-step resolution (root cause, owner task, done-by) both live behind one "File friction" action, which opens a drawer; resolving launches that same drawer already seeded from the row you clicked "Resolve" on.
|
|
46403
|
+
|
|
46359
46404
|
## Operate Friction, tasks, and escalations from CLI or MCP
|
|
46360
46405
|
|
|
46361
46406
|
Friction, tasks, and escalations are the issue-to-action loop. A seat files, a task carries the work, and an escalation routes a decision a seat cannot make alone.
|
|
@@ -46617,7 +46662,7 @@ Do not claim that a live charge happened unless Stripe test/live evidence proves
|
|
|
46617
46662
|
order: 72,
|
|
46618
46663
|
title: "Settings guide",
|
|
46619
46664
|
summary: "How to use Settings as the control plane for company access, channels, providers, tokens, and operating defaults.",
|
|
46620
|
-
version: "2026-07-
|
|
46665
|
+
version: "2026-07-04.1",
|
|
46621
46666
|
public: true,
|
|
46622
46667
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46623
46668
|
stages: ["company_setup", "staffing"],
|
|
@@ -46640,7 +46685,9 @@ Start with members and invites, then provider and channel connections, then MCP
|
|
|
46640
46685
|
|
|
46641
46686
|
## Navigating Settings
|
|
46642
46687
|
|
|
46643
|
-
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.
|
|
46644
46691
|
|
|
46645
46692
|
## What belongs in Settings
|
|
46646
46693
|
|
|
@@ -46797,7 +46844,7 @@ Every notification should include the seat, cause, evidence, and requested decis
|
|
|
46797
46844
|
order: 75,
|
|
46798
46845
|
title: "Local runner guide",
|
|
46799
46846
|
summary: "How local agent sessions and runner surfaces should operate through {{brand}} without bypassing Charters or audit.",
|
|
46800
|
-
version: "2026-07-04.
|
|
46847
|
+
version: "2026-07-04.4",
|
|
46801
46848
|
public: true,
|
|
46802
46849
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
46803
46850
|
stages: ["staffing", "operating_rhythm"],
|
|
@@ -46841,6 +46888,8 @@ Headless and service accounts are supported first-class: when no usable OS keych
|
|
|
46841
46888
|
|
|
46842
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\`.
|
|
46843
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
|
+
|
|
46844
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.
|
|
46845
46894
|
|
|
46846
46895
|
## Interactive AICOS runner turns
|
|
@@ -49113,6 +49162,36 @@ function dryRunToolActionLabel(status) {
|
|
|
49113
49162
|
return status;
|
|
49114
49163
|
}
|
|
49115
49164
|
}
|
|
49165
|
+
var DRY_RUN_LANE_LABELS = {
|
|
49166
|
+
cloud: "cloud lane",
|
|
49167
|
+
runner: "runner lane",
|
|
49168
|
+
mcp_session: "MCP-session lane"
|
|
49169
|
+
};
|
|
49170
|
+
function dryRunLaneLabel(lane) {
|
|
49171
|
+
return DRY_RUN_LANE_LABELS[lane] ?? `${lane} lane`;
|
|
49172
|
+
}
|
|
49173
|
+
function dryRunLaneVerdict(lane, grantedCount, unimplementedCount) {
|
|
49174
|
+
const laneLabel = dryRunLaneLabel(lane);
|
|
49175
|
+
if (unimplementedCount === 0) {
|
|
49176
|
+
return grantedCount === 1 ? `The single granted tool is implemented in the ${laneLabel}.` : `All ${grantedCount} granted tools are implemented in the ${laneLabel}.`;
|
|
49177
|
+
}
|
|
49178
|
+
const verb = unimplementedCount === 1 ? "doesn't" : "don't";
|
|
49179
|
+
return `${unimplementedCount} of ${grantedCount} granted tools ${verb} execute in the ${laneLabel} yet.`;
|
|
49180
|
+
}
|
|
49181
|
+
function dryRunLaneExecutabilityLines(transcript) {
|
|
49182
|
+
const lane = transcript.lane;
|
|
49183
|
+
const grantedTools = asArray(transcript.granted_tools);
|
|
49184
|
+
if (typeof lane !== "string" || grantedTools.length === 0) {
|
|
49185
|
+
return [];
|
|
49186
|
+
}
|
|
49187
|
+
const laneLabel = dryRunLaneLabel(lane);
|
|
49188
|
+
const unimplemented = grantedTools.map((tool) => asRecord(tool)).filter((tool) => tool.executability === "unimplemented_in_lane");
|
|
49189
|
+
const warningLines = unimplemented.map(
|
|
49190
|
+
(tool) => ` warning: ${field(tool, "tool_name")} is granted but unimplemented in the ${laneLabel}`
|
|
49191
|
+
);
|
|
49192
|
+
const verdict = dryRunLaneVerdict(lane, grantedTools.length, unimplemented.length);
|
|
49193
|
+
return ["lane executability:", ...warningLines, ` verdict: ${verdict}`];
|
|
49194
|
+
}
|
|
49116
49195
|
function formatDryRunTranscript(value) {
|
|
49117
49196
|
const transcript = asRecord(value);
|
|
49118
49197
|
const steps = asArray(transcript.steps);
|
|
@@ -49129,7 +49208,13 @@ function formatDryRunTranscript(value) {
|
|
|
49129
49208
|
const record2 = asRecord(action);
|
|
49130
49209
|
return ` ${dryRunToolActionLabel(field(record2, "status"))}: ${field(record2, "intended_action")} [${field(record2, "tool_name")}]`;
|
|
49131
49210
|
});
|
|
49132
|
-
|
|
49211
|
+
const laneLines = dryRunLaneExecutabilityLines(transcript);
|
|
49212
|
+
return [
|
|
49213
|
+
"rehearsal:",
|
|
49214
|
+
...lines,
|
|
49215
|
+
...toolPreview.length > 0 ? ["tool preview:", ...toolPreview] : [],
|
|
49216
|
+
...laneLines
|
|
49217
|
+
].join("\n");
|
|
49133
49218
|
}
|
|
49134
49219
|
function isScalar(value) {
|
|
49135
49220
|
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
@@ -51328,7 +51413,7 @@ function parseSemver2(value) {
|
|
|
51328
51413
|
// src/runner-serve.ts
|
|
51329
51414
|
import { execFile as execFileCallback3, spawn } from "node:child_process";
|
|
51330
51415
|
import { createHash } from "node:crypto";
|
|
51331
|
-
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";
|
|
51332
51417
|
import { cpus, hostname as hostname3, platform, totalmem, tmpdir } from "node:os";
|
|
51333
51418
|
import path3 from "node:path";
|
|
51334
51419
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
@@ -51373,6 +51458,7 @@ ${usage()}
|
|
|
51373
51458
|
do {
|
|
51374
51459
|
const detectedCapabilities = await detectCapabilities();
|
|
51375
51460
|
const capabilities = capabilitiesForConfiguredRuntime(detectedCapabilities, config2.runtime);
|
|
51461
|
+
const localRuntime = config2.execute ? effectiveExecutionRuntime(config2.runtime, capabilities) : null;
|
|
51376
51462
|
try {
|
|
51377
51463
|
const heartbeat = await post2(fetchImpl, options.appUrl, "/api/runner/heartbeat", {
|
|
51378
51464
|
capabilities,
|
|
@@ -51387,15 +51473,12 @@ ${usage()}
|
|
|
51387
51473
|
options.io.stdout.write(`heartbeat ok #${beat} runner_id=${state.runner_id} claude=${capabilities.claude.installed} codex=${capabilities.codex.installed}
|
|
51388
51474
|
`);
|
|
51389
51475
|
}
|
|
51390
|
-
if (
|
|
51391
|
-
|
|
51392
|
-
|
|
51393
|
-
|
|
51394
|
-
|
|
51395
|
-
|
|
51396
|
-
} finally {
|
|
51397
|
-
activeSessions = Math.max(0, activeSessions - 1);
|
|
51398
|
-
}
|
|
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);
|
|
51399
51482
|
}
|
|
51400
51483
|
}
|
|
51401
51484
|
} catch (error51) {
|
|
@@ -51405,11 +51488,27 @@ ${usage()}
|
|
|
51405
51488
|
}
|
|
51406
51489
|
beat += 1;
|
|
51407
51490
|
if (!config2.once) {
|
|
51408
|
-
await
|
|
51491
|
+
await waitForNextHeartbeat(fetchImpl, options.appUrl, state, config2, options.io, localRuntime);
|
|
51409
51492
|
}
|
|
51410
51493
|
} while (!config2.once);
|
|
51411
51494
|
return heartbeatSucceeded && !failed ? 0 : 1;
|
|
51412
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
|
+
}
|
|
51413
51512
|
function detectTelemetry(capabilities, env, activeSessions) {
|
|
51414
51513
|
const accountAlias = (env.RUNNER_MODEL_ACCOUNT_ALIAS ?? env.ROST_RUNNER_MODEL_ACCOUNT_ALIAS ?? "").trim() || "default";
|
|
51415
51514
|
const usageState = env.RUNNER_MODEL_ACCOUNT_STATE ?? env.ROST_RUNNER_MODEL_ACCOUNT_STATE ?? "unknown";
|
|
@@ -51431,6 +51530,7 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51431
51530
|
let name = env.RUNNER_NAME ?? `headless-${hostname3()}`;
|
|
51432
51531
|
let stateFile = env.RUNNER_STATE_FILE ?? path3.join(tmpdir(), `rost-runner-${createHash("sha1").update(appUrl2).digest("hex").slice(0, 8)}.json`);
|
|
51433
51532
|
let heartbeatMs = Number(env.RUNNER_HEARTBEAT_MS ?? 3e4);
|
|
51533
|
+
let interactiveClaimMs = Number(env.RUNNER_INTERACTIVE_CLAIM_MS ?? 1e3);
|
|
51434
51534
|
let execute2 = env.RUNNER_EXECUTE === "1";
|
|
51435
51535
|
let once = false;
|
|
51436
51536
|
let runtime = parseRuntime(env.RUNNER_RUNTIME ?? "auto");
|
|
@@ -51446,6 +51546,9 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51446
51546
|
} else if (token === "--heartbeat-ms") {
|
|
51447
51547
|
heartbeatMs = Number(requiredValue(args, index));
|
|
51448
51548
|
index += 1;
|
|
51549
|
+
} else if (token === "--interactive-claim-ms") {
|
|
51550
|
+
interactiveClaimMs = Number(requiredValue(args, index));
|
|
51551
|
+
index += 1;
|
|
51449
51552
|
} else if (token === "--user-code") {
|
|
51450
51553
|
userCode = requiredValue(args, index);
|
|
51451
51554
|
index += 1;
|
|
@@ -51463,7 +51566,11 @@ function parseServeArgs(args, appUrl2, env) {
|
|
|
51463
51566
|
if (!Number.isSafeInteger(heartbeatMs) || heartbeatMs < 1e3) {
|
|
51464
51567
|
throw new Error("--heartbeat-ms must be an integer >= 1000.");
|
|
51465
51568
|
}
|
|
51466
|
-
|
|
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 } : {} };
|
|
51467
51574
|
}
|
|
51468
51575
|
function parseRuntime(value) {
|
|
51469
51576
|
if (value === "auto" || value === "claude" || value === "codex") {
|
|
@@ -51545,10 +51652,7 @@ async function pairIfNeeded(client, fetchImpl, appUrl2, config2, io) {
|
|
|
51545
51652
|
...typeof tenantId === "string" ? { tenant_id: tenantId } : config2.expectedTenantId ? { tenant_id: config2.expectedTenantId } : {},
|
|
51546
51653
|
name: typeof claim.json.name === "string" ? claim.json.name : config2.name
|
|
51547
51654
|
};
|
|
51548
|
-
await
|
|
51549
|
-
await writeFile2(config2.stateFile, `${JSON.stringify(state)}
|
|
51550
|
-
`, { mode: 384 });
|
|
51551
|
-
await chmod(config2.stateFile, 384);
|
|
51655
|
+
await saveState(config2.stateFile, state);
|
|
51552
51656
|
io.stdout.write(`paired runner_id=${runnerId} state_file=${config2.stateFile}
|
|
51553
51657
|
`);
|
|
51554
51658
|
return state;
|
|
@@ -51564,7 +51668,8 @@ async function loadState(filePath, expectedTenantId) {
|
|
|
51564
51668
|
runner_id: parsed.runner_id,
|
|
51565
51669
|
runner_secret: parsed.runner_secret,
|
|
51566
51670
|
...typeof parsed.tenant_id === "string" ? { tenant_id: parsed.tenant_id } : {},
|
|
51567
|
-
...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 } : {}
|
|
51568
51673
|
};
|
|
51569
51674
|
}
|
|
51570
51675
|
} catch {
|
|
@@ -51572,6 +51677,31 @@ async function loadState(filePath, expectedTenantId) {
|
|
|
51572
51677
|
}
|
|
51573
51678
|
return null;
|
|
51574
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
|
+
}
|
|
51575
51705
|
async function startPairing(client, config2) {
|
|
51576
51706
|
const response = await client.execute("runner.pairing.start", { name: config2.name, platform: process.platform });
|
|
51577
51707
|
const userCode = response.output && typeof response.output === "object" ? response.output.user_code : void 0;
|
|
@@ -51611,11 +51741,17 @@ async function post2(fetchImpl, appUrl2, pathName, body, token) {
|
|
|
51611
51741
|
}
|
|
51612
51742
|
return { status: response.status, json: json2 };
|
|
51613
51743
|
}
|
|
51614
|
-
async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
51615
|
-
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
|
+
);
|
|
51616
51752
|
const turnExecution = claimed.json.turn_execution;
|
|
51617
51753
|
if (claimed.status === 200 && turnExecution && typeof turnExecution === "object") {
|
|
51618
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, {
|
|
51754
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, {
|
|
51619
51755
|
kind: "turn_execution",
|
|
51620
51756
|
idField: "turn_execution_id",
|
|
51621
51757
|
item: turnExecution,
|
|
@@ -51629,7 +51765,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
|
51629
51765
|
if (claimed.status !== 200 || !workOrder || typeof workOrder !== "object") {
|
|
51630
51766
|
return;
|
|
51631
51767
|
}
|
|
51632
|
-
await executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, {
|
|
51768
|
+
await executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, {
|
|
51633
51769
|
kind: "work_order",
|
|
51634
51770
|
idField: "work_order_id",
|
|
51635
51771
|
item: workOrder,
|
|
@@ -51638,7 +51774,7 @@ async function claimAndExecute(fetchImpl, appUrl2, state, io, runtime) {
|
|
|
51638
51774
|
logRef: (id) => `runner-local:${id}`
|
|
51639
51775
|
});
|
|
51640
51776
|
}
|
|
51641
|
-
async function executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, input) {
|
|
51777
|
+
async function executeClaimedUnit(fetchImpl, appUrl2, state, config2, io, runtime, input) {
|
|
51642
51778
|
const id = input.item.id;
|
|
51643
51779
|
if (typeof id !== "string") {
|
|
51644
51780
|
return;
|
|
@@ -51648,10 +51784,22 @@ async function executeClaimedUnit(fetchImpl, appUrl2, state, io, runtime, input)
|
|
|
51648
51784
|
throw new Error(`${claimedUnitLabel(input.kind)} start failed ${started.status}: ${redactForLog(JSON.stringify(started.json))}`);
|
|
51649
51785
|
}
|
|
51650
51786
|
const turnRuntime = runtimeForClaimedUnit(input.item, runtime, input.kind);
|
|
51651
|
-
const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind) : {
|
|
51787
|
+
const result = turnRuntime ? await runLocalTurn(appUrl2, input.item, turnRuntime, input.kind, state) : {
|
|
51652
51788
|
ok: false,
|
|
51653
51789
|
summary: `Runner runtime ${runtime} cannot execute requested ${String(input.item.brain ?? "unknown")} turn.`
|
|
51654
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
|
+
}
|
|
51655
51803
|
const reported = await post2(fetchImpl, appUrl2, input.resultPath(id), {
|
|
51656
51804
|
status: result.ok ? "succeeded" : "failed",
|
|
51657
51805
|
summary: result.summary,
|
|
@@ -51676,16 +51824,16 @@ function runtimeForClaimedUnit(item, fallback, kind) {
|
|
|
51676
51824
|
}
|
|
51677
51825
|
return fallback === "claude" ? "claude" : null;
|
|
51678
51826
|
}
|
|
51679
|
-
function runLocalTurn(appUrl2, workOrder, runtime, kind) {
|
|
51680
|
-
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);
|
|
51681
51829
|
}
|
|
51682
|
-
function runClaudeTurn(appUrl2, workOrder, kind) {
|
|
51683
|
-
return spawnRunnerTurn(appUrl2, workOrder, "claude", kind);
|
|
51830
|
+
function runClaudeTurn(appUrl2, workOrder, kind, state) {
|
|
51831
|
+
return spawnRunnerTurn(appUrl2, workOrder, "claude", kind, state);
|
|
51684
51832
|
}
|
|
51685
51833
|
function runCodexTurn(appUrl2, workOrder, kind) {
|
|
51686
51834
|
return spawnRunnerTurn(appUrl2, workOrder, "codex", kind);
|
|
51687
51835
|
}
|
|
51688
|
-
function buildTurnCommand(runtime, prompt, configPath, kind) {
|
|
51836
|
+
function buildTurnCommand(runtime, prompt, configPath, kind, resumeSessionId) {
|
|
51689
51837
|
if (runtime === "codex") {
|
|
51690
51838
|
return {
|
|
51691
51839
|
command: "codex",
|
|
@@ -51703,11 +51851,12 @@ function buildTurnCommand(runtime, prompt, configPath, kind) {
|
|
|
51703
51851
|
"--allowedTools",
|
|
51704
51852
|
(kind === "turn_execution" ? RUNNER_AICOS_TURN_TOOLS : RUNNER_WORK_ORDER_TOOLS).join(","),
|
|
51705
51853
|
"--output-format",
|
|
51706
|
-
kind === "turn_execution" ? "json" : "text"
|
|
51854
|
+
kind === "turn_execution" ? "json" : "text",
|
|
51855
|
+
...resumeSessionId ? ["--resume", resumeSessionId] : []
|
|
51707
51856
|
]
|
|
51708
51857
|
};
|
|
51709
51858
|
}
|
|
51710
|
-
async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
51859
|
+
async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind, state) {
|
|
51711
51860
|
const mcp = typeof workOrder.mcp === "object" && workOrder.mcp !== null ? workOrder.mcp : {};
|
|
51712
51861
|
const token = typeof mcp.token === "string" ? mcp.token : "";
|
|
51713
51862
|
const url2 = typeof mcp.url === "string" ? `${appUrl2.replace(/\/+$/, "")}${mcp.url}` : `${appUrl2.replace(/\/+$/, "")}/mcp`;
|
|
@@ -51741,7 +51890,9 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51741
51890
|
await writeFile2(configPath, `${mcpConfig}
|
|
51742
51891
|
`, { mode: 384 });
|
|
51743
51892
|
await chmod(configPath, 384);
|
|
51744
|
-
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";
|
|
51745
51896
|
return new Promise((resolve2) => {
|
|
51746
51897
|
let settled = false;
|
|
51747
51898
|
const finish = (result) => {
|
|
@@ -51750,7 +51901,7 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51750
51901
|
}
|
|
51751
51902
|
settled = true;
|
|
51752
51903
|
void rm2(configPath, { force: true });
|
|
51753
|
-
resolve2(result);
|
|
51904
|
+
resolve2(resumeSessionId ? { ...result, resumedFromSessionId: resumeSessionId } : result);
|
|
51754
51905
|
};
|
|
51755
51906
|
const child = spawn(command, args, {
|
|
51756
51907
|
cwd: tmpdir(),
|
|
@@ -51770,7 +51921,12 @@ async function spawnRunnerTurn(appUrl2, workOrder, runtime, kind) {
|
|
|
51770
51921
|
});
|
|
51771
51922
|
child.on("close", (code) => {
|
|
51772
51923
|
clearTimeout(timer);
|
|
51773
|
-
|
|
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
|
+
});
|
|
51774
51930
|
});
|
|
51775
51931
|
child.on("error", (error51) => {
|
|
51776
51932
|
clearTimeout(timer);
|
|
@@ -51796,8 +51952,83 @@ function extractRunnerSummary(output) {
|
|
|
51796
51952
|
}
|
|
51797
51953
|
return trimmed;
|
|
51798
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
|
+
}
|
|
51799
52030
|
function usage() {
|
|
51800
|
-
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]";
|
|
51801
52032
|
}
|
|
51802
52033
|
|
|
51803
52034
|
// src/runner-service.ts
|