@cabane/companion 0.6.157 → 0.6.159

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/cli.js CHANGED
@@ -2896,6 +2896,208 @@ var init_options = __esm({
2896
2896
  }
2897
2897
  });
2898
2898
 
2899
+ // packages/agent-runtime/src/claude-code/failure-evidence.ts
2900
+ function createFailureEvidence() {
2901
+ const signals = { sawRejectedLimit: false };
2902
+ return {
2903
+ observe(msg) {
2904
+ if (msg.type === "assistant" || msg.type === "auth_status") {
2905
+ const error = msg.error;
2906
+ if (typeof error === "string" && error.length > 0) {
2907
+ if (msg.type === "assistant") signals.lastAssistantError = error;
2908
+ else signals.authError = error;
2909
+ }
2910
+ } else if (msg.type === "rate_limit_event") {
2911
+ const info = msg.rate_limit_info;
2912
+ if (info?.status === "rejected") {
2913
+ signals.sawRejectedLimit = true;
2914
+ signals.rateLimitResetIso = resetsAtToIso(info.resetsAt) ?? signals.rateLimitResetIso;
2915
+ if (typeof info.rateLimitType === "string") signals.rateLimitType = info.rateLimitType;
2916
+ }
2917
+ }
2918
+ },
2919
+ preInitReason(msg) {
2920
+ const failure = msg ? classifyPreInitFrame(msg, signals) : classifyFailure(signals, { text: "" });
2921
+ return failure ? encodeFailureReason(failure) : "session_start_failed";
2922
+ },
2923
+ failedResultReason(msg) {
2924
+ const failure = classifyResultFailure(msg, signals);
2925
+ const resultText = msg.result ?? "";
2926
+ const isError = msg.is_error === true;
2927
+ return failure ? encodeFailureReason(failure) : isError ? `error:${resultText.slice(0, 200) || "unknown"}` : `result_error:${msg.subtype}`;
2928
+ },
2929
+ thrownFailure(message2, initReceived) {
2930
+ return initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
2931
+ }
2932
+ };
2933
+ }
2934
+ function classifyFailure(s, frame) {
2935
+ const rejectedIsCap = s.sawRejectedLimit && (s.rateLimitResetIso !== void 0 || isSubscriptionWindow(s.rateLimitType));
2936
+ if (rejectedIsCap || frame.terminalReason === "blocking_limit")
2937
+ return {
2938
+ kind: "usage_capped",
2939
+ ...s.rateLimitResetIso ? { resetsAt: s.rateLimitResetIso } : {}
2940
+ };
2941
+ return classifyAssistantError(s.lastAssistantError) ?? classifyErrorText([s.authError, frame.text].filter(Boolean).join(" "));
2942
+ }
2943
+ function classifyResultFailure(msg, s) {
2944
+ const resultText = msg.result ?? "";
2945
+ const terminalReason = msg.terminal_reason;
2946
+ const errors = msg.errors;
2947
+ const text = [resultText, ...Array.isArray(errors) ? errors.map(String) : []].join(" ");
2948
+ return classifyFailure(s, { terminalReason, text });
2949
+ }
2950
+ function classifyPreInitFrame(msg, s) {
2951
+ if (msg.type === "result") {
2952
+ const isError = msg.is_error === true;
2953
+ if (msg.subtype === "success" && !isError) return classifyFailure(s, { text: "" });
2954
+ return classifyResultFailure(msg, s);
2955
+ }
2956
+ if (msg.type !== "assistant") return classifyFailure(s, { text: "" });
2957
+ const text = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join(" ");
2958
+ return classifyFailure(s, { text });
2959
+ }
2960
+ function preInitToolActivity(msg) {
2961
+ const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
2962
+ return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
2963
+ }
2964
+ function contentBlocks(msg) {
2965
+ const content = msg.message?.content;
2966
+ return Array.isArray(content) ? content : [];
2967
+ }
2968
+ function isSubscriptionWindow(value) {
2969
+ return value === "five_hour" || value === "seven_day" || value === "seven_day_opus" || value === "seven_day_sonnet" || value === "overage";
2970
+ }
2971
+ var init_failure_evidence = __esm({
2972
+ "packages/agent-runtime/src/claude-code/failure-evidence.ts"() {
2973
+ "use strict";
2974
+ init_failure();
2975
+ }
2976
+ });
2977
+
2978
+ // packages/agent-runtime/src/claude-code/context-usage.ts
2979
+ function createContextUsage() {
2980
+ let resolvedModel;
2981
+ let lastRequestContextTokens;
2982
+ const compactions = [];
2983
+ return {
2984
+ get resolvedModel() {
2985
+ return resolvedModel;
2986
+ },
2987
+ recordInit(msg) {
2988
+ const model = msg.model;
2989
+ if (typeof model === "string" && model.length > 0) resolvedModel = model;
2990
+ },
2991
+ recordCompaction(msg) {
2992
+ const compaction = readCompaction(msg);
2993
+ if (compaction) compactions.push(compaction);
2994
+ },
2995
+ recordRequest(msg) {
2996
+ const tokens = readRequestContextTokens(msg);
2997
+ if (tokens !== void 0) lastRequestContextTokens = tokens;
2998
+ },
2999
+ async resultUsage(msg, readContextUsage) {
3000
+ const usage = readSdkUsage(msg.usage);
3001
+ if (usage) {
3002
+ if (lastRequestContextTokens !== void 0) usage.contextTokens = lastRequestContextTokens;
3003
+ const window = readContextWindow(msg, resolvedModel);
3004
+ if (window !== void 0) usage.contextWindow = window;
3005
+ if (compactions.length > 0) usage.contextCompactions = compactions;
3006
+ const reading = await readHarnessContext(readContextUsage);
3007
+ if (reading) Object.assign(usage, reading);
3008
+ }
3009
+ return usage;
3010
+ }
3011
+ };
3012
+ }
3013
+ function readSdkUsage(usage) {
3014
+ if (!usage || typeof usage !== "object") return void 0;
3015
+ const fields = usage;
3016
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3017
+ const cacheReadTokens = num(fields.cache_read_input_tokens);
3018
+ const cacheCreationTokens = num(fields.cache_creation_input_tokens);
3019
+ const inputTokens = num(fields.input_tokens) + cacheCreationTokens + cacheReadTokens;
3020
+ const outputTokens = num(fields.output_tokens);
3021
+ return { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens };
3022
+ }
3023
+ function readRequestContextTokens(msg) {
3024
+ return readSdkUsage(msg.message?.usage)?.inputTokens;
3025
+ }
3026
+ async function readHarnessContext(read) {
3027
+ if (!read) return void 0;
3028
+ let timer;
3029
+ const timeout = new Promise((resolve2) => {
3030
+ timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
3031
+ });
3032
+ try {
3033
+ return parseContextUsage(await Promise.race([read(), timeout]));
3034
+ } catch {
3035
+ return void 0;
3036
+ } finally {
3037
+ clearTimeout(timer);
3038
+ }
3039
+ }
3040
+ function parseContextUsage(response) {
3041
+ const r = response;
3042
+ if (!r || typeof r !== "object") return void 0;
3043
+ const total = readContextCount(r.totalTokens);
3044
+ const window = readContextCount(r.maxTokens);
3045
+ if (total === void 0 || window === void 0 || window === 0) return void 0;
3046
+ const categories = [];
3047
+ for (const c of Array.isArray(r.categories) ? r.categories : []) {
3048
+ const row3 = c;
3049
+ const tokens = readContextCount(row3?.tokens);
3050
+ if (typeof row3?.name === "string" && tokens !== void 0 && typeof row3.kind === "string" && CATEGORY_KINDS.has(row3.kind)) {
3051
+ categories.push({ name: row3.name, tokens, kind: row3.kind });
3052
+ }
3053
+ }
3054
+ const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
3055
+ return {
3056
+ contextTokens: total,
3057
+ contextWindow: window,
3058
+ ...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
3059
+ ...categories.length > 0 ? { contextCategories: categories } : {}
3060
+ };
3061
+ }
3062
+ function readCompaction(msg) {
3063
+ const meta = msg.compact_metadata;
3064
+ const preTokens = readContextCount(meta?.pre_tokens);
3065
+ if (!meta || preTokens === void 0) return void 0;
3066
+ if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
3067
+ const postTokens = readContextCount(meta.post_tokens);
3068
+ return {
3069
+ trigger: meta.trigger,
3070
+ preTokens,
3071
+ ...postTokens !== void 0 ? { postTokens } : {}
3072
+ };
3073
+ }
3074
+ function readContextWindow(msg, resolvedModel) {
3075
+ const modelUsage = msg.modelUsage;
3076
+ if (!modelUsage || typeof modelUsage !== "object") return void 0;
3077
+ const pos = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
3078
+ if (resolvedModel) {
3079
+ const direct = pos(modelUsage[resolvedModel]?.contextWindow);
3080
+ if (direct !== void 0) return direct;
3081
+ }
3082
+ let max;
3083
+ for (const entry of Object.values(modelUsage)) {
3084
+ const w = pos(entry?.contextWindow);
3085
+ if (w !== void 0 && (max === void 0 || w > max)) max = w;
3086
+ }
3087
+ return max;
3088
+ }
3089
+ function readContextCount(v) {
3090
+ return typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
3091
+ }
3092
+ var CONTEXT_READING_TIMEOUT_MS, CATEGORY_KINDS;
3093
+ var init_context_usage = __esm({
3094
+ "packages/agent-runtime/src/claude-code/context-usage.ts"() {
3095
+ "use strict";
3096
+ CONTEXT_READING_TIMEOUT_MS = 2e3;
3097
+ CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
3098
+ }
3099
+ });
3100
+
2899
3101
  // packages/agent-runtime/src/claude-code/loop.ts
