@dungle-scrubs/harness-cli-normalizer 0.6.0 → 0.6.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.
- package/README.md +18 -6
- package/dist/cli/args.d.ts +9 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +31 -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 +6 -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 +6 -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 +266 -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 +25 -44
- 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 +38 -12
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +26 -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/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 +38 -21
- 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 +23 -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 +105 -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.js +3 -3
- 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 +49 -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 +7 -5
- 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 +35 -27
- package/src/cli/check.ts +10 -3
- package/src/cli/config.ts +12 -11
- package/src/cli/exit-codes.ts +0 -2
- package/src/cli/help.ts +6 -2
- package/src/cli/index.ts +3 -1
- package/src/cli/inspect.ts +88 -269
- package/src/cli/plan-turn.ts +346 -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 +34 -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 +62 -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/limits.ts +14 -75
- package/src/interpretation/question.ts +46 -0
- package/src/interpretation/refusal.ts +28 -18
- package/src/interpretation/resolve-options.ts +85 -32
- 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 +19 -6
- package/src/interpretation/tool-selection.ts +67 -122
- package/src/interpretation/tool-vocabulary.ts +48 -53
- package/src/interpretation/turn-options.ts +123 -220
- package/src/interpretation/vocabulary.ts +15 -10
- package/src/knowledge/claude-code.ts +3 -3
- package/src/knowledge/codex.ts +31 -7
- package/src/knowledge/descriptor.ts +105 -49
- package/src/knowledge/matchers.ts +35 -0
- package/src/knowledge/muse.ts +7 -5
- 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,28 @@
|
|
|
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";
|
|
16
27
|
import { ArgvRefusalError } from "./refusal.js";
|
|
28
|
+
import { supportedBy } from "./support.js";
|
|
17
29
|
import { renderToolSelection } from "./tool-selection.js";
|
|
18
|
-
import { READ_PRESET } from "./tool-vocabulary.js";
|
|
30
|
+
import { canonicalTable, hasCounterpart, READ_PRESET } from "./tool-vocabulary.js";
|
|
19
31
|
import { CLEAN_SELECTOR, resolveModel, validateAccess, validateEffort } from "./vocabulary.js";
|
|
20
32
|
|
|
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
33
|
const isPlainObject = (v: unknown): v is Record<string, unknown> =>
|
|
27
34
|
typeof v === "object" && v !== null && !Array.isArray(v);
|
|
28
35
|
|
|
@@ -35,20 +42,10 @@ export const renderTurnOptions = (
|
|
|
35
42
|
phase: "launch" | "resume",
|
|
36
43
|
): string[] => {
|
|
37
44
|
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
|
-
}
|
|
45
|
+
// The turn option the access preset displaces when set (codex: sandbox),
|
|
46
|
+
// read from the descriptor so no arm below branches on a harness name.
|
|
47
|
+
const accessSpec = h.turnOptions.access;
|
|
48
|
+
const claimed = accessSpec?.kind === "access" ? accessSpec.claims : undefined;
|
|
52
49
|
|
|
53
50
|
for (const key of TURN_OPTION_KEYS) {
|
|
54
51
|
const spec = h.turnOptions[key];
|
|
@@ -109,16 +106,11 @@ export const renderTurnOptions = (
|
|
|
109
106
|
for (const facet of DISCOVERY_FACETS) {
|
|
110
107
|
const val = discObj[facet];
|
|
111
108
|
if (val === undefined) continue;
|
|
112
|
-
// Polarity handling: disables emits on false, enables emits on true
|
|
113
109
|
const facetSpec = spec.facets[facet];
|
|
114
110
|
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
|
|
111
|
+
// A facet the descriptor does not declare cannot be expressed.
|
|
112
|
+
// Only a value that would have emitted refuses: every current
|
|
113
|
+
// facet has "disables" polarity, so false would emit.
|
|
122
114
|
if (val === false) {
|
|
123
115
|
const supportedFacets = Object.keys(spec.facets);
|
|
124
116
|
throw new ArgvRefusalError({
|
|
@@ -132,26 +124,15 @@ export const renderTurnOptions = (
|
|
|
132
124
|
}
|
|
133
125
|
continue;
|
|
134
126
|
}
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
else {
|
|
140
|
-
// true for disables or false for enables => no-op
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
127
|
+
// Polarity: disables emits on false, enables emits on true.
|
|
128
|
+
const shouldEmit =
|
|
129
|
+
(facetSpec.polarity === "disables" && val === false) ||
|
|
130
|
+
(facetSpec.polarity === "enables" && val === true);
|
|
143
131
|
if (!shouldEmit) continue;
|
|
144
|
-
// Check resumeRender refusal
|
|
145
132
|
const effective = resolveRender(facetSpec, phase);
|
|
146
133
|
if (effective === null) {
|
|
147
134
|
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
|
-
)
|
|
135
|
+
.filter(([, v]) => resolveRender(v as SpecBase, "resume") !== null)
|
|
155
136
|
.map(([k]) => k);
|
|
156
137
|
throw new ArgvRefusalError({
|
|
157
138
|
issue: "unsupported-on-resume",
|
|
@@ -161,8 +142,8 @@ export const renderTurnOptions = (
|
|
|
161
142
|
supported: resumeSupported.length ? resumeSupported : ["(none resume)"],
|
|
162
143
|
});
|
|
163
144
|
}
|
|
164
|
-
//
|
|
165
|
-
if (
|
|
145
|
+
// Discovery is not a closed vocabulary, so config-kv cannot carry it.
|
|
146
|
+
if (effective.kind === "config-kv") {
|
|
166
147
|
throw new ArgvRefusalError({
|
|
167
148
|
issue: "invalid-option-value",
|
|
168
149
|
harness: h.name,
|
|
@@ -171,21 +152,17 @@ export const renderTurnOptions = (
|
|
|
171
152
|
supported: ["flag-list only for discovery"],
|
|
172
153
|
});
|
|
173
154
|
}
|
|
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);
|
|
155
|
+
sequences.push([...tokensFor(effective)]);
|
|
181
156
|
}
|
|
182
157
|
continue;
|
|
183
158
|
}
|
|
184
159
|
|
|
185
|
-
// Access is opt-in-only, no profile default
|
|
160
|
+
// Access is opt-in-only, no profile default. One arm for every harness:
|
|
161
|
+
// the descriptor says whether a value renders through the tool list, a
|
|
162
|
+
// phase-aware render, or nothing at all.
|
|
186
163
|
if (key === "access") {
|
|
187
164
|
if (raw === undefined) continue;
|
|
188
|
-
if (spec === undefined) {
|
|
165
|
+
if (spec === undefined || spec.kind !== "access") {
|
|
189
166
|
throw new ArgvRefusalError({
|
|
190
167
|
issue: "unsupported-option",
|
|
191
168
|
harness: h.name,
|
|
@@ -195,93 +172,63 @@ export const renderTurnOptions = (
|
|
|
195
172
|
hint: hintFor(h.name, key),
|
|
196
173
|
});
|
|
197
174
|
}
|
|
198
|
-
|
|
175
|
+
// The resume path builds from unresolved options, so the value is
|
|
176
|
+
// checked here as well as in option resolution - one rule, two gates.
|
|
177
|
+
const v = validateAccess(String(raw));
|
|
199
178
|
if (!v.ok) {
|
|
200
179
|
throw new ArgvRefusalError({
|
|
201
180
|
issue: "invalid-option-value",
|
|
202
181
|
harness: h.name,
|
|
203
182
|
option: key,
|
|
204
|
-
supported: [
|
|
183
|
+
supported: [...ACCESS_VALUES],
|
|
205
184
|
detail: String(raw),
|
|
206
185
|
});
|
|
207
186
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
187
|
+
const value = v.id as AccessValue;
|
|
188
|
+
const target = spec.renders[value];
|
|
189
|
+
if (target === null) continue;
|
|
190
|
+
if (target === "tool-preset") {
|
|
191
|
+
// The read preset rides the tool list. When discovery.tools is off,
|
|
192
|
+
// it must not switch tools back on; write is no restriction.
|
|
193
|
+
if (value !== "read" || opts.discovery?.tools === false) continue;
|
|
194
|
+
const table = canonicalTable(defaultDescriptors());
|
|
195
|
+
const filtered = (READ_PRESET as readonly string[]).filter((c) =>
|
|
196
|
+
hasCounterpart(table, c, h.name, opts.toolMap),
|
|
197
|
+
);
|
|
198
|
+
if (filtered.length === 0) continue;
|
|
199
|
+
const rendered = renderToolSelection(h, { include: filtered, toolMap: opts.toolMap });
|
|
200
|
+
if (rendered.tokens.length > 0) sequences.push([...rendered.tokens]);
|
|
201
|
+
continue;
|
|
211
202
|
}
|
|
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
|
-
}
|
|
203
|
+
const render = resolveRender(target, phase);
|
|
204
|
+
if (render === null) {
|
|
205
|
+
throw new ArgvRefusalError({
|
|
206
|
+
issue: "unsupported-on-resume",
|
|
207
|
+
harness: h.name,
|
|
208
|
+
option: key,
|
|
209
|
+
supported: ["re-launch with --access, or resume without it"],
|
|
210
|
+
});
|
|
254
211
|
}
|
|
212
|
+
sequences.push([...tokensFor(render, target.value)]);
|
|
255
213
|
continue;
|
|
256
214
|
}
|
|
257
215
|
|
|
258
216
|
// Non-discovery keys
|
|
259
|
-
// Handle enum default on launch.
|
|
260
|
-
//
|
|
261
|
-
//
|
|
217
|
+
// Handle enum default on launch. A set access preset displaces the
|
|
218
|
+
// option it claims (codex: sandbox), so that option's default must not
|
|
219
|
+
// emit a second flag beside the preset's.
|
|
262
220
|
if (raw === undefined) {
|
|
263
|
-
if (key ===
|
|
221
|
+
if (key === claimed && opts.access !== undefined) continue;
|
|
264
222
|
if (
|
|
265
223
|
spec !== undefined &&
|
|
266
224
|
spec.kind === "enum" &&
|
|
267
225
|
spec.default !== undefined &&
|
|
268
226
|
phase === "launch"
|
|
269
227
|
) {
|
|
270
|
-
const effective = resolveRender(spec
|
|
228
|
+
const effective = resolveRender(spec, phase);
|
|
271
229
|
if (effective === null) continue; // should not happen for launch
|
|
272
230
|
// 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);
|
|
231
|
+
sequences.push([...tokensFor(effective, spec.default)]);
|
|
285
232
|
}
|
|
286
233
|
continue;
|
|
287
234
|
}
|
|
@@ -293,59 +240,59 @@ export const renderTurnOptions = (
|
|
|
293
240
|
harness: h.name,
|
|
294
241
|
option: key,
|
|
295
242
|
supported: Object.keys(h.turnOptions).length ? Object.keys(h.turnOptions) : ["(none)"],
|
|
243
|
+
supportedBy: supportedBy(defaultDescriptors(), key),
|
|
296
244
|
detail: String(raw),
|
|
297
245
|
hint: hintFor(h.name, key),
|
|
298
246
|
});
|
|
299
247
|
}
|
|
300
248
|
|
|
249
|
+
// From here every spec carries a render: the discovery table and the
|
|
250
|
+
// access spec were handled above and left the loop with `continue`.
|
|
251
|
+
if (spec.kind === "discovery" || spec.kind === "access") continue;
|
|
252
|
+
|
|
301
253
|
// 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
|
-
|
|
254
|
+
const render = resolveRender(spec, phase);
|
|
255
|
+
if (render === null) {
|
|
256
|
+
const resumeSupported = TURN_OPTION_KEYS.filter((k) => {
|
|
257
|
+
const s = h.turnOptions[k];
|
|
258
|
+
if (!s) return false;
|
|
259
|
+
if (s.kind === "discovery") return Object.keys(s.facets).length > 0;
|
|
260
|
+
if (s.kind === "access") {
|
|
261
|
+
const read = s.renders.read;
|
|
262
|
+
return (
|
|
263
|
+
read === "tool-preset" || (read !== null && resolveRender(read, "resume") !== null)
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return resolveRender(s, "resume") !== null;
|
|
267
|
+
}) as string[];
|
|
268
|
+
throw new ArgvRefusalError({
|
|
269
|
+
issue: "unsupported-on-resume",
|
|
270
|
+
harness: h.name,
|
|
271
|
+
option: key,
|
|
272
|
+
supported: resumeSupported.length ? resumeSupported : ["(none)"],
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// Reject config-kv for open vocabularies. Exception (issue #48):
|
|
276
|
+
// prompt-text rides config-kv VERBATIM on codex - both a literal and
|
|
277
|
+
// a path are accepted (live-verified 0.146.1); no quoting, because
|
|
278
|
+
// codex's k=v split takes the rest of the token raw and both probed
|
|
279
|
+
// forms passed unquoted.
|
|
280
|
+
if (
|
|
281
|
+
spec.render.kind === "config-kv" &&
|
|
282
|
+
spec.kind !== "enum" &&
|
|
283
|
+
spec.kind !== "effort" &&
|
|
284
|
+
spec.kind !== "integer" &&
|
|
285
|
+
spec.kind !== "prompt-text"
|
|
286
|
+
) {
|
|
287
|
+
throw new ArgvRefusalError({
|
|
288
|
+
issue: "invalid-option-value",
|
|
289
|
+
harness: h.name,
|
|
290
|
+
option: key,
|
|
291
|
+
supported: ["config-kv only for enum, effort, integer, and prompt-text"],
|
|
292
|
+
});
|
|
340
293
|
}
|
|
341
294
|
|
|
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
|
-
|
|
295
|
+
// Per-kind validation; token generation is tokensFor's alone.
|
|
349
296
|
switch (spec.kind) {
|
|
350
297
|
case "effort": {
|
|
351
298
|
if (typeof raw !== "string") {
|
|
@@ -380,13 +327,7 @@ export const renderTurnOptions = (
|
|
|
380
327
|
detail: raw,
|
|
381
328
|
});
|
|
382
329
|
}
|
|
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);
|
|
330
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
390
331
|
break;
|
|
391
332
|
}
|
|
392
333
|
case "enum": {
|
|
@@ -408,13 +349,7 @@ export const renderTurnOptions = (
|
|
|
408
349
|
detail: raw,
|
|
409
350
|
});
|
|
410
351
|
}
|
|
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);
|
|
352
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
418
353
|
break;
|
|
419
354
|
}
|
|
420
355
|
case "prompt-text": {
|
|
@@ -431,13 +366,7 @@ export const renderTurnOptions = (
|
|
|
431
366
|
detail: typeof raw === "string" ? "(empty)" : String(raw),
|
|
432
367
|
});
|
|
433
368
|
}
|
|
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);
|
|
369
|
+
sequences.push([...tokensFor(render, raw, "verbatim")]);
|
|
441
370
|
break;
|
|
442
371
|
}
|
|
443
372
|
case "selector": {
|
|
@@ -459,13 +388,7 @@ export const renderTurnOptions = (
|
|
|
459
388
|
detail: raw,
|
|
460
389
|
});
|
|
461
390
|
}
|
|
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);
|
|
391
|
+
sequences.push([...tokensFor(render, raw)]);
|
|
469
392
|
break;
|
|
470
393
|
}
|
|
471
394
|
case "toggle": {
|
|
@@ -478,17 +401,11 @@ export const renderTurnOptions = (
|
|
|
478
401
|
detail: String(raw),
|
|
479
402
|
});
|
|
480
403
|
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
404
|
+
const shouldEmit =
|
|
405
|
+
(spec.polarity === "disables" && raw === false) ||
|
|
406
|
+
(spec.polarity === "enables" && raw === true);
|
|
484
407
|
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);
|
|
408
|
+
sequences.push([...tokensFor(render, String(raw))]);
|
|
492
409
|
break;
|
|
493
410
|
}
|
|
494
411
|
case "integer": {
|
|
@@ -510,25 +427,11 @@ export const renderTurnOptions = (
|
|
|
510
427
|
detail: String(raw),
|
|
511
428
|
});
|
|
512
429
|
}
|
|
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);
|
|
430
|
+
sequences.push([...tokensFor(render, String(raw), "verbatim")]);
|
|
520
431
|
break;
|
|
521
432
|
}
|
|
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
433
|
default: {
|
|
531
|
-
const _exhaustive: never = spec
|
|
434
|
+
const _exhaustive: never = spec;
|
|
532
435
|
throw new ArgvRefusalError({
|
|
533
436
|
issue: "invalid-option-value",
|
|
534
437
|
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) {
|
|
@@ -27,7 +27,6 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
27
27
|
baseFlags: ["-p"],
|
|
28
28
|
subcommands: [],
|
|
29
29
|
promptStyle: "positional",
|
|
30
|
-
toolsFlag: "--allowedTools",
|
|
31
30
|
// A headless turn launches with the full stream-json output set so the
|
|
32
31
|
// runner can decode identity/limits and stream token deltas; bare -p
|
|
33
32
|
// (granularity none) is a degraded invocation this builder never emits.
|
|
@@ -198,7 +197,9 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
198
197
|
},
|
|
199
198
|
},
|
|
200
199
|
},
|
|
201
|
-
|
|
200
|
+
// read renders the read preset through the tool list (grant plus deny
|
|
201
|
+
// complement); write is the harness default and emits nothing.
|
|
202
|
+
access: { kind: "access", renders: { read: "tool-preset", write: null } },
|
|
202
203
|
},
|
|
203
204
|
// Phase 0 fixtures: claude-tool-interplay.md. include is a permission
|
|
204
205
|
// grant (Bash, Edit stay visible under --allowedTools Read); only the
|
|
@@ -211,7 +212,6 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
211
212
|
includeFlag: "--allowedTools",
|
|
212
213
|
excludeFlag: "--disallowedTools",
|
|
213
214
|
includeIsStrictAllowlist: false,
|
|
214
|
-
composable: true,
|
|
215
215
|
builtins: [
|
|
216
216
|
{ name: "Bash", defaultEnabled: true, canonical: "shell" },
|
|
217
217
|
{ name: "Edit", defaultEnabled: true, canonical: "edit" },
|
package/src/knowledge/codex.ts
CHANGED
|
@@ -23,7 +23,6 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
23
23
|
baseFlags: ["exec", "--json", "--skip-git-repo-check"],
|
|
24
24
|
subcommands: ["exec"],
|
|
25
25
|
promptStyle: "positional",
|
|
26
|
-
toolsFlag: null,
|
|
27
26
|
streamFlags: [],
|
|
28
27
|
// Codex mints its own thread id; there is nothing to assign at launch.
|
|
29
28
|
idFlag: null,
|
|
@@ -62,11 +61,13 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
62
61
|
autonomy: { flag: "--yolo" },
|
|
63
62
|
vocabulary: {
|
|
64
63
|
modelFlag: "--model",
|
|
65
|
-
models: ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"],
|
|
64
|
+
models: ["gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-5.5"],
|
|
66
65
|
aliases: {},
|
|
67
66
|
efforts: ["minimal", "low", "medium", "high", "xhigh", "max"],
|
|
68
67
|
// Codex constrains ladders per model generation (v1 registry).
|
|
69
68
|
effortsByModel: {
|
|
69
|
+
// https://developers.openai.com/api/docs/models/gpt-6-astra (2026-09-06).
|
|
70
|
+
"gpt-6-astra": ["low", "medium", "high", "xhigh", "max"],
|
|
70
71
|
"gpt-5.5": ["minimal", "low", "medium", "high"],
|
|
71
72
|
"gpt-5.6-sol": ["medium", "high", "xhigh", "max"],
|
|
72
73
|
"gpt-5.6-terra": ["medium", "high", "xhigh", "max"],
|
|
@@ -109,6 +110,14 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
109
110
|
observedOn: { harness: "codex", model: "", version: "0.146.1", date: "2026-08-19" },
|
|
110
111
|
},
|
|
111
112
|
turnOptions: {
|
|
113
|
+
// Codex config reference; accepted as an integer on CLI 0.153.4.
|
|
114
|
+
// Native compaction uses this window; hcn does not count request tokens.
|
|
115
|
+
contextWindow: {
|
|
116
|
+
kind: "integer",
|
|
117
|
+
min: 1,
|
|
118
|
+
max: 272000,
|
|
119
|
+
render: { kind: "config-kv", flag: "-c", key: "model_context_window" },
|
|
120
|
+
},
|
|
112
121
|
effort: {
|
|
113
122
|
kind: "effort",
|
|
114
123
|
render: { kind: "config-kv", flag: "-c", key: "model_reasoning_effort" },
|
|
@@ -138,11 +147,27 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
138
147
|
// caller asked.
|
|
139
148
|
resumeRender: { kind: "config-kv", flag: "-c", key: "sandbox_mode" },
|
|
140
149
|
},
|
|
150
|
+
// The preset maps onto the sandbox dimension and therefore CLAIMS it:
|
|
151
|
+
// an explicit --sandbox alongside --access refuses, and the profile's
|
|
152
|
+
// sandbox default yields. Data here, not a harness-name branch.
|
|
153
|
+
// On resume the preset rides the same config-kv spelling the sandbox
|
|
154
|
+
// dimension uses (issue #72 evidence above): `codex exec resume`
|
|
155
|
+
// rejects --sandbox, and -c sandbox_mode is enforced there.
|
|
141
156
|
access: {
|
|
142
|
-
kind: "
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
157
|
+
kind: "access",
|
|
158
|
+
claims: "sandbox",
|
|
159
|
+
renders: {
|
|
160
|
+
read: {
|
|
161
|
+
render: { kind: "flag-value", flag: "--sandbox" },
|
|
162
|
+
resumeRender: { kind: "config-kv", flag: "-c", key: "sandbox_mode" },
|
|
163
|
+
value: "read-only",
|
|
164
|
+
},
|
|
165
|
+
write: {
|
|
166
|
+
render: { kind: "flag-value", flag: "--sandbox" },
|
|
167
|
+
resumeRender: { kind: "config-kv", flag: "-c", key: "sandbox_mode" },
|
|
168
|
+
value: "workspace-write",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
146
171
|
},
|
|
147
172
|
},
|
|
148
173
|
// Tools: no built-in name lists; control is feature booleans
|
|
@@ -158,7 +183,6 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
158
183
|
includeFlag: null,
|
|
159
184
|
excludeFlag: null,
|
|
160
185
|
includeIsStrictAllowlist: false,
|
|
161
|
-
composable: false,
|
|
162
186
|
builtins: [],
|
|
163
187
|
categories: [
|
|
164
188
|
{ key: "shell", disableFlag: null, configKey: "features.shell_tool", canonical: [] },
|