claudish 10.1.1 → 10.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +2114 -424
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -715,7 +715,7 @@ var init_onepassword_config = __esm(() => {
715
715
  });
716
716
 
717
717
  // src/version.ts
718
- var VERSION = "10.1.1";
718
+ var VERSION = "10.2.0";
719
719
 
720
720
  // src/logger.ts
721
721
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -802,7 +802,7 @@ function redactDeep(val, key) {
802
802
  return val;
803
803
  }
804
804
  function isStructuralLogWorthy(msg) {
805
- return msg.startsWith("[SSE:") || msg.startsWith("[Suppressed]") || msg.startsWith("[Proxy]") || msg.startsWith("[Claude Code]") || msg.startsWith("[Fallback]") || msg.startsWith("[Streaming] ===") || msg.startsWith("[Streaming] Chunk:") || msg.startsWith("[Streaming] Received") || msg.startsWith("[Streaming] Text-based tool calls") || msg.startsWith("[Streaming] Final usage") || msg.startsWith("[Streaming] Sending") || msg.startsWith("[AnthropicSSE] Stream complete") || msg.startsWith("[AnthropicSSE] Tool use:") || msg.includes("Response status:") || msg.includes("Error") || msg.includes("error") || msg.includes("[Auto-route]");
805
+ return msg.startsWith("[SSE:") || msg.startsWith("[Suppressed]") || msg.startsWith("[Proxy]") || msg.startsWith("[Claude Code]") || msg.startsWith("[Fallback]") || msg.startsWith("[Recovery]") || msg.startsWith("[Streaming] ===") || msg.startsWith("[Streaming] Chunk:") || msg.startsWith("[Streaming] Received") || msg.startsWith("[Streaming] Text-based tool calls") || msg.startsWith("[Streaming] Final usage") || msg.startsWith("[Streaming] Sending") || msg.startsWith("[AnthropicSSE] Stream complete") || msg.startsWith("[AnthropicSSE] Tool use:") || msg.includes("Response status:") || msg.includes("Error") || msg.includes("error") || msg.includes("[Auto-route]");
806
806
  }
807
807
  function redactLogLine(message, timestamp) {
808
808
  if (message.startsWith("[SSE:")) {
@@ -885,6 +885,9 @@ function logStderr(message) {
885
885
  }
886
886
  log(message);
887
887
  }
888
+ function logRecovery(message) {
889
+ logStderr(message);
890
+ }
888
891
  function setDiagOutput(output) {
889
892
  diagOutput = output;
890
893
  }
@@ -18769,6 +18772,12 @@ function loadConfig() {
18769
18772
  if (config.proOnUltracode !== undefined) {
18770
18773
  merged.proOnUltracode = config.proOnUltracode;
18771
18774
  }
18775
+ if (config.recovery !== undefined) {
18776
+ merged.recovery = config.recovery;
18777
+ }
18778
+ if (config.recoveryUi !== undefined) {
18779
+ merged.recoveryUi = config.recoveryUi;
18780
+ }
18772
18781
  return merged;
18773
18782
  } catch (error) {
18774
18783
  console.error(`Warning: Failed to load config, using defaults: ${error}`);
@@ -18817,6 +18826,26 @@ function readProOnUltracode(paths = defaultScopedConfigPaths) {
18817
18826
  }
18818
18827
  return;
18819
18828
  }
18829
+ function readNestedEnabled(key, paths) {
18830
+ for (const pathFn of [paths.project, paths.global]) {
18831
+ try {
18832
+ const path = pathFn();
18833
+ if (!existsSync6(path))
18834
+ continue;
18835
+ const parsed = JSON.parse(readFileSync6(path, "utf-8"));
18836
+ const value = parsed?.[key]?.enabled;
18837
+ if (typeof value === "boolean")
18838
+ return value;
18839
+ } catch {}
18840
+ }
18841
+ return;
18842
+ }
18843
+ function readRecoveryEnabled(paths = defaultScopedConfigPaths) {
18844
+ return readNestedEnabled("recovery", paths);
18845
+ }
18846
+ function readRecoveryUi(paths = defaultScopedConfigPaths) {
18847
+ return readNestedEnabled("recoveryUi", paths);
18848
+ }
18820
18849
  function isProjectDirectory() {
18821
18850
  const cwd = process.cwd();
18822
18851
  return [".git", "package.json", "Cargo.toml", "go.mod", "pyproject.toml", ".claudish.json"].some((f) => existsSync6(join8(cwd, f)));
@@ -24926,6 +24955,749 @@ Signed in to Grok. Try it with: claudish --model gk@grok-4.6 "hello"
24926
24955
  };
24927
24956
  });
24928
24957
 
24958
+ // src/handlers/shared/connection-error.ts
24959
+ function markOwnTimeout(error) {
24960
+ if (error && typeof error === "object") {
24961
+ try {
24962
+ Object.defineProperty(error, OWN_TIMEOUT_FLAG, {
24963
+ value: true,
24964
+ enumerable: false,
24965
+ configurable: true,
24966
+ writable: true
24967
+ });
24968
+ } catch {}
24969
+ }
24970
+ return error;
24971
+ }
24972
+ function isOwnTimeout(error) {
24973
+ return Boolean(error?.[OWN_TIMEOUT_FLAG]);
24974
+ }
24975
+ function findConnectionCode(error) {
24976
+ let e = error;
24977
+ const seen = new Set;
24978
+ for (let depth = 0;e && typeof e === "object" && depth < 8 && !seen.has(e); depth++) {
24979
+ seen.add(e);
24980
+ if (typeof e.code === "string" && e.code in CODE_KIND)
24981
+ return e.code;
24982
+ if (typeof e.name === "string" && e.name in NAME_KIND && isOwnTimeout(e))
24983
+ return e.name;
24984
+ e = e.cause;
24985
+ }
24986
+ const msg = String(error?.message ?? error ?? "");
24987
+ if (/getaddrinfo|ENOTFOUND|EAI_AGAIN|nodename nor servname/i.test(msg))
24988
+ return "ENOTFOUND";
24989
+ if (BUN_CONNECT_MESSAGE.test(msg))
24990
+ return "ConnectionRefused";
24991
+ return null;
24992
+ }
24993
+ function isLoopback(endpoint) {
24994
+ try {
24995
+ const { hostname } = new URL(endpoint);
24996
+ return /^(localhost|127\.\d+\.\d+\.\d+|0\.0\.0\.0|\[?::1\]?)$/i.test(hostname);
24997
+ } catch {
24998
+ return false;
24999
+ }
25000
+ }
25001
+ function classifyConnectionError(error) {
25002
+ const code = findConnectionCode(error);
25003
+ if (!code)
25004
+ return null;
25005
+ return { kind: CODE_KIND[code] ?? NAME_KIND[code] ?? "unreachable", code };
25006
+ }
25007
+ function hostOf(endpoint) {
25008
+ try {
25009
+ return new URL(endpoint).host || endpoint;
25010
+ } catch {
25011
+ return endpoint;
25012
+ }
25013
+ }
25014
+ function buildConnectionErrorMessage(kind, displayName, endpoint) {
25015
+ const host = hostOf(endpoint);
25016
+ switch (kind) {
25017
+ case "dns":
25018
+ return `Cannot resolve ${host} for ${displayName}. This is a DNS/network problem on your machine \u2014 check your internet connection, VPN, or DNS resolver (e.g. Tailscale MagicDNS) \u2014 not ${displayName}.`;
25019
+ case "refused":
25020
+ if (isLoopback(endpoint)) {
25021
+ return `Cannot connect to ${displayName} at ${endpoint}. Make sure the server is running.`;
25022
+ }
25023
+ return `Cannot reach ${host} for ${displayName}. This is a network problem on your machine \u2014 check your internet connection, VPN, or DNS resolver (e.g. Tailscale MagicDNS) \u2014 not ${displayName}.`;
25024
+ case "unreachable":
25025
+ return `Cannot reach ${displayName} at ${endpoint}. Check your network connection.`;
25026
+ }
25027
+ }
25028
+ function formatHoldDuration(ms) {
25029
+ const total = Math.max(0, Math.round(ms / 1000));
25030
+ const minutes = Math.floor(total / 60);
25031
+ const seconds = total % 60;
25032
+ return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
25033
+ }
25034
+ function buildRecoveryHoldMessage(reason, attempts, elapsedMs) {
25035
+ return `${reason} claudish retried ${attempts}\xD7 over ${formatHoldDuration(elapsedMs)} ` + "without reaching it \u2014 still trying, watch the recovery pane.";
25036
+ }
25037
+ var CODE_KIND, BUN_CONNECT_MESSAGE, NAME_KIND, OWN_TIMEOUT_FLAG = "claudishOwnTimeout";
25038
+ var init_connection_error = __esm(() => {
25039
+ CODE_KIND = {
25040
+ ENOTFOUND: "dns",
25041
+ EAI_AGAIN: "dns",
25042
+ ECONNREFUSED: "refused",
25043
+ ETIMEDOUT: "unreachable",
25044
+ ECONNRESET: "unreachable",
25045
+ ENETUNREACH: "unreachable",
25046
+ EHOSTUNREACH: "unreachable",
25047
+ EPIPE: "unreachable",
25048
+ UND_ERR_CONNECT_TIMEOUT: "unreachable",
25049
+ UND_ERR_SOCKET: "unreachable",
25050
+ ConnectionRefused: "refused",
25051
+ ConnectionClosed: "unreachable",
25052
+ FailedToOpenSocket: "unreachable",
25053
+ ERR_SOCKET_CLOSED: "unreachable"
25054
+ };
25055
+ BUN_CONNECT_MESSAGE = /unable to connect\. is the computer able to access the url\?/i;
25056
+ NAME_KIND = {
25057
+ TimeoutError: "unreachable"
25058
+ };
25059
+ });
25060
+
25061
+ // src/recovery/clock.ts
25062
+ function recoveryClock() {
25063
+ return active;
25064
+ }
25065
+ var REAL_CLOCK, active;
25066
+ var init_clock = __esm(() => {
25067
+ REAL_CLOCK = {
25068
+ now: () => performance.now(),
25069
+ setTimeout: (fn, ms) => setTimeout(fn, ms),
25070
+ clearTimeout: (handle) => clearTimeout(handle),
25071
+ unref: (handle) => {
25072
+ handle?.unref?.();
25073
+ }
25074
+ };
25075
+ active = REAL_CLOCK;
25076
+ });
25077
+
25078
+ // src/recovery/coordinator.ts
25079
+ import { randomUUID } from "crypto";
25080
+ function hostOf2(endpoint) {
25081
+ try {
25082
+ return new URL(endpoint).host || endpoint;
25083
+ } catch {
25084
+ return endpoint;
25085
+ }
25086
+ }
25087
+ function episodeCount() {
25088
+ return episodes.size;
25089
+ }
25090
+ function registerRecoveryUi(hooks) {
25091
+ uiHooks = hooks;
25092
+ }
25093
+ function uiLeaseValid(episodeId) {
25094
+ if (!uiHooks)
25095
+ return false;
25096
+ try {
25097
+ return uiHooks.leaseValid(episodeId);
25098
+ } catch {
25099
+ return false;
25100
+ }
25101
+ }
25102
+ function markPaneRequested(episodeId) {
25103
+ const ep = byId.get(episodeId);
25104
+ if (!ep || ep.paneRequestedAtPerf !== null)
25105
+ return;
25106
+ ep.paneRequestedAtPerf = recoveryClock().now();
25107
+ }
25108
+ function liveEpisodes() {
25109
+ const live = [...episodes.values()].filter((e) => e.state === "attempting" || e.state === "waiting" || e.state === "handoff");
25110
+ live.sort((a, b) => b.waiters.size - a.waiters.size || a.startedAtMs - b.startedAtMs);
25111
+ return live;
25112
+ }
25113
+ function renderableEpisodeFrame() {
25114
+ const live = liveEpisodes();
25115
+ const ep = live[0];
25116
+ if (!ep)
25117
+ return null;
25118
+ const clock = recoveryClock();
25119
+ const perfNow = clock.now();
25120
+ const epochNow = Date.now();
25121
+ const nextAttemptAtMs = ep.nextAttemptAtPerf === null ? null : epochNow + (ep.nextAttemptAtPerf - perfNow);
25122
+ return {
25123
+ type: "episode",
25124
+ episodeId: ep.episodeId,
25125
+ state: ep.state,
25126
+ tier: ep.maxTier,
25127
+ providerDisplayName: ep.providerDisplayName,
25128
+ host: ep.host,
25129
+ endpoint: ep.endpoint,
25130
+ kind: ep.kind,
25131
+ code: ep.code,
25132
+ loopback: ep.loopback,
25133
+ reason: ep.reason,
25134
+ attempts: ep.attempts,
25135
+ clientRetries: ep.clientRetries,
25136
+ startedAtMs: ep.startedAtMs,
25137
+ nextAttemptAtMs,
25138
+ lastOutcome: ep.lastOutcome,
25139
+ waiters: ep.waiters.size,
25140
+ otherEpisodes: live.length - 1
25141
+ };
25142
+ }
25143
+ function joinEpisode(seed) {
25144
+ const clock = recoveryClock();
25145
+ const host = hostOf2(seed.endpoint);
25146
+ const key = `${seed.providerName}|${host}`;
25147
+ let ep = episodes.get(key);
25148
+ if (ep && (ep.state === "recovered" || ep.state === "abandoned")) {
25149
+ episodes.delete(key);
25150
+ ep = undefined;
25151
+ }
25152
+ if (ep) {
25153
+ if (ep.state === "handoff") {
25154
+ if (ep.graceTimer !== null) {
25155
+ clock.clearTimeout(ep.graceTimer);
25156
+ ep.graceTimer = null;
25157
+ }
25158
+ ep.clientRetries++;
25159
+ ep.state = "attempting";
25160
+ ep.maxTier = 2;
25161
+ logRecovery(`[Recovery] ${ep.providerDisplayName} rejoined episode ${ep.episodeId} ` + `(client retry ${ep.clientRetries}, ladder ${ep.ladderIndex}, attempts ${ep.attempts})`);
25162
+ }
25163
+ } else {
25164
+ const nowPerf = clock.now();
25165
+ ep = {
25166
+ episodeId: randomUUID(),
25167
+ key,
25168
+ providerDisplayName: seed.providerDisplayName,
25169
+ endpoint: seed.endpoint,
25170
+ host,
25171
+ kind: seed.kind,
25172
+ loopback: isLoopback(seed.endpoint),
25173
+ code: seed.code,
25174
+ reason: seed.reason,
25175
+ state: "attempting",
25176
+ startedAtPerf: nowPerf,
25177
+ startedAtMs: Date.now(),
25178
+ paneRequestedAtPerf: null,
25179
+ ladderIndex: 0,
25180
+ attempts: 0,
25181
+ clientRetries: 0,
25182
+ round: 0,
25183
+ advancedForRound: 0,
25184
+ recoveredCount: 0,
25185
+ lastOutcome: seed.code ?? "unknown",
25186
+ maxTier: 1,
25187
+ waiters: new Set,
25188
+ attemptTimer: null,
25189
+ graceTimer: null,
25190
+ nextAttemptAtPerf: null,
25191
+ anyLeftByDeadline: false,
25192
+ gaveUp: false
25193
+ };
25194
+ episodes.set(key, ep);
25195
+ byId.set(ep.episodeId, ep);
25196
+ logRecovery(`[Recovery] episode ${ep.episodeId} opened for ${ep.providerDisplayName} at ${ep.endpoint} ` + `(${ep.kind}/${ep.code ?? "?"})`);
25197
+ if (uiHooks) {
25198
+ try {
25199
+ uiHooks.onEpisodeOpened(ep.episodeId);
25200
+ } catch (err) {
25201
+ log(`[Recovery] UI could not open for episode ${ep.episodeId}: ${String(err)}`);
25202
+ }
25203
+ }
25204
+ }
25205
+ const episode = ep;
25206
+ const waiter = {
25207
+ deadlineAt: seed.deadlineAt,
25208
+ wake: null,
25209
+ fail: null,
25210
+ detach: null
25211
+ };
25212
+ episode.waiters.add(waiter);
25213
+ let left = false;
25214
+ return {
25215
+ episodeId: episode.episodeId,
25216
+ key: episode.key,
25217
+ attempts: () => episode.attempts,
25218
+ recoveryMs: () => Math.round(recoveryClock().now() - episode.startedAtPerf),
25219
+ clientRetries: () => episode.clientRetries,
25220
+ ladderIndex: () => episode.ladderIndex,
25221
+ uiLeaseValid: () => uiLeaseValid(episode.episodeId),
25222
+ waitForNextAttempt(signal, delayMs) {
25223
+ if (episode.gaveUp || episode.state === "abandoned") {
25224
+ return Promise.resolve({ kind: "gave_up" });
25225
+ }
25226
+ if (signal.aborted)
25227
+ return Promise.reject(signal.reason ?? new Error("aborted"));
25228
+ return new Promise((resolve, reject) => {
25229
+ const onAbort = () => {
25230
+ waiter.wake = null;
25231
+ waiter.fail = null;
25232
+ waiter.detach = null;
25233
+ signal.removeEventListener("abort", onAbort);
25234
+ reject(signal.reason ?? new Error("aborted"));
25235
+ };
25236
+ waiter.detach = () => signal.removeEventListener("abort", onAbort);
25237
+ waiter.wake = (outcome) => {
25238
+ waiter.detach?.();
25239
+ waiter.detach = null;
25240
+ waiter.wake = null;
25241
+ waiter.fail = null;
25242
+ resolve(outcome);
25243
+ };
25244
+ waiter.fail = (err) => {
25245
+ waiter.detach?.();
25246
+ waiter.detach = null;
25247
+ waiter.wake = null;
25248
+ waiter.fail = null;
25249
+ reject(err);
25250
+ };
25251
+ signal.addEventListener("abort", onAbort, { once: true });
25252
+ armAttemptTimer(episode, delayMs);
25253
+ });
25254
+ },
25255
+ recordAttemptResult(outcome, ok) {
25256
+ episode.attempts++;
25257
+ episode.lastOutcome = outcome;
25258
+ if (ok) {
25259
+ episode.recoveredCount++;
25260
+ episode.ladderIndex = 0;
25261
+ episode.advancedForRound = episode.round;
25262
+ episode.state = "attempting";
25263
+ clearAttemptTimer(episode);
25264
+ for (const w of [...episode.waiters])
25265
+ w.wake?.({ kind: "attempt" });
25266
+ return;
25267
+ }
25268
+ if (episode.advancedForRound !== episode.round) {
25269
+ episode.advancedForRound = episode.round;
25270
+ episode.ladderIndex++;
25271
+ }
25272
+ logRecovery(`[Recovery] ${episode.providerDisplayName} attempt ${episode.attempts} failed: ${outcome} ` + `(episode ${episode.episodeId}, ladder ${episode.ladderIndex})`);
25273
+ },
25274
+ handoff() {
25275
+ episode.anyLeftByDeadline = true;
25276
+ },
25277
+ close(outcome) {
25278
+ closeEpisode(episode, outcome);
25279
+ },
25280
+ leave() {
25281
+ if (left)
25282
+ return;
25283
+ left = true;
25284
+ waiter.detach?.();
25285
+ waiter.detach = null;
25286
+ waiter.wake = null;
25287
+ waiter.fail = null;
25288
+ episode.waiters.delete(waiter);
25289
+ if (episode.waiters.size > 0)
25290
+ return;
25291
+ clearAttemptTimer(episode);
25292
+ if (episode.state === "recovered" || episode.state === "abandoned") {
25293
+ byId.delete(episode.episodeId);
25294
+ return;
25295
+ }
25296
+ if (episode.recoveredCount > 0 && !episode.anyLeftByDeadline) {
25297
+ closeEpisode(episode, "recovered");
25298
+ } else if (episode.anyLeftByDeadline) {
25299
+ episode.state = "handoff";
25300
+ const c = recoveryClock();
25301
+ const timer = c.setTimeout(() => {
25302
+ episode.graceTimer = null;
25303
+ closeEpisode(episode, "grace_expired");
25304
+ }, EPISODE_GRACE_MS);
25305
+ c.unref?.(timer);
25306
+ episode.graceTimer = timer;
25307
+ logRecovery(`[Recovery] episode ${episode.episodeId} handed off after ${Math.round(c.now() - episode.startedAtPerf)}ms, ${episode.attempts} attempts \u2014 holding ${EPISODE_GRACE_MS / 1000}s for a client retry`);
25308
+ } else {
25309
+ closeEpisode(episode, "client_gone");
25310
+ }
25311
+ }
25312
+ };
25313
+ }
25314
+ function armAttemptTimer(episode, delay) {
25315
+ if (episode.attemptTimer !== null)
25316
+ return;
25317
+ const clock = recoveryClock();
25318
+ episode.state = "waiting";
25319
+ episode.nextAttemptAtPerf = clock.now() + delay;
25320
+ episode.attemptTimer = clock.setTimeout(() => {
25321
+ episode.attemptTimer = null;
25322
+ episode.nextAttemptAtPerf = null;
25323
+ episode.state = "attempting";
25324
+ episode.round++;
25325
+ for (const w of [...episode.waiters])
25326
+ w.wake?.({ kind: "attempt" });
25327
+ }, delay);
25328
+ logRecovery(`[Recovery] ${episode.providerDisplayName} waiting ${delay / 1000}s before attempt ` + `${episode.attempts + 1} (episode ${episode.episodeId}, ${episode.waiters.size} waiting)`);
25329
+ }
25330
+ function clearAttemptTimer(episode) {
25331
+ if (episode.attemptTimer === null)
25332
+ return;
25333
+ recoveryClock().clearTimeout(episode.attemptTimer);
25334
+ episode.attemptTimer = null;
25335
+ episode.nextAttemptAtPerf = null;
25336
+ }
25337
+ function closeEpisode(episode, outcome) {
25338
+ if (episode.state === "recovered" || episode.state === "abandoned")
25339
+ return;
25340
+ const clock = recoveryClock();
25341
+ clearAttemptTimer(episode);
25342
+ if (episode.graceTimer !== null) {
25343
+ clock.clearTimeout(episode.graceTimer);
25344
+ episode.graceTimer = null;
25345
+ }
25346
+ episode.state = outcome === "recovered" ? "recovered" : "abandoned";
25347
+ if (episodes.get(episode.key) === episode)
25348
+ episodes.delete(episode.key);
25349
+ byId.delete(episode.episodeId);
25350
+ if (episode.state === "abandoned") {
25351
+ for (const w of [...episode.waiters])
25352
+ w.wake?.({ kind: "gave_up" });
25353
+ }
25354
+ logRecovery(`[Recovery] episode ${episode.episodeId} closed: ${outcome} after ` + `${Math.round(clock.now() - episode.startedAtPerf)}ms and ${episode.attempts} attempts`);
25355
+ if (uiHooks) {
25356
+ try {
25357
+ uiHooks.onEpisodeClosed(episode.episodeId, outcome, episode.attempts);
25358
+ } catch {}
25359
+ }
25360
+ }
25361
+ function noteTargetReachable(providerName, endpoint) {
25362
+ const exact = `${providerName}|${hostOf2(endpoint)}`;
25363
+ const prefix = `${providerName}|`;
25364
+ for (const [key, ep] of [...episodes]) {
25365
+ if (key !== exact && !key.startsWith(prefix))
25366
+ continue;
25367
+ if (ep.state !== "handoff")
25368
+ continue;
25369
+ logRecovery(`[Recovery] ${ep.providerDisplayName} answered on the client's own retry \u2014 ` + `closing episode ${ep.episodeId} instead of waiting out its grace`);
25370
+ closeEpisode(ep, "recovered");
25371
+ }
25372
+ }
25373
+ function episodeIsLive(episodeId) {
25374
+ const ep = byId.get(episodeId);
25375
+ if (!ep)
25376
+ return false;
25377
+ return ep.state === "attempting" || ep.state === "waiting" || ep.state === "handoff";
25378
+ }
25379
+ function closeAllEpisodes(outcome = "shutdown") {
25380
+ for (const ep of [...episodes.values()])
25381
+ closeEpisode(ep, outcome);
25382
+ for (const ep of [...byId.values()])
25383
+ closeEpisode(ep, outcome);
25384
+ episodes.clear();
25385
+ byId.clear();
25386
+ }
25387
+ var EPISODE_GRACE_MS = 120000, episodes, byId, uiHooks = null;
25388
+ var init_coordinator = __esm(() => {
25389
+ init_connection_error();
25390
+ init_logger();
25391
+ init_clock();
25392
+ episodes = new Map;
25393
+ byId = new Map;
25394
+ });
25395
+
25396
+ // src/config.ts
25397
+ var DEFAULT_PORT_RANGE, ENV;
25398
+ var init_config = __esm(() => {
25399
+ DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
25400
+ ENV = {
25401
+ OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
25402
+ CLAUDISH_MODEL: "CLAUDISH_MODEL",
25403
+ CLAUDISH_PORT: "CLAUDISH_PORT",
25404
+ CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME",
25405
+ CLAUDISH_TOKEN_FILE: "CLAUDISH_TOKEN_FILE",
25406
+ CLAUDISH_PROVIDER_NAME: "CLAUDISH_PROVIDER_NAME",
25407
+ ANTHROPIC_MODEL: "ANTHROPIC_MODEL",
25408
+ ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL",
25409
+ CLAUDISH_MODEL_OPUS: "CLAUDISH_MODEL_OPUS",
25410
+ CLAUDISH_MODEL_SONNET: "CLAUDISH_MODEL_SONNET",
25411
+ CLAUDISH_MODEL_HAIKU: "CLAUDISH_MODEL_HAIKU",
25412
+ CLAUDISH_MODEL_SUBAGENT: "CLAUDISH_MODEL_SUBAGENT",
25413
+ ANTHROPIC_DEFAULT_OPUS_MODEL: "ANTHROPIC_DEFAULT_OPUS_MODEL",
25414
+ ANTHROPIC_DEFAULT_SONNET_MODEL: "ANTHROPIC_DEFAULT_SONNET_MODEL",
25415
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
25416
+ CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
25417
+ CLAUDE_CODE_AUTO_COMPACT_WINDOW: "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
25418
+ CLAUDE_CODE_MAX_CONTEXT_TOKENS: "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
25419
+ OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
25420
+ OLLAMA_HOST: "OLLAMA_HOST",
25421
+ LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
25422
+ VLLM_BASE_URL: "VLLM_BASE_URL",
25423
+ GEMINI_API_KEY: "GEMINI_API_KEY",
25424
+ GEMINI_BASE_URL: "GEMINI_BASE_URL",
25425
+ OPENAI_API_KEY: "OPENAI_API_KEY",
25426
+ OPENAI_BASE_URL: "OPENAI_BASE_URL",
25427
+ CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS",
25428
+ CLAUDISH_DIAG_MODE: "CLAUDISH_DIAG_MODE",
25429
+ CLAUDISH_DEBUG: "CLAUDISH_DEBUG",
25430
+ CLAUDISH_ANTHROPIC_API_BILLING: "CLAUDISH_ANTHROPIC_API_BILLING",
25431
+ CLAUDISH_CLASSIFIER_PROVIDER: "CLAUDISH_CLASSIFIER_PROVIDER",
25432
+ CLAUDISH_CLASSIFIER_MODEL: "CLAUDISH_CLASSIFIER_MODEL",
25433
+ CLAUDISH_RECOVERY: "CLAUDISH_RECOVERY",
25434
+ CLAUDISH_RECOVERY_UI: "CLAUDISH_RECOVERY_UI",
25435
+ API_TIMEOUT_MS: "API_TIMEOUT_MS"
25436
+ };
25437
+ });
25438
+
25439
+ // src/recovery/settings.ts
25440
+ function setRecoveryFlagOverrides(overrides) {
25441
+ flagOverrides = { ...flagOverrides, ...overrides };
25442
+ }
25443
+ function parseEnvBool(raw) {
25444
+ const v = raw?.trim().toLowerCase();
25445
+ if (v === "1" || v === "true")
25446
+ return true;
25447
+ if (v === "0" || v === "false")
25448
+ return false;
25449
+ return;
25450
+ }
25451
+ function resolveRecoveryEnabled() {
25452
+ if (flagOverrides.recovery !== undefined)
25453
+ return flagOverrides.recovery;
25454
+ const env = parseEnvBool(process.env[ENV.CLAUDISH_RECOVERY]);
25455
+ if (env !== undefined)
25456
+ return env;
25457
+ const scoped = readRecoveryEnabled();
25458
+ if (scoped !== undefined)
25459
+ return scoped;
25460
+ return true;
25461
+ }
25462
+ function resolveRecoveryUi() {
25463
+ if (flagOverrides.recoveryUi !== undefined)
25464
+ return flagOverrides.recoveryUi;
25465
+ const env = parseEnvBool(process.env[ENV.CLAUDISH_RECOVERY_UI]);
25466
+ if (env !== undefined)
25467
+ return env;
25468
+ const scoped = readRecoveryUi();
25469
+ if (scoped !== undefined)
25470
+ return scoped;
25471
+ return true;
25472
+ }
25473
+ function recoverySurfaceAllowed() {
25474
+ return resolveRecoveryEnabled() && resolveRecoveryUi();
25475
+ }
25476
+ function resolveTier1DeadlineMs(env = process.env) {
25477
+ const raw = env[ENV.API_TIMEOUT_MS];
25478
+ const parsed = raw === undefined ? Number.NaN : Number(raw);
25479
+ const clientCeiling = Number.isFinite(parsed) && parsed > 0 ? Math.min(parsed, CLIENT_CEILING_CLAMP_MS) : CLIENT_CEILING_CLAMP_MS;
25480
+ const derived = clientCeiling - DEADLINE_MARGIN_MS;
25481
+ return Math.max(TIER1_DEADLINE_FLOOR_MS, derived);
25482
+ }
25483
+ function logDeadlineIfShortened(deadlineMs) {
25484
+ if (deadlineNoticeLogged)
25485
+ return;
25486
+ if (deadlineMs >= DEFAULT_TIER1_DEADLINE_MS)
25487
+ return;
25488
+ deadlineNoticeLogged = true;
25489
+ const raw = process.env[ENV.API_TIMEOUT_MS];
25490
+ log(`[Recovery] tier-1 hold shortened to ${Math.round(deadlineMs / 1000)}s ` + `(API_TIMEOUT_MS=${raw ?? "unset"}; default would be ${Math.round(DEFAULT_TIER1_DEADLINE_MS / 1000)}s)`);
25491
+ }
25492
+ function retryWatchdogEnv(opts) {
25493
+ if (!opts.paneEligible)
25494
+ return {};
25495
+ return recoverySurfaceAllowed() ? { CLAUDE_CODE_RETRY_WATCHDOG: "1" } : {};
25496
+ }
25497
+ function applyRetryWatchdog(env, opts) {
25498
+ const grant = retryWatchdogEnv(opts);
25499
+ if (grant.CLAUDE_CODE_RETRY_WATCHDOG) {
25500
+ const usersOwn = env.CLAUDE_CODE_RETRY_WATCHDOG !== undefined && env[RETRY_WATCHDOG_OWNER_ENV] !== "1";
25501
+ Object.assign(env, grant);
25502
+ if (!usersOwn)
25503
+ env[RETRY_WATCHDOG_OWNER_ENV] = "1";
25504
+ return;
25505
+ }
25506
+ if (env[RETRY_WATCHDOG_OWNER_ENV] === "1") {
25507
+ delete env.CLAUDE_CODE_RETRY_WATCHDOG;
25508
+ delete env[RETRY_WATCHDOG_OWNER_ENV];
25509
+ }
25510
+ }
25511
+ var flagOverrides, CLIENT_CEILING_CLAMP_MS = 300000, DEADLINE_MARGIN_MS = 30000, TIER1_DEADLINE_FLOOR_MS = 15000, DEFAULT_TIER1_DEADLINE_MS, deadlineNoticeLogged = false, RETRY_WATCHDOG_OWNER_ENV = "CLAUDISH_SET_RETRY_WATCHDOG";
25512
+ var init_settings = __esm(() => {
25513
+ init_config();
25514
+ init_logger();
25515
+ init_profile_config();
25516
+ flagOverrides = {};
25517
+ DEFAULT_TIER1_DEADLINE_MS = CLIENT_CEILING_CLAMP_MS - DEADLINE_MARGIN_MS;
25518
+ });
25519
+
25520
+ // src/handlers/shared/transient-retry.ts
25521
+ function connectionRetryDelayMs(ladderIndex) {
25522
+ const i = Math.max(0, Math.min(ladderIndex, CONNECTION_RETRY_DELAYS_MS.length - 1));
25523
+ return CONNECTION_RETRY_DELAYS_MS[i];
25524
+ }
25525
+ function refreshDeadlineAt(deadlineAt) {
25526
+ const reserve = Math.min(PER_ATTEMPT_CONNECT_CAP_MS, resolveTier1DeadlineMs() / 2);
25527
+ return deadlineAt - reserve;
25528
+ }
25529
+ function inboundStartedAtPerf(c) {
25530
+ const key = c.req?.raw;
25531
+ if (!key)
25532
+ return recoveryClock().now();
25533
+ const seen = INBOUND_START.get(key);
25534
+ if (seen !== undefined)
25535
+ return seen;
25536
+ const t = recoveryClock().now();
25537
+ INBOUND_START.set(key, t);
25538
+ return t;
25539
+ }
25540
+ function tier1DeadlineAt(c) {
25541
+ return inboundStartedAtPerf(c) + resolveTier1DeadlineMs();
25542
+ }
25543
+ function noteDeadlineForEpisode() {
25544
+ logDeadlineIfShortened(resolveTier1DeadlineMs());
25545
+ }
25546
+ function deadlineClamp(deadlineAt) {
25547
+ const clock = recoveryClock();
25548
+ const remaining = deadlineAt - clock.now();
25549
+ if (!(remaining > 0))
25550
+ return { disarm: () => {} };
25551
+ const ac = new AbortController;
25552
+ const timer = clock.setTimeout(() => ac.abort(markOwnTimeout(new DOMException("request deadline", "TimeoutError"))), remaining);
25553
+ return {
25554
+ signal: ac.signal,
25555
+ disarm: () => clock.clearTimeout(timer)
25556
+ };
25557
+ }
25558
+ function mergeSignalIntoInit(init, signal) {
25559
+ if (!signal)
25560
+ return init;
25561
+ const own = init.signal;
25562
+ if (!own || own.aborted)
25563
+ return { ...init, signal };
25564
+ return { ...init, signal: AbortSignal.any([own, signal]) };
25565
+ }
25566
+ function isClientAbort(signal, err) {
25567
+ if (signal.aborted)
25568
+ return true;
25569
+ return err?.name === "AbortError";
25570
+ }
25571
+ function untilAborted(p, signal) {
25572
+ if (signal.aborted) {
25573
+ p.catch(() => {});
25574
+ return Promise.reject(signal.reason ?? new DOMException("aborted", "AbortError"));
25575
+ }
25576
+ return new Promise((resolve, reject) => {
25577
+ const onAbort = () => {
25578
+ reject(signal.reason ?? new DOMException("aborted", "AbortError"));
25579
+ };
25580
+ signal.addEventListener("abort", onAbort, { once: true });
25581
+ p.then((v) => {
25582
+ signal.removeEventListener("abort", onAbort);
25583
+ resolve(v);
25584
+ }, (e) => {
25585
+ signal.removeEventListener("abort", onAbort);
25586
+ reject(e);
25587
+ });
25588
+ });
25589
+ }
25590
+ async function withConnectionRetry(op, first, ctx) {
25591
+ const clock = recoveryClock();
25592
+ const enteredAtPerf = clock.now();
25593
+ let requestRetries = 0;
25594
+ const firstConn = classifyConnectionError(first);
25595
+ if (!firstConn) {
25596
+ return {
25597
+ kind: "threw",
25598
+ error: first,
25599
+ attempts: 0,
25600
+ recoveryMs: 0,
25601
+ requestRetries: 0,
25602
+ requestRecoveryMs: 0,
25603
+ clientRetry: 0,
25604
+ episodeId: ""
25605
+ };
25606
+ }
25607
+ let conn = firstConn;
25608
+ let error = first;
25609
+ let endpoint = ctx.resolveEndpoint(first);
25610
+ noteDeadlineForEpisode();
25611
+ const handle = joinEpisode({
25612
+ providerName: ctx.providerName,
25613
+ providerDisplayName: ctx.providerDisplayName,
25614
+ endpoint,
25615
+ kind: conn.kind,
25616
+ code: conn.code,
25617
+ reason: buildConnectionErrorMessage(conn.kind, ctx.providerDisplayName, endpoint),
25618
+ deadlineAt: ctx.deadlineAt
25619
+ });
25620
+ const tally = () => ({
25621
+ attempts: handle.attempts(),
25622
+ recoveryMs: handle.recoveryMs(),
25623
+ requestRetries,
25624
+ requestRecoveryMs: Math.round(clock.now() - enteredAtPerf),
25625
+ clientRetry: handle.clientRetries(),
25626
+ episodeId: handle.episodeId
25627
+ });
25628
+ try {
25629
+ handle.recordAttemptResult(conn.code, false);
25630
+ for (;; ) {
25631
+ if (ctx.signal.aborted) {
25632
+ return { kind: "client_gone", ...tally() };
25633
+ }
25634
+ const waitBudget = ctx.deadlineAt - clock.now() - MIN_ATTEMPT_SLOT_MS;
25635
+ let delay = connectionRetryDelayMs(handle.ladderIndex());
25636
+ if (delay > waitBudget) {
25637
+ const mayTruncate = requestRetries === 0 && waitBudget >= MIN_ATTEMPT_SLOT_MS;
25638
+ if (!mayTruncate) {
25639
+ handle.handoff();
25640
+ return { kind: "exhausted", conn, error, endpoint, ...tally() };
25641
+ }
25642
+ delay = waitBudget;
25643
+ }
25644
+ let waited;
25645
+ try {
25646
+ waited = await handle.waitForNextAttempt(ctx.signal, delay);
25647
+ } catch (err) {
25648
+ if (isClientAbort(ctx.signal, err)) {
25649
+ log(`[Recovery] client gone during wait (episode ${handle.episodeId})`);
25650
+ return { kind: "client_gone", ...tally() };
25651
+ }
25652
+ throw err;
25653
+ }
25654
+ if (waited.kind === "gave_up") {
25655
+ return { kind: "gave_up", conn, error, endpoint, ...tally() };
25656
+ }
25657
+ const attemptAc = new AbortController;
25658
+ const remaining = ctx.deadlineAt - clock.now();
25659
+ const clampMs = Math.max(MIN_ATTEMPT_SLOT_MS, Math.min(PER_ATTEMPT_CONNECT_CAP_MS, remaining));
25660
+ const clampTimer = clock.setTimeout(() => attemptAc.abort(markOwnTimeout(new DOMException("attempt cap", "TimeoutError"))), clampMs);
25661
+ const merged = AbortSignal.any([ctx.signal, attemptAc.signal]);
25662
+ try {
25663
+ requestRetries++;
25664
+ const value = await untilAborted(op(merged), merged);
25665
+ handle.recordAttemptResult("ok", true);
25666
+ logRecovery(`[Recovery] ${ctx.providerDisplayName} recovered after ${handle.attempts()} attempts ` + `in ${handle.recoveryMs()}ms (episode ${handle.episodeId})`);
25667
+ return { kind: "ok", value, ...tally() };
25668
+ } catch (err) {
25669
+ if (isClientAbort(ctx.signal, err)) {
25670
+ log(`[Recovery] client gone during attempt (episode ${handle.episodeId})`);
25671
+ return { kind: "client_gone", ...tally() };
25672
+ }
25673
+ const next = classifyConnectionError(err);
25674
+ if (!next) {
25675
+ return { kind: "threw", error: err, ...tally() };
25676
+ }
25677
+ conn = next;
25678
+ error = err;
25679
+ endpoint = ctx.resolveEndpoint(err) || endpoint;
25680
+ handle.recordAttemptResult(next.code, false);
25681
+ } finally {
25682
+ clock.clearTimeout(clampTimer);
25683
+ }
25684
+ }
25685
+ } finally {
25686
+ handle.leave();
25687
+ }
25688
+ }
25689
+ var CONNECTION_RETRY_DELAYS_MS, PER_ATTEMPT_CONNECT_CAP_MS = 45000, MIN_ATTEMPT_SLOT_MS = 3000, TOKEN_REFRESH_TIMEOUT_MS = 20000, INBOUND_START;
25690
+ var init_transient_retry = __esm(() => {
25691
+ init_logger();
25692
+ init_clock();
25693
+ init_coordinator();
25694
+ init_settings();
25695
+ init_connection_error();
25696
+ init_coordinator();
25697
+ CONNECTION_RETRY_DELAYS_MS = [5000, 1e4, 30000, 60000];
25698
+ INBOUND_START = new WeakMap;
25699
+ });
25700
+
24929
25701
  // src/providers/grok/grok-credentials.ts
