@claudexor/harness-claude 3.0.4 → 3.1.1

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.
@@ -0,0 +1,339 @@
1
+ import { EffortHint } from "@claudexor/schema";
2
+ import { normalizeEffort, resolveEffort, runCapture } from "@claudexor/core";
3
+ import { nowIso, redactSecrets } from "@claudexor/util";
4
+ export const BIN = process.env.CLAUDEXOR_CLAUDE_BIN || "claude";
5
+ /**
6
+ * Recorded fallback, captured from `claude --help` on the CLI version stamped
7
+ * below. Used ONLY when the live parse cannot answer.
8
+ */
9
+ export const CLAUDE_EFFORT_SNAPSHOT = [
10
+ "low",
11
+ "medium",
12
+ "high",
13
+ "xhigh",
14
+ "max",
15
+ ];
16
+ /** Vendor CLI version `CLAUDE_EFFORT_SNAPSHOT` was captured from. */
17
+ export const CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST = "2.1.165";
18
+ /**
19
+ * Whether the recorded snapshot may be TRUSTED for arg emission against the
20
+ * installed binary (INV-105). The snapshot is another `--help`'s recorded
21
+ * answer, so it is only that binary's truth on the exact CLI version it was
22
+ * captured from: 2.1.165 advertises `xhigh`, 2.1.89 rejects it, and a 2.1.89
23
+ * install whose live parse failed used to be handed the 2.1.165 ladder anyway
24
+ * — `--effort xhigh` then went to a CLI that refuses the flag.
25
+ *
26
+ * The installed version string is whatever `claude --version` printed
27
+ * (e.g. `2.1.165 (Claude Code)`), so the comparison extracts the full dotted
28
+ * numeric token and requires it to EQUAL the snapshot stamp exactly. An
29
+ * unknown or unparseable version can never vouch for the snapshot.
30
+ */
31
+ export function claudeSnapshotTrustedForVersion(installedVersion) {
32
+ if (installedVersion === null)
33
+ return false;
34
+ const token = installedVersion.match(/\d+(?:\.\d+)+/);
35
+ return token !== null && token[0] === CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST;
36
+ }
37
+ /**
38
+ * The effort ladder the RUN may resolve `--effort` against, version-gating
39
+ * snapshot trust (INV-105):
40
+ *
41
+ * - a LIVE parse is the installed binary's own answer — trusted on any version;
42
+ * - the snapshot FALLBACK is trusted only when the installed version equals
43
+ * `CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST` (same binary, same ladder);
44
+ * - a fallback on ANY OTHER version (mismatch, unknown, unparseable) yields an
45
+ * EMPTY ladder: the normalizer then sends no `--effort` flag at all, and the
46
+ * existing drop seam (`claudeEffortIgnoredEvent`) discloses it — the run
47
+ * proceeds at the vendor default rather than forwarding a level another
48
+ * version's snapshot advertises to a binary that may reject it.
49
+ */
50
+ export function claudeAdvertisedEffortsForRun(efforts, installedVersion) {
51
+ if (efforts.live)
52
+ return efforts.levels;
53
+ return claudeSnapshotTrustedForVersion(installedVersion) ? efforts.levels : [];
54
+ }
55
+ /**
56
+ * The RUN's whole INV-105 effort seam in one place: probe the installed
57
+ * binary's ladder, version-gate snapshot-fallback trust
58
+ * (`claudeAdvertisedEffortsForRun`), and derive the DROP/CLAMP disclosure on
59
+ * the SAME advertised list the arg builder will resolve against — so the flag
60
+ * sent and the disclosure emitted can never disagree. The `--version` spawn
61
+ * happens only when it can matter (fallback ladder AND an effort actually
62
+ * requested); a live parse or a hint-less run never pays for it.
63
+ */
64
+ export async function claudeRunEffortResolution(spec, deps, abortSignal) {
65
+ const efforts = await deps.probeEffortLevels(abortSignal);
66
+ const advertised = efforts.live || !spec.effort_hint
67
+ ? efforts.levels
68
+ : claudeAdvertisedEffortsForRun(efforts, await deps.detectVersion(abortSignal));
69
+ return { advertised, disclosure: claudeEffortDisclosureEvent(spec, advertised) };
70
+ }
71
+ /**
72
+ * How many lines of a wrapped `--effort` block the parse will read. Generous
73
+ * next to the widest observed wrap (four lines at 40 columns) and still a hard
74
+ * stop, so an `--effort` line that documents nothing cannot reach into a later
75
+ * option's parentheses.
76
+ */
77
+ const EFFORT_HELP_BLOCK_MAX_LINES = 8;
78
+ /**
79
+ * Pull the advertised levels out of `claude --help` text, or null when the
80
+ * `--effort` line is absent or documents no values.
81
+ *
82
+ * Pure and separately testable: the spawn lives in the adapter's runtime deps so
83
+ * a test can feed recorded help text for any CLI version.
84
+ */
85
+ export function parseClaudeEffortHelp(help) {
86
+ const lines = help.split(/\r?\n/);
87
+ // Match the flag TOKEN, not a substring: `line.includes("--effort")` also
88
+ // matched `--effort-budget`, anchoring the parse on the wrong flag's block.
89
+ const start = lines.findIndex((line) => line.split(/[\s,]+/).some((token) => token === "--effort"));
90
+ if (start < 0)
91
+ return null;
92
+ // The value list wraps at the rendering width, and a narrower terminal pushes
93
+ // the closing paren further down (2.1.165 at 40 columns needs four lines), so
94
+ // a fixed line count silently loses the list. Read this flag's OWN block
95
+ // instead: everything up to the next documented flag or the blank line that
96
+ // ends the section, bounded so a paren from unrelated prose can never be
97
+ // mistaken for the effort list.
98
+ let end = start + 1;
99
+ for (; end < lines.length && end - start < EFFORT_HELP_BLOCK_MAX_LINES; end += 1) {
100
+ const next = lines[end] ?? "";
101
+ // ANY new option line ends the block, SHORT alias included. Matching only a
102
+ // leading `--` let a layout that renders aliases (` -m, --model <model>`)
103
+ // run straight past the next flag; the block then reached far enough that the
104
+ // LAST parenthesized group came from THAT flag instead — and a group like
105
+ // `(opus, sonnet, haiku)` is comma-separated lowercase slugs, so it passes as
106
+ // a value list and publishes model names as the effort ladder.
107
+ if (next.trim() === "" || /^\s*--?[A-Za-z0-9]/.test(next))
108
+ break;
109
+ }
110
+ const window = lines.slice(start, end).join(" ");
111
+ // The FIRST paren in the block is not necessarily the value list: a vendor
112
+ // annotation can precede it ("Effort level (beta) (low, medium, high, max)"),
113
+ // and `beta` is itself a well-formed slug, so anchoring on the first group
114
+ // published a bogus one-level ladder instead of falling back to the snapshot.
115
+ // Take the LAST group that actually looks like a value list — which also
116
+ // survives an annotation placed AFTER the list.
117
+ let levels = null;
118
+ for (const group of window.matchAll(/\(([^()]*)\)/g)) {
119
+ const parsed = readEffortGroup(group[1] ?? "");
120
+ // Same policy as the codex probe: ONE malformed token inside a value list
121
+ // fails the WHOLE parse (snapshot fallback). Skipping the token instead
122
+ // would publish a silently NARROWED ladder stamped as live — a real level
123
+ // would clamp away or refuse against a list the vendor never printed.
124
+ if (parsed === MALFORMED)
125
+ return null;
126
+ levels = parsed ?? levels;
127
+ }
128
+ return levels;
129
+ }
130
+ /**
131
+ * A parenthesized group that ENUMERATES like a value list but carries a token
132
+ * that is not an `EffortHint`. Distinct from `null` (not a value list at all —
133
+ * an annotation like `(beta)`, which is skipped): this poisons the whole parse.
134
+ */
135
+ const MALFORMED = Symbol("malformed-effort-group");
136
+ /**
137
+ * The levels one parenthesized group advertises, or null when the group is not a
138
+ * value list at all.
139
+ *
140
+ * A group qualifies only if it ENUMERATES (a comma or pipe): with no rank
141
+ * table, a lone `(high)` is indistinguishable from an annotation like
142
+ * `(beta)`, and the vendor's real value list has always enumerated. A help
143
+ * text that ever documents a genuine one-level ladder falls back to the
144
+ * recorded snapshot instead of guessing.
145
+ *
146
+ * STRICT inside an enumerating group, matching the codex probe's reviewed
147
+ * policy: a token that fails the `EffortHint` contract (prose, an over-long
148
+ * slug, invalid characters) is MALFORMED and fails the whole parse, because
149
+ * skipping it would publish a silently narrowed ladder as live — and the
150
+ * snapshot fallback is a real, usable ladder, so strictness costs freshness,
151
+ * never capability.
152
+ */
153
+ function readEffortGroup(body) {
154
+ if (!/[,|]/.test(body))
155
+ return null;
156
+ const levels = [];
157
+ for (const raw of body.split(/[,|]/)) {
158
+ const parsed = EffortHint.safeParse(raw.trim());
159
+ if (!parsed.success)
160
+ return MALFORMED;
161
+ if (!levels.includes(parsed.data))
162
+ levels.push(parsed.data);
163
+ }
164
+ return levels.length > 0 ? levels : null;
165
+ }
166
+ /**
167
+ * Wall clock the shared capture is bounded by. It replaces the caller abort
168
+ * signal that used to bound it, so the spawn still cannot hang forever without
169
+ * belonging to any one caller.
170
+ */
171
+ const HELP_PROBE_TIMEOUT_MS = 10_000;
172
+ let helpProbePromise = null;
173
+ /** What an abandoned caller reads, without the shared capture ever seeing it. */
174
+ function abandonedProbe() {
175
+ return { ok: false, error: "claude --help probe abandoned: the caller was cancelled" };
176
+ }
177
+ /**
178
+ * The ONE `claude --help` capture, owned by the module rather than by whichever
179
+ * caller happened to ask for it first. The readonly-flag probe and the effort
180
+ * ladder both ask the same installed binary the same question, so they share a
181
+ * single spawn rather than growing a second discovery mechanism.
182
+ *
183
+ * Two properties keep the sharing honest, and the first cache had neither:
184
+ *
185
+ * 1. NO CALLER'S ABORT SIGNAL REACHES THE SPAWN. Threading the first caller's
186
+ * signal in here made a process-wide resource the private property of one
187
+ * run: cancelling that run killed the capture, and the memo then handed the
188
+ * corpse to every later run. Its own wall clock bounds it instead.
189
+ * 2. ONLY AN ANSWER IS KEPT. A `--help` that never ran (spawn error) or that the
190
+ * timeout killed (`code === null`, no exit status of its own) says nothing
191
+ * about the installed binary, so keeping it turns one bad moment into a
192
+ * permanent one; those outcomes drop the memo and the next caller re-probes.
193
+ * A real exit is a fact about this binary — non-zero included — and stays.
194
+ *
195
+ * The stakes are higher than staleness. The ladder falls back to a snapshot
196
+ * recorded from CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST, so a poisoned memo on a
197
+ * machine running an older CLI does not merely lose freshness: it advertises and
198
+ * forwards `xhigh` to a binary that rejects it, for the life of the daemon.
199
+ */
200
+ function sharedHelpCapture() {
201
+ if (helpProbePromise)
202
+ return helpProbePromise;
203
+ const pending = (async () => {
204
+ try {
205
+ const result = await runCapture(BIN, ["--help"], {
206
+ timeoutMs: HELP_PROBE_TIMEOUT_MS,
207
+ cancelSignal: "SIGTERM",
208
+ cancelKillDelayMs: 0,
209
+ });
210
+ return { ok: true, help: `${result.stdout}\n${result.stderr}`, code: result.code };
211
+ }
212
+ catch (error) {
213
+ return {
214
+ ok: false,
215
+ error: redactSecrets(error instanceof Error ? error.message : String(error)),
216
+ };
217
+ }
218
+ })();
219
+ helpProbePromise = pending;
220
+ // Identity-guarded so a late settle can only ever clear its OWN memo, never a
221
+ // re-probe another caller has already started.
222
+ const forget = () => {
223
+ if (helpProbePromise === pending)
224
+ helpProbePromise = null;
225
+ };
226
+ void pending.then((probe) => {
227
+ if (!probe.ok || probe.code === null)
228
+ forget();
229
+ }, forget);
230
+ return pending;
231
+ }
232
+ /**
233
+ * The shared capture, with the caller's cancellation bounding only the CALLER'S
234
+ * OWN wait. An abandoned caller reads a probe failure — its run is going away
235
+ * anyway, and the ladder falls back to the snapshot for that one run — while the
236
+ * capture keeps running for everybody else.
237
+ */
238
+ export function probeClaudeHelp(abortSignal) {
239
+ const shared = sharedHelpCapture();
240
+ if (!abortSignal)
241
+ return shared;
242
+ if (abortSignal.aborted)
243
+ return Promise.resolve(abandonedProbe());
244
+ return new Promise((resolve) => {
245
+ const abandon = () => resolve(abandonedProbe());
246
+ abortSignal.addEventListener("abort", abandon, { once: true });
247
+ void shared.then((probe) => {
248
+ abortSignal.removeEventListener("abort", abandon);
249
+ resolve(probe);
250
+ });
251
+ });
252
+ }
253
+ /**
254
+ * The effort ladder the INSTALLED claude binary advertises, falling back to the
255
+ * recorded snapshot when `--help` cannot be read or no longer documents the
256
+ * values. A probe failure costs freshness, never the run.
257
+ */
258
+ export async function probeClaudeEffortLevels(abortSignal) {
259
+ const probe = await probeClaudeHelp(abortSignal);
260
+ const parsed = probe.ok ? parseClaudeEffortHelp(probe.help) : null;
261
+ return parsed ? { levels: parsed, live: true } : { levels: CLAUDE_EFFORT_SNAPSHOT, live: false };
262
+ }
263
+ /**
264
+ * The INV-105 disclosure for an effort the RUN itself could not honor, or null
265
+ * when nothing was dropped. Preflight validates against the manifest ladder,
266
+ * but the arg builder resolves against what the INSTALLED binary advertises at
267
+ * run time (an older CLI's `--help` can be narrower than the discovered
268
+ * manifest), and its normalizer answers "send no flag" — which without this
269
+ * event was a silent vendor-default run. The payload rides the same
270
+ * `ignored_settings` channel governance uses (QA-070 timeline warning).
271
+ */
272
+ export function claudeEffortIgnoredEvent(spec, advertised) {
273
+ if (!spec.effort_hint)
274
+ return null;
275
+ if (normalizeEffort(spec.effort_hint, advertised) !== null)
276
+ return null;
277
+ // An EMPTY advertised list is the version-gated snapshot distrust case
278
+ // (`claudeAdvertisedEffortsForRun`): the installed binary's ladder could not
279
+ // be read and the recorded snapshot belongs to a different CLI version, so
280
+ // the honest statement is "unverifiable", not "not accepted".
281
+ const detail = advertised.length > 0
282
+ ? `effort=${spec.effort_hint} (not accepted by the installed claude CLI; ` +
283
+ `it advertises: ${advertised.join(", ")}; the run used the vendor default)`
284
+ : `effort=${spec.effort_hint} (could not be verified against the installed claude CLI: ` +
285
+ "its effort ladder could not be read from --help, and the recorded snapshot was " +
286
+ `captured from CLI ${CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST}, a different version, ` +
287
+ "so no effort flag was sent; the run used the vendor default)";
288
+ return {
289
+ type: "message",
290
+ session_id: spec.session_id,
291
+ ts: nowIso(),
292
+ text: `[effort] ignored: ${detail}`,
293
+ payload: { ignored_settings: [detail] },
294
+ };
295
+ }
296
+ /**
297
+ * The INV-105 disclosure for an effort the resolution CLAMPED, or null when the
298
+ * level rode through verbatim (or was dropped — `claudeEffortIgnoredEvent`'s
299
+ * shape; the two are mutually exclusive: a drop sends no flag, a clamp sends a
300
+ * different one). Mirrors the codex clamp seam so a moved level is disclosed
301
+ * the same way on both adapters, and takes the SAME (advertised, ladder)
302
+ * inputs the arg builder's normalizer takes so the disclosure can never
303
+ * disagree with the flag actually sent.
304
+ *
305
+ * Reachability today: the arg builder resolves with the installed binary's own
306
+ * list as both advertised set and rank ladder (`normalizeEffort`'s two-arg
307
+ * form), and against one's own ladder every miss is a DROP, never a clamp —
308
+ * so with current call sites this event only fires if a rank ladder broader
309
+ * than the binary's list is ever threaded through (the codex-shaped future,
310
+ * e.g. `ultra` ranking above a max-capped binary and clamping onto `max`).
311
+ * The seam exists precisely so that future cannot be silent.
312
+ */
313
+ /**
314
+ * The one INV-105 seam the run yields: the DROP disclosure or the CLAMP
315
+ * disclosure, whichever applies (they are mutually exclusive by construction —
316
+ * a drop sends no flag, a clamp sends a different one), or null when the
317
+ * requested level rode through verbatim or nothing was requested.
318
+ */
319
+ export function claudeEffortDisclosureEvent(spec, advertised) {
320
+ return claudeEffortIgnoredEvent(spec, advertised) ?? claudeEffortClampedEvent(spec, advertised);
321
+ }
322
+ export function claudeEffortClampedEvent(spec, advertised, ladder = advertised) {
323
+ if (!spec.effort_hint)
324
+ return null;
325
+ const check = resolveEffort(spec.effort_hint, advertised, ladder);
326
+ if (check.status !== "ok" || !check.clamped || check.effort === null)
327
+ return null;
328
+ const detail = `effort=${spec.effort_hint} (clamped to ${check.effort}: the requested level is not ` +
329
+ `advertised by the installed claude CLI (it advertises: ${advertised.join(", ")}), ` +
330
+ `so the run sent ${check.effort}, the nearest level it advertises)`;
331
+ return {
332
+ type: "message",
333
+ session_id: spec.session_id,
334
+ ts: nowIso(),
335
+ text: `[effort] clamped: ${detail}`,
336
+ payload: { ignored_settings: [detail] },
337
+ };
338
+ }
339
+ //# sourceMappingURL=effort-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effort-probe.js","sourceRoot":"","sources":["../src/effort-probe.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import type { AuthSourceReadiness, HarnessRunSpec } from "@claudexor/schema";
1
+ import type { AuthSourceReadiness, EffortHint, HarnessRunSpec } from "@claudexor/schema";
2
2
  import type { HarnessAdapter } from "@claudexor/core";