2900
3102
  async function* decodeSdkStream(iter, ctx2) {
2901
3103
  const pending = /* @__PURE__ */ new Map();
@@ -2911,17 +3113,15 @@ async function* decodeSdkStream(iter, ctx2) {
2911
3113
  let resultReason;
2912
3114
  let sawResult = false;
2913
3115
  let usage;
2914
- const compactions = [];
2915
- let lastRequestContextTokens;
2916
- let resolvedModel;
2917
- const signals = { sawRejectedLimit: false };
3116
+ const contextUsage = createContextUsage();
3117
+ const failureEvidence = createFailureEvidence();
2918
3118
  let preInitNotice = null;
2919
3119
  try {
2920
3120
  for await (const msg of iter) {
2921
3121
  if (ctx2.signal.aborted) return;
3122
+ failureEvidence.observe(msg);
2922
3123
  if (msg.type === "system" && msg.subtype === "init") {
2923
- const initModel = msg.model;
2924
- if (typeof initModel === "string" && initModel.length > 0) resolvedModel = initModel;
3124
+ contextUsage.recordInit(msg);
2925
3125
  mcpInventory = readMcpInventory(msg);
2926
3126
  workspaceProven = provesWorkspaceTools(msg);
2927
3127
  if (!workspaceProven) {
@@ -2938,63 +3138,35 @@ async function* decodeSdkStream(iter, ctx2) {
2938
3138
  };
2939
3139
  }
2940
3140
  } else if (msg.type === "system" && msg.subtype === "compact_boundary") {
2941
- const compaction = readCompaction(msg);
2942
- if (compaction) compactions.push(compaction);
3141
+ contextUsage.recordCompaction(msg);
2943
3142
  } else if (!workspaceProven && (msg.type === "assistant" || msg.type === "user" || msg.type === "result")) {
2944
3143
  if (preInitToolActivity(msg) && ctx2.startEvidence) ctx2.startEvidence.toolActivity = true;
2945
- const failure = classifyPreInitFrame(msg, signals);
2946
- resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
3144
+ resultReason = failureEvidence.preInitReason(msg);
2947
3145
  if (msg.type === "assistant" && isHarnessAuthored(msg))
2948
3146
  preInitNotice = harnessNoticeText(msg);
2949
3147
  break;
2950
3148
  } else if (msg.type === "assistant") {
2951
- const assistantErr = msg.error;
2952
- if (typeof assistantErr === "string" && assistantErr.length > 0) {
2953
- signals.lastAssistantError = assistantErr;
2954
- }
2955
3149
  if (isHarnessAuthored(msg)) {
2956
3150
  await emitHarnessMessage(msg, emit, pending, buffer);
2957
3151
  yield* drain(out);
2958
3152
  continue;
2959
3153
  }
2960
- const reqContext = readRequestContextTokens(msg);
2961
- if (reqContext !== void 0) lastRequestContextTokens = reqContext;
3154
+ contextUsage.recordRequest(msg);
2962
3155
  await processAssistantMessage(msg, emit, pending, buffer);
2963
3156
  yield* drain(out);
2964
3157
  } else if (msg.type === "user") {
2965
3158
  await emitToolResults(msg, emit, pending);
2966
3159
  yield* drain(out);
2967
- } else if (msg.type === "rate_limit_event") {
2968
- const info = msg.rate_limit_info;
2969
- if (info?.status === "rejected") {
2970
- signals.sawRejectedLimit = true;
2971
- signals.rateLimitResetIso = resetsAtToIso(info.resetsAt) ?? signals.rateLimitResetIso;
2972
- if (typeof info.rateLimitType === "string") signals.rateLimitType = info.rateLimitType;
2973
- }
2974
- } else if (msg.type === "auth_status") {
2975
- const err = msg.error;
2976
- if (typeof err === "string" && err.length > 0) signals.authError = err;
2977
3160
  } else if (msg.type === "result") {
2978
3161
  const origin = msg.origin;
2979
3162
  if (origin?.kind === "task-notification") continue;
2980
3163
  sawResult = true;
2981
- usage = readSdkUsage(msg);
2982
- if (usage) {
2983
- if (lastRequestContextTokens !== void 0)
2984
- usage.contextTokens = lastRequestContextTokens;
2985
- const window = readContextWindow(msg, resolvedModel);
2986
- if (window !== void 0) usage.contextWindow = window;
2987
- if (compactions.length > 0) usage.contextCompactions = compactions;
2988
- const reading = await readHarnessContext(ctx2.readContextUsage);
2989
- if (reading) Object.assign(usage, reading);
2990
- }
3164
+ usage = await contextUsage.resultUsage(msg, ctx2.readContextUsage);
2991
3165
  const isError = msg.is_error === true;
2992
3166
  if (msg.subtype === "success" && !isError) {
2993
3167
  ok = true;
2994
3168
  } else {
2995
- const resultText = msg.result ?? "";
2996
- const failure = classifyResultFailure(msg, signals);
2997
- resultReason = failure ? encodeFailureReason(failure) : isError ? `error:${resultText.slice(0, 200) || "unknown"}` : `result_error:${msg.subtype}`;
3169
+ resultReason = failureEvidence.failedResultReason(msg);
2998
3170
  ok = false;
2999
3171
  }
3000
3172
  break;
@@ -3003,7 +3175,7 @@ async function* decodeSdkStream(iter, ctx2) {
3003
3175
  } catch (err) {
3004
3176
  if (ctx2.signal.aborted) throw err;
3005
3177
  const message2 = err instanceof Error ? err.message : String(err);
3006
- const failure = mcpInventory.initReceived ? classifyErrorText(message2) : classifyFailure(signals, { text: message2 });
3178
+ const failure = failureEvidence.thrownFailure(message2, mcpInventory.initReceived);
3007
3179
  if (!failure && (mcpInventory.initReceived || ctx2.resumedSessionId === null)) throw err;
3008
3180
  ok = false;
3009
3181
  if (failure) {
@@ -3018,8 +3190,7 @@ async function* decodeSdkStream(iter, ctx2) {
3018
3190
  if (!workspaceProven) {
3019
3191
  ok = false;
3020
3192
  if (resultReason === void 0) {
3021
- const failure = classifyFailure(signals, { text: "" });
3022
- resultReason = failure ? encodeFailureReason(failure) : "session_start_failed";
3193
+ resultReason = failureEvidence.preInitReason();
3023
3194
  }
3024
3195
  if (preInitNotice) yield { type: "runtime_notice", body: preInitNotice };
3025
3196
  }
@@ -3037,7 +3208,7 @@ async function* decodeSdkStream(iter, ctx2) {
3037
3208
  ok,
3038
3209
  ...resultReason ? { reason: resultReason } : {},
3039
3210
  ...usage ? { usage } : {},
3040
- ...resolvedModel ? { resolvedModel } : {},
3211
+ ...contextUsage.resolvedModel ? { resolvedModel: contextUsage.resolvedModel } : {},
3041
3212
  mcpInventory
3042
3213
  };
3043
3214
  }
@@ -3056,38 +3227,6 @@ function provesWorkspaceTools(msg) {
3056
3227
  const tools = msg.tools;
3057
3228
  return Array.isArray(tools) && tools.includes("mcp__cabane__query");
3058
3229
  }
3059
- function classifyFailure(s, frame) {
3060
- const rejectedIsCap = s.sawRejectedLimit && (s.rateLimitResetIso !== void 0 || isSubscriptionWindow(s.rateLimitType));
3061
- if (rejectedIsCap || frame.terminalReason === "blocking_limit")
3062
- return {
3063
- kind: "usage_capped",
3064
- ...s.rateLimitResetIso ? { resetsAt: s.rateLimitResetIso } : {}
3065
- };
3066
- return classifyAssistantError(s.lastAssistantError) ?? classifyErrorText([s.authError, frame.text].filter(Boolean).join(" "));
3067
- }
3068
- function classifyResultFailure(msg, s) {
3069
- const resultText = msg.result ?? "";
3070
- const terminalReason = msg.terminal_reason;
3071
- const errors = msg.errors;
3072
- const text = [resultText, ...Array.isArray(errors) ? errors.map(String) : []].join(" ");
3073
- return classifyFailure(s, { terminalReason, text });
3074
- }
3075
- function classifyPreInitFrame(msg, s) {
3076
- if (msg.type === "result") {
3077
- const isError = msg.is_error === true;
3078
- if (msg.subtype === "success" && !isError) return classifyFailure(s, { text: "" });
3079
- return classifyResultFailure(msg, s);
3080
- }
3081
- if (msg.type !== "assistant") return classifyFailure(s, { text: "" });
3082
- const error = msg.error;
3083
- const signals = typeof error === "string" && error.length > 0 ? { ...s, lastAssistantError: error } : s;
3084
- const text = contentBlocks(msg).flatMap((b) => b.type === "text" && typeof b.text === "string" ? [b.text] : []).join(" ");
3085
- return classifyFailure(signals, { text });
3086
- }
3087
- function preInitToolActivity(msg) {
3088
- const kind = msg.type === "assistant" ? "tool_use" : msg.type === "user" ? "tool_result" : null;
3089
- return kind !== null && contentBlocks(msg).some((b) => b.type === kind);
3090
- }
3091
3230
  function isHarnessAuthored(msg) {
3092
3231
  const error = msg.error;
3093
3232
  if (typeof error === "string" && error.length > 0) return true;
@@ -3111,107 +3250,19 @@ async function emitHarnessMessage(msg, emit, pending, buffer) {
3111
3250
  );
3112
3251
  }
3113
3252
  }
3114
- function contentBlocks(msg) {
3115
- const content = msg.message?.content;
3116
- return Array.isArray(content) ? content : [];
3117
- }
3118
- function isSubscriptionWindow(value) {
3119
- return value === "five_hour" || value === "seven_day" || value === "seven_day_opus" || value === "seven_day_sonnet" || value === "overage";
3120
- }
3121
3253
  function* drain(out) {
3122
3254
  while (out.length > 0) yield out.shift();
3123
3255
  }
3124
- function readSdkUsage(msg) {
3125
- const usage = msg.usage;
3126
- if (!usage || typeof usage !== "object") return void 0;
3127
- const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3128
- const cacheReadTokens = num(usage.cache_read_input_tokens);
3129
- const cacheCreationTokens = num(usage.cache_creation_input_tokens);
3130
- const inputTokens = num(usage.input_tokens) + cacheCreationTokens + cacheReadTokens;
3131
- const outputTokens = num(usage.output_tokens);
3132
- return { inputTokens, outputTokens, cacheReadTokens, cacheCreationTokens };
3133
- }
3134
- function readRequestContextTokens(msg) {
3135
- const usage = msg.message?.usage;
3136
- if (!usage || typeof usage !== "object") return void 0;
3137
- const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : 0;
3138
- return num(usage.input_tokens) + num(usage.cache_read_input_tokens) + num(usage.cache_creation_input_tokens);
3139
- }
3140
- async function readHarnessContext(read) {
3141
- if (!read) return void 0;
3142
- let timer;
3143
- const timeout = new Promise((resolve2) => {
3144
- timer = setTimeout(() => resolve2(void 0), CONTEXT_READING_TIMEOUT_MS);
3145
- });
3146
- try {
3147
- return parseContextUsage(await Promise.race([read(), timeout]));
3148
- } catch {
3149
- return void 0;
3150
- } finally {
3151
- clearTimeout(timer);
3152
- }
3153
- }
3154
- function parseContextUsage(response) {
3155
- const r = response;
3156
- if (!r || typeof r !== "object") return void 0;
3157
- const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
3158
- const total = count(r.totalTokens);
3159
- const window = count(r.maxTokens);
3160
- if (total === void 0 || window === void 0 || window === 0) return void 0;
3161
- const categories = [];
3162
- for (const c of Array.isArray(r.categories) ? r.categories : []) {
3163
- const row3 = c;
3164
- const tokens = count(row3?.tokens);
3165
- if (typeof row3?.name === "string" && tokens !== void 0 && typeof row3.kind === "string" && CATEGORY_KINDS.has(row3.kind)) {
3166
- categories.push({ name: row3.name, tokens, kind: row3.kind });
3167
- }
3168
- }
3169
- const buffer = categories.filter((c) => c.kind === "buffer").reduce((sum, c) => sum + c.tokens, 0);
3170
- return {
3171
- contextTokens: total,
3172
- contextWindow: window,
3173
- ...buffer > 0 && buffer < window ? { contextCompactAt: window - buffer } : {},
3174
- ...categories.length > 0 ? { contextCategories: categories } : {}
3175
- };
3176
- }
3177
- function readCompaction(msg) {
3178
- const meta = msg.compact_metadata;
3179
- const count = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? Math.round(v) : void 0;
3180
- const preTokens = count(meta?.pre_tokens);
3181
- if (!meta || preTokens === void 0) return void 0;
3182
- if (meta.trigger !== "manual" && meta.trigger !== "auto") return void 0;
3183
- const postTokens = count(meta.post_tokens);
3184
- return {
3185
- trigger: meta.trigger,
3186
- preTokens,
3187
- ...postTokens !== void 0 ? { postTokens } : {}
3188
- };
3189
- }
3190
- function readContextWindow(msg, resolvedModel) {
3191
- const modelUsage = msg.modelUsage;
3192
- if (!modelUsage || typeof modelUsage !== "object") return void 0;
3193
- const pos = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
3194
- if (resolvedModel) {
3195
- const direct = pos(modelUsage[resolvedModel]?.contextWindow);
3196
- if (direct !== void 0) return direct;
3197
- }
3198
- let max;
3199
- for (const entry of Object.values(modelUsage)) {
3200
- const w = pos(entry?.contextWindow);
3201
- if (w !== void 0 && (max === void 0 || w > max)) max = w;
3202
- }
3203
- return max;
3204
- }
3205
- var HARNESS_PLACEHOLDERS, CONTEXT_READING_TIMEOUT_MS, CATEGORY_KINDS;
3256
+ var HARNESS_PLACEHOLDERS;
3206
3257
  var init_loop = __esm({
3207
3258
  "packages/agent-runtime/src/claude-code/loop.ts"() {
3208
3259
  "use strict";
3209
3260
  init_transcript();
3210
3261
  init_failure();
3211
3262
  init_session();
3263
+ init_failure_evidence();
3264
+ init_context_usage();
3212
3265
  HARNESS_PLACEHOLDERS = /* @__PURE__ */ new Set(["No response requested.", "(no content)"]);
3213
- CONTEXT_READING_TIMEOUT_MS = 2e3;
3214
- CATEGORY_KINDS = /* @__PURE__ */ new Set(["used", "free", "buffer", "deferred"]);
3215
3266
  }
3216
3267
  });
3217
3268
 
@@ -15382,6 +15433,8 @@ var SseSubscriber = class {
15382
15433
  opts;
15383
15434
  aborted = false;
15384
15435
  controller = null;
15436
+ // Ends the reconnect backoff early, so a stop never waits out a sleep of up to 30 s.
15437
+ wake = null;
15385
15438
  lastEventId;
15386
15439
  // Set after `start()`; resolved when the subscriber loop exits (either
15387
15440
  // because `stop()` was called or because an auth failure tore it down).
@@ -15392,12 +15445,7 @@ var SseSubscriber = class {
15392
15445
  stop() {
15393
15446
  this.aborted = true;
15394
15447
  this.controller?.abort();
15395
- }
15396
- // Connect again after a `stop()`, once its loop has exited.
15397
- async resume() {
15398
- await this.finished;
15399
- this.aborted = false;
15400
- this.start();
15448
+ this.wake?.();
15401
15449
  }
15402
15450
  setCursor(eventId) {
15403
15451
  this.lastEventId = eventId;
@@ -15410,7 +15458,7 @@ var SseSubscriber = class {
15410
15458
  backoff = 500;
15411
15459
  if (!this.aborted) {
15412
15460
  this.opts.log.warn("Lost the connection to Cabane; reconnecting");
15413
- await sleep4(backoff);
15461
+ await this.backoff(backoff);
15414
15462
  }
15415
15463
  } catch (err) {
15416
15464
  if (this.aborted) return;
@@ -15430,11 +15478,21 @@ var SseSubscriber = class {
15430
15478
  },
15431
15479
  "Lost the connection to Cabane; reconnecting"
15432
15480
  );
15433
- await sleep4(backoff);
15481
+ await this.backoff(backoff);
15434
15482
  backoff = Math.min(backoff * 2, MAX_BACKOFF_MS);
15435
15483
  }
15436
15484
  }
15437
15485
  }
15486
+ backoff(ms) {
15487
+ return new Promise((resolve2) => {
15488
+ const timer = setTimeout(done, ms);
15489
+ this.wake = done;
15490
+ function done() {
15491
+ clearTimeout(timer);
15492
+ resolve2();
15493
+ }
15494
+ });
15495
+ }
15438
15496
  async connect() {
15439
15497
  const base = this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
15440
15498
  const url = `${base}${this.opts.path}`;
@@ -15481,9 +15539,6 @@ var SseSubscriber = class {
15481
15539
  }
15482
15540
  }
15483
15541
  };
15484
- function sleep4(ms) {
15485
- return new Promise((resolve2) => setTimeout(resolve2, ms));
15486
- }
15487
15542
 
15488
15543
  // src/supervisor.ts
15489
15544
  init_transcript_writer();
@@ -15666,7 +15721,7 @@ function updateMode(env = process.env) {
15666
15721
  return env.CABANE_COMPANION_SUPERVISED === "1" ? "supervised" : "attached";
15667
15722
  }
15668
15723
  var STAGE_RETRY_MS = 60 * 6e4;
15669
- var SWAP_DRAIN_MS = 1e4;
15724
+ var SWAP_WAIT_MS = 10 * 6e4;
15670
15725
  var SOURCE_BUILD_VERSION = "0.0.0";
15671
15726
  function versionToStage(input) {
15672
15727
  const { own, state, now, force } = input;
@@ -15816,6 +15871,8 @@ var CompanionUpdater = class {
15816
15871
  deps;
15817
15872
  staging = null;
15818
15873
  ready = null;
15874
+ // When `ready` verified; `SWAP_WAIT_MS` counts from here.
15875
+ readyAt = 0;
15819
15876
  swapping = false;
15820
15877
  watchdog = null;
15821
15878
  now;
@@ -15914,7 +15971,7 @@ var CompanionUpdater = class {
15914
15971
  return { ok: true, message: `Cabane ${this.deps.own} is up to date.` };
15915
15972
  if (state.pending?.to === latest) return { ok: true, message: this.waitingMessage(latest) };
15916
15973
  if (this.ready === latest) {
15917
- void this.trySwap();
15974
+ void this.trySwap(true);
15918
15975
  return { ok: true, message: this.waitingMessage(latest) };
15919
15976
  }
15920
15977
  this.maybeStage(true);
@@ -15930,13 +15987,14 @@ var CompanionUpdater = class {
15930
15987
  message: failed?.kind === "failed" && failed.to === latest ? `Couldn't update to ${latest}: ${failed.reason ?? "see cabane logs"}.` : `Couldn't update to ${latest}. cabane logs says why.`
15931
15988
  };
15932
15989
  }
15933
- void this.trySwap();
15990
+ void this.trySwap(true);
15934
15991
  return { ok: true, message: this.waitingMessage(latest) };
15935
15992
  }
15993
+ // Only the person's `cabane update` asks, and that swaps at once.
15936
15994
  waitingMessage(version) {
15937
15995
  if (this.deps.mode === "attached")
15938
15996
  return `Cabane ${version} is installed. It runs from the next cabane start.`;
15939
- return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version} once the turns running now finish.`;
15997
+ return this.deps.isIdle() ? `Updating to ${version} now.` : `Updating to ${version} now. The turns running now carry on after the restart.`;
15940
15998
  }
15941
15999
  maybeStage(force) {
15942
16000
  if (this.staging) return;
@@ -15954,6 +16012,7 @@ var CompanionUpdater = class {
15954
16012
  const outcome = await stage(version, this.deps.harnesses());
15955
16013
  if (outcome.ok) {
15956
16014
  this.ready = version;
16015
+ this.readyAt = this.now();
15957
16016
  updateUpdateState((s) => {
15958
16017
  const { failed: _failed, ...rest } = s;
15959
16018
  return rest;
@@ -15992,8 +16051,10 @@ var CompanionUpdater = class {
15992
16051
  });
15993
16052
  void this.staging.then(() => this.trySwap());
15994
16053
  }
15995
- // Swap into the ready version once nothing is running. Called on each heartbeat until it can.
15996
- async trySwap() {
16054
+ // Swap into the ready version: when nothing is running, when it has waited `SWAP_WAIT_MS` for
16055
+ // that, when this version is below the server's minimum, or `now` (the person asked). Called on
16056
+ // each heartbeat until it does.
16057
+ async trySwap(now = false) {
15997
16058
  const to = this.ready;
15998
16059
  if (!to || this.swapping) return;
15999
16060
  if (readUpdateState().pending) return;
@@ -16002,18 +16063,9 @@ var CompanionUpdater = class {
16002
16063
  this.deps.log.info({ version: to }, `Cabane ${to} is installed; it runs from the next start`);
16003
16064
  return;
16004
16065
  }
16005
- if (!this.deps.isIdle()) return;
16066
+ if (!now && !this.deps.isIdle() && !this.waitedLongEnough() && !this.belowMinimum()) return;
16006
16067
  this.swapping = true;
16007
16068
  try {
16008
- await this.deps.pauseStream();
16009
- const sleep5 = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
16010
- const deadline = this.now() + SWAP_DRAIN_MS;
16011
- await sleep5(250);
16012
- while (!this.deps.isIdle() && this.now() < deadline) await sleep5(250);
16013
- if (!this.deps.isIdle()) {
16014
- this.deps.resumeStream();
16015
- return;
16016
- }
16017
16069
  this.select(to);
16018
16070
  this.deps.log.info({ from: this.deps.own, to }, `Restarting into Cabane ${to}`);
16019
16071
  await this.deps.restart();
@@ -16021,6 +16073,14 @@ var CompanionUpdater = class {
16021
16073
  this.swapping = false;
16022
16074
  }
16023
16075
  }
16076
+ waitedLongEnough() {
16077
+ return this.now() - this.readyAt >= SWAP_WAIT_MS;
16078
+ }
16079
+ // Cabane refuses this version's turns: waiting would only turn them away.
16080
+ belowMinimum() {
16081
+ const minimum = readUpdateState().server?.minimum;
16082
+ return !!minimum && compareVersions(this.deps.own, minimum) < 0;
16083
+ }
16024
16084
  // Record the swap and point `current` at `to`, leaving the guard that counts its starts.
16025
16085
  select(to) {
16026
16086
  const fromRoot = currentRoot() ?? ownPackageRoot();
@@ -16045,6 +16105,7 @@ init_version2();
16045
16105
  init_src();
16046
16106
  var HEARTBEAT_INTERVAL_MS = 3e4;
16047
16107
  var ARCHIVE_PRUNE_MS = 24 * 60 * 6e4;
16108
+ var FORCE_EXIT_MS = 4e3;
16048
16109
  var SHUTDOWN_HEARTBEAT_WAIT_MS = 1e3;
16049
16110
  function positiveIntEnv(name) {
16050
16111
  const raw = process.env[name];
@@ -16265,14 +16326,7 @@ var CompanionSupervisor = class {
16265
16326
  autoUpdate: () => this.config.update?.auto === true,
16266
16327
  harnesses: () => this.connectedBundledHarnesses(),
16267
16328
  isIdle: () => this.idle(),
16268
- pauseStream: async () => {
16269
- const sub = this.deviceSub;
16270
- if (!sub) return;
16271
- sub.stop();
16272
- await sub.finished;
16273
- },
16274
- resumeStream: () => void this.deviceSub?.resume(),
16275
- restart: () => this.restartForUpdate(),
16329
+ restart: () => this.restartBounded(),
16276
16330
  log: this.log
16277
16331
  });
16278
16332
  this.updater.start();
@@ -17394,8 +17448,22 @@ var CompanionSupervisor = class {
17394
17448
  );
17395
17449
  }
17396
17450
  }
17451
+ // The update's restart, held to the same `FORCE_EXIT_MS` a signal's stop gets. Exit 0: the
17452
+ // swap is recorded and `current` names the new version, so the service starts it either way.
17453
+ async restartBounded() {
17454
+ const force = setTimeout(() => {
17455
+ this.log.warn({ ms: FORCE_EXIT_MS }, "Restarting for the update: shutdown took too long");
17456
+ this.exitFn(0);
17457
+ }, FORCE_EXIT_MS);
17458
+ force.unref?.();
17459
+ try {
17460
+ await this.restartForUpdate();
17461
+ } finally {
17462
+ clearTimeout(force);
17463
+ }
17464
+ }
17397
17465
  // Nothing running and nothing about to: no dispatch claimed, no turn chained, no recovery
17398
- // in progress. What a self-update swap waits for.
17466
+ // in progress. What a self-update swap waits for, up to `SWAP_WAIT_MS`.
17399
17467
  idle() {
17400
17468
  if (this.claims.size > 0 || this.recovering) return false;
17401
17469
  for (const wr of this.workspaces.values()) if (wr.chains.size > 0) return false;
@@ -17417,7 +17485,7 @@ var CompanionSupervisor = class {
17417
17485
  if (auto) this.kickHeartbeat();
17418
17486
  return {
17419
17487
  ok: true,
17420
- message: auto ? "Cabane will keep itself up to date. Updates install between turns and roll back if they fail." : "Cabane won't update itself. Run cabane update to update."
17488
+ message: auto ? "Cabane will keep itself up to date. Updates install between turns when they can, and roll back if they fail." : "Cabane won't update itself. Run cabane update to update."
17421
17489
  };
17422
17490
  }
17423
17491
  if (!this.updater) return { ok: false, message: "This computer isn't paired yet." };
@@ -18127,7 +18195,7 @@ async function startDaemon(opts = {}, deps = {}) {
18127
18195
  const readState = deps.readState ?? readLiveRuntimeState;
18128
18196
  const verify = deps.verify ?? ((s) => verifyRuntime(s));
18129
18197
  const spawnDetached = deps.spawnDetached ?? defaultSpawnDetached;
18130
- const sleep5 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18198
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18131
18199
  const now = deps.now ?? (() => Date.now());
18132
18200
  const quiet = opts.report === "failures";
18133
18201
  const cfg = requireStartConfig({
@@ -18175,7 +18243,7 @@ async function startDaemon(opts = {}, deps = {}) {
18175
18243
  const deadline = now() + STARTUP_TIMEOUT_MS;
18176
18244
  let seen = readState();
18177
18245
  while (!ready(seen) && now() < deadline) {
18178
- await sleep5(POLL_INTERVAL_MS);
18246
+ await sleep4(POLL_INTERVAL_MS);
18179
18247
  seen = readState();
18180
18248
  }
18181
18249
  return ready(seen) ? seen : null;
@@ -18248,7 +18316,7 @@ async function stop(deps = {}) {
18248
18316
  const readState = deps.readState ?? readLiveRuntimeState;
18249
18317
  const verify = deps.verify ?? ((s) => verifyRuntime(s));
18250
18318
  const kill = deps.kill ?? ((pid2, signal) => process.kill(pid2, signal));
18251
- const sleep5 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18319
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18252
18320
  const now = deps.now ?? (() => Date.now());
18253
18321
  const state = readState();
18254
18322
  if (!state) {
@@ -18276,7 +18344,7 @@ async function stop(deps = {}) {
18276
18344
  process.stdout.write("companion: stopped.\n");
18277
18345
  return;
18278
18346
  }
18279
- await sleep5(POLL_INTERVAL_MS2);
18347
+ await sleep4(POLL_INTERVAL_MS2);
18280
18348
  }
18281
18349
  process.stdout.write(
18282
18350
  `companion: didn't exit within ${TERM_GRACE_MS / 1e3}s, sending SIGKILL.
@@ -18302,7 +18370,7 @@ function isAlive(kill, pid) {
18302
18370
  var STARTUP_WAIT_MS = 2e4;
18303
18371
  async function installAndStartService(deps = {}) {
18304
18372
  const readState = deps.readState ?? readLiveRuntimeState;
18305
- const sleep5 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18373
+ const sleep4 = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
18306
18374
  const now = deps.now ?? Date.now;
18307
18375
  const before = await serviceState(deps);
18308
18376
  const running = readState();
@@ -18318,7 +18386,7 @@ async function installAndStartService(deps = {}) {
18318
18386
  while (now() < deadline) {
18319
18387
  const state = readState();
18320
18388
  if (state?.socket) return true;
18321
- await sleep5(250);
18389
+ await sleep4(250);
18322
18390
  }
18323
18391
  write(
18324
18392
  `${INDENT}The service is installed but Cabane hasn't come up yet. ${cabane("service status")} and ${cabane("logs")} say why.`
@@ -18332,7 +18400,7 @@ async function serviceInstall(deps = {}) {
18332
18400
  }
18333
18401
  write(
18334
18402
  INDENT + tick(
18335
- "Cabane runs as a service. It starts at boot, restarts if it stops, and updates itself between turns."
18403
+ "Cabane runs as a service. It starts at boot, restarts if it stops, and updates itself, usually between turns."
18336
18404
  )
18337
18405
  );
18338
18406
  write(`${INDENT} Status: ${cabane("service status")} Logs: ${cabane("logs")}`);
@@ -18395,7 +18463,7 @@ function updateRows(state, auto, opts = {}) {
18395
18463
  rows.push(
18396
18464
  row(
18397
18465
  "Auto-update",
18398
- !opts.service ? `off \u2014 runs with the service: ${cabane("service install")}` : auto === true ? "on \u2014 updates install between turns and roll back if they fail" : `off \u2014 ${cabane("update --auto on")} turns it on`
18466
+ !opts.service ? `off \u2014 runs with the service: ${cabane("service install")}` : auto === true ? "on \u2014 updates install between turns when they can, and roll back if they fail" : `off \u2014 ${cabane("update --auto on")} turns it on`
18399
18467
  )
18400
18468
  );
18401
18469
  const last = state.lastResult;
@@ -18426,7 +18494,6 @@ function setRunsAsService(on) {
18426
18494
  }
18427
18495
 
18428
18496
  // src/commands/start.ts
18429
- var FORCE_EXIT_MS = 4e3;
18430
18497
  var MAX_CODES = 3;
18431
18498
  async function start(opts = {}) {
18432
18499
  setLogOverrides(opts);
@@ -18892,7 +18959,7 @@ async function askHowToRun(deps = {}) {
18892
18959
  return { asService: false };
18893
18960
  blank();
18894
18961
  const asService = await ask(
18895
- "Keep Cabane running and up to date on this computer? It starts at boot, restarts if it stops, and updates itself between turns."
18962
+ "Keep Cabane running and up to date on this computer? It starts at boot, restarts if it stops, and updates itself, usually between turns."
18896
18963
  );
18897
18964
  if (!asService) saveConfig({ ...cfg, serviceDeclined: true });
18898
18965
  blank();
@@ -19316,7 +19383,7 @@ async function update(opts = {}) {
19316
19383
  saveConfig({ ...cfg2, update: { auto } });
19317
19384
  write(
19318
19385
  INDENT + tick(
19319
- auto ? "Cabane will keep itself up to date. Updates install between turns and roll back if they fail." : "Cabane won't update itself."
19386
+ auto ? "Cabane will keep itself up to date. Updates install between turns when they can, and roll back if they fail." : "Cabane won't update itself."
19320
19387
  )
19321
19388
  );
19322
19389
  return;