@cabane/companion 0.6.186 → 0.6.188
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/README.md +16 -10
- package/dist/cli.js +599 -628
- package/dist/launch.js +1 -1
- package/dist/pairing-config.js +1 -1
- package/dist/runtime.js +408 -522
- package/dist/static/app.js +1 -2
- package/dist/static/styles.css +0 -7
- package/package.json +2 -1
package/dist/runtime.js
CHANGED
|
@@ -230,7 +230,7 @@ var localAgentConfigSchema = z2.object({
|
|
|
230
230
|
// yourself, `claudeCode: { autoMemory: true }` hands auto-memory back to your
|
|
231
231
|
// own `~/.claude/settings.json` — Cabane then stops
|
|
232
232
|
// injecting the off switch and your normal Claude Code memory workflow applies
|
|
233
|
-
// (
|
|
233
|
+
// (the user and project settings load on every Claude Code turn).
|
|
234
234
|
claudeCode: z2.object({ autoMemory: z2.boolean().optional() }).strict().optional()
|
|
235
235
|
}).strict();
|
|
236
236
|
var agentsConfigSchema = z2.record(z2.string(), localAgentConfigSchema);
|
|
@@ -847,7 +847,6 @@ var CompanionStateHub = class {
|
|
|
847
847
|
agent_id: a.agentId,
|
|
848
848
|
username: a.username,
|
|
849
849
|
display_name: a.displayName,
|
|
850
|
-
mode: a.mode,
|
|
851
850
|
has_credential: a.hasCredential,
|
|
852
851
|
missing_secrets: a.missingSecrets
|
|
853
852
|
}))
|
|
@@ -2345,14 +2344,19 @@ var DeviceApi = class {
|
|
|
2345
2344
|
return parsed;
|
|
2346
2345
|
}
|
|
2347
2346
|
// Pull the agents this device should run, across all the owner's workspaces.
|
|
2348
|
-
getAssignments() {
|
|
2349
|
-
return this.request("GET", "/api/companion/assignments");
|
|
2347
|
+
getAssignments(signal) {
|
|
2348
|
+
return this.request("GET", "/api/companion/assignments", void 0, requestSignal(signal));
|
|
2350
2349
|
}
|
|
2351
2350
|
// Ask again for the credential of an agent assigned to this device, when the saved copy is
|
|
2352
2351
|
// gone. The previous credential stops working; any agent not assigned here is a 404, and a
|
|
2353
2352
|
// second ask inside a minute a 429.
|
|
2354
|
-
reissueAgentCredential(agentId) {
|
|
2355
|
-
return this.request(
|
|
2353
|
+
reissueAgentCredential(agentId, signal) {
|
|
2354
|
+
return this.request(
|
|
2355
|
+
"POST",
|
|
2356
|
+
`/api/companion/agents/${encodeURIComponent(agentId)}/credential`,
|
|
2357
|
+
void 0,
|
|
2358
|
+
requestSignal(signal, 3e4)
|
|
2359
|
+
);
|
|
2356
2360
|
}
|
|
2357
2361
|
getRuns(cursor) {
|
|
2358
2362
|
const query = new URLSearchParams({ limit: "100", order: "asc" });
|
|
@@ -2392,6 +2396,10 @@ function errorMessage2(status, body) {
|
|
|
2392
2396
|
if (typeof body === "string" && body.length > 0) return `${status} ${body.slice(0, 200)}`;
|
|
2393
2397
|
return `${status} error`;
|
|
2394
2398
|
}
|
|
2399
|
+
function requestSignal(signal, timeoutMs = 5e3) {
|
|
2400
|
+
const deadline = AbortSignal.timeout(timeoutMs);
|
|
2401
|
+
return signal ? AbortSignal.any([signal, deadline]) : deadline;
|
|
2402
|
+
}
|
|
2395
2403
|
|
|
2396
2404
|
// src/credentials.ts
|
|
2397
2405
|
import {
|
|
@@ -2620,35 +2628,9 @@ function noResume() {
|
|
|
2620
2628
|
// packages/agent-runtime/src/version.ts
|
|
2621
2629
|
var TURN_PROTOCOL_VERSION = 1;
|
|
2622
2630
|
|
|
2623
|
-
// packages/agent-runtime/src/host-policy.ts
|
|
2624
|
-
import { z as z6 } from "zod";
|
|
2625
|
-
var hostPolicySchema = z6.object({
|
|
2626
|
-
// Host filesystem and shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the notebook pair,
|
|
2627
|
-
// git-worktree tools and `REPL`. Off on the locked assistant surface; on in the coding posture.
|
|
2628
|
-
hostFs: z6.boolean(),
|
|
2629
|
-
// Public web (`WebSearch` / `WebFetch`). Not host reach; a grant like the rest.
|
|
2630
|
-
web: z6.boolean(),
|
|
2631
|
-
// Browser automation (the Playwright MCP surface). Varies by host.
|
|
2632
|
-
browser: z6.boolean(),
|
|
2633
|
-
// User-configured MCP servers permitted.
|
|
2634
|
-
userMcp: z6.boolean(),
|
|
2635
|
-
// Subagent dispatch (`Task` / `Agent`) — a grant: `false` on the locked surface, `true` in the
|
|
2636
|
-
// coding posture, where the coding allow-hook reaches sub-agent tool calls. A subagent completes
|
|
2637
|
-
// within the turn, so it's not an invariant like `scheduling`.
|
|
2638
|
-
subagents: z6.boolean(),
|
|
2639
|
-
// ── Hard platform invariants — always denied, never granted ────────────────
|
|
2640
|
-
// Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*` families,
|
|
2641
|
-
// `Monitor`, `Workflow`, …). A turn is one query that resolves on `result`; a scheduled callback
|
|
2642
|
-
// fires after the reply window has closed and strands the agent.
|
|
2643
|
-
scheduling: z6.literal("never"),
|
|
2644
|
-
// Human-facing UI prompts (`AskUserQuestion`). Nothing in a Cabane conversation can answer a
|
|
2645
|
-
// structured prompt, so the call hangs the turn. The agent asks in its reply instead.
|
|
2646
|
-
uiPrompts: z6.literal("never")
|
|
2647
|
-
});
|
|
2648
|
-
|
|
2649
2631
|
// packages/agent-runtime/src/turn-event.ts
|
|
2650
|
-
import { z as
|
|
2651
|
-
var turnEventSchema =
|
|
2632
|
+
import { z as z6 } from "zod";
|
|
2633
|
+
var turnEventSchema = z6.discriminatedUnion("type", [
|
|
2652
2634
|
// The runtime's opaque session state, emitted when the adapter learns it (e.g. the SDK
|
|
2653
2635
|
// `system/init` frame). The platform stores `state` verbatim per (conversation, agent) and
|
|
2654
2636
|
// hands it back next turn; only the adapter knows what it means.
|
|
@@ -2660,21 +2642,21 @@ var turnEventSchema = z7.discriminatedUnion("type", [
|
|
|
2660
2642
|
// server rewinds the mark, so the next turn rebuilds a full catch-up (this turn is unavoidably
|
|
2661
2643
|
// lossy — the degrade is known only after the manifest was committed). A plain boolean; the
|
|
2662
2644
|
// runtime-specific reason stays in the adapter's `onWarn` log. Optional.
|
|
2663
|
-
|
|
2664
|
-
type:
|
|
2665
|
-
state:
|
|
2666
|
-
degraded:
|
|
2645
|
+
z6.object({
|
|
2646
|
+
type: z6.literal("session"),
|
|
2647
|
+
state: z6.string(),
|
|
2648
|
+
degraded: z6.boolean().optional()
|
|
2667
2649
|
}),
|
|
2668
2650
|
// One readable thinking summary. Transient — surfaced live, never persisted as content.
|
|
2669
|
-
|
|
2651
|
+
z6.object({ type: z6.literal("thinking"), text: z6.string() }),
|
|
2670
2652
|
// Assistant text. `terminal: false` is interim narration (commits as `progress`);
|
|
2671
2653
|
// `terminal: true` is the turn's closing reply (commits as `final`).
|
|
2672
|
-
|
|
2654
|
+
z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
|
|
2673
2655
|
// Runtime/provider-authored prose about the turn — a failure's explanation, or a line the
|
|
2674
2656
|
// harness wrote on a turn it recovered. Not the agent's words: the pump persists it as
|
|
2675
2657
|
// `runtime_notice`, drawn in the SystemNote voice. Adapters emit it only from positive runtime
|
|
2676
2658
|
// evidence; the web never classifies English strings.
|
|
2677
|
-
|
|
2659
|
+
z6.object({ type: z6.literal("runtime_notice"), body: z6.string() }),
|
|
2678
2660
|
// A tool-activity transition. `name` is prefix-stripped (`cabane_read`, not
|
|
2679
2661
|
// `mcp__cabane__cabane_read`); `summary` is the short card label; `phase` pairs `start` with a
|
|
2680
2662
|
// later `done`/`error` on the same `id`.
|
|
@@ -2683,15 +2665,15 @@ var turnEventSchema = z7.discriminatedUnion("type", [
|
|
|
2683
2665
|
// log are a consumer concern (docs/turn-protocol.md). `mcpServer` is the MCP server the call
|
|
2684
2666
|
// came from (`cabane`, `playwright`, …), derived before `name` dropped the prefix; null for a
|
|
2685
2667
|
// host tool. Optional, read as null when absent.
|
|
2686
|
-
|
|
2687
|
-
type:
|
|
2688
|
-
id:
|
|
2689
|
-
name:
|
|
2690
|
-
phase:
|
|
2691
|
-
summary:
|
|
2692
|
-
input:
|
|
2693
|
-
result:
|
|
2694
|
-
mcpServer:
|
|
2668
|
+
z6.object({
|
|
2669
|
+
type: z6.literal("tool"),
|
|
2670
|
+
id: z6.string(),
|
|
2671
|
+
name: z6.string(),
|
|
2672
|
+
phase: z6.enum(["start", "done", "error"]),
|
|
2673
|
+
summary: z6.string(),
|
|
2674
|
+
input: z6.unknown().optional(),
|
|
2675
|
+
result: z6.unknown().optional(),
|
|
2676
|
+
mcpServer: z6.string().nullable().optional()
|
|
2695
2677
|
}),
|
|
2696
2678
|
// The turn's terminal outcome. `ok:false` carries a machine reason (`no_session`, an error
|
|
2697
2679
|
// code), surfaced by the host's clear-PATCH as `errorReason`.
|
|
@@ -2712,70 +2694,70 @@ var turnEventSchema = z7.discriminatedUnion("type", [
|
|
|
2712
2694
|
// started with); `resolvedConfig` the knobs the harness reported (`{ reasoningEffort }` for
|
|
2713
2695
|
// codex). Known only after the run streams, so they ride this event home and the host relays
|
|
2714
2696
|
// them on settle. A cancel has no result event at all.
|
|
2715
|
-
|
|
2716
|
-
type:
|
|
2717
|
-
ok:
|
|
2718
|
-
reason:
|
|
2719
|
-
usage:
|
|
2720
|
-
inputTokens:
|
|
2721
|
-
outputTokens:
|
|
2722
|
-
cacheReadTokens:
|
|
2723
|
-
cacheCreationTokens:
|
|
2724
|
-
contextTokens:
|
|
2725
|
-
contextWindow:
|
|
2697
|
+
z6.object({
|
|
2698
|
+
type: z6.literal("result"),
|
|
2699
|
+
ok: z6.boolean(),
|
|
2700
|
+
reason: z6.string().optional(),
|
|
2701
|
+
usage: z6.object({
|
|
2702
|
+
inputTokens: z6.number(),
|
|
2703
|
+
outputTokens: z6.number(),
|
|
2704
|
+
cacheReadTokens: z6.number().optional(),
|
|
2705
|
+
cacheCreationTokens: z6.number().optional(),
|
|
2706
|
+
contextTokens: z6.number().optional(),
|
|
2707
|
+
contextWindow: z6.number().optional(),
|
|
2726
2708
|
// The rest of the harness's context reading, when it gave one: where it will compact, its
|
|
2727
2709
|
// `/context` breakdown, and every compaction it reported. Claude Code only.
|
|
2728
|
-
contextCompactAt:
|
|
2729
|
-
contextCategories:
|
|
2730
|
-
|
|
2731
|
-
name:
|
|
2732
|
-
tokens:
|
|
2733
|
-
kind:
|
|
2710
|
+
contextCompactAt: z6.number().optional(),
|
|
2711
|
+
contextCategories: z6.array(
|
|
2712
|
+
z6.object({
|
|
2713
|
+
name: z6.string(),
|
|
2714
|
+
tokens: z6.number(),
|
|
2715
|
+
kind: z6.enum(["used", "free", "buffer", "deferred"])
|
|
2734
2716
|
})
|
|
2735
2717
|
).optional(),
|
|
2736
|
-
contextCompactions:
|
|
2737
|
-
|
|
2738
|
-
trigger:
|
|
2739
|
-
preTokens:
|
|
2740
|
-
postTokens:
|
|
2718
|
+
contextCompactions: z6.array(
|
|
2719
|
+
z6.object({
|
|
2720
|
+
trigger: z6.enum(["manual", "auto"]),
|
|
2721
|
+
preTokens: z6.number(),
|
|
2722
|
+
postTokens: z6.number().optional()
|
|
2741
2723
|
})
|
|
2742
2724
|
).optional()
|
|
2743
2725
|
}).optional(),
|
|
2744
|
-
resolvedModel:
|
|
2745
|
-
resolvedConfig:
|
|
2746
|
-
effort:
|
|
2747
|
-
thinking:
|
|
2748
|
-
reasoningEffort:
|
|
2726
|
+
resolvedModel: z6.string().optional(),
|
|
2727
|
+
resolvedConfig: z6.object({
|
|
2728
|
+
effort: z6.string().optional(),
|
|
2729
|
+
thinking: z6.string().optional(),
|
|
2730
|
+
reasoningEffort: z6.string().optional()
|
|
2749
2731
|
}).optional(),
|
|
2750
2732
|
// The harness-reported MCP inventory from this turn's init frame. Diagnostic only: names,
|
|
2751
2733
|
// statuses and a count — never server definitions, credentials or session ids.
|
|
2752
2734
|
// `initReceived:false` tells a missing init frame from a real empty inventory.
|
|
2753
|
-
mcpInventory:
|
|
2754
|
-
initReceived:
|
|
2755
|
-
servers:
|
|
2756
|
-
toolCount:
|
|
2735
|
+
mcpInventory: z6.object({
|
|
2736
|
+
initReceived: z6.boolean(),
|
|
2737
|
+
servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
|
|
2738
|
+
toolCount: z6.number().int().nonnegative()
|
|
2757
2739
|
}).optional()
|
|
2758
2740
|
})
|
|
2759
2741
|
]);
|
|
2760
2742
|
|
|
2761
2743
|
// packages/agent-runtime/src/turn-diagnostics.ts
|
|
2762
|
-
import { z as
|
|
2763
|
-
var turnResultReasonSchema =
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2744
|
+
import { z as z7 } from "zod";
|
|
2745
|
+
var turnResultReasonSchema = z7.discriminatedUnion("kind", [
|
|
2746
|
+
z7.object({ kind: z7.literal("usage_capped"), resetsAt: z7.string().optional() }),
|
|
2747
|
+
z7.object({ kind: z7.literal("rate_limited") }),
|
|
2748
|
+
z7.object({ kind: z7.literal("server_error") }),
|
|
2749
|
+
z7.object({ kind: z7.literal("auth_expired") }),
|
|
2750
|
+
z7.object({ kind: z7.literal("no_result") }),
|
|
2751
|
+
z7.object({ kind: z7.literal("empty_result") }),
|
|
2752
|
+
z7.object({ kind: z7.literal("empty_result_unverified") }),
|
|
2753
|
+
z7.object({ kind: z7.literal("timeout_idle") }),
|
|
2754
|
+
z7.object({ kind: z7.literal("timeout_total") }),
|
|
2755
|
+
z7.object({ kind: z7.literal("cancelled") }),
|
|
2756
|
+
z7.object({ kind: z7.literal("lease_lost") }),
|
|
2757
|
+
z7.object({ kind: z7.literal("skipped") }),
|
|
2758
|
+
z7.object({ kind: z7.literal("session_start_failed") }),
|
|
2759
|
+
z7.object({ kind: z7.literal("workspace_tools_missing") }),
|
|
2760
|
+
z7.object({ kind: z7.literal("runtime_error") })
|
|
2779
2761
|
]);
|
|
2780
2762
|
var turnOutcomes = ["success", "failure", "cancelled", "skipped"];
|
|
2781
2763
|
var turnSessionModes = ["fresh", "resumed", "degraded"];
|
|
@@ -2787,123 +2769,125 @@ var turnFinalSources = [
|
|
|
2787
2769
|
"marker",
|
|
2788
2770
|
"none"
|
|
2789
2771
|
];
|
|
2790
|
-
var turnDiagnosticsSchema =
|
|
2791
|
-
outcome:
|
|
2772
|
+
var turnDiagnosticsSchema = z7.object({
|
|
2773
|
+
outcome: z7.enum(turnOutcomes),
|
|
2792
2774
|
resultReason: turnResultReasonSchema.nullable(),
|
|
2793
|
-
sessionMode:
|
|
2794
|
-
sessionFingerprint:
|
|
2795
|
-
eventCounts:
|
|
2796
|
-
session:
|
|
2797
|
-
text:
|
|
2798
|
-
runtime_notice:
|
|
2799
|
-
thinking:
|
|
2800
|
-
tool:
|
|
2801
|
-
result:
|
|
2775
|
+
sessionMode: z7.enum(turnSessionModes),
|
|
2776
|
+
sessionFingerprint: z7.string().regex(/^[a-f0-9]{16}$/).nullable(),
|
|
2777
|
+
eventCounts: z7.object({
|
|
2778
|
+
session: z7.number().int().nonnegative(),
|
|
2779
|
+
text: z7.number().int().nonnegative(),
|
|
2780
|
+
runtime_notice: z7.number().int().nonnegative(),
|
|
2781
|
+
thinking: z7.number().int().nonnegative(),
|
|
2782
|
+
tool: z7.number().int().nonnegative(),
|
|
2783
|
+
result: z7.number().int().nonnegative()
|
|
2802
2784
|
}),
|
|
2803
|
-
runtimeResultKind:
|
|
2804
|
-
finalSource:
|
|
2805
|
-
mcpInventory:
|
|
2806
|
-
initReceived:
|
|
2807
|
-
servers:
|
|
2808
|
-
toolCount:
|
|
2785
|
+
runtimeResultKind: z7.enum(turnRuntimeResultKinds).nullable(),
|
|
2786
|
+
finalSource: z7.enum(turnFinalSources),
|
|
2787
|
+
mcpInventory: z7.object({
|
|
2788
|
+
initReceived: z7.boolean(),
|
|
2789
|
+
servers: z7.array(z7.object({ name: z7.string(), status: z7.string() })),
|
|
2790
|
+
toolCount: z7.number().int().nonnegative()
|
|
2809
2791
|
}).optional()
|
|
2810
2792
|
});
|
|
2811
2793
|
|
|
2812
2794
|
// packages/agent-runtime/src/turn-request.ts
|
|
2813
|
-
import { z as
|
|
2814
|
-
var contentBlockSchema =
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
type:
|
|
2818
|
-
source:
|
|
2795
|
+
import { z as z8 } from "zod";
|
|
2796
|
+
var contentBlockSchema = z8.discriminatedUnion("type", [
|
|
2797
|
+
z8.object({ type: z8.literal("text"), text: z8.string() }),
|
|
2798
|
+
z8.object({
|
|
2799
|
+
type: z8.literal("image"),
|
|
2800
|
+
source: z8.object({ type: z8.literal("url"), url: z8.string() })
|
|
2819
2801
|
}),
|
|
2820
|
-
|
|
2821
|
-
type:
|
|
2822
|
-
source:
|
|
2802
|
+
z8.object({
|
|
2803
|
+
type: z8.literal("document"),
|
|
2804
|
+
source: z8.object({ type: z8.literal("url"), url: z8.string() })
|
|
2823
2805
|
})
|
|
2824
2806
|
]);
|
|
2825
|
-
var effortLevelSchema =
|
|
2826
|
-
var resolvedRunConfigSchema =
|
|
2827
|
-
model:
|
|
2807
|
+
var effortLevelSchema = z8.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
2808
|
+
var resolvedRunConfigSchema = z8.object({
|
|
2809
|
+
model: z8.string().nullable(),
|
|
2828
2810
|
effort: effortLevelSchema.optional(),
|
|
2829
|
-
runtimeOptions:
|
|
2811
|
+
runtimeOptions: z8.record(z8.string(), z8.unknown()).optional()
|
|
2830
2812
|
});
|
|
2831
|
-
var resolvedMcpServerSchema =
|
|
2832
|
-
|
|
2833
|
-
type:
|
|
2834
|
-
command:
|
|
2835
|
-
args:
|
|
2836
|
-
env:
|
|
2837
|
-
envFromEnv:
|
|
2813
|
+
var resolvedMcpServerSchema = z8.union([
|
|
2814
|
+
z8.object({
|
|
2815
|
+
type: z8.literal("stdio").optional(),
|
|
2816
|
+
command: z8.string(),
|
|
2817
|
+
args: z8.array(z8.string()).optional(),
|
|
2818
|
+
env: z8.record(z8.string(), z8.string()).optional(),
|
|
2819
|
+
envFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2838
2820
|
}),
|
|
2839
|
-
|
|
2840
|
-
type:
|
|
2841
|
-
url:
|
|
2842
|
-
headers:
|
|
2843
|
-
headersFromEnv:
|
|
2821
|
+
z8.object({
|
|
2822
|
+
type: z8.literal("http"),
|
|
2823
|
+
url: z8.string(),
|
|
2824
|
+
headers: z8.record(z8.string(), z8.string()).optional(),
|
|
2825
|
+
headersFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2844
2826
|
}),
|
|
2845
|
-
|
|
2846
|
-
type:
|
|
2847
|
-
url:
|
|
2848
|
-
headers:
|
|
2849
|
-
headersFromEnv:
|
|
2827
|
+
z8.object({
|
|
2828
|
+
type: z8.literal("sse"),
|
|
2829
|
+
url: z8.string(),
|
|
2830
|
+
headers: z8.record(z8.string(), z8.string()).optional(),
|
|
2831
|
+
headersFromEnv: z8.record(z8.string(), z8.string()).optional()
|
|
2850
2832
|
})
|
|
2851
2833
|
]);
|
|
2852
|
-
var resolvedMcpServersSchema =
|
|
2853
|
-
var hostInjectedServersSchema =
|
|
2854
|
-
var turnRequestSchema =
|
|
2834
|
+
var resolvedMcpServersSchema = z8.record(z8.string(), resolvedMcpServerSchema);
|
|
2835
|
+
var hostInjectedServersSchema = z8.record(z8.string(), z8.unknown());
|
|
2836
|
+
var turnRequestSchema = z8.object({
|
|
2855
2837
|
// Server-composed standing instructions: the core segment (body, tool naming, identity, agent
|
|
2856
2838
|
// charter) and, after it, the conversation segment (workspace and room charters, skill indexes,
|
|
2857
2839
|
// roster). One string to the adapter, current as of this dispatch.
|
|
2858
|
-
systemPrompt:
|
|
2840
|
+
systemPrompt: z8.string(),
|
|
2859
2841
|
// The same text as its two ordered segments, so an adapter that writes standing text into the
|
|
2860
2842
|
// conversation (Codex's input preamble) can tell which half changed. `systemPrompt` is
|
|
2861
2843
|
// `joinStanding(standing)`. Optional: absent from an API older than the split, where
|
|
2862
2844
|
// `systemPrompt` is the core alone and the conversation material rides `prompt`.
|
|
2863
|
-
standing:
|
|
2845
|
+
standing: z8.object({ core: z8.string(), conversation: z8.string() }).optional(),
|
|
2864
2846
|
// Server-composed per-turn user text (anchor reminder + the triggering message).
|
|
2865
|
-
prompt:
|
|
2847
|
+
prompt: z8.string(),
|
|
2866
2848
|
// The multi-block user-message body (text + vision).
|
|
2867
|
-
content:
|
|
2849
|
+
content: z8.array(contentBlockSchema),
|
|
2868
2850
|
// Portable-or-dialect run-config (above).
|
|
2869
2851
|
config: resolvedRunConfigSchema,
|
|
2870
|
-
// Abstract capability grants; the adapter maps them to tool names.
|
|
2871
|
-
policy: hostPolicySchema,
|
|
2872
2852
|
// Prior opaque session state, or null for a fresh session.
|
|
2873
|
-
session:
|
|
2853
|
+
session: z8.string().nullable(),
|
|
2874
2854
|
// The cabane control-plane coordinates for this turn's MCP + post-back.
|
|
2875
|
-
cabane:
|
|
2876
|
-
mcpUrl:
|
|
2877
|
-
bearer:
|
|
2878
|
-
activeConversationId:
|
|
2855
|
+
cabane: z8.object({
|
|
2856
|
+
mcpUrl: z8.string(),
|
|
2857
|
+
bearer: z8.string(),
|
|
2858
|
+
activeConversationId: z8.string(),
|
|
2879
2859
|
// Scoped turn-control MCP URL. Codex and opencode mount it as `cabane_companion` with the bearer
|
|
2880
2860
|
// and active-conversation header, for ask, withdraw_ask, wake_me, cancel_wake and skip_turn.
|
|
2881
2861
|
// Claude Code mounts the companion's in-process instance instead. Optional; the companion
|
|
2882
2862
|
// populates it.
|
|
2883
|
-
turnControlUrl:
|
|
2863
|
+
turnControlUrl: z8.string().optional(),
|
|
2884
2864
|
// Workspace coordinate retained for envelope compatibility. Current
|
|
2885
2865
|
// adapters use MCP, whose workspace operations take workspaceId explicitly.
|
|
2886
2866
|
// The companion supplies this field; it is not required by those adapters.
|
|
2887
|
-
workspaceId:
|
|
2867
|
+
workspaceId: z8.string().optional()
|
|
2888
2868
|
}),
|
|
2889
2869
|
// Machine-local resolution (host-filled): the checkout cwd, extra env from a
|
|
2890
2870
|
// prepare hook, and the resolved user MCP servers.
|
|
2891
|
-
local:
|
|
2892
|
-
cwd:
|
|
2893
|
-
env:
|
|
2871
|
+
local: z8.object({
|
|
2872
|
+
cwd: z8.string().optional(),
|
|
2873
|
+
env: z8.record(z8.string(), z8.string()).optional(),
|
|
2894
2874
|
mcpServers: resolvedMcpServersSchema.optional(),
|
|
2895
2875
|
// The values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` → complete header value). The
|
|
2896
2876
|
// adapter merges them into the harness process's environment and nowhere else; the host never
|
|
2897
2877
|
// merges them into its own. Absent when no server references one.
|
|
2898
|
-
secretEnv:
|
|
2878
|
+
secretEnv: z8.record(z8.string(), z8.string()).optional(),
|
|
2899
2879
|
// Machine-local claude-code knobs for an operator running their own companion — the auto-memory
|
|
2900
2880
|
// escape hatch. `autoMemory: true` opts back into Claude Code's auto-memory (governed by the
|
|
2901
2881
|
// operator's `.claude/settings.json`); absent/false keeps the adapter's force-off default
|
|
2902
2882
|
// (`buildClaudeCodeOptions`).
|
|
2903
|
-
claudeCode:
|
|
2883
|
+
claudeCode: z8.object({ autoMemory: z8.boolean().optional() }).optional(),
|
|
2884
|
+
// Machine-local Codex facts the host read before the turn. `configSetsSandboxMode`: the
|
|
2885
|
+
// person's own Codex `config.toml` names a `sandbox_mode`, so the adapter passes no sandbox
|
|
2886
|
+
// flag and the file governs (`codexSandboxMode`). Absent/false → `danger-full-access`.
|
|
2887
|
+
codex: z8.object({ configSetsSandboxMode: z8.boolean().optional() }).optional()
|
|
2904
2888
|
}),
|
|
2905
2889
|
// Host-owned injected servers (host-filled).
|
|
2906
|
-
extra:
|
|
2890
|
+
extra: z8.object({
|
|
2907
2891
|
mcpServers: hostInjectedServersSchema
|
|
2908
2892
|
})
|
|
2909
2893
|
});
|
|
@@ -2919,12 +2903,12 @@ function standingSegments(req) {
|
|
|
2919
2903
|
}
|
|
2920
2904
|
|
|
2921
2905
|
// packages/agent-runtime/src/conformance.ts
|
|
2922
|
-
import { z as
|
|
2923
|
-
var conformanceFixtureSchema =
|
|
2924
|
-
name:
|
|
2906
|
+
import { z as z9 } from "zod";
|
|
2907
|
+
var conformanceFixtureSchema = z9.object({
|
|
2908
|
+
name: z9.string(),
|
|
2925
2909
|
request: turnRequestSchema,
|
|
2926
|
-
nativeStream:
|
|
2927
|
-
expected:
|
|
2910
|
+
nativeStream: z9.array(z9.unknown()),
|
|
2911
|
+
expected: z9.array(turnEventSchema)
|
|
2928
2912
|
});
|
|
2929
2913
|
|
|
2930
2914
|
// packages/agent-runtime/src/transcript.ts
|
|
@@ -3189,24 +3173,7 @@ function envResolvedFrom(server, secretEnv) {
|
|
|
3189
3173
|
}
|
|
3190
3174
|
|
|
3191
3175
|
// packages/agent-runtime/src/claude-code/policy.ts
|
|
3192
|
-
import { z as
|
|
3193
|
-
var HOST_FS_TOOLS = [
|
|
3194
|
-
// shell + local filesystem
|
|
3195
|
-
"Bash",
|
|
3196
|
-
"Read",
|
|
3197
|
-
"Write",
|
|
3198
|
-
"Edit",
|
|
3199
|
-
"Glob",
|
|
3200
|
-
"Grep",
|
|
3201
|
-
// notebook tools (host-fs, Jupyter) — same host reach as Read/Write/Edit
|
|
3202
|
-
"NotebookEdit",
|
|
3203
|
-
"NotebookRead",
|
|
3204
|
-
// git-worktree manipulation on the host checkout
|
|
3205
|
-
"EnterWorktree",
|
|
3206
|
-
"ExitWorktree",
|
|
3207
|
-
// host code execution
|
|
3208
|
-
"REPL"
|
|
3209
|
-
];
|
|
3176
|
+
import { z as z10 } from "zod";
|
|
3210
3177
|
var SUBAGENT_TOOLS = ["Task", "Agent"];
|
|
3211
3178
|
var UNSUPPORTED_TOOLS = ["AskUserQuestion"];
|
|
3212
3179
|
var STRANDING_TOOLS = [
|
|
@@ -3228,29 +3195,23 @@ var STRANDING_TOOLS = [
|
|
|
3228
3195
|
"Workflow"
|
|
3229
3196
|
];
|
|
3230
3197
|
var DEFAULT_WEB_TOOLS = ["WebSearch", "WebFetch"];
|
|
3231
|
-
function disallowedToolsFor(policy) {
|
|
3232
|
-
const tools = [...UNSUPPORTED_TOOLS, ...STRANDING_TOOLS];
|
|
3233
|
-
if (!policy.subagents) tools.unshift(...SUBAGENT_TOOLS);
|
|
3234
|
-
if (!policy.hostFs) tools.unshift(...HOST_FS_TOOLS);
|
|
3235
|
-
return tools;
|
|
3236
|
-
}
|
|
3237
3198
|
function withThinkingSummaries(thinking) {
|
|
3238
3199
|
if (!thinking) return { type: "adaptive", display: "summarized" };
|
|
3239
3200
|
if (thinking.type === "disabled") return thinking;
|
|
3240
3201
|
return { display: "summarized", ...thinking };
|
|
3241
3202
|
}
|
|
3242
|
-
var claudeCodeDialectSchema =
|
|
3243
|
-
thinking:
|
|
3244
|
-
|
|
3245
|
-
type:
|
|
3246
|
-
display:
|
|
3203
|
+
var claudeCodeDialectSchema = z10.object({
|
|
3204
|
+
thinking: z10.discriminatedUnion("type", [
|
|
3205
|
+
z10.object({
|
|
3206
|
+
type: z10.literal("adaptive"),
|
|
3207
|
+
display: z10.enum(["summarized", "omitted"]).optional()
|
|
3247
3208
|
}),
|
|
3248
|
-
|
|
3249
|
-
type:
|
|
3250
|
-
budgetTokens:
|
|
3251
|
-
display:
|
|
3209
|
+
z10.object({
|
|
3210
|
+
type: z10.literal("enabled"),
|
|
3211
|
+
budgetTokens: z10.number().int().positive().optional(),
|
|
3212
|
+
display: z10.enum(["summarized", "omitted"]).optional()
|
|
3252
3213
|
}),
|
|
3253
|
-
|
|
3214
|
+
z10.object({ type: z10.literal("disabled") })
|
|
3254
3215
|
]).optional()
|
|
3255
3216
|
}).loose();
|
|
3256
3217
|
function readThinking(runtimeOptions) {
|
|
@@ -3313,13 +3274,13 @@ async function preToolUseHook(input) {
|
|
|
3313
3274
|
hookSpecificOutput: {
|
|
3314
3275
|
hookEventName: "PreToolUse",
|
|
3315
3276
|
permissionDecision: "allow",
|
|
3316
|
-
permissionDecisionReason: "
|
|
3277
|
+
permissionDecisionReason: "headless never-prompt (CT680)",
|
|
3317
3278
|
updatedInput: toolInput
|
|
3318
3279
|
}
|
|
3319
3280
|
};
|
|
3320
3281
|
}
|
|
3321
3282
|
function buildClaudeCodeOptions(req, augment) {
|
|
3322
|
-
const {
|
|
3283
|
+
const { config } = req;
|
|
3323
3284
|
const cwd = req.local.cwd;
|
|
3324
3285
|
const userServers = {};
|
|
3325
3286
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
@@ -3352,15 +3313,10 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
3352
3313
|
alwaysLoad: true
|
|
3353
3314
|
};
|
|
3354
3315
|
}
|
|
3355
|
-
const useCodingPreset = policy.hostFs;
|
|
3356
3316
|
const cabaneGlob = `mcp__${CABANE_MCP_SERVER}__*`;
|
|
3357
3317
|
const extraServerGlobs = Object.keys(req.extra.mcpServers).map((name) => `mcp__${name}__*`);
|
|
3358
|
-
const allowedTools = dedupe([
|
|
3359
|
-
|
|
3360
|
-
...extraServerGlobs,
|
|
3361
|
-
...policy.web ? DEFAULT_WEB_TOOLS : []
|
|
3362
|
-
]);
|
|
3363
|
-
const disallowedTools = dedupe([...disallowedToolsFor(policy)]);
|
|
3318
|
+
const allowedTools = dedupe([cabaneGlob, ...extraServerGlobs, ...DEFAULT_WEB_TOOLS]);
|
|
3319
|
+
const disallowedTools = dedupe([...UNSUPPORTED_TOOLS, ...STRANDING_TOOLS]);
|
|
3364
3320
|
const resumeDecision = decideResume(req.session, cwd);
|
|
3365
3321
|
const resume = "resume" in resumeDecision ? resumeDecision.resume : null;
|
|
3366
3322
|
const freshReason = "fresh" in resumeDecision ? resumeDecision.reason : void 0;
|
|
@@ -3378,9 +3334,7 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
3378
3334
|
// (`runtimeOptions['claude-code']`).
|
|
3379
3335
|
thinking: withThinkingSummaries(readThinking(config.runtimeOptions)),
|
|
3380
3336
|
...config.effort ? { effort: config.effort } : {},
|
|
3381
|
-
//
|
|
3382
|
-
// user's existing `cabane` MCP registration could shadow ours.
|
|
3383
|
-
settingSources: [],
|
|
3337
|
+
// Without `strictMcpConfig` the user's existing `cabane` MCP registration could shadow ours.
|
|
3384
3338
|
strictMcpConfig: true,
|
|
3385
3339
|
// Auto-memory off unless the operator opted back in (see above).
|
|
3386
3340
|
...devControlsAutoMemory ? {} : { settings: { autoMemoryEnabled: false } },
|
|
@@ -3416,31 +3370,19 @@ function buildClaudeCodeOptions(req, augment) {
|
|
|
3416
3370
|
},
|
|
3417
3371
|
...resume ? { resume } : {}
|
|
3418
3372
|
};
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
hooks: { PreToolUse: [{ hooks: [preToolUseHook] }] }
|
|
3433
|
-
};
|
|
3434
|
-
} else {
|
|
3435
|
-
options = {
|
|
3436
|
-
...base,
|
|
3437
|
-
systemPrompt: { type: "custom", prompt: req.systemPrompt, snapshot: false },
|
|
3438
|
-
allowedTools,
|
|
3439
|
-
disallowedTools,
|
|
3440
|
-
permissionMode: "bypassPermissions",
|
|
3441
|
-
allowDangerouslySkipPermissions: true
|
|
3442
|
-
};
|
|
3443
|
-
}
|
|
3373
|
+
const options = {
|
|
3374
|
+
...base,
|
|
3375
|
+
systemPrompt: {
|
|
3376
|
+
type: "preset",
|
|
3377
|
+
preset: "claude_code",
|
|
3378
|
+
append: req.systemPrompt,
|
|
3379
|
+
snapshot: false
|
|
3380
|
+
},
|
|
3381
|
+
settingSources: ["user", "project"],
|
|
3382
|
+
allowedTools,
|
|
3383
|
+
disallowedTools,
|
|
3384
|
+
hooks: { PreToolUse: [{ hooks: [preToolUseHook] }] }
|
|
3385
|
+
};
|
|
3444
3386
|
return { options: augment ? augment(options, req) : options, resume, freshReason };
|
|
3445
3387
|
}
|
|
3446
3388
|
function dedupe(xs) {
|
|
@@ -3920,15 +3862,6 @@ var claudeCodeAdapter = createClaudeCodeAdapter();
|
|
|
3920
3862
|
|
|
3921
3863
|
// packages/agent-runtime/src/claude-code/conformance.ts
|
|
3922
3864
|
var ABORT_SENTINEL = { __abortHere: true };
|
|
3923
|
-
var CABANE_POLICY = {
|
|
3924
|
-
hostFs: false,
|
|
3925
|
-
web: true,
|
|
3926
|
-
browser: false,
|
|
3927
|
-
userMcp: false,
|
|
3928
|
-
subagents: false,
|
|
3929
|
-
scheduling: "never",
|
|
3930
|
-
uiPrompts: "never"
|
|
3931
|
-
};
|
|
3932
3865
|
var CWD = "/env/here";
|
|
3933
3866
|
var HEALTHY_MCP_INVENTORY = {
|
|
3934
3867
|
initReceived: true,
|
|
@@ -3942,7 +3875,6 @@ function makeRequest(overrides2 = {}) {
|
|
|
3942
3875
|
content: [{ type: "text", text: "hi there" }],
|
|
3943
3876
|
// The adapter rejects a null model, so the fixture carries a real bare SDK id.
|
|
3944
3877
|
config: { model: "claude-opus-4-8" },
|
|
3945
|
-
policy: CABANE_POLICY,
|
|
3946
3878
|
session: null,
|
|
3947
3879
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
3948
3880
|
local: { cwd: CWD },
|
|
@@ -4689,35 +4621,17 @@ function sealHeld(held, terminal) {
|
|
|
4689
4621
|
}
|
|
4690
4622
|
|
|
4691
4623
|
// packages/agent-runtime/src/opencode/policy.ts
|
|
4692
|
-
import { z as
|
|
4693
|
-
var OPENCODE_HOST_TOOLS = [
|
|
4694
|
-
"bash",
|
|
4695
|
-
"edit",
|
|
4696
|
-
"write",
|
|
4697
|
-
"read",
|
|
4698
|
-
"grep",
|
|
4699
|
-
"glob",
|
|
4700
|
-
"list",
|
|
4701
|
-
"patch"
|
|
4702
|
-
];
|
|
4703
|
-
var OPENCODE_WEB_TOOLS = ["webfetch"];
|
|
4704
|
-
var OPENCODE_SUBAGENT_TOOLS = ["task"];
|
|
4624
|
+
import { z as z11 } from "zod";
|
|
4705
4625
|
var OPENCODE_SCHEDULING_TOOLS = [];
|
|
4706
4626
|
var OPENCODE_UI_PROMPT_TOOLS = ["question"];
|
|
4707
|
-
function
|
|
4627
|
+
function opencodeTools() {
|
|
4708
4628
|
const tools = {};
|
|
4709
|
-
const
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
deny(OPENCODE_SCHEDULING_TOOLS);
|
|
4716
|
-
deny(OPENCODE_UI_PROMPT_TOOLS);
|
|
4717
|
-
return { tools, allowAllHostTools: policy.hostFs };
|
|
4718
|
-
}
|
|
4719
|
-
var opencodeDialectSchema = z12.object({
|
|
4720
|
-
agent: z12.string().min(1).optional()
|
|
4629
|
+
for (const name of [...OPENCODE_SCHEDULING_TOOLS, ...OPENCODE_UI_PROMPT_TOOLS])
|
|
4630
|
+
tools[name] = false;
|
|
4631
|
+
return tools;
|
|
4632
|
+
}
|
|
4633
|
+
var opencodeDialectSchema = z11.object({
|
|
4634
|
+
agent: z11.string().min(1).optional()
|
|
4721
4635
|
}).loose();
|
|
4722
4636
|
function readOpencodeDialect(runtimeOptions) {
|
|
4723
4637
|
const parsed = opencodeDialectSchema.safeParse(runtimeOptions?.["opencode"] ?? {});
|
|
@@ -4736,18 +4650,16 @@ var CABANE_MCP_SERVER2 = "cabane";
|
|
|
4736
4650
|
var TURN_CONTROL_MCP_SERVER = "cabane_companion";
|
|
4737
4651
|
var ACTIVE_CONVERSATION_HEADER2 = "x-cabane-active-conversation";
|
|
4738
4652
|
function buildRunSpec(req, resumeSessionId) {
|
|
4739
|
-
const {
|
|
4653
|
+
const { config } = req;
|
|
4740
4654
|
const directory = req.local.cwd ?? "";
|
|
4741
4655
|
const dialect = readOpencodeDialect(config.runtimeOptions);
|
|
4742
|
-
const { tools, allowAllHostTools } = opencodeToolPolicy(policy);
|
|
4743
4656
|
return {
|
|
4744
4657
|
resumeSessionId,
|
|
4745
4658
|
directory,
|
|
4746
4659
|
model: config.model ? parseOpencodeModel(config.model) : null,
|
|
4747
4660
|
systemPrompt: req.systemPrompt,
|
|
4748
4661
|
parts: buildParts(req),
|
|
4749
|
-
tools,
|
|
4750
|
-
allowAllHostTools,
|
|
4662
|
+
tools: opencodeTools(),
|
|
4751
4663
|
...dialect.agent ? { agent: dialect.agent } : {},
|
|
4752
4664
|
mcp: buildMcp(req),
|
|
4753
4665
|
// Every server this turn installs with a credential resolved into it: a remote one's
|
|
@@ -4879,11 +4791,12 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4879
4791
|
const doFetch = opts.fetchImpl ?? fetch;
|
|
4880
4792
|
return {
|
|
4881
4793
|
async run(spec, signal) {
|
|
4794
|
+
const url = scopedUrl(base, spec.directory);
|
|
4882
4795
|
const release = await acquireServerTurnLock(base);
|
|
4883
4796
|
if (signal.aborted) release();
|
|
4884
4797
|
else signal.addEventListener("abort", release, { once: true });
|
|
4885
4798
|
try {
|
|
4886
|
-
const eventRes = await doFetch(
|
|
4799
|
+
const eventRes = await doFetch(url("/event"), {
|
|
4887
4800
|
headers: { accept: "text/event-stream" },
|
|
4888
4801
|
signal
|
|
4889
4802
|
});
|
|
@@ -4893,7 +4806,7 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4893
4806
|
let sessionId = spec.resumeSessionId;
|
|
4894
4807
|
let created = false;
|
|
4895
4808
|
if (!sessionId) {
|
|
4896
|
-
const res = await doFetch(
|
|
4809
|
+
const res = await doFetch(url("/session"), {
|
|
4897
4810
|
method: "POST",
|
|
4898
4811
|
headers: { "content-type": "application/json" },
|
|
4899
4812
|
body: JSON.stringify({ body: {} }),
|
|
@@ -4905,9 +4818,9 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4905
4818
|
sessionId = json.id;
|
|
4906
4819
|
created = true;
|
|
4907
4820
|
}
|
|
4908
|
-
await installMcp(doFetch,
|
|
4821
|
+
await installMcp(doFetch, url, spec, signal);
|
|
4909
4822
|
const promptRes = await doFetch(
|
|
4910
|
-
|
|
4823
|
+
url(`/session/${encodeURIComponent(sessionId)}/prompt_async`),
|
|
4911
4824
|
{
|
|
4912
4825
|
method: "POST",
|
|
4913
4826
|
headers: { "content-type": "application/json" },
|
|
@@ -4926,7 +4839,7 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4926
4839
|
}
|
|
4927
4840
|
const sid = sessionId;
|
|
4928
4841
|
const onAbort = () => {
|
|
4929
|
-
void doFetch(
|
|
4842
|
+
void doFetch(url(`/session/${encodeURIComponent(sid)}/abort`), {
|
|
4930
4843
|
method: "POST"
|
|
4931
4844
|
}).catch(() => {
|
|
4932
4845
|
});
|
|
@@ -4938,7 +4851,13 @@ function createHttpOpencodeTransport(opts) {
|
|
|
4938
4851
|
created,
|
|
4939
4852
|
directory: spec.directory,
|
|
4940
4853
|
// The lock is released when this stream finishes draining.
|
|
4941
|
-
events: releaseAfter(
|
|
4854
|
+
events: releaseAfter(
|
|
4855
|
+
approvePermissions(
|
|
4856
|
+
parseSseStream(eventRes.body, sessionId, signal),
|
|
4857
|
+
(ev) => replyToPermission(doFetch, url, ev, signal)
|
|
4858
|
+
),
|
|
4859
|
+
release
|
|
4860
|
+
)
|
|
4942
4861
|
};
|
|
4943
4862
|
} catch (err) {
|
|
4944
4863
|
release();
|
|
@@ -4959,16 +4878,16 @@ var SCRUBBED_MCP_CONFIG = {
|
|
|
4959
4878
|
url: "http://127.0.0.1:9/cabane-removed",
|
|
4960
4879
|
enabled: false
|
|
4961
4880
|
};
|
|
4962
|
-
async function installMcp(doFetch,
|
|
4881
|
+
async function installMcp(doFetch, url, spec, signal) {
|
|
4963
4882
|
const [live, configured] = await Promise.all([
|
|
4964
|
-
getJson(doFetch,
|
|
4965
|
-
getJson(doFetch,
|
|
4883
|
+
getJson(doFetch, url("/mcp"), signal),
|
|
4884
|
+
getJson(doFetch, url("/config"), signal)
|
|
4966
4885
|
]);
|
|
4967
4886
|
const operatorOwned = new Set(Object.keys(asRecord2(asRecord2(configured).mcp)));
|
|
4968
4887
|
const stale = Object.keys(asRecord2(live)).filter(
|
|
4969
4888
|
(name) => !(name in spec.mcp) && !operatorOwned.has(name)
|
|
4970
4889
|
);
|
|
4971
|
-
await Promise.all(stale.map((name) => postMcp(doFetch,
|
|
4890
|
+
await Promise.all(stale.map((name) => postMcp(doFetch, url, name, SCRUBBED_MCP_CONFIG, signal)));
|
|
4972
4891
|
const shadowing = (spec.credentialServers ?? []).filter((name) => operatorOwned.has(name));
|
|
4973
4892
|
if (shadowing.length > 0) {
|
|
4974
4893
|
throw new Error(
|
|
@@ -4976,11 +4895,11 @@ async function installMcp(doFetch, base, spec, signal) {
|
|
|
4976
4895
|
);
|
|
4977
4896
|
}
|
|
4978
4897
|
await Promise.all(
|
|
4979
|
-
Object.entries(spec.mcp).map(([name, config]) => postMcp(doFetch,
|
|
4898
|
+
Object.entries(spec.mcp).map(([name, config]) => postMcp(doFetch, url, name, config, signal))
|
|
4980
4899
|
);
|
|
4981
4900
|
}
|
|
4982
|
-
async function postMcp(doFetch,
|
|
4983
|
-
const res = await doFetch(
|
|
4901
|
+
async function postMcp(doFetch, url, name, config, signal) {
|
|
4902
|
+
const res = await doFetch(url("/mcp"), {
|
|
4984
4903
|
method: "POST",
|
|
4985
4904
|
headers: { "content-type": "application/json" },
|
|
4986
4905
|
body: JSON.stringify({ name, config }),
|
|
@@ -5044,6 +4963,33 @@ function belongsToSession(ev, sessionId) {
|
|
|
5044
4963
|
if (typeof infoSid === "string") return infoSid === sessionId;
|
|
5045
4964
|
return true;
|
|
5046
4965
|
}
|
|
4966
|
+
async function* approvePermissions(events, reply2) {
|
|
4967
|
+
for await (const ev of events) {
|
|
4968
|
+
if (ev.type === "permission.asked" || ev.type === "permission.v2.asked") await reply2(ev);
|
|
4969
|
+
yield ev;
|
|
4970
|
+
}
|
|
4971
|
+
}
|
|
4972
|
+
async function replyToPermission(doFetch, url, ev, signal) {
|
|
4973
|
+
const props = asRecord2(ev.properties);
|
|
4974
|
+
const id = typeof props.id === "string" ? props.id : null;
|
|
4975
|
+
const sessionId = typeof props.sessionID === "string" ? props.sessionID : null;
|
|
4976
|
+
if (!id) throw new Error(`opencode: ${ev.type} carried no request id`);
|
|
4977
|
+
const target = ev.type === "permission.v2.asked" && sessionId ? url(
|
|
4978
|
+
`/api/session/${encodeURIComponent(sessionId)}/permission/${encodeURIComponent(id)}/reply`,
|
|
4979
|
+
{ unscoped: true }
|
|
4980
|
+
) : url(`/permission/${encodeURIComponent(id)}/reply`);
|
|
4981
|
+
const res = await doFetch(target, {
|
|
4982
|
+
method: "POST",
|
|
4983
|
+
headers: { "content-type": "application/json" },
|
|
4984
|
+
body: JSON.stringify({ reply: "once" }),
|
|
4985
|
+
signal
|
|
4986
|
+
});
|
|
4987
|
+
if (!res.ok) throw new Error(`opencode: permission reply failed (HTTP ${res.status})`);
|
|
4988
|
+
}
|
|
4989
|
+
function scopedUrl(base, directory) {
|
|
4990
|
+
const scope = directory ? `directory=${encodeURIComponent(directory)}` : "";
|
|
4991
|
+
return (path, opts) => scope && !opts?.unscoped ? `${base}${path}${path.includes("?") ? "&" : "?"}${scope}` : base + path;
|
|
4992
|
+
}
|
|
5047
4993
|
function trimSlash(s) {
|
|
5048
4994
|
return s.endsWith("/") ? s.slice(0, -1) : s;
|
|
5049
4995
|
}
|
|
@@ -5106,15 +5052,6 @@ var opencodeAdapter = createOpencodeAdapter();
|
|
|
5106
5052
|
var ABORT_SENTINEL2 = { __abortHere: true };
|
|
5107
5053
|
var NEW_SESSION_ID = "sess_new";
|
|
5108
5054
|
var BOUNDED_NOTICE = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
5109
|
-
var COMPANION_POLICY = {
|
|
5110
|
-
hostFs: false,
|
|
5111
|
-
web: true,
|
|
5112
|
-
browser: true,
|
|
5113
|
-
userMcp: true,
|
|
5114
|
-
subagents: false,
|
|
5115
|
-
scheduling: "never",
|
|
5116
|
-
uiPrompts: "never"
|
|
5117
|
-
};
|
|
5118
5055
|
var DIR = "/env/here";
|
|
5119
5056
|
function makeRequest2(overrides2 = {}) {
|
|
5120
5057
|
return {
|
|
@@ -5122,7 +5059,6 @@ function makeRequest2(overrides2 = {}) {
|
|
|
5122
5059
|
prompt: "hi there",
|
|
5123
5060
|
content: [{ type: "text", text: "hi there" }],
|
|
5124
5061
|
config: { model: "deepseek/deepseek-chat" },
|
|
5125
|
-
policy: COMPANION_POLICY,
|
|
5126
5062
|
session: null,
|
|
5127
5063
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
5128
5064
|
local: { cwd: DIR },
|
|
@@ -5928,27 +5864,13 @@ function sealHeld2(held, terminal) {
|
|
|
5928
5864
|
}
|
|
5929
5865
|
|
|
5930
5866
|
// packages/agent-runtime/src/codex/policy.ts
|
|
5931
|
-
import { z as
|
|
5932
|
-
function
|
|
5933
|
-
return
|
|
5934
|
-
sandboxMode: "danger-full-access",
|
|
5935
|
-
// Headless: nothing to approve, because nothing is denied.
|
|
5936
|
-
approvalPolicy: "never",
|
|
5937
|
-
// Full access means the command network is open regardless of the grant —
|
|
5938
|
-
// recorded as `true` so the value never claims a restriction that isn't
|
|
5939
|
-
// enforced.
|
|
5940
|
-
networkAccessEnabled: true
|
|
5941
|
-
} : {
|
|
5942
|
-
sandboxMode: "read-only",
|
|
5943
|
-
// Headless: the sandbox is the boundary; never pause for a human.
|
|
5944
|
-
approvalPolicy: "never",
|
|
5945
|
-
// Retained in the policy value for symmetry; read-only ignores it.
|
|
5946
|
-
networkAccessEnabled: policy.web
|
|
5947
|
-
};
|
|
5867
|
+
import { z as z12 } from "zod";
|
|
5868
|
+
function codexSandboxMode(local) {
|
|
5869
|
+
return local.codex?.configSetsSandboxMode ? null : "danger-full-access";
|
|
5948
5870
|
}
|
|
5949
5871
|
var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
5950
|
-
var codexDialectSchema =
|
|
5951
|
-
modelReasoningEffort:
|
|
5872
|
+
var codexDialectSchema = z12.object({
|
|
5873
|
+
modelReasoningEffort: z12.enum(CODEX_REASONING_EFFORTS).optional()
|
|
5952
5874
|
}).loose();
|
|
5953
5875
|
function readCodexDialect(runtimeOptions) {
|
|
5954
5876
|
const parsed = codexDialectSchema.safeParse(runtimeOptions?.["codex"] ?? {});
|
|
@@ -5972,14 +5894,12 @@ var ENV_ENVELOPE_KEYS = [
|
|
|
5972
5894
|
"CABANE_PLAYGROUND_BIN",
|
|
5973
5895
|
"CABANE_CONVERSATION_ID",
|
|
5974
5896
|
"CABANE_AGENT_ID",
|
|
5975
|
-
"CABANE_CONVERSATION_TITLE"
|
|
5976
|
-
"CABANE_HOST_ACCESS"
|
|
5897
|
+
"CABANE_CONVERSATION_TITLE"
|
|
5977
5898
|
];
|
|
5978
|
-
function buildRunSpec2(req, resumeThreadId,
|
|
5979
|
-
const {
|
|
5899
|
+
function buildRunSpec2(req, resumeThreadId, threadStanding = null, imagePaths = []) {
|
|
5900
|
+
const { config } = req;
|
|
5980
5901
|
const directory = req.local.cwd ?? "";
|
|
5981
5902
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
5982
|
-
const baseInstructionsFile = !policy.hostFs && instructionsFile ? instructionsFile : null;
|
|
5983
5903
|
const model = config.model ? parseCodexModel(config.model) : null;
|
|
5984
5904
|
const segments = standingSegments(req);
|
|
5985
5905
|
const conversationText = segments.conversation.trim();
|
|
@@ -5989,25 +5909,24 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadStand
|
|
|
5989
5909
|
};
|
|
5990
5910
|
const resumed = resumeThreadId !== null;
|
|
5991
5911
|
const held = resumed ? threadStanding : null;
|
|
5992
|
-
const viaInput = baseInstructionsFile === null;
|
|
5993
5912
|
const preamble = {
|
|
5994
|
-
core:
|
|
5913
|
+
core: (held?.core ?? null) !== fingerprints.core,
|
|
5995
5914
|
// Compared both ways: a held segment that is now empty differs from it, and nothing held
|
|
5996
5915
|
// equals nothing to send.
|
|
5997
|
-
conversation:
|
|
5916
|
+
conversation: (held?.conversation ?? null) !== fingerprints.conversation
|
|
5998
5917
|
};
|
|
5999
5918
|
return {
|
|
6000
5919
|
resumeThreadId,
|
|
6001
5920
|
directory,
|
|
6002
5921
|
model,
|
|
6003
|
-
|
|
5922
|
+
sandboxMode: codexSandboxMode(req.local),
|
|
5923
|
+
approvalPolicy: "never",
|
|
6004
5924
|
skipGitRepoCheck: true,
|
|
6005
5925
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
6006
|
-
baseInstructionsFile,
|
|
6007
5926
|
input: withImages(buildInput(req, segments, preamble, resumed), imagePaths),
|
|
6008
5927
|
preamble,
|
|
6009
5928
|
fingerprints,
|
|
6010
|
-
config: buildConfig(req
|
|
5929
|
+
config: buildConfig(req),
|
|
6011
5930
|
secretEnv: codexSecretEnv(req)
|
|
6012
5931
|
};
|
|
6013
5932
|
}
|
|
@@ -6052,7 +5971,7 @@ function withImages(text, imagePaths) {
|
|
|
6052
5971
|
...imagePaths.map((path) => ({ type: "local_image", path }))
|
|
6053
5972
|
];
|
|
6054
5973
|
}
|
|
6055
|
-
function buildConfig(req
|
|
5974
|
+
function buildConfig(req) {
|
|
6056
5975
|
const mcp_servers = {};
|
|
6057
5976
|
for (const [name, server] of Object.entries(req.local.mcpServers ?? {})) {
|
|
6058
5977
|
if ("url" in server) {
|
|
@@ -6118,9 +6037,6 @@ function buildConfig(req, baseInstructionsFile) {
|
|
|
6118
6037
|
return {
|
|
6119
6038
|
mcp_servers,
|
|
6120
6039
|
experimental_use_rmcp_client: true,
|
|
6121
|
-
// The no-host-access prompt replacement — Cabane's composed prompt becomes Codex's base
|
|
6122
|
-
// instructions, and Codex's environment preamble goes with the harness prompt it belonged to.
|
|
6123
|
-
...baseInstructionsFile ? { model_instructions_file: baseInstructionsFile, include_environment_context: false } : {},
|
|
6124
6040
|
...Object.keys(shellEnv).length ? { shell_environment_policy: { set: shellEnv } } : {}
|
|
6125
6041
|
};
|
|
6126
6042
|
}
|
|
@@ -6196,10 +6112,10 @@ function buildConfigOverrides(config) {
|
|
|
6196
6112
|
function buildSdkThreadOptions(spec) {
|
|
6197
6113
|
return {
|
|
6198
6114
|
...spec.model ? { model: spec.model } : {},
|
|
6199
|
-
sandboxMode: spec.
|
|
6115
|
+
...spec.sandboxMode ? { sandboxMode: spec.sandboxMode } : {},
|
|
6200
6116
|
workingDirectory: spec.directory,
|
|
6201
6117
|
skipGitRepoCheck: spec.skipGitRepoCheck,
|
|
6202
|
-
approvalPolicy: spec.
|
|
6118
|
+
approvalPolicy: spec.approvalPolicy,
|
|
6203
6119
|
...spec.modelReasoningEffort ? { modelReasoningEffort: spec.modelReasoningEffort } : {}
|
|
6204
6120
|
};
|
|
6205
6121
|
}
|
|
@@ -6295,23 +6211,6 @@ function createCodexAdapter(deps = {}) {
|
|
|
6295
6211
|
);
|
|
6296
6212
|
}
|
|
6297
6213
|
}
|
|
6298
|
-
let instructions = null;
|
|
6299
|
-
if (!req.policy.hostFs) {
|
|
6300
|
-
if (!deps.writeInstructionsFile) {
|
|
6301
|
-
deps.onWarn?.(
|
|
6302
|
-
"codex adapter: no instructions-file writer wired on this host \u2014 the composed prompt rides as the input preamble, so Codex keeps its own base instructions (CT871)"
|
|
6303
|
-
);
|
|
6304
|
-
} else {
|
|
6305
|
-
try {
|
|
6306
|
-
instructions = await deps.writeInstructionsFile(req.systemPrompt);
|
|
6307
|
-
} catch (err) {
|
|
6308
|
-
deps.onWarn?.(
|
|
6309
|
-
"codex adapter: failed to write the per-turn instructions file \u2014 falling back to the input preamble (CT871)",
|
|
6310
|
-
{ err: err instanceof Error ? err.message : String(err) }
|
|
6311
|
-
);
|
|
6312
|
-
}
|
|
6313
|
-
}
|
|
6314
|
-
}
|
|
6315
6214
|
let images = null;
|
|
6316
6215
|
try {
|
|
6317
6216
|
const imageUrls = req.content.flatMap((b) => b.type === "image" ? [b.source.url] : []);
|
|
@@ -6335,13 +6234,7 @@ function createCodexAdapter(deps = {}) {
|
|
|
6335
6234
|
}
|
|
6336
6235
|
if (signal.aborted) return;
|
|
6337
6236
|
}
|
|
6338
|
-
const spec = buildRunSpec2(
|
|
6339
|
-
req,
|
|
6340
|
-
resumeThreadId,
|
|
6341
|
-
instructions?.path ?? null,
|
|
6342
|
-
threadStanding,
|
|
6343
|
-
images?.paths ?? []
|
|
6344
|
-
);
|
|
6237
|
+
const spec = buildRunSpec2(req, resumeThreadId, threadStanding, images?.paths ?? []);
|
|
6345
6238
|
if (spec.model === null) {
|
|
6346
6239
|
deps.onWarn?.("Codex is using its default model because no model was resolved", {
|
|
6347
6240
|
model: req.config.model
|
|
@@ -6383,15 +6276,6 @@ function createCodexAdapter(deps = {}) {
|
|
|
6383
6276
|
});
|
|
6384
6277
|
}
|
|
6385
6278
|
}
|
|
6386
|
-
if (instructions) {
|
|
6387
|
-
try {
|
|
6388
|
-
await instructions.cleanup();
|
|
6389
|
-
} catch (err) {
|
|
6390
|
-
deps.onWarn?.("codex adapter: instructions-file cleanup failed (CT871)", {
|
|
6391
|
-
err: err instanceof Error ? err.message : String(err)
|
|
6392
|
-
});
|
|
6393
|
-
}
|
|
6394
|
-
}
|
|
6395
6279
|
}
|
|
6396
6280
|
}
|
|
6397
6281
|
};
|
|
@@ -6402,15 +6286,6 @@ var codexAdapter = createCodexAdapter();
|
|
|
6402
6286
|
var ABORT_SENTINEL3 = { __abortHere: true };
|
|
6403
6287
|
var NEW_THREAD_ID = "th_new";
|
|
6404
6288
|
var BOUNDED_NOTICE2 = "You've hit your usage limit \xB7 ".padEnd(200, "x");
|
|
6405
|
-
var COMPANION_POLICY2 = {
|
|
6406
|
-
hostFs: false,
|
|
6407
|
-
web: true,
|
|
6408
|
-
browser: true,
|
|
6409
|
-
userMcp: true,
|
|
6410
|
-
subagents: false,
|
|
6411
|
-
scheduling: "never",
|
|
6412
|
-
uiPrompts: "never"
|
|
6413
|
-
};
|
|
6414
6289
|
var DIR2 = "/env/here";
|
|
6415
6290
|
var PROMPT = "system";
|
|
6416
6291
|
function makeRequest3(overrides2 = {}) {
|
|
@@ -6422,7 +6297,6 @@ function makeRequest3(overrides2 = {}) {
|
|
|
6422
6297
|
// Behavioural fixtures use the model-less token (no `resolvedModel` emitted); a dedicated capture
|
|
6423
6298
|
// fixture sets a real model and effort.
|
|
6424
6299
|
config: { model: null },
|
|
6425
|
-
policy: COMPANION_POLICY2,
|
|
6426
6300
|
session: null,
|
|
6427
6301
|
cabane: { mcpUrl: "https://cabane.test/api/mcp", bearer: "tok", activeConversationId: "conv" },
|
|
6428
6302
|
local: { cwd: DIR2 },
|
|
@@ -7454,52 +7328,47 @@ function classifyStopError(message) {
|
|
|
7454
7328
|
}
|
|
7455
7329
|
|
|
7456
7330
|
// src/runner/protocol.ts
|
|
7457
|
-
import { z as
|
|
7331
|
+
import { z as z14 } from "zod";
|
|
7458
7332
|
|
|
7459
7333
|
// src/run-config.ts
|
|
7460
|
-
import { z as
|
|
7461
|
-
var mcpStdioServerSchema =
|
|
7462
|
-
type:
|
|
7463
|
-
command:
|
|
7464
|
-
args:
|
|
7465
|
-
env:
|
|
7334
|
+
import { z as z13 } from "zod";
|
|
7335
|
+
var mcpStdioServerSchema = z13.object({
|
|
7336
|
+
type: z13.literal("stdio").optional(),
|
|
7337
|
+
command: z13.string().min(1),
|
|
7338
|
+
args: z13.array(z13.string()).optional(),
|
|
7339
|
+
env: z13.record(z13.string(), z13.string()).optional(),
|
|
7466
7340
|
// Variable → the name of a `secretEnv` entry holding its value. Set by the server on a
|
|
7467
7341
|
// room's shared browser, whose connect URL is a credential.
|
|
7468
|
-
envFromEnv:
|
|
7342
|
+
envFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7469
7343
|
});
|
|
7470
|
-
var mcpHttpServerSchema =
|
|
7471
|
-
type:
|
|
7472
|
-
url:
|
|
7473
|
-
headers:
|
|
7344
|
+
var mcpHttpServerSchema = z13.object({
|
|
7345
|
+
type: z13.literal("http"),
|
|
7346
|
+
url: z13.string().url(),
|
|
7347
|
+
headers: z13.record(z13.string(), z13.string()).optional(),
|
|
7474
7348
|
// Header → the name of a `secretEnv` entry holding its value. Set by
|
|
7475
7349
|
// the server on a room connection's entry, and by `resolveMcpSecrets` on a
|
|
7476
7350
|
// header whose `${VAR}` it resolved.
|
|
7477
|
-
headersFromEnv:
|
|
7351
|
+
headersFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7478
7352
|
});
|
|
7479
|
-
var mcpSseServerSchema =
|
|
7480
|
-
type:
|
|
7481
|
-
url:
|
|
7482
|
-
headers:
|
|
7353
|
+
var mcpSseServerSchema = z13.object({
|
|
7354
|
+
type: z13.literal("sse"),
|
|
7355
|
+
url: z13.string().url(),
|
|
7356
|
+
headers: z13.record(z13.string(), z13.string()).optional(),
|
|
7483
7357
|
// Header → the name of a `secretEnv` entry holding its value. Set by
|
|
7484
7358
|
// the server on a room connection's entry, and by `resolveMcpSecrets` on a
|
|
7485
7359
|
// header whose `${VAR}` it resolved.
|
|
7486
|
-
headersFromEnv:
|
|
7360
|
+
headersFromEnv: z13.record(z13.string(), z13.string()).optional()
|
|
7487
7361
|
});
|
|
7488
|
-
var mcpServerDefSchema =
|
|
7489
|
-
var thinkingConfigSchema =
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7362
|
+
var mcpServerDefSchema = z13.union([mcpHttpServerSchema, mcpSseServerSchema, mcpStdioServerSchema]);
|
|
7363
|
+
var thinkingConfigSchema = z13.discriminatedUnion("type", [
|
|
7364
|
+
z13.object({ type: z13.literal("adaptive") }),
|
|
7365
|
+
z13.object({ type: z13.literal("enabled"), budgetTokens: z13.number().int().positive().optional() }),
|
|
7366
|
+
z13.object({ type: z13.literal("disabled") })
|
|
7493
7367
|
]);
|
|
7494
|
-
var effortSchema =
|
|
7495
|
-
var runConfigSchema =
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
// `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
|
|
7499
|
-
// stripped, so an older companion riding a newer server never rejects the config).
|
|
7500
|
-
hostAccess: z14.boolean().optional(),
|
|
7501
|
-
mcpServers: z14.record(z14.string(), mcpServerDefSchema).optional(),
|
|
7502
|
-
model: z14.string().min(1).optional(),
|
|
7368
|
+
var effortSchema = z13.enum(["low", "medium", "high", "xhigh", "max"]);
|
|
7369
|
+
var runConfigSchema = z13.object({
|
|
7370
|
+
mcpServers: z13.record(z13.string(), mcpServerDefSchema).optional(),
|
|
7371
|
+
model: z13.string().min(1).optional(),
|
|
7503
7372
|
thinking: thinkingConfigSchema.optional(),
|
|
7504
7373
|
effort: effortSchema.optional()
|
|
7505
7374
|
});
|
|
@@ -7527,56 +7396,56 @@ var RUNNER_ENV = {
|
|
|
7527
7396
|
// Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
|
|
7528
7397
|
spec: "CABANE_RUNNER_SPEC"
|
|
7529
7398
|
};
|
|
7530
|
-
var runtimeHealthSchema =
|
|
7531
|
-
quotaState:
|
|
7532
|
-
limitedUntil:
|
|
7533
|
-
lastError:
|
|
7399
|
+
var runtimeHealthSchema = z14.object({
|
|
7400
|
+
quotaState: z14.enum(["ok", "limited", "auth_expired", "unknown"]),
|
|
7401
|
+
limitedUntil: z14.string().nullable(),
|
|
7402
|
+
lastError: z14.string().nullable()
|
|
7534
7403
|
});
|
|
7535
|
-
var runnerSpecSchema =
|
|
7536
|
-
deviceId:
|
|
7404
|
+
var runnerSpecSchema = z14.object({
|
|
7405
|
+
deviceId: z14.string().optional(),
|
|
7537
7406
|
// The launcher's boot id: the one its heartbeat carries, so a runner's lease
|
|
7538
7407
|
// matches the device that holds it.
|
|
7539
|
-
bootId:
|
|
7540
|
-
workspaceId:
|
|
7541
|
-
workspaceSlug:
|
|
7542
|
-
agentId:
|
|
7543
|
-
agentUsername:
|
|
7544
|
-
agentName:
|
|
7408
|
+
bootId: z14.string(),
|
|
7409
|
+
workspaceId: z14.string(),
|
|
7410
|
+
workspaceSlug: z14.string(),
|
|
7411
|
+
agentId: z14.string(),
|
|
7412
|
+
agentUsername: z14.string(),
|
|
7413
|
+
agentName: z14.string(),
|
|
7545
7414
|
local: localAgentConfigSchema,
|
|
7546
7415
|
runConfig: runConfigSchema,
|
|
7547
7416
|
// The device's `root`, resolved by the launcher: the pair directory the turn
|
|
7548
7417
|
// stands in when nothing else names one lives under it (`work-dir.ts`).
|
|
7549
|
-
workRoot:
|
|
7550
|
-
opencodeServerUrl:
|
|
7551
|
-
claudeCodeAvailable:
|
|
7552
|
-
codexAvailable:
|
|
7553
|
-
idleTimeoutMs:
|
|
7554
|
-
totalTimeoutMs:
|
|
7555
|
-
leaseRenewalMs:
|
|
7556
|
-
preparingRowDelayMs:
|
|
7418
|
+
workRoot: z14.string(),
|
|
7419
|
+
opencodeServerUrl: z14.string().optional(),
|
|
7420
|
+
claudeCodeAvailable: z14.boolean(),
|
|
7421
|
+
codexAvailable: z14.boolean(),
|
|
7422
|
+
idleTimeoutMs: z14.number().int().positive().optional(),
|
|
7423
|
+
totalTimeoutMs: z14.number().int().positive().optional(),
|
|
7424
|
+
leaseRenewalMs: z14.number().int().positive().optional(),
|
|
7425
|
+
preparingRowDelayMs: z14.number().int().nonnegative().optional(),
|
|
7557
7426
|
// Whether the turn writes a transcript, into the device's own directory
|
|
7558
7427
|
// (`transcriptsDir()`).
|
|
7559
|
-
transcripts:
|
|
7560
|
-
logLevel:
|
|
7428
|
+
transcripts: z14.boolean(),
|
|
7429
|
+
logLevel: z14.string(),
|
|
7561
7430
|
// The launcher's connector-health observations, by runtime, as of the start.
|
|
7562
|
-
connectorHealth:
|
|
7431
|
+
connectorHealth: z14.record(z14.string(), runtimeHealthSchema),
|
|
7563
7432
|
// True when this (conversation, agent) pair was already told its session
|
|
7564
7433
|
// could not be saved: the notice is once per pair, not once per process.
|
|
7565
|
-
sessionWriteNoticeClaimed:
|
|
7434
|
+
sessionWriteNoticeClaimed: z14.boolean(),
|
|
7566
7435
|
// The runner's identity, minted with its credential. The start
|
|
7567
7436
|
// PATCH carries it so the lease knows which runner holds the span.
|
|
7568
|
-
runnerId:
|
|
7437
|
+
runnerId: z14.string()
|
|
7569
7438
|
});
|
|
7570
|
-
var dispatchSchema =
|
|
7571
|
-
type:
|
|
7572
|
-
agentId:
|
|
7573
|
-
deviceId:
|
|
7574
|
-
conversationId:
|
|
7575
|
-
messageId:
|
|
7576
|
-
episode:
|
|
7439
|
+
var dispatchSchema = z14.object({
|
|
7440
|
+
type: z14.literal("device:dispatch_requested"),
|
|
7441
|
+
agentId: z14.string(),
|
|
7442
|
+
deviceId: z14.string(),
|
|
7443
|
+
conversationId: z14.string(),
|
|
7444
|
+
messageId: z14.string(),
|
|
7445
|
+
episode: z14.number().int().optional()
|
|
7577
7446
|
});
|
|
7578
|
-
var credentialsSchema =
|
|
7579
|
-
credential:
|
|
7447
|
+
var credentialsSchema = z14.object({
|
|
7448
|
+
credential: z14.string().startsWith("cabrn_")
|
|
7580
7449
|
});
|
|
7581
7450
|
function runnerEnv(input) {
|
|
7582
7451
|
return {
|
|
@@ -7965,7 +7834,7 @@ import { join as join13 } from "path";
|
|
|
7965
7834
|
|
|
7966
7835
|
// src/turn-control-tools.ts
|
|
7967
7836
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
7968
|
-
import { z as
|
|
7837
|
+
import { z as z15 } from "zod";
|
|
7969
7838
|
var COMPANION_LOCAL_MCP_SERVER = "cabane_companion";
|
|
7970
7839
|
var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
|
|
7971
7840
|
var ASK_TOOL = "ask";
|
|
@@ -7977,20 +7846,21 @@ var WAKE_ME_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${WAKE_ME_TOOL}`;
|
|
|
7977
7846
|
var CANCEL_WAKE_TOOL = "cancel_wake";
|
|
7978
7847
|
var CANCEL_WAKE_TOOL_NAME = `mcp__${COMPANION_LOCAL_MCP_SERVER}__${CANCEL_WAKE_TOOL}`;
|
|
7979
7848
|
|
|
7980
|
-
// src/codex-
|
|
7981
|
-
import {
|
|
7982
|
-
import {
|
|
7849
|
+
// src/codex-config.ts
|
|
7850
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
7851
|
+
import { homedir as homedir6 } from "os";
|
|
7983
7852
|
import { join as join14 } from "path";
|
|
7853
|
+
import { parse } from "smol-toml";
|
|
7984
7854
|
|
|
7985
7855
|
// src/codex-images.ts
|
|
7986
|
-
import { mkdtemp
|
|
7987
|
-
import { tmpdir
|
|
7856
|
+
import { mkdtemp, rm, writeFile } from "fs/promises";
|
|
7857
|
+
import { tmpdir } from "os";
|
|
7988
7858
|
import { join as join15 } from "path";
|
|
7989
7859
|
|
|
7990
7860
|
// src/codex-compactions.ts
|
|
7991
7861
|
import { createReadStream } from "fs";
|
|
7992
7862
|
import { readdir } from "fs/promises";
|
|
7993
|
-
import { homedir as
|
|
7863
|
+
import { homedir as homedir7 } from "os";
|
|
7994
7864
|
import { join as join16 } from "path";
|
|
7995
7865
|
import { createInterface as createInterface2 } from "readline";
|
|
7996
7866
|
|
|
@@ -8005,12 +7875,12 @@ import { join as join17 } from "path";
|
|
|
8005
7875
|
// src/secrets.ts
|
|
8006
7876
|
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
8007
7877
|
import { join as join18 } from "path";
|
|
8008
|
-
import { z as
|
|
7878
|
+
import { z as z16 } from "zod";
|
|
8009
7879
|
var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
|
|
8010
7880
|
function secretsPath() {
|
|
8011
7881
|
return join18(cabaneDir(), "secrets.json");
|
|
8012
7882
|
}
|
|
8013
|
-
var secretStoreSchema =
|
|
7883
|
+
var secretStoreSchema = z16.record(z16.string(), z16.string());
|
|
8014
7884
|
function loadSecretStore() {
|
|
8015
7885
|
const path = secretsPath();
|
|
8016
7886
|
if (!existsSync10(path)) return makeStore({});
|
|
@@ -8361,7 +8231,7 @@ var DeviceLocks = class {
|
|
|
8361
8231
|
// src/manifest.ts
|
|
8362
8232
|
var DEVICE_MANIFEST = {
|
|
8363
8233
|
runtimes: [{ name: "claude-code", version: null }],
|
|
8364
|
-
capabilities: {
|
|
8234
|
+
capabilities: { browser: true, userMcp: true }
|
|
8365
8235
|
};
|
|
8366
8236
|
function buildCompanionManifest(opts) {
|
|
8367
8237
|
const v = opts.versions ?? {};
|
|
@@ -8465,7 +8335,7 @@ function emptyHarnessVersions() {
|
|
|
8465
8335
|
}
|
|
8466
8336
|
|
|
8467
8337
|
// src/harness-copy.ts
|
|
8468
|
-
import { homedir as
|
|
8338
|
+
import { homedir as homedir8 } from "os";
|
|
8469
8339
|
import { join as join21 } from "path";
|
|
8470
8340
|
var HARNESS_LABELS = {
|
|
8471
8341
|
"claude-code": "Claude Code",
|
|
@@ -8480,7 +8350,7 @@ function failureText(runtime, reason) {
|
|
|
8480
8350
|
case "spawn":
|
|
8481
8351
|
return `Couldn't check whether ${label} is signed in: Cabane's bundled ${label} wouldn't start. Repair it with ${repairSteps()} and try again.${spaced(repairCost())}`;
|
|
8482
8352
|
case "config":
|
|
8483
|
-
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(
|
|
8353
|
+
return `Couldn't check whether ${label} is signed in: its configuration file didn't load${runtime === "codex" ? ` (${tildePath(join21(homedir8(), ".codex", "config.toml"))})` : ""}. Fix it, then try again.`;
|
|
8484
8354
|
case "unreadable":
|
|
8485
8355
|
return `Couldn't check whether ${label} is signed in: it answered in a way Cabane couldn't read. Try again, and if it keeps happening, update Cabane.`;
|
|
8486
8356
|
}
|
|
@@ -9227,27 +9097,27 @@ function compareVersions(a, b) {
|
|
|
9227
9097
|
// src/update-state.ts
|
|
9228
9098
|
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, renameSync as renameSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
9229
9099
|
import { dirname as dirname10, join as join25 } from "path";
|
|
9230
|
-
import { z as
|
|
9231
|
-
var resultSchema =
|
|
9232
|
-
kind:
|
|
9233
|
-
from:
|
|
9234
|
-
to:
|
|
9235
|
-
at:
|
|
9236
|
-
reason:
|
|
9100
|
+
import { z as z17 } from "zod";
|
|
9101
|
+
var resultSchema = z17.object({
|
|
9102
|
+
kind: z17.enum(["updated", "rolled_back", "failed"]),
|
|
9103
|
+
from: z17.string(),
|
|
9104
|
+
to: z17.string(),
|
|
9105
|
+
at: z17.string(),
|
|
9106
|
+
reason: z17.string().optional()
|
|
9237
9107
|
});
|
|
9238
|
-
var pendingSchema =
|
|
9239
|
-
from:
|
|
9108
|
+
var pendingSchema = z17.object({
|
|
9109
|
+
from: z17.string(),
|
|
9240
9110
|
// The package root `current` pointed at before the swap, so a rollback can point it back.
|
|
9241
|
-
fromRoot:
|
|
9242
|
-
to:
|
|
9243
|
-
at:
|
|
9244
|
-
boots:
|
|
9111
|
+
fromRoot: z17.string(),
|
|
9112
|
+
to: z17.string(),
|
|
9113
|
+
at: z17.string(),
|
|
9114
|
+
boots: z17.number().int().nonnegative()
|
|
9245
9115
|
});
|
|
9246
|
-
var stateSchema =
|
|
9247
|
-
server:
|
|
9116
|
+
var stateSchema = z17.object({
|
|
9117
|
+
server: z17.object({ latest: z17.string(), minimum: z17.string(), at: z17.string() }).optional(),
|
|
9248
9118
|
pending: pendingSchema.optional(),
|
|
9249
9119
|
lastResult: resultSchema.optional(),
|
|
9250
|
-
failed:
|
|
9120
|
+
failed: z17.object({ version: z17.string(), attempts: z17.number().int(), at: z17.string() }).optional()
|
|
9251
9121
|
});
|
|
9252
9122
|
function updateStatePath(dir2 = cabaneDir()) {
|
|
9253
9123
|
return join25(dir2, "update.json");
|
|
@@ -9762,6 +9632,7 @@ var CompanionSupervisor = class {
|
|
|
9762
9632
|
pollTimer = null;
|
|
9763
9633
|
refreshing = false;
|
|
9764
9634
|
stopped = false;
|
|
9635
|
+
reconciliationAbort = new AbortController();
|
|
9765
9636
|
// The version-skew warning is logged once, not every heartbeat.
|
|
9766
9637
|
versionSkewWarned = false;
|
|
9767
9638
|
// This device's id, captured from the heartbeat / assignments response. The SSE
|
|
@@ -9807,6 +9678,7 @@ var CompanionSupervisor = class {
|
|
|
9807
9678
|
// heartbeat + poll loops. A companion with no device token (logged out) does
|
|
9808
9679
|
// nothing but say so.
|
|
9809
9680
|
async start() {
|
|
9681
|
+
if (this.stopped) return;
|
|
9810
9682
|
this.log.info(
|
|
9811
9683
|
{ protocolVersion: TURN_PROTOCOL_VERSION, version: COMPANION_VERSION },
|
|
9812
9684
|
`Companion ${COMPANION_VERSION} starting on ${this.config.deviceLabel ?? hostname2()}`
|
|
@@ -9891,6 +9763,7 @@ var CompanionSupervisor = class {
|
|
|
9891
9763
|
});
|
|
9892
9764
|
this.updater.start();
|
|
9893
9765
|
await this.refreshAssignments();
|
|
9766
|
+
if (this.stopped) return;
|
|
9894
9767
|
this.deviceSub.start();
|
|
9895
9768
|
this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
9896
9769
|
this.heartbeatTimer.unref?.();
|
|
@@ -10003,10 +9876,12 @@ var CompanionSupervisor = class {
|
|
|
10003
9876
|
let items;
|
|
10004
9877
|
let device;
|
|
10005
9878
|
try {
|
|
10006
|
-
const resp = await this.deviceApi.getAssignments();
|
|
9879
|
+
const resp = await this.deviceApi.getAssignments(this.reconciliationAbort.signal);
|
|
9880
|
+
if (this.stopped) return;
|
|
10007
9881
|
items = resp.assignments;
|
|
10008
9882
|
device = resp.device;
|
|
10009
9883
|
} catch (err) {
|
|
9884
|
+
if (this.stopped) return;
|
|
10010
9885
|
this.log.error(
|
|
10011
9886
|
{ ...apiErrorLogFields(err) },
|
|
10012
9887
|
"Couldn't load the assigned agents. Check that this device is active in Cabane; trying again shortly."
|
|
@@ -10025,6 +9900,7 @@ var CompanionSupervisor = class {
|
|
|
10025
9900
|
if (it.credential) setCredential(device.id, it.agentId, it.credential);
|
|
10026
9901
|
}
|
|
10027
9902
|
items = await this.restoreMissingCredentials(device.id, items);
|
|
9903
|
+
if (this.stopped) return;
|
|
10028
9904
|
this.uncredentialed = new Set(
|
|
10029
9905
|
items.filter((it) => !it.credential && !getCredential(device.id, it.agentId)).map((it) => `${it.workspaceId}|${it.agentId}`)
|
|
10030
9906
|
);
|
|
@@ -10037,6 +9913,7 @@ var CompanionSupervisor = class {
|
|
|
10037
9913
|
for (const wsId of [...this.workspaces.keys()]) {
|
|
10038
9914
|
if (!byWorkspace.has(wsId)) await this.removeWorkspace(wsId);
|
|
10039
9915
|
}
|
|
9916
|
+
if (this.stopped) return;
|
|
10040
9917
|
for (const [wsId, list] of byWorkspace) this.reconcileWorkspace(device.id, wsId, list);
|
|
10041
9918
|
const pruned = pruneCredentials(
|
|
10042
9919
|
device.id,
|
|
@@ -10068,6 +9945,7 @@ var CompanionSupervisor = class {
|
|
|
10068
9945
|
async restoreMissingCredentials(deviceId, items) {
|
|
10069
9946
|
const restored = [];
|
|
10070
9947
|
for (const it of items) {
|
|
9948
|
+
if (this.stopped) return restored;
|
|
10071
9949
|
if (it.credential || getCredential(deviceId, it.agentId)) {
|
|
10072
9950
|
restored.push(it);
|
|
10073
9951
|
continue;
|
|
@@ -10079,7 +9957,11 @@ var CompanionSupervisor = class {
|
|
|
10079
9957
|
continue;
|
|
10080
9958
|
}
|
|
10081
9959
|
try {
|
|
10082
|
-
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
9960
|
+
const { credential } = await this.deviceApi.reissueAgentCredential(
|
|
9961
|
+
it.agentId,
|
|
9962
|
+
this.reconciliationAbort.signal
|
|
9963
|
+
);
|
|
9964
|
+
if (this.stopped) return restored;
|
|
10083
9965
|
setCredential(deviceId, it.agentId, credential);
|
|
10084
9966
|
this.log.info(
|
|
10085
9967
|
{ agentId: it.agentId, agentName: it.agentDisplayName },
|
|
@@ -10143,7 +10025,6 @@ var CompanionSupervisor = class {
|
|
|
10143
10025
|
agentId: it.agentId,
|
|
10144
10026
|
username: it.agentUsername,
|
|
10145
10027
|
displayName: it.agentDisplayName,
|
|
10146
|
-
mode: runConfig.hostAccess ? "full" : "none",
|
|
10147
10028
|
hasCredential: false,
|
|
10148
10029
|
missingSecrets: missing
|
|
10149
10030
|
});
|
|
@@ -10163,7 +10044,6 @@ var CompanionSupervisor = class {
|
|
|
10163
10044
|
agentId: it.agentId,
|
|
10164
10045
|
username: it.agentUsername,
|
|
10165
10046
|
displayName: it.agentDisplayName,
|
|
10166
|
-
mode: runConfig.hostAccess ? "full" : "none",
|
|
10167
10047
|
hasCredential: true,
|
|
10168
10048
|
missingSecrets: missing
|
|
10169
10049
|
});
|
|
@@ -10956,6 +10836,7 @@ var CompanionSupervisor = class {
|
|
|
10956
10836
|
// ---- control actions ----
|
|
10957
10837
|
async shutdown() {
|
|
10958
10838
|
this.stopped = true;
|
|
10839
|
+
this.reconciliationAbort.abort();
|
|
10959
10840
|
this.updater?.stop();
|
|
10960
10841
|
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
10961
10842
|
if (this.detectTimer) clearInterval(this.detectTimer);
|
|
@@ -11231,33 +11112,46 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
11231
11112
|
});
|
|
11232
11113
|
let restartForUpdate = async () => {
|
|
11233
11114
|
};
|
|
11234
|
-
|
|
11235
|
-
config: cfg,
|
|
11236
|
-
log,
|
|
11237
|
-
hub,
|
|
11238
|
-
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
11239
|
-
...embeddedRunner({
|
|
11240
|
-
electron: Boolean(process.versions.electron),
|
|
11241
|
-
execPath: process.execPath,
|
|
11242
|
-
moduleUrl: import.meta.url,
|
|
11243
|
-
env: process.env
|
|
11244
|
-
}) ?? {},
|
|
11245
|
-
restartForUpdate: () => restartForUpdate()
|
|
11246
|
-
});
|
|
11247
|
-
await supervisor.start();
|
|
11115
|
+
let supervisor;
|
|
11248
11116
|
let control = null;
|
|
11249
11117
|
let dashboard = null;
|
|
11118
|
+
let stopped = false;
|
|
11119
|
+
const stop = async () => {
|
|
11120
|
+
if (stopped) return;
|
|
11121
|
+
stopped = true;
|
|
11122
|
+
clearRuntimeStateIfOurs(instanceId);
|
|
11123
|
+
process.removeListener("exit", clearRuntimeOnExit);
|
|
11124
|
+
await Promise.allSettled([
|
|
11125
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
11126
|
+
...control ? [control.close()] : [],
|
|
11127
|
+
...dashboard ? [dashboard.close()] : []
|
|
11128
|
+
]);
|
|
11129
|
+
};
|
|
11250
11130
|
try {
|
|
11131
|
+
supervisor = new CompanionSupervisor({
|
|
11132
|
+
config: cfg,
|
|
11133
|
+
log,
|
|
11134
|
+
hub,
|
|
11135
|
+
...opts.checkHarness ? { checkHarness: opts.checkHarness } : {},
|
|
11136
|
+
...embeddedRunner({
|
|
11137
|
+
electron: Boolean(process.versions.electron),
|
|
11138
|
+
execPath: process.execPath,
|
|
11139
|
+
moduleUrl: import.meta.url,
|
|
11140
|
+
env: process.env
|
|
11141
|
+
}) ?? {},
|
|
11142
|
+
restartForUpdate: () => restartForUpdate()
|
|
11143
|
+
});
|
|
11144
|
+
const active2 = supervisor;
|
|
11251
11145
|
control = await startControlServer({
|
|
11252
11146
|
status: () => ({
|
|
11253
11147
|
...hub.statusJson(),
|
|
11254
|
-
...
|
|
11255
|
-
detection:
|
|
11148
|
+
...active2.lastHeartbeat(),
|
|
11149
|
+
detection: active2.currentDetection()
|
|
11256
11150
|
}),
|
|
11257
|
-
detect: () =>
|
|
11258
|
-
allow: (runtime) =>
|
|
11259
|
-
update: (auto) =>
|
|
11260
|
-
stop: () => void
|
|
11151
|
+
detect: () => active2.redetect(),
|
|
11152
|
+
allow: (runtime) => active2.allowHarness(runtime),
|
|
11153
|
+
update: (auto) => active2.update(auto),
|
|
11154
|
+
stop: () => void stop().then(() => active2.requestStop())
|
|
11261
11155
|
});
|
|
11262
11156
|
if (opts.dashboard) {
|
|
11263
11157
|
const preferredPort = opts.port ?? cfg.dashboardPort;
|
|
@@ -11268,6 +11162,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
11268
11162
|
});
|
|
11269
11163
|
hub.setDashboardUrl(dashboard.url);
|
|
11270
11164
|
}
|
|
11165
|
+
if (stopped) throw new Error("The companion was stopped during startup.");
|
|
11271
11166
|
writeRuntimeState({
|
|
11272
11167
|
pid: process.pid,
|
|
11273
11168
|
socket: control.path,
|
|
@@ -11285,7 +11180,7 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
11285
11180
|
clearRuntimeStateIfOurs(instanceId);
|
|
11286
11181
|
process.removeListener("exit", clearRuntimeOnExit);
|
|
11287
11182
|
await Promise.allSettled([
|
|
11288
|
-
supervisor.shutdown(),
|
|
11183
|
+
...supervisor ? [supervisor.shutdown()] : [],
|
|
11289
11184
|
...control ? [control.close()] : [],
|
|
11290
11185
|
...dashboard ? [dashboard.close()] : []
|
|
11291
11186
|
]);
|
|
@@ -11297,22 +11192,17 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
11297
11192
|
});
|
|
11298
11193
|
throw err;
|
|
11299
11194
|
}
|
|
11300
|
-
|
|
11301
|
-
const stop = async () => {
|
|
11302
|
-
if (stopped) return;
|
|
11303
|
-
stopped = true;
|
|
11304
|
-
clearRuntimeStateIfOurs(instanceId);
|
|
11305
|
-
process.removeListener("exit", clearRuntimeOnExit);
|
|
11306
|
-
try {
|
|
11307
|
-
await supervisor.shutdown();
|
|
11308
|
-
await closeSurfaces(control, dashboard);
|
|
11309
|
-
} catch {
|
|
11310
|
-
}
|
|
11311
|
-
};
|
|
11195
|
+
const active = supervisor;
|
|
11312
11196
|
restartForUpdate = async () => {
|
|
11313
11197
|
await stop();
|
|
11314
11198
|
process.exit(0);
|
|
11315
11199
|
};
|
|
11200
|
+
void active.start().catch(async (err) => {
|
|
11201
|
+
log.error({ err }, "Couldn't initialize the companion.");
|
|
11202
|
+
recordCrash({ reason: String(err), origin: "startup", at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
11203
|
+
await stop();
|
|
11204
|
+
process.exitCode = 1;
|
|
11205
|
+
});
|
|
11316
11206
|
return {
|
|
11317
11207
|
ok: true,
|
|
11318
11208
|
runtime: {
|
|
@@ -11320,16 +11210,12 @@ async function createCompanionRuntime(opts = {}) {
|
|
|
11320
11210
|
socketPath: control.path,
|
|
11321
11211
|
config: cfg,
|
|
11322
11212
|
stop,
|
|
11323
|
-
heartbeatNow: () =>
|
|
11213
|
+
heartbeatNow: () => active.heartbeatNow(),
|
|
11324
11214
|
harnesses: () => hub.statusJson().harnesses ?? [],
|
|
11325
|
-
detect: () =>
|
|
11215
|
+
detect: () => active.redetect()
|
|
11326
11216
|
}
|
|
11327
11217
|
};
|
|
11328
11218
|
}
|
|
11329
|
-
async function closeSurfaces(control, dashboard) {
|
|
11330
|
-
await control.close();
|
|
11331
|
-
if (dashboard) await dashboard.close();
|
|
11332
|
-
}
|
|
11333
11219
|
export {
|
|
11334
11220
|
createCompanionRuntime,
|
|
11335
11221
|
embeddedRunner,
|