3
3
  import { runCapture, runCliHarness, selectStrictAuthRoute } from "@claudexor/core";
4
4
  export { claudeAccountIdentity, defaultNativeClaudeConfigDir } from "./native-home.js";
5
5
  export { canonicalProfileConfigDir } from "./profile.js";
6
6
  import { smokeIsolatedApiKey, smokeIsolatedOAuthToken } from "./smoke.js";
7
- export declare const BIN: string;
7
+ import { probeClaudeEffortLevels } from "./effort-probe.js";
8
+ export { BIN, CLAUDE_EFFORT_SNAPSHOT } from "./effort-probe.js";
8
9
  export declare const CLAUDE_PROVIDER_ENV_DENYLIST: string[];
9
10
  /** Exported for focused route-policy tests; runtime uses this exact selector. */
10
11
  export declare const selectClaudeRunAuthRoute: typeof selectStrictAuthRoute;
@@ -13,6 +14,14 @@ export interface ClaudeReadonlyProfileProbe {
13
14
  missingFlags: string[];
14
15
  detail: string;
15
16
  }
17
+ /**
18
+ * Derived from the SHARED `--help` capture on every call instead of behind a
19
+ * second cache of its own. That cache had the same defect as the one under it —
20
+ * a probe that failed once (or that a cancelled run read) stayed failed for the
21
+ * process lifetime, so a long-lived daemon reported readonly enforcement
22
+ * unavailable forever. It bought nothing either: the spawn is already memoized,
23
+ * and what is left is a handful of `includes` over text we already hold.
24
+ */
16
25
  export declare function probeClaudeReadonlyProfile(abortSignal?: AbortSignal): Promise<ClaudeReadonlyProfileProbe>;