24930
25702
  import { existsSync as existsSync11, readFileSync as readFileSync10, renameSync, writeFileSync as writeFileSync7 } from "fs";
24931
25703
  import { homedir as homedir13 } from "os";
@@ -25070,10 +25842,11 @@ async function performRefresh(cred) {
25070
25842
  response = await fetch(tokenEndpoint, {
25071
25843
  method: "POST",
25072
25844
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
25073
- body
25845
+ body,
25846
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
25074
25847
  });
25075
25848
  } catch (error) {
25076
- throw new Error(`Could not reach ${tokenEndpoint} to refresh the Grok token: ${error.message}`);
25849
+ throw Object.assign(new Error(`Could not reach ${tokenEndpoint} to refresh the Grok token: ${error.message}`, { cause: markOwnTimeout(error) }), { claudishEndpoint: tokenEndpoint });
25077
25850
  }
25078
25851
  const text = await response.text();
25079
25852
  if (!response.ok) {
@@ -25137,6 +25910,8 @@ function refreshShared(cred) {
25137
25910
  var GROK_PROXY_URL_ENV = "GROK_PROXY_URL", DEFAULT_GROK_PROXY_URL = "https://cli-chat-proxy.grok.com/v1", GROK_CLIENT_IDENTIFIER = "grok-shell", FALLBACK_GROK_CLIENT_VERSION = "1.0.4", LEGACY_SCOPE = "https://accounts.x.ai/sign-in", EXPIRY_SKEW_MS2, grokHomeOverride = null, claudishOAuthPathOverride = null, GROK_CHANNEL_URL = "https://x.ai/cli/stable", liveClientVersion = null, SIGN_IN_HINT, refreshInFlight = null;
25138
25911
  var init_grok_credentials = __esm(() => {
25139
25912
  init_grok_oauth();
25913
+ init_connection_error();
25914
+ init_transient_retry();
25140
25915
  EXPIRY_SKEW_MS2 = 5 * 60 * 1000;
25141
25916
  SIGN_IN_HINT = "Run `claudish login grok` (no Grok CLI required \u2014 SuperGrok or X Premium+ subscription). " + "An existing `grok login` (~/.grok/auth.json) is also picked up automatically.";
25142
25917
  });
@@ -25461,7 +26236,7 @@ function globMatches(pattern, value) {
25461
26236
  return new RegExp(`^${escaped}$`).test(value);
25462
26237
  }
25463
26238
  var SeveritySchema, BehaviorConfigSchema;
25464
- var init_config = __esm(() => {
26239
+ var init_config2 = __esm(() => {
25465
26240
  init_zod();
25466
26241
  init_logger();
25467
26242
  SeveritySchema = _enum(["off", "warn", "fix"]);
@@ -26482,7 +27257,7 @@ class BehaviorEngine {
26482
27257
  var MAX_OBSERVED_CHARS, MAX_OBSERVED_TOOLS = 200, MAX_TRACKED_CONVERSATIONS = 64;
26483
27258
  var init_engine = __esm(() => {
26484
27259
  init_logger();
26485
- init_config();
27260
+ init_config2();
26486
27261
  init_harness();
26487
27262
  init_journal();
26488
27263
  init_aggregate();
@@ -26790,12 +27565,12 @@ function registerHookRules(rules) {
26790
27565
  var BUILTIN_RULES, sharedEngine = null, hookRules;
26791
27566
  var init_behavior = __esm(() => {
26792
27567
  init_profile_config();
26793
- init_config();
27568
+ init_config2();
26794
27569
  init_engine();
26795
27570
  init_plan_mode();
26796
27571
  init_session_context();
26797
27572
  init_engine();
26798
- init_config();
27573
+ init_config2();
26799
27574
  init_harness();
26800
27575
  init_hooks();
26801
27576
  init_digest();
@@ -27303,7 +28078,7 @@ var init_gemini_queue = __esm(() => {
27303
28078
  });
27304
28079
 
27305
28080
  // src/providers/transport/antigravity.ts
27306
- import { randomUUID } from "crypto";
28081
+ import { randomUUID as randomUUID2 } from "crypto";
27307
28082
  function antigravityEndpoint() {
27308
28083
  return `${antigravityHost()}/v1internal:streamGenerateContent?alt=sse`;
27309
28084
  }
@@ -27481,7 +28256,7 @@ class AntigravityProviderTransport {
27481
28256
  const envelope = {
27482
28257
  model,
27483
28258
  project: this.projectId,
27484
- user_prompt_id: randomUUID(),
28259
+ user_prompt_id: randomUUID2(),
27485
28260
  request: innerPayload
27486
28261
  };
27487
28262
  if (this.tierId && this.tierId !== "free-tier") {
@@ -27735,7 +28510,7 @@ function redactSecrets(text) {
27735
28510
  }
27736
28511
 
27737
28512
  // src/auth/credentials/antigravity-credential.ts
27738
- import { randomUUID as randomUUID2 } from "crypto";
28513
+ import { randomUUID as randomUUID3 } from "crypto";
27739
28514
  function createActivityRequestId2() {
27740
28515
  return Math.random().toString(36).substring(7);
27741
28516
  }
@@ -27770,7 +28545,7 @@ class AntigravityCredentialProvider {
27770
28545
  const env = {
27771
28546
  model: servedModel,
27772
28547
  project: projectId,
27773
- user_prompt_id: randomUUID2(),
28548
+ user_prompt_id: randomUUID3(),
27774
28549
  request: inner
27775
28550
  };
27776
28551
  if (tierId && tierId !== "free-tier") {
@@ -28407,8 +29182,9 @@ class CodexOAuth {
28407
29182
  throw new Error("No OpenAI Codex OAuth credentials found. Please run `claudish login codex` first.");
28408
29183
  }
28409
29184
  log("[CodexOAuth] Refreshing access token");
29185
+ let response;
28410
29186
  try {
28411
- const response = await fetch(OAUTH_CONFIG.tokenUrl, {
29187
+ response = await fetch(OAUTH_CONFIG.tokenUrl, {
28412
29188
  method: "POST",
28413
29189
  headers: {
28414
29190
  "Content-Type": "application/json"
@@ -28417,8 +29193,14 @@ class CodexOAuth {
28417
29193
  grant_type: "refresh_token",
28418
29194
  refresh_token: this.credentials.refresh_token,
28419
29195
  client_id: OAUTH_CONFIG.clientId
28420
- })
29196
+ }),
29197
+ signal: AbortSignal.timeout(TOKEN_REFRESH_TIMEOUT_MS)
28421
29198
  });
29199
+ } catch (e) {
29200
+ log(`[CodexOAuth] Could not reach ${OAUTH_CONFIG.tokenUrl}: ${e.message}`);
29201
+ throw Object.assign(new Error(`Could not reach ${OAUTH_CONFIG.tokenUrl} to refresh the Codex token: ${e.message}`, { cause: markOwnTimeout(e) }), { claudishEndpoint: OAUTH_CONFIG.tokenUrl });
29202
+ }
29203
+ try {
28422
29204
  if (!response.ok) {
28423
29205
  const errorText = await response.text();
28424
29206
  throw new Error(`Token refresh failed: ${response.status} - ${errorText}`);
@@ -28439,7 +29221,7 @@ class CodexOAuth {
28439
29221
  log(`[CodexOAuth] Refresh failed: ${e.message}`);
28440
29222
  throw new Error(`OAuth credentials invalid. Please run \`claudish login codex\` again.
28441
29223
 
28442
- Details: ${e.message}`);
29224
+ Details: ${e.message}`, { cause: e });
28443
29225
  }
28444
29226
  }
28445
29227
  loadCredentials() {
@@ -28666,6 +29448,8 @@ function getCodexOAuth() {
28666
29448
  }
28667
29449
  var execAsync3, OAUTH_CONFIG;
28668
29450
  var init_codex_oauth = __esm(() => {
29451
+ init_connection_error();
29452
+ init_transient_retry();
28669
29453
  init_logger();
28670
29454
  execAsync3 = promisify3(exec3);
28671
29455
  OAUTH_CONFIG = {
@@ -29804,14 +30588,14 @@ var init_proto_codec = __esm(() => {
29804
30588
  });
29805
30589
 
29806
30590
  // src/providers/devin/devin-request.ts
29807
- import { randomUUID as randomUUID3 } from "crypto";
30591
+ import { randomUUID as randomUUID4 } from "crypto";
29808
30592
  function encodeChatMetadata(meta) {
29809
30593
  const version = meta.clientVersion ?? DEVIN_CLI_VERSION;
29810
30594
  return msg(bytes(1, "devin-cli"), bytes(2, version), bytes(3, meta.apiKey), bytes(4, "en"), bytes(5, meta.platform ?? process.platform), bytes(7, version), bytes(12, "chisel"), bytes(28, "chisel"));
29811
30595
  }
29812
30596
  function encodeMessage(message) {
29813
30597
  const parts = [
29814
- bytes(1, message.id ?? randomUUID3()),
30598
+ bytes(1, message.id ?? randomUUID4()),
29815
30599
  vint(2, DEVIN_ROLE[message.role])
29816
30600
  ];
29817
30601
  if (message.text)
@@ -31266,11 +32050,11 @@ class OpenAIProviderTransport {
31266
32050
  } catch (fetchError) {
31267
32051
  if (fetchError.name === "AbortError") {
31268
32052
  log(`[${this.displayName}] Request timed out after 30s`);
31269
- throw new OpenAITimeoutError(this.provider.baseUrl);
32053
+ throw new OpenAITimeoutError(this.provider.baseUrl, fetchError);
31270
32054
  }
31271
32055
  if (fetchError.cause?.code === "UND_ERR_CONNECT_TIMEOUT") {
31272
32056
  log(`[${this.displayName}] Connection timeout: ${fetchError.message}`);
31273
- throw new OpenAIConnectionError(this.provider.baseUrl, fetchError.cause?.code);
32057
+ throw new OpenAIConnectionError(this.provider.baseUrl, fetchError.cause?.code, fetchError);
31274
32058
  }
31275
32059
  throw fetchError;
31276
32060
  }
@@ -31305,15 +32089,20 @@ var init_openai = __esm(() => {
31305
32089
  init_logger();
31306
32090
  init_provider_model_discovery();
31307
32091
  OpenAITimeoutError = class OpenAITimeoutError extends Error {
31308
- constructor(baseUrl) {
31309
- super(`Request to OpenAI API timed out. Check your network connection to ${baseUrl}`);
32092
+ code = "ETIMEDOUT";
32093
+ constructor(baseUrl, cause) {
32094
+ super(`Request to OpenAI API timed out. Check your network connection to ${baseUrl}`, {
32095
+ cause
32096
+ });
31310
32097
  this.name = "OpenAITimeoutError";
31311
32098
  }
31312
32099
  };
31313
32100
  OpenAIConnectionError = class OpenAIConnectionError extends Error {
31314
- constructor(baseUrl, code) {
31315
- super(`Cannot connect to OpenAI API (${baseUrl}). This may be due to: network/firewall blocking, VPN interference, or regional restrictions. Error: ${code}`);
32101
+ code;
32102
+ constructor(baseUrl, code, cause) {
32103
+ super(`Cannot connect to OpenAI API (${baseUrl}). This may be due to: network/firewall blocking, VPN interference, or regional restrictions. Error: ${code}`, { cause });
31316
32104
  this.name = "OpenAIConnectionError";
32105
+ this.code = code;
31317
32106
  }
31318
32107
  };
31319
32108
  });
@@ -32188,6 +32977,21 @@ function eventToLogRecord(event) {
32188
32977
  if (event.fallback_attempts !== undefined) {
32189
32978
  attributes.push(intAttr("llm.fallback_attempts", event.fallback_attempts));
32190
32979
  }
32980
+ if (event.retry_attempts !== undefined) {
32981
+ attributes.push(intAttr("llm.retry_attempts", event.retry_attempts));
32982
+ }
32983
+ if (event.recovery_ms !== undefined) {
32984
+ attributes.push(intAttr("llm.recovery_ms", event.recovery_ms));
32985
+ }
32986
+ if (event.recovery_episode_id !== undefined) {
32987
+ attributes.push(stringAttr("llm.recovery_episode_id", event.recovery_episode_id));
32988
+ }
32989
+ if (event.recovery_client_retry !== undefined) {
32990
+ attributes.push(intAttr("llm.recovery_client_retry", event.recovery_client_retry));
32991
+ }
32992
+ if (event.recovery_outcome !== undefined) {
32993
+ attributes.push(stringAttr("llm.recovery_outcome", event.recovery_outcome));
32994
+ }
32191
32995
  return {
32192
32996
  timeUnixNano,
32193
32997
  severityNumber: 9,
@@ -32726,6 +33530,16 @@ function recordStats(partial) {
32726
33530
  event.fallback_chain = partial.fallback_chain;
32727
33531
  if (partial.fallback_attempts !== undefined)
32728
33532
  event.fallback_attempts = partial.fallback_attempts;
33533
+ if (partial.retry_attempts !== undefined)
33534
+ event.retry_attempts = partial.retry_attempts;
33535
+ if (partial.recovery_ms !== undefined)
33536
+ event.recovery_ms = partial.recovery_ms;
33537
+ if (partial.recovery_episode_id !== undefined)
33538
+ event.recovery_episode_id = partial.recovery_episode_id;
33539
+ if (partial.recovery_client_retry !== undefined)
33540
+ event.recovery_client_retry = partial.recovery_client_retry;
33541
+ if (partial.recovery_outcome !== undefined)
33542
+ event.recovery_outcome = partial.recovery_outcome;
32729
33543
  appendEvent(event);
32730
33544
  checkAndFlush();
32731
33545
  } catch {}
@@ -33291,79 +34105,6 @@ var init_collect_sse_message = __esm(() => {
33291
34105
  init_logger();
33292
34106
  });
33293
34107
 
33294
- // src/handlers/shared/connection-error.ts
33295
- function findConnectionCode(error) {
33296
- let e = error;
33297
- const seen = new Set;
33298
- for (let depth = 0;e && typeof e === "object" && depth < 8 && !seen.has(e); depth++) {
33299
- seen.add(e);
33300
- if (typeof e.code === "string" && e.code in CODE_KIND)
33301
- return e.code;
33302
- e = e.cause;
33303
- }
33304
- const msg = String(error?.message ?? error ?? "");
33305
- if (/getaddrinfo|ENOTFOUND|EAI_AGAIN|nodename nor servname/i.test(msg))
33306
- return "ENOTFOUND";
33307
- if (BUN_CONNECT_MESSAGE.test(msg))
33308
- return "ConnectionRefused";
33309
- return null;
33310
- }
33311
- function isLoopback(endpoint) {
33312
- try {
33313
- const { hostname } = new URL(endpoint);
33314
- return /^(localhost|127\.\d+\.\d+\.\d+|0\.0\.0\.0|\[?::1\]?)$/i.test(hostname);
33315
- } catch {
33316
- return false;
33317
- }
33318
- }
33319
- function classifyConnectionError(error) {
33320
- const code = findConnectionCode(error);
33321
- if (!code)
33322
- return null;
33323
- return { kind: CODE_KIND[code] ?? "unreachable", code };
33324
- }
33325
- function hostOf(endpoint) {
33326
- try {
33327
- return new URL(endpoint).host || endpoint;
33328
- } catch {
33329
- return endpoint;
33330
- }
33331
- }
33332
- function buildConnectionErrorMessage(kind, displayName, endpoint) {
33333
- const host = hostOf(endpoint);
33334
- switch (kind) {
33335
- case "dns":
33336
- return `Cannot resolve ${host} for ${displayName}. This is a DNS/network problem on your machine \u2014 check your internet connection, VPN, or DNS resolver (e.g. Tailscale MagicDNS) \u2014 not ${displayName}.`;
33337
- case "refused":
33338
- if (isLoopback(endpoint)) {
33339
- return `Cannot connect to ${displayName} at ${endpoint}. Make sure the server is running.`;
33340
- }
33341
- return `Cannot reach ${host} for ${displayName}. This is a network problem on your machine \u2014 check your internet connection, VPN, or DNS resolver (e.g. Tailscale MagicDNS) \u2014 not ${displayName}.`;
33342
- case "unreachable":
33343
- return `Cannot reach ${displayName} at ${endpoint}. Check your network connection.`;
33344
- }
33345
- }
33346
- var CODE_KIND, BUN_CONNECT_MESSAGE;
33347
- var init_connection_error = __esm(() => {
33348
- CODE_KIND = {
33349
- ENOTFOUND: "dns",
33350
- EAI_AGAIN: "dns",
33351
- ECONNREFUSED: "refused",
33352
- ETIMEDOUT: "unreachable",
33353
- ECONNRESET: "unreachable",
33354
- ENETUNREACH: "unreachable",
33355
- EHOSTUNREACH: "unreachable",
33356
- EPIPE: "unreachable",
33357
- UND_ERR_CONNECT_TIMEOUT: "unreachable",
33358
- UND_ERR_SOCKET: "unreachable",
33359
- ConnectionRefused: "refused",
33360
- ConnectionClosed: "unreachable",
33361
- FailedToOpenSocket: "unreachable",
33362
- ERR_SOCKET_CLOSED: "unreachable"
33363
- };
33364
- BUN_CONNECT_MESSAGE = /unable to connect\. is the computer able to access the url\?/i;
33365
- });
33366
-
33367
34108
  // src/handlers/shared/devin-stream-head-sniffer.ts
33368
34109
  function classifyDevinStreamError(code, message) {
33369
34110
  const lowerCode = code.toLowerCase();
@@ -33513,6 +34254,39 @@ var init_model_unsupported = __esm(() => {
33513
34254
  PARAMETER_IS_UNSUPPORTED = /\b(thinking[ _-]?(level|budget)|reasoning[ _-]?effort|effort|temperature|top[_ ]?[pk]|tool[_ ]?choice|tools?|function[ _-]?calling|parameter|argument|field|image[s]?|audio|video|streaming|stream|json[ _-]?mode|response[ _-]?format|system[ _-]?(prompt|instruction)s?)\b[^.]{0,60}?\b(is|are|was|were)?\s*not supported\b/;
33514
34255
  });
33515
34256
 
34257
+ // src/handlers/shared/recovery-marker.ts
34258
+ function recoveryHoldHeaders() {
34259
+ return {
34260
+ "content-type": "application/json",
34261
+ "x-should-retry": "true",
34262
+ [RECOVERY_MARKER_HEADER]: RECOVERY_MARKER_VALUE
34263
+ };
34264
+ }
34265
+ function connectionFaultHeaders() {
34266
+ return { [CONNECTION_FAULT_HEADER]: RECOVERY_MARKER_VALUE };
34267
+ }
34268
+ function hasRecoveryMarker(headers) {
34269
+ try {
34270
+ return headers?.get(RECOVERY_MARKER_HEADER) === RECOVERY_MARKER_VALUE;
34271
+ } catch {
34272
+ return false;
34273
+ }
34274
+ }
34275
+ function hasConnectionFaultMarker(headers) {
34276
+ try {
34277
+ return headers?.get(CONNECTION_FAULT_HEADER) === RECOVERY_MARKER_VALUE;
34278
+ } catch {
34279
+ return false;
34280
+ }
34281
+ }
34282
+ function isClaudishConnectionVerdict(headers) {
34283
+ return hasRecoveryMarker(headers) || hasConnectionFaultMarker(headers);
34284
+ }
34285
+ function isRecoveryHoldResponse(response) {
34286
+ return hasRecoveryMarker(response?.headers);
34287
+ }
34288
+ var RECOVERY_MARKER_HEADER = "x-claudish-recovery", RECOVERY_MARKER_VALUE = "1", CONNECTION_FAULT_HEADER = "x-claudish-connection-error";
34289
+
33516
34290
  // src/handlers/shared/stream-head-sniffer.ts
33517
34291
  function isRetryableStreamError(code, type, message) {
33518
34292
  if (RETRYABLE_ERROR_CODES.has(code))
@@ -35628,11 +36402,204 @@ class ComposedHandler {
35628
36402
  return this.modelAdapter.getName();
35629
36403
  return this.getAdapter().getName();
35630
36404
  }
36405
+ connectionEndpointFor(error, requestEndpoint) {
36406
+ const attached = error?.claudishEndpoint;
36407
+ if (typeof attached === "string" && attached.length > 0)
36408
+ return attached;
36409
+ if (requestEndpoint)
36410
+ return requestEndpoint;
36411
+ try {
36412
+ return this.provider.getEndpoint(this.targetModel);
36413
+ } catch {
36414
+ return this.provider.displayName;
36415
+ }
36416
+ }
36417
+ static newRecoveryTally() {
36418
+ return { retries: 0, recoveryMs: 0, clientRetry: 0 };
36419
+ }
36420
+ recoveryStats(tally) {
36421
+ if (!tally?.episodeId)
36422
+ return {};
36423
+ return {
36424
+ retry_attempts: tally.retries,
36425
+ recovery_ms: tally.recoveryMs,
36426
+ recovery_episode_id: tally.episodeId,
36427
+ recovery_client_retry: tally.clientRetry,
36428
+ ...tally.outcome ? { recovery_outcome: tally.outcome } : {}
36429
+ };
36430
+ }
36431
+ respondConnectionError(c, error, conn, endpoint, ctx) {
36432
+ const msg = buildConnectionErrorMessage(conn.kind, this.provider.displayName, endpoint);
36433
+ log(`[${this.provider.displayName}] ${msg} (code=${conn.code}${ctx.site ? `, site=${ctx.site}` : ""})`);
36434
+ logStderr(`Error: ${msg}`);
36435
+ reportError({
36436
+ error,
36437
+ providerName: this.provider.name,
36438
+ providerDisplayName: this.provider.displayName,
36439
+ streamFormat: this.provider.streamFormat,
36440
+ modelId: this.targetModel,
36441
+ httpStatus: undefined,
36442
+ isStreaming: false,
36443
+ retryAttempted: (ctx.retriedBeforeLadder ?? false) || (ctx.recovery?.retries ?? 0) > 0,
36444
+ isInteractive: this.isInteractive,
36445
+ ...ctx.authType ? { authType: ctx.authType } : {}
36446
+ });
36447
+ try {
36448
+ const { error_class, error_code } = classifyError(error, undefined);
36449
+ recordStats({
36450
+ model_id: this.targetModel,
36451
+ provider_name: this.provider.name,
36452
+ stream_format: this.provider.streamFormat,
36453
+ latency_ms: Math.round(performance.now() - ctx.startTime),
36454
+ success: false,
36455
+ http_status: 0,
36456
+ error_class,
36457
+ error_code,
36458
+ token_strategy: this.options.tokenStrategy ?? "standard",
36459
+ adapter_name: this.getActiveAdapterName(),
36460
+ middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
36461
+ fallback_used: ctx.fallbackMeta !== undefined,
36462
+ fallback_chain: ctx.fallbackMeta?.chain,
36463
+ fallback_attempts: ctx.fallbackMeta?.attempts,
36464
+ invocation_mode: this.options.invocationMode ?? "auto-route",
36465
+ ...this.recoveryStats(ctx.recovery)
36466
+ });
36467
+ } catch {}
36468
+ return c.json(wrapAnthropicError(400, msg, "connection_error"), 400, connectionFaultHeaders());
36469
+ }
36470
+ respondRecoveryHold(error, conn, endpoint, ctx) {
36471
+ const reason = buildConnectionErrorMessage(conn.kind, this.provider.displayName, endpoint);
36472
+ const msg = buildRecoveryHoldMessage(reason, ctx.attempts, ctx.recoveryMs);
36473
+ try {
36474
+ const { error_class, error_code } = classifyError(error, undefined);
36475
+ recordStats({
36476
+ model_id: this.targetModel,
36477
+ provider_name: this.provider.name,
36478
+ stream_format: this.provider.streamFormat,
36479
+ latency_ms: Math.round(performance.now() - ctx.startTime),
36480
+ success: false,
36481
+ http_status: 503,
36482
+ error_class,
36483
+ error_code,
36484
+ token_strategy: this.options.tokenStrategy ?? "standard",
36485
+ adapter_name: this.getActiveAdapterName(),
36486
+ middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
36487
+ fallback_used: ctx.fallbackMeta !== undefined,
36488
+ fallback_chain: ctx.fallbackMeta?.chain,
36489
+ fallback_attempts: ctx.fallbackMeta?.attempts,
36490
+ invocation_mode: this.options.invocationMode ?? "auto-route",
36491
+ ...this.recoveryStats(ctx.recovery)
36492
+ });
36493
+ } catch {}
36494
+ return new Response(JSON.stringify(wrapAnthropicError(503, msg, "overloaded_error")), {
36495
+ status: 503,
36496
+ headers: recoveryHoldHeaders()
36497
+ });
36498
+ }
36499
+ clientSignal(c) {
36500
+ try {
36501
+ const signal = c.req?.raw?.signal;
36502
+ if (signal)
36503
+ return signal;
36504
+ } catch {}
36505
+ return new AbortController().signal;
36506
+ }
36507
+ shouldSkipTier1(c, deadlineAt) {
36508
+ if (c.req.header("x-claudish-no-recovery") === "1")
36509
+ return "probe";
36510
+ if (!resolveRecoveryEnabled())
36511
+ return "recovery-disabled";
36512
+ if (recoveryClock().now() + MIN_ATTEMPT_SLOT_MS > deadlineAt)
36513
+ return "no-budget";
36514
+ return null;
36515
+ }
36516
+ async recoverConnection(c, first, conn, endpoint, op, ctx) {
36517
+ const skip = this.shouldSkipTier1(c, ctx.deadlineAt);
36518
+ if (skip) {
36519
+ log(`[Recovery] skipped (${skip}) \u2014 ${this.provider.displayName} at ${endpoint}, site=${ctx.site}`);
36520
+ return {
36521
+ kind: "respond",
36522
+ response: this.respondConnectionError(c, first, conn, endpoint, ctx)
36523
+ };
36524
+ }
36525
+ try {
36526
+ c.env?.timeout?.(c.req.raw, 0);
36527
+ } catch {}
36528
+ const result = await withConnectionRetry(op, first, {
36529
+ providerName: this.provider.name,
36530
+ providerDisplayName: this.provider.displayName,
36531
+ resolveEndpoint: (err) => this.connectionEndpointFor(err, endpoint),
36532
+ deadlineAt: ctx.deadlineAt,
36533
+ signal: this.clientSignal(c)
36534
+ });
36535
+ ctx.recovery.retries += result.requestRetries;
36536
+ ctx.recovery.recoveryMs += result.requestRecoveryMs;
36537
+ ctx.recovery.episodeId = result.episodeId;
36538
+ ctx.recovery.clientRetry = result.clientRetry;
36539
+ switch (result.kind) {
36540
+ case "ok":
36541
+ ctx.recovery.outcome = "recovered";
36542
+ return { kind: "value", value: result.value };
36543
+ case "threw":
36544
+ throw result.error;
36545
+ case "client_gone":
36546
+ ctx.recovery.outcome = "client_gone";
36547
+ this.recordClientGone(ctx.startTime, ctx.fallbackMeta, result.episodeId, result.attempts, ctx.recovery);
36548
+ return { kind: "respond", response: new Response(null, { status: 499 }) };
36549
+ default: {
36550
+ const leased = uiLeaseValid(result.episodeId);
36551
+ ctx.recovery.outcome = result.kind === "exhausted" ? "handoff" : "gave_up";
36552
+ if (result.kind === "exhausted" && leased) {
36553
+ logRecovery(`[Recovery] ${this.provider.displayName} exhausted after ${result.attempts} attempts ` + `in ${result.recoveryMs}ms (episode ${result.episodeId}, ui_lease=true) \u2014 ` + "handing the retry back to the client (503, tier 2)");
36554
+ return {
36555
+ kind: "respond",
36556
+ response: this.respondRecoveryHold(result.error, result.conn, result.endpoint, {
36557
+ startTime: ctx.startTime,
36558
+ attempts: result.attempts,
36559
+ recoveryMs: result.recoveryMs,
36560
+ fallbackMeta: ctx.fallbackMeta,
36561
+ recovery: ctx.recovery
36562
+ })
36563
+ };
36564
+ }
36565
+ logRecovery(`[Recovery] ${this.provider.displayName} exhausted after ${result.attempts} attempts ` + `in ${result.recoveryMs}ms (episode ${result.episodeId}, outcome ${result.kind}, ` + `ui_lease=${leased}) \u2014 answering connection_error`);
36566
+ return {
36567
+ kind: "respond",
36568
+ response: this.respondConnectionError(c, result.error, result.conn, result.endpoint, ctx)
36569
+ };
36570
+ }
36571
+ }
36572
+ }
36573
+ recordClientGone(startTime, fallbackMeta, episodeId, attempts, recovery) {
36574
+ log(`[Recovery] client disconnected after ${attempts} attempts (episode ${episodeId}) \u2014 ` + "releasing the request");
36575
+ try {
36576
+ recordStats({
36577
+ model_id: this.targetModel,
36578
+ provider_name: this.provider.name,
36579
+ stream_format: this.provider.streamFormat,
36580
+ latency_ms: Math.round(performance.now() - startTime),
36581
+ success: false,
36582
+ http_status: 0,
36583
+ error_class: "network",
36584
+ error_code: "client_disconnected",
36585
+ token_strategy: this.options.tokenStrategy ?? "standard",
36586
+ adapter_name: this.getActiveAdapterName(),
36587
+ middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
36588
+ fallback_used: fallbackMeta !== undefined,
36589
+ fallback_chain: fallbackMeta?.chain,
36590
+ fallback_attempts: fallbackMeta?.attempts,
36591
+ invocation_mode: this.options.invocationMode ?? "auto-route",
36592
+ ...this.recoveryStats(recovery)
36593
+ });
36594
+ } catch {}
36595
+ }
35631
36596
  async handle(c, payload) {
35632
36597
  const startTime = performance.now();
36598
+ inboundStartedAtPerf(c);
35633
36599
  let latencyMs = 0;
35634
36600
  const fallbackMeta = this.pendingFallbackMeta;
35635
36601
  this.pendingFallbackMeta = undefined;
36602
+ const recoveryTally = ComposedHandler.newRecoveryTally();
35636
36603
  const { claudeRequest, droppedParams } = transformOpenAIToClaude(payload);
35637
36604
  const adapter = this.getAdapter();
35638
36605
  if (typeof adapter.reset === "function")
@@ -35764,28 +36731,42 @@ class ComposedHandler {
35764
36731
  if (this.provider.refreshAuth) {
35765
36732
  try {
35766
36733
  await this.provider.refreshAuth();
35767
- if (this.provider.displayName) {
35768
- this.tokenTracker.setProviderDisplayName(this.provider.displayName);
35769
- }
35770
36734
  } catch (err) {
35771
- log(`[${this.provider.displayName}] Auth/health check failed: ${err.message}`);
35772
- logStderr(`Error [${this.provider.displayName}]: Auth/health check failed \u2014 ${err.message}. Check credentials and server.`);
35773
- reportError({
35774
- error: err,
35775
- providerName: this.provider.name,
35776
- providerDisplayName: this.provider.displayName,
35777
- streamFormat: this.provider.streamFormat,
35778
- modelId: this.targetModel,
35779
- httpStatus: 401,
35780
- isStreaming: false,
35781
- retryAttempted: false,
35782
- isInteractive: this.isInteractive,
35783
- authType: "oauth"
35784
- });
35785
- if (err?.terminal) {
35786
- return c.json(wrapAnthropicError(400, err.message, "invalid_request_error"), 400);
36735
+ const conn = classifyConnectionError(err);
36736
+ if (conn) {
36737
+ const outcome = await this.recoverConnection(c, err, conn, this.connectionEndpointFor(err), () => this.provider.refreshAuth(), {
36738
+ startTime,
36739
+ deadlineAt: refreshDeadlineAt(tier1DeadlineAt(c)),
36740
+ fallbackMeta,
36741
+ authType: "oauth",
36742
+ site: "refreshAuth",
36743
+ recovery: recoveryTally
36744
+ });
36745
+ if (outcome.kind === "respond")
36746
+ return outcome.response;
36747
+ } else {
36748
+ log(`[${this.provider.displayName}] Auth/health check failed: ${err.message}`);
36749
+ logStderr(`Error [${this.provider.displayName}]: Auth/health check failed \u2014 ${err.message}. Check credentials and server.`);
36750
+ reportError({
36751
+ error: err,
36752
+ providerName: this.provider.name,
36753
+ providerDisplayName: this.provider.displayName,
36754
+ streamFormat: this.provider.streamFormat,
36755
+ modelId: this.targetModel,
36756
+ httpStatus: 401,
36757
+ isStreaming: false,
36758
+ retryAttempted: false,
36759
+ isInteractive: this.isInteractive,
36760
+ authType: "oauth"
36761
+ });
36762
+ if (err?.terminal) {
36763
+ return c.json(wrapAnthropicError(400, err.message, "invalid_request_error"), 400);
36764
+ }
36765
+ return c.json(wrapAnthropicError(401, err.message, "authentication_error"), 401);
35787
36766
  }
35788
- return c.json(wrapAnthropicError(401, err.message, "authentication_error"), 401);
36767
+ }
36768
+ if (this.provider.displayName) {
36769
+ this.tokenTracker.setProviderDisplayName(this.provider.displayName);
35789
36770
  }
35790
36771
  }
35791
36772
  if (this.provider.getContextWindow) {
@@ -35798,61 +36779,72 @@ class ComposedHandler {
35798
36779
  requestPayload = this.provider.transformPayload(requestPayload, claudeRequest);
35799
36780
  }
35800
36781
  const endpoint = this.provider.getEndpoint(this.targetModel);
35801
- const headers = await this.provider.getHeaders(payload);
36782
+ let headers;
36783
+ try {
36784
+ headers = await this.provider.getHeaders(payload);
36785
+ } catch (err) {
36786
+ const conn = classifyConnectionError(err);
36787
+ if (!conn) {
36788
+ throw err;
36789
+ }
36790
+ const outcome = await this.recoverConnection(c, err, conn, this.connectionEndpointFor(err, endpoint), () => this.provider.getHeaders(), {
36791
+ startTime,
36792
+ deadlineAt: refreshDeadlineAt(tier1DeadlineAt(c)),
36793
+ fallbackMeta,
36794
+ authType: "oauth",
36795
+ site: "getHeaders",
36796
+ recovery: recoveryTally
36797
+ });
36798
+ if (outcome.kind === "respond")
36799
+ return outcome.response;
36800
+ headers = outcome.value;
36801
+ }
35802
36802
  const serialized = this.provider.serializeBody?.(requestPayload);
35803
36803
  headers["Content-Type"] = serialized?.contentType ?? "application/json";
35804
36804
  log(`[${this.provider.displayName}] Calling API: ${endpoint}`);
35805
36805
  const requestInit = this.provider.getRequestInit?.() || {};
35806
- const doFetch = () => fetch(endpoint, {
36806
+ const deadlineAt = tier1DeadlineAt(c);
36807
+ const clamp = deadlineClamp(deadlineAt);
36808
+ const doFetch = () => fetch(endpoint, mergeSignalIntoInit({
35807
36809
  method: "POST",
35808
36810
  headers,
35809
36811
  body: serialized?.body ?? JSON.stringify(requestPayload),
35810
36812
  ...requestInit
35811
- });
36813
+ }, clamp.signal));
35812
36814
  let response;
35813
36815
  try {
35814
- response = this.provider.enqueueRequest ? await this.provider.enqueueRequest(doFetch) : await doFetch();
36816
+ try {
36817
+ response = this.provider.enqueueRequest ? await this.provider.enqueueRequest(doFetch) : await doFetch();
36818
+ } finally {
36819
+ clamp.disarm();
36820
+ }
35815
36821
  } catch (error) {
35816
36822
  const conn = classifyConnectionError(error);
35817
- if (conn) {
35818
- const msg = buildConnectionErrorMessage(conn.kind, this.provider.displayName, endpoint);
35819
- log(`[${this.provider.displayName}] ${msg} (code=${conn.code})`);
35820
- logStderr(`Error: ${msg}`);
35821
- reportError({
35822
- error,
35823
- providerName: this.provider.name,
35824
- providerDisplayName: this.provider.displayName,
35825
- streamFormat: this.provider.streamFormat,
35826
- modelId: this.targetModel,
35827
- httpStatus: undefined,
35828
- isStreaming: false,
35829
- retryAttempted: false,
35830
- isInteractive: this.isInteractive
35831
- });
35832
- try {
35833
- const { error_class, error_code } = classifyError(error, undefined);
35834
- recordStats({
35835
- model_id: this.targetModel,
35836
- provider_name: this.provider.name,
35837
- stream_format: this.provider.streamFormat,
35838
- latency_ms: Math.round(performance.now() - startTime),
35839
- success: false,
35840
- http_status: 0,
35841
- error_class,
35842
- error_code,
35843
- token_strategy: this.options.tokenStrategy ?? "standard",
35844
- adapter_name: this.getActiveAdapterName(),
35845
- middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
35846
- fallback_used: fallbackMeta !== undefined,
35847
- fallback_chain: fallbackMeta?.chain,
35848
- fallback_attempts: fallbackMeta?.attempts,
35849
- invocation_mode: this.options.invocationMode ?? "auto-route"
35850
- });
35851
- } catch {}
35852
- return c.json(wrapAnthropicError(400, msg, "connection_error"), 400);
35853
- }
35854
- throw error;
36823
+ if (!conn)
36824
+ throw error;
36825
+ const doFetchWith = (sig) => fetch(endpoint, mergeSignalIntoInit({
36826
+ method: "POST",
36827
+ headers,
36828
+ body: serialized?.body ?? JSON.stringify(requestPayload),
36829
+ ...this.provider.getRequestInit?.() || {}
36830
+ }, sig));
36831
+ const reissue = (sig) => {
36832
+ const attempt = () => doFetchWith(sig);
36833
+ return this.provider.enqueueRequest ? this.provider.enqueueRequest(attempt, { signal: sig }) : attempt();
36834
+ };
36835
+ const outcome = await this.recoverConnection(c, error, conn, endpoint, reissue, {
36836
+ startTime,
36837
+ deadlineAt,
36838
+ fallbackMeta,
36839
+ site: "fetch",
36840
+ recovery: recoveryTally
36841
+ });
36842
+ if (outcome.kind === "respond")
36843
+ return outcome.response;
36844
+ response = outcome.value;
35855
36845
  }
36846
+ if (episodeCount() > 0)
36847
+ noteTargetReachable(this.provider.name, endpoint);
35856
36848
  if (this.provider.getActiveModelName?.()) {
35857
36849
  const activeModel = this.provider.getActiveModelName();
35858
36850
  this.tokenTracker.setActiveModelName(activeModel);
@@ -35874,14 +36866,35 @@ class ComposedHandler {
35874
36866
  log(`[${this.provider.displayName}] Parameter rejected \u2014 retrying: ${recovery.note}`);
35875
36867
  requestPayload = recovery.payload;
35876
36868
  const retrySerialized = this.provider.serializeBody?.(requestPayload);
35877
- const retryHeaders = await this.provider.getHeaders(payload);
35878
- retryHeaders["Content-Type"] = retrySerialized?.contentType ?? "application/json";
35879
- const retryResp = await fetch(endpoint, {
35880
- method: "POST",
35881
- headers: retryHeaders,
35882
- body: retrySerialized?.body ?? JSON.stringify(requestPayload),
35883
- ...this.provider.getRequestInit?.() || {}
35884
- });
36869
+ const doParamRetry = async (sig) => {
36870
+ const retryHeaders = await this.provider.getHeaders(payload);
36871
+ retryHeaders["Content-Type"] = retrySerialized?.contentType ?? "application/json";
36872
+ return fetch(endpoint, mergeSignalIntoInit({
36873
+ method: "POST",
36874
+ headers: retryHeaders,
36875
+ body: retrySerialized?.body ?? JSON.stringify(requestPayload),
36876
+ ...this.provider.getRequestInit?.() || {}
36877
+ }, sig));
36878
+ };
36879
+ let retryResp;
36880
+ try {
36881
+ retryResp = await doParamRetry();
36882
+ } catch (err) {
36883
+ const conn = classifyConnectionError(err);
36884
+ if (!conn)
36885
+ throw err;
36886
+ const outcome = await this.recoverConnection(c, err, conn, this.connectionEndpointFor(err, endpoint), doParamRetry, {
36887
+ startTime,
36888
+ deadlineAt: tier1DeadlineAt(c),
36889
+ fallbackMeta,
36890
+ retriedBeforeLadder: true,
36891
+ site: "parameter-recovery",
36892
+ recovery: recoveryTally
36893
+ });
36894
+ if (outcome.kind === "respond")
36895
+ return outcome.response;
36896
+ retryResp = outcome.value;
36897
+ }
35885
36898
  if (retryResp.ok) {
35886
36899
  response = retryResp;
35887
36900
  } else {
@@ -35893,17 +36906,19 @@ class ComposedHandler {
35893
36906
  if (!response.ok) {
35894
36907
  if (response.status === 401 && this.provider.forceRefreshAuth) {
35895
36908
  log(`[${this.provider.displayName}] Got 401, forcing auth refresh and retrying`);
35896
- try {
36909
+ const doAuthRetry = async (sig) => {
35897
36910
  await this.provider.forceRefreshAuth();
35898
36911
  const retryHeaders = await this.provider.getHeaders(payload);
35899
36912
  retryHeaders["Content-Type"] = serialized?.contentType ?? "application/json";
35900
36913
  const retryInit = this.provider.getRequestInit?.() || {};
35901
- const retryResp = await fetch(endpoint, {
36914
+ return fetch(endpoint, mergeSignalIntoInit({
35902
36915
  method: "POST",
35903
36916
  headers: retryHeaders,
35904
36917
  body: serialized?.body ?? JSON.stringify(requestPayload),
35905
36918
  ...retryInit
35906
- });
36919
+ }, sig));
36920
+ };
36921
+ const settleAuthRetry = async (retryResp) => {
35907
36922
  if (retryResp.ok) {
35908
36923
  response = retryResp;
35909
36924
  } else {
@@ -35939,47 +36954,73 @@ class ComposedHandler {
35939
36954
  fallback_used: fallbackMeta !== undefined,
35940
36955
  fallback_chain: fallbackMeta?.chain,
35941
36956
  fallback_attempts: fallbackMeta?.attempts,
35942
- invocation_mode: this.options.invocationMode ?? "auto-route"
36957
+ invocation_mode: this.options.invocationMode ?? "auto-route",
36958
+ ...this.recoveryStats(recoveryTally)
35943
36959
  });
35944
36960
  } catch {}
35945
36961
  return c.json(wrapAnthropicError(retryResp.status, errorText), retryResp.status);
35946
36962
  }
36963
+ return null;
36964
+ };
36965
+ try {
36966
+ const answered = await settleAuthRetry(await doAuthRetry());
36967
+ if (answered)
36968
+ return answered;
35947
36969
  } catch (err) {
35948
- log(`[${this.provider.displayName}] Auth refresh failed: ${err.message}`);
35949
- logStderr(`Error [${this.provider.displayName}]: Authentication failed \u2014 ${err.message}. Check API key.`);
35950
- reportError({
35951
- error: err,
35952
- providerName: this.provider.name,
35953
- providerDisplayName: this.provider.displayName,
35954
- streamFormat: this.provider.streamFormat,
35955
- modelId: this.targetModel,
35956
- httpStatus: 401,
35957
- isStreaming: false,
35958
- retryAttempted: true,
35959
- isInteractive: this.isInteractive,
35960
- authType: "oauth"
35961
- });
35962
- try {
35963
- const { error_class, error_code } = classifyError(err, 401, err.message);
35964
- recordStats({
35965
- model_id: this.targetModel,
35966
- provider_name: this.provider.name,
35967
- stream_format: this.provider.streamFormat,
35968
- latency_ms: Math.round(performance.now() - startTime),
35969
- success: false,
35970
- http_status: 401,
35971
- error_class,
35972
- error_code,
35973
- token_strategy: this.options.tokenStrategy ?? "standard",
35974
- adapter_name: this.getActiveAdapterName(),
35975
- middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
35976
- fallback_used: fallbackMeta !== undefined,
35977
- fallback_chain: fallbackMeta?.chain,
35978
- fallback_attempts: fallbackMeta?.attempts,
35979
- invocation_mode: this.options.invocationMode ?? "auto-route"
36970
+ const conn = classifyConnectionError(err);
36971
+ if (conn) {
36972
+ const outcome = await this.recoverConnection(c, err, conn, this.connectionEndpointFor(err, endpoint), doAuthRetry, {
36973
+ startTime,
36974
+ deadlineAt: tier1DeadlineAt(c),
36975
+ fallbackMeta,
36976
+ retriedBeforeLadder: true,
36977
+ authType: "oauth",
36978
+ site: "forceRefreshAuth",
36979
+ recovery: recoveryTally
35980
36980
  });
35981
- } catch {}
35982
- return c.json(wrapAnthropicError(401, err.message, "authentication_error"), 401);
36981
+ if (outcome.kind === "respond")
36982
+ return outcome.response;
36983
+ const answered = await settleAuthRetry(outcome.value);
36984
+ if (answered)
36985
+ return answered;
36986
+ } else {
36987
+ log(`[${this.provider.displayName}] Auth refresh failed: ${err.message}`);
36988
+ logStderr(`Error [${this.provider.displayName}]: Authentication failed \u2014 ${err.message}. Check API key.`);
36989
+ reportError({
36990
+ error: err,
36991
+ providerName: this.provider.name,
36992
+ providerDisplayName: this.provider.displayName,
36993
+ streamFormat: this.provider.streamFormat,
36994
+ modelId: this.targetModel,
36995
+ httpStatus: 401,
36996
+ isStreaming: false,
36997
+ retryAttempted: true,
36998
+ isInteractive: this.isInteractive,
36999
+ authType: "oauth"
37000
+ });
37001
+ try {
37002
+ const { error_class, error_code } = classifyError(err, 401, err.message);
37003
+ recordStats({
37004
+ model_id: this.targetModel,
37005
+ provider_name: this.provider.name,
37006
+ stream_format: this.provider.streamFormat,
37007
+ latency_ms: Math.round(performance.now() - startTime),
37008
+ success: false,
37009
+ http_status: 401,
37010
+ error_class,
37011
+ error_code,
37012
+ token_strategy: this.options.tokenStrategy ?? "standard",
37013
+ adapter_name: this.getActiveAdapterName(),
37014
+ middleware_names: this.middlewareManager.getActiveNames(this.bareModelName),
37015
+ fallback_used: fallbackMeta !== undefined,
37016
+ fallback_chain: fallbackMeta?.chain,
37017
+ fallback_attempts: fallbackMeta?.attempts,
37018
+ invocation_mode: this.options.invocationMode ?? "auto-route",
37019
+ ...this.recoveryStats(recoveryTally)
37020
+ });
37021
+ } catch {}
37022
+ return c.json(wrapAnthropicError(401, err.message, "authentication_error"), 401);
37023
+ }
35983
37024
  }
35984
37025
  } else {
35985
37026
  const errorText = await response.text();
@@ -36039,7 +37080,8 @@ class ComposedHandler {
36039
37080
  fallback_used: fallbackMeta !== undefined,
36040
37081
  fallback_chain: fallbackMeta?.chain,
36041
37082
  fallback_attempts: fallbackMeta?.attempts,
36042
- invocation_mode: this.options.invocationMode ?? "auto-route"
37083
+ invocation_mode: this.options.invocationMode ?? "auto-route",
37084
+ ...this.recoveryStats(recoveryTally)
36043
37085
  });
36044
37086
  } catch {}
36045
37087
  const errorBody = parsedErrorBody ?? {
@@ -36083,7 +37125,8 @@ class ComposedHandler {
36083
37125
  fallback_used: fallbackMeta !== undefined,
36084
37126
  fallback_chain: fallbackMeta?.chain,
36085
37127
  fallback_attempts: fallbackMeta?.attempts,
36086
- invocation_mode: this.options.invocationMode ?? "auto-route"
37128
+ invocation_mode: this.options.invocationMode ?? "auto-route",
37129
+ ...this.recoveryStats(recoveryTally)
36087
37130
  });
36088
37131
  } catch {}
36089
37132
  return c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503);
@@ -36125,7 +37168,8 @@ class ComposedHandler {
36125
37168
  fallback_used: fallbackMeta !== undefined,
36126
37169
  fallback_chain: fallbackMeta?.chain,
36127
37170
  fallback_attempts: fallbackMeta?.attempts,
36128
- invocation_mode: this.options.invocationMode ?? "auto-route"
37171
+ invocation_mode: this.options.invocationMode ?? "auto-route",
37172
+ ...this.recoveryStats(recoveryTally)
36129
37173
  });
36130
37174
  } catch {}
36131
37175
  return isTerminal ? c.json(wrapAnthropicError(400, surfaced, "invalid_request_error", undefined, settled.message), 400) : c.json(wrapAnthropicError(503, surfaced, "overloaded_error", undefined, settled.message), 503);
@@ -36156,7 +37200,8 @@ class ComposedHandler {
36156
37200
  fallback_used: fallbackMeta !== undefined,
36157
37201
  fallback_chain: fallbackMeta?.chain,
36158
37202
  fallback_attempts: fallbackMeta?.attempts,
36159
- invocation_mode: this.options.invocationMode ?? "auto-route"
37203
+ invocation_mode: this.options.invocationMode ?? "auto-route",
37204
+ ...this.recoveryStats(recoveryTally)
36160
37205
  });
36161
37206
  } catch {}
36162
37207
  try {
@@ -36501,6 +37546,9 @@ var init_composed_handler = __esm(() => {
36501
37546
  init_middleware();
36502
37547
  init_provider_definitions();
36503
37548
  init_openai();
37549
+ init_clock();
37550
+ init_coordinator();
37551
+ init_settings();
36504
37552
  init_vision_proxy();
36505
37553
  init_session_events();
36506
37554
  init_pro_injection();
@@ -36522,6 +37570,7 @@ var init_composed_handler = __esm(() => {
36522
37570
  init_openai_responses_sse();
36523
37571
  init_openai_sse();
36524
37572
  init_token_tracker();
37573
+ init_transient_retry();
36525
37574
  init_upstream_error_capture();
36526
37575
  STREAM_RETRY_DELAYS_MS = [3000, 15000, 30000];
36527
37576
  });
@@ -37085,6 +38134,7 @@ var init_openai_codex = __esm(() => {
37085
38134
  init_model_catalog();
37086
38135
  init_authority();
37087
38136
  init_billing_probe();
38137
+ init_connection_error();
37088
38138
  init_conversation_key();
37089
38139
  init_openai();
37090
38140
  OpenAICodexTransport = class OpenAICodexTransport extends OpenAIProviderTransport {
@@ -37092,7 +38142,9 @@ var init_openai_codex = __esm(() => {
37092
38142
  async refreshAuth() {
37093
38143
  try {
37094
38144
  this.cachedAuth = await credentials.getRequestAuth("openai-codex", { model: "" });
37095
- } catch {
38145
+ } catch (err) {
38146
+ if (classifyConnectionError(err))
38147
+ throw err;
37096
38148
  this.cachedAuth = null;
37097
38149
  }
37098
38150
  const signedWithOAuth = this.cachedAuth?.arm === "oauth";
@@ -37381,7 +38433,7 @@ class VertexProviderTransport {
37381
38433
  try {
37382
38434
  this.cachedAuth = await credentials.getRequestAuth("vertex", { model: this.parsed.model });
37383
38435
  } catch (e) {
37384
- throw new Error(`Vertex AI auth failed: ${e.message}`);
38436
+ throw new Error(`Vertex AI auth failed: ${e.message}`, { cause: e });
37385
38437
  }
37386
38438
  }
37387
38439
  async forceRefreshAuth() {
@@ -41849,46 +42901,6 @@ var init_stream_json_reducer = __esm(() => {
41849
42901
  };
41850
42902
  });
41851
42903
 
41852
- // src/config.ts
41853
- var DEFAULT_PORT_RANGE, ENV;
41854
- var init_config2 = __esm(() => {
41855
- DEFAULT_PORT_RANGE = { start: 3000, end: 9000 };
41856
- ENV = {
41857
- OPENROUTER_API_KEY: "OPENROUTER_API_KEY",
41858
- CLAUDISH_MODEL: "CLAUDISH_MODEL",
41859
- CLAUDISH_PORT: "CLAUDISH_PORT",
41860
- CLAUDISH_ACTIVE_MODEL_NAME: "CLAUDISH_ACTIVE_MODEL_NAME",
41861
- CLAUDISH_TOKEN_FILE: "CLAUDISH_TOKEN_FILE",
41862
- CLAUDISH_PROVIDER_NAME: "CLAUDISH_PROVIDER_NAME",
41863
- ANTHROPIC_MODEL: "ANTHROPIC_MODEL",
41864
- ANTHROPIC_SMALL_FAST_MODEL: "ANTHROPIC_SMALL_FAST_MODEL",
41865
- CLAUDISH_MODEL_OPUS: "CLAUDISH_MODEL_OPUS",
41866
- CLAUDISH_MODEL_SONNET: "CLAUDISH_MODEL_SONNET",
41867
- CLAUDISH_MODEL_HAIKU: "CLAUDISH_MODEL_HAIKU",
41868
- CLAUDISH_MODEL_SUBAGENT: "CLAUDISH_MODEL_SUBAGENT",
41869
- ANTHROPIC_DEFAULT_OPUS_MODEL: "ANTHROPIC_DEFAULT_OPUS_MODEL",
41870
- ANTHROPIC_DEFAULT_SONNET_MODEL: "ANTHROPIC_DEFAULT_SONNET_MODEL",
41871
- ANTHROPIC_DEFAULT_HAIKU_MODEL: "ANTHROPIC_DEFAULT_HAIKU_MODEL",
41872
- CLAUDE_CODE_SUBAGENT_MODEL: "CLAUDE_CODE_SUBAGENT_MODEL",
41873
- CLAUDE_CODE_AUTO_COMPACT_WINDOW: "CLAUDE_CODE_AUTO_COMPACT_WINDOW",
41874
- CLAUDE_CODE_MAX_CONTEXT_TOKENS: "CLAUDE_CODE_MAX_CONTEXT_TOKENS",
41875
- OLLAMA_BASE_URL: "OLLAMA_BASE_URL",
41876
- OLLAMA_HOST: "OLLAMA_HOST",
41877
- LMSTUDIO_BASE_URL: "LMSTUDIO_BASE_URL",
41878
- VLLM_BASE_URL: "VLLM_BASE_URL",
41879
- GEMINI_API_KEY: "GEMINI_API_KEY",
41880
- GEMINI_BASE_URL: "GEMINI_BASE_URL",
41881
- OPENAI_API_KEY: "OPENAI_API_KEY",
41882
- OPENAI_BASE_URL: "OPENAI_BASE_URL",
41883
- CLAUDISH_SUMMARIZE_TOOLS: "CLAUDISH_SUMMARIZE_TOOLS",
41884
- CLAUDISH_DIAG_MODE: "CLAUDISH_DIAG_MODE",
41885
- CLAUDISH_DEBUG: "CLAUDISH_DEBUG",
41886
- CLAUDISH_ANTHROPIC_API_BILLING: "CLAUDISH_ANTHROPIC_API_BILLING",
41887
- CLAUDISH_CLASSIFIER_PROVIDER: "CLAUDISH_CLASSIFIER_PROVIDER",
41888
- CLAUDISH_CLASSIFIER_MODEL: "CLAUDISH_CLASSIFIER_MODEL"
41889
- };
41890
- });
41891
-
41892
42904
  // src/process-tree.ts
41893
42905
  function signalProcessTree(proc, signal) {
41894
42906
  if (KILL_PROCESS_GROUP && proc.pid) {
@@ -43323,7 +44335,7 @@ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LI
43323
44335
  var init_team_orchestrator = __esm(() => {
43324
44336
  init_prehydrate();
43325
44337
  init_stream_json_reducer();
43326
- init_config2();
44338
+ init_config();
43327
44339
  init_upstream_error_capture();
43328
44340
  init_process_tree();
43329
44341
  init_redact();
@@ -43337,7 +44349,7 @@ var init_team_orchestrator = __esm(() => {
43337
44349
 
43338
44350
  // src/channel/session-manager.ts
43339
44351
  import { spawn as spawn3 } from "child_process";
43340
- import { randomUUID as randomUUID4 } from "crypto";
44352
+ import { randomUUID as randomUUID5 } from "crypto";
43341
44353
  import {
43342
44354
  appendFileSync as appendFileSync6,
43343
44355
  closeSync as closeSync7,
@@ -43493,8 +44505,8 @@ class SessionManager {
43493
44505
  if (opts.sessionId !== undefined && this.sessions.has(opts.sessionId)) {
43494
44506
  throw new Error(`Session id already in use: ${opts.sessionId}`);
43495
44507
  }
43496
- const sessionId = opts.sessionId ?? randomUUID4().slice(0, 8);
43497
- const claudeSessionId = randomUUID4();
44508
+ const sessionId = opts.sessionId ?? randomUUID5().slice(0, 8);
44509
+ const claudeSessionId = randomUUID5();
43498
44510
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
43499
44511
  const startedAt = new Date().toISOString();
43500
44512
  const sessionDir = opts.sessionDir ?? join31(this.sessionsDir, sessionId);
@@ -44224,7 +45236,7 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
44224
45236
  }
44225
45237
  var DEFAULT_MAX_SESSIONS = 20, DRAIN_TIMEOUT_MS = 1e4, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000, TERMINAL_RETENTION_MS, MAX_TERMINAL_SESSIONS = 50, STDERR_SIDE_LIMIT, EVENT_LOG_LIMIT, EVENT_RING_SIZE = 200, EVENT_PREVIEW_CHARS = 800, DEFAULT_EVENT_LIMIT = 40, UPSTREAM_ERROR_TAIL_BYTES, STDERR_TRUNCATION_MARKER = "[claudish] \u2026 stderr truncated to", TERMINAL_STATUSES, KNOWN_STATUSES, SESSION_ID_RE, META_READ_LIMIT, OUTPUT_TAIL_BYTES, STDERR_READ_BYTES, EVENT_TAIL_BYTES, NO_TERMINAL_RECORD = "claudish_no_terminal_record", RESERVED_FLAG_ALIASES, TRANSPORT_BREAKING_FLAGS, RESERVED_CHILD_FLAGS, metaString = (v) => typeof v === "string" && v.length > 0 ? v : null, metaNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : null, metaStatus = (v) => typeof v === "string" && KNOWN_STATUSES.includes(v) ? v : null, CLAUDISH_NOTE_PREFIX = "[claudish] ";
44226
45238
  var init_session_manager = __esm(() => {
44227
- init_config2();
45239
+ init_config();
44228
45240
  init_upstream_error_capture();
44229
45241
  init_process_tree();
44230
45242
  init_redact();
@@ -44849,7 +45861,8 @@ async function probeLink(proxyUrl, link, timeoutMs) {
44849
45861
  response = await fetch(`${proxyUrl}/v1/messages`, {
44850
45862
  method: "POST",
44851
45863
  headers: {
44852
- "Content-Type": "application/json"
45864
+ "Content-Type": "application/json",
45865
+ "x-claudish-no-recovery": "1"
44853
45866
  },
44854
45867
  body: JSON.stringify({
44855
45868
  model: link.modelSpec,
@@ -48774,8 +49787,12 @@ class FallbackHandler {
48774
49787
  }
48775
49788
  return response;
48776
49789
  }
49790
+ if (isRecoveryHoldResponse(response)) {
49791
+ logStderr(`[Fallback] ${name} is unreachable and claudish is still retrying it \u2014 ` + "holding the chain here rather than switching providers mid-outage.");
49792
+ return response;
49793
+ }
48777
49794
  const errorBody = await response.clone().text();
48778
- if (!isRetryableError(response.status, errorBody, name)) {
49795
+ if (!isRetryableError(response.status, errorBody, name, response.headers)) {
48779
49796
  if (errors.length > 0) {
48780
49797
  errors.push({ provider: name, status: response.status, message: errorBody });
48781
49798
  return this.formatCombinedError(c, errors, payload.model);
@@ -48824,7 +49841,9 @@ ${summary}`);
48824
49841
  }
48825
49842
  }
48826
49843
  }
48827
- function isRetryableError(status, errorBody, provider) {
49844
+ function isRetryableError(status, errorBody, provider, headers) {
49845
+ if (isClaudishConnectionVerdict(headers))
49846
+ return false;
48828
49847
  if (hasQuotaExhaustionWording(errorBody))
48829
49848
  return true;
48830
49849
  const upstream = status === 400 ? extractUpstreamStatus(errorBody) : undefined;
@@ -49071,6 +50090,14 @@ var init_native_handler = __esm(() => {
49071
50090
  });
49072
50091
 
49073
50092
  // src/handlers/shared/local-queue.ts
50093
+ function toAbortError(reason) {
50094
+ if (reason instanceof Error)
50095
+ return reason;
50096
+ const err = new Error("Request aborted while queued");
50097
+ err.name = "AbortError";
50098
+ return err;
50099
+ }
50100
+
49074
50101
  class LocalModelQueue {
49075
50102
  static instance = null;
49076
50103
  queue = [];
@@ -49101,7 +50128,10 @@ class LocalModelQueue {
49101
50128
  return true;
49102
50129
  return enabled !== "false" && enabled !== "0";
49103
50130
  }
49104
- async enqueue(fetchFn, providerId, concurrencyOverride) {
50131
+ async enqueue(fetchFn, providerId, concurrencyOverride, signal) {
50132
+ if (signal?.aborted) {
50133
+ throw toAbortError(signal.reason);
50134
+ }
49105
50135
  if (concurrencyOverride !== undefined) {
49106
50136
  if (concurrencyOverride === 0) {
49107
50137
  if (getLogLevel() === "debug") {
@@ -49128,8 +50158,22 @@ class LocalModelQueue {
49128
50158
  fetchFn,
49129
50159
  resolve,
49130
50160
  reject,
49131
- providerId
50161
+ providerId,
50162
+ signal
49132
50163
  };
50164
+ if (signal) {
50165
+ const onAbort = () => {
50166
+ queuedRequest.abandoned = true;
50167
+ queuedRequest.detach?.();
50168
+ queuedRequest.detach = undefined;
50169
+ const i = this.queue.indexOf(queuedRequest);
50170
+ if (i !== -1)
50171
+ this.queue.splice(i, 1);
50172
+ reject(toAbortError(signal.reason));
50173
+ };
50174
+ queuedRequest.detach = () => signal.removeEventListener("abort", onAbort);
50175
+ signal.addEventListener("abort", onAbort, { once: true });
50176
+ }
49133
50177
  this.queue.push(queuedRequest);
49134
50178
  if (getLogLevel() === "debug") {
49135
50179
  log(`[LocalQueue] Request enqueued for ${providerId} (queue length: ${this.queue.length}, active: ${this.activeRequests}/${this.maxParallel})`);
@@ -49142,6 +50186,11 @@ class LocalModelQueue {
49142
50186
  const request = this.queue.shift();
49143
50187
  if (!request)
49144
50188
  break;
50189
+ if (request.abandoned || request.signal?.aborted) {
50190
+ request.detach?.();
50191
+ request.detach = undefined;
50192
+ continue;
50193
+ }
49145
50194
  if (getLogLevel() === "debug") {
49146
50195
  log(`[LocalQueue] Processing request for ${request.providerId} (${this.queue.length} remaining in queue, ${this.activeRequests + 1}/${this.maxParallel} active)`);
49147
50196
  }
@@ -49155,6 +50204,8 @@ class LocalModelQueue {
49155
50204
  }
49156
50205
  async executeRequest(request) {
49157
50206
  this.activeRequests++;
50207
+ request.detach?.();
50208
+ request.detach = undefined;
49158
50209
  try {
49159
50210
  const response = await request.fetchFn();
49160
50211
  if (response.status === 500) {
@@ -49250,6 +50301,8 @@ class LocalTransport {
49250
50301
  concurrency;
49251
50302
  healthChecked = false;
49252
50303
  isHealthy = false;
50304
+ lastProbeError;
50305
+ lastProbeUrl;
49253
50306
  _contextWindow = 32768;
49254
50307
  constructor(config, modelName, options) {
49255
50308
  this.config = config;
@@ -49317,17 +50370,19 @@ class LocalTransport {
49317
50370
  }
49318
50371
  return {};
49319
50372
  }
49320
- async enqueueRequest(fetchFn) {
50373
+ async enqueueRequest(fetchFn, opts) {
49321
50374
  if (!LocalModelQueue.isEnabled())
49322
50375
  return fetchFn();
49323
- return LocalModelQueue.getInstance().enqueue(fetchFn, this.name, this.concurrency);
50376
+ return LocalModelQueue.getInstance().enqueue(fetchFn, this.name, this.concurrency, opts?.signal);
49324
50377
  }
49325
50378
  async refreshAuth() {
49326
50379
  if (this.healthChecked)
49327
50380
  return;
49328
50381
  const healthy = await this.checkHealth();
49329
50382
  if (!healthy) {
49330
- throw new Error(this.getConnectionErrorMessage());
50383
+ throw Object.assign(new Error(this.getConnectionErrorMessage(), {
50384
+ cause: this.lastProbeError
50385
+ }), { claudishEndpoint: this.lastProbeUrl ?? this.config.baseUrl });
49331
50386
  }
49332
50387
  await this.fetchContextWindow();
49333
50388
  }
@@ -49340,8 +50395,10 @@ class LocalTransport {
49340
50395
  async checkHealth() {
49341
50396
  if (this.healthChecked)
49342
50397
  return this.isHealthy;
50398
+ this.lastProbeError = undefined;
50399
+ this.lastProbeUrl = undefined;
50400
+ const healthUrl = `${this.config.baseUrl}/api/tags`;
49343
50401
  try {
49344
- const healthUrl = `${this.config.baseUrl}/api/tags`;
49345
50402
  log(`[${this.displayName}] Trying health check: ${healthUrl}`);
49346
50403
  const response = await fetch(healthUrl, {
49347
50404
  method: "GET",
@@ -49355,10 +50412,12 @@ class LocalTransport {
49355
50412
  }
49356
50413
  log(`[${this.displayName}] /api/tags returned ${response.status}, trying /v1/models`);
49357
50414
  } catch (e) {
50415
+ this.lastProbeError = markOwnTimeout(e);
50416
+ this.lastProbeUrl = healthUrl;
49358
50417
  log(`[${this.displayName}] /api/tags failed: ${e?.message || e}, trying /v1/models`);
49359
50418
  }
50419
+ const modelsUrl = `${this.config.baseUrl}/v1/models`;
49360
50420
  try {
49361
- const modelsUrl = `${this.config.baseUrl}/v1/models`;
49362
50421
  log(`[${this.displayName}] Trying health check: ${modelsUrl}`);
49363
50422
  const response = await fetch(modelsUrl, {
49364
50423
  method: "GET",
@@ -49372,9 +50431,10 @@ class LocalTransport {
49372
50431
  }
49373
50432
  log(`[${this.displayName}] /v1/models returned ${response.status}`);
49374
50433
  } catch (e) {
50434
+ this.lastProbeError = markOwnTimeout(e);
50435
+ this.lastProbeUrl = modelsUrl;
49375
50436
  log(`[${this.displayName}] /v1/models failed: ${e?.message || e}`);
49376
50437
  }
49377
- this.healthChecked = true;
49378
50438
  this.isHealthy = false;
49379
50439
  log(`[${this.displayName}] Health check FAILED - provider not available`);
49380
50440
  return false;
@@ -49467,6 +50527,7 @@ class LocalTransport {
49467
50527
  var localProviderAgent, DISPLAY_NAMES2;
49468
50528
  var init_local = __esm(() => {
49469
50529
  init_authority();
50530
+ init_connection_error();
49470
50531
  init_local_queue();
49471
50532
  init_logger();
49472
50533
  init_probe_discovery();
@@ -50337,6 +51398,7 @@ ${plan.hint}` : `[Route] ${plan.reason}`;
50337
51398
  modelRequestCount: () => modelRequestCount,
50338
51399
  shutdown: async () => {
50339
51400
  await server.stop(true);
51401
+ closeAllEpisodes();
50340
51402
  },
50341
51403
  invalidateHandlerCache: (providerSlug) => {
50342
51404
  if (!providerSlug) {
@@ -50389,6 +51451,7 @@ var init_proxy_server = __esm(() => {
50389
51451
  init_local();
50390
51452
  init_openrouter();
50391
51453
  init_poe();
51454
+ init_coordinator();
50392
51455
  init_pricing_cache();
50393
51456
  RoutingError = class RoutingError extends Error {
50394
51457
  constructor(message) {
@@ -52322,14 +53385,57 @@ var init_behavior_command = __esm(() => {
52322
53385
  init_ansi();
52323
53386
  });
52324
53387
 
53388
+ // src/launcher/magmux-binary.ts
53389
+ import { execSync } from "child_process";
53390
+ import { existsSync as existsSync27 } from "fs";
53391
+ import { dirname as dirname11, join as join36 } from "path";
53392
+ import { fileURLToPath as fileURLToPath2 } from "url";
53393
+ function findMagmuxBinaryOrNull() {
53394
+ const thisFile = fileURLToPath2(import.meta.url);
53395
+ const thisDir = dirname11(thisFile);
53396
+ const pkgRoots = [join36(thisDir, ".."), join36(thisDir, "..", "..")];
53397
+ const platform = process.platform;
53398
+ const arch = process.arch;
53399
+ for (const pkgRoot of pkgRoots) {
53400
+ const bundled = join36(pkgRoot, "native", `magmux-${platform}-${arch}`);
53401
+ if (existsSync27(bundled))
53402
+ return bundled;
53403
+ }
53404
+ try {
53405
+ const pkgName = `@claudish/magmux-${platform}-${arch}`;
53406
+ let searchDir = pkgRoots[0];
53407
+ for (let i = 0;i < 5; i++) {
53408
+ const candidate = join36(searchDir, "node_modules", pkgName, "bin", "magmux");
53409
+ if (existsSync27(candidate))
53410
+ return candidate;
53411
+ const parent = dirname11(searchDir);
53412
+ if (parent === searchDir)
53413
+ break;
53414
+ searchDir = parent;
53415
+ }
53416
+ } catch {}
53417
+ try {
53418
+ const result = execSync("which magmux", { encoding: "utf-8" }).trim();
53419
+ if (result)
53420
+ return result;
53421
+ } catch {}
53422
+ return null;
53423
+ }
53424
+ function findMagmuxBinary() {
53425
+ const found = findMagmuxBinaryOrNull();
53426
+ if (found)
53427
+ return found;
53428
+ throw new Error(`magmux not found. Install it:
53429
+ brew install MadAppGang/tap/magmux`);
53430
+ }
53431
+ var init_magmux_binary = () => {};
53432
+
52325
53433
  // src/team-grid.ts
52326
53434
  import { spawn as spawn4 } from "child_process";
52327
- import { execSync } from "child_process";
52328
- import { existsSync as existsSync27, readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "fs";
53435
+ import { existsSync as existsSync28, readFileSync as readFileSync28, writeFileSync as writeFileSync17 } from "fs";
52329
53436
  import { connect as netConnect } from "net";
52330
- import { dirname as dirname11, join as join36 } from "path";
53437
+ import { join as join37 } from "path";
52331
53438
  import { setTimeout as wait } from "timers/promises";
52332
- import { fileURLToPath as fileURLToPath2 } from "url";
52333
53439
  function resolveRouteInfo(modelId) {
52334
53440
  const parsed = parseModelSpec(modelId);
52335
53441
  if (parsed.isExplicitProvider) {
@@ -52418,36 +53524,6 @@ function buildPaneHeader(model, prompt, bg) {
52418
53524
  lines.push(`printf '\\033[2m %s\\033[0m\\n\\n' '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500';`);
52419
53525
  return lines.join(" ");
52420
53526
  }
52421
- function findMagmuxBinary() {
52422
- const thisFile = fileURLToPath2(import.meta.url);
52423
- const thisDir = dirname11(thisFile);
52424
- const pkgRoot = join36(thisDir, "..");
52425
- const platform = process.platform;
52426
- const arch = process.arch;
52427
- const bundledMagmux = join36(pkgRoot, "native", `magmux-${platform}-${arch}`);
52428
- if (existsSync27(bundledMagmux))
52429
- return bundledMagmux;
52430
- try {
52431
- const pkgName = `@claudish/magmux-${platform}-${arch}`;
52432
- let searchDir = pkgRoot;
52433
- for (let i = 0;i < 5; i++) {
52434
- const candidate = join36(searchDir, "node_modules", pkgName, "bin", "magmux");
52435
- if (existsSync27(candidate))
52436
- return candidate;
52437
- const parent = dirname11(searchDir);
52438
- if (parent === searchDir)
52439
- break;
52440
- searchDir = parent;
52441
- }
52442
- } catch {}
52443
- try {
52444
- const result = execSync("which magmux", { encoding: "utf-8" }).trim();
52445
- if (result)
52446
- return result;
52447
- } catch {}
52448
- throw new Error(`magmux not found. Install it:
52449
- brew install MadAppGang/tap/magmux`);
52450
- }
52451
53527
  function withoutControlPanes(evt) {
52452
53528
  if (!Array.isArray(evt.panes))
52453
53529
  return evt;
@@ -52456,7 +53532,7 @@ function withoutControlPanes(evt) {
52456
53532
  async function subscribeToMagmux(sockPath, onEvent) {
52457
53533
  let client = null;
52458
53534
  for (let attempt = 0;attempt < 40; attempt++) {
52459
- if (existsSync27(sockPath)) {
53535
+ if (existsSync28(sockPath)) {
52460
53536
  try {
52461
53537
  client = await new Promise((resolve, reject) => {
52462
53538
  const s = netConnect(sockPath);
@@ -52543,9 +53619,9 @@ async function runWithGrid(sessionPath, models, input, opts) {
52543
53619
  const keep = opts?.keep ?? false;
52544
53620
  const manifest = setupSession(sessionPath, models, input);
52545
53621
  const startedAt = new Date().toISOString();
52546
- const gridfilePath = join36(sessionPath, "gridfile.txt");
52547
- const prompt = readFileSync28(join36(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
52548
- const rawPrompt = readFileSync28(join36(sessionPath, "input.md"), "utf-8");
53622
+ const gridfilePath = join37(sessionPath, "gridfile.txt");
53623
+ const prompt = readFileSync28(join37(sessionPath, "input.md"), "utf-8").replace(/'/g, "'\\''").replace(/\n/g, " ");
53624
+ const rawPrompt = readFileSync28(join37(sessionPath, "input.md"), "utf-8");
52549
53625
  const usedBannerColors = new Set;
52550
53626
  const gridLines = Object.entries(manifest.models).map(([anonId]) => {
52551
53627
  const model = manifest.models[anonId].model;
@@ -52576,12 +53652,13 @@ async function runWithGrid(sessionPath, models, input, opts) {
52576
53652
  });
52577
53653
  const [{ results }] = await Promise.all([subscription, procExit]);
52578
53654
  const status = buildTeamStatus(manifest, startedAt, results?.panes ?? null);
52579
- const statusPath = join36(sessionPath, "status.json");
53655
+ const statusPath = join37(sessionPath, "status.json");
52580
53656
  writeFileSync17(statusPath, JSON.stringify(status, null, 2), "utf-8");
52581
53657
  return status;
52582
53658
  }
52583
53659
  var BANNER_BG_COLORS;
52584
53660
  var init_team_grid = __esm(() => {
53661
+ init_magmux_binary();
52585
53662
  init_profile_config();
52586
53663
  init_model_parser();
52587
53664
  init_routing_rules();
@@ -52598,7 +53675,7 @@ var init_team_grid = __esm(() => {
52598
53675
 
52599
53676
  // src/team-cli.ts
52600
53677
  import { readFileSync as readFileSync29 } from "fs";
52601
- import { join as join37 } from "path";
53678
+ import { join as join38 } from "path";
52602
53679
  function getFlag(args, flag) {
52603
53680
  const idx = args.indexOf(flag);
52604
53681
  if (idx === -1 || idx + 1 >= args.length)
@@ -52721,7 +53798,7 @@ async function teamCommand(args) {
52721
53798
  }
52722
53799
  case "judge": {
52723
53800
  await judgeResponses(sessionPath, { judges });
52724
- console.log(readFileSync29(join37(sessionPath, "verdict.md"), "utf-8"));
53801
+ console.log(readFileSync29(join38(sessionPath, "verdict.md"), "utf-8"));
52725
53802
  break;
52726
53803
  }
52727
53804
  case "run-and-judge": {
@@ -52738,7 +53815,7 @@ async function teamCommand(args) {
52738
53815
  });
52739
53816
  printStatus(status);
52740
53817
  await judgeResponses(sessionPath, { judges });
52741
- console.log(readFileSync29(join37(sessionPath, "verdict.md"), "utf-8"));
53818
+ console.log(readFileSync29(join38(sessionPath, "verdict.md"), "utf-8"));
52742
53819
  break;
52743
53820
  }
52744
53821
  case "status": {
@@ -55578,9 +56655,9 @@ var init_keychain_command = __esm(() => {
55578
56655
 
55579
56656
  // src/auth/antigravity-oauth.ts
55580
56657
  import { spawnSync as spawnSync2 } from "child_process";
55581
- import { existsSync as existsSync28, unlinkSync as unlinkSync6 } from "fs";
56658
+ import { existsSync as existsSync29, unlinkSync as unlinkSync6 } from "fs";
55582
56659
  import { homedir as homedir33 } from "os";
55583
- import { join as join38 } from "path";
56660
+ import { join as join39 } from "path";
55584
56661
  async function defaultSuggestModel() {
55585
56662
  try {
55586
56663
  const tok = readSharedAntigravityToken();
@@ -55701,8 +56778,8 @@ No session detected yet. Starting the Antigravity CLI interactively \u2014
55701
56778
  async logout(deps) {
55702
56779
  deleteSharedAntigravityToken(deps);
55703
56780
  try {
55704
- const tokenFile = join38(homedir33(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
55705
- if (existsSync28(tokenFile))
56781
+ const tokenFile = join39(homedir33(), ".gemini", "antigravity-cli", "antigravity-oauth-token");
56782
+ if (existsSync29(tokenFile))
55706
56783
  unlinkSync6(tokenFile);
55707
56784
  } catch {}
55708
56785
  log("[AntigravityOAuth] Antigravity session cleared (keychain + agy token file)");
@@ -57211,9 +58288,9 @@ var init_DiscoveryNotice = __esm(() => {
57211
58288
  });
57212
58289
 
57213
58290
  // src/providers/model-descriptions.ts
57214
- import { existsSync as existsSync29, mkdirSync as mkdirSync17, readFileSync as readFileSync30, writeFileSync as writeFileSync18 } from "fs";
58291
+ import { existsSync as existsSync30, mkdirSync as mkdirSync17, readFileSync as readFileSync30, writeFileSync as writeFileSync18 } from "fs";
57215
58292
  import { homedir as homedir34 } from "os";
57216
- import { dirname as dirname12, join as join39 } from "path";
58293
+ import { dirname as dirname12, join as join40 } from "path";
57217
58294
  function emptyDescriptionIndex() {
57218
58295
  return EMPTY_INDEX;
57219
58296
  }
@@ -57232,7 +58309,7 @@ function indexFrom(cache) {
57232
58309
  };
57233
58310
  }
57234
58311
  function readCache(path) {
57235
- if (!existsSync29(path))
58312
+ if (!existsSync30(path))
57236
58313
  return null;
57237
58314
  try {
57238
58315
  const raw = JSON.parse(readFileSync30(path, "utf-8"));
@@ -57306,7 +58383,7 @@ var init_model_descriptions = __esm(() => {
57306
58383
  init_logger();
57307
58384
  init_model_loader();
57308
58385
  init_cache_ttl();
57309
- MODEL_DESCRIPTIONS_CACHE_PATH = join39(homedir34(), ".claudish", "model-descriptions.json");
58386
+ MODEL_DESCRIPTIONS_CACHE_PATH = join40(homedir34(), ".claudish", "model-descriptions.json");
57310
58387
  EMPTY_INDEX = { get: () => {
57311
58388
  return;
57312
58389
  }, size: 0 };
@@ -62462,30 +63539,30 @@ var init_branding = __esm(() => {
62462
63539
  });
62463
63540
 
62464
63541
  // src/update-checker.ts
62465
- import { existsSync as existsSync30, mkdirSync as mkdirSync18, readFileSync as readFileSync31, unlinkSync as unlinkSync7, writeFileSync as writeFileSync19 } from "fs";
63542
+ import { existsSync as existsSync31, mkdirSync as mkdirSync18, readFileSync as readFileSync31, unlinkSync as unlinkSync7, writeFileSync as writeFileSync19 } from "fs";
62466
63543
  import { homedir as homedir35, platform as platform2, tmpdir } from "os";
62467
- import { join as join40 } from "path";
63544
+ import { join as join41 } from "path";
62468
63545
  function getCacheFilePath() {
62469
63546
  let cacheDir;
62470
63547
  if (isWindows) {
62471
- const localAppData = process.env.LOCALAPPDATA || join40(homedir35(), "AppData", "Local");
62472
- cacheDir = join40(localAppData, "claudish");
63548
+ const localAppData = process.env.LOCALAPPDATA || join41(homedir35(), "AppData", "Local");
63549
+ cacheDir = join41(localAppData, "claudish");
62473
63550
  } else {
62474
- cacheDir = join40(homedir35(), ".cache", "claudish");
63551
+ cacheDir = join41(homedir35(), ".cache", "claudish");
62475
63552
  }
62476
63553
  try {
62477
- if (!existsSync30(cacheDir)) {
63554
+ if (!existsSync31(cacheDir)) {
62478
63555
  mkdirSync18(cacheDir, { recursive: true });
62479
63556
  }
62480
- return join40(cacheDir, "update-check.json");
63557
+ return join41(cacheDir, "update-check.json");
62481
63558
  } catch {
62482
- return join40(tmpdir(), "claudish-update-check.json");
63559
+ return join41(tmpdir(), "claudish-update-check.json");
62483
63560
  }
62484
63561
  }
62485
63562
  function readCache2() {
62486
63563
  try {
62487
63564
  const cachePath = getCacheFilePath();
62488
- if (!existsSync30(cachePath)) {
63565
+ if (!existsSync31(cachePath)) {
62489
63566
  return null;
62490
63567
  }
62491
63568
  const data = JSON.parse(readFileSync31(cachePath, "utf-8"));
@@ -62511,7 +63588,7 @@ function isCacheValid(cache) {
62511
63588
  function clearCache() {
62512
63589
  try {
62513
63590
  const cachePath = getCacheFilePath();
62514
- if (existsSync30(cachePath)) {
63591
+ if (existsSync31(cachePath)) {
62515
63592
  unlinkSync7(cachePath);
62516
63593
  }
62517
63594
  } catch {}
@@ -62596,7 +63673,7 @@ var init_update_checker = __esm(() => {
62596
63673
  // src/cli.ts
62597
63674
  import {
62598
63675
  copyFileSync as copyFileSync2,
62599
- existsSync as existsSync31,
63676
+ existsSync as existsSync32,
62600
63677
  mkdirSync as mkdirSync19,
62601
63678
  readFileSync as readFileSync32,
62602
63679
  readdirSync as readdirSync7,
@@ -62604,14 +63681,14 @@ import {
62604
63681
  writeFileSync as writeFileSync20
62605
63682
  } from "fs";
62606
63683
  import { homedir as homedir36 } from "os";
62607
- import { dirname as dirname13, join as join41 } from "path";
63684
+ import { dirname as dirname13, join as join42 } from "path";
62608
63685
  import { fileURLToPath as fileURLToPath3 } from "url";
62609
63686
  function getVersion3() {
62610
63687
  return VERSION;
62611
63688
  }
62612
63689
  function clearAllModelCaches() {
62613
- const cacheDir = join41(homedir36(), ".claudish");
62614
- if (!existsSync31(cacheDir))
63690
+ const cacheDir = join42(homedir36(), ".claudish");
63691
+ if (!existsSync32(cacheDir))
62615
63692
  return;
62616
63693
  const cachePatterns = ["pricing-cache.json", "recommended-models-cache.json"];
62617
63694
  let cleared = 0;
@@ -62619,7 +63696,7 @@ function clearAllModelCaches() {
62619
63696
  const files = readdirSync7(cacheDir);
62620
63697
  for (const file of files) {
62621
63698
  if (cachePatterns.includes(file)) {
62622
- unlinkSync8(join41(cacheDir, file));
63699
+ unlinkSync8(join42(cacheDir, file));
62623
63700
  cleared++;
62624
63701
  }
62625
63702
  }
@@ -62774,6 +63851,10 @@ async function parseArgs(args) {
62774
63851
  if (config.logLevel === "info") {
62775
63852
  config.logLevel = "debug";
62776
63853
  }
63854
+ } else if (arg === "--recovery-ui" || arg === "--no-recovery-ui") {
63855
+ setRecoveryFlagOverrides({ recoveryUi: arg === "--recovery-ui" });
63856
+ } else if (arg === "--recovery" || arg === "--no-recovery") {
63857
+ setRecoveryFlagOverrides({ recovery: arg === "--recovery" });
62777
63858
  } else if (arg === "--no-debug-claudish") {
62778
63859
  config.debug = false;
62779
63860
  } else if (arg === "--log-debug") {
@@ -63093,8 +64174,8 @@ Usage: claudish --models --provider <slug>`);
63093
64174
  });
63094
64175
  config.resolvedDefaultProvider = resolved;
63095
64176
  if (resolved.legacyAutoPromoted && !config.quiet) {
63096
- const markerFile = join41(homedir36(), ".claudish", ".legacy-litellm-hint-shown");
63097
- if (!existsSync31(markerFile)) {
64177
+ const markerFile = join42(homedir36(), ".claudish", ".legacy-litellm-hint-shown");
64178
+ if (!existsSync32(markerFile)) {
63098
64179
  const hint = buildLegacyHint(resolved);
63099
64180
  if (hint) {
63100
64181
  console.error(hint);
@@ -64302,7 +65383,7 @@ ${h("MORE INFO")}
64302
65383
  }
64303
65384
  function printAIAgentGuide() {
64304
65385
  try {
64305
- const guidePath = join41(__dirname3, "../AI_AGENT_GUIDE.md");
65386
+ const guidePath = join42(__dirname3, "../AI_AGENT_GUIDE.md");
64306
65387
  const guideContent = readFileSync32(guidePath, "utf-8");
64307
65388
  console.log(guideContent);
64308
65389
  } catch (error) {
@@ -64319,19 +65400,19 @@ async function initializeClaudishSkill() {
64319
65400
  console.log(`\uD83D\uDD27 Initializing Claudish skill in current project...
64320
65401
  `);
64321
65402
  const cwd = process.cwd();
64322
- const claudeDir = join41(cwd, ".claude");
64323
- const skillsDir = join41(claudeDir, "skills");
64324
- const claudishSkillDir = join41(skillsDir, "claudish-usage");
64325
- const skillFile = join41(claudishSkillDir, "SKILL.md");
64326
- if (existsSync31(skillFile)) {
65403
+ const claudeDir = join42(cwd, ".claude");
65404
+ const skillsDir = join42(claudeDir, "skills");
65405
+ const claudishSkillDir = join42(skillsDir, "claudish-usage");
65406
+ const skillFile = join42(claudishSkillDir, "SKILL.md");
65407
+ if (existsSync32(skillFile)) {
64327
65408
  console.log("\u2705 Claudish skill already installed at:");
64328
65409
  console.log(` ${skillFile}
64329
65410
  `);
64330
65411
  console.log("\uD83D\uDCA1 To reinstall, delete the file and run 'claudish --init' again.");
64331
65412
  return;
64332
65413
  }
64333
- const sourceSkillPath = join41(__dirname3, "../skills/claudish-usage/SKILL.md");
64334
- if (!existsSync31(sourceSkillPath)) {
65414
+ const sourceSkillPath = join42(__dirname3, "../skills/claudish-usage/SKILL.md");
65415
+ if (!existsSync32(sourceSkillPath)) {
64335
65416
  console.error("\u274C Error: Claudish skill file not found in installation.");
64336
65417
  console.error(` Expected at: ${sourceSkillPath}`);
64337
65418
  console.error(`
@@ -64340,15 +65421,15 @@ async function initializeClaudishSkill() {
64340
65421
  process.exit(1);
64341
65422
  }
64342
65423
  try {
64343
- if (!existsSync31(claudeDir)) {
65424
+ if (!existsSync32(claudeDir)) {
64344
65425
  mkdirSync19(claudeDir, { recursive: true });
64345
65426
  console.log("\uD83D\uDCC1 Created .claude/ directory");
64346
65427
  }
64347
- if (!existsSync31(skillsDir)) {
65428
+ if (!existsSync32(skillsDir)) {
64348
65429
  mkdirSync19(skillsDir, { recursive: true });
64349
65430
  console.log("\uD83D\uDCC1 Created .claude/skills/ directory");
64350
65431
  }
64351
- if (!existsSync31(claudishSkillDir)) {
65432
+ if (!existsSync32(claudishSkillDir)) {
64352
65433
  mkdirSync19(claudishSkillDir, { recursive: true });
64353
65434
  console.log("\uD83D\uDCC1 Created .claude/skills/claudish-usage/ directory");
64354
65435
  }
@@ -64407,7 +65488,7 @@ function printAvailableModels() {
64407
65488
  var __filename3, __dirname3;
64408
65489
  var init_cli = __esm(() => {
64409
65490
  init_base_api_format();
64410
- init_config2();
65491
+ init_config();
64411
65492
  init_logger();
64412
65493
  init_model_loader();
64413
65494
  init_model_selector();
@@ -64425,6 +65506,7 @@ var init_cli = __esm(() => {
64425
65506
  init_provider_definitions();
64426
65507
  init_provider_slug_resolve();
64427
65508
  init_routing_rules();
65509
+ init_settings();
64428
65510
  init_ansi();
64429
65511
  init_provider_resolver();
64430
65512
  __filename3 = fileURLToPath3(import.meta.url);
@@ -71760,12 +72842,592 @@ var init_tui = __esm(() => {
71760
72842
  }
71761
72843
  });
71762
72844
 
72845
+ // src/launcher/magmux-wrapper.ts
72846
+ import {
72847
+ chmodSync,
72848
+ closeSync as closeSync8,
72849
+ existsSync as existsSync33,
72850
+ mkdirSync as mkdirSync20,
72851
+ mkdtempSync,
72852
+ openSync as openSync8,
72853
+ rmSync as rmSync2,
72854
+ writeFileSync as writeFileSync21
72855
+ } from "fs";
72856
+ import { connect as netConnect2 } from "net";
72857
+ import { join as join43 } from "path";
72858
+ function envDeltas(parentEnv, childEnv) {
72859
+ const set = [];
72860
+ const unset = [];
72861
+ for (const [k, v] of Object.entries(childEnv)) {
72862
+ if (STRIPPED_CHILD_VARS.includes(k))
72863
+ continue;
72864
+ if (parentEnv[k] !== v)
72865
+ set.push([k, v]);
72866
+ }
72867
+ for (const k of Object.keys(parentEnv)) {
72868
+ if (!(k in childEnv))
72869
+ unset.push(k);
72870
+ }
72871
+ for (const k of STRIPPED_CHILD_VARS) {
72872
+ if (!unset.includes(k))
72873
+ unset.push(k);
72874
+ }
72875
+ return { set, unset };
72876
+ }
72877
+ function shq(s) {
72878
+ return `'${s.replace(/'/g, "'\\''")}'`;
72879
+ }
72880
+ function buildLauncherScript(claudeBinary, claudeArgs, deltas) {
72881
+ const lines = [
72882
+ "#!/bin/sh",
72883
+ "# Generated by claudish. Re-asserts claudish's OWN environment deltas after",
72884
+ "# the login shell has run the user's profile, then hands the terminal to",
72885
+ "# Claude Code. Only deltas are written here: the full environment would put",
72886
+ "# every exported secret into a file."
72887
+ ];
72888
+ for (const k of deltas.unset)
72889
+ lines.push(`unset ${k}`);
72890
+ for (const [k, v] of deltas.set)
72891
+ lines.push(`export ${k}=${shq(v)}`);
72892
+ lines.push(`exec ${shq(claudeBinary)} ${claudeArgs.map(shq).join(" ")}`);
72893
+ return `${lines.join(`
72894
+ `)}
72895
+ `;
72896
+ }
72897
+ function magmuxPaneCapability(input) {
72898
+ const parentEnv = input.parentEnv ?? process.env;
72899
+ if (!input.interactive)
72900
+ return { kind: "none", reason: "not-interactive" };
72901
+ const ambient = parentEnv.MAGMUX_SOCK;
72902
+ if (ambient)
72903
+ return { kind: "ambient", sock: ambient };
72904
+ if (!input.stdoutIsTty)
72905
+ return { kind: "none", reason: "no-tty" };
72906
+ const magmux = input.magmuxBinary === undefined ? findMagmuxBinaryOrNull() : input.magmuxBinary;
72907
+ if (!magmux)
72908
+ return { kind: "none", reason: "no-magmux" };
72909
+ return { kind: "wrap", magmux };
72910
+ }
72911
+ function planMagmuxWrap(input) {
72912
+ const capability = magmuxPaneCapability(input);
72913
+ if (capability.kind !== "wrap")
72914
+ return null;
72915
+ const magmux = capability.magmux;
72916
+ const pid = input.pid ?? process.pid;
72917
+ const id = `claudish-${pid}`;
72918
+ const controlSocket = `/tmp/magmux-${id}.sock`;
72919
+ const tmpRoot = input.tmpRoot ?? "/tmp";
72920
+ mkdirSync20(tmpRoot, { recursive: true });
72921
+ const scriptDir = mkdtempSync(join43(tmpRoot, "claudish-launch-"));
72922
+ chmodSync(scriptDir, 448);
72923
+ const scriptPath = join43(scriptDir, "claude-launch.sh");
72924
+ const parentEnv = input.parentEnv ?? process.env;
72925
+ const deltas = envDeltas(parentEnv, input.childEnv);
72926
+ const fd = openSync8(scriptPath, "wx", 384);
72927
+ try {
72928
+ writeFileSync21(fd, buildLauncherScript(input.claudeBinary, input.claudeArgs, deltas));
72929
+ } finally {
72930
+ closeSync8(fd);
72931
+ }
72932
+ const env = { ...input.childEnv };
72933
+ for (const k of STRIPPED_CHILD_VARS)
72934
+ delete env[k];
72935
+ if (!env.MAGMUX_SCROLLBACK)
72936
+ env.MAGMUX_SCROLLBACK = "10000";
72937
+ const args = [
72938
+ "--id",
72939
+ id,
72940
+ "--no-idle-done",
72941
+ "-w",
72942
+ "--no-status",
72943
+ "-e",
72944
+ `. ${shq(scriptPath)}`
72945
+ ];
72946
+ let paneExit = null;
72947
+ const exitListeners = [];
72948
+ let control = null;
72949
+ let cleaned = false;
72950
+ const cleanup = () => {
72951
+ if (cleaned)
72952
+ return;
72953
+ cleaned = true;
72954
+ process.removeListener("exit", cleanup);
72955
+ try {
72956
+ control?.destroy();
72957
+ } catch {}
72958
+ control = null;
72959
+ try {
72960
+ rmSync2(scriptDir, { recursive: true, force: true });
72961
+ } catch {}
72962
+ try {
72963
+ if (existsSync33(controlSocket))
72964
+ rmSync2(controlSocket, { force: true });
72965
+ } catch {}
72966
+ };
72967
+ process.on("exit", cleanup);
72968
+ return {
72969
+ command: magmux,
72970
+ args,
72971
+ env,
72972
+ controlSocket,
72973
+ scriptPath,
72974
+ paneExitCode: () => paneExit,
72975
+ onClaudeExit(fn) {
72976
+ exitListeners.push(fn);
72977
+ },
72978
+ watch(proc) {
72979
+ let stopped = false;
72980
+ const attempt = async () => {
72981
+ for (let i = 0;i < 60 && !stopped; i++) {
72982
+ if (existsSync33(controlSocket)) {
72983
+ try {
72984
+ const s = await new Promise((resolve, reject) => {
72985
+ const sock = netConnect2(controlSocket);
72986
+ sock.once("connect", () => resolve(sock));
72987
+ sock.once("error", reject);
72988
+ });
72989
+ control = s;
72990
+ let buf = "";
72991
+ s.on("data", (chunk) => {
72992
+ buf += chunk.toString("utf-8");
72993
+ let nl = buf.indexOf(`
72994
+ `);
72995
+ while (nl >= 0) {
72996
+ const line = buf.slice(0, nl).trim();
72997
+ buf = buf.slice(nl + 1);
72998
+ nl = buf.indexOf(`
72999
+ `);
73000
+ if (!line)
73001
+ continue;
73002
+ try {
73003
+ const evt = JSON.parse(line);
73004
+ if (evt.type === "exit" && evt.pane === 0) {
73005
+ paneExit = typeof evt.exitCode === "number" ? evt.exitCode : null;
73006
+ for (const fn of exitListeners)
73007
+ fn(paneExit);
73008
+ }
73009
+ } catch {}
73010
+ }
73011
+ });
73012
+ s.on("error", () => {
73013
+ control = null;
73014
+ });
73015
+ return;
73016
+ } catch {}
73017
+ }
73018
+ await new Promise((r) => setTimeout(r, 50));
73019
+ }
73020
+ };
73021
+ proc.once("exit", () => {
73022
+ stopped = true;
73023
+ cleanup();
73024
+ });
73025
+ attempt();
73026
+ },
73027
+ cleanup
73028
+ };
73029
+ }
73030
+ var STRIPPED_CHILD_VARS;
73031
+ var init_magmux_wrapper = __esm(() => {
73032
+ init_magmux_binary();
73033
+ STRIPPED_CHILD_VARS = ["CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION"];
73034
+ });
73035
+
73036
+ // src/recovery/types.ts
73037
+ var UI_LEASE_MS = 1e4, FRAME_TICK_MS = 1000;
73038
+
73039
+ // src/recovery/magmux-ui.ts
73040
+ import { execFile as execFile2 } from "child_process";
73041
+ import { existsSync as existsSync34 } from "fs";
73042
+ import { connect as netConnect3 } from "net";
73043
+ import { promisify as promisify5 } from "util";
73044
+ function resolveControlEndpoint() {
73045
+ if (explicitControlSock && existsSync34(explicitControlSock)) {
73046
+ return { sock: explicitControlSock, wrapped: true };
73047
+ }
73048
+ const ambient = process.env.MAGMUX_SOCK;
73049
+ if (ambient && existsSync34(ambient))
73050
+ return { sock: ambient, wrapped: false };
73051
+ return null;
73052
+ }
73053
+ function noteUnavailable(message) {
73054
+ if (state.unavailableLogged)
73055
+ return;
73056
+ state.unavailableLogged = true;
73057
+ log(message);
73058
+ }
73059
+ function uiLeaseValid2(episodeId) {
73060
+ if (!state.control || state.targetPane === null)
73061
+ return false;
73062
+ const last = state.leases.get(episodeId);
73063
+ if (last === undefined)
73064
+ return false;
73065
+ return recoveryClock().now() - last < UI_LEASE_MS;
73066
+ }
73067
+ async function connectControl(sock) {
73068
+ try {
73069
+ return await new Promise((resolve, reject) => {
73070
+ const s = netConnect3(sock);
73071
+ s.once("connect", () => resolve(s));
73072
+ s.once("error", reject);
73073
+ });
73074
+ } catch {
73075
+ return null;
73076
+ }
73077
+ }
73078
+ function request(socket, msg, timeoutMs = CONTROL_TIMEOUT_MS) {
73079
+ const id = state.nextControlId++;
73080
+ return new Promise((resolve) => {
73081
+ let buf = "";
73082
+ let settled = false;
73083
+ const finish = (v) => {
73084
+ if (settled)
73085
+ return;
73086
+ settled = true;
73087
+ socket.removeListener("data", onData);
73088
+ clearTimeout(timer);
73089
+ resolve(v);
73090
+ };
73091
+ const onData = (chunk) => {
73092
+ buf += chunk.toString("utf-8");
73093
+ let nl = buf.indexOf(`
73094
+ `);
73095
+ while (nl >= 0) {
73096
+ const line = buf.slice(0, nl).trim();
73097
+ buf = buf.slice(nl + 1);
73098
+ nl = buf.indexOf(`
73099
+ `);
73100
+ if (!line)
73101
+ continue;
73102
+ try {
73103
+ const evt = JSON.parse(line);
73104
+ if (evt.type === "reply" && evt.id === id)
73105
+ finish(evt);
73106
+ } catch {}
73107
+ }
73108
+ };
73109
+ const timer = setTimeout(() => finish(null), timeoutMs);
73110
+ timer.unref?.();
73111
+ socket.on("data", onData);
73112
+ try {
73113
+ socket.write(`${JSON.stringify({ ...msg, id })}
73114
+ `);
73115
+ } catch {
73116
+ finish(null);
73117
+ }
73118
+ });
73119
+ }
73120
+ function dropConnection() {
73121
+ state.control = null;
73122
+ state.targetPane = null;
73123
+ state.leases.clear();
73124
+ }
73125
+ async function ancestorPids(maxDepth = 6) {
73126
+ const out = new Set([process.pid]);
73127
+ let pid = process.ppid;
73128
+ for (let i = 0;i < maxDepth && pid > 1; i++) {
73129
+ out.add(pid);
73130
+ try {
73131
+ const { stdout } = await execFileAsync("ps", ["-o", "ppid=", "-p", String(pid)], {
73132
+ timeout: 1000
73133
+ });
73134
+ const next = Number(stdout.trim());
73135
+ if (!Number.isFinite(next) || next <= 1)
73136
+ break;
73137
+ pid = next;
73138
+ } catch {
73139
+ break;
73140
+ }
73141
+ }
73142
+ return out;
73143
+ }
73144
+ async function findOwnPane(socket) {
73145
+ const reply = await request(socket, { type: "list" });
73146
+ const result = reply?.result;
73147
+ const panes = Array.isArray(result) ? result : Array.isArray(result?.panes) ? result.panes : [];
73148
+ const ancestors = await ancestorPids();
73149
+ for (const p of panes) {
73150
+ const pane = p.pane;
73151
+ const pid = p.pid;
73152
+ if (typeof pane === "number" && typeof pid === "number" && ancestors.has(pid))
73153
+ return pane;
73154
+ }
73155
+ return null;
73156
+ }
73157
+ function ensureConnected() {
73158
+ if (state.control && state.targetPane !== null)
73159
+ return Promise.resolve(true);
73160
+ if (state.paneNotFound)
73161
+ return Promise.resolve(false);
73162
+ if (state.connecting)
73163
+ return state.connecting;
73164
+ state.connecting = (async () => {
73165
+ const endpoint = resolveControlEndpoint();
73166
+ if (!endpoint) {
73167
+ noteUnavailable("[Recovery] no magmux control endpoint \u2014 no banner; recovery continues without one");
73168
+ return false;
73169
+ }
73170
+ const socket = await connectControl(endpoint.sock);
73171
+ if (!socket) {
73172
+ noteUnavailable(`[Recovery] could not reach magmux at ${endpoint.sock} \u2014 no banner`);
73173
+ return false;
73174
+ }
73175
+ const onGone = () => {
73176
+ if (state.control === socket)
73177
+ dropConnection();
73178
+ };
73179
+ socket.on("error", onGone);
73180
+ socket.on("close", onGone);
73181
+ state.control = socket;
73182
+ const pane = endpoint.wrapped ? 0 : await findOwnPane(socket);
73183
+ if (pane === null) {
73184
+ state.paneNotFound = true;
73185
+ log("[Recovery] this claudish's pane was not found in magmux \u2014 no banner");
73186
+ try {
73187
+ socket.end();
73188
+ } catch {}
73189
+ dropConnection();
73190
+ return false;
73191
+ }
73192
+ state.targetPane = pane;
73193
+ state.unavailableLogged = false;
73194
+ log(`[Recovery] banner will draw on magmux pane ${pane} ` + `(${endpoint.wrapped ? "magmux launched by claudish" : "ambient magmux"})`);
73195
+ return true;
73196
+ })().finally(() => {
73197
+ state.connecting = null;
73198
+ });
73199
+ return state.connecting;
73200
+ }
73201
+ function formatElapsed2(ms) {
73202
+ const s = Math.max(0, Math.round(ms / 1000));
73203
+ if (s < 60)
73204
+ return `${s}s`;
73205
+ const m = Math.floor(s / 60);
73206
+ if (m < 60)
73207
+ return `${m}m ${String(s % 60).padStart(2, "0")}s`;
73208
+ return `${Math.floor(m / 60)}h ${m % 60}m`;
73209
+ }
73210
+ function wrap(text, cols) {
73211
+ const out = [];
73212
+ let line = "";
73213
+ for (const word of text.split(/\s+/).filter(Boolean)) {
73214
+ if (line && line.length + 1 + word.length > cols) {
73215
+ out.push(line);
73216
+ line = word;
73217
+ } else {
73218
+ line = line ? `${line} ${word}` : word;
73219
+ }
73220
+ }
73221
+ if (line)
73222
+ out.push(line);
73223
+ return out;
73224
+ }
73225
+ function bannerText(frame, nowMs) {
73226
+ const lines = [`\u2717 NETWORK \xB7 ${frame.providerDisplayName} ${KIND_WORD[frame.kind]}`];
73227
+ lines.push(...wrap(frame.reason, OVERLAY_WRAP_COLS));
73228
+ lines.push(`${frame.host} \xB7 ${frame.code ?? frame.kind}${frame.loopback ? " \xB7 local" : ""}`);
73229
+ const elapsed = formatElapsed2(nowMs - frame.startedAtMs);
73230
+ const attempt = Math.max(1, frame.attempts);
73231
+ if (frame.state === "handoff") {
73232
+ lines.push(`handed back to Claude Code \xB7 waiting for its retry \xB7 ${elapsed} in recovery`);
73233
+ } else if (frame.nextAttemptAtMs === null) {
73234
+ lines.push(`connecting to ${frame.host}\u2026 \xB7 attempt ${attempt} \xB7 ${elapsed} in recovery`);
73235
+ } else {
73236
+ const secs = Math.max(0, Math.ceil((frame.nextAttemptAtMs - nowMs) / 1000));
73237
+ lines.push(`next attempt in ${secs}s \xB7 attempt ${attempt} \xB7 ${elapsed} in recovery`);
73238
+ }
73239
+ const counts = [];
73240
+ if (frame.waiters > 0)
73241
+ counts.push(`${frame.waiters} request${frame.waiters === 1 ? "" : "s"} held`);
73242
+ if (frame.otherEpisodes > 0) {
73243
+ counts.push(`+${frame.otherEpisodes} more outage${frame.otherEpisodes === 1 ? "" : "s"}`);
73244
+ }
73245
+ if (counts.length > 0)
73246
+ lines.push(counts.join(" \xB7 "));
73247
+ lines.push("Esc in Claude Code stops the turn");
73248
+ return lines.join(`
73249
+ `);
73250
+ }
73251
+ function recoveredText(frame, nowMs) {
73252
+ return [
73253
+ `\u2713 NETWORK \xB7 ${frame.providerDisplayName} recovered`,
73254
+ `${frame.attempts} attempt${frame.attempts === 1 ? "" : "s"} over ${formatElapsed2(nowMs - frame.startedAtMs)}`
73255
+ ].join(`
73256
+ `);
73257
+ }
73258
+ async function paint2(frame) {
73259
+ if (state.painting)
73260
+ return;
73261
+ state.painting = true;
73262
+ try {
73263
+ if (!await ensureConnected())
73264
+ return;
73265
+ const socket = state.control;
73266
+ const pane = state.targetPane;
73267
+ if (!socket || pane === null)
73268
+ return;
73269
+ const reply = await request(socket, {
73270
+ type: "overlay",
73271
+ pane,
73272
+ text: bannerText(frame, Date.now()),
73273
+ style: "error"
73274
+ });
73275
+ const ok = reply?.ok === true;
73276
+ if (ok && !episodeIsLive(frame.episodeId)) {
73277
+ state.lastPaintOk = ok;
73278
+ return;
73279
+ }
73280
+ if (ok) {
73281
+ const first = !state.leases.has(frame.episodeId);
73282
+ state.leases.set(frame.episodeId, recoveryClock().now());
73283
+ state.lastFrame = frame;
73284
+ if (first) {
73285
+ log(`[Recovery] banner painted for episode ${frame.episodeId} on magmux pane ${pane} ` + "\u2014 lease granted");
73286
+ }
73287
+ request(socket, { type: "tint", pane, color: "red" });
73288
+ } else if (state.lastPaintOk !== false) {
73289
+ log(`[Recovery] magmux did not acknowledge the banner: ${JSON.stringify(reply)}`);
73290
+ }
73291
+ state.lastPaintOk = ok;
73292
+ } finally {
73293
+ state.painting = false;
73294
+ }
73295
+ }
73296
+ async function writeOverlay(text, style, tint) {
73297
+ const socket = state.control;
73298
+ const pane = state.targetPane;
73299
+ if (!socket || pane === null)
73300
+ return;
73301
+ await request(socket, { type: "overlay", pane, text, style });
73302
+ await request(socket, { type: "tint", pane, color: tint });
73303
+ }
73304
+ function clearOverlay() {
73305
+ return writeOverlay("", "", "reset");
73306
+ }
73307
+ function stopTick() {
73308
+ if (state.tickTimer !== null) {
73309
+ recoveryClock().clearTimeout(state.tickTimer);
73310
+ state.tickTimer = null;
73311
+ }
73312
+ }
73313
+ function cancelLinger() {
73314
+ if (state.lingerTimer !== null) {
73315
+ recoveryClock().clearTimeout(state.lingerTimer);
73316
+ state.lingerTimer = null;
73317
+ }
73318
+ }
73319
+ function startTick() {
73320
+ if (state.tickTimer !== null)
73321
+ return;
73322
+ const clock = recoveryClock();
73323
+ const loop = () => {
73324
+ state.tickTimer = null;
73325
+ const frame = renderableEpisodeFrame();
73326
+ if (!frame)
73327
+ return;
73328
+ paint2(frame);
73329
+ const t = clock.setTimeout(loop, FRAME_TICK_MS);
73330
+ clock.unref?.(t);
73331
+ state.tickTimer = t;
73332
+ };
73333
+ loop();
73334
+ }
73335
+ function onEpisodeOpened(episodeId) {
73336
+ markPaneRequested(episodeId);
73337
+ cancelLinger();
73338
+ startTick();
73339
+ }
73340
+ function onEpisodeClosed(episodeId, outcome, attempts) {
73341
+ state.leases.delete(episodeId);
73342
+ if (renderableEpisodeFrame() !== null)
73343
+ return;
73344
+ stopTick();
73345
+ const last = state.lastFrame?.episodeId === episodeId ? state.lastFrame : null;
73346
+ state.lastFrame = null;
73347
+ if (outcome !== "recovered" || !last) {
73348
+ clearOverlay();
73349
+ return;
73350
+ }
73351
+ writeOverlay(recoveredText({ ...last, attempts }, Date.now()), "success", "green");
73352
+ cancelLinger();
73353
+ const clock = recoveryClock();
73354
+ const timer = clock.setTimeout(() => {
73355
+ state.lingerTimer = null;
73356
+ if (renderableEpisodeFrame() !== null)
73357
+ return;
73358
+ clearOverlay();
73359
+ }, OUTCOME_LINGER_MS);
73360
+ clock.unref?.(timer);
73361
+ state.lingerTimer = timer;
73362
+ }
73363
+ function installRecoveryUi(controlSock) {
73364
+ explicitControlSock = controlSock;
73365
+ if (installed)
73366
+ return;
73367
+ installed = true;
73368
+ registerRecoveryUi(hooks);
73369
+ }
73370
+ async function shutdownRecoveryUi() {
73371
+ stopTick();
73372
+ cancelLinger();
73373
+ if (state.control && state.targetPane !== null) {
73374
+ const socket = state.control;
73375
+ const pane = state.targetPane;
73376
+ await request(socket, { type: "overlay", pane, text: "", style: "" }, 500);
73377
+ await request(socket, { type: "tint", pane, color: "reset" }, 500);
73378
+ }
73379
+ if (state.control) {
73380
+ try {
73381
+ state.control.end();
73382
+ } catch {}
73383
+ }
73384
+ dropConnection();
73385
+ state.lastFrame = null;
73386
+ state.lastPaintOk = null;
73387
+ state.unavailableLogged = false;
73388
+ state.paneNotFound = false;
73389
+ registerRecoveryUi(null);
73390
+ installed = false;
73391
+ explicitControlSock = null;
73392
+ }
73393
+ var execFileAsync, OUTCOME_LINGER_MS = 4000, OVERLAY_WRAP_COLS = 60, CONTROL_TIMEOUT_MS = 3000, explicitControlSock = null, state, installed = false, KIND_WORD, hooks;
73394
+ var init_magmux_ui = __esm(() => {
73395
+ init_logger();
73396
+ init_clock();
73397
+ init_coordinator();
73398
+ execFileAsync = promisify5(execFile2);
73399
+ state = {
73400
+ control: null,
73401
+ connecting: null,
73402
+ targetPane: null,
73403
+ leases: new Map,
73404
+ lastFrame: null,
73405
+ tickTimer: null,
73406
+ lingerTimer: null,
73407
+ painting: false,
73408
+ lastPaintOk: null,
73409
+ unavailableLogged: false,
73410
+ paneNotFound: false,
73411
+ nextControlId: 1
73412
+ };
73413
+ KIND_WORD = {
73414
+ refused: "refused",
73415
+ dns: "not found",
73416
+ unreachable: "unreachable"
73417
+ };
73418
+ hooks = {
73419
+ onEpisodeOpened,
73420
+ onEpisodeClosed,
73421
+ leaseValid: (episodeId) => uiLeaseValid2(episodeId)
73422
+ };
73423
+ });
73424
+
71763
73425
  // src/terminal-isolation.ts
71764
73426
  import { format } from "util";
71765
73427
  function beginTerminalIsolation(onSuppressed) {
71766
- if (active)
73428
+ if (active2)
71767
73429
  return () => {};
71768
- active = true;
73430
+ active2 = true;
71769
73431
  const emit = (source, text) => {
71770
73432
  if (routing)
71771
73433
  return;
@@ -71795,17 +73457,17 @@ function beginTerminalIsolation(onSuppressed) {
71795
73457
  process.stdout.write = makeWrite("stdout");
71796
73458
  process.stderr.write = makeWrite("stderr");
71797
73459
  return function restore() {
71798
- if (!active)
73460
+ if (!active2)
71799
73461
  return;
71800
73462
  for (const method of CONSOLE_METHODS) {
71801
73463
  console[method] = originalConsole[method];
71802
73464
  }
71803
73465
  process.stdout.write = originalStdoutWrite;
71804
73466
  process.stderr.write = originalStderrWrite;
71805
- active = false;
73467
+ active2 = false;
71806
73468
  };
71807
73469
  }
71808
- var CONSOLE_METHODS, active = false, routing = false;
73470
+ var CONSOLE_METHODS, active2 = false, routing = false;
71809
73471
  var init_terminal_isolation = __esm(() => {
71810
73472
  CONSOLE_METHODS = ["log", "error", "warn", "info", "debug", "trace", "dir"];
71811
73473
  });
@@ -71813,18 +73475,18 @@ var init_terminal_isolation = __esm(() => {
71813
73475
  // src/claude-runner.ts
71814
73476
  import { spawn as spawn5, spawnSync as spawnSync4 } from "child_process";
71815
73477
  import {
71816
- closeSync as closeSync8,
71817
- existsSync as existsSync32,
71818
- mkdirSync as mkdirSync20,
71819
- openSync as openSync8,
73478
+ closeSync as closeSync9,
73479
+ existsSync as existsSync35,
73480
+ mkdirSync as mkdirSync21,
73481
+ openSync as openSync9,
71820
73482
  readFileSync as readFileSync33,
71821
73483
  readdirSync as readdirSync8,
71822
73484
  statSync as statSync8,
71823
73485
  unlinkSync as unlinkSync9,
71824
- writeFileSync as writeFileSync21
73486
+ writeFileSync as writeFileSync22
71825
73487
  } from "fs";
71826
73488
  import { homedir as homedir37, tmpdir as tmpdir2 } from "os";
71827
- import { dirname as dirname14, join as join42 } from "path";
73489
+ import { dirname as dirname14, join as join44 } from "path";
71828
73490
  import { isatty } from "tty";
71829
73491
  function releaseTerminalIsolation() {
71830
73492
  if (!restoreTerminal)
@@ -71884,12 +73546,12 @@ function isRealAnthropicEnvCredential(env, name) {
71884
73546
  }
71885
73547
  function hasResolvableAnthropicAuth(deps = {}) {
71886
73548
  const env = deps.env ?? process.env;
71887
- const fileExists = deps.fileExists ?? existsSync32;
73549
+ const fileExists = deps.fileExists ?? existsSync35;
71888
73550
  const keychainProbe = deps.keychainProbe ?? defaultKeychainAnthropicProbe;
71889
73551
  if (isRealAnthropicEnvCredential(env, "ANTHROPIC_API_KEY") || isRealAnthropicEnvCredential(env, "ANTHROPIC_AUTH_TOKEN")) {
71890
73552
  return true;
71891
73553
  }
71892
- if (fileExists(join42(homedir37(), ".claude", ".credentials.json")))
73554
+ if (fileExists(join44(homedir37(), ".claude", ".credentials.json")))
71893
73555
  return true;
71894
73556
  return keychainProbe();
71895
73557
  }
@@ -71904,7 +73566,7 @@ function isProxyAuthMode(config) {
71904
73566
  }
71905
73567
  function managedSettingsPath() {
71906
73568
  if (isWindows2()) {
71907
- return join42(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
73569
+ return join44(process.env.PROGRAMDATA || "C:\\ProgramData", "ClaudeCode", "managed-settings.json");
71908
73570
  }
71909
73571
  if (process.platform === "darwin") {
71910
73572
  return "/Library/Application Support/ClaudeCode/managed-settings.json";
@@ -71925,9 +73587,9 @@ function isWindows2() {
71925
73587
  }
71926
73588
  function createStatusLineScript(tokenFilePath) {
71927
73589
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir2();
71928
- const claudishDir = join42(homeDir, ".claudish");
73590
+ const claudishDir = join44(homeDir, ".claudish");
71929
73591
  const timestamp = Date.now();
71930
- const scriptPath = join42(claudishDir, `status-${timestamp}.js`);
73592
+ const scriptPath = join44(claudishDir, `status-${timestamp}.js`);
71931
73593
  const escapedTokenPath = tokenFilePath.replace(/\\/g, "\\\\");
71932
73594
  const light = getThemeMode() === "light";
71933
73595
  const cyanCode = light ? "38;2;14;116;144" : "96";
@@ -72060,13 +73722,13 @@ process.stdin.on('end', () => {
72060
73722
  }
72061
73723
  });
72062
73724
  `;
72063
- writeFileSync21(scriptPath, script, "utf-8");
73725
+ writeFileSync22(scriptPath, script, "utf-8");
72064
73726
  return scriptPath;
72065
73727
  }
72066
73728
  function initializeTokenFile(tokenFilePath) {
72067
73729
  try {
72068
- mkdirSync20(dirname14(tokenFilePath), { recursive: true });
72069
- writeFileSync21(tokenFilePath, JSON.stringify({
73730
+ mkdirSync21(dirname14(tokenFilePath), { recursive: true });
73731
+ writeFileSync22(tokenFilePath, JSON.stringify({
72070
73732
  input_tokens: 0,
72071
73733
  output_tokens: 0,
72072
73734
  total_tokens: 0,
@@ -72097,7 +73759,7 @@ function cleanupStaleTokenFiles(dir, now = Date.now(), maxAgeMs = STALE_TOKEN_FI
72097
73759
  if (!name.startsWith("tokens-") || !name.endsWith(".json"))
72098
73760
  continue;
72099
73761
  scanned++;
72100
- const full = join42(dir, name);
73762
+ const full = join44(dir, name);
72101
73763
  try {
72102
73764
  if (statSync8(full).mtimeMs >= cutoff)
72103
73765
  continue;
@@ -72126,13 +73788,13 @@ function parseSettingsArgSafe(value) {
72126
73788
  }
72127
73789
  function userSettingsFileCandidates(cwd) {
72128
73790
  return [
72129
- join42(homedir37(), ".claude", "settings.json"),
72130
- join42(cwd, ".claude", "settings.json"),
72131
- join42(cwd, ".claude", "settings.local.json")
73791
+ join44(homedir37(), ".claude", "settings.json"),
73792
+ join44(cwd, ".claude", "settings.json"),
73793
+ join44(cwd, ".claude", "settings.local.json")
72132
73794
  ];
72133
73795
  }
72134
73796
  function discoverUserStatusLineCommand(claudeArgs = [], cwd = process.cwd()) {
72135
- const sources = userSettingsFileCandidates(cwd).filter((file) => existsSync32(file));
73797
+ const sources = userSettingsFileCandidates(cwd).filter((file) => existsSync35(file));
72136
73798
  const idx = claudeArgs.indexOf("--settings");
72137
73799
  const settingsArg = idx === -1 ? undefined : claudeArgs[idx + 1];
72138
73800
  if (settingsArg)
@@ -72169,13 +73831,13 @@ function buildChainedStatusCommand(userCommand, claudishBody, claudishSegment) {
72169
73831
  }
72170
73832
  function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLineCommand) {
72171
73833
  const homeDir = process.env.HOME || process.env.USERPROFILE || tmpdir2();
72172
- const claudishDir = join42(homeDir, ".claudish");
73834
+ const claudishDir = join44(homeDir, ".claudish");
72173
73835
  try {
72174
- mkdirSync20(claudishDir, { recursive: true });
73836
+ mkdirSync21(claudishDir, { recursive: true });
72175
73837
  } catch {}
72176
73838
  const timestamp = Date.now();
72177
- const tempPath = join42(claudishDir, `settings-${timestamp}.json`);
72178
- const tokenFilePath = join42(claudishDir, `tokens-${port}.json`);
73839
+ const tempPath = join44(claudishDir, `settings-${timestamp}.json`);
73840
+ const tokenFilePath = join44(claudishDir, `tokens-${port}.json`);
72179
73841
  cleanupStaleTokenFiles(claudishDir);
72180
73842
  initializeTokenFile(tokenFilePath);
72181
73843
  let statusCommand;
@@ -72209,7 +73871,7 @@ function createTempSettingsFile(_modelDisplay, port, proxyAuthMode, userStatusLi
72209
73871
  padding: 0
72210
73872
  };
72211
73873
  const settings = buildClaudishSettingsOverlay(statusLine, proxyAuthMode);
72212
- writeFileSync21(tempPath, JSON.stringify(settings, null, 2), "utf-8");
73874
+ writeFileSync22(tempPath, JSON.stringify(settings, null, 2), "utf-8");
72213
73875
  return { path: tempPath, statusLine, tokenFilePath };
72214
73876
  }
72215
73877
  function buildClaudishSettingsOverlay(statusLine, proxyAuthMode) {
@@ -72234,7 +73896,7 @@ function mergeUserSettingsIfPresent(config, tempSettingsPath, statusLine, proxyA
72234
73896
  if (proxyAuthMode && !("forceLoginMethod" in userSettings)) {
72235
73897
  userSettings.forceLoginMethod = "console";
72236
73898
  }
72237
- writeFileSync21(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
73899
+ writeFileSync22(tempSettingsPath, JSON.stringify(userSettings, null, 2), "utf-8");
72238
73900
  } catch {
72239
73901
  if (!config.quiet) {
72240
73902
  console.warn(`[claudish] Warning: could not merge user settings: ${userSettingsValue}`);
@@ -72305,13 +73967,13 @@ function resolveAdvisorToolEnv(config, processEnv = process.env) {
72305
73967
  return { vars: { [ADVISOR_TOOL_ENV_VAR]: "1" }, source: "claudish" };
72306
73968
  }
72307
73969
  function discoverUserAdvisorModel(claudeArgs = [], cwd = process.cwd()) {
72308
- const sources = userSettingsFileCandidates(cwd).filter((file) => existsSync32(file));
73970
+ const sources = userSettingsFileCandidates(cwd).filter((file) => existsSync35(file));
72309
73971
  const idx = claudeArgs.indexOf("--settings");
72310
73972
  const settingsArg = idx === -1 ? undefined : claudeArgs[idx + 1];
72311
73973
  if (settingsArg)
72312
73974
  sources.push(settingsArg);
72313
73975
  const managed = managedSettingsPath();
72314
- if (existsSync32(managed))
73976
+ if (existsSync35(managed))
72315
73977
  sources.push(managed);
72316
73978
  let effective;
72317
73979
  for (const source of sources) {
@@ -72434,6 +74096,11 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
72434
74096
  [ENV.CLAUDISH_TOKEN_FILE]: tokenFilePath,
72435
74097
  ...advisorToolEnv.vars
72436
74098
  };
74099
+ const paneCapability = magmuxPaneCapability({
74100
+ interactive: Boolean(config.interactive),
74101
+ stdoutIsTty: Boolean(process.stdout.isTTY)
74102
+ });
74103
+ applyRetryWatchdog(env, { paneEligible: paneCapability.kind !== "none" });
72437
74104
  if (modelId) {
72438
74105
  const parsedSpec = parseModelSpec(modelId);
72439
74106
  const providerDisplayName = parsedSpec.isExplicitProvider ? getProviderByName(parsedSpec.provider)?.displayName : undefined;
@@ -72526,7 +74193,7 @@ async function runClaudeWithProxy(config, proxyUrl, onCleanup) {
72526
74193
  console.error(`
72527
74194
  Or set CLAUDE_PATH to your custom installation:`);
72528
74195
  const home = homedir37();
72529
- const localPath = isWindows2() ? join42(home, ".claude", "local", "claude.exe") : join42(home, ".claude", "local", "claude");
74196
+ const localPath = isWindows2() ? join44(home, ".claude", "local", "claude.exe") : join44(home, ".claude", "local", "claude");
72530
74197
  console.error(` export CLAUDE_PATH=${localPath}`);
72531
74198
  process.exit(1);
72532
74199
  }
@@ -72537,11 +74204,11 @@ Or set CLAUDE_PATH to your custom installation:`);
72537
74204
  const childWantsTty = config.interactive && !process.stdout.isTTY && Boolean(process.stdin.isTTY);
72538
74205
  if (childWantsTty) {
72539
74206
  try {
72540
- const fd = openSync8("/dev/fd/0", "r+");
74207
+ const fd = openSync9("/dev/fd/0", "r+");
72541
74208
  if (isatty(fd)) {
72542
74209
  ttyFd = fd;
72543
74210
  } else {
72544
- closeSync8(fd);
74211
+ closeSync9(fd);
72545
74212
  }
72546
74213
  } catch {
72547
74214
  ttyFd = undefined;
@@ -72551,11 +74218,29 @@ Or set CLAUDE_PATH to your custom installation:`);
72551
74218
  }
72552
74219
  const filterChildStderr = !config.interactive && ttyFd === undefined;
72553
74220
  const stdio = ttyFd !== undefined ? [0, ttyFd, ttyFd] : filterChildStderr ? ["inherit", "inherit", "pipe"] : "inherit";
72554
- const proc = spawn5(spawnCommand, claudeArgs, {
74221
+ const wrap = recoverySurfaceAllowed() && paneCapability.kind === "wrap" ? planMagmuxWrap({
74222
+ claudeBinary,
74223
+ claudeArgs,
74224
+ childEnv: env,
74225
+ cwd: process.cwd(),
74226
+ interactive: Boolean(config.interactive),
74227
+ stdoutIsTty: Boolean(process.stdout.isTTY),
74228
+ magmuxBinary: paneCapability.magmux
74229
+ }) : null;
74230
+ const proc = wrap ? spawn5(wrap.command, wrap.args, { env: wrap.env, stdio, shell: false }) : spawn5(spawnCommand, claudeArgs, {
72555
74231
  env,
72556
74232
  stdio,
72557
74233
  shell: needsShell
72558
74234
  });
74235
+ if (wrap) {
74236
+ wrap.watch(proc);
74237
+ installRecoveryUi(wrap.controlSocket);
74238
+ wrap.onClaudeExit(() => {
74239
+ shutdownRecoveryUi();
74240
+ });
74241
+ } else if (recoverySurfaceAllowed() && config.interactive && process.env.MAGMUX_SOCK) {
74242
+ installRecoveryUi(null);
74243
+ }
72559
74244
  if (filterChildStderr && proc.stderr) {
72560
74245
  relayChildStderr(proc.stderr);
72561
74246
  }
@@ -72568,7 +74253,7 @@ Or set CLAUDE_PATH to your custom installation:`);
72568
74253
  const fdToClose = ttyFd;
72569
74254
  proc.on("spawn", () => {
72570
74255
  try {
72571
- closeSync8(fdToClose);
74256
+ closeSync9(fdToClose);
72572
74257
  } catch {}
72573
74258
  });
72574
74259
  }
@@ -72576,12 +74261,14 @@ Or set CLAUDE_PATH to your custom installation:`);
72576
74261
  const { exitCode, exitSignal } = await new Promise((resolve) => {
72577
74262
  proc.on("exit", (code, signal) => {
72578
74263
  setClaudeCodeRunning(false);
74264
+ const paneExit = wrap?.paneExitCode() ?? null;
72579
74265
  resolve({
72580
- exitCode: signal ? 128 + (SIGNAL_EXIT_NUMBERS[signal] ?? 0) : code ?? 1,
74266
+ exitCode: paneExit !== null ? paneExit : signal ? 128 + (SIGNAL_EXIT_NUMBERS[signal] ?? 0) : code ?? 1,
72581
74267
  exitSignal: signal
72582
74268
  });
72583
74269
  });
72584
74270
  });
74271
+ wrap?.cleanup();
72585
74272
  log(exitSignal ? `[Claude Code] Exited from ${exitSignal} (exit code ${exitCode})` : `[Claude Code] Exited with code ${exitCode}`);
72586
74273
  releaseTerminalIsolation();
72587
74274
  try {
@@ -72614,23 +74301,23 @@ function setupSignalHandlers(proc, tempSettingsPath, quiet, onCleanup) {
72614
74301
  async function findClaudeBinary() {
72615
74302
  const isWindows = process.platform === "win32";
72616
74303
  if (process.env.CLAUDE_PATH) {
72617
- if (existsSync32(process.env.CLAUDE_PATH)) {
74304
+ if (existsSync35(process.env.CLAUDE_PATH)) {
72618
74305
  return process.env.CLAUDE_PATH;
72619
74306
  }
72620
74307
  }
72621
74308
  const home = homedir37();
72622
- const localPath = isWindows ? join42(home, ".claude", "local", "claude.exe") : join42(home, ".claude", "local", "claude");
72623
- if (existsSync32(localPath)) {
74309
+ const localPath = isWindows ? join44(home, ".claude", "local", "claude.exe") : join44(home, ".claude", "local", "claude");
74310
+ if (existsSync35(localPath)) {
72624
74311
  return localPath;
72625
74312
  }
72626
74313
  if (isWindows) {
72627
74314
  const windowsPaths = [
72628
- join42(home, "AppData", "Roaming", "npm", "claude.cmd"),
72629
- join42(home, ".npm-global", "claude.cmd"),
72630
- join42(home, "node_modules", ".bin", "claude.cmd")
74315
+ join44(home, "AppData", "Roaming", "npm", "claude.cmd"),
74316
+ join44(home, ".npm-global", "claude.cmd"),
74317
+ join44(home, "node_modules", ".bin", "claude.cmd")
72631
74318
  ];
72632
74319
  for (const path of windowsPaths) {
72633
- if (existsSync32(path)) {
74320
+ if (existsSync35(path)) {
72634
74321
  return path;
72635
74322
  }
72636
74323
  }
@@ -72638,14 +74325,14 @@ async function findClaudeBinary() {
72638
74325
  const commonPaths = [
72639
74326
  "/usr/local/bin/claude",
72640
74327
  "/opt/homebrew/bin/claude",
72641
- join42(home, ".npm-global/bin/claude"),
72642
- join42(home, ".local/bin/claude"),
72643
- join42(home, "node_modules/.bin/claude"),
74328
+ join44(home, ".npm-global/bin/claude"),
74329
+ join44(home, ".local/bin/claude"),
74330
+ join44(home, "node_modules/.bin/claude"),
72644
74331
  "/data/data/com.termux/files/usr/bin/claude",
72645
- join42(home, "../usr/bin/claude")
74332
+ join44(home, "../usr/bin/claude")
72646
74333
  ];
72647
74334
  for (const path of commonPaths) {
72648
- if (existsSync32(path)) {
74335
+ if (existsSync35(path)) {
72649
74336
  return path;
72650
74337
  }
72651
74338
  }
@@ -72699,13 +74386,16 @@ var restoreTerminal = null, macosKeychainAnthropicResult, defaultKeychainAnthrop
72699
74386
  }, CLAUDISH_PLACEHOLDER_API_KEY = "sk-ant-api03-placeholder-not-used-proxy-handles-auth-with-openrouter-key-xxxxxxxxxxxxxxxxxxxxx", CLAUDISH_PLACEHOLDER_AUTH_TOKEN = "placeholder-token-not-used-proxy-handles-auth", STALE_TOKEN_FILE_MS, MAX_TOKEN_FILES_SCANNED = 4000, CLAUDE_CODE_DEFAULT_MAX_CONTEXT = 200000, USER_STATUS_LINE_TIMEOUT_SECONDS = 3, MIN_AUTO_COMPACT_WINDOW = 200000, ADVISOR_TOOL_ENV_VAR = "CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL", CLAUDISH_CHILD_ADVISOR_MODEL = "sonnet", CHILD_STDERR_NOISE, SIGNAL_EXIT_NUMBERS;
72700
74387
  var init_claude_runner = __esm(() => {
72701
74388
  init_model_catalog();
72702
- init_config2();
74389
+ init_config();
74390
+ init_magmux_wrapper();
72703
74391
  init_logger();
72704
74392
  init_profile_config();
72705
74393
  init_model_discovery();
72706
74394
  init_model_parser();
72707
74395
  init_provider_definitions();
72708
74396
  init_routing_rules();
74397
+ init_magmux_ui();
74398
+ init_settings();
72709
74399
  init_telemetry();
72710
74400
  init_terminal_isolation();
72711
74401
  init_theme_mode();
@@ -72720,18 +74410,18 @@ var init_claude_runner = __esm(() => {
72720
74410
  });
72721
74411
 
72722
74412
  // src/diag-output.ts
72723
- import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync21, unlinkSync as unlinkSync10, writeFileSync as writeFileSync22 } from "fs";
74413
+ import { createWriteStream as createWriteStream3, mkdirSync as mkdirSync22, unlinkSync as unlinkSync10, writeFileSync as writeFileSync23 } from "fs";
72724
74414
  import { homedir as homedir38 } from "os";
72725
- import { join as join43 } from "path";
74415
+ import { join as join45 } from "path";
72726
74416
  function getClaudishDir() {
72727
- const dir = join43(homedir38(), ".claudish");
74417
+ const dir = join45(homedir38(), ".claudish");
72728
74418
  try {
72729
- mkdirSync21(dir, { recursive: true });
74419
+ mkdirSync22(dir, { recursive: true });
72730
74420
  } catch {}
72731
74421
  return dir;
72732
74422
  }
72733
74423
  function getDiagLogPath() {
72734
- return join43(getClaudishDir(), `diag-${process.pid}.log`);
74424
+ return join45(getClaudishDir(), `diag-${process.pid}.log`);
72735
74425
  }
72736
74426
 
72737
74427
  class LogFileDiagOutput {
@@ -72740,7 +74430,7 @@ class LogFileDiagOutput {
72740
74430
  constructor() {
72741
74431
  this.logPath = getDiagLogPath();
72742
74432
  try {
72743
- writeFileSync22(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
74433
+ writeFileSync23(this.logPath, `--- claudish diag session ${new Date().toISOString()} ---
72744
74434
  `);
72745
74435
  } catch {}
72746
74436
  this.stream = createWriteStream3(this.logPath, { flags: "a" });
@@ -73221,7 +74911,7 @@ var init_advisor_startup = __esm(() => {
73221
74911
  });
73222
74912
 
73223
74913
  // src/session/conversation.ts
73224
- import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, statSync as statSync9 } from "fs";
74914
+ import { closeSync as closeSync10, openSync as openSync10, readSync as readSync4, statSync as statSync9 } from "fs";
73225
74915
  import { StringDecoder as StringDecoder3 } from "string_decoder";
73226
74916
  function looksLikeTurn(line) {
73227
74917
  const assistant = line.includes('"type":"assistant"');
@@ -73277,7 +74967,7 @@ function readConversation(file, opts = {}) {
73277
74967
  let fd = null;
73278
74968
  try {
73279
74969
  const size = statSync9(file).size;
73280
- fd = openSync9(file, "r");
74970
+ fd = openSync10(file, "r");
73281
74971
  const buf = Buffer.allocUnsafe(chunkBytes);
73282
74972
  const decoder = new StringDecoder3("utf-8");
73283
74973
  let pending = "";
@@ -73333,7 +75023,7 @@ function readConversation(file, opts = {}) {
73333
75023
  } catch {} finally {
73334
75024
  if (fd !== null) {
73335
75025
  try {
73336
- closeSync9(fd);
75026
+ closeSync10(fd);
73337
75027
  } catch {}
73338
75028
  }
73339
75029
  }
@@ -74937,9 +76627,9 @@ __export(exports_session_stats, {
74937
76627
  });
74938
76628
  import { readFileSync as readFileSync34 } from "fs";
74939
76629
  import { homedir as homedir39 } from "os";
74940
- import { join as join44 } from "path";
76630
+ import { join as join46 } from "path";
74941
76631
  function tokenFilePath(port) {
74942
- return process.env.CLAUDISH_TOKEN_FILE || join44(homedir39(), ".claudish", `tokens-${port}.json`);
76632
+ return process.env.CLAUDISH_TOKEN_FILE || join46(homedir39(), ".claudish", `tokens-${port}.json`);
74943
76633
  }
74944
76634
  function readSessionStats(port, opts) {
74945
76635
  let raw;
@@ -75024,7 +76714,7 @@ function bg(hex) {
75024
76714
  const [r, g, b] = rgb(hex);
75025
76715
  return `\x1B[48;2;${r};${g};${b}m`;
75026
76716
  }
75027
- function paint2(text, hex, bold = false) {
76717
+ function paint3(text, hex, bold = false) {
75028
76718
  return `${bold ? BOLD4 : ""}${fg(hex)}${text}${RESET3}`;
75029
76719
  }
75030
76720
  function meter(pct, width, ramp = ramps.load) {
@@ -75032,7 +76722,7 @@ function meter(pct, width, ramp = ramps.load) {
75032
76722
  if (!Number.isFinite(cells) || cells <= 0)
75033
76723
  return "";
75034
76724
  if (Number.isNaN(pct))
75035
- return paint2(NODATA2.repeat(cells), tokens.dead);
76725
+ return paint3(NODATA2.repeat(cells), tokens.dead);
75036
76726
  const cols = rampFor(cells, ramp);
75037
76727
  const filled = fillCells(pct, cells);
75038
76728
  let out = "";
@@ -75053,7 +76743,7 @@ function stackedBar(segments, width) {
75053
76743
  return "";
75054
76744
  const split = splitCells(segments.map((s) => s.value), cells);
75055
76745
  if (split.reduce((a, b) => a + b, 0) === 0)
75056
- return paint2(TRACK2.repeat(cells), tokens.border);
76746
+ return paint3(TRACK2.repeat(cells), tokens.border);
75057
76747
  let out = "";
75058
76748
  for (let i = 0;i < split.length; i++) {
75059
76749
  const n = split[i];
@@ -75178,14 +76868,14 @@ function renderSessionSummary(input) {
75178
76868
  const W = cardWidth();
75179
76869
  const inner = W - CHROME;
75180
76870
  const out = [];
75181
- const dim = (s) => paint2(s, tokens.subtle);
75182
- const body = (s) => paint2(s, tokens.text);
76871
+ const dim = (s) => paint3(s, tokens.subtle);
76872
+ const body = (s) => paint3(s, tokens.text);
75183
76873
  const titleText = exitCode === 0 ? " session " : " session \xB7 failed ";
75184
76874
  const titleHex = exitCode === 0 ? tokens.accent : tokens.error;
75185
76875
  const rule = "\u2500".repeat(Math.max(0, W - 2 - visibleWidth(titleText) - 1));
75186
- out.push(paint2("\u256D\u2500", tokens.border) + paint2(titleText, titleHex) + paint2(rule + "\u256E", tokens.border));
76876
+ out.push(paint3("\u256D\u2500", tokens.border) + paint3(titleText, titleHex) + paint3(rule + "\u256E", tokens.border));
75187
76877
  const row = (s) => {
75188
- out.push(`${paint2("\u2502", tokens.border)} ${padVisible2(s, inner)} ${paint2("\u2502", tokens.border)}`);
76878
+ out.push(`${paint3("\u2502", tokens.border)} ${padVisible2(s, inner)} ${paint3("\u2502", tokens.border)}`);
75189
76879
  };
75190
76880
  const blank = () => row("");
75191
76881
  const chips = [badge(truncate2(modelSpec, 34), tokens.accent)];
@@ -75235,13 +76925,13 @@ function renderSessionSummary(input) {
75235
76925
  if (rest > 0)
75236
76926
  segs.push({ value: rest, color: area(other) });
75237
76927
  dataRow("tools", stackedBar(segs, barW), body(padStartTo(String(stats.toolCallTotal), 4)) + dim(" calls"));
75238
- const legend = shown.map((t, i) => paint2(`${t.name} ${t.count}`, toolCols[i])).concat(rest > 0 ? [paint2(`other ${rest}`, other)] : []);
76928
+ const legend = shown.map((t, i) => paint3(`${t.name} ${t.count}`, toolCols[i])).concat(rest > 0 ? [paint3(`other ${rest}`, other)] : []);
75239
76929
  for (const line of wrapStyled(legend, dim(" \xB7 "), inner - LABEL_W)) {
75240
76930
  row(" ".repeat(LABEL_W) + line);
75241
76931
  }
75242
76932
  }
75243
76933
  blank();
75244
- row(dim(padTo("cost", LABEL_W)) + paint2(stats.isFree ? "free" : usd(stats.costUsd), tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim(" estimated") : ""));
76934
+ row(dim(padTo("cost", LABEL_W)) + paint3(stats.isFree ? "free" : usd(stats.costUsd), tokens.text, true) + (stats.isEstimated && !stats.isFree ? dim(" estimated") : ""));
75245
76935
  const dearest = Math.max(0, ...stats.savings.map((s) => s.baselineUsd));
75246
76936
  for (const s of stats.savings) {
75247
76937
  const label = padTo(`vs ${s.label}`, LABEL_W);
@@ -75250,10 +76940,10 @@ function renderSessionSummary(input) {
75250
76940
  const share = dearest > 0 ? s.savedUsd / dearest * 100 : 0;
75251
76941
  dataRow(label, meter(share, barW, areaRamp(ramps.volume)), dim(`${padStartTo(`${Math.round(pct)}%`, 4)} saved `) + body(usd(s.savedUsd)));
75252
76942
  } else {
75253
- dataRow(label, meter(0, barW, areaRamp(ramps.volume)), dim("over by ") + paint2(usd(-s.savedUsd), tokens.error));
76943
+ dataRow(label, meter(0, barW, areaRamp(ramps.volume)), dim("over by ") + paint3(usd(-s.savedUsd), tokens.error));
75254
76944
  }
75255
76945
  }
75256
- out.push(paint2(`\u2570${"\u2500".repeat(W - 2)}\u256F`, tokens.border));
76946
+ out.push(paint3(`\u2570${"\u2500".repeat(W - 2)}\u256F`, tokens.border));
75257
76947
  if (resumeId) {
75258
76948
  out.push("");
75259
76949
  out.push(dim("Resume this session with:"));
@@ -75302,8 +76992,8 @@ init_op_source();
75302
76992
  init_startup_trace();
75303
76993
  init_picker_cancelled();
75304
76994
  var import_dotenv3 = __toESM(require_main(), 1);
75305
- import { existsSync as existsSync33, readFileSync as readFileSync35 } from "fs";
75306
- import { join as join45, resolve as resolve6 } from "path";
76995
+ import { existsSync as existsSync36, readFileSync as readFileSync35 } from "fs";
76996
+ import { join as join47, resolve as resolve6 } from "path";
75307
76997
 
75308
76998
  // src/tui/runtime/should-open-picker.ts
75309
76999
  function noModelConfigured(gate) {
@@ -75416,7 +77106,7 @@ async function applyConfigOverride() {
75416
77106
  await Promise.resolve();
75417
77107
  const plan = planConfigOverride(process.argv.slice(2), process.env, {
75418
77108
  resolve: resolve6,
75419
- exists: existsSync33
77109
+ exists: existsSync36
75420
77110
  });
75421
77111
  if (plan.kind === "none")
75422
77112
  return;
@@ -75558,7 +77248,7 @@ async function runCli() {
75558
77248
  const endImports = beginSpan("startup:cli-imports");
75559
77249
  await Promise.resolve().then(() => init_claude_runner());
75560
77250
  await Promise.resolve().then(() => init_cli());
75561
- await Promise.resolve().then(() => init_config2());
77251
+ await Promise.resolve().then(() => init_config());
75562
77252
  await Promise.resolve().then(() => init_model_selector());
75563
77253
  await Promise.resolve().then(() => init_provider_resolver());
75564
77254
  await Promise.resolve().then(() => init_logger());
@@ -75596,7 +77286,7 @@ async function runCli() {
75596
77286
  process.exit(1);
75597
77287
  }
75598
77288
  const mode = cliConfig.teamMode ?? "default";
75599
- const sessionPath = join45(process.cwd(), `.claudish-team-${Date.now()}`);
77289
+ const sessionPath = join47(process.cwd(), `.claudish-team-${Date.now()}`);
75600
77290
  if (mode === "json") {
75601
77291
  await Promise.resolve().then(() => init_team_orchestrator());
75602
77292
  setupSession(sessionPath, cliConfig.team, prompt);
@@ -75605,7 +77295,7 @@ async function runCli() {
75605
77295
  });
75606
77296
  const result = { ...status, responses: {} };
75607
77297
  for (const anonId of Object.keys(status.models)) {
75608
- const responsePath = join45(sessionPath, `response-${anonId}.md`);
77298
+ const responsePath = join47(sessionPath, `response-${anonId}.md`);
75609
77299
  try {
75610
77300
  const raw = readFileSync35(responsePath, "utf-8").trim();
75611
77301
  try {