@cabane/companion 0.6.178 → 0.6.179

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/runtime.js CHANGED
@@ -1714,16 +1714,49 @@ async function verifyRuntime(state, requestImpl = controlRequest) {
1714
1714
  return body.instance_id === state.instanceId ? "ours" : "stale";
1715
1715
  }
1716
1716
 
1717
+ // src/heartbeat-status.ts
1718
+ import { z as z3 } from "zod";
1719
+
1720
+ // src/device-state.ts
1721
+ var DEVICE_STATES = ["waiting", "online", "offline", "deactivated"];
1722
+ var HARNESS_STATES = [
1723
+ "checking",
1724
+ "connected",
1725
+ "needs_sign_in",
1726
+ "limited",
1727
+ "problem",
1728
+ "off",
1729
+ "gone"
1730
+ ];
1731
+ var DEVICE_HEALTH_STATES = ["checking", "ready", "degraded"];
1732
+
1733
+ // src/heartbeat-status.ts
1734
+ var acknowledgedDeviceSchema = z3.object({
1735
+ state: z3.enum(DEVICE_STATES),
1736
+ health: z3.enum(DEVICE_HEALTH_STATES).nullable(),
1737
+ harnesses: z3.array(z3.object({ runtime: z3.string(), state: z3.enum(HARNESS_STATES) }))
1738
+ });
1739
+ var heartbeatStatusSchema = z3.object({
1740
+ lastHeartbeatAckAt: z3.string().datetime().nullable().default(null),
1741
+ lastAcknowledgedDevice: acknowledgedDeviceSchema.nullable().default(null),
1742
+ lastHeartbeatRefused: z3.boolean().default(false)
1743
+ });
1744
+ var emptyHeartbeatStatus = () => ({
1745
+ lastHeartbeatAckAt: null,
1746
+ lastAcknowledgedDevice: null,
1747
+ lastHeartbeatRefused: false
1748
+ });
1749
+
1717
1750
  // src/supervisor.ts
1718
1751
  import { hostname as hostname2 } from "os";
1719
1752
 
1720
1753
  // packages/agent-runtime/src/failure.ts