17
26
  declare function detectVersion(abortSignal?: AbortSignal): Promise<string | null>;
18
27
  /**
@@ -69,8 +78,13 @@ type ClaudeRuntimeDeps = {
69
78
  smokeIsolatedApiKey: typeof smokeIsolatedApiKey;
70
79
  smokeIsolatedOAuthToken: typeof smokeIsolatedOAuthToken;
71
80
  probeReadonlyProfile: typeof probeClaudeReadonlyProfile;
81
+ /** Effort ladder of the installed binary; falls back to the recorded snapshot. */
82
+ probeEffortLevels: typeof probeClaudeEffortLevels;
72
83
  runCliHarness: typeof runCliHarness;
73
84
  };
74
85
  export declare function createClaudeAdapter(deps?: Partial<ClaudeRuntimeDeps>): HarnessAdapter;
75
- export declare function claudeArgsForSpec(spec: HarnessRunSpec, interactive?: boolean, suppressBare?: boolean): string[];
86
+ export declare function claudeArgsForSpec(spec: HarnessRunSpec, interactive?: boolean, suppressBare?: boolean,
87
+ /** What the installed CLI advertises; the recorded snapshot by default so
88
+ * arg-shape callers stay synchronous and the probe stays optional. */
89
+ advertisedEfforts?: readonly EffortHint[]): string[];
76
90
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -10,16 +10,11 @@ import { createClaudeParser } from "./parse.js";
10
10
  import { probeClaudeCredentialProfile, resolveClaudeProfileRoute } from "./profile.js";
