claudish 7.51.0 → 7.53.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.
- package/bin/claudish.cjs +80 -4
- package/dist/index.js +550 -99
- package/package.json +5 -5
package/bin/claudish.cjs
CHANGED
|
@@ -3,8 +3,38 @@
|
|
|
3
3
|
// Launcher script: checks for Bun runtime before starting claudish.
|
|
4
4
|
// Claudish uses Bun-specific APIs (bun:ffi for TUI, Bun.spawn, etc.)
|
|
5
5
|
// so it cannot run under Node.js directly.
|
|
6
|
+
//
|
|
7
|
+
// ── Why this is spawn() and not spawnSync() ──────────────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// This process is a THIN WRAPPER around the real CLI, and it is the process
|
|
10
|
+
// every caller's signal actually lands on. It used to run the child with
|
|
11
|
+
// `spawnSync(bun, ..., { stdio: "inherit" })`, which made the whole tree
|
|
12
|
+
// unkillable in a way that looked like the child was ignoring signals:
|
|
13
|
+
//
|
|
14
|
+
// · `spawnSync` blocks the event loop, so this process cannot forward
|
|
15
|
+
// anything while the child runs.
|
|
16
|
+
// · `stdio: "inherit"` gives the Bun child THIS process's fd 0/1/2. When a
|
|
17
|
+
// caller pipes our stdout, the Bun child holds the write end of that pipe.
|
|
18
|
+
// · So SIGTERM killed only this launcher. The Bun child was ORPHANED, not
|
|
19
|
+
// killed — it kept running, kept billing, and kept the caller's pipe open,
|
|
20
|
+
// writing into it long after the caller had given up.
|
|
21
|
+
//
|
|
22
|
+
// Measured 2026-08-15 with a repro of this exact topology: SIGTERM to the
|
|
23
|
+
// launcher pid leaked 330 B of post-kill output, and so did SIGKILL — because
|
|
24
|
+
// the problem is signalling the wrong process, not a stubborn one. It cost a
|
|
25
|
+
// real `team` run: a model declared TIMEOUT with 0 B wrote a complete 40,699 B
|
|
26
|
+
// answer into the response file 375 s later, and was scored as empty.
|
|
27
|
+
//
|
|
28
|
+
// Now: async spawn, forward the signals a supervisor actually sends, and exit
|
|
29
|
+
// with the child's own status. A caller that terminates `claudish` terminates
|
|
30
|
+
// claudish.
|
|
31
|
+
//
|
|
32
|
+
// This does NOT cover being SIGKILLed ourselves — nothing running in this
|
|
33
|
+
// process can. Callers that need a hard guarantee should spawn claudish
|
|
34
|
+
// `detached` and signal the process GROUP, which reaches the Bun child (and its
|
|
35
|
+
// own descendants) directly; `team-orchestrator.ts` does exactly that.
|
|
6
36
|
|
|
7
|
-
const { execFileSync, execSync } = require("node:child_process");
|
|
37
|
+
const { execFileSync, execSync, spawn } = require("node:child_process");
|
|
8
38
|
const { resolve } = require("node:path");
|
|
9
39
|
|
|
10
40
|
/**
|
|
@@ -75,18 +105,64 @@ Learn more: https://bun.sh`);
|
|
|
75
105
|
process.exit(1);
|
|
76
106
|
}
|
|
77
107
|
|
|
78
|
-
// Exec into bun with the real entry point
|
|
79
108
|
const entry = resolve(__dirname, "..", "dist", "index.js");
|
|
109
|
+
|
|
110
|
+
let child;
|
|
80
111
|
try {
|
|
81
|
-
|
|
112
|
+
child = spawn(bun, [entry, ...process.argv.slice(2)], {
|
|
82
113
|
stdio: "inherit",
|
|
83
114
|
env: process.env,
|
|
84
115
|
});
|
|
85
|
-
process.exit(result.status ?? 1);
|
|
86
116
|
} catch (err) {
|
|
87
117
|
console.error("Failed to start claudish:", err.message);
|
|
88
118
|
process.exit(1);
|
|
89
119
|
}
|
|
120
|
+
|
|
121
|
+
// Forward the signals a supervisor, terminal, or parent process actually sends.
|
|
122
|
+
// SIGKILL is deliberately absent — it cannot be caught, which is the whole
|
|
123
|
+
// reason callers wanting a hard kill must target the process group instead.
|
|
124
|
+
//
|
|
125
|
+
// Handlers are installed only AFTER a successful spawn, so a signal arriving
|
|
126
|
+
// during startup keeps Node's default terminate-immediately behaviour rather
|
|
127
|
+
// than being swallowed by a forward to a child that does not exist yet.
|
|
128
|
+
const FORWARDED =
|
|
129
|
+
process.platform === "win32" ? ["SIGINT", "SIGTERM"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
130
|
+
for (const signal of FORWARDED) {
|
|
131
|
+
process.on(signal, () => {
|
|
132
|
+
// Best-effort: the child may have exited between the signal and this line.
|
|
133
|
+
try {
|
|
134
|
+
if (!child.killed) child.kill(signal);
|
|
135
|
+
} catch {}
|
|
136
|
+
// Do NOT exit here. Waiting for the child's own "exit" keeps our status
|
|
137
|
+
// honest and, more importantly, keeps this process alive as long as the
|
|
138
|
+
// child is — a caller watching OUR pid must not see us disappear while the
|
|
139
|
+
// real work is still running.
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
child.on("error", (err) => {
|
|
144
|
+
console.error("Failed to start claudish:", err.message);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
child.on("exit", (code, signal) => {
|
|
149
|
+
if (signal) {
|
|
150
|
+
// Re-raise the signal on ourselves so our parent observes the same cause
|
|
151
|
+
// of death the child had, rather than a synthesised exit code. The handler
|
|
152
|
+
// is removed first, or we would forward it straight back to a dead child.
|
|
153
|
+
process.removeAllListeners(signal);
|
|
154
|
+
try {
|
|
155
|
+
process.kill(process.pid, signal);
|
|
156
|
+
return;
|
|
157
|
+
} catch {
|
|
158
|
+
// Re-raise failed (unsupported signal on this platform) — fall back to
|
|
159
|
+
// the shell convention for "died from signal N".
|
|
160
|
+
const NUMBERS = { SIGINT: 2, SIGQUIT: 3, SIGKILL: 9, SIGTERM: 15, SIGHUP: 1 };
|
|
161
|
+
process.exit(128 + (NUMBERS[signal] ?? 0));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
process.exit(code ?? 1);
|
|
165
|
+
});
|
|
90
166
|
}
|
|
91
167
|
|
|
92
168
|
// Running it as a program launches. Requiring it exposes the helpers so the
|
package/dist/index.js
CHANGED
|
@@ -729,7 +729,7 @@ var init_onepassword_config = __esm(() => {
|
|
|
729
729
|
});
|
|
730
730
|
|
|
731
731
|
// src/version.ts
|
|
732
|
-
var VERSION = "7.
|
|
732
|
+
var VERSION = "7.53.0";
|
|
733
733
|
|
|
734
734
|
// src/logger.ts
|
|
735
735
|
var exports_logger = {};
|
|
@@ -30167,6 +30167,9 @@ function parseRecord(raw) {
|
|
|
30167
30167
|
return null;
|
|
30168
30168
|
}
|
|
30169
30169
|
}
|
|
30170
|
+
function encodeRecord(rec) {
|
|
30171
|
+
return PREFIX + Buffer.from(JSON.stringify(rec), "utf8").toString("base64");
|
|
30172
|
+
}
|
|
30170
30173
|
function readSharedAntigravityToken(deps = defaultDeps) {
|
|
30171
30174
|
const rec = parseRecord(deps.readStore());
|
|
30172
30175
|
return rec ? rec.token : null;
|
|
@@ -30184,6 +30187,15 @@ function hasSharedAntigravityToken(deps = defaultDeps) {
|
|
|
30184
30187
|
cachedHasToken = { at: now, value };
|
|
30185
30188
|
return value;
|
|
30186
30189
|
}
|
|
30190
|
+
function writeSharedAntigravityToken(tok, deps = defaultDeps) {
|
|
30191
|
+
const existing = parseRecord(deps.readStore());
|
|
30192
|
+
const base = existing ?? { token: tok };
|
|
30193
|
+
const merged = {
|
|
30194
|
+
...base,
|
|
30195
|
+
token: { ...base.token, ...tok }
|
|
30196
|
+
};
|
|
30197
|
+
deps.writeStore(encodeRecord(merged));
|
|
30198
|
+
}
|
|
30187
30199
|
function deleteSharedAntigravityToken(deps = defaultDeps) {
|
|
30188
30200
|
(deps.deleteStore ?? defaultDeleteStore)();
|
|
30189
30201
|
_resetAntigravityTokenState();
|
|
@@ -30222,6 +30234,31 @@ function getValidAntigravityAccessToken(deps = defaultDeps) {
|
|
|
30222
30234
|
});
|
|
30223
30235
|
return inFlight;
|
|
30224
30236
|
}
|
|
30237
|
+
async function forceRefreshAntigravityToken(deps = defaultDeps) {
|
|
30238
|
+
if (process.platform !== "darwin") {
|
|
30239
|
+
throw new Error("[Antigravity] The shared Antigravity token store is macOS-only for now. " + "Use g@<model> with GEMINI_API_KEY on this platform.");
|
|
30240
|
+
}
|
|
30241
|
+
_resetAntigravityTokenState();
|
|
30242
|
+
const rec = parseRecord(deps.readStore());
|
|
30243
|
+
if (!rec) {
|
|
30244
|
+
throw new Error("[Antigravity] No Antigravity session found. Sign in with `claudish login antigravity`, " + "or use g@<model> with GEMINI_API_KEY " + "(get one at https://aistudio.google.com/app/apikey).");
|
|
30245
|
+
}
|
|
30246
|
+
const original = rec.token;
|
|
30247
|
+
log("[Antigravity] Upstream rejected the current token \u2014 asking the Antigravity CLI to re-mint.");
|
|
30248
|
+
writeSharedAntigravityToken({ ...original, expiry: new Date(deps.now() - 1000).toISOString() }, deps);
|
|
30249
|
+
deps.runAgyRefresh();
|
|
30250
|
+
const refreshed = parseRecord(deps.readStore());
|
|
30251
|
+
const token = refreshed?.token;
|
|
30252
|
+
if (token && token.access_token !== original.access_token && !needsRefresh(token, deps.now())) {
|
|
30253
|
+
log("[Antigravity] Shared token re-minted by the Antigravity CLI.");
|
|
30254
|
+
return token.access_token;
|
|
30255
|
+
}
|
|
30256
|
+
if (!refreshed || refreshed.token.access_token === original.access_token) {
|
|
30257
|
+
writeSharedAntigravityToken(original, deps);
|
|
30258
|
+
}
|
|
30259
|
+
_resetAntigravityTokenState();
|
|
30260
|
+
throw new Error("[Antigravity] The Antigravity session was rejected upstream and could not be re-minted. " + "Run `claudish login antigravity` to sign in again.");
|
|
30261
|
+
}
|
|
30225
30262
|
function _resetAntigravityTokenState() {
|
|
30226
30263
|
inFlight = null;
|
|
30227
30264
|
cachedHasToken = null;
|
|
@@ -30248,6 +30285,13 @@ function makeTerminalSetupError(message) {
|
|
|
30248
30285
|
function buildAntigravityUserAgent() {
|
|
30249
30286
|
return `antigravity/cli/1.1.9 (aidev_client; os_type=${process.platform}; arch=${process.arch}; auth_method=consumer)`;
|
|
30250
30287
|
}
|
|
30288
|
+
function resetAntigravityUserCache() {
|
|
30289
|
+
cachedAgProjectId = null;
|
|
30290
|
+
cachedAgTierId = null;
|
|
30291
|
+
cachedAgTierName = null;
|
|
30292
|
+
agServedCache = null;
|
|
30293
|
+
agServedCacheAt = 0;
|
|
30294
|
+
}
|
|
30251
30295
|
async function callLoadCodeAssistAntigravity(accessToken) {
|
|
30252
30296
|
const res = await fetch(`${ANTIGRAVITY_API_BASE}:loadCodeAssist`, {
|
|
30253
30297
|
method: "POST",
|
|
@@ -30632,6 +30676,22 @@ class AntigravityProviderTransport {
|
|
|
30632
30676
|
this.servedModelName = resolveAntigravityModelId(this.modelName, this.servedModels, this.defaultServedModel, lookupFamilyDefaultVariant(this.modelName, "antigravity"));
|
|
30633
30677
|
log(`[Antigravity] Auth refreshed, project: ${this.projectId}, tier: ${this._displayName}, ` + `model: ${this.modelName} -> ${this.servedModelName}, served: ${this.servedModels.join(",") || "(none)"}`);
|
|
30634
30678
|
}
|
|
30679
|
+
async forceRefreshAuth() {
|
|
30680
|
+
await forceRefreshAntigravityToken();
|
|
30681
|
+
resetAntigravityUserCache();
|
|
30682
|
+
this.cachedAuth = null;
|
|
30683
|
+
await this.refreshAuth();
|
|
30684
|
+
}
|
|
30685
|
+
classifyTerminalError(status, bodyText) {
|
|
30686
|
+
if (status !== 429)
|
|
30687
|
+
return;
|
|
30688
|
+
const classification = classify429(bodyText);
|
|
30689
|
+
if (!classification)
|
|
30690
|
+
return;
|
|
30691
|
+
if (classification.reason === "MODEL_CAPACITY_EXHAUSTED")
|
|
30692
|
+
return false;
|
|
30693
|
+
return classification.terminal;
|
|
30694
|
+
}
|
|
30635
30695
|
transformPayload(payload) {
|
|
30636
30696
|
const envelope = this.cachedAuth?.transformPayload ? this.cachedAuth.transformPayload(payload) : this.buildEnvelope(payload, this.servedModelName);
|
|
30637
30697
|
this.lastEnvelope = envelope;
|
|
@@ -30672,8 +30732,7 @@ class AntigravityProviderTransport {
|
|
|
30672
30732
|
return this.handleCapacityExhausted(response, queue);
|
|
30673
30733
|
}
|
|
30674
30734
|
if (classification.terminal) {
|
|
30675
|
-
|
|
30676
|
-
return response;
|
|
30735
|
+
return await this.explainTerminalQuota(response, bodyText, classification.reason);
|
|
30677
30736
|
}
|
|
30678
30737
|
if (attempt < MAX_RETRY_ATTEMPTS) {
|
|
30679
30738
|
const delay = classification.retryDelayMs ?? DEFAULT_RATE_LIMIT_DELAY_MS;
|
|
@@ -30768,6 +30827,53 @@ class AntigravityProviderTransport {
|
|
|
30768
30827
|
headers: { "Content-Type": "application/json" }
|
|
30769
30828
|
});
|
|
30770
30829
|
}
|
|
30830
|
+
async quotaRemainingForServedModel() {
|
|
30831
|
+
if (!this.accessToken || !this.projectId)
|
|
30832
|
+
return;
|
|
30833
|
+
let timer;
|
|
30834
|
+
const data = await Promise.race([
|
|
30835
|
+
retrieveUserQuota(this.accessToken, this.projectId).catch(() => null),
|
|
30836
|
+
new Promise((resolve) => {
|
|
30837
|
+
timer = setTimeout(() => resolve(null), QUOTA_CHECK_TIMEOUT_MS);
|
|
30838
|
+
})
|
|
30839
|
+
]).finally(() => {
|
|
30840
|
+
if (timer)
|
|
30841
|
+
clearTimeout(timer);
|
|
30842
|
+
});
|
|
30843
|
+
const buckets = data?.buckets;
|
|
30844
|
+
if (!buckets?.length)
|
|
30845
|
+
return;
|
|
30846
|
+
const bucket = buckets.find((b) => b.modelId === this.servedModelName) ?? buckets.find((b) => b.modelId === this.modelName);
|
|
30847
|
+
return typeof bucket?.remainingFraction === "number" ? bucket.remainingFraction : undefined;
|
|
30848
|
+
}
|
|
30849
|
+
async explainTerminalQuota(response, bodyText, reason) {
|
|
30850
|
+
const remaining = await this.quotaRemainingForServedModel();
|
|
30851
|
+
const model = this.servedModelName;
|
|
30852
|
+
let advice;
|
|
30853
|
+
if (remaining !== undefined && remaining > 0) {
|
|
30854
|
+
advice = `your Antigravity plan still reports ${(remaining * 100).toFixed(1)}% of the ${model} ` + "allowance available, so this is probably NOT an exhausted plan. The usual cause is an " + "Antigravity session Google invalidated server-side; run `claudish login antigravity`.";
|
|
30855
|
+
} else if (remaining === undefined) {
|
|
30856
|
+
advice = "claudish could not read your Antigravity quota to confirm this. If your plan is not " + "actually spent, the session may be stale \u2014 run `claudish login antigravity`.";
|
|
30857
|
+
} else {
|
|
30858
|
+
advice = `your Antigravity plan reports no remaining ${model} allowance; it refills on Google's own schedule.`;
|
|
30859
|
+
}
|
|
30860
|
+
logStderr(`[Antigravity] Terminal 429 (${reason || "daily limit"}) \u2014 ${advice}`);
|
|
30861
|
+
let parsed;
|
|
30862
|
+
try {
|
|
30863
|
+
parsed = JSON.parse(bodyText);
|
|
30864
|
+
} catch {
|
|
30865
|
+
return response;
|
|
30866
|
+
}
|
|
30867
|
+
const target = Array.isArray(parsed) ? parsed[0]?.error : parsed?.error;
|
|
30868
|
+
if (!target || typeof target !== "object")
|
|
30869
|
+
return response;
|
|
30870
|
+
const upstream = typeof target.message === "string" && target.message.length > 0 ? target.message : "Resource has been exhausted";
|
|
30871
|
+
target.message = `${upstream} \u2014 ${advice}`;
|
|
30872
|
+
return new Response(JSON.stringify(parsed), {
|
|
30873
|
+
status: 429,
|
|
30874
|
+
headers: { "Content-Type": "application/json" }
|
|
30875
|
+
});
|
|
30876
|
+
}
|
|
30771
30877
|
async logQuotaInfo() {
|
|
30772
30878
|
if (!this.accessToken || !this.projectId)
|
|
30773
30879
|
return;
|
|
@@ -30776,6 +30882,7 @@ class AntigravityProviderTransport {
|
|
|
30776
30882
|
if (!data?.buckets?.length)
|
|
30777
30883
|
return;
|
|
30778
30884
|
const lines = [];
|
|
30885
|
+
let sawThrottledModel = false;
|
|
30779
30886
|
for (const bucket of data.buckets) {
|
|
30780
30887
|
if (!bucket.modelId)
|
|
30781
30888
|
continue;
|
|
@@ -30784,15 +30891,24 @@ class AntigravityProviderTransport {
|
|
|
30784
30891
|
hour: "2-digit",
|
|
30785
30892
|
minute: "2-digit"
|
|
30786
30893
|
}) : "?";
|
|
30787
|
-
|
|
30894
|
+
const mine = this.isThrottledModel(bucket.modelId);
|
|
30895
|
+
if (mine)
|
|
30896
|
+
sawThrottledModel = true;
|
|
30897
|
+
lines.push(` ${mine ? "> " : " "}${bucket.modelId}: ${pct} remaining (resets ${reset})`);
|
|
30788
30898
|
}
|
|
30789
|
-
if (lines.length
|
|
30790
|
-
|
|
30899
|
+
if (lines.length === 0)
|
|
30900
|
+
return;
|
|
30901
|
+
const requested = this.servedModelName && this.servedModelName !== this.modelName ? `${this.modelName} (served as ${this.servedModelName})` : this.modelName;
|
|
30902
|
+
const header = sawThrottledModel ? `[Antigravity] Quota status (> = ${requested}):` : `[Antigravity] Quota status \u2014 NOTE: ${requested} has no quota bucket below, ` + "so these figures do not explain its rate limit:";
|
|
30903
|
+
logStderr(`${header}
|
|
30791
30904
|
${lines.join(`
|
|
30792
30905
|
`)}`);
|
|
30793
|
-
}
|
|
30794
30906
|
} catch {}
|
|
30795
30907
|
}
|
|
30908
|
+
isThrottledModel(bucketModelId) {
|
|
30909
|
+
const id = bucketModelId.toLowerCase();
|
|
30910
|
+
return id === this.modelName.toLowerCase() || id === this.servedModelName.toLowerCase();
|
|
30911
|
+
}
|
|
30796
30912
|
async getQuotaRemaining(modelName) {
|
|
30797
30913
|
if (!this.accessToken || !this.projectId)
|
|
30798
30914
|
return;
|
|
@@ -30807,7 +30923,7 @@ ${lines.join(`
|
|
|
30807
30923
|
}
|
|
30808
30924
|
}
|
|
30809
30925
|
}
|
|
30810
|
-
var ANTIGRAVITY_BASE = "https://cloudcode-pa.googleapis.com", ANTIGRAVITY_ENDPOINT, MAX_RETRY_ATTEMPTS = 3, DEFAULT_RATE_LIMIT_DELAY_MS = 1e4, REASONING_TIER_RANK;
|
|
30926
|
+
var ANTIGRAVITY_BASE = "https://cloudcode-pa.googleapis.com", ANTIGRAVITY_ENDPOINT, MAX_RETRY_ATTEMPTS = 3, DEFAULT_RATE_LIMIT_DELAY_MS = 1e4, QUOTA_CHECK_TIMEOUT_MS = 3000, REASONING_TIER_RANK;
|
|
30811
30927
|
var init_antigravity = __esm(() => {
|
|
30812
30928
|
init_model_catalog();
|
|
30813
30929
|
init_antigravity_token();
|
|
@@ -32675,12 +32791,14 @@ var init_deepseek_model_dialect = __esm(() => {
|
|
|
32675
32791
|
}
|
|
32676
32792
|
applyNativeReasoning(request, originalRequest) {
|
|
32677
32793
|
const effort = this.resolveEffortLevel(originalRequest);
|
|
32678
|
-
if (effort && this.
|
|
32794
|
+
if (effort && this.acceptsReasoningControls()) {
|
|
32679
32795
|
if (effort === "none" || effort === "minimal") {
|
|
32680
32796
|
request.thinking = { type: "disabled" };
|
|
32681
32797
|
log(`[DeepSeekModelDialect] effort ${effort} -> thinking.type: disabled for ${this.modelId}`);
|
|
32682
32798
|
} else {
|
|
32683
|
-
const
|
|
32799
|
+
const reasoning = this.lookupReasoningCapability();
|
|
32800
|
+
const clamped = reasoning?.control === "effort" && reasoning.efforts?.length ? this.clampToAdvertisedEffort(effort, reasoning) : undefined;
|
|
32801
|
+
const value = clamped ?? (effort === "xhigh" || effort === "max" ? "max" : "high");
|
|
32684
32802
|
request.reasoning_effort = value;
|
|
32685
32803
|
log(`[DeepSeekModelDialect] effort ${effort} -> reasoning_effort: ${value} for ${this.modelId}`);
|
|
32686
32804
|
}
|
|
@@ -32692,9 +32810,15 @@ var init_deepseek_model_dialect = __esm(() => {
|
|
|
32692
32810
|
}
|
|
32693
32811
|
return request;
|
|
32694
32812
|
}
|
|
32695
|
-
|
|
32813
|
+
acceptsReasoningControls() {
|
|
32814
|
+
const reasoning = this.lookupReasoningCapability();
|
|
32815
|
+
if (reasoning)
|
|
32816
|
+
return reasoning.supported === true && reasoning.control === "effort";
|
|
32696
32817
|
const model = this.modelId.toLowerCase();
|
|
32697
|
-
|
|
32818
|
+
if (model.includes("deepseek-chat") || model.includes("deepseek-reasoner"))
|
|
32819
|
+
return true;
|
|
32820
|
+
const version2 = /(?:^|[^a-z0-9])v(\d+)/.exec(model);
|
|
32821
|
+
return version2 ? Number(version2[1]) >= 4 : false;
|
|
32698
32822
|
}
|
|
32699
32823
|
shouldHandle(modelId) {
|
|
32700
32824
|
return matchesModelFamily(modelId, "deepseek");
|
|
@@ -34105,11 +34229,87 @@ var init_glm_model_dialect = __esm(() => {
|
|
|
34105
34229
|
};
|
|
34106
34230
|
});
|
|
34107
34231
|
|
|
34232
|
+
// src/adapters/grok-effort-support.ts
|
|
34233
|
+
function acceptsReasoningEffort(modelId) {
|
|
34234
|
+
const id = norm(modelId);
|
|
34235
|
+
if (!id)
|
|
34236
|
+
return false;
|
|
34237
|
+
if (learnedRejects.has(id))
|
|
34238
|
+
return false;
|
|
34239
|
+
const reasoning = lookupModelReasoning(id) ?? lookupModelReasoning(modelId);
|
|
34240
|
+
if (reasoning)
|
|
34241
|
+
return reasoning.supported === true && reasoning.control === "effort";
|
|
34242
|
+
return !SEED_REJECTS.some((re) => re.test(id));
|
|
34243
|
+
}
|
|
34244
|
+
function catalogReasoningFor(modelId) {
|
|
34245
|
+
return lookupModelReasoning(norm(modelId)) ?? lookupModelReasoning(modelId);
|
|
34246
|
+
}
|
|
34247
|
+
function rememberReasoningEffortRejected(modelId) {
|
|
34248
|
+
const id = norm(modelId);
|
|
34249
|
+
if (!id || learnedRejects.has(id))
|
|
34250
|
+
return;
|
|
34251
|
+
learnedRejects.add(id);
|
|
34252
|
+
log(`[GrokEffortSupport] ${id} rejected reasoning_effort \u2014 not sending it again this session.`);
|
|
34253
|
+
}
|
|
34254
|
+
function isReasoningEffortRejection(errorText) {
|
|
34255
|
+
if (!errorText)
|
|
34256
|
+
return false;
|
|
34257
|
+
return /does not support parameter\s+`?reasoning[_]?effort`?/i.test(errorText);
|
|
34258
|
+
}
|
|
34259
|
+
function acceptsReasoningEffortValue(modelId, value) {
|
|
34260
|
+
return !learnedValueRejects.has(pairKey(modelId, value));
|
|
34261
|
+
}
|
|
34262
|
+
function rememberReasoningEffortValueRejected(modelId, value) {
|
|
34263
|
+
const key = pairKey(modelId, value);
|
|
34264
|
+
if (learnedValueRejects.has(key))
|
|
34265
|
+
return;
|
|
34266
|
+
learnedValueRejects.add(key);
|
|
34267
|
+
log(`[GrokEffortSupport] ${norm(modelId)} rejected reasoning_effort value "${value}" \u2014 ` + "falling back and not sending it again this session.");
|
|
34268
|
+
}
|
|
34269
|
+
function rejectedReasoningEffortValue(errorText) {
|
|
34270
|
+
if (!errorText)
|
|
34271
|
+
return null;
|
|
34272
|
+
const m = /does not support\s+`?reasoning[_]?effort`?\s+value\s+`?([a-z]+)`?/i.exec(errorText);
|
|
34273
|
+
return m ? m[1].toLowerCase() : null;
|
|
34274
|
+
}
|
|
34275
|
+
function fallbackReasoningEffortValue(value) {
|
|
34276
|
+
switch (value.toLowerCase()) {
|
|
34277
|
+
case "none":
|
|
34278
|
+
return "low";
|
|
34279
|
+
case "minimal":
|
|
34280
|
+
return "low";
|
|
34281
|
+
case "low":
|
|
34282
|
+
return null;
|
|
34283
|
+
case "medium":
|
|
34284
|
+
return "low";
|
|
34285
|
+
case "high":
|
|
34286
|
+
return "medium";
|
|
34287
|
+
default:
|
|
34288
|
+
return null;
|
|
34289
|
+
}
|
|
34290
|
+
}
|
|
34291
|
+
var SEED_REJECTS, learnedRejects, norm = (modelId) => modelId.trim().toLowerCase().replace(/^x-ai\//, ""), learnedValueRejects, pairKey = (modelId, value) => `${norm(modelId)}\x00${value.toLowerCase()}`;
|
|
34292
|
+
var init_grok_effort_support = __esm(() => {
|
|
34293
|
+
init_logger();
|
|
34294
|
+
init_model_catalog();
|
|
34295
|
+
SEED_REJECTS = [
|
|
34296
|
+
/non-reasoning/i,
|
|
34297
|
+
/^grok-2/i,
|
|
34298
|
+
/^grok-4(?![.\d])(?!.*fast-reasoning)/i,
|
|
34299
|
+
/^grok-build-/i,
|
|
34300
|
+
/^grok-code-/i,
|
|
34301
|
+
/^grok-4\.20/i
|
|
34302
|
+
];
|
|
34303
|
+
learnedRejects = new Set;
|
|
34304
|
+
learnedValueRejects = new Set;
|
|
34305
|
+
});
|
|
34306
|
+
|
|
34108
34307
|
// src/adapters/grok-model-dialect.ts
|
|
34109
34308
|
var GrokModelDialect;
|
|
34110
34309
|
var init_grok_model_dialect = __esm(() => {
|
|
34111
34310
|
init_logger();
|
|
34112
34311
|
init_base_api_format();
|
|
34312
|
+
init_grok_effort_support();
|
|
34113
34313
|
init_model_catalog();
|
|
34114
34314
|
GrokModelDialect = class GrokModelDialect extends BaseAPIFormat {
|
|
34115
34315
|
xmlBuffer = "";
|
|
@@ -34172,34 +34372,78 @@ var init_grok_model_dialect = __esm(() => {
|
|
|
34172
34372
|
return request;
|
|
34173
34373
|
}
|
|
34174
34374
|
effortToReasoningEffort(effort) {
|
|
34175
|
-
|
|
34176
|
-
const isMini = model.includes("mini");
|
|
34177
|
-
const isGrok43 = /grok-4\.3(\b|[-.]|$)/.test(model);
|
|
34178
|
-
const isFastReasoning = model.includes("fast-reasoning");
|
|
34179
|
-
if (!(isMini || isGrok43 || isFastReasoning)) {
|
|
34375
|
+
if (!acceptsReasoningEffort(this.modelId)) {
|
|
34180
34376
|
return;
|
|
34181
34377
|
}
|
|
34182
|
-
|
|
34183
|
-
|
|
34184
|
-
|
|
34185
|
-
|
|
34186
|
-
|
|
34187
|
-
|
|
34188
|
-
|
|
34189
|
-
|
|
34378
|
+
let value;
|
|
34379
|
+
const reasoning = catalogReasoningFor(this.modelId);
|
|
34380
|
+
const clamped = reasoning ? this.clampToAdvertisedEffort(effort, reasoning) : undefined;
|
|
34381
|
+
if (clamped) {
|
|
34382
|
+
value = clamped;
|
|
34383
|
+
} else {
|
|
34384
|
+
const model = this.modelId.toLowerCase();
|
|
34385
|
+
const isMini = model.includes("mini");
|
|
34386
|
+
if (isMini) {
|
|
34387
|
+
switch (effort) {
|
|
34388
|
+
case "high":
|
|
34389
|
+
case "xhigh":
|
|
34390
|
+
case "max":
|
|
34391
|
+
value = "high";
|
|
34392
|
+
break;
|
|
34393
|
+
default:
|
|
34394
|
+
value = "low";
|
|
34395
|
+
}
|
|
34396
|
+
} else {
|
|
34397
|
+
switch (effort) {
|
|
34398
|
+
case "none":
|
|
34399
|
+
value = "none";
|
|
34400
|
+
break;
|
|
34401
|
+
case "minimal":
|
|
34402
|
+
case "low":
|
|
34403
|
+
value = "low";
|
|
34404
|
+
break;
|
|
34405
|
+
case "medium":
|
|
34406
|
+
value = "medium";
|
|
34407
|
+
break;
|
|
34408
|
+
default:
|
|
34409
|
+
value = "high";
|
|
34410
|
+
}
|
|
34190
34411
|
}
|
|
34191
34412
|
}
|
|
34192
|
-
|
|
34193
|
-
|
|
34194
|
-
|
|
34195
|
-
|
|
34196
|
-
|
|
34197
|
-
|
|
34198
|
-
|
|
34199
|
-
|
|
34200
|
-
|
|
34201
|
-
|
|
34413
|
+
let guard = 0;
|
|
34414
|
+
while (!acceptsReasoningEffortValue(this.modelId, value) && guard++ < 8) {
|
|
34415
|
+
const next = fallbackReasoningEffortValue(value);
|
|
34416
|
+
if (!next)
|
|
34417
|
+
return;
|
|
34418
|
+
value = next;
|
|
34419
|
+
}
|
|
34420
|
+
return value;
|
|
34421
|
+
}
|
|
34422
|
+
recoverFromRejection(payload, errorText) {
|
|
34423
|
+
if (!payload || payload.reasoning_effort === undefined)
|
|
34424
|
+
return null;
|
|
34425
|
+
if (isReasoningEffortRejection(errorText)) {
|
|
34426
|
+
rememberReasoningEffortRejected(this.modelId);
|
|
34427
|
+
const next = { ...payload };
|
|
34428
|
+
delete next.reasoning_effort;
|
|
34429
|
+
return { payload: next, note: `dropped reasoning_effort for ${this.modelId}` };
|
|
34430
|
+
}
|
|
34431
|
+
const rejected = rejectedReasoningEffortValue(errorText);
|
|
34432
|
+
if (rejected) {
|
|
34433
|
+
rememberReasoningEffortValueRejected(this.modelId, rejected);
|
|
34434
|
+
const fallback = fallbackReasoningEffortValue(rejected);
|
|
34435
|
+
const next = { ...payload };
|
|
34436
|
+
if (fallback) {
|
|
34437
|
+
next.reasoning_effort = fallback;
|
|
34438
|
+
return {
|
|
34439
|
+
payload: next,
|
|
34440
|
+
note: `reasoning_effort "${rejected}" -> "${fallback}" for ${this.modelId}`
|
|
34441
|
+
};
|
|
34442
|
+
}
|
|
34443
|
+
delete next.reasoning_effort;
|
|
34444
|
+
return { payload: next, note: `dropped unsupported reasoning_effort for ${this.modelId}` };
|
|
34202
34445
|
}
|
|
34446
|
+
return null;
|
|
34203
34447
|
}
|
|
34204
34448
|
parseXmlParameters(xmlContent) {
|
|
34205
34449
|
const params = {};
|
|
@@ -36079,6 +36323,45 @@ Do not invent a different filename, and do not derive one from the task. Claude
|
|
|
36079
36323
|
PLAN_MODE_RULES = [planFilePathRule];
|
|
36080
36324
|
});
|
|
36081
36325
|
|
|
36326
|
+
// src/behavior/rules/session-context.ts
|
|
36327
|
+
function hasInjectedSessionContext(systemText2, messages) {
|
|
36328
|
+
const haystacks = [systemText2 ?? ""];
|
|
36329
|
+
const first = Array.isArray(messages) ? messages[0] : undefined;
|
|
36330
|
+
if (first && typeof first === "object") {
|
|
36331
|
+
const content = first.content;
|
|
36332
|
+
if (typeof content === "string") {
|
|
36333
|
+
haystacks.push(content);
|
|
36334
|
+
} else if (Array.isArray(content)) {
|
|
36335
|
+
for (const block of content) {
|
|
36336
|
+
const text = block?.text;
|
|
36337
|
+
if (typeof text === "string")
|
|
36338
|
+
haystacks.push(text);
|
|
36339
|
+
}
|
|
36340
|
+
}
|
|
36341
|
+
}
|
|
36342
|
+
return haystacks.some((h) => {
|
|
36343
|
+
const lower = h.toLowerCase();
|
|
36344
|
+
return SESSION_CONTEXT_MARKERS.some((m) => lower.includes(m));
|
|
36345
|
+
});
|
|
36346
|
+
}
|
|
36347
|
+
var SESSION_CONTEXT_MARKERS, NOTE, noHarnessEchoRule, SESSION_CONTEXT_RULES;
|
|
36348
|
+
var init_session_context = __esm(() => {
|
|
36349
|
+
SESSION_CONTEXT_MARKERS = ["sessionstart hook additional context", "hook additional context"];
|
|
36350
|
+
NOTE = "Operational context note. This session may include text injected by the harness " + "itself \u2014 SessionStart hook output, coaching or insight blocks, tooling banners, " + "status lines. That material is addressed to you, not to the user, and it is not " + "part of the task. Never reproduce, summarise, or quote it in your answer, and never " + "let it open your response. Begin your answer with the requested deliverable and " + "nothing before it.";
|
|
36351
|
+
noHarnessEchoRule = {
|
|
36352
|
+
id: "session-context/no-harness-echo",
|
|
36353
|
+
description: "Stop foreign models opening their answer with harness-injected session context " + "(SessionStart hook output, coaching/insight blocks).",
|
|
36354
|
+
defaultSeverity: "fix",
|
|
36355
|
+
appliesTo: ({ isNativeAnthropic }) => !isNativeAnthropic,
|
|
36356
|
+
onRequest(ctx) {
|
|
36357
|
+
if (!hasInjectedSessionContext(ctx.systemText, ctx.messages))
|
|
36358
|
+
return [];
|
|
36359
|
+
return [{ type: "injectSystemNote", text: NOTE }];
|
|
36360
|
+
}
|
|
36361
|
+
};
|
|
36362
|
+
SESSION_CONTEXT_RULES = [noHarnessEchoRule];
|
|
36363
|
+
});
|
|
36364
|
+
|
|
36082
36365
|
// src/behavior/hooks.ts
|
|
36083
36366
|
import { isAbsolute, resolve } from "path";
|
|
36084
36367
|
function isBehaviorRule(value) {
|
|
@@ -36288,6 +36571,7 @@ var init_behavior = __esm(() => {
|
|
|
36288
36571
|
init_config();
|
|
36289
36572
|
init_engine();
|
|
36290
36573
|
init_plan_mode();
|
|
36574
|
+
init_session_context();
|
|
36291
36575
|
init_engine();
|
|
36292
36576
|
init_config();
|
|
36293
36577
|
init_harness();
|
|
@@ -36297,7 +36581,7 @@ var init_behavior = __esm(() => {
|
|
|
36297
36581
|
init_corpus();
|
|
36298
36582
|
init_aggregate();
|
|
36299
36583
|
init_upload();
|
|
36300
|
-
BUILTIN_RULES = [...PLAN_MODE_RULES];
|
|
36584
|
+
BUILTIN_RULES = [...PLAN_MODE_RULES, ...SESSION_CONTEXT_RULES];
|
|
36301
36585
|
hookRules = [];
|
|
36302
36586
|
});
|
|
36303
36587
|
|
|
@@ -40840,6 +41124,30 @@ class ComposedHandler {
|
|
|
40840
41124
|
}
|
|
40841
41125
|
log(`[${this.provider.displayName}] Response status: ${response.status}`);
|
|
40842
41126
|
this.capturePlanUsage(response);
|
|
41127
|
+
if (!response.ok) {
|
|
41128
|
+
if (response.status >= 400 && response.status < 500 && this.modelAdapter?.recoverFromRejection) {
|
|
41129
|
+
const errorText = await response.clone().text();
|
|
41130
|
+
const recovery = this.modelAdapter.recoverFromRejection(requestPayload, errorText);
|
|
41131
|
+
if (recovery) {
|
|
41132
|
+
log(`[${this.provider.displayName}] Parameter rejected \u2014 retrying: ${recovery.note}`);
|
|
41133
|
+
requestPayload = recovery.payload;
|
|
41134
|
+
const retrySerialized = this.provider.serializeBody?.(requestPayload);
|
|
41135
|
+
const retryHeaders = await this.provider.getHeaders();
|
|
41136
|
+
retryHeaders["Content-Type"] = retrySerialized?.contentType ?? "application/json";
|
|
41137
|
+
const retryResp = await fetch(endpoint, {
|
|
41138
|
+
method: "POST",
|
|
41139
|
+
headers: retryHeaders,
|
|
41140
|
+
body: retrySerialized?.body ?? JSON.stringify(requestPayload),
|
|
41141
|
+
...this.provider.getRequestInit?.() || {}
|
|
41142
|
+
});
|
|
41143
|
+
if (retryResp.ok) {
|
|
41144
|
+
response = retryResp;
|
|
41145
|
+
} else {
|
|
41146
|
+
log(`[${this.provider.displayName}] Retry after ${recovery.note} still failed ` + `(HTTP ${retryResp.status})`);
|
|
41147
|
+
}
|
|
41148
|
+
}
|
|
41149
|
+
}
|
|
41150
|
+
}
|
|
40843
41151
|
if (!response.ok) {
|
|
40844
41152
|
if (response.status === 401 && this.provider.forceRefreshAuth) {
|
|
40845
41153
|
log(`[${this.provider.displayName}] Got 401, forcing auth refresh and retrying`);
|
|
@@ -40934,7 +41242,8 @@ class ComposedHandler {
|
|
|
40934
41242
|
} else {
|
|
40935
41243
|
const errorText = await response.text();
|
|
40936
41244
|
log(`[${this.provider.displayName}] Error: ${errorText}`);
|
|
40937
|
-
const
|
|
41245
|
+
const transportTerminal = this.provider.classifyTerminalError?.(response.status, errorText);
|
|
41246
|
+
const hint = getRecoveryHint(response.status, errorText, this.provider.displayName, transportTerminal);
|
|
40938
41247
|
let parsedErrorBody;
|
|
40939
41248
|
try {
|
|
40940
41249
|
parsedErrorBody = JSON.parse(errorText);
|
|
@@ -40987,7 +41296,7 @@ class ComposedHandler {
|
|
|
40987
41296
|
const errorBody = parsedErrorBody ?? {
|
|
40988
41297
|
error: { type: "api_error", message: errorText }
|
|
40989
41298
|
};
|
|
40990
|
-
if (isTerminalError(response.status, errorText, isTerminal429(errorText))) {
|
|
41299
|
+
if (isTerminalError(response.status, errorText, transportTerminal ?? isTerminal429(errorText))) {
|
|
40991
41300
|
const surfaced = buildSurfacedErrorMessage({
|
|
40992
41301
|
providerDisplayName: this.provider.displayName,
|
|
40993
41302
|
status: response.status,
|
|
@@ -41363,14 +41672,17 @@ class ComposedHandler {
|
|
|
41363
41672
|
}
|
|
41364
41673
|
}
|
|
41365
41674
|
}
|
|
41366
|
-
function getRecoveryHint(status, errorText, providerName) {
|
|
41675
|
+
function getRecoveryHint(status, errorText, providerName, transportTerminal429) {
|
|
41367
41676
|
const lower = errorText.toLowerCase();
|
|
41368
41677
|
if (status === 503 || lower.includes("overloaded")) {
|
|
41369
41678
|
return "Provider overloaded. Retry or use a different model.";
|
|
41370
41679
|
}
|
|
41371
|
-
if (status === 429 && isTerminal429(errorText)) {
|
|
41680
|
+
if (status === 429 && (transportTerminal429 ?? isTerminal429(errorText))) {
|
|
41372
41681
|
return "Out of quota \u2014 check your plan & billing details. This won't recover on retry.";
|
|
41373
41682
|
}
|
|
41683
|
+
if (status === 429 && transportTerminal429 === false) {
|
|
41684
|
+
return "Rate limited. Wait, reduce concurrency, or check plan limits.";
|
|
41685
|
+
}
|
|
41374
41686
|
if (isQuotaExhaustionError(status, errorText)) {
|
|
41375
41687
|
return "Subscription allowance spent \u2014 this refills on the provider's own schedule (see the message below). Reducing concurrency won't help; switch model/provider or wait.";
|
|
41376
41688
|
}
|
|
@@ -44643,6 +44955,48 @@ var init_signal_watcher = __esm(() => {
|
|
|
44643
44955
|
QUESTION_PATTERNS = [/\?\s*$/m, /\bchoose\b.*:/im, /\bselect\b.*:/im, /\benter\b.*:/im];
|
|
44644
44956
|
});
|
|
44645
44957
|
|
|
44958
|
+
// src/process-tree.ts
|
|
44959
|
+
function signalProcessTree(proc, signal) {
|
|
44960
|
+
if (KILL_PROCESS_GROUP && proc.pid) {
|
|
44961
|
+
try {
|
|
44962
|
+
process.kill(-proc.pid, signal);
|
|
44963
|
+
} catch {}
|
|
44964
|
+
}
|
|
44965
|
+
try {
|
|
44966
|
+
if (!proc.killed)
|
|
44967
|
+
proc.kill(signal);
|
|
44968
|
+
} catch {}
|
|
44969
|
+
}
|
|
44970
|
+
function waitForExit(proc, ms) {
|
|
44971
|
+
if (proc.exitCode !== null || proc.signalCode !== null)
|
|
44972
|
+
return Promise.resolve(true);
|
|
44973
|
+
return new Promise((resolve2) => {
|
|
44974
|
+
let settled = false;
|
|
44975
|
+
const done = (value) => {
|
|
44976
|
+
if (settled)
|
|
44977
|
+
return;
|
|
44978
|
+
settled = true;
|
|
44979
|
+
proc.off("exit", onExit);
|
|
44980
|
+
resolve2(value);
|
|
44981
|
+
};
|
|
44982
|
+
const onExit = () => done(true);
|
|
44983
|
+
proc.once("exit", onExit);
|
|
44984
|
+
const t = setTimeout(() => done(false), ms);
|
|
44985
|
+
t.unref?.();
|
|
44986
|
+
});
|
|
44987
|
+
}
|
|
44988
|
+
async function terminateChildTree(proc, graceMs = TERMINATE_GRACE_MS) {
|
|
44989
|
+
signalProcessTree(proc, "SIGTERM");
|
|
44990
|
+
if (await waitForExit(proc, graceMs))
|
|
44991
|
+
return true;
|
|
44992
|
+
signalProcessTree(proc, "SIGKILL");
|
|
44993
|
+
return waitForExit(proc, graceMs);
|
|
44994
|
+
}
|
|
44995
|
+
var KILL_PROCESS_GROUP, TERMINATE_GRACE_MS = 5000;
|
|
44996
|
+
var init_process_tree = __esm(() => {
|
|
44997
|
+
KILL_PROCESS_GROUP = process.platform !== "win32";
|
|
44998
|
+
});
|
|
44999
|
+
|
|
44646
45000
|
// src/spawn-claudish.ts
|
|
44647
45001
|
function resolveClaudishSpawn(env = process.env) {
|
|
44648
45002
|
const bin = env[CLAUDISH_BIN_ENV]?.trim();
|
|
@@ -44699,7 +45053,8 @@ class SessionManager {
|
|
|
44699
45053
|
const proc = spawn(spawnTarget.command, [...spawnTarget.prefixArgs, ...args], {
|
|
44700
45054
|
cwd: opts.cwd ?? process.cwd(),
|
|
44701
45055
|
stdio: ["pipe", "pipe", "pipe"],
|
|
44702
|
-
shell: false
|
|
45056
|
+
shell: false,
|
|
45057
|
+
detached: KILL_PROCESS_GROUP
|
|
44703
45058
|
});
|
|
44704
45059
|
const scrollback = new ScrollbackBuffer(this.scrollbackCapacity);
|
|
44705
45060
|
const watcher = new SignalWatcher(sessionId2, (sid, data) => {
|
|
@@ -44779,10 +45134,10 @@ class SessionManager {
|
|
|
44779
45134
|
});
|
|
44780
45135
|
entry.timeoutHandle = setTimeout(() => {
|
|
44781
45136
|
if (!proc.killed) {
|
|
44782
|
-
proc
|
|
45137
|
+
signalProcessTree(proc, "SIGTERM");
|
|
44783
45138
|
entry.killHandle = setTimeout(() => {
|
|
44784
45139
|
try {
|
|
44785
|
-
proc
|
|
45140
|
+
signalProcessTree(proc, "SIGKILL");
|
|
44786
45141
|
} catch {}
|
|
44787
45142
|
}, KILL_GRACE_MS);
|
|
44788
45143
|
entry.info.status = "timeout";
|
|
@@ -44840,10 +45195,10 @@ class SessionManager {
|
|
|
44840
45195
|
entry.info.completedAt = new Date().toISOString();
|
|
44841
45196
|
entry.watcher.forceState("cancelled", "Session cancelled");
|
|
44842
45197
|
if (!entry.process.killed) {
|
|
44843
|
-
entry.process
|
|
45198
|
+
signalProcessTree(entry.process, "SIGTERM");
|
|
44844
45199
|
entry.killHandle = setTimeout(() => {
|
|
44845
45200
|
try {
|
|
44846
|
-
entry.process
|
|
45201
|
+
signalProcessTree(entry.process, "SIGKILL");
|
|
44847
45202
|
} catch {}
|
|
44848
45203
|
}, KILL_GRACE_MS);
|
|
44849
45204
|
}
|
|
@@ -44874,11 +45229,11 @@ class SessionManager {
|
|
|
44874
45229
|
const promises = [];
|
|
44875
45230
|
for (const [, entry] of this.sessions) {
|
|
44876
45231
|
if (!entry.process.killed) {
|
|
44877
|
-
entry.process
|
|
45232
|
+
signalProcessTree(entry.process, "SIGTERM");
|
|
44878
45233
|
promises.push(new Promise((resolve2) => {
|
|
44879
45234
|
const timeout = setTimeout(() => {
|
|
44880
45235
|
try {
|
|
44881
|
-
entry.process
|
|
45236
|
+
signalProcessTree(entry.process, "SIGKILL");
|
|
44882
45237
|
} catch {}
|
|
44883
45238
|
resolve2();
|
|
44884
45239
|
}, KILL_GRACE_MS);
|
|
@@ -44924,6 +45279,7 @@ class SessionManager {
|
|
|
44924
45279
|
}
|
|
44925
45280
|
var DEFAULT_MAX_SESSIONS = 20, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000;
|
|
44926
45281
|
var init_session_manager = __esm(() => {
|
|
45282
|
+
init_process_tree();
|
|
44927
45283
|
init_scrollback_buffer();
|
|
44928
45284
|
init_signal_watcher();
|
|
44929
45285
|
});
|
|
@@ -50810,6 +51166,7 @@ __export(exports_team_orchestrator, {
|
|
|
50810
51166
|
runModels: () => runModels,
|
|
50811
51167
|
resolveCaptureMode: () => resolveCaptureMode,
|
|
50812
51168
|
parseJudgeVotes: () => parseJudgeVotes,
|
|
51169
|
+
meaningfulStderr: () => meaningfulStderr,
|
|
50813
51170
|
judgeResponses: () => judgeResponses,
|
|
50814
51171
|
getStatus: () => getStatus,
|
|
50815
51172
|
fisherYatesShuffle: () => fisherYatesShuffle,
|
|
@@ -50818,6 +51175,9 @@ __export(exports_team_orchestrator, {
|
|
|
50818
51175
|
aggregateVerdict: () => aggregateVerdict,
|
|
50819
51176
|
TEAM_CAPTURE_ENV_VAR: () => TEAM_CAPTURE_ENV_VAR,
|
|
50820
51177
|
STDOUT_TAIL_LIMIT: () => STDOUT_TAIL_LIMIT,
|
|
51178
|
+
GRACE_INTERVAL_MS: () => GRACE_INTERVAL_MS,
|
|
51179
|
+
DRAIN_TIMEOUT_MS: () => DRAIN_TIMEOUT_MS,
|
|
51180
|
+
DEFAULT_STALL_SECONDS: () => DEFAULT_STALL_SECONDS,
|
|
50821
51181
|
DEFAULT_MIN_OUTPUT_BYTES: () => DEFAULT_MIN_OUTPUT_BYTES
|
|
50822
51182
|
});
|
|
50823
51183
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -50890,6 +51250,13 @@ function classifyRunOutput(opts) {
|
|
|
50890
51250
|
}
|
|
50891
51251
|
return null;
|
|
50892
51252
|
}
|
|
51253
|
+
function meaningfulStderr(stderr) {
|
|
51254
|
+
if (!stderr)
|
|
51255
|
+
return "";
|
|
51256
|
+
return stderr.split(`
|
|
51257
|
+
`).filter((line) => line.trim().length > 0).filter((line) => !BENIGN_STDERR_PATTERNS.some((re) => re.test(line))).join(`
|
|
51258
|
+
`).trim();
|
|
51259
|
+
}
|
|
50893
51260
|
function persistErrorLog(errorLogPath, header, stderr, stdoutTail) {
|
|
50894
51261
|
const parts = [`=== ${redactSecrets(header)} ===`, ""];
|
|
50895
51262
|
parts.push("--- stderr ---", stderr.trim() ? redactSecrets(stderr) : "(empty)", "");
|
|
@@ -51001,13 +51368,40 @@ async function runModels(sessionPath, opts = {}) {
|
|
|
51001
51368
|
const minOutputBytes = opts.minOutputBytes ?? DEFAULT_MIN_OUTPUT_BYTES;
|
|
51002
51369
|
const requirePattern = opts.requirePattern;
|
|
51003
51370
|
const captureMode = resolveCaptureMode(opts.captureMode);
|
|
51371
|
+
function reconcileTimedOutOutput(id, finalBytes, stdoutTail, stderr) {
|
|
51372
|
+
const current = statusCache.models[id];
|
|
51373
|
+
if (!current || current.state !== "TIMEOUT")
|
|
51374
|
+
return;
|
|
51375
|
+
if (finalBytes <= current.outputSize)
|
|
51376
|
+
return;
|
|
51377
|
+
const degraded = classifyRunOutput({
|
|
51378
|
+
outputSize: finalBytes,
|
|
51379
|
+
stdoutTail,
|
|
51380
|
+
stderr,
|
|
51381
|
+
minOutputBytes
|
|
51382
|
+
});
|
|
51383
|
+
if (degraded) {
|
|
51384
|
+
updateModelStatus(id, { outputSize: finalBytes });
|
|
51385
|
+
return;
|
|
51386
|
+
}
|
|
51387
|
+
updateModelStatus(id, {
|
|
51388
|
+
state: "COMPLETED",
|
|
51389
|
+
outputSize: finalBytes,
|
|
51390
|
+
completedAt: new Date().toISOString(),
|
|
51391
|
+
error: undefined
|
|
51392
|
+
});
|
|
51393
|
+
const note = `Recovered after the deadline: the child flushed ${finalBytes} B while shutting down, ` + "so its answer is complete and is being counted. The run still exceeded its timeout.";
|
|
51394
|
+
const rt = runtimes.get(id);
|
|
51395
|
+
if (rt)
|
|
51396
|
+
persistErrorLog(rt.errorLogPath, `RECOVERED: ${note}`, stderr, stdoutTail);
|
|
51397
|
+
opts.onStatusChange?.(id, statusCache.models[id]);
|
|
51398
|
+
}
|
|
51004
51399
|
mkdirSync12(statsDir(sessionPath), { recursive: true });
|
|
51005
51400
|
const processes = new Map;
|
|
51006
51401
|
const runtimes = new Map;
|
|
51007
51402
|
const sigintHandler = () => {
|
|
51008
51403
|
for (const [, proc] of processes) {
|
|
51009
|
-
|
|
51010
|
-
proc.kill("SIGTERM");
|
|
51404
|
+
signalProcessTree(proc, "SIGTERM");
|
|
51011
51405
|
}
|
|
51012
51406
|
process.exit(1);
|
|
51013
51407
|
};
|
|
@@ -51033,6 +51427,7 @@ async function runModels(sessionPath, opts = {}) {
|
|
|
51033
51427
|
const proc = spawn2(teamSpawnTarget.command, [...teamSpawnTarget.prefixArgs, ...args], {
|
|
51034
51428
|
stdio: ["pipe", "pipe", "pipe"],
|
|
51035
51429
|
shell: false,
|
|
51430
|
+
detached: KILL_PROCESS_GROUP,
|
|
51036
51431
|
env: {
|
|
51037
51432
|
...process.env,
|
|
51038
51433
|
CLAUDISH_TOKEN_FILE: tokenFileFor(sessionPath, anonId)
|
|
@@ -51093,6 +51488,7 @@ async function runModels(sessionPath, opts = {}) {
|
|
|
51093
51488
|
return;
|
|
51094
51489
|
if (statusCache.models[anonId].state === "TIMEOUT") {
|
|
51095
51490
|
resolved = true;
|
|
51491
|
+
reconcileTimedOutOutput(anonId, byteCount, stdoutTail, stderr);
|
|
51096
51492
|
resolve4();
|
|
51097
51493
|
return;
|
|
51098
51494
|
}
|
|
@@ -51150,13 +51546,8 @@ async function runModels(sessionPath, opts = {}) {
|
|
|
51150
51546
|
};
|
|
51151
51547
|
outputStream.on("close", finish);
|
|
51152
51548
|
proc.on("exit", (code) => {
|
|
51153
|
-
const
|
|
51154
|
-
if (
|
|
51155
|
-
resolved = true;
|
|
51156
|
-
resolve4();
|
|
51157
|
-
return;
|
|
51158
|
-
}
|
|
51159
|
-
if (stderr) {
|
|
51549
|
+
const timedOut = statusCache.models[anonId]?.state === "TIMEOUT";
|
|
51550
|
+
if (!timedOut && meaningfulStderr(stderr)) {
|
|
51160
51551
|
writeFileSync12(errorLogPath, redactSecrets(stderr), "utf-8");
|
|
51161
51552
|
}
|
|
51162
51553
|
exitCode = code;
|
|
@@ -51198,48 +51589,98 @@ async function runModels(sessionPath, opts = {}) {
|
|
|
51198
51589
|
emitProgress();
|
|
51199
51590
|
const progressHandle = setInterval(() => emitProgress("running"), POLL_MS);
|
|
51200
51591
|
progressHandle.unref?.();
|
|
51201
|
-
|
|
51202
|
-
|
|
51203
|
-
|
|
51204
|
-
|
|
51205
|
-
|
|
51206
|
-
|
|
51207
|
-
|
|
51208
|
-
|
|
51209
|
-
|
|
51210
|
-
|
|
51211
|
-
|
|
51212
|
-
|
|
51213
|
-
|
|
51214
|
-
|
|
51215
|
-
|
|
51216
|
-
|
|
51217
|
-
|
|
51218
|
-
|
|
51219
|
-
|
|
51220
|
-
|
|
51221
|
-
|
|
51222
|
-
|
|
51223
|
-
|
|
51224
|
-
|
|
51225
|
-
|
|
51226
|
-
|
|
51227
|
-
|
|
51228
|
-
|
|
51229
|
-
|
|
51230
|
-
|
|
51231
|
-
|
|
51232
|
-
|
|
51233
|
-
|
|
51234
|
-
|
|
51235
|
-
|
|
51592
|
+
const graceEnabled = opts.graceExtension ?? true;
|
|
51593
|
+
const maxGraceMs = Math.max(0, (opts.maxGraceSeconds ?? timeoutMs / 1000) * 1000);
|
|
51594
|
+
const stallMs = Math.max(0, (opts.stallSeconds ?? DEFAULT_STALL_SECONDS) * 1000);
|
|
51595
|
+
const idleMsFor = (id) => {
|
|
51596
|
+
const s = readTokenStats(sessionPath, id);
|
|
51597
|
+
if (!s || typeof s.updated_at !== "number" || s.updated_at <= 0)
|
|
51598
|
+
return null;
|
|
51599
|
+
return Math.max(0, Date.now() - s.updated_at);
|
|
51600
|
+
};
|
|
51601
|
+
const graceStartedAt = new Map;
|
|
51602
|
+
const graceUsedMs = (id, now) => {
|
|
51603
|
+
const start = graceStartedAt.get(id);
|
|
51604
|
+
return start === undefined ? 0 : Math.max(0, now - start);
|
|
51605
|
+
};
|
|
51606
|
+
const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
|
|
51607
|
+
const timeoutModel = async (id, why) => {
|
|
51608
|
+
const proc = processes.get(id);
|
|
51609
|
+
if (!proc || statusCache.models[id]?.state !== "RUNNING")
|
|
51610
|
+
return;
|
|
51611
|
+
const rt = runtimes.get(id);
|
|
51612
|
+
rt?.flushPartial();
|
|
51613
|
+
const stderr = rt?.getStderr() ?? "";
|
|
51614
|
+
const stdoutTail = rt?.getStdoutTail() ?? "";
|
|
51615
|
+
const bytes2 = rt?.getByteCount() ?? 0;
|
|
51616
|
+
const grace = graceUsedMs(id, Date.now());
|
|
51617
|
+
const detail = `Killed by the orchestrator after ${(timeoutMs + grace) / 1000}s ` + `(deadline ${timeoutMs / 1000}s${grace ? ` + ${grace / 1000}s grace` : ""}) ` + `with ${bytes2} B of stdout \u2014 ${why}. ` + "That figure counts the ANSWER, not the wire format, so 0 B means the child had " + `not produced an assistant message yet \u2014 "did not finish", not "produced nothing".`;
|
|
51618
|
+
if (rt)
|
|
51619
|
+
persistErrorLog(rt.errorLogPath, `TIMEOUT: ${detail}`, stderr, stdoutTail);
|
|
51620
|
+
updateModelStatus(id, {
|
|
51621
|
+
state: "TIMEOUT",
|
|
51622
|
+
completedAt: new Date().toISOString(),
|
|
51623
|
+
outputSize: bytes2,
|
|
51624
|
+
error: rt ? {
|
|
51625
|
+
model: id,
|
|
51626
|
+
command: rt.command,
|
|
51627
|
+
reason: "timeout",
|
|
51628
|
+
detail,
|
|
51629
|
+
stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
|
|
51630
|
+
stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
|
|
51631
|
+
errorLogPath: rt.errorLogPath,
|
|
51632
|
+
workDir: sessionPath
|
|
51633
|
+
} : undefined
|
|
51634
|
+
});
|
|
51635
|
+
opts.onStatusChange?.(id, statusCache.models[id]);
|
|
51636
|
+
const stopped = await terminateChildTree(proc);
|
|
51637
|
+
if (!stopped) {
|
|
51638
|
+
persistErrorLog(rt?.errorLogPath ?? join28(sessionPath, "errors", `${id}.log`), "TIMEOUT: child survived SIGKILL \u2014 it may still be running and billing", stderr, stdoutTail);
|
|
51639
|
+
}
|
|
51640
|
+
};
|
|
51641
|
+
const allDone = Promise.all(completionPromises);
|
|
51642
|
+
let settled = false;
|
|
51643
|
+
allDone.then(() => {
|
|
51644
|
+
settled = true;
|
|
51645
|
+
}, () => {
|
|
51646
|
+
settled = true;
|
|
51647
|
+
});
|
|
51648
|
+
const deadlineWatcher = (async () => {
|
|
51649
|
+
await delay(timeoutMs);
|
|
51650
|
+
for (;; ) {
|
|
51651
|
+
if (settled)
|
|
51652
|
+
return;
|
|
51653
|
+
const running = runningIds();
|
|
51654
|
+
if (running.length === 0)
|
|
51655
|
+
return;
|
|
51656
|
+
const extended = [];
|
|
51657
|
+
const now = Date.now();
|
|
51658
|
+
for (const id of running) {
|
|
51659
|
+
const idleMs = idleMsFor(id);
|
|
51660
|
+
const usedGrace = graceUsedMs(id, now);
|
|
51661
|
+
if (!graceEnabled) {
|
|
51662
|
+
await timeoutModel(id, "deadline reached (grace extension disabled)");
|
|
51663
|
+
} else if (usedGrace >= maxGraceMs) {
|
|
51664
|
+
await timeoutModel(id, `grace exhausted after ${Math.round(usedGrace / 1000)}s of extra time`);
|
|
51665
|
+
} else if (idleMs === null) {
|
|
51666
|
+
await timeoutModel(id, "deadline reached with no measurable progress to extend for");
|
|
51667
|
+
} else if (idleMs >= stallMs) {
|
|
51668
|
+
await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
|
|
51669
|
+
} else {
|
|
51670
|
+
if (!graceStartedAt.has(id))
|
|
51671
|
+
graceStartedAt.set(id, now);
|
|
51672
|
+
extended.push(id);
|
|
51236
51673
|
}
|
|
51237
|
-
|
|
51238
|
-
|
|
51239
|
-
|
|
51240
|
-
|
|
51241
|
-
|
|
51242
|
-
|
|
51674
|
+
}
|
|
51675
|
+
if (extended.length === 0)
|
|
51676
|
+
return;
|
|
51677
|
+
emitProgress("running");
|
|
51678
|
+
await delay(Math.min(GRACE_INTERVAL_MS, Math.max(1000, stallMs)));
|
|
51679
|
+
}
|
|
51680
|
+
})().catch(() => {});
|
|
51681
|
+
await Promise.race([allDone, deadlineWatcher]);
|
|
51682
|
+
if (!settled)
|
|
51683
|
+
await Promise.race([allDone, delay(DRAIN_TIMEOUT_MS)]);
|
|
51243
51684
|
clearInterval(progressHandle);
|
|
51244
51685
|
emitProgress("settled");
|
|
51245
51686
|
process.off("SIGINT", sigintHandler);
|
|
@@ -51425,14 +51866,19 @@ function formatVerdict(verdict, sessionPath) {
|
|
|
51425
51866
|
}
|
|
51426
51867
|
return output;
|
|
51427
51868
|
}
|
|
51428
|
-
var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0,
|
|
51869
|
+
var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, DRAIN_TIMEOUT_MS = 1e4, GRACE_INTERVAL_MS = 60000, DEFAULT_STALL_SECONDS = 90, delay = (ms) => new Promise((resolve4) => {
|
|
51870
|
+
const t = setTimeout(resolve4, ms);
|
|
51871
|
+
t.unref?.();
|
|
51872
|
+
}), BENIGN_STDERR_PATTERNS, SENTINEL_MODELS;
|
|
51429
51873
|
var init_team_orchestrator = __esm(() => {
|
|
51430
51874
|
init_prehydrate();
|
|
51875
|
+
init_process_tree();
|
|
51431
51876
|
init_redact();
|
|
51432
51877
|
init_team_stats();
|
|
51433
51878
|
init_team_stream_capture();
|
|
51434
51879
|
API_ERROR_RE = /\[API Error:\s*([^\]]{0,300})\]/i;
|
|
51435
51880
|
BG_CEILING_RE = /Background tasks still running after (\d+)s; terminating/i;
|
|
51881
|
+
BENIGN_STDERR_PATTERNS = [/^\s*\[claude-code:unrecognized_model\]/];
|
|
51436
51882
|
SENTINEL_MODELS = new Set([
|
|
51437
51883
|
"internal",
|
|
51438
51884
|
"default",
|
|
@@ -52976,6 +53422,11 @@ function findMagmuxBinary() {
|
|
|
52976
53422
|
throw new Error(`magmux not found. Install it:
|
|
52977
53423
|
brew install MadAppGang/tap/magmux`);
|
|
52978
53424
|
}
|
|
53425
|
+
function withoutControlPanes(evt) {
|
|
53426
|
+
if (!Array.isArray(evt.panes))
|
|
53427
|
+
return evt;
|
|
53428
|
+
return { ...evt, panes: evt.panes.filter((p) => p?.control !== true) };
|
|
53429
|
+
}
|
|
52979
53430
|
async function subscribeToMagmux(sockPath, onEvent) {
|
|
52980
53431
|
let client = null;
|
|
52981
53432
|
for (let attempt = 0;attempt < 40; attempt++) {
|
|
@@ -53012,7 +53463,7 @@ async function subscribeToMagmux(sockPath, onEvent) {
|
|
|
53012
53463
|
const evt = JSON.parse(line);
|
|
53013
53464
|
onEvent?.(evt);
|
|
53014
53465
|
if (evt.type === "results") {
|
|
53015
|
-
finalResults = evt;
|
|
53466
|
+
finalResults = withoutControlPanes(evt);
|
|
53016
53467
|
}
|
|
53017
53468
|
} catch {}
|
|
53018
53469
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claudish",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.53.0",
|
|
4
4
|
"description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -60,10 +60,10 @@
|
|
|
60
60
|
"ai"
|
|
61
61
|
],
|
|
62
62
|
"optionalDependencies": {
|
|
63
|
-
"@claudish/magmux-darwin-arm64": "7.
|
|
64
|
-
"@claudish/magmux-darwin-x64": "7.
|
|
65
|
-
"@claudish/magmux-linux-arm64": "7.
|
|
66
|
-
"@claudish/magmux-linux-x64": "7.
|
|
63
|
+
"@claudish/magmux-darwin-arm64": "7.53.0",
|
|
64
|
+
"@claudish/magmux-darwin-x64": "7.53.0",
|
|
65
|
+
"@claudish/magmux-linux-arm64": "7.53.0",
|
|
66
|
+
"@claudish/magmux-linux-x64": "7.53.0"
|
|
67
67
|
},
|
|
68
68
|
"author": "Jack Rudenko <i@madappgang.com>",
|
|
69
69
|
"license": "MIT",
|