@dungle-scrubs/harness-cli-normalizer 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +53 -7
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +13 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +65 -0
- package/dist/cli/config.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 +28 -7
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +81 -25
- package/dist/cli/inspect.js.map +1 -1
- package/dist/cli/provenance.d.ts +3 -0
- package/dist/cli/provenance.d.ts.map +1 -0
- package/dist/cli/provenance.js +11 -0
- package/dist/cli/provenance.js.map +1 -0
- package/dist/cli/refuse.d.ts +14 -0
- package/dist/cli/refuse.d.ts.map +1 -0
- package/dist/cli/refuse.js +38 -0
- package/dist/cli/refuse.js.map +1 -0
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +27 -68
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/session.d.ts.map +1 -1
- package/dist/cli/session.js +3 -3
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/skills-root.d.ts +2 -1
- package/dist/cli/skills-root.d.ts.map +1 -1
- package/dist/cli/skills-root.js +6 -4
- package/dist/cli/skills-root.js.map +1 -1
- package/dist/execution/decode.d.ts.map +1 -1
- package/dist/execution/decode.js +2 -1
- package/dist/execution/decode.js.map +1 -1
- package/dist/execution/deps.d.ts +1 -1
- package/dist/execution/deps.js +1 -1
- package/dist/execution/failure.d.ts +5 -1
- package/dist/execution/failure.d.ts.map +1 -1
- package/dist/execution/failure.js +12 -0
- package/dist/execution/failure.js.map +1 -1
- package/dist/execution/index.d.ts +2 -2
- package/dist/execution/index.js +2 -2
- package/dist/execution/open-session.d.ts.map +1 -1
- package/dist/execution/open-session.js +23 -8
- package/dist/execution/open-session.js.map +1 -1
- package/dist/execution/stream-turn.d.ts.map +1 -1
- package/dist/execution/stream-turn.js +18 -10
- package/dist/execution/stream-turn.js.map +1 -1
- package/dist/interpretation/argv.d.ts +3 -0
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +6 -1
- package/dist/interpretation/argv.js.map +1 -1
- package/dist/interpretation/content.d.ts.map +1 -1
- package/dist/interpretation/content.js +4 -1
- package/dist/interpretation/content.js.map +1 -1
- package/dist/interpretation/hints.d.ts.map +1 -1
- package/dist/interpretation/hints.js +4 -0
- package/dist/interpretation/hints.js.map +1 -1
- package/dist/interpretation/limits.d.ts +1 -0
- package/dist/interpretation/limits.d.ts.map +1 -1
- package/dist/interpretation/limits.js +22 -0
- package/dist/interpretation/limits.js.map +1 -1
- package/dist/interpretation/resolve-options.d.ts.map +1 -1
- package/dist/interpretation/resolve-options.js +124 -3
- package/dist/interpretation/resolve-options.js.map +1 -1
- package/dist/interpretation/support.d.ts +1 -0
- package/dist/interpretation/support.d.ts.map +1 -1
- package/dist/interpretation/support.js +29 -0
- package/dist/interpretation/support.js.map +1 -1
- package/dist/interpretation/tool-selection.d.ts +6 -16
- package/dist/interpretation/tool-selection.d.ts.map +1 -1
- package/dist/interpretation/tool-selection.js +216 -80
- package/dist/interpretation/tool-selection.js.map +1 -1
- package/dist/interpretation/tool-vocabulary.d.ts +38 -0
- package/dist/interpretation/tool-vocabulary.d.ts.map +1 -0
- package/dist/interpretation/tool-vocabulary.js +142 -0
- package/dist/interpretation/tool-vocabulary.js.map +1 -0
- package/dist/interpretation/turn-options.d.ts.map +1 -1
- package/dist/interpretation/turn-options.js +107 -2
- package/dist/interpretation/turn-options.js.map +1 -1
- package/dist/interpretation/versions.d.ts +1 -1
- package/dist/interpretation/versions.d.ts.map +1 -1
- package/dist/interpretation/vocabulary.d.ts +1 -0
- package/dist/interpretation/vocabulary.d.ts.map +1 -1
- package/dist/interpretation/vocabulary.js +5 -0
- package/dist/interpretation/vocabulary.js.map +1 -1
- package/dist/knowledge/claude-code.d.ts.map +1 -1
- package/dist/knowledge/claude-code.js +14 -13
- package/dist/knowledge/claude-code.js.map +1 -1
- package/dist/knowledge/codex.d.ts.map +1 -1
- package/dist/knowledge/codex.js +10 -4
- package/dist/knowledge/codex.js.map +1 -1
- package/dist/knowledge/descriptor.d.ts +20 -1
- package/dist/knowledge/descriptor.d.ts.map +1 -1
- package/dist/knowledge/descriptor.js +18 -1
- package/dist/knowledge/descriptor.js.map +1 -1
- package/dist/knowledge/index.d.ts +1 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/index.js +1 -1
- package/dist/knowledge/index.js.map +1 -1
- package/dist/knowledge/matchers.d.ts +2 -1
- package/dist/knowledge/matchers.d.ts.map +1 -1
- package/dist/knowledge/matchers.js +15 -0
- package/dist/knowledge/matchers.js.map +1 -1
- package/dist/knowledge/muse.d.ts.map +1 -1
- package/dist/knowledge/muse.js +18 -3
- package/dist/knowledge/muse.js.map +1 -1
- package/dist/knowledge/overrides.d.ts.map +1 -1
- package/dist/knowledge/overrides.js +3 -2
- package/dist/knowledge/overrides.js.map +1 -1
- package/dist/knowledge/pi.d.ts.map +1 -1
- package/dist/knowledge/pi.js +12 -8
- package/dist/knowledge/pi.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/args.ts +13 -0
- package/src/cli/config.ts +88 -0
- package/src/cli/help.ts +28 -7
- package/src/cli/inspect.ts +78 -21
- package/src/cli/provenance.ts +19 -0
- package/src/cli/refuse.ts +50 -0
- package/src/cli/run.ts +27 -94
- package/src/cli/session.ts +4 -3
- package/src/cli/skills-root.ts +11 -4
- package/src/execution/decode.ts +2 -1
- package/src/execution/deps.ts +1 -1
- package/src/execution/failure.ts +17 -1
- package/src/execution/index.ts +2 -2
- package/src/execution/open-session.ts +33 -7
- package/src/execution/stream-turn.ts +21 -9
- package/src/interpretation/argv.ts +9 -1
- package/src/interpretation/content.ts +5 -1
- package/src/interpretation/hints.ts +8 -0
- package/src/interpretation/limits.ts +29 -0
- package/src/interpretation/resolve-options.ts +129 -3
- package/src/interpretation/support.ts +30 -0
- package/src/interpretation/tool-selection.ts +236 -96
- package/src/interpretation/tool-vocabulary.ts +183 -0
- package/src/interpretation/turn-options.ts +99 -2
- package/src/interpretation/versions.ts +1 -1
- package/src/interpretation/vocabulary.ts +5 -0
- package/src/knowledge/claude-code.ts +14 -13
- package/src/knowledge/codex.ts +10 -4
- package/src/knowledge/descriptor.ts +42 -2
- package/src/knowledge/index.ts +5 -1
- package/src/knowledge/matchers.ts +17 -1
- package/src/knowledge/muse.ts +18 -3
- package/src/knowledge/overrides.ts +4 -2
- package/src/knowledge/pi.ts +12 -8
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { DescriptorSet } from "../knowledge/overrides.js";
|
|
2
|
+
import { ArgvRefusalError } from "./refusal.js";
|
|
3
|
+
|
|
4
|
+
export const NATIVE_PREFIX = "native:" as const;
|
|
5
|
+
|
|
6
|
+
export type VocabularyEntry =
|
|
7
|
+
| { kind: "builtin"; native: string }
|
|
8
|
+
| { kind: "category"; key: string };
|
|
9
|
+
|
|
10
|
+
export const READ_PRESET = ["read", "grep", "glob", "list", "web-fetch", "web-search"] as const;
|
|
11
|
+
|
|
12
|
+
export type ParsedSelector = { kind: "canonical"; name: string } | { kind: "native"; name: string };
|
|
13
|
+
|
|
14
|
+
export const parseToolSelector = (raw: string): ParsedSelector =>
|
|
15
|
+
raw.startsWith(NATIVE_PREFIX)
|
|
16
|
+
? { kind: "native", name: raw.slice(NATIVE_PREFIX.length) }
|
|
17
|
+
: { kind: "canonical", name: raw };
|
|
18
|
+
|
|
19
|
+
export type ToolMap = Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
20
|
+
|
|
21
|
+
export type ToolMapTier = "user-config" | "project-config";
|
|
22
|
+
|
|
23
|
+
export type MergedToolMap = Readonly<
|
|
24
|
+
Record<string, Readonly<Record<string, { native: string; tier: ToolMapTier }>>>
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/** Pure merge per harness per canonical: project > user. */
|
|
28
|
+
export const mergeToolMaps = (tiers: {
|
|
29
|
+
readonly user?: ToolMap;
|
|
30
|
+
readonly project?: ToolMap;
|
|
31
|
+
}): MergedToolMap => {
|
|
32
|
+
const merged: Record<string, Record<string, { native: string; tier: ToolMapTier }>> = {};
|
|
33
|
+
const harnesses = new Set<string>([
|
|
34
|
+
...Object.keys(tiers.user ?? {}),
|
|
35
|
+
...Object.keys(tiers.project ?? {}),
|
|
36
|
+
]);
|
|
37
|
+
for (const h of harnesses) {
|
|
38
|
+
const userEntries = tiers.user?.[h] ?? {};
|
|
39
|
+
const projectEntries = tiers.project?.[h] ?? {};
|
|
40
|
+
const canonicals = new Set<string>([
|
|
41
|
+
...Object.keys(userEntries),
|
|
42
|
+
...Object.keys(projectEntries),
|
|
43
|
+
]);
|
|
44
|
+
const perHarness: Record<string, { native: string; tier: ToolMapTier }> = {};
|
|
45
|
+
for (const c of canonicals) {
|
|
46
|
+
if (projectEntries[c] !== undefined) {
|
|
47
|
+
perHarness[c] = { native: projectEntries[c] as string, tier: "project-config" };
|
|
48
|
+
} else if (userEntries[c] !== undefined) {
|
|
49
|
+
perHarness[c] = { native: userEntries[c] as string, tier: "user-config" };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (Object.keys(perHarness).length > 0) {
|
|
53
|
+
merged[h] = perHarness;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return merged;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type CanonicalTable = Readonly<
|
|
60
|
+
Record<string, Readonly<Partial<Record<string, VocabularyEntry>>>>
|
|
61
|
+
>;
|
|
62
|
+
|
|
63
|
+
const tableCache = new WeakMap<DescriptorSet, CanonicalTable>();
|
|
64
|
+
|
|
65
|
+
export const canonicalTable = (set: DescriptorSet): CanonicalTable => {
|
|
66
|
+
const cached = tableCache.get(set);
|
|
67
|
+
if (cached) return cached;
|
|
68
|
+
const allCanonical = new Set<string>();
|
|
69
|
+
for (const h of Object.values(set)) {
|
|
70
|
+
if (!h) continue;
|
|
71
|
+
for (const b of h.tools.builtins) {
|
|
72
|
+
if (b.canonical !== null) allCanonical.add(b.canonical);
|
|
73
|
+
}
|
|
74
|
+
for (const c of h.tools.categories) {
|
|
75
|
+
for (const n of c.canonical) allCanonical.add(n);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const table: Record<string, Record<string, VocabularyEntry>> = {};
|
|
79
|
+
for (const canonical of allCanonical) {
|
|
80
|
+
const perHarness: Record<string, VocabularyEntry> = {};
|
|
81
|
+
for (const h of Object.values(set)) {
|
|
82
|
+
if (!h) continue;
|
|
83
|
+
const builtin = h.tools.builtins.find((b) => b.canonical === canonical);
|
|
84
|
+
if (builtin) {
|
|
85
|
+
perHarness[h.name] = { kind: "builtin", native: builtin.name };
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const cat = h.tools.categories.find((c) =>
|
|
89
|
+
(c.canonical as readonly string[]).includes(canonical),
|
|
90
|
+
);
|
|
91
|
+
if (cat) {
|
|
92
|
+
perHarness[h.name] = { kind: "category", key: cat.key };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
table[canonical] = perHarness;
|
|
96
|
+
}
|
|
97
|
+
const frozen = table as CanonicalTable;
|
|
98
|
+
tableCache.set(set, frozen);
|
|
99
|
+
return frozen;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Backwards compat alias
|
|
103
|
+
export const canonicalToolTable = canonicalTable;
|
|
104
|
+
|
|
105
|
+
export const canonicalNames = (set: DescriptorSet): readonly string[] => {
|
|
106
|
+
const table = canonicalTable(set);
|
|
107
|
+
return Object.keys(table).sort();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const allCanonicalNames = (
|
|
111
|
+
set: DescriptorSet,
|
|
112
|
+
toolMap?: ToolMap | MergedToolMap,
|
|
113
|
+
): readonly string[] => {
|
|
114
|
+
const base = canonicalNames(set);
|
|
115
|
+
const extra = toolMap
|
|
116
|
+
? Object.values(toolMap).flatMap((m) => Object.keys(m as Record<string, unknown>))
|
|
117
|
+
: [];
|
|
118
|
+
return [...new Set([...base, ...extra])].sort();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const hasCounterpart = (
|
|
122
|
+
table: CanonicalTable,
|
|
123
|
+
canonical: string,
|
|
124
|
+
harnessName: string,
|
|
125
|
+
toolMap?: MergedToolMap | ToolMap,
|
|
126
|
+
): boolean => {
|
|
127
|
+
if (toolMap) {
|
|
128
|
+
const hm = (toolMap as MergedToolMap)[harnessName];
|
|
129
|
+
if (hm && (hm as Record<string, unknown>)[canonical] !== undefined) {
|
|
130
|
+
const v = (hm as Record<string, unknown>)[canonical] as unknown;
|
|
131
|
+
if (typeof v === "object" && v !== null && "native" in (v as Record<string, unknown>))
|
|
132
|
+
return true;
|
|
133
|
+
if (typeof v === "string") return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const entry = table[canonical];
|
|
137
|
+
if (!entry) return false;
|
|
138
|
+
return entry[harnessName] !== undefined;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export const nativeFor = (
|
|
142
|
+
table: CanonicalTable,
|
|
143
|
+
canonical: string,
|
|
144
|
+
harnessName: string,
|
|
145
|
+
toolMap?: MergedToolMap | ToolMap,
|
|
146
|
+
): string | null => {
|
|
147
|
+
if (toolMap) {
|
|
148
|
+
const hm = (toolMap as Record<string, Record<string, unknown>>)[harnessName];
|
|
149
|
+
const val = hm?.[canonical];
|
|
150
|
+
if (val !== undefined) {
|
|
151
|
+
if (typeof val === "object" && val !== null && "native" in (val as Record<string, unknown>)) {
|
|
152
|
+
return (val as { native: string }).native;
|
|
153
|
+
}
|
|
154
|
+
if (typeof val === "string") return val as string;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const entry = table[canonical];
|
|
158
|
+
const v = entry?.[harnessName];
|
|
159
|
+
if (v?.kind === "builtin") return v.native;
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export const validateCanonicalList = (
|
|
164
|
+
names: readonly string[] | undefined,
|
|
165
|
+
allCanonical: readonly string[],
|
|
166
|
+
harness: string,
|
|
167
|
+
): void => {
|
|
168
|
+
if (!names) return;
|
|
169
|
+
const set = new Set(allCanonical);
|
|
170
|
+
for (const name of names) {
|
|
171
|
+
if (name.startsWith(NATIVE_PREFIX)) continue;
|
|
172
|
+
if (!set.has(name)) {
|
|
173
|
+
throw new ArgvRefusalError({
|
|
174
|
+
issue: "unknown-tool-name",
|
|
175
|
+
harness: harness as import("../knowledge/descriptor.js").HarnessName,
|
|
176
|
+
option: "tools",
|
|
177
|
+
supported: allCanonical as unknown as string[],
|
|
178
|
+
hint: "use native:<name> for an extension or MCP tool",
|
|
179
|
+
detail: `unknown tool name ${JSON.stringify(name)}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
@@ -14,7 +14,9 @@ import { DISCOVERY_FACETS, resolveRender, TURN_OPTION_KEYS } from "../knowledge/
|
|
|
14
14
|
import type { DiscoveryOptions, TurnOptions } from "./argv.js";
|
|
15
15
|
import { hintFor } from "./hints.js";
|
|
16
16
|
import { ArgvRefusalError } from "./refusal.js";
|
|
17
|
-
import {
|
|
17
|
+
import { renderToolSelection } from "./tool-selection.js";
|
|
18
|
+
import { READ_PRESET } from "./tool-vocabulary.js";
|
|
19
|
+
import { CLEAN_SELECTOR, resolveModel, validateAccess, validateEffort } from "./vocabulary.js";
|
|
18
20
|
|
|
19
21
|
// TOML-quoted value for config-kv: JSON.stringify is sufficient for the
|
|
20
22
|
// closed vocabularies that may use it (enum, effort) - no value contains a
|
|
@@ -34,6 +36,20 @@ export const renderTurnOptions = (
|
|
|
34
36
|
): string[] => {
|
|
35
37
|
const sequences: string[][] = [];
|
|
36
38
|
|
|
39
|
+
const accessRaw = opts.access;
|
|
40
|
+
if (accessRaw !== undefined) {
|
|
41
|
+
const v = validateAccess(accessRaw);
|
|
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
|
+
}
|
|
52
|
+
|
|
37
53
|
for (const key of TURN_OPTION_KEYS) {
|
|
38
54
|
const spec = h.turnOptions[key];
|
|
39
55
|
const raw = (opts as unknown as Record<string, unknown>)[key];
|
|
@@ -166,9 +182,85 @@ export const renderTurnOptions = (
|
|
|
166
182
|
continue;
|
|
167
183
|
}
|
|
168
184
|
|
|
185
|
+
// Access is opt-in-only, no profile default; dispatch to tool-selection for the preset.
|
|
186
|
+
if (key === "access") {
|
|
187
|
+
if (raw === undefined) continue;
|
|
188
|
+
if (spec === undefined) {
|
|
189
|
+
throw new ArgvRefusalError({
|
|
190
|
+
issue: "unsupported-option",
|
|
191
|
+
harness: h.name,
|
|
192
|
+
option: key,
|
|
193
|
+
supported: Object.keys(h.turnOptions).length ? Object.keys(h.turnOptions) : ["(none)"],
|
|
194
|
+
detail: String(raw),
|
|
195
|
+
hint: hintFor(h.name, key),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const v = validateAccess(raw as string);
|
|
199
|
+
if (!v.ok) {
|
|
200
|
+
throw new ArgvRefusalError({
|
|
201
|
+
issue: "invalid-option-value",
|
|
202
|
+
harness: h.name,
|
|
203
|
+
option: key,
|
|
204
|
+
supported: ["read", "write"],
|
|
205
|
+
detail: String(raw),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// When discovery.tools is off, the read preset must not re-enable tools.
|
|
209
|
+
if (raw === "read" && opts.discovery?.tools === false) {
|
|
210
|
+
if (spec.kind === "tool-preset") continue;
|
|
211
|
+
}
|
|
212
|
+
switch (spec.kind) {
|
|
213
|
+
case "tool-preset": {
|
|
214
|
+
if (raw === "write") break;
|
|
215
|
+
const toolMapForHarness = (opts as { toolMap?: Record<string, Record<string, string>> })
|
|
216
|
+
.toolMap?.[h.name];
|
|
217
|
+
const filtered = (READ_PRESET as readonly string[]).filter((c) => {
|
|
218
|
+
if (toolMapForHarness?.[c] !== undefined) return true;
|
|
219
|
+
if (h.tools.builtins.some((b) => b.canonical === c)) return true;
|
|
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
|
+
}
|
|
254
|
+
}
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
|
|
169
258
|
// Non-discovery keys
|
|
170
|
-
// Handle enum default on launch
|
|
259
|
+
// Handle enum default on launch. An access preset already claimed the
|
|
260
|
+
// sandbox flag on harnesses that express access through it (codex);
|
|
261
|
+
// the enum default must not emit a second --sandbox.
|
|
171
262
|
if (raw === undefined) {
|
|
263
|
+
if (key === "sandbox" && opts.access !== undefined) continue;
|
|
172
264
|
if (
|
|
173
265
|
spec !== undefined &&
|
|
174
266
|
spec.kind === "enum" &&
|
|
@@ -430,6 +522,11 @@ export const renderTurnOptions = (
|
|
|
430
522
|
case "discovery":
|
|
431
523
|
// already handled
|
|
432
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;
|
|
433
530
|
default: {
|
|
434
531
|
const _exhaustive: never = spec as never;
|
|
435
532
|
throw new ArgvRefusalError({
|
|
@@ -57,6 +57,11 @@ export const validateModel = (h: HarnessDescriptor, model: string): Validated =>
|
|
|
57
57
|
/** Validate an effort against the ladder that applies to the pick: the
|
|
58
58
|
* model's own ladder where the harness constrains per model (codex), else
|
|
59
59
|
* the harness-wide ladder. */
|
|
60
|
+
export const validateAccess = (value: string): Validated => {
|
|
61
|
+
if (value === "read" || value === "write") return { ok: true, id: value };
|
|
62
|
+
return { ok: false, reason: `unknown access ${JSON.stringify(value)}; accepted: read, write` };
|
|
63
|
+
};
|
|
64
|
+
|
|
60
65
|
export const validateEffort = (h: HarnessDescriptor, effort: string, model?: string): Validated => {
|
|
61
66
|
let ladder = h.vocabulary.efforts;
|
|
62
67
|
if (model !== undefined && h.vocabulary.effortsByModel !== undefined) {
|
|
@@ -177,6 +177,7 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
177
177
|
},
|
|
178
178
|
},
|
|
179
179
|
},
|
|
180
|
+
access: { kind: "tool-preset", render: { kind: "flag-value", flag: "--allowedTools" } },
|
|
180
181
|
},
|
|
181
182
|
// Phase 0 fixtures: claude-tool-interplay.md. include is a permission
|
|
182
183
|
// grant (Bash, Edit stay visible under --allowedTools Read); only the
|
|
@@ -191,19 +192,19 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
191
192
|
includeIsStrictAllowlist: false,
|
|
192
193
|
composable: true,
|
|
193
194
|
builtins: [
|
|
194
|
-
{ name: "Bash", defaultEnabled: true },
|
|
195
|
-
{ name: "Edit", defaultEnabled: true },
|
|
196
|
-
{ name: "Glob", defaultEnabled: true },
|
|
197
|
-
{ name: "Grep", defaultEnabled: true },
|
|
198
|
-
{ name: "Read", defaultEnabled: true },
|
|
199
|
-
{ name: "Write", defaultEnabled: true },
|
|
200
|
-
{ name: "WebFetch", defaultEnabled: true },
|
|
201
|
-
{ name: "WebSearch", defaultEnabled: true },
|
|
202
|
-
{ name: "Monitor", defaultEnabled: true },
|
|
203
|
-
{ name: "Task", defaultEnabled: true },
|
|
204
|
-
{ name: "Skill", defaultEnabled: true },
|
|
205
|
-
{ name: "NotebookEdit", defaultEnabled: true },
|
|
206
|
-
{ name: "LSP", defaultEnabled: true },
|
|
195
|
+
{ name: "Bash", defaultEnabled: true, canonical: "shell" },
|
|
196
|
+
{ name: "Edit", defaultEnabled: true, canonical: "edit" },
|
|
197
|
+
{ name: "Glob", defaultEnabled: true, canonical: "glob" },
|
|
198
|
+
{ name: "Grep", defaultEnabled: true, canonical: "grep" },
|
|
199
|
+
{ name: "Read", defaultEnabled: true, canonical: "read" },
|
|
200
|
+
{ name: "Write", defaultEnabled: true, canonical: "write" },
|
|
201
|
+
{ name: "WebFetch", defaultEnabled: true, canonical: "web-fetch" },
|
|
202
|
+
{ name: "WebSearch", defaultEnabled: true, canonical: "web-search" },
|
|
203
|
+
{ name: "Monitor", defaultEnabled: true, canonical: null },
|
|
204
|
+
{ name: "Task", defaultEnabled: true, canonical: "subagent" },
|
|
205
|
+
{ name: "Skill", defaultEnabled: true, canonical: "skill" },
|
|
206
|
+
{ name: "NotebookEdit", defaultEnabled: true, canonical: null },
|
|
207
|
+
{ name: "LSP", defaultEnabled: true, canonical: null },
|
|
207
208
|
],
|
|
208
209
|
categories: [],
|
|
209
210
|
denySemantics: "remove-from-set",
|
package/src/knowledge/codex.ts
CHANGED
|
@@ -122,6 +122,12 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
122
122
|
render: { kind: "flag-value", flag: "--sandbox" },
|
|
123
123
|
resumeRender: null,
|
|
124
124
|
},
|
|
125
|
+
access: {
|
|
126
|
+
kind: "flag-value",
|
|
127
|
+
flag: "--sandbox",
|
|
128
|
+
values: { read: "read-only", write: "workspace-write" },
|
|
129
|
+
render: { kind: "flag-value", flag: "--sandbox" },
|
|
130
|
+
},
|
|
125
131
|
},
|
|
126
132
|
// Phase 0 fixtures: codex-tool-surface.md. No name lists anywhere -
|
|
127
133
|
// not on the CLI, not in config.toml. Control is feature booleans
|
|
@@ -136,10 +142,10 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
136
142
|
composable: false,
|
|
137
143
|
builtins: [],
|
|
138
144
|
categories: [
|
|
139
|
-
{ key: "shell", disableFlag: null, configKey: "features.shell_tool" },
|
|
140
|
-
{ key: "exec", disableFlag: null, configKey: "features.unified_exec" },
|
|
141
|
-
{ key: "web", disableFlag: null, configKey: "web_search" },
|
|
142
|
-
{ key: "view-image", disableFlag: null, configKey: "tools.view_image" },
|
|
145
|
+
{ key: "shell", disableFlag: null, configKey: "features.shell_tool", canonical: [] },
|
|
146
|
+
{ key: "exec", disableFlag: null, configKey: "features.unified_exec", canonical: [] },
|
|
147
|
+
{ key: "web", disableFlag: null, configKey: "web_search", canonical: [] },
|
|
148
|
+
{ key: "view-image", disableFlag: null, configKey: "tools.view_image", canonical: [] },
|
|
143
149
|
],
|
|
144
150
|
denySemantics: "no-lists",
|
|
145
151
|
},
|
|
@@ -26,6 +26,21 @@ export const deepFreeze = <T>(value: T): T => {
|
|
|
26
26
|
return value;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export const CANONICAL_TOOLS = deepFreeze([
|
|
30
|
+
"read",
|
|
31
|
+
"edit",
|
|
32
|
+
"write",
|
|
33
|
+
"shell",
|
|
34
|
+
"grep",
|
|
35
|
+
"glob",
|
|
36
|
+
"list",
|
|
37
|
+
"web-fetch",
|
|
38
|
+
"web-search",
|
|
39
|
+
"subagent",
|
|
40
|
+
"skill",
|
|
41
|
+
] as const);
|
|
42
|
+
export type CanonicalTool = (typeof CANONICAL_TOOLS)[number];
|
|
43
|
+
|
|
29
44
|
export type StreamingGranularity = "token" | "message" | "none";
|
|
30
45
|
|
|
31
46
|
export type HarnessMode = "headless-turn" | "headless-session" | "interactive";
|
|
@@ -71,6 +86,11 @@ export interface AuthMatcher {
|
|
|
71
86
|
readonly kind: AuthFailureKind;
|
|
72
87
|
}
|
|
73
88
|
|
|
89
|
+
export interface TransportMatcher {
|
|
90
|
+
readonly pattern: string;
|
|
91
|
+
readonly flags?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
74
94
|
/** The turn-option vocabulary is closed for the same reason `LimitCode` is:
|
|
75
95
|
* a descriptor must not invent an option a consumer has no field for. Every
|
|
76
96
|
* key here maps to a field on `TurnOptions`; adding a key without a consumer
|
|
@@ -90,6 +110,7 @@ export const TURN_OPTION_KEYS = deepFreeze([
|
|
|
90
110
|
// that strips the payload changes every bare run's semantics.
|
|
91
111
|
"systemPrompt",
|
|
92
112
|
"appendSystemPrompt",
|
|
113
|
+
"access",
|
|
93
114
|
] as const);
|
|
94
115
|
export type TurnOptionKey = (typeof TURN_OPTION_KEYS)[number];
|
|
95
116
|
|
|
@@ -136,6 +157,17 @@ export type TurnOptionSpec =
|
|
|
136
157
|
readonly values: readonly string[];
|
|
137
158
|
readonly default?: string;
|
|
138
159
|
})
|
|
160
|
+
/** Access preset: read/write dimension with per-harness rendering. */
|
|
161
|
+
| (SpecBase & { readonly kind: "tool-preset" })
|
|
162
|
+
| (SpecBase & {
|
|
163
|
+
readonly kind: "flag-value";
|
|
164
|
+
readonly flag: string;
|
|
165
|
+
readonly values: Readonly<Record<string, string>>;
|
|
166
|
+
})
|
|
167
|
+
| (SpecBase & {
|
|
168
|
+
readonly kind: "flag-list-by-value";
|
|
169
|
+
readonly flags: Readonly<Record<string, readonly string[]>>;
|
|
170
|
+
})
|
|
139
171
|
/** Ladder comes from vocabulary.efforts / effortsByModel, not from here. */
|
|
140
172
|
| (SpecBase & { readonly kind: "effort" })
|
|
141
173
|
/** Open selector, CLEAN_SELECTOR-validated. */
|
|
@@ -182,8 +214,14 @@ export const getOptionRender = (
|
|
|
182
214
|
spec: TurnOptionSpec,
|
|
183
215
|
phase: "launch" | "resume",
|
|
184
216
|
): OptionRender | null => {
|
|
185
|
-
if (
|
|
186
|
-
|
|
217
|
+
if (
|
|
218
|
+
spec.kind === "discovery" ||
|
|
219
|
+
spec.kind === "tool-preset" ||
|
|
220
|
+
spec.kind === "flag-value" ||
|
|
221
|
+
spec.kind === "flag-list-by-value"
|
|
222
|
+
)
|
|
223
|
+
return null;
|
|
224
|
+
return resolveRender(spec as SpecBase, phase);
|
|
187
225
|
};
|
|
188
226
|
|
|
189
227
|
export interface HarnessDescriptor {
|
|
@@ -400,11 +438,13 @@ export interface HarnessDescriptor {
|
|
|
400
438
|
readonly builtins: ReadonlyArray<{
|
|
401
439
|
readonly name: string;
|
|
402
440
|
readonly defaultEnabled: boolean;
|
|
441
|
+
readonly canonical: CanonicalTool | null;
|
|
403
442
|
}>;
|
|
404
443
|
readonly categories: ReadonlyArray<{
|
|
405
444
|
readonly key: "shell" | "write" | "web" | "exec" | "view-image";
|
|
406
445
|
readonly disableFlag: string | null;
|
|
407
446
|
readonly configKey: string | null;
|
|
447
|
+
readonly canonical: readonly CanonicalTool[];
|
|
408
448
|
}>;
|
|
409
449
|
readonly denySemantics: "remove-from-set" | "policy-gate" | "no-lists";
|
|
410
450
|
};
|
package/src/knowledge/index.ts
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
export { claudeCode } from "./claude-code.js";
|
|
13
13
|
export { codexCli } from "./codex.js";
|
|
14
14
|
export * from "./descriptor.js";
|
|
15
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
SHARED_AUTH_MATCHERS,
|
|
17
|
+
SHARED_LIMIT_MATCHERS,
|
|
18
|
+
SHARED_TRANSPORT_MATCHERS,
|
|
19
|
+
} from "./matchers.js";
|
|
16
20
|
export { museCode } from "./muse.js";
|
|
17
21
|
export {
|
|
18
22
|
type DescriptorSet,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* or a provider 401 must classify no matter which CLI printed it. Each
|
|
5
5
|
* descriptor spreads these after its harness-specific phrasings.
|
|
6
6
|
*/
|
|
7
|
-
import type { AuthMatcher, LimitMatcher } from "./descriptor.js";
|
|
7
|
+
import type { AuthMatcher, LimitMatcher, TransportMatcher } from "./descriptor.js";
|
|
8
8
|
|
|
9
9
|
export const SHARED_LIMIT_MATCHERS: ReadonlyArray<LimitMatcher> = [
|
|
10
10
|
{ pattern: "you'?ve hit your usage limit", flags: "i", code: "usage-limit" },
|
|
@@ -37,3 +37,19 @@ export const SHARED_AUTH_MATCHERS: ReadonlyArray<AuthMatcher> = [
|
|
|
37
37
|
{ pattern: "401 unauthorized", flags: "i", kind: "expired" },
|
|
38
38
|
{ pattern: "invalid api key", flags: "i", kind: "invalid-key" },
|
|
39
39
|
];
|
|
40
|
+
|
|
41
|
+
export const SHARED_TRANSPORT_MATCHERS: ReadonlyArray<TransportMatcher> = [
|
|
42
|
+
{ pattern: "connection error", flags: "i" },
|
|
43
|
+
{ pattern: "ECONNREFUSED", flags: "i" },
|
|
44
|
+
{ pattern: "ECONNRESET", flags: "i" },
|
|
45
|
+
{ pattern: "ENOTFOUND", flags: "i" },
|
|
46
|
+
{ pattern: "EAI_AGAIN", flags: "i" },
|
|
47
|
+
{ pattern: "ETIMEDOUT", flags: "i" },
|
|
48
|
+
{ pattern: "fetch failed", flags: "i" },
|
|
49
|
+
{ pattern: "socket hang up", flags: "i" },
|
|
50
|
+
{ pattern: "network error", flags: "i" },
|
|
51
|
+
{ pattern: "service unavailable", flags: "i" },
|
|
52
|
+
{ pattern: "bad gateway", flags: "i" },
|
|
53
|
+
{ pattern: "gateway time-?out", flags: "i" },
|
|
54
|
+
{ pattern: "\\b(?:HTTP|status(?:[_ ]?code)?|code)\\b\\W*[:=]?\\W*50[234]\\b", flags: "i" },
|
|
55
|
+
];
|
package/src/knowledge/muse.ts
CHANGED
|
@@ -109,6 +109,11 @@ export const museCode: HarnessDescriptor = deepFreeze({
|
|
|
109
109
|
max: 10000,
|
|
110
110
|
render: { kind: "flag-value", flag: "--max-model-steps" },
|
|
111
111
|
},
|
|
112
|
+
access: {
|
|
113
|
+
kind: "flag-list-by-value",
|
|
114
|
+
flags: { read: ["--disable-write", "--disable-shell"], write: [] },
|
|
115
|
+
render: { kind: "flag-list", flags: ["--disable-write", "--disable-shell"] },
|
|
116
|
+
},
|
|
112
117
|
},
|
|
113
118
|
// Phase 0 fixtures: muse-category-flags.md. No name lists; category
|
|
114
119
|
// switches are enforcement gates - tools stay listed in the catalog but
|
|
@@ -124,9 +129,19 @@ export const museCode: HarnessDescriptor = deepFreeze({
|
|
|
124
129
|
composable: false,
|
|
125
130
|
builtins: [],
|
|
126
131
|
categories: [
|
|
127
|
-
{
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
{
|
|
133
|
+
key: "write",
|
|
134
|
+
disableFlag: "--disable-write",
|
|
135
|
+
configKey: null,
|
|
136
|
+
canonical: ["write", "edit"],
|
|
137
|
+
},
|
|
138
|
+
{ key: "shell", disableFlag: "--disable-shell", configKey: null, canonical: ["shell"] },
|
|
139
|
+
{
|
|
140
|
+
key: "web",
|
|
141
|
+
disableFlag: "--disable-web-tools",
|
|
142
|
+
configKey: null,
|
|
143
|
+
canonical: ["web-fetch", "web-search"],
|
|
144
|
+
},
|
|
130
145
|
],
|
|
131
146
|
denySemantics: "policy-gate",
|
|
132
147
|
},
|
|
@@ -25,12 +25,14 @@ import { piCli } from "./pi.js";
|
|
|
25
25
|
|
|
26
26
|
export type DescriptorSet = Partial<Record<HarnessName, HarnessDescriptor>>;
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
const SHARED_DESCRIPTORS: DescriptorSet = {
|
|
29
29
|
claude: claudeCode,
|
|
30
30
|
codex: codexCli,
|
|
31
31
|
pi: piCli,
|
|
32
32
|
muse: museCode,
|
|
33
|
-
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const defaultDescriptors = (): DescriptorSet => SHARED_DESCRIPTORS;
|
|
34
36
|
|
|
35
37
|
/** Tracks which descriptors were produced by an override file and what their
|
|
36
38
|
* matcher counts are, so the execution layer can emit `matcherOverrides` on
|
package/src/knowledge/pi.ts
CHANGED
|
@@ -78,7 +78,10 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
78
78
|
announce: { match: { type: "session" }, idField: "id" },
|
|
79
79
|
},
|
|
80
80
|
limitMatchers: [...SHARED_LIMIT_MATCHERS],
|
|
81
|
-
authMatchers: [
|
|
81
|
+
authMatchers: [
|
|
82
|
+
{ pattern: "No API key found for", flags: "i", kind: "not-logged-in" },
|
|
83
|
+
...SHARED_AUTH_MATCHERS,
|
|
84
|
+
],
|
|
82
85
|
autonomy: null,
|
|
83
86
|
vocabulary: {
|
|
84
87
|
modelFlag: "--model",
|
|
@@ -151,6 +154,7 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
151
154
|
},
|
|
152
155
|
},
|
|
153
156
|
},
|
|
157
|
+
access: { kind: "tool-preset", render: { kind: "flag-value", flag: "--tools" } },
|
|
154
158
|
},
|
|
155
159
|
// Phase 0 fixtures: pi-both-tool-flags.md. Both list flags legal at once;
|
|
156
160
|
// exclude subtracts from include. --tools is strict over BUILT-INS but
|
|
@@ -163,13 +167,13 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
163
167
|
includeIsStrictAllowlist: true,
|
|
164
168
|
composable: true,
|
|
165
169
|
builtins: [
|
|
166
|
-
{ name: "read", defaultEnabled: true },
|
|
167
|
-
{ name: "bash", defaultEnabled: true },
|
|
168
|
-
{ name: "edit", defaultEnabled: true },
|
|
169
|
-
{ name: "write", defaultEnabled: true },
|
|
170
|
-
{ name: "grep", defaultEnabled: false },
|
|
171
|
-
{ name: "find", defaultEnabled: false },
|
|
172
|
-
{ name: "ls", defaultEnabled: false },
|
|
170
|
+
{ name: "read", defaultEnabled: true, canonical: "read" },
|
|
171
|
+
{ name: "bash", defaultEnabled: true, canonical: "shell" },
|
|
172
|
+
{ name: "edit", defaultEnabled: true, canonical: "edit" },
|
|
173
|
+
{ name: "write", defaultEnabled: true, canonical: "write" },
|
|
174
|
+
{ name: "grep", defaultEnabled: false, canonical: "grep" },
|
|
175
|
+
{ name: "find", defaultEnabled: false, canonical: "glob" },
|
|
176
|
+
{ name: "ls", defaultEnabled: false, canonical: "list" },
|
|
173
177
|
],
|
|
174
178
|
categories: [],
|
|
175
179
|
denySemantics: "remove-from-set",
|