11
11
  export { canonicalProfileConfigDir } from "./profile.js";
12
12
  import { smokeIsolatedApiKey, smokeIsolatedOAuthToken } from "./smoke.js";
13
+ import { BIN, CLAUDE_EFFORT_SNAPSHOT, CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST, claudeRunEffortResolution, probeClaudeEffortLevels, probeClaudeHelp, } from "./effort-probe.js";
14
+ export { BIN, CLAUDE_EFFORT_SNAPSHOT } from "./effort-probe.js";
13
15
  import { claudeAttachmentBlocks, handleControlRequestFrame, initialSessionFrames, isControlRequestFrame, isResultFrame, } from "./interactive.js";
14
- export const BIN = process.env.CLAUDEXOR_CLAUDE_BIN || "claude";
15
16
  export const CLAUDE_PROVIDER_ENV_DENYLIST = PROVIDER_SECRET_ENV.filter((k) => k !== "ANTHROPIC_API_KEY");
16
- /**
17
- * Ordered (weakest→strongest) reasoning-effort levels `claude --effort` accepts.
18
- * Verified against the installed CLI (`claude --help`, v2.1.165): the full
19
- * ladder is low|medium|high|max. SINGLE source for the manifest's
20
- * `effort_levels` and the run-time normalizer (which now clamps nothing away).
21
- */
22
- const CLAUDE_EFFORT_LEVELS = ["low", "medium", "high", "max"];
17
+ // The `--effort` ladder is read from the INSTALLED binary (`probeClaudeEffortLevels`).
23
18
  /** Exported for focused route-policy tests; runtime uses this exact selector. */