1721
- import { z as z3 } from "zod";
1722
- var turnFailureSchema = z3.discriminatedUnion("kind", [
1723
- z3.object({ kind: z3.literal("usage_capped"), resetsAt: z3.string().optional() }),
1724
- z3.object({ kind: z3.literal("rate_limited") }),
1725
- z3.object({ kind: z3.literal("server_error") }),
1726
- z3.object({ kind: z3.literal("auth_expired") })
1754
+ import { z as z4 } from "zod";
1755
+ var turnFailureSchema = z4.discriminatedUnion("kind", [
1756
+ z4.object({ kind: z4.literal("usage_capped"), resetsAt: z4.string().optional() }),
1757
+ z4.object({ kind: z4.literal("rate_limited") }),
1758
+ z4.object({ kind: z4.literal("server_error") }),
1759
+ z4.object({ kind: z4.literal("auth_expired") })
1727
1760
  ]);
1728
1761
  var USAGE_CAPPED = "usage_capped";
1729
1762
  var RATE_LIMITED = "rate_limited";
@@ -2552,8 +2585,8 @@ import {
2552
2585
  writeFileSync as writeFileSync3
2553
2586
  } from "fs";
2554
2587
  import { join as join11 } from "path";
2555
- import { z as z4 } from "zod";
2556
- var credentialMapSchema = z4.record(z4.string(), z4.string());
2588
+ import { z as z5 } from "zod";
2589
+ var credentialMapSchema = z5.record(z5.string(), z5.string());
2557
2590
  function credentialsDir() {
2558
2591
  return join11(cabaneDir(), "credentials");
2559
2592
  }
@@ -2769,34 +2802,34 @@ function noResume() {
2769
2802
  var TURN_PROTOCOL_VERSION = 1;
2770
2803
 
2771
2804
  // packages/agent-runtime/src/host-policy.ts
2772
- import { z as z5 } from "zod";
2773
- var hostPolicySchema = z5.object({
2805
+ import { z as z6 } from "zod";
2806
+ var hostPolicySchema = z6.object({
2774
2807
  // Host filesystem and shell: `Bash`/`Read`/`Write`/`Edit`/`Glob`/`Grep`, the notebook pair,
2775
2808
  // git-worktree tools and `REPL`. Off on the locked assistant surface; on in the coding posture.
2776
- hostFs: z5.boolean(),
2809
+ hostFs: z6.boolean(),
2777
2810
  // Public web (`WebSearch` / `WebFetch`). Not host reach; a grant like the rest.
2778
- web: z5.boolean(),
2811
+ web: z6.boolean(),
2779
2812
  // Browser automation (the Playwright MCP surface). Varies by host.
2780
- browser: z5.boolean(),
2813
+ browser: z6.boolean(),
2781
2814
  // User-configured MCP servers permitted.
2782
- userMcp: z5.boolean(),
2815
+ userMcp: z6.boolean(),
2783
2816
  // Subagent dispatch (`Task` / `Agent`) — a grant: `false` on the locked surface, `true` in the
2784
2817
  // coding posture, where the coding allow-hook reaches sub-agent tool calls. A subagent completes
2785
2818
  // within the turn, so it's not an invariant like `scheduling`.
2786
- subagents: z5.boolean(),
2819
+ subagents: z6.boolean(),
2787
2820
  // ── Hard platform invariants — always denied, never granted ────────────────
2788
2821
  // Deferred re-invocation / scheduling (`ScheduleWakeup`, the `Cron*` / `Task*` families,
2789
2822
  // `Monitor`, `Workflow`, …). A turn is one query that resolves on `result`; a scheduled callback
2790
2823
  // fires after the reply window has closed and strands the agent.
2791
- scheduling: z5.literal("never"),
2824
+ scheduling: z6.literal("never"),
2792
2825
  // Human-facing UI prompts (`AskUserQuestion`). Nothing in a Cabane conversation can answer a
2793
2826
  // structured prompt, so the call hangs the turn. The agent asks in its reply instead.
2794
- uiPrompts: z5.literal("never")
2827
+ uiPrompts: z6.literal("never")
2795
2828
  });
2796
2829
 
2797
2830
  // packages/agent-runtime/src/turn-event.ts
2798
- import { z as z6 } from "zod";
2799
- var turnEventSchema = z6.discriminatedUnion("type", [
2831
+ import { z as z7 } from "zod";
2832
+ var turnEventSchema = z7.discriminatedUnion("type", [
2800
2833
  // The runtime's opaque session state, emitted when the adapter learns it (e.g. the SDK
2801
2834
  // `system/init` frame). The platform stores `state` verbatim per (conversation, agent) and
2802
2835
  // hands it back next turn; only the adapter knows what it means.
@@ -2808,21 +2841,21 @@ var turnEventSchema = z6.discriminatedUnion("type", [
2808
2841
  // server rewinds the mark, so the next turn rebuilds a full catch-up (this turn is unavoidably
2809
2842
  // lossy — the degrade is known only after the manifest was committed). A plain boolean; the
2810
2843
  // runtime-specific reason stays in the adapter's `onWarn` log. Optional.
2811
- z6.object({
2812
- type: z6.literal("session"),
2813
- state: z6.string(),
2814
- degraded: z6.boolean().optional()
2844
+ z7.object({
2845
+ type: z7.literal("session"),
2846
+ state: z7.string(),
2847
+ degraded: z7.boolean().optional()
2815
2848
  }),
2816
2849
  // One readable thinking summary. Transient — surfaced live, never persisted as content.
2817
- z6.object({ type: z6.literal("thinking"), text: z6.string() }),
2850
+ z7.object({ type: z7.literal("thinking"), text: z7.string() }),
2818
2851
  // Assistant text. `terminal: false` is interim narration (commits as `progress`);
2819
2852
  // `terminal: true` is the turn's closing reply (commits as `final`).
2820
- z6.object({ type: z6.literal("text"), body: z6.string(), terminal: z6.boolean() }),
2853
+ z7.object({ type: z7.literal("text"), body: z7.string(), terminal: z7.boolean() }),
2821
2854
  // Runtime/provider-authored prose about the turn — a failure's explanation, or a line the
2822
2855
  // harness wrote on a turn it recovered. Not the agent's words: the pump persists it as
2823
2856
  // `runtime_notice`, drawn in the SystemNote voice. Adapters emit it only from positive runtime
2824
2857
  // evidence; the web never classifies English strings.
2825
- z6.object({ type: z6.literal("runtime_notice"), body: z6.string() }),
2858
+ z7.object({ type: z7.literal("runtime_notice"), body: z7.string() }),
2826
2859
  // A tool-activity transition. `name` is prefix-stripped (`cabane_read`, not
2827
2860
  // `mcp__cabane__cabane_read`); `summary` is the short card label; `phase` pairs `start` with a
2828
2861
  // later `done`/`error` on the same `id`.
@@ -2831,15 +2864,15 @@ var turnEventSchema = z6.discriminatedUnion("type", [
2831
2864
  // log are a consumer concern (docs/turn-protocol.md). `mcpServer` is the MCP server the call
2832
2865
  // came from (`cabane`, `playwright`, …), derived before `name` dropped the prefix; null for a
2833
2866
  // host tool. Optional, read as null when absent.
2834
- z6.object({
2835
- type: z6.literal("tool"),
2836
- id: z6.string(),
2837
- name: z6.string(),
2838
- phase: z6.enum(["start", "done", "error"]),
2839
- summary: z6.string(),
2840
- input: z6.unknown().optional(),
2841
- result: z6.unknown().optional(),
2842
- mcpServer: z6.string().nullable().optional()
2867
+ z7.object({
2868
+ type: z7.literal("tool"),
2869
+ id: z7.string(),
2870
+ name: z7.string(),
2871
+ phase: z7.enum(["start", "done", "error"]),
2872
+ summary: z7.string(),
2873
+ input: z7.unknown().optional(),
2874
+ result: z7.unknown().optional(),
2875
+ mcpServer: z7.string().nullable().optional()
2843
2876
  }),
2844
2877
  // The turn's terminal outcome. `ok:false` carries a machine reason (`no_session`, an error
2845
2878
  // code), surfaced by the host's clear-PATCH as `errorReason`.
@@ -2860,70 +2893,70 @@ var turnEventSchema = z6.discriminatedUnion("type", [
2860
2893
  // started with); `resolvedConfig` the knobs the harness reported (`{ reasoningEffort }` for
2861
2894
  // codex). Known only after the run streams, so they ride this event home and the host relays
2862
2895
  // them on settle. A cancel has no result event at all.
2863
- z6.object({
2864
- type: z6.literal("result"),
2865
- ok: z6.boolean(),
2866
- reason: z6.string().optional(),
2867
- usage: z6.object({
2868
- inputTokens: z6.number(),
2869
- outputTokens: z6.number(),
2870
- cacheReadTokens: z6.number().optional(),
2871
- cacheCreationTokens: z6.number().optional(),
2872
- contextTokens: z6.number().optional(),
2873
- contextWindow: z6.number().optional(),
2896
+ z7.object({
2897
+ type: z7.literal("result"),
2898
+ ok: z7.boolean(),
2899
+ reason: z7.string().optional(),
2900
+ usage: z7.object({
2901
+ inputTokens: z7.number(),
2902
+ outputTokens: z7.number(),
2903
+ cacheReadTokens: z7.number().optional(),
2904
+ cacheCreationTokens: z7.number().optional(),
2905
+ contextTokens: z7.number().optional(),
2906
+ contextWindow: z7.number().optional(),
2874
2907
  // The rest of the harness's context reading, when it gave one: where it will compact, its
2875
2908
  // `/context` breakdown, and every compaction it reported. Claude Code only.
2876
- contextCompactAt: z6.number().optional(),
2877
- contextCategories: z6.array(
2878
- z6.object({
2879
- name: z6.string(),
2880
- tokens: z6.number(),
2881
- kind: z6.enum(["used", "free", "buffer", "deferred"])
2909
+ contextCompactAt: z7.number().optional(),
2910
+ contextCategories: z7.array(
2911
+ z7.object({
2912
+ name: z7.string(),
2913
+ tokens: z7.number(),
2914
+ kind: z7.enum(["used", "free", "buffer", "deferred"])
2882
2915
  })
2883
2916
  ).optional(),
2884
- contextCompactions: z6.array(
2885
- z6.object({
2886
- trigger: z6.enum(["manual", "auto"]),
2887
- preTokens: z6.number(),
2888
- postTokens: z6.number().optional()
2917
+ contextCompactions: z7.array(
2918
+ z7.object({
2919
+ trigger: z7.enum(["manual", "auto"]),
2920
+ preTokens: z7.number(),
2921
+ postTokens: z7.number().optional()
2889
2922
  })
2890
2923
  ).optional()
2891
2924
  }).optional(),
2892
- resolvedModel: z6.string().optional(),
2893
- resolvedConfig: z6.object({
2894
- effort: z6.string().optional(),
2895
- thinking: z6.string().optional(),
2896
- reasoningEffort: z6.string().optional()
2925
+ resolvedModel: z7.string().optional(),
2926
+ resolvedConfig: z7.object({
2927
+ effort: z7.string().optional(),
2928
+ thinking: z7.string().optional(),
2929
+ reasoningEffort: z7.string().optional()
2897
2930
  }).optional(),
2898
2931
  // The harness-reported MCP inventory from this turn's init frame. Diagnostic only: names,
2899
2932
  // statuses and a count — never server definitions, credentials or session ids.
2900
2933
  // `initReceived:false` tells a missing init frame from a real empty inventory.
2901
- mcpInventory: z6.object({
2902
- initReceived: z6.boolean(),
2903
- servers: z6.array(z6.object({ name: z6.string(), status: z6.string() })),
2904
- toolCount: z6.number().int().nonnegative()
2934
+ mcpInventory: z7.object({
2935
+ initReceived: z7.boolean(),
2936
+ servers: z7.array(z7.object({ name: z7.string(), status: z7.string() })),
2937
+ toolCount: z7.number().int().nonnegative()
2905
2938
  }).optional()
2906
2939
  })
2907
2940
  ]);
2908
2941
 
2909
2942
  // packages/agent-runtime/src/turn-diagnostics.ts
2910
- import { z as z7 } from "zod";
2911
- var turnResultReasonSchema = z7.discriminatedUnion("kind", [
2912
- z7.object({ kind: z7.literal("usage_capped"), resetsAt: z7.string().optional() }),
2913
- z7.object({ kind: z7.literal("rate_limited") }),
2914
- z7.object({ kind: z7.literal("server_error") }),
2915
- z7.object({ kind: z7.literal("auth_expired") }),
2916
- z7.object({ kind: z7.literal("no_result") }),
2917
- z7.object({ kind: z7.literal("empty_result") }),
2918
- z7.object({ kind: z7.literal("empty_result_unverified") }),
2919
- z7.object({ kind: z7.literal("timeout_idle") }),
2920
- z7.object({ kind: z7.literal("timeout_total") }),
2921
- z7.object({ kind: z7.literal("cancelled") }),
2922
- z7.object({ kind: z7.literal("lease_lost") }),
2923
- z7.object({ kind: z7.literal("skipped") }),
2924
- z7.object({ kind: z7.literal("session_start_failed") }),
2925
- z7.object({ kind: z7.literal("workspace_tools_missing") }),
2926
- z7.object({ kind: z7.literal("runtime_error") })
2943
+ import { z as z8 } from "zod";
2944
+ var turnResultReasonSchema = z8.discriminatedUnion("kind", [
2945
+ z8.object({ kind: z8.literal("usage_capped"), resetsAt: z8.string().optional() }),
2946
+ z8.object({ kind: z8.literal("rate_limited") }),
2947
+ z8.object({ kind: z8.literal("server_error") }),
2948
+ z8.object({ kind: z8.literal("auth_expired") }),
2949
+ z8.object({ kind: z8.literal("no_result") }),
2950
+ z8.object({ kind: z8.literal("empty_result") }),
2951
+ z8.object({ kind: z8.literal("empty_result_unverified") }),
2952
+ z8.object({ kind: z8.literal("timeout_idle") }),
2953
+ z8.object({ kind: z8.literal("timeout_total") }),
2954
+ z8.object({ kind: z8.literal("cancelled") }),
2955
+ z8.object({ kind: z8.literal("lease_lost") }),
2956
+ z8.object({ kind: z8.literal("skipped") }),
2957
+ z8.object({ kind: z8.literal("session_start_failed") }),
2958
+ z8.object({ kind: z8.literal("workspace_tools_missing") }),
2959
+ z8.object({ kind: z8.literal("runtime_error") })
2927
2960
  ]);
2928
2961
  var turnOutcomes = ["success", "failure", "cancelled", "skipped"];
2929
2962
  var turnSessionModes = ["fresh", "resumed", "degraded"];
@@ -2935,25 +2968,25 @@ var turnFinalSources = [
2935
2968
  "marker",
2936
2969
  "none"
2937
2970
  ];
2938
- var turnDiagnosticsSchema = z7.object({
2939
- outcome: z7.enum(turnOutcomes),
2971
+ var turnDiagnosticsSchema = z8.object({
2972
+ outcome: z8.enum(turnOutcomes),
2940
2973
  resultReason: turnResultReasonSchema.nullable(),
2941
- sessionMode: z7.enum(turnSessionModes),
2942
- sessionFingerprint: z7.string().regex(/^[a-f0-9]{16}$/).nullable(),
2943
- eventCounts: z7.object({
2944
- session: z7.number().int().nonnegative(),
2945
- text: z7.number().int().nonnegative(),
2946
- runtime_notice: z7.number().int().nonnegative(),
2947
- thinking: z7.number().int().nonnegative(),
2948
- tool: z7.number().int().nonnegative(),
2949
- result: z7.number().int().nonnegative()
2974
+ sessionMode: z8.enum(turnSessionModes),
2975
+ sessionFingerprint: z8.string().regex(/^[a-f0-9]{16}$/).nullable(),
2976
+ eventCounts: z8.object({
2977
+ session: z8.number().int().nonnegative(),
2978
+ text: z8.number().int().nonnegative(),
2979
+ runtime_notice: z8.number().int().nonnegative(),
2980
+ thinking: z8.number().int().nonnegative(),
2981
+ tool: z8.number().int().nonnegative(),
2982
+ result: z8.number().int().nonnegative()
2950
2983
  }),
2951
- runtimeResultKind: z7.enum(turnRuntimeResultKinds).nullable(),
2952
- finalSource: z7.enum(turnFinalSources),
2953
- mcpInventory: z7.object({
2954
- initReceived: z7.boolean(),
2955
- servers: z7.array(z7.object({ name: z7.string(), status: z7.string() })),
2956
- toolCount: z7.number().int().nonnegative()
2984
+ runtimeResultKind: z8.enum(turnRuntimeResultKinds).nullable(),
2985
+ finalSource: z8.enum(turnFinalSources),
2986
+ mcpInventory: z8.object({
2987
+ initReceived: z8.boolean(),
2988
+ servers: z8.array(z8.object({ name: z8.string(), status: z8.string() })),
2989
+ toolCount: z8.number().int().nonnegative()
2957
2990
  }).optional()
2958
2991
  });
2959
2992
 
@@ -3014,100 +3047,100 @@ function classifyEmptyResult(input) {
3014
3047
  }
3015
3048
 
3016
3049
  // packages/agent-runtime/src/turn-request.ts
3017
- import { z as z8 } from "zod";
3018
- var contentBlockSchema = z8.discriminatedUnion("type", [
3019
- z8.object({ type: z8.literal("text"), text: z8.string() }),
3020
- z8.object({
3021
- type: z8.literal("image"),
3022
- source: z8.object({ type: z8.literal("url"), url: z8.string() })
3050
+ import { z as z9 } from "zod";
3051
+ var contentBlockSchema = z9.discriminatedUnion("type", [
3052
+ z9.object({ type: z9.literal("text"), text: z9.string() }),
3053
+ z9.object({
3054
+ type: z9.literal("image"),
3055
+ source: z9.object({ type: z9.literal("url"), url: z9.string() })
3023
3056
  }),
3024
- z8.object({
3025
- type: z8.literal("document"),
3026
- source: z8.object({ type: z8.literal("url"), url: z8.string() })
3057
+ z9.object({
3058
+ type: z9.literal("document"),
3059
+ source: z9.object({ type: z9.literal("url"), url: z9.string() })
3027
3060
  })
3028
3061
  ]);
3029
- var effortLevelSchema = z8.enum(["low", "medium", "high", "xhigh", "max"]);
3030
- var resolvedRunConfigSchema = z8.object({
3031
- model: z8.string().nullable(),
3062
+ var effortLevelSchema = z9.enum(["low", "medium", "high", "xhigh", "max"]);
3063
+ var resolvedRunConfigSchema = z9.object({
3064
+ model: z9.string().nullable(),
3032
3065
  effort: effortLevelSchema.optional(),
3033
- runtimeOptions: z8.record(z8.string(), z8.unknown()).optional()
3066
+ runtimeOptions: z9.record(z9.string(), z9.unknown()).optional()
3034
3067
  });
3035
- var resolvedMcpServerSchema = z8.union([
3036
- z8.object({
3037
- type: z8.literal("stdio").optional(),
3038
- command: z8.string(),
3039
- args: z8.array(z8.string()).optional(),
3040
- env: z8.record(z8.string(), z8.string()).optional(),
3041
- envFromEnv: z8.record(z8.string(), z8.string()).optional()
3068
+ var resolvedMcpServerSchema = z9.union([
3069
+ z9.object({
3070
+ type: z9.literal("stdio").optional(),
3071
+ command: z9.string(),
3072
+ args: z9.array(z9.string()).optional(),
3073
+ env: z9.record(z9.string(), z9.string()).optional(),
3074
+ envFromEnv: z9.record(z9.string(), z9.string()).optional()
3042
3075
  }),
3043
- z8.object({
3044
- type: z8.literal("http"),
3045
- url: z8.string(),
3046
- headers: z8.record(z8.string(), z8.string()).optional(),
3047
- headersFromEnv: z8.record(z8.string(), z8.string()).optional()
3076
+ z9.object({
3077
+ type: z9.literal("http"),
3078
+ url: z9.string(),
3079
+ headers: z9.record(z9.string(), z9.string()).optional(),
3080
+ headersFromEnv: z9.record(z9.string(), z9.string()).optional()
3048
3081
  }),
3049
- z8.object({
3050
- type: z8.literal("sse"),
3051
- url: z8.string(),
3052
- headers: z8.record(z8.string(), z8.string()).optional(),
3053
- headersFromEnv: z8.record(z8.string(), z8.string()).optional()
3082
+ z9.object({
3083
+ type: z9.literal("sse"),
3084
+ url: z9.string(),
3085
+ headers: z9.record(z9.string(), z9.string()).optional(),
3086
+ headersFromEnv: z9.record(z9.string(), z9.string()).optional()
3054
3087
  })
3055
3088
  ]);
3056
- var resolvedMcpServersSchema = z8.record(z8.string(), resolvedMcpServerSchema);
3057
- var hostInjectedServersSchema = z8.record(z8.string(), z8.unknown());
3058
- var turnRequestSchema = z8.object({
3089
+ var resolvedMcpServersSchema = z9.record(z9.string(), resolvedMcpServerSchema);
3090
+ var hostInjectedServersSchema = z9.record(z9.string(), z9.unknown());
3091
+ var turnRequestSchema = z9.object({
3059
3092
  // Server-composed standing instructions: the core segment (body, tool naming, identity, agent
3060
3093
  // charter) and, after it, the conversation segment (workspace and room charters, skill indexes,
3061
3094
  // roster). One string to the adapter, current as of this dispatch.
3062
- systemPrompt: z8.string(),
3095
+ systemPrompt: z9.string(),
3063
3096
  // The same text as its two ordered segments, so an adapter that writes standing text into the
3064
3097
  // conversation (Codex's input preamble) can tell which half changed. `systemPrompt` is
3065
3098
  // `joinStanding(standing)`. Optional: absent from an API older than the split, where
3066
3099
  // `systemPrompt` is the core alone and the conversation material rides `prompt`.
3067
- standing: z8.object({ core: z8.string(), conversation: z8.string() }).optional(),
3100
+ standing: z9.object({ core: z9.string(), conversation: z9.string() }).optional(),
3068
3101
  // Server-composed per-turn user text (anchor reminder + the triggering message).
3069
- prompt: z8.string(),
3102
+ prompt: z9.string(),
3070
3103
  // The multi-block user-message body (text + vision).
3071
- content: z8.array(contentBlockSchema),
3104
+ content: z9.array(contentBlockSchema),
3072
3105
  // Portable-or-dialect run-config (above).
3073
3106
  config: resolvedRunConfigSchema,
3074
3107
  // Abstract capability grants; the adapter maps them to tool names.
3075
3108
  policy: hostPolicySchema,
3076
3109
  // Prior opaque session state, or null for a fresh session.
3077
- session: z8.string().nullable(),
3110
+ session: z9.string().nullable(),
3078
3111
  // The cabane control-plane coordinates for this turn's MCP + post-back.
3079
- cabane: z8.object({
3080
- mcpUrl: z8.string(),
3081
- bearer: z8.string(),
3082
- activeConversationId: z8.string(),
3112
+ cabane: z9.object({
3113
+ mcpUrl: z9.string(),
3114
+ bearer: z9.string(),
3115
+ activeConversationId: z9.string(),
3083
3116
  // Scoped turn-control MCP URL. Codex and opencode mount it as `cabane_companion` with the bearer
3084
3117
  // and active-conversation header, for ask, withdraw_ask, wake_me, cancel_wake and skip_turn.
3085
3118
  // Claude Code mounts the companion's in-process instance instead. Optional; the companion
3086
3119
  // populates it.
3087
- turnControlUrl: z8.string().optional(),
3120
+ turnControlUrl: z9.string().optional(),
3088
3121
  // Workspace coordinate retained for envelope compatibility. Current
3089
3122
  // adapters use MCP, whose workspace operations take workspaceId explicitly.
3090
3123
  // The companion supplies this field; it is not required by those adapters.
3091
- workspaceId: z8.string().optional()
3124
+ workspaceId: z9.string().optional()
3092
3125
  }),
3093
3126
  // Machine-local resolution (host-filled): the checkout cwd, extra env from a
3094
3127
  // prepare hook, and the resolved user MCP servers.
3095
- local: z8.object({
3096
- cwd: z8.string().optional(),
3097
- env: z8.record(z8.string(), z8.string()).optional(),
3128
+ local: z9.object({
3129
+ cwd: z9.string().optional(),
3130
+ env: z9.record(z9.string(), z9.string()).optional(),
3098
3131
  mcpServers: resolvedMcpServersSchema.optional(),
3099
3132
  // The values `headersFromEnv` names, per turn (`CABANE_MCP_<n>` → complete header value). The
3100
3133
  // adapter merges them into the harness process's environment and nowhere else; the host never
3101
3134
  // merges them into its own. Absent when no server references one.
3102
- secretEnv: z8.record(z8.string(), z8.string()).optional(),
3135
+ secretEnv: z9.record(z9.string(), z9.string()).optional(),
3103
3136
  // Machine-local claude-code knobs for an operator running their own companion — the auto-memory
3104
3137
  // escape hatch. `autoMemory: true` opts back into Claude Code's auto-memory (governed by the
3105
3138
  // operator's `.claude/settings.json`); absent/false keeps the adapter's force-off default
3106
3139
  // (`buildClaudeCodeOptions`).
3107
- claudeCode: z8.object({ autoMemory: z8.boolean().optional() }).optional()
3140
+ claudeCode: z9.object({ autoMemory: z9.boolean().optional() }).optional()
3108
3141
  }),
3109
3142
  // Host-owned injected servers (host-filled).
3110
- extra: z8.object({
3143
+ extra: z9.object({
3111
3144
  mcpServers: hostInjectedServersSchema
3112
3145
  })
3113
3146
  });
@@ -3123,12 +3156,12 @@ function standingSegments(req) {
3123
3156
  }
3124
3157
 
3125
3158
  // packages/agent-runtime/src/conformance.ts
3126
- import { z as z9 } from "zod";
3127
- var conformanceFixtureSchema = z9.object({
3128
- name: z9.string(),
3159
+ import { z as z10 } from "zod";
3160
+ var conformanceFixtureSchema = z10.object({
3161
+ name: z10.string(),
3129
3162
  request: turnRequestSchema,
3130
- nativeStream: z9.array(z9.unknown()),
3131
- expected: z9.array(turnEventSchema)
3163
+ nativeStream: z10.array(z10.unknown()),
3164
+ expected: z10.array(turnEventSchema)
3132
3165
  });
3133
3166
 
3134
3167
  // packages/agent-runtime/src/transcript.ts
@@ -3583,7 +3616,7 @@ function envResolvedFrom(server, secretEnv) {
3583
3616
  }
3584
3617
 
3585
3618
  // packages/agent-runtime/src/claude-code/policy.ts
3586
- import { z as z10 } from "zod";
3619
+ import { z as z11 } from "zod";
3587
3620
  var HOST_FS_TOOLS = [
3588
3621
  // shell + local filesystem
3589
3622
  "Bash",
@@ -3633,18 +3666,18 @@ function withThinkingSummaries(thinking) {
3633
3666
  if (thinking.type === "disabled") return thinking;
3634
3667
  return { display: "summarized", ...thinking };
3635
3668
  }
3636
- var claudeCodeDialectSchema = z10.object({
3637
- thinking: z10.discriminatedUnion("type", [
3638
- z10.object({
3639
- type: z10.literal("adaptive"),
3640
- display: z10.enum(["summarized", "omitted"]).optional()
3669
+ var claudeCodeDialectSchema = z11.object({
3670
+ thinking: z11.discriminatedUnion("type", [
3671
+ z11.object({
3672
+ type: z11.literal("adaptive"),
3673
+ display: z11.enum(["summarized", "omitted"]).optional()
3641
3674
  }),
3642
- z10.object({
3643
- type: z10.literal("enabled"),
3644
- budgetTokens: z10.number().int().positive().optional(),
3645
- display: z10.enum(["summarized", "omitted"]).optional()
3675
+ z11.object({
3676
+ type: z11.literal("enabled"),
3677
+ budgetTokens: z11.number().int().positive().optional(),
3678
+ display: z11.enum(["summarized", "omitted"]).optional()
3646
3679
  }),
3647
- z10.object({ type: z10.literal("disabled") })
3680
+ z11.object({ type: z11.literal("disabled") })
3648
3681
  ]).optional()
3649
3682
  }).loose();
3650
3683
  function readThinking(runtimeOptions) {
@@ -5113,7 +5146,7 @@ function sealHeld(held, terminal) {
5113
5146
  }
5114
5147
 
5115
5148
  // packages/agent-runtime/src/opencode/policy.ts
5116
- import { z as z11 } from "zod";
5149
+ import { z as z12 } from "zod";
5117
5150
  var OPENCODE_HOST_TOOLS = [
5118
5151
  "bash",
5119
5152
  "edit",
@@ -5140,8 +5173,8 @@ function opencodeToolPolicy(policy) {
5140
5173
  deny(OPENCODE_UI_PROMPT_TOOLS);
5141
5174
  return { tools, allowAllHostTools: policy.hostFs };
5142
5175
  }
5143
- var opencodeDialectSchema = z11.object({
5144
- agent: z11.string().min(1).optional()
5176
+ var opencodeDialectSchema = z12.object({
5177
+ agent: z12.string().min(1).optional()
5145
5178
  }).loose();
5146
5179
  function readOpencodeDialect(runtimeOptions) {
5147
5180
  const parsed = opencodeDialectSchema.safeParse(runtimeOptions?.["opencode"] ?? {});
@@ -6355,7 +6388,7 @@ function sealHeld2(held, terminal) {
6355
6388
  }
6356
6389
 
6357
6390
  // packages/agent-runtime/src/codex/policy.ts
6358
- import { z as z12 } from "zod";
6391
+ import { z as z13 } from "zod";
6359
6392
  function codexToolPolicy(policy) {
6360
6393
  return policy.hostFs ? {
6361
6394
  sandboxMode: "danger-full-access",
@@ -6374,8 +6407,8 @@ function codexToolPolicy(policy) {
6374
6407
  };
6375
6408
  }
6376
6409
  var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
6377
- var codexDialectSchema = z12.object({
6378
- modelReasoningEffort: z12.enum(CODEX_REASONING_EFFORTS).optional()
6410
+ var codexDialectSchema = z13.object({
6411
+ modelReasoningEffort: z13.enum(CODEX_REASONING_EFFORTS).optional()
6379
6412
  }).loose();
6380
6413
  function readCodexDialect(runtimeOptions) {
6381
6414
  const parsed = codexDialectSchema.safeParse(runtimeOptions?.["codex"] ?? {});
@@ -7817,30 +7850,30 @@ import { createInterface } from "readline";
7817
7850
  // src/runner/sandbox-policy.ts
7818
7851
  import { NetworkConfigSchema } from "@anthropic-ai/sandbox-runtime";
7819
7852
  import { globToRegex } from "@anthropic-ai/sandbox-runtime/dist/sandbox/sandbox-utils.js";
7820
- import { z as z13 } from "zod";
7821
- var hostPattern = z13.string().min(1);
7822
- var secretRef = z13.string().regex(/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/, "must be a ${NAME} reference into secrets.json");
7823
- var envCredentialSchema = z13.object({ secret: secretRef, hosts: z13.array(hostPattern).min(1) }).strict();
7824
- var gitCredentialSchema = z13.object({
7825
- host: z13.string().regex(/^[A-Za-z0-9.-]+(:\d+)?$/, "must be a host name, like github.com"),
7826
- username: z13.string().min(1).optional(),
7853
+ import { z as z14 } from "zod";
7854
+ var hostPattern = z14.string().min(1);
7855
+ var secretRef = z14.string().regex(/^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/, "must be a ${NAME} reference into secrets.json");
7856
+ var envCredentialSchema = z14.object({ secret: secretRef, hosts: z14.array(hostPattern).min(1) }).strict();
7857
+ var gitCredentialSchema = z14.object({
7858
+ host: z14.string().regex(/^[A-Za-z0-9.-]+(:\d+)?$/, "must be a host name, like github.com"),
7859
+ username: z14.string().min(1).optional(),
7827
7860
  secret: secretRef
7828
7861
  }).strict();
7829
- var sandboxPolicySchema = z13.object({
7830
- hosts: z13.object({
7831
- allow: z13.array(hostPattern).optional(),
7832
- deny: z13.array(hostPattern).optional(),
7833
- defaults: z13.boolean().optional()
7862
+ var sandboxPolicySchema = z14.object({
7863
+ hosts: z14.object({
7864
+ allow: z14.array(hostPattern).optional(),
7865
+ deny: z14.array(hostPattern).optional(),
7866
+ defaults: z14.boolean().optional()
7834
7867
  }).strict().optional(),
7835
- filesystem: z13.object({
7836
- mode: z13.enum(["blocklist", "strict"]).optional(),
7837
- denyRead: z13.array(z13.string().min(1)).optional(),
7838
- allowRead: z13.array(z13.string().min(1)).optional(),
7839
- allowWrite: z13.array(z13.string().min(1)).optional()
7868
+ filesystem: z14.object({
7869
+ mode: z14.enum(["blocklist", "strict"]).optional(),
7870
+ denyRead: z14.array(z14.string().min(1)).optional(),
7871
+ allowRead: z14.array(z14.string().min(1)).optional(),
7872
+ allowWrite: z14.array(z14.string().min(1)).optional()
7840
7873
  }).strict().optional(),
7841
- credentials: z13.object({
7842
- env: z13.record(z13.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), envCredentialSchema).optional(),
7843
- git: z13.array(gitCredentialSchema).optional()
7874
+ credentials: z14.object({
7875
+ env: z14.record(z14.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/), envCredentialSchema).optional(),
7876
+ git: z14.array(gitCredentialSchema).optional()
7844
7877
  }).strict().optional()
7845
7878
  }).strict();
7846
7879
  function parseSandboxPolicy(raw) {
@@ -8269,12 +8302,12 @@ function createTurnContainment(turnId, deps = {}) {
8269
8302
  // src/secrets.ts
8270
8303
  import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
8271
8304
  import { join as join13 } from "path";
8272
- import { z as z14 } from "zod";
8305
+ import { z as z15 } from "zod";
8273
8306
  var PLACEHOLDER_RE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
8274
8307
  function secretsPath() {
8275
8308
  return join13(cabaneDir(), "secrets.json");
8276
8309
  }
8277
- var secretStoreSchema = z14.record(z14.string(), z14.string());
8310
+ var secretStoreSchema = z15.record(z15.string(), z15.string());
8278
8311
  function loadSecretStore() {
8279
8312
  const path = secretsPath();
8280
8313
  if (!existsSync9(path)) return makeStore({});
@@ -8391,52 +8424,52 @@ function missingSecretReason(missing) {
8391
8424
  }
8392
8425
 
8393
8426
  // src/runner/protocol.ts
8394
- import { z as z16 } from "zod";
8427
+ import { z as z17 } from "zod";
8395
8428
 
8396
8429
  // src/run-config.ts
8397
- import { z as z15 } from "zod";
8398
- var mcpStdioServerSchema = z15.object({
8399
- type: z15.literal("stdio").optional(),
8400
- command: z15.string().min(1),
8401
- args: z15.array(z15.string()).optional(),
8402
- env: z15.record(z15.string(), z15.string()).optional(),
8430
+ import { z as z16 } from "zod";
8431
+ var mcpStdioServerSchema = z16.object({
8432
+ type: z16.literal("stdio").optional(),
8433
+ command: z16.string().min(1),
8434
+ args: z16.array(z16.string()).optional(),
8435
+ env: z16.record(z16.string(), z16.string()).optional(),
8403
8436
  // Variable → the name of a `secretEnv` entry holding its value. Set by the server on a
8404
8437
  // room's shared browser, whose connect URL is a credential.
8405
- envFromEnv: z15.record(z15.string(), z15.string()).optional()
8438
+ envFromEnv: z16.record(z16.string(), z16.string()).optional()
8406
8439
  });
8407
- var mcpHttpServerSchema = z15.object({
8408
- type: z15.literal("http"),
8409
- url: z15.string().url(),
8410
- headers: z15.record(z15.string(), z15.string()).optional(),
8440
+ var mcpHttpServerSchema = z16.object({
8441
+ type: z16.literal("http"),
8442
+ url: z16.string().url(),
8443
+ headers: z16.record(z16.string(), z16.string()).optional(),
8411
8444
  // Header → the name of a `secretEnv` entry holding its value. Set by
8412
8445
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8413
8446
  // header whose `${VAR}` it resolved.
8414
- headersFromEnv: z15.record(z15.string(), z15.string()).optional()
8447
+ headersFromEnv: z16.record(z16.string(), z16.string()).optional()
8415
8448
  });
8416
- var mcpSseServerSchema = z15.object({
8417
- type: z15.literal("sse"),
8418
- url: z15.string().url(),
8419
- headers: z15.record(z15.string(), z15.string()).optional(),
8449
+ var mcpSseServerSchema = z16.object({
8450
+ type: z16.literal("sse"),
8451
+ url: z16.string().url(),
8452
+ headers: z16.record(z16.string(), z16.string()).optional(),
8420
8453
  // Header → the name of a `secretEnv` entry holding its value. Set by
8421
8454
  // the server on a room connection's entry, and by `resolveMcpSecrets` on a
8422
8455
  // header whose `${VAR}` it resolved.
8423
- headersFromEnv: z15.record(z15.string(), z15.string()).optional()
8456
+ headersFromEnv: z16.record(z16.string(), z16.string()).optional()
8424
8457
  });
8425
- var mcpServerDefSchema = z15.union([mcpHttpServerSchema, mcpSseServerSchema, mcpStdioServerSchema]);
8426
- var thinkingConfigSchema = z15.discriminatedUnion("type", [
8427
- z15.object({ type: z15.literal("adaptive") }),
8428
- z15.object({ type: z15.literal("enabled"), budgetTokens: z15.number().int().positive().optional() }),
8429
- z15.object({ type: z15.literal("disabled") })
8458
+ var mcpServerDefSchema = z16.union([mcpHttpServerSchema, mcpSseServerSchema, mcpStdioServerSchema]);
8459
+ var thinkingConfigSchema = z16.discriminatedUnion("type", [
8460
+ z16.object({ type: z16.literal("adaptive") }),
8461
+ z16.object({ type: z16.literal("enabled"), budgetTokens: z16.number().int().positive().optional() }),
8462
+ z16.object({ type: z16.literal("disabled") })
8430
8463
  ]);
8431
- var effortSchema = z15.enum(["low", "medium", "high", "xhigh", "max"]);
8432
- var runConfigSchema = z15.object({
8464
+ var effortSchema = z16.enum(["low", "medium", "high", "xhigh", "max"]);
8465
+ var runConfigSchema = z16.object({
8433
8466
  // `true` grants the host filesystem/shell; absent is the locked surface. Kept
8434
8467
  // in lockstep with `@cabane/shared`'s
8435
8468
  // `agentRunConfigSchema` (independent zod, forward-compatible: unknown keys are
8436
8469
  // stripped, so an older companion riding a newer server never rejects the config).
8437
- hostAccess: z15.boolean().optional(),
8438
- mcpServers: z15.record(z15.string(), mcpServerDefSchema).optional(),
8439
- model: z15.string().min(1).optional(),
8470
+ hostAccess: z16.boolean().optional(),
8471
+ mcpServers: z16.record(z16.string(), mcpServerDefSchema).optional(),
8472
+ model: z16.string().min(1).optional(),
8440
8473
  thinking: thinkingConfigSchema.optional(),
8441
8474
  effort: effortSchema.optional()
8442
8475
  });
@@ -8464,76 +8497,76 @@ var RUNNER_ENV = {
8464
8497
  // Everything else the turn is constructed from, as JSON: see `RunnerSpec`.
8465
8498
  spec: "CABANE_RUNNER_SPEC"
8466
8499
  };
8467
- var runtimeHealthSchema = z16.object({
8468
- quotaState: z16.enum(["ok", "limited", "auth_expired", "unknown"]),
8469
- limitedUntil: z16.string().nullable(),
8470
- lastError: z16.string().nullable()
8500
+ var runtimeHealthSchema = z17.object({
8501
+ quotaState: z17.enum(["ok", "limited", "auth_expired", "unknown"]),
8502
+ limitedUntil: z17.string().nullable(),
8503
+ lastError: z17.string().nullable()
8471
8504
  });
8472
- var runnerSpecSchema = z16.object({
8473
- deviceId: z16.string().optional(),
8505
+ var runnerSpecSchema = z17.object({
8506
+ deviceId: z17.string().optional(),
8474
8507
  // The launcher's boot id: the one its heartbeat carries, so a runner's lease
8475
8508
  // matches the device that holds it.
8476
- bootId: z16.string(),
8477
- workspaceId: z16.string(),
8478
- workspaceSlug: z16.string(),
8479
- agentId: z16.string(),
8480
- agentUsername: z16.string(),
8481
- agentName: z16.string(),
8509
+ bootId: z17.string(),
8510
+ workspaceId: z17.string(),
8511
+ workspaceSlug: z17.string(),
8512
+ agentId: z17.string(),
8513
+ agentUsername: z17.string(),
8514
+ agentName: z17.string(),
8482
8515
  local: localAgentConfigSchema,
8483
8516
  runConfig: runConfigSchema,
8484
- opencodeServerUrl: z16.string().optional(),
8485
- claudeCodeAvailable: z16.boolean(),
8486
- codexAvailable: z16.boolean(),
8487
- idleTimeoutMs: z16.number().int().positive().optional(),
8488
- totalTimeoutMs: z16.number().int().positive().optional(),
8489
- leaseRenewalMs: z16.number().int().positive().optional(),
8490
- preparingRowDelayMs: z16.number().int().nonnegative().optional(),
8517
+ opencodeServerUrl: z17.string().optional(),
8518
+ claudeCodeAvailable: z17.boolean(),
8519
+ codexAvailable: z17.boolean(),
8520
+ idleTimeoutMs: z17.number().int().positive().optional(),
8521
+ totalTimeoutMs: z17.number().int().positive().optional(),
8522
+ leaseRenewalMs: z17.number().int().positive().optional(),
8523
+ preparingRowDelayMs: z17.number().int().nonnegative().optional(),
8491
8524
  // Whether the turn writes a transcript. The directory is the runner's own
8492
8525
  // (`transcriptsDir()`), so `CABANE_RUNNER_STATE_DIR` moves it with the outbox.
8493
- transcripts: z16.boolean(),
8526
+ transcripts: z17.boolean(),
8494
8527
  // Whether the harness starts under the OS sandbox. The launcher sends 'os'
8495
8528
  // only when this host can make one.
8496
- sandbox: z16.enum(["os", "none"]),
8529
+ sandbox: z17.enum(["os", "none"]),
8497
8530
  // The device's sandbox policy, validated by the launcher. Absent
8498
8531
  // when the sandbox is off.
8499
8532
  sandboxPolicy: sandboxPolicySchema.optional(),
8500
8533
  // Every companion store the launcher's latest walk found, denied
8501
8534
  // to the harness. Absent when the sandbox is off.
8502
- companionStores: z16.array(z16.string()).optional(),
8503
- logLevel: z16.string(),
8535
+ companionStores: z17.array(z17.string()).optional(),
8536
+ logLevel: z17.string(),
8504
8537
  // The launcher's connector-health observations, by runtime, as of the start.
8505
- connectorHealth: z16.record(z16.string(), runtimeHealthSchema),
8538
+ connectorHealth: z17.record(z17.string(), runtimeHealthSchema),
8506
8539
  // True when this (conversation, agent) pair was already told its session
8507
8540
  // could not be saved: the notice is once per pair, not once per process.
8508
- sessionWriteNoticeClaimed: z16.boolean(),
8541
+ sessionWriteNoticeClaimed: z17.boolean(),
8509
8542
  // The runner's identity, minted with its credential. The start
8510
8543
  // PATCH carries it so the lease knows which runner holds the span.
8511
- runnerId: z16.string(),
8544
+ runnerId: z17.string(),
8512
8545
  // The launcher keeps the runtime's session files beside the attempt
8513
8546
  // and restores them into this runner's home, so the runner names the attempt
8514
8547
  // each context was composed for and waits for the launcher to say it's in
8515
8548
  // place. Absent: nothing to wait for (a runner on the launcher's own disk).
8516
- attemptState: z16.boolean().optional(),
8549
+ attemptState: z17.boolean().optional(),
8517
8550
  // MCP `${NAME}` secrets come from the launcher, by name, instead of
8518
8551
  // this machine's secrets.json. Absent: the runner reads the device's file.
8519
- secretsFromLauncher: z16.boolean().optional()
8552
+ secretsFromLauncher: z17.boolean().optional()
8520
8553
  });
8521
- var dispatchSchema = z16.object({
8522
- type: z16.literal("device:dispatch_requested"),
8523
- agentId: z16.string(),
8524
- deviceId: z16.string(),
8525
- conversationId: z16.string(),
8526
- messageId: z16.string(),
8527
- episode: z16.number().int().optional()
8554
+ var dispatchSchema = z17.object({
8555
+ type: z17.literal("device:dispatch_requested"),
8556
+ agentId: z17.string(),
8557
+ deviceId: z17.string(),
8558
+ conversationId: z17.string(),
8559
+ messageId: z17.string(),
8560
+ episode: z17.number().int().optional()
8528
8561
  });
8529
- var credentialsSchema = z16.object({
8530
- credential: z16.string().startsWith("cabrn_"),
8531
- sandboxCredentials: z16.array(
8532
- z16.object({
8533
- name: z16.string(),
8534
- value: z16.string(),
8535
- hosts: z16.array(z16.string()),
8536
- gitHost: z16.string().optional()
8562
+ var credentialsSchema = z17.object({
8563
+ credential: z17.string().startsWith("cabrn_"),
8564
+ sandboxCredentials: z17.array(
8565
+ z17.object({
8566
+ name: z17.string(),
8567
+ value: z17.string(),
8568
+ hosts: z17.array(z17.string()),
8569
+ gitHost: z17.string().optional()
8537
8570
  })
8538
8571
  ).optional()
8539
8572
  });
@@ -9002,7 +9035,7 @@ import { existsSync as existsSync11 } from "fs";
9002
9035
 
9003
9036
  // src/turn-control-tools.ts
9004
9037
  import { randomUUID as randomUUID2 } from "crypto";
9005
- import { z as z17 } from "zod";
9038
+ import { z as z18 } from "zod";
9006
9039
  var COMPANION_LOCAL_MCP_SERVER = "cabane_companion";
9007
9040
  var COMPANION_LOCAL_TOOL_GLOB = `mcp__${COMPANION_LOCAL_MCP_SERVER}__*`;
9008
9041
  var SKIP_TURN_TOOL = "skip_turn";
@@ -9065,7 +9098,7 @@ function createTurnControlMcpServer(acts, skipState) {
9065
9098
  SKIP_TURN_TOOL,
9066
9099
  TURN_CONTROL_TOOL_DESCRIPTIONS.skip_turn,
9067
9100
  {
9068
- reason: z17.string().min(1).max(MAX_SKIP_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.skip_turn.reason)
9101
+ reason: z18.string().min(1).max(MAX_SKIP_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.skip_turn.reason)
9069
9102
  },
9070
9103
  async (args) => {
9071
9104
  skipState.skipped = true;
@@ -9081,22 +9114,22 @@ function createTurnControlMcpServer(acts, skipState) {
9081
9114
  ASK_TOOL,
9082
9115
  TURN_CONTROL_TOOL_DESCRIPTIONS.ask,
9083
9116
  {
9084
- targetUserId: z17.string().regex(ASK_TARGET_PATTERN, { error: ASK_TARGET_INVALID }).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.targetUserId),
9085
- question: z17.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.question),
9086
- headline: z17.string().min(1).max(MAX_ASK_HEADLINE_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.headline),
9087
- options: z17.array(z17.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.options),
9088
- questions: z17.array(
9089
- z17.object({
9090
- headline: z17.string().min(1).max(MAX_ASK_HEADLINE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.headline"]),
9091
- body: z17.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.body"]),
9092
- options: z17.array(
9093
- z17.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)
9117
+ targetUserId: z18.string().regex(ASK_TARGET_PATTERN, { error: ASK_TARGET_INVALID }).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.targetUserId),
9118
+ question: z18.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.question),
9119
+ headline: z18.string().min(1).max(MAX_ASK_HEADLINE_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.headline),
9120
+ options: z18.array(z18.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.options),
9121
+ questions: z18.array(
9122
+ z18.object({
9123
+ headline: z18.string().min(1).max(MAX_ASK_HEADLINE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.headline"]),
9124
+ body: z18.string().min(1).max(MAX_ASK_QUESTION_BYTES).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.body"]),
9125
+ options: z18.array(
9126
+ z18.string({ error: ASK_OPTION_NOT_A_STRING }).min(1).max(MAX_ASK_OPTION_BYTES)
9094
9127
  ).min(MIN_ASK_OPTIONS).max(MAX_ASK_OPTIONS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["questions.options"])
9095
9128
  })
9096
9129
  ).min(1).max(MAX_ASK_ITEMS).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.questions),
9097
- replaces: z17.object({
9098
- askId: z17.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.askId"]),
9099
- reason: z17.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.reason"])
9130
+ replaces: z18.object({
9131
+ askId: z18.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.askId"]),
9132
+ reason: z18.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask["replaces.reason"])
9100
9133
  }).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.ask.replaces)
9101
9134
  },
9102
9135
  async (args) => {
@@ -9137,9 +9170,9 @@ function createTurnControlMcpServer(acts, skipState) {
9137
9170
  WAKE_ME_TOOL,
9138
9171
  TURN_CONTROL_TOOL_DESCRIPTIONS.wake_me,
9139
9172
  {
9140
- afterSeconds: z17.number().int().positive().optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.afterSeconds),
9141
- at: z17.string().datetime({ offset: true }).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.at),
9142
- note: z17.string().min(1).max(MAX_WAKE_NOTE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.note)
9173
+ afterSeconds: z18.number().int().positive().optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.afterSeconds),
9174
+ at: z18.string().datetime({ offset: true }).optional().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.at),
9175
+ note: z18.string().min(1).max(MAX_WAKE_NOTE_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.wake_me.note)
9143
9176
  },
9144
9177
  async (args) => {
9145
9178
  const hasAfter = args.afterSeconds !== void 0;
@@ -9173,8 +9206,8 @@ function createTurnControlMcpServer(acts, skipState) {
9173
9206
  WITHDRAW_ASK_TOOL,
9174
9207
  TURN_CONTROL_TOOL_DESCRIPTIONS.withdraw_ask,
9175
9208
  {
9176
- askId: z17.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.askId),
9177
- reason: z17.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.reason)
9209
+ askId: z18.string().uuid().describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.askId),
9210
+ reason: z18.string().min(1).max(MAX_WITHDRAW_REASON_BYTES).describe(TURN_CONTROL_FIELD_DESCRIPTIONS.withdraw_ask.reason)
9178
9211
  },
9179
9212
  async (args) => performAct(acts, {
9180
9213
  kind: "withdraw_ask",
@@ -12848,7 +12881,7 @@ function unusableReason(runtime, check) {
12848
12881
  if (check.status === "check_failed") return failureText(runtime, check.reason);
12849
12882
  return null;
12850
12883
  }
12851
- var SETUP_AGAIN = `Then run ${cabane("setup")} again. It picks up where you left off.`;
12884
+ var SETUP_AGAIN = `Then ${cabane("status")} shows it signed in; nothing else to run.`;
12852
12885
  function spaced(sentence) {
12853
12886
  return sentence ? ` ${sentence}` : "";
12854
12887
  }
@@ -13572,27 +13605,27 @@ function compareVersions(a, b) {
13572
13605
  // src/update-state.ts
13573
13606
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync15, renameSync as renameSync10, writeFileSync as writeFileSync10 } from "fs";
13574
13607
  import { dirname as dirname12, join as join28 } from "path";
13575
- import { z as z18 } from "zod";
13576
- var resultSchema = z18.object({
13577
- kind: z18.enum(["updated", "rolled_back", "failed"]),
13578
- from: z18.string(),
13579
- to: z18.string(),
13580
- at: z18.string(),
13581
- reason: z18.string().optional()
13608
+ import { z as z19 } from "zod";
13609
+ var resultSchema = z19.object({
13610
+ kind: z19.enum(["updated", "rolled_back", "failed"]),
13611
+ from: z19.string(),
13612
+ to: z19.string(),
13613
+ at: z19.string(),
13614
+ reason: z19.string().optional()
13582
13615
  });
13583
- var pendingSchema = z18.object({
13584
- from: z18.string(),
13616
+ var pendingSchema = z19.object({
13617
+ from: z19.string(),
13585
13618
  // The package root `current` pointed at before the swap, so a rollback can point it back.
13586
- fromRoot: z18.string(),
13587
- to: z18.string(),
13588
- at: z18.string(),
13589
- boots: z18.number().int().nonnegative()
13619
+ fromRoot: z19.string(),
13620
+ to: z19.string(),
13621
+ at: z19.string(),
13622
+ boots: z19.number().int().nonnegative()
13590
13623
  });
13591
- var stateSchema = z18.object({
13592
- server: z18.object({ latest: z18.string(), minimum: z18.string(), at: z18.string() }).optional(),
13624
+ var stateSchema = z19.object({
13625
+ server: z19.object({ latest: z19.string(), minimum: z19.string(), at: z19.string() }).optional(),
13593
13626
  pending: pendingSchema.optional(),
13594
13627
  lastResult: resultSchema.optional(),
13595
- failed: z18.object({ version: z18.string(), attempts: z18.number().int(), at: z18.string() }).optional()
13628
+ failed: z19.object({ version: z19.string(), attempts: z19.number().int(), at: z19.string() }).optional()
13596
13629
  });
13597
13630
  function updateStatePath(dir2 = cabaneDir()) {
13598
13631
  return join28(dir2, "update.json");
@@ -14080,6 +14113,13 @@ var CompanionSupervisor = class {
14080
14113
  recoveryChecked = false;
14081
14114
  recovering = false;
14082
14115
  heartbeated = false;
14116
+ heartbeatStatus = emptyHeartbeatStatus();
14117
+ currentDetection() {
14118
+ return this.detection;
14119
+ }
14120
+ lastHeartbeat() {
14121
+ return { ...this.heartbeatStatus };
14122
+ }
14083
14123
  recoveryCursor;
14084
14124
  recoveredTurns = /* @__PURE__ */ new Set();
14085
14125
  // Agents assigned here, as `workspaceId|agentId`, whose sign-in to Cabane is missing and
@@ -14199,19 +14239,26 @@ var CompanionSupervisor = class {
14199
14239
  else this.log.error({ posture }, `runners: ${summary}`);
14200
14240
  void this.companionStores().refresh();
14201
14241
  }
14202
- const startupHarnessRefresh = this.detect().then(() => {
14242
+ if (this.config.deviceToken) {
14243
+ this.deviceApi = new DeviceApi({
14244
+ baseUrl: this.config.baseUrl,
14245
+ deviceToken: this.config.deviceToken
14246
+ });
14247
+ this.trackHeartbeat(this.sendHeartbeat(true));
14248
+ }
14249
+ const firstHeartbeat = this.inFlightHeartbeat;
14250
+ const startupHarnessRefresh = this.detect();
14251
+ void startupHarnessRefresh.then(async () => {
14252
+ await firstHeartbeat;
14253
+ if (this.stopped) return;
14254
+ await this.heartbeatNow();
14203
14255
  });
14204
- this.trackHeartbeat(startupHarnessRefresh);
14205
14256
  this.detectTimer = setInterval(() => void this.detect(), DETECT_INTERVAL_MS);
14206
14257
  this.detectTimer.unref?.();
14207
- if (!this.config.deviceToken) {
14208
- this.log.warn("This device isn't paired. Run cabane start to pair it.");
14258
+ if (!this.deviceApi || !this.config.deviceToken) {
14259
+ this.log.warn("This device isn't paired. Get a setup line from Settings \u2192 Devices.");
14209
14260
  return;
14210
14261
  }
14211
- this.deviceApi = new DeviceApi({
14212
- baseUrl: this.config.baseUrl,
14213
- deviceToken: this.config.deviceToken
14214
- });
14215
14262
  this.deviceSub = new SseSubscriber({
14216
14263
  baseUrl: this.config.baseUrl,
14217
14264
  workspaceId: "device-control",
@@ -14257,11 +14304,6 @@ var CompanionSupervisor = class {
14257
14304
  this.updater.start();
14258
14305
  await this.refreshAssignments();
14259
14306
  this.deviceSub.start();
14260
- const firstHeartbeat = startupHarnessRefresh.then(async () => {
14261
- if (this.stopped) return;
14262
- await this.sendHeartbeat();
14263
- });
14264
- this.trackHeartbeat(firstHeartbeat);
14265
14307
  this.heartbeatTimer = setInterval(() => this.kickHeartbeat(), HEARTBEAT_INTERVAL_MS);
14266
14308
  this.heartbeatTimer.unref?.();
14267
14309
  this.pollTimer = setInterval(() => void this.refreshAssignments(), ASSIGNMENTS_POLL_MS);
@@ -14282,15 +14324,16 @@ var CompanionSupervisor = class {
14282
14324
  if (this.inFlightHeartbeat === pending) this.inFlightHeartbeat = null;
14283
14325
  });
14284
14326
  }
14285
- async sendHeartbeat() {
14327
+ async sendHeartbeat(first = false) {
14286
14328
  if (!this.deviceApi) return;
14287
14329
  try {
14288
- if (!this.detection || advertisedRuntimes(this.detection).length === 0) await this.detect();
14330
+ if (!first && this.detection && advertisedRuntimes(this.detection).length === 0)
14331
+ await this.detect();
14289
14332
  const store = loadSecretStoreTolerant(
14290
14333
  (m) => this.log.warn({ err: m }, "Could not read the secrets file. Check ~/.cabane/secrets.json.")
14291
14334
  );
14292
14335
  const connectorReports = this.connectorReports();
14293
- const opencodeUrl = this.opencodeUrl();
14336
+ const opencodeUrl = first ? null : this.opencodeUrl();
14294
14337
  const opencodeModels = opencodeUrl ? await enumerateOpencodeModels(opencodeUrl) : null;
14295
14338
  const res = await this.deviceApi.heartbeat({
14296
14339
  bootId,
@@ -14299,31 +14342,39 @@ var CompanionSupervisor = class {
14299
14342
  // Each runtime only when this device can actually run it: what the last
14300
14343
  // detection found usable (signed in, or an API key, or a serve
14301
14344
  // answering) — a usable bundled binary is a present one.
14302
- manifest: buildCompanionManifest({
14303
- claudeCode: this.offered("claude-code"),
14304
- opencode: this.offered("opencode"),
14305
- codex: this.offered("codex"),
14306
- // WHY an allowed harness is absent above. Diagnostic only, and always
14307
- // sent, so an empty array clears a previously reported issue.
14308
- runtimeIssues: this.runtimeIssues(),
14309
- // Informational only; the server matches on name.
14310
- versions: this.harnessVersions,
14311
- // Shown on the device page.
14312
- ...runnersEnabled(this.config) ? { runner: this.runnerManifest() } : {}
14313
- }),
14345
+ ...!first && this.detection ? {
14346
+ manifest: buildCompanionManifest({
14347
+ claudeCode: this.offered("claude-code"),
14348
+ opencode: this.offered("opencode"),
14349
+ codex: this.offered("codex"),
14350
+ // WHY an allowed harness is absent above. Diagnostic only, and always
14351
+ // sent, so an empty array clears a previously reported issue.
14352
+ runtimeIssues: this.runtimeIssues(),
14353
+ // Informational only; the server matches on name.
14354
+ versions: this.harnessVersions,
14355
+ // Shown on the device page.
14356
+ ...runnersEnabled(this.config) ? { runner: this.runnerManifest() } : {}
14357
+ })
14358
+ } : {},
14314
14359
  // Omitted until the first observed failure or heal, so the server's
14315
14360
  // status-less manifest synthesis still runs for a fresh companion.
14316
- ...connectorReports.length > 0 ? { connectors: connectorReports } : {},
14361
+ ...!first && connectorReports.length > 0 ? { connectors: connectorReports } : {},
14317
14362
  // The complete harness inventory in the machine's words, once detection
14318
14363
  // has run; omitted before it, which the server reads as "nothing yet".
14319
- ...this.detection ? { harnesses: harnessEntriesFromDetection(this.detection) } : {},
14364
+ ...!first && this.detection ? { harnesses: harnessEntriesFromDetection(this.detection) } : {},
14320
14365
  ...opencodeModels !== null ? { models: opencodeModels } : {},
14321
- ...this.updater ? { update: this.updater.report() } : {}
14366
+ ...!first && this.updater ? { update: this.updater.report() } : {}
14322
14367
  });
14323
14368
  this.hub.setDevice({ deviceId: res.deviceId });
14324
14369
  this.deviceId = res.deviceId;
14325
14370
  this.checkVersionSkew(res.serverVersion);
14326
14371
  this.heartbeated = true;
14372
+ const view = acknowledgedDeviceSchema.safeParse(res.device);
14373
+ this.heartbeatStatus = {
14374
+ lastHeartbeatAckAt: (/* @__PURE__ */ new Date()).toISOString(),
14375
+ lastAcknowledgedDevice: view.success ? view.data : null,
14376
+ lastHeartbeatRefused: false
14377
+ };
14327
14378
  void this.recoverRuns();
14328
14379
  void this.updater?.onHeartbeat(res.companion, this.deviceConnected).catch(
14329
14380
  (err) => this.log.warn(
@@ -14332,9 +14383,10 @@ var CompanionSupervisor = class {
14332
14383
  )
14333
14384
  );
14334
14385
  } catch (err) {
14386
+ this.heartbeatStatus.lastHeartbeatRefused = err instanceof ApiError && (err.status === 401 || err.status === 403);
14335
14387
  this.log.warn(
14336
14388
  { ...apiErrorLogFields(err) },
14337
- "Couldn't check in with Cabane; trying again shortly."
14389
+ "Couldn't check in with the server; trying again shortly."
14338
14390
  );
14339
14391
  }
14340
14392
  }
@@ -15421,7 +15473,8 @@ var CompanionSupervisor = class {
15421
15473
  if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
15422
15474
  if (this.detectTimer) clearInterval(this.detectTimer);
15423
15475
  if (this.pollTimer) clearInterval(this.pollTimer);
15424
- const heartbeat = this.inFlightHeartbeat;
15476
+ const heartbeat = Promise.all([this.inFlightHeartbeat, this.detecting]).then(() => {
15477
+ });
15425
15478
  this.deviceSub?.stop();
15426
15479
  for (const wr of this.workspaces.values()) {
15427
15480
  for (const a of wr.agents.values()) a.cancelDrain();
@@ -15699,7 +15752,11 @@ async function createCompanionRuntime(opts = {}) {
15699
15752
  let dashboard = null;
15700
15753
  try {
15701
15754
  control = await startControlServer({
15702
- status: () => hub.statusJson(),
15755
+ status: () => ({
15756
+ ...hub.statusJson(),
15757
+ ...supervisor.lastHeartbeat(),
15758
+ detection: supervisor.currentDetection()
15759
+ }),
15703
15760
  detect: () => supervisor.redetect(),
15704
15761
  allow: (runtime) => supervisor.allowHarness(runtime),
15705
15762
  update: (auto) => supervisor.update(auto),