@dungle-scrubs/harness-cli-normalizer 0.6.0 → 0.6.2
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/README.md +33 -6
- package/dist/cli/args.d.ts +9 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +36 -26
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/check.d.ts +14 -0
- package/dist/cli/check.d.ts.map +1 -1
- package/dist/cli/check.js +10 -3
- package/dist/cli/check.js.map +1 -1
- package/dist/cli/config.d.ts +4 -4
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +8 -6
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/exit-codes.d.ts +0 -1
- package/dist/cli/exit-codes.d.ts.map +1 -1
- package/dist/cli/exit-codes.js +0 -1
- package/dist/cli/exit-codes.js.map +1 -1
- package/dist/cli/help.d.ts +2 -2
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +10 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +84 -265
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/plan-turn.d.ts +68 -0
- package/dist/cli/plan-turn.d.ts.map +1 -0
- package/dist/cli/plan-turn.js +268 -0
- package/dist/cli/plan-turn.js.map +1 -0
- package/dist/cli/refuse.d.ts.map +1 -1
- package/dist/cli/refuse.js +2 -1
- package/dist/cli/refuse.js.map +1 -1
- package/dist/cli/resolve-harness.d.ts.map +1 -1
- package/dist/cli/resolve-harness.js +2 -3
- package/dist/cli/resolve-harness.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +33 -367
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/session-json.d.ts +1 -1
- package/dist/cli/session-json.d.ts.map +1 -1
- package/dist/cli/session-json.js +1 -3
- package/dist/cli/session-json.js.map +1 -1
- package/dist/cli/session.d.ts.map +1 -1
- package/dist/cli/session.js +35 -43
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/skills-root.d.ts +1 -1
- package/dist/cli/skills-root.d.ts.map +1 -1
- package/dist/cli/skills-root.js +1 -1
- package/dist/cli/skills-root.js.map +1 -1
- package/dist/execution/decode.d.ts.map +1 -1
- package/dist/execution/decode.js +11 -36
- package/dist/execution/decode.js.map +1 -1
- package/dist/execution/events.d.ts +4 -1
- package/dist/execution/events.d.ts.map +1 -1
- package/dist/execution/events.js.map +1 -1
- package/dist/execution/failure.d.ts +3 -1
- package/dist/execution/failure.d.ts.map +1 -1
- package/dist/execution/failure.js +14 -11
- package/dist/execution/failure.js.map +1 -1
- package/dist/execution/open-session.d.ts +1 -1
- package/dist/execution/open-session.d.ts.map +1 -1
- package/dist/execution/open-session.js +150 -223
- package/dist/execution/open-session.js.map +1 -1
- package/dist/execution/stream-turn.d.ts +9 -14
- package/dist/execution/stream-turn.d.ts.map +1 -1
- package/dist/execution/stream-turn.js +85 -188
- package/dist/execution/stream-turn.js.map +1 -1
- package/dist/execution/supervisor.d.ts +71 -0
- package/dist/execution/supervisor.d.ts.map +1 -0
- package/dist/execution/supervisor.js +127 -0
- package/dist/execution/supervisor.js.map +1 -0
- package/dist/interpretation/argv.d.ts +39 -12
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +28 -20
- package/dist/interpretation/argv.js.map +1 -1
- package/dist/interpretation/content.d.ts +10 -1
- package/dist/interpretation/content.d.ts.map +1 -1
- package/dist/interpretation/content.js +20 -11
- package/dist/interpretation/content.js.map +1 -1
- package/dist/interpretation/hints.d.ts +1 -1
- package/dist/interpretation/hints.d.ts.map +1 -1
- package/dist/interpretation/hints.js +10 -11
- package/dist/interpretation/hints.js.map +1 -1
- package/dist/interpretation/index.d.ts +0 -1
- package/dist/interpretation/index.d.ts.map +1 -1
- package/dist/interpretation/index.js +0 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/isolation.d.ts +10 -0
- package/dist/interpretation/isolation.d.ts.map +1 -0
- package/dist/interpretation/isolation.js +30 -0
- package/dist/interpretation/isolation.js.map +1 -0
- package/dist/interpretation/limits.d.ts +2 -5
- package/dist/interpretation/limits.d.ts.map +1 -1
- package/dist/interpretation/limits.js +8 -62
- package/dist/interpretation/limits.js.map +1 -1
- package/dist/interpretation/question.d.ts +27 -0
- package/dist/interpretation/question.d.ts.map +1 -1
- package/dist/interpretation/question.js +28 -0
- package/dist/interpretation/question.js.map +1 -1
- package/dist/interpretation/refusal.d.ts +10 -5
- package/dist/interpretation/refusal.d.ts.map +1 -1
- package/dist/interpretation/refusal.js +24 -15
- package/dist/interpretation/refusal.js.map +1 -1
- package/dist/interpretation/resolve-options.d.ts +36 -2
- package/dist/interpretation/resolve-options.d.ts.map +1 -1
- package/dist/interpretation/resolve-options.js +53 -22
- package/dist/interpretation/resolve-options.js.map +1 -1
- package/dist/interpretation/session-input.d.ts +44 -1
- package/dist/interpretation/session-input.d.ts.map +1 -1
- package/dist/interpretation/session-input.js +59 -4
- package/dist/interpretation/session-input.js.map +1 -1
- package/dist/interpretation/shape.d.ts +1 -0
- package/dist/interpretation/shape.d.ts.map +1 -1
- package/dist/interpretation/shape.js +10 -0
- package/dist/interpretation/shape.js.map +1 -1
- package/dist/interpretation/skills-selection.d.ts +25 -33
- package/dist/interpretation/skills-selection.d.ts.map +1 -1
- package/dist/interpretation/skills-selection.js +41 -48
- package/dist/interpretation/skills-selection.js.map +1 -1
- package/dist/interpretation/support.d.ts.map +1 -1
- package/dist/interpretation/support.js +25 -8
- package/dist/interpretation/support.js.map +1 -1
- package/dist/interpretation/tool-selection.d.ts +7 -6
- package/dist/interpretation/tool-selection.d.ts.map +1 -1
- package/dist/interpretation/tool-selection.js +54 -116
- package/dist/interpretation/tool-selection.js.map +1 -1
- package/dist/interpretation/tool-vocabulary.d.ts +31 -12
- package/dist/interpretation/tool-vocabulary.d.ts.map +1 -1
- package/dist/interpretation/tool-vocabulary.js +19 -41
- package/dist/interpretation/tool-vocabulary.js.map +1 -1
- package/dist/interpretation/turn-options.d.ts +4 -0
- package/dist/interpretation/turn-options.d.ts.map +1 -1
- package/dist/interpretation/turn-options.js +107 -241
- package/dist/interpretation/turn-options.js.map +1 -1
- package/dist/interpretation/vocabulary.d.ts +8 -6
- package/dist/interpretation/vocabulary.d.ts.map +1 -1
- package/dist/interpretation/vocabulary.js +22 -9
- package/dist/interpretation/vocabulary.js.map +1 -1
- package/dist/knowledge/claude-code.d.ts +3 -1
- package/dist/knowledge/claude-code.d.ts.map +1 -1
- package/dist/knowledge/claude-code.js +17 -4
- package/dist/knowledge/claude-code.js.map +1 -1
- package/dist/knowledge/codex.d.ts.map +1 -1
- package/dist/knowledge/codex.js +31 -7
- package/dist/knowledge/codex.js.map +1 -1
- package/dist/knowledge/descriptor.d.ts +67 -36
- package/dist/knowledge/descriptor.d.ts.map +1 -1
- package/dist/knowledge/descriptor.js +50 -15
- package/dist/knowledge/descriptor.js.map +1 -1
- package/dist/knowledge/matchers.d.ts +12 -0
- package/dist/knowledge/matchers.d.ts.map +1 -1
- package/dist/knowledge/matchers.js +34 -0
- package/dist/knowledge/matchers.js.map +1 -1
- package/dist/knowledge/muse.d.ts.map +1 -1
- package/dist/knowledge/muse.js +13 -6
- package/dist/knowledge/muse.js.map +1 -1
- package/dist/knowledge/overrides.d.ts.map +1 -1
- package/dist/knowledge/overrides.js +9 -20
- package/dist/knowledge/overrides.js.map +1 -1
- package/dist/knowledge/pi.d.ts.map +1 -1
- package/dist/knowledge/pi.js +8 -6
- package/dist/knowledge/pi.js.map +1 -1
- package/dist/knowledge/profile.d.ts +1 -0
- package/dist/knowledge/profile.d.ts.map +1 -1
- package/dist/knowledge/profile.js +1 -0
- package/dist/knowledge/profile.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/args.ts +39 -27
- package/src/cli/check.ts +10 -3
- package/src/cli/config.ts +14 -11
- package/src/cli/exit-codes.ts +0 -2
- package/src/cli/help.ts +10 -2
- package/src/cli/index.ts +3 -1
- package/src/cli/inspect.ts +88 -269
- package/src/cli/plan-turn.ts +350 -0
- package/src/cli/refuse.ts +2 -1
- package/src/cli/resolve-harness.ts +4 -5
- package/src/cli/run.ts +36 -399
- package/src/cli/session-json.ts +1 -6
- package/src/cli/session.ts +51 -44
- package/src/cli/skills-root.ts +1 -4
- package/src/execution/decode.ts +10 -36
- package/src/execution/events.ts +4 -1
- package/src/execution/failure.ts +18 -11
- package/src/execution/open-session.ts +154 -228
- package/src/execution/stream-turn.ts +90 -193
- package/src/execution/supervisor.ts +199 -0
- package/src/interpretation/argv.ts +65 -34
- package/src/interpretation/content.ts +30 -13
- package/src/interpretation/hints.ts +2 -12
- package/src/interpretation/index.ts +0 -1
- package/src/interpretation/isolation.ts +39 -0
- package/src/interpretation/limits.ts +14 -75
- package/src/interpretation/question.ts +46 -0
- package/src/interpretation/refusal.ts +28 -18
- package/src/interpretation/resolve-options.ts +98 -33
- package/src/interpretation/session-input.ts +88 -5
- package/src/interpretation/shape.ts +10 -0
- package/src/interpretation/skills-selection.ts +70 -74
- package/src/interpretation/support.ts +21 -6
- package/src/interpretation/tool-selection.ts +67 -122
- package/src/interpretation/tool-vocabulary.ts +48 -53
- package/src/interpretation/turn-options.ts +125 -220
- package/src/interpretation/vocabulary.ts +15 -10
- package/src/knowledge/claude-code.ts +17 -4
- package/src/knowledge/codex.ts +31 -7
- package/src/knowledge/descriptor.ts +106 -49
- package/src/knowledge/matchers.ts +35 -0
- package/src/knowledge/muse.ts +13 -6
- package/src/knowledge/overrides.ts +17 -32
- package/src/knowledge/pi.ts +8 -6
- package/src/knowledge/profile.ts +1 -0
- package/dist/interpretation/dimensions.d.ts +0 -11
- package/dist/interpretation/dimensions.d.ts.map +0 -1
- package/dist/interpretation/dimensions.js +0 -4
- package/dist/interpretation/dimensions.js.map +0 -1
- package/src/interpretation/dimensions.ts +0 -14
|
@@ -8,21 +8,29 @@
|
|
|
8
8
|
* it must refuse - is a distinct responsibility with its own vocabulary.
|
|
9
9
|
* Folding it into argv.ts would make the largest interpretation file the
|
|
10
10
|
* place two unrelated questions are answered.
|
|
11
|
+
*
|
|
12
|
+
* The render-to-tokens rule itself has one owner, `tokensFor` in the
|
|
13
|
+
* knowledge layer (RFC-02 change 1): every arm here validates its value,
|
|
14
|
+
* resolves the render for the phase, and calls it.
|
|
11
15
|
*/
|
|
12
|
-
import type {
|
|
13
|
-
import {
|
|
16
|
+
import type { AccessValue, HarnessDescriptor, SpecBase } from "../knowledge/descriptor.js";
|
|
17
|
+
import {
|
|
18
|
+
ACCESS_VALUES,
|
|
19
|
+
DISCOVERY_FACETS,
|
|
20
|
+
resolveRender,
|
|
21
|
+
TURN_OPTION_KEYS,
|
|
22
|
+
tokensFor,
|
|
23
|
+
} from "../knowledge/descriptor.js";
|
|
24
|
+
import { defaultDescriptors } from "../knowledge/overrides.js";
|
|
14
25
|
import type { DiscoveryOptions, TurnOptions } from "./argv.js";
|
|
15
26
|
import { hintFor } from "./hints.js";
|
|
27
|
+
import { assertIsolationCombination } from "./isolation.js";
|
|
16
28
|
import { ArgvRefusalError } from "./refusal.js";
|
|
29
|
+
import { supportedBy } from "./support.js";
|
|
17
30
|
import { renderToolSelection } from "./tool-selection.js";
|
|
18
|
-
import { READ_PRESET } from "./tool-vocabulary.js";
|
|
31
|
+
import { canonicalTable, hasCounterpart, READ_PRESET } from "./tool-vocabulary.js";
|
|
19
32
|
import { CLEAN_SELECTOR, resolveModel, validateAccess, validateEffort } from "./vocabulary.js";
|
|
20
33
|
|
|
21
|
-
// TOML-quoted value for config-kv: JSON.stringify is sufficient for the
|
|
22
|
-
// closed vocabularies that may use it (enum, effort) - no value contains a
|
|
23
|
-
// quote that would need escaping beyond JSON's.
|
|
24
|
-
const tomlQuote = (value: string): string => JSON.stringify(value);
|
|
25
|
-
|
|
26
34
|
const isPlainObject = (v: unknown): v is Record<string, unknown> =>
|
|
27
35
|
typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28
36
|
|
|
@@ -34,21 +42,12 @@ export const renderTurnOptions = (
|
|
|
34
42
|
opts: TurnOptions,
|
|
35
43
|
phase: "launch" | "resume",
|
|
36
44
|
): string[] => {
|
|
45
|
+
assertIsolationCombination(h, opts);
|
|
37
46
|
const sequences: string[][] = [];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!v.ok) {
|
|
43
|
-
throw new ArgvRefusalError({
|
|
44
|
-
issue: "invalid-option-value",
|
|
45
|
-
harness: h.name,
|
|
46
|
-
option: "access",
|
|
47
|
-
supported: ["read", "write"],
|
|
48
|
-
detail: String(accessRaw),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
47
|
+
// The turn option the access preset displaces when set (codex: sandbox),
|
|
48
|
+
// read from the descriptor so no arm below branches on a harness name.
|
|
49
|
+
const accessSpec = h.turnOptions.access;
|
|
50
|
+
const claimed = accessSpec?.kind === "access" ? accessSpec.claims : undefined;
|
|
52
51
|
|
|
53
52
|
for (const key of TURN_OPTION_KEYS) {
|
|
54
53
|
const spec = h.turnOptions[key];
|
|
@@ -109,16 +108,11 @@ export const renderTurnOptions = (
|
|
|
109
108
|
for (const facet of DISCOVERY_FACETS) {
|
|
110
109
|
const val = discObj[facet];
|
|
111
110
|
if (val === undefined) continue;
|
|
112
|
-
// Polarity handling: disables emits on false, enables emits on true
|
|
113
111
|
const facetSpec = spec.facets[facet];
|
|
114
112
|
if (facetSpec === undefined) {
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// For all current descriptors polarity is disables, so false would emit -> refuse
|
|
119
|
-
// For safety, check wouldEmit logic
|
|
120
|
-
// If val would not emit anyway, skip
|
|
121
|
-
// Determine wouldEmit: if facetSpec undefined, we cannot know polarity, so treat false as intended disable -> refuse
|
|
113
|
+
// A facet the descriptor does not declare cannot be expressed.
|
|
114
|
+
// Only a value that would have emitted refuses: every current
|
|
115
|
+
// facet has "disables" polarity, so false would emit.
|
|
122
116
|
if (val === false) {
|
|
123
117
|
const supportedFacets = Object.keys(spec.facets);
|
|
124
118
|
throw new ArgvRefusalError({
|
|
@@ -132,26 +126,15 @@ export const renderTurnOptions = (
|
|
|
132
126
|
}
|
|
133
127
|
continue;
|
|
134
128
|
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
else {
|
|
140
|
-
// true for disables or false for enables => no-op
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
129
|
+
// Polarity: disables emits on false, enables emits on true.
|
|
130
|
+
const shouldEmit =
|
|
131
|
+
(facetSpec.polarity === "disables" && val === false) ||
|
|
132
|
+
(facetSpec.polarity === "enables" && val === true);
|
|
143
133
|
if (!shouldEmit) continue;
|
|
144
|
-
// Check resumeRender refusal
|
|
145
134
|
const effective = resolveRender(facetSpec, phase);
|
|
146
135
|
if (effective === null) {
|
|
147
136
|
const resumeSupported = Object.entries(spec.facets)
|
|
148
|
-
.filter(
|
|
149
|
-
([, v]) =>
|
|
150
|
-
resolveRender(
|
|
151
|
-
v as unknown as import("../knowledge/descriptor.js").SpecBase,
|
|
152
|
-
"resume",
|
|
153
|
-
) !== null,
|
|
154
|
-
)
|
|
137
|
+
.filter(([, v]) => resolveRender(v as SpecBase, "resume") !== null)
|
|
155
138
|
.map(([k]) => k);
|
|
156
139
|
throw new ArgvRefusalError({
|
|
157
140
|
issue: "unsupported-on-resume",
|
|
@@ -161,8 +144,8 @@ export const renderTurnOptions = (
|
|
|
161
144
|
supported: resumeSupported.length ? resumeSupported : ["(none resume)"],
|
|
162
145
|
});
|
|
163
146
|
}
|
|
164
|
-
//
|
|
165
|
-
if (
|
|
147
|
+
// Discovery is not a closed vocabulary, so config-kv cannot carry it.
|
|
148
|
+
if (effective.kind === "config-kv") {
|
|
166
149
|
throw new ArgvRefusalError({
|
|
167
150
|
issue: "invalid-option-value",
|
|
168
151
|
harness: h.name,
|
|
@@ -171,21 +154,17 @@ export const renderTurnOptions = (
|
|
|
171
154
|
supported: ["flag-list only for discovery"],
|
|
172
155
|
});
|
|
173
156
|
}
|
|
174
|
-
|
|
175
|
-
if ((effective as OptionRender).kind === "flag-list")
|
|
176
|
-
seq = [...(effective as Extract<OptionRender, { kind: "flag-list" }>).flags];
|
|
177
|
-
else if ((effective as OptionRender).kind === "flag-value")
|
|
178
|
-
seq = [(effective as Extract<OptionRender, { kind: "flag-value" }>).flag, ""];
|
|
179
|
-
else seq = [];
|
|
180
|
-
sequences.push(seq);
|
|
157
|
+
sequences.push([...tokensFor(effective)]);
|
|
181
158
|
}
|
|
182
159
|
continue;
|
|
183
160
|
}
|
|
184
161
|
|
|
185
|
-
// Access is opt-in-only, no profile default
|
|
162
|
+
// Access is opt-in-only, no profile default. One arm for every harness:
|
|
163
|
+
// the descriptor says whether a value renders through the tool list, a
|
|
164
|
+
// phase-aware render, or nothing at all.
|
|
186
165
|
if (key === "access") {
|
|
187
166
|
if (raw === undefined) continue;
|
|
188
|
-
if (spec === undefined) {
|
|
167
|
+
if (spec === undefined || spec.kind !== "access") {
|
|
189
168
|
throw new ArgvRefusalError({
|
|
190
169
|
issue: "unsupported-option",
|
|
191
170
|
harness: h.name,
|
|
@@ -195,93 +174,63 @@ export const renderTurnOptions = (
|
|
|
195
174
|
hint: hintFor(h.name, key),
|
|
196
175
|
});
|
|
197
176
|
}
|
|
198
|
-
|
|
177
|
+
// The resume path builds from unresolved options, so the value is
|
|
178
|
+
// checked here as well as in option resolution - one rule, two gates.
|
|
179
|
+
const v = validateAccess(String(raw));
|
|
199
180
|
if (!v.ok) {
|
|
200
181
|
throw new ArgvRefusalError({
|
|
201
182
|
issue: "invalid-option-value",
|
|
202
183
|
harness: h.name,
|
|
203
184
|
option: key,
|
|
204
|
-
supported: [
|
|
185
|
+
supported: [...ACCESS_VALUES],
|
|
205
186
|
detail: String(raw),
|
|
206
187
|
});
|
|
207
188
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
189
|
+
const value = v.id as AccessValue;
|
|
190
|
+
const target = spec.renders[value];
|
|
191
|
+
if (target === null) continue;
|
|
192
|
+
if (target === "tool-preset") {
|
|
193
|
+
// The read preset rides the tool list. When discovery.tools is off,
|
|
194
|
+
// it must not switch tools back on; write is no restriction.
|
|
195
|
+
if (value !== "read" || opts.discovery?.tools === false) continue;
|
|
196
|
+
const table = canonicalTable(defaultDescriptors());
|
|
197
|
+
const filtered = (READ_PRESET as readonly string[]).filter((c) =>
|
|
198
|
+
hasCounterpart(table, c, h.name, opts.toolMap),
|
|
199
|
+
);
|
|
200
|
+
if (filtered.length === 0) continue;
|
|
201
|
+
const rendered = renderToolSelection(h, { include: filtered, toolMap: opts.toolMap });
|
|
202
|
+
if (rendered.tokens.length > 0) sequences.push([...rendered.tokens]);
|
|
203
|
+
continue;
|
|
211
204
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (h.tools.categories.some((cat) => (cat.canonical as readonly string[]).includes(c)))
|
|
221
|
-
return true;
|
|
222
|
-
return false;
|
|
223
|
-
});
|
|
224
|
-
if (filtered.length === 0) break;
|
|
225
|
-
const rendered = renderToolSelection(h, {
|
|
226
|
-
include: filtered as unknown as string[],
|
|
227
|
-
toolMap: toolMapForHarness,
|
|
228
|
-
});
|
|
229
|
-
if (rendered.tokens.length > 0) sequences.push([...rendered.tokens]);
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
case "flag-value": {
|
|
233
|
-
const fv = spec as Extract<typeof spec, { kind: "flag-value" }>;
|
|
234
|
-
const mapped = fv.values[raw as string];
|
|
235
|
-
if (mapped !== undefined) sequences.push([fv.flag, mapped]);
|
|
236
|
-
break;
|
|
237
|
-
}
|
|
238
|
-
case "flag-list-by-value": {
|
|
239
|
-
const fl = spec as Extract<typeof spec, { kind: "flag-list-by-value" }>;
|
|
240
|
-
const list = fl.flags[raw as string] ?? [];
|
|
241
|
-
if (list.length > 0) sequences.push([...list]);
|
|
242
|
-
break;
|
|
243
|
-
}
|
|
244
|
-
default: {
|
|
245
|
-
const _exhaustive: never = spec as never;
|
|
246
|
-
throw new ArgvRefusalError({
|
|
247
|
-
issue: "invalid-option-value",
|
|
248
|
-
harness: h.name,
|
|
249
|
-
option: key,
|
|
250
|
-
supported: [],
|
|
251
|
-
detail: String(_exhaustive),
|
|
252
|
-
});
|
|
253
|
-
}
|
|
205
|
+
const render = resolveRender(target, phase);
|
|
206
|
+
if (render === null) {
|
|
207
|
+
throw new ArgvRefusalError({
|
|
208
|
+
issue: "unsupported-on-resume",
|
|
209
|
+
harness: h.name,
|
|
210
|
+
option: key,
|
|
211
|
+
supported: ["re-launch with --access, or resume without it"],
|
|
212
|
+
});
|
|
254
213
|
}
|
|
214
|
+
sequences.push([...tokensFor(render, target.value)]);
|
|
255
215
|
continue;
|
|
256
216
|
}
|
|
257
217
|
|
|
258
218
|
// Non-discovery keys
|
|
259
|
-
// Handle enum default on launch.
|
|
260
|
-
//
|
|
261
|
-
//
|
|
219
|
+
// Handle enum default on launch. A set access preset displaces the
|
|
220
|
+
// option it claims (codex: sandbox), so that option's default must not
|
|
221
|
+
// emit a second flag beside the preset's.
|
|
262
222
|
if (raw === undefined) {
|
|
263
|
-
if (key ===
|
|
223
|
+
if (key === claimed && opts.access !== undefined) continue;
|
|
264
224
|
if (
|
|
265
225
|
spec !== undefined &&
|
|
266
226
|
spec.kind === "enum" &&
|
|
267
227
|
spec.default !== undefined &&
|
|
268
228
|
phase === "launch"
|
|
269
229
|
) {
|
|
270
|
-
const effective = resolveRender(spec
|
|
230
|
+
const effective = resolveRender(spec, phase);
|
|
271
231
|
if (effective === null) continue; // should not happen for launch
|
|
272
232
|
// config-kv is allowed for enum (closed vocab) - no rejection needed here
|
|
273
|
-
|
|
274
|
-
if ((effective as OptionRender).kind === "flag-value")
|
|
275
|
-
seq = [(effective as Extract<OptionRender, { kind: "flag-value" }>).flag, spec.default];
|
|
276
|
-
else if ((effective as OptionRender).kind === "config-kv")
|
|
277
|
-
seq = [
|
|
278
|
-
(effective as Extract<OptionRender, { kind: "config-kv" }>).flag,
|
|
279
|
-
`${(effective as Extract<OptionRender, { kind: "config-kv" }>).key}=${tomlQuote(spec.default)}`,
|
|
280
|
-
];
|
|
281
|
-
else if ((effective as OptionRender).kind === "flag-list")
|
|
282
|
-
seq = [...(effective as Extract<OptionRender, { kind: "flag-list" }>).flags];
|
|
283
|
-
else seq = [];
|
|
284
|
-
sequences.push(seq);
|
|
233
|
+
sequences.push([...tokensFor(effective, spec.default)]);
|
|
285
234
|
}
|
|
286
235
|
continue;
|
|
287
236
|
}
|
|
@@ -293,59 +242,59 @@ export const renderTurnOptions = (
|
|
|
293
242
|
harness: h.name,
|
|
294
243
|
option: key,
|
|
295
244
|
supported: Object.keys(h.turnOptions).length ? Object.keys(h.turnOptions) : ["(none)"],
|
|
245
|
+
supportedBy: supportedBy(defaultDescriptors(), key),
|
|
296
246
|
detail: String(raw),
|
|
297
247
|
hint: hintFor(h.name, key),
|
|
298
248
|
});
|
|
299
249
|
}
|
|
300
250
|
|
|
251
|
+
// From here every spec carries a render: the discovery table and the
|
|
252
|
+
// access spec were handled above and left the loop with `continue`.
|
|
253
|
+
if (spec.kind === "discovery" || spec.kind === "access") continue;
|
|
254
|
+
|
|
301
255
|
// Check resumeRender null => unsupported-on-resume
|
|
302
|
-
|
|
303
|
-
if (
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
256
|
+
const render = resolveRender(spec, phase);
|
|
257
|
+
if (render === null) {
|
|
258
|
+
const resumeSupported = TURN_OPTION_KEYS.filter((k) => {
|
|
259
|
+
const s = h.turnOptions[k];
|
|
260
|
+
if (!s) return false;
|
|
261
|
+
if (s.kind === "discovery") return Object.keys(s.facets).length > 0;
|
|
262
|
+
if (s.kind === "access") {
|
|
263
|
+
const read = s.renders.read;
|
|
264
|
+
return (
|
|
265
|
+
read === "tool-preset" || (read !== null && resolveRender(read, "resume") !== null)
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return resolveRender(s, "resume") !== null;
|
|
269
|
+
}) as string[];
|
|
270
|
+
throw new ArgvRefusalError({
|
|
271
|
+
issue: "unsupported-on-resume",
|
|
272
|
+
harness: h.name,
|
|
273
|
+
option: key,
|
|
274
|
+
supported: resumeSupported.length ? resumeSupported : ["(none)"],
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
// Reject config-kv for open vocabularies. Exception (issue #48):
|
|
278
|
+
// prompt-text rides config-kv VERBATIM on codex - both a literal and
|
|
279
|
+
// a path are accepted (live-verified 0.146.1); no quoting, because
|
|
280
|
+
// codex's k=v split takes the rest of the token raw and both probed
|
|
281
|
+
// forms passed unquoted.
|
|
282
|
+
if (
|
|
283
|
+
spec.render.kind === "config-kv" &&
|
|
284
|
+
spec.kind !== "enum" &&
|
|
285
|
+
spec.kind !== "effort" &&
|
|
286
|
+
spec.kind !== "integer" &&
|
|
287
|
+
spec.kind !== "prompt-text"
|
|
288
|
+
) {
|
|
289
|
+
throw new ArgvRefusalError({
|
|
290
|
+
issue: "invalid-option-value",
|
|
291
|
+
harness: h.name,
|
|
292
|
+
option: key,
|
|
293
|
+
supported: ["config-kv only for enum, effort, integer, and prompt-text"],
|
|
294
|
+
});
|
|
340
295
|
}
|
|
341
296
|
|
|
342
|
-
// Per-kind validation
|
|
343
|
-
const effectiveRender =
|
|
344
|
-
(spec as { kind: string }).kind === "discovery"
|
|
345
|
-
? null
|
|
346
|
-
: resolveRender(spec as unknown as SpecBase, phase);
|
|
347
|
-
// effectiveRender already checked for null
|
|
348
|
-
|
|
297
|
+
// Per-kind validation; token generation is tokensFor's alone.
|
|
349
298
|
switch (spec.kind) {
|
|
350
299
|
case "effort": {
|
|
351
300
|
if (typeof raw !== "string") {
|
|
@@ -380,13 +329,7 @@ export const renderTurnOptions = (
|
|
|
380
329
|
detail: raw,
|
|
381
330
|
});
|
|
382
331
|
}
|
|
383
|
-
|
|
384
|
-
let seq: string[];
|
|
385
|
-
if (r.kind === "flag-value") seq = [r.flag, raw];
|
|
386
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${tomlQuote(raw)}`];
|
|
387
|
-
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
388
|
-
else seq = [];
|
|
389
|
-
sequences.push(seq);
|
|
332
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
390
333
|
break;
|
|
391
334
|
}
|
|
392
335
|
case "enum": {
|
|
@@ -408,13 +351,7 @@ export const renderTurnOptions = (
|
|
|
408
351
|
detail: raw,
|
|
409
352
|
});
|
|
410
353
|
}
|
|
411
|
-
|
|
412
|
-
let seq: string[];
|
|
413
|
-
if (r.kind === "flag-value") seq = [r.flag, raw];
|
|
414
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${tomlQuote(raw)}`];
|
|
415
|
-
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
416
|
-
else seq = [];
|
|
417
|
-
sequences.push(seq);
|
|
354
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
418
355
|
break;
|
|
419
356
|
}
|
|
420
357
|
case "prompt-text": {
|
|
@@ -431,13 +368,7 @@ export const renderTurnOptions = (
|
|
|
431
368
|
detail: typeof raw === "string" ? "(empty)" : String(raw),
|
|
432
369
|
});
|
|
433
370
|
}
|
|
434
|
-
|
|
435
|
-
let seq: string[];
|
|
436
|
-
if (r.kind === "flag-value") seq = [...(r.extraFlags ?? []), r.flag, raw];
|
|
437
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${raw}`];
|
|
438
|
-
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
439
|
-
else seq = [];
|
|
440
|
-
sequences.push(seq);
|
|
371
|
+
sequences.push([...tokensFor(render, raw, "verbatim")]);
|
|
441
372
|
break;
|
|
442
373
|
}
|
|
443
374
|
case "selector": {
|
|
@@ -459,13 +390,7 @@ export const renderTurnOptions = (
|
|
|
459
390
|
detail: raw,
|
|
460
391
|
});
|
|
461
392
|
}
|
|
462
|
-
|
|
463
|
-
let seq: string[];
|
|
464
|
-
if (r.kind === "flag-value") seq = [r.flag, raw];
|
|
465
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${tomlQuote(raw)}`];
|
|
466
|
-
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
467
|
-
else seq = [];
|
|
468
|
-
sequences.push(seq);
|
|
393
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
469
394
|
break;
|
|
470
395
|
}
|
|
471
396
|
case "toggle": {
|
|
@@ -478,17 +403,11 @@ export const renderTurnOptions = (
|
|
|
478
403
|
detail: String(raw),
|
|
479
404
|
});
|
|
480
405
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
406
|
+
const shouldEmit =
|
|
407
|
+
(spec.polarity === "disables" && raw === false) ||
|
|
408
|
+
(spec.polarity === "enables" && raw === true);
|
|
484
409
|
if (!shouldEmit) break;
|
|
485
|
-
|
|
486
|
-
let seq: string[];
|
|
487
|
-
if (r.kind === "flag-list") seq = [...r.flags];
|
|
488
|
-
else if (r.kind === "flag-value") seq = [r.flag, String(raw)];
|
|
489
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${tomlQuote(String(raw))}`];
|
|
490
|
-
else seq = [];
|
|
491
|
-
sequences.push(seq);
|
|
410
|
+
sequences.push([...tokensFor(render, String(raw))]);
|
|
492
411
|
break;
|
|
493
412
|
}
|
|
494
413
|
case "integer": {
|
|
@@ -510,25 +429,11 @@ export const renderTurnOptions = (
|
|
|
510
429
|
detail: String(raw),
|
|
511
430
|
});
|
|
512
431
|
}
|
|
513
|
-
|
|
514
|
-
let seq: string[];
|
|
515
|
-
if (r.kind === "flag-value") seq = [r.flag, String(raw)];
|
|
516
|
-
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${tomlQuote(String(raw))}`];
|
|
517
|
-
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
518
|
-
else seq = [];
|
|
519
|
-
sequences.push(seq);
|
|
432
|
+
sequences.push([...tokensFor(render, String(raw), "verbatim")]);
|
|
520
433
|
break;
|
|
521
434
|
}
|
|
522
|
-
case "discovery":
|
|
523
|
-
// already handled
|
|
524
|
-
break;
|
|
525
|
-
case "tool-preset":
|
|
526
|
-
case "flag-value":
|
|
527
|
-
case "flag-list-by-value":
|
|
528
|
-
// access-only kinds - handled above for key === "access"
|
|
529
|
-
break;
|
|
530
435
|
default: {
|
|
531
|
-
const _exhaustive: never = spec
|
|
436
|
+
const _exhaustive: never = spec;
|
|
532
437
|
throw new ArgvRefusalError({
|
|
533
438
|
issue: "invalid-option-value",
|
|
534
439
|
harness: h.name,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* actionable without reading the descriptor. Resolution lives in ONE place
|
|
6
6
|
* (resolveModel) so validation and capability claims cannot drift.
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { ACCESS_VALUES, type HarnessDescriptor } from "../knowledge/descriptor.js";
|
|
9
9
|
|
|
10
10
|
export type Validated = { readonly ok: true; readonly id: string } | ValidationRefusal;
|
|
11
11
|
|
|
@@ -14,10 +14,11 @@ export interface ValidationRefusal {
|
|
|
14
14
|
readonly reason: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** The selector grammar
|
|
18
|
-
* models as provider/id[:thinking], so
|
|
19
|
-
* separators - never whitespace, shell
|
|
20
|
-
* characters, and bounded like session
|
|
17
|
+
/** The one selector grammar for model ids, tool names, and toolMap keys
|
|
18
|
+
* (RFC-02 change 9): pi documents models as provider/id[:thinking], so
|
|
19
|
+
* word characters plus the few real separators - never whitespace, shell
|
|
20
|
+
* metacharacters, or control/format characters, and bounded like session
|
|
21
|
+
* ids. */
|
|
21
22
|
export const CLEAN_SELECTOR = /^[A-Za-z0-9][A-Za-z0-9._:/@-]{0,127}$/;
|
|
22
23
|
|
|
23
24
|
export interface ResolvedModel {
|
|
@@ -54,14 +55,18 @@ export const validateModel = (h: HarnessDescriptor, model: string): Validated =>
|
|
|
54
55
|
};
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
/**
|
|
58
|
-
* model's own ladder where the harness constrains per model (codex), else
|
|
59
|
-
* the harness-wide ladder. */
|
|
58
|
+
/** The access preset takes one of the closed ACCESS_VALUES. */
|
|
60
59
|
export const validateAccess = (value: string): Validated => {
|
|
61
|
-
if (
|
|
62
|
-
return {
|
|
60
|
+
if ((ACCESS_VALUES as readonly string[]).includes(value)) return { ok: true, id: value };
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `unknown access ${JSON.stringify(value)}; accepted: ${ACCESS_VALUES.join(", ")}`,
|
|
64
|
+
};
|
|
63
65
|
};
|
|
64
66
|
|
|
67
|
+
/** Validate an effort against the ladder that applies to the pick: the
|
|
68
|
+
* model's own ladder where the harness constrains per model (codex), else
|
|
69
|
+
* the harness-wide ladder. */
|
|
65
70
|
export const validateEffort = (h: HarnessDescriptor, effort: string, model?: string): Validated => {
|
|
66
71
|
let ladder = h.vocabulary.efforts;
|
|
67
72
|
if (model !== undefined && h.vocabulary.effortsByModel !== undefined) {
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* `ANTHROPIC_API_KEY` / `apiKeyHelper` (OAuth never read) - so a caller
|
|
10
10
|
* authenticated via OAuth would break. The descriptor therefore offers no
|
|
11
11
|
* `instructionFiles` facet and a call passing it must refuse. Likewise
|
|
12
|
-
* `tools` has no discovery flag on claude and refuses.
|
|
12
|
+
* `tools` has no discovery flag on claude and refuses. The explicit
|
|
13
|
+
* tool-free isolation composite below accepts the bare-mode auth cost and
|
|
14
|
+
* disables native tools too. It is separate from a granular discovery facet.
|
|
13
15
|
*
|
|
14
16
|
* Effort: A-002 showed `--effort bogus` warns on stderr and runs at DEFAULT
|
|
15
17
|
* effort, exit 0, with nothing echoed in the stream. The library-side
|
|
@@ -27,7 +29,6 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
27
29
|
baseFlags: ["-p"],
|
|
28
30
|
subcommands: [],
|
|
29
31
|
promptStyle: "positional",
|
|
30
|
-
toolsFlag: "--allowedTools",
|
|
31
32
|
// A headless turn launches with the full stream-json output set so the
|
|
32
33
|
// runner can decode identity/limits and stream token deltas; bare -p
|
|
33
34
|
// (granularity none) is a degraded invocation this builder never emits.
|
|
@@ -162,6 +163,17 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
162
163
|
observedOn: { harness: "claude", model: "", version: "2.1.235", date: "2026-08-19" },
|
|
163
164
|
},
|
|
164
165
|
turnOptions: {
|
|
166
|
+
// Native CLI reference: bare removes discovery; the empty built-in list and
|
|
167
|
+
// MCP deny remove tool access. Resume and native overrides are refused.
|
|
168
|
+
isolation: {
|
|
169
|
+
kind: "enum",
|
|
170
|
+
values: ["tool-free"],
|
|
171
|
+
resumeRender: null,
|
|
172
|
+
render: {
|
|
173
|
+
kind: "flag-list",
|
|
174
|
+
flags: ["--bare", "--tools", "", "--disallowedTools", "mcp__*", "--strict-mcp-config"],
|
|
175
|
+
},
|
|
176
|
+
},
|
|
165
177
|
effort: { kind: "effort", render: { kind: "flag-value", flag: "--effort" } },
|
|
166
178
|
// issue #48, live-verified 2.1.235: --system-prompt replaces the built-in
|
|
167
179
|
// prompt; --exclude-dynamic-system-prompt-sections strips the dynamic
|
|
@@ -198,7 +210,9 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
198
210
|
},
|
|
199
211
|
},
|
|
200
212
|
},
|
|
201
|
-
|
|
213
|
+
// read renders the read preset through the tool list (grant plus deny
|
|
214
|
+
// complement); write is the harness default and emits nothing.
|
|
215
|
+
access: { kind: "access", renders: { read: "tool-preset", write: null } },
|
|
202
216
|
},
|
|
203
217
|
// Phase 0 fixtures: claude-tool-interplay.md. include is a permission
|
|
204
218
|
// grant (Bash, Edit stay visible under --allowedTools Read); only the
|
|
@@ -211,7 +225,6 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
211
225
|
includeFlag: "--allowedTools",
|
|
212
226
|
excludeFlag: "--disallowedTools",
|
|
213
227
|
includeIsStrictAllowlist: false,
|
|
214
|
-
composable: true,
|
|
215
228
|
builtins: [
|
|
216
229
|
{ name: "Bash", defaultEnabled: true, canonical: "shell" },
|
|
217
230
|
{ name: "Edit", defaultEnabled: true, canonical: "edit" },
|