24
19
  export const selectClaudeRunAuthRoute = selectStrictAuthRoute;
25
20
  function permissionArgs(access) {
@@ -54,40 +49,36 @@ const CLAUDE_READONLY_REQUIRED_FLAGS = [
54
49
  "--disable-slash-commands",
55
50
  "--no-chrome",
56
51
  ];
57
- let readonlyProbePromise = null;
58
- export function probeClaudeReadonlyProfile(abortSignal) {
59
- if (readonlyProbePromise)
60
- return readonlyProbePromise;
61
- readonlyProbePromise = (async () => {
62
- try {
63
- const result = await runCapture(BIN, ["--help"], {
64
- timeoutMs: 10_000,
65
- abortSignal,
66
- cancelSignal: "SIGTERM",
67
- cancelKillDelayMs: 0,
68
- });
69
- const help = `${result.stdout}\n${result.stderr}`;
70
- const missingFlags = CLAUDE_READONLY_REQUIRED_FLAGS.filter((flag) => !help.includes(flag));
71
- const hasPlanMode = help.includes('"plan"') || help.includes("plan,") || help.includes(", plan");
72
- if (!hasPlanMode)
73
- missingFlags.push("--permission-mode=plan");
74
- return {
75
- supported: result.code === 0 && missingFlags.length === 0,
76
- missingFlags,
77
- detail: result.code === 0 && missingFlags.length === 0
78
- ? "installed Claude CLI exposes the complete restrictive readonly flag set"
79
- : `readonly enforcement unavailable; missing ${missingFlags.join(", ") || `help exited ${result.code}`}`,
80
- };
81
- }
82
- catch (error) {
83
- return {
84
- supported: false,
85
- missingFlags: [...CLAUDE_READONLY_REQUIRED_FLAGS],
86
- detail: `readonly enforcement probe failed: ${redactSecrets(error instanceof Error ? error.message : String(error))}`,
87
- };
88
- }
89
- })();
90
- return readonlyProbePromise;
52
+ /**
53
+ * Derived from the SHARED `--help` capture on every call instead of behind a
54
+ * second cache of its own. That cache had the same defect as the one under it —
55
+ * a probe that failed once (or that a cancelled run read) stayed failed for the
56
+ * process lifetime, so a long-lived daemon reported readonly enforcement
57
+ * unavailable forever. It bought nothing either: the spawn is already memoized,
58
+ * and what is left is a handful of `includes` over text we already hold.
59
+ */
60
+ export async function probeClaudeReadonlyProfile(abortSignal) {
61
+ const probe = await probeClaudeHelp(abortSignal);
62
+ if (!probe.ok) {
63
+ return {
64
+ supported: false,
65
+ missingFlags: [...CLAUDE_READONLY_REQUIRED_FLAGS],
66
+ detail: `readonly enforcement probe failed: ${probe.error}`,
67
+ };
68
+ }
69
+ const help = probe.help;
70
+ const missingFlags = CLAUDE_READONLY_REQUIRED_FLAGS.filter((flag) => !help.includes(flag));
71
+ const hasPlanMode = help.includes('"plan"') || help.includes("plan,") || help.includes(", plan");
72
+ if (!hasPlanMode)
73
+ missingFlags.push("--permission-mode=plan");
74
+ const supported = probe.code === 0 && missingFlags.length === 0;
75
+ return {
76
+ supported,
77
+ missingFlags,
78
+ detail: supported
79
+ ? "installed Claude CLI exposes the complete restrictive readonly flag set"
80
+ : `readonly enforcement unavailable; missing ${missingFlags.join(", ") || `help exited ${probe.code}`}`,
81
+ };
91
82
  }
92
83
  async function detectVersion(abortSignal) {
93
84
  try {
@@ -219,6 +210,7 @@ export function createClaudeAdapter(deps = {}) {
219
210
  smokeIsolatedApiKey,
220
211
  smokeIsolatedOAuthToken,
221
212
  probeReadonlyProfile: probeClaudeReadonlyProfile,
213
+ probeEffortLevels: probeClaudeEffortLevels,
222
214
  runCliHarness,
223
215
  ...deps,
224
216
  };
@@ -231,6 +223,9 @@ export function createClaudeAdapter(deps = {}) {
231
223
  }
232
224
  const apiKey = runtime.anthropicApiKey() !== null;
233
225
  const readonlyProfile = await runtime.probeReadonlyProfile();
226
+ // The ladder belongs to the INSTALLED CLI, so read it from that binary
227
+ // rather than declaring one version's list for every version.
228
+ const efforts = await runtime.probeEffortLevels();
234
229
  const native = await runtime.probeAuthStatus(BIN, { env: claudeNativeEnv() });
235
230
  const authed = native.authed;
236
231
  const oauthTokenAvailable = runtime.claudeOAuthToken() !== null;
@@ -257,13 +252,23 @@ export function createClaudeAdapter(deps = {}) {
257
252
  browser_tool: true,
258
253
  // LIVE-VERIFIED (claude 2.1.165): `--json-schema <schema>` (inline JSON).
259
254
  json_schema_output: true,
255
+ // D-16: `--json-schema` materializes a StructuredOutput TOOL, so a
256
+ // WorkReport envelope rides the tool while the prose final stays
257
+ // markdown (side_tool). Interactive stream-json lanes disclose
258
+ // unsupported at spec build, not here.
259
+ work_report_transport: "constrained",
260
+ structured_output_channel: "side_tool",
260
261
  web_policy: "tools",
261
262
  max_turns: true,
262
263
  tool_lists: true,
263
264
  interactive: true,
264
- // claude --effort accepts low|medium|high|xhigh|max (verified against
265
- // the installed CLI's --help). Single source for the run-time normalizer.
266
- effort_levels: [...CLAUDE_EFFORT_LEVELS],
265
+ // Whatever THIS binary's --help documents; the snapshot fills in when
266
+ // the parse fails. Claude's ladder is CLI-wide, not per model, so
267
+ // `model_effort_levels` stays empty and every model falls back here.
268
+ effort_levels: [...efforts.levels],
269
+ effort_levels_verified_against: efforts.live
270
+ ? version
271
+ : CLAUDE_EFFORT_SNAPSHOT_VERIFIED_AGAINST,
267
272
  // Manifest model truth source (strict model-truth validation: an explicit model outside
268
273
  // this list is refused, never forwarded to die as a native error).
269
274
  // Stable aliases plus current full ids; verified against the vendor
@@ -519,7 +524,10 @@ const CLAUDE_READONLY_DENIED_TOOLS = [
519
524
  "Agent",
520
525
  "Skill",
521
526
  ];
522
- export function claudeArgsForSpec(spec, interactive = false, suppressBare = false) {
527
+ export function claudeArgsForSpec(spec, interactive = false, suppressBare = false,
528
+ /** What the installed CLI advertises; the recorded snapshot by default so
529
+ * arg-shape callers stay synchronous and the probe stays optional. */
530
+ advertisedEfforts = CLAUDE_EFFORT_SNAPSHOT) {
523
531
  // Interactive sessions deliver the prompt as a stream-json user message on
524
532
  // stdin (the control protocol's transport); one-shot runs keep the prompt arg.
525
533
  // `--permission-prompt-tool stdio` is the live-verified switch that routes
@@ -550,9 +558,11 @@ export function claudeArgsForSpec(spec, interactive = false, suppressBare = fals
550
558
  // W-C4 live deltas (engine-gated to single-candidate lanes; parser tags payload.delta).
551
559
  if (spec.stream_deltas)
552
560
  args.push("--include-partial-messages");
553
- // Clamp onto claude's declared effort ladder; null = not
554
- // requested OR not tunable -> pass no flag. Never sends an invalid level.
555
- const eff = normalizeEffort(spec.effort_hint, CLAUDE_EFFORT_LEVELS);
561
+ // Resolve against what the INSTALLED CLI advertises: an advertised level goes
562
+ // through verbatim (so a newer binary's level needs no code change here), a
563
+ // rankable one clamps, and anything else sends no flag rather than a level the
564
+ // vendor would reject. Null = not requested OR not tunable -> pass no flag.
565
+ const eff = normalizeEffort(spec.effort_hint, advertisedEfforts);
556
566
  if (eff)
557
567
  args.push("--effort", eff);
558
568
  if (spec.max_turns !== null && spec.max_turns > 0)
@@ -767,7 +777,15 @@ async function* runClaude(spec, runtime) {
767
777
  }
768
778
  }
769
779
  const useSubscription = route === "subscription";
770
- const args = claudeArgsForSpec(spec, interactive, useSubscription);
780
+ // Shared with discovery through the memoized --help probe, so a run never
781
+ // re-spawns the CLI just to learn its ladder.
782
+ // INV-105 on the RUN: probe the installed ladder, version-gate snapshot
783
+ // fallback trust (an installed 2.1.89 is never sent the 2.1.165 snapshot's
784
+ // xhigh), and disclose a DROP/CLAMP on the same inputs the args resolve with.
785
+ const effort = await claudeRunEffortResolution(spec, runtime, abortSignalFromSpec(spec));
786
+ const args = claudeArgsForSpec(spec, interactive, useSubscription, effort.advertised);
787
+ if (effort.disclosure)
788
+ yield effort.disclosure;
771
789
  // Scrub EVERY provider secret (incl. OpenAI/others — the cross-provider leak
772
790
  // fix) via the single core table, then re-add only the var this route needs.
773
791
  const env = subscriptionSource === "native_session" ? nativeEnv : { ...spec.env, ...providerScrubEnv() };