@dungle-scrubs/harness-cli-normalizer 0.4.4 → 0.4.6
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 +23 -8
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +16 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +4 -0
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +10 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/inspect.d.ts.map +1 -1
- package/dist/cli/inspect.js +74 -24
- 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/run.d.ts.map +1 -1
- package/dist/cli/run.js +135 -85
- 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 +3 -3
- package/dist/execution/decode.d.ts.map +1 -1
- package/dist/execution/decode.js +46 -13
- package/dist/execution/decode.js.map +1 -1
- package/dist/execution/deps.d.ts +4 -1
- package/dist/execution/deps.d.ts.map +1 -1
- package/dist/execution/deps.js +1 -1
- package/dist/execution/events.d.ts +7 -2
- package/dist/execution/events.d.ts.map +1 -1
- package/dist/execution/events.js.map +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/node-deps.d.ts.map +1 -1
- package/dist/execution/node-deps.js +1 -0
- package/dist/execution/node-deps.js.map +1 -1
- package/dist/execution/open-session.d.ts.map +1 -1
- package/dist/execution/open-session.js +54 -8
- package/dist/execution/open-session.js.map +1 -1
- package/dist/execution/stream-turn.d.ts +4 -0
- package/dist/execution/stream-turn.d.ts.map +1 -1
- package/dist/execution/stream-turn.js +147 -33
- package/dist/execution/stream-turn.js.map +1 -1
- package/dist/interpretation/argv.d.ts +7 -0
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +28 -5
- package/dist/interpretation/argv.js.map +1 -1
- package/dist/interpretation/capabilities.d.ts.map +1 -1
- package/dist/interpretation/capabilities.js +4 -1
- package/dist/interpretation/capabilities.js.map +1 -1
- package/dist/interpretation/content.d.ts +9 -1
- package/dist/interpretation/content.d.ts.map +1 -1
- package/dist/interpretation/content.js +16 -4
- package/dist/interpretation/content.js.map +1 -1
- package/dist/interpretation/hints.d.ts.map +1 -1
- package/dist/interpretation/hints.js +5 -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 +16 -0
- package/dist/interpretation/resolve-options.js.map +1 -1
- package/dist/interpretation/tool-selection.d.ts.map +1 -1
- package/dist/interpretation/tool-selection.js +25 -0
- package/dist/interpretation/tool-selection.js.map +1 -1
- package/dist/interpretation/turn-options.d.ts.map +1 -1
- package/dist/interpretation/turn-options.js +35 -3
- 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/knowledge/claude-code.d.ts.map +1 -1
- package/dist/knowledge/claude-code.js +21 -0
- package/dist/knowledge/claude-code.js.map +1 -1
- package/dist/knowledge/codex.d.ts.map +1 -1
- package/dist/knowledge/codex.js +9 -0
- package/dist/knowledge/codex.js.map +1 -1
- package/dist/knowledge/descriptor.d.ts +25 -10
- package/dist/knowledge/descriptor.d.ts.map +1 -1
- package/dist/knowledge/descriptor.js +6 -0
- 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 +23 -1
- package/dist/knowledge/matchers.js.map +1 -1
- package/dist/knowledge/pi.d.ts.map +1 -1
- package/dist/knowledge/pi.js +24 -1
- package/dist/knowledge/pi.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/args.ts +15 -0
- package/src/cli/config.ts +4 -0
- package/src/cli/help.ts +10 -0
- package/src/cli/inspect.ts +70 -22
- package/src/cli/provenance.ts +19 -0
- package/src/cli/run.ts +172 -91
- package/src/cli/session.ts +4 -3
- package/src/cli/skills-root.ts +11 -4
- package/src/execution/decode.ts +46 -12
- package/src/execution/deps.ts +4 -1
- package/src/execution/events.ts +5 -2
- package/src/execution/failure.ts +17 -1
- package/src/execution/index.ts +2 -2
- package/src/execution/node-deps.ts +1 -0
- package/src/execution/open-session.ts +63 -6
- package/src/execution/stream-turn.ts +150 -31
- package/src/interpretation/argv.ts +34 -5
- package/src/interpretation/capabilities.ts +4 -1
- package/src/interpretation/content.ts +22 -5
- package/src/interpretation/hints.ts +8 -0
- package/src/interpretation/limits.ts +29 -0
- package/src/interpretation/resolve-options.ts +17 -0
- package/src/interpretation/tool-selection.ts +25 -0
- package/src/interpretation/turn-options.ts +31 -3
- package/src/interpretation/versions.ts +1 -1
- package/src/knowledge/claude-code.ts +21 -0
- package/src/knowledge/codex.ts +9 -0
- package/src/knowledge/descriptor.ts +33 -10
- package/src/knowledge/index.ts +5 -1
- package/src/knowledge/matchers.ts +25 -2
- package/src/knowledge/pi.ts +24 -1
|
@@ -23,7 +23,9 @@ import type {
|
|
|
23
23
|
HarnessDescriptor,
|
|
24
24
|
LimitCode,
|
|
25
25
|
LimitMatcher,
|
|
26
|
+
TransportMatcher,
|
|
26
27
|
} from "../knowledge/descriptor.js";
|
|
28
|
+
import { SHARED_TRANSPORT_MATCHERS } from "../knowledge/matchers.js";
|
|
27
29
|
|
|
28
30
|
/** Bottom-up batch scans stop after this many non-empty lines: the wall is
|
|
29
31
|
* virtually always the last thing a dying turn printed, and an unbounded
|
|
@@ -152,3 +154,30 @@ export const detectAuthFailureInLine = (
|
|
|
152
154
|
|
|
153
155
|
export const detectAuthFailure = (h: HarnessDescriptor, output: string): AuthFailureKind | null =>
|
|
154
156
|
scanTail(output, compileAuthMatchers(h.authMatchers));
|
|
157
|
+
|
|
158
|
+
const transportCache = new WeakMap<
|
|
159
|
+
ReadonlyArray<TransportMatcher>,
|
|
160
|
+
ReadonlyArray<readonly [RegExp, boolean]>
|
|
161
|
+
>();
|
|
162
|
+
|
|
163
|
+
const compileTransportMatchers = (
|
|
164
|
+
matchers: ReadonlyArray<TransportMatcher>,
|
|
165
|
+
): ReadonlyArray<readonly [RegExp, boolean]> => {
|
|
166
|
+
const cached = transportCache.get(matchers);
|
|
167
|
+
if (cached !== undefined) return cached;
|
|
168
|
+
if (matchers.length > MAX_MATCHERS_PER_KIND) {
|
|
169
|
+
throw new Error(`more than ${MAX_MATCHERS_PER_KIND} matchers per harness per kind`);
|
|
170
|
+
}
|
|
171
|
+
const compiled = matchers.map((m) => [validateAndCompile(m.pattern, m.flags), true] as const);
|
|
172
|
+
transportCache.set(matchers, compiled);
|
|
173
|
+
return compiled;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export const detectTransportInLine = (line: string): boolean => {
|
|
177
|
+
const compiled = compileTransportMatchers(SHARED_TRANSPORT_MATCHERS);
|
|
178
|
+
const windowed = line.slice(0, WINDOW).trim();
|
|
179
|
+
for (const [re] of compiled) {
|
|
180
|
+
if (re.test(windowed)) return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
};
|
|
@@ -163,6 +163,23 @@ export const resolveEffectiveOptions = (
|
|
|
163
163
|
// the emit-nothing rule, recorded in provenance. On a harness with
|
|
164
164
|
// dormant built-ins (pi), it becomes the enabling include list.
|
|
165
165
|
if (key === "tools" && value === "all-known") {
|
|
166
|
+
// --no-tools containment: a tier that switched discovery.tools off
|
|
167
|
+
// must not have the profile grant switch them back on (pi reads
|
|
168
|
+
// --tools as an enabling allowlist). The tier that turned tools off
|
|
169
|
+
// owns the skip.
|
|
170
|
+
const toolsOff = (o: Partial<TurnOptions> | undefined): boolean =>
|
|
171
|
+
o?.discovery?.tools === false;
|
|
172
|
+
const offTier: ProvenanceTier | undefined = toolsOff(effectiveArgs)
|
|
173
|
+
? "arg"
|
|
174
|
+
: toolsOff(tiers.project)
|
|
175
|
+
? "project-config"
|
|
176
|
+
: toolsOff(tiers.user)
|
|
177
|
+
? "user-config"
|
|
178
|
+
: undefined;
|
|
179
|
+
if (offTier !== undefined) {
|
|
180
|
+
provenance.push({ key, value: "none (discovery.tools off)", tier: offTier });
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
166
183
|
const enabled = h.tools.builtins.filter((t) => t.defaultEnabled).length;
|
|
167
184
|
const all = h.tools.builtins.length;
|
|
168
185
|
if (enabled === all) {
|
|
@@ -107,8 +107,33 @@ export const renderToolSelection = (
|
|
|
107
107
|
|
|
108
108
|
if (hasInclude) {
|
|
109
109
|
const names = validateNames(h, selection.include!);
|
|
110
|
+
if (names.length === 0) {
|
|
111
|
+
// Empty include list = deny everything. On pi --tools "" has no
|
|
112
|
+
// defined meaning, so refuse; on claude emit the deny complement
|
|
113
|
+
// without an empty include token.
|
|
114
|
+
if (h.tools.includeIsStrictAllowlist || h.tools.excludeFlag === null) {
|
|
115
|
+
throw new ArgvRefusalError({
|
|
116
|
+
issue: "invalid-tool-grant",
|
|
117
|
+
harness: h.name,
|
|
118
|
+
option: "tools",
|
|
119
|
+
supported: ["non-empty tool list (empty include would deny everything)"],
|
|
120
|
+
detail: "empty include list",
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const known = h.tools.builtins.map((t) => t.name);
|
|
124
|
+
return { tokens: [h.tools.excludeFlag, known.join(",")], unmapped: [] };
|
|
125
|
+
}
|
|
110
126
|
const { mapped, unmapped } = resolveNames(h, names);
|
|
111
127
|
if (!h.tools.includeIsStrictAllowlist) {
|
|
128
|
+
if (mapped.length === 0 && unmapped.length > 0) {
|
|
129
|
+
throw new ArgvRefusalError({
|
|
130
|
+
issue: "unknown-tool-name",
|
|
131
|
+
harness: h.name,
|
|
132
|
+
option: "tools",
|
|
133
|
+
supported: [`known tool names: ${h.tools.builtins.map((t) => t.name).join(", ")}`],
|
|
134
|
+
detail: `unknown tool name(s) ${unmapped.join(", ")}`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
112
137
|
// claude: exact allowlist must reshape the visible set via the deny
|
|
113
138
|
// complement (probe 2b). The include flag also carries the granted
|
|
114
139
|
// names (curated + pass-throughs) so they skip approval prompts;
|
|
@@ -227,17 +227,22 @@ export const renderTurnOptions = (
|
|
|
227
227
|
supported: resumeSupported.length ? resumeSupported : ["(none)"],
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
|
-
// Reject config-kv for
|
|
230
|
+
// Reject config-kv for open vocabularies. Exception (issue #48):
|
|
231
|
+
// prompt-text rides config-kv VERBATIM on codex - both a literal and
|
|
232
|
+
// a path are accepted (live-verified 0.146.1); no quoting, because
|
|
233
|
+
// codex's k=v split takes the rest of the token raw and both probed
|
|
234
|
+
// forms passed unquoted.
|
|
231
235
|
if (
|
|
232
236
|
(spec as unknown as SpecBase).render.kind === "config-kv" &&
|
|
233
237
|
(spec as { kind: string }).kind !== "enum" &&
|
|
234
|
-
(spec as { kind: string }).kind !== "effort"
|
|
238
|
+
(spec as { kind: string }).kind !== "effort" &&
|
|
239
|
+
(spec as { kind: string }).kind !== "prompt-text"
|
|
235
240
|
) {
|
|
236
241
|
throw new ArgvRefusalError({
|
|
237
242
|
issue: "invalid-option-value",
|
|
238
243
|
harness: h.name,
|
|
239
244
|
option: key,
|
|
240
|
-
supported: ["config-kv only for enum and
|
|
245
|
+
supported: ["config-kv only for enum, effort, and prompt-text (issue #48)"],
|
|
241
246
|
});
|
|
242
247
|
}
|
|
243
248
|
}
|
|
@@ -320,6 +325,29 @@ export const renderTurnOptions = (
|
|
|
320
325
|
sequences.push(seq);
|
|
321
326
|
break;
|
|
322
327
|
}
|
|
328
|
+
case "prompt-text": {
|
|
329
|
+
// issue #48: free-form prompt prose (systemPrompt / appendSystemPrompt).
|
|
330
|
+
// No closed vocabulary - validate non-empty string, render verbatim.
|
|
331
|
+
// Values may be multi-line and may contain shell-hostile characters;
|
|
332
|
+
// they cross as single argv tokens, never through a shell.
|
|
333
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
334
|
+
throw new ArgvRefusalError({
|
|
335
|
+
issue: "invalid-option-value",
|
|
336
|
+
harness: h.name,
|
|
337
|
+
option: key,
|
|
338
|
+
supported: ["non-empty prompt text"],
|
|
339
|
+
detail: typeof raw === "string" ? "(empty)" : String(raw),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
const r = effectiveRender!;
|
|
343
|
+
let seq: string[];
|
|
344
|
+
if (r.kind === "flag-value") seq = [...(r.extraFlags ?? []), r.flag, raw];
|
|
345
|
+
else if (r.kind === "config-kv") seq = [r.flag, `${r.key}=${raw}`];
|
|
346
|
+
else if (r.kind === "flag-list") seq = [...r.flags];
|
|
347
|
+
else seq = [];
|
|
348
|
+
sequences.push(seq);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
323
351
|
case "selector": {
|
|
324
352
|
if (typeof raw !== "string") {
|
|
325
353
|
throw new ArgvRefusalError({
|
|
@@ -49,7 +49,10 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
49
49
|
// A-001: one process, many turns; `result` delimits turns; mid-turn sends
|
|
50
50
|
// queue. --setting-sources project isolates the child from user-level
|
|
51
51
|
// hooks (D-025). Token deltas require this exact output flag set.
|
|
52
|
+
// sessionMode.flags is the complete flag list after the binary, so -p
|
|
53
|
+
// lives here rather than being inherited from launch.baseFlags.
|
|
52
54
|
flags: [
|
|
55
|
+
"-p",
|
|
53
56
|
"--input-format",
|
|
54
57
|
"stream-json",
|
|
55
58
|
"--output-format",
|
|
@@ -139,6 +142,24 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
139
142
|
},
|
|
140
143
|
turnOptions: {
|
|
141
144
|
effort: { kind: "effort", render: { kind: "flag-value", flag: "--effort" } },
|
|
145
|
+
// issue #48, live-verified 2.1.235: --system-prompt replaces the built-in
|
|
146
|
+
// prompt; --exclude-dynamic-system-prompt-sections strips the dynamic
|
|
147
|
+
// sections (git state, directory listing) that ride even under a
|
|
148
|
+
// replacement. hcn pairs them - a payload-stripping replacement without
|
|
149
|
+
// the exclusion keeps injected sections. Verified live: the pair changes
|
|
150
|
+
// model behavior (NAKED-HAIKU probe vs BASELINE-OK).
|
|
151
|
+
systemPrompt: {
|
|
152
|
+
kind: "prompt-text",
|
|
153
|
+
render: {
|
|
154
|
+
kind: "flag-value",
|
|
155
|
+
flag: "--system-prompt",
|
|
156
|
+
extraFlags: ["--exclude-dynamic-system-prompt-sections"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
appendSystemPrompt: {
|
|
160
|
+
kind: "prompt-text",
|
|
161
|
+
render: { kind: "flag-value", flag: "--append-system-prompt" },
|
|
162
|
+
},
|
|
142
163
|
discovery: {
|
|
143
164
|
kind: "discovery",
|
|
144
165
|
facets: {
|
package/src/knowledge/codex.ts
CHANGED
|
@@ -106,6 +106,15 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
106
106
|
kind: "effort",
|
|
107
107
|
render: { kind: "config-kv", flag: "-c", key: "model_reasoning_effort" },
|
|
108
108
|
},
|
|
109
|
+
// issue #48, live-verified 0.146.1 under --strict-config: the config key
|
|
110
|
+
// `instructions` accepts BOTH a literal string (LITERAL-OK probe) and a
|
|
111
|
+
// file path (FILE-OK probe); hcn passes the value verbatim and codex
|
|
112
|
+
// validates. `model_instructions` / `experimental_instructions_file`
|
|
113
|
+
// are refused by codex (probed) - wrong spellings, not alternates.
|
|
114
|
+
systemPrompt: {
|
|
115
|
+
kind: "prompt-text",
|
|
116
|
+
render: { kind: "config-kv", flag: "-c", key: "instructions" },
|
|
117
|
+
},
|
|
109
118
|
sandbox: {
|
|
110
119
|
kind: "enum",
|
|
111
120
|
values: ["read-only", "workspace-write", "danger-full-access"],
|
|
@@ -71,6 +71,11 @@ export interface AuthMatcher {
|
|
|
71
71
|
readonly kind: AuthFailureKind;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export interface TransportMatcher {
|
|
75
|
+
readonly pattern: string;
|
|
76
|
+
readonly flags?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
/** The turn-option vocabulary is closed for the same reason `LimitCode` is:
|
|
75
80
|
* a descriptor must not invent an option a consumer has no field for. Every
|
|
76
81
|
* key here maps to a field on `TurnOptions`; adding a key without a consumer
|
|
@@ -84,6 +89,12 @@ export const TURN_OPTION_KEYS = deepFreeze([
|
|
|
84
89
|
"write",
|
|
85
90
|
"shell",
|
|
86
91
|
"maxSteps",
|
|
92
|
+
// issue #48 (ratified 2026-08-20): the payload-stripping dimensions.
|
|
93
|
+
// systemPrompt replaces the built-in prompt; appendSystemPrompt adds to
|
|
94
|
+
// it. Both are opt-in-only (arg/config, no profile entry) - a default
|
|
95
|
+
// that strips the payload changes every bare run's semantics.
|
|
96
|
+
"systemPrompt",
|
|
97
|
+
"appendSystemPrompt",
|
|
87
98
|
] as const);
|
|
88
99
|
export type TurnOptionKey = (typeof TURN_OPTION_KEYS)[number];
|
|
89
100
|
|
|
@@ -101,8 +112,14 @@ export const DISCOVERY_FACETS = deepFreeze([
|
|
|
101
112
|
export type DiscoveryFacet = (typeof DISCOVERY_FACETS)[number];
|
|
102
113
|
|
|
103
114
|
export type OptionRender =
|
|
104
|
-
/** `--flag <value
|
|
105
|
-
|
|
115
|
+
/** `--flag <value>`; `extraFlags` are fixed companion tokens emitted
|
|
116
|
+
* before the value pair (claude's --system-prompt pairs with
|
|
117
|
+
* --exclude-dynamic-system-prompt-sections - issue #48). */
|
|
118
|
+
| {
|
|
119
|
+
readonly kind: "flag-value";
|
|
120
|
+
readonly flag: string;
|
|
121
|
+
readonly extraFlags?: readonly string[];
|
|
122
|
+
}
|
|
106
123
|
/** `-c key=value` - codex's config-override grammar. Permitted only for
|
|
107
124
|
* closed-vocabulary specs, so no value can need escaping. */
|
|
108
125
|
| { readonly kind: "config-kv"; readonly flag: string; readonly key: string }
|
|
@@ -128,6 +145,10 @@ export type TurnOptionSpec =
|
|
|
128
145
|
| (SpecBase & { readonly kind: "effort" })
|
|
129
146
|
/** Open selector, CLEAN_SELECTOR-validated. */
|
|
130
147
|
| (SpecBase & { readonly kind: "selector" })
|
|
148
|
+
/** Free-form prompt text (issue #48): systemPrompt / appendSystemPrompt.
|
|
149
|
+
* Values are prose, never a closed vocabulary - no validation beyond
|
|
150
|
+
* non-emptiness, rendering is verbatim. */
|
|
151
|
+
| (SpecBase & { readonly kind: "prompt-text" })
|
|
131
152
|
/** `polarity: "disables"` emits the render when the caller asks for FALSE. */
|
|
132
153
|
| (SpecBase & { readonly kind: "toggle"; readonly polarity: "enables" | "disables" })
|
|
133
154
|
| (SpecBase & { readonly kind: "integer"; readonly min: number; readonly max: number })
|
|
@@ -239,14 +260,16 @@ export interface HarnessDescriptor {
|
|
|
239
260
|
* harness. */
|
|
240
261
|
readonly onMissing: "error" | "create";
|
|
241
262
|
};
|
|
242
|
-
/** Persistent headless session support: the
|
|
243
|
-
* lucid-owned process serving many turns, or null
|
|
244
|
-
* such mode.
|
|
245
|
-
*
|
|
246
|
-
* `
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
263
|
+
/** Persistent headless session support: the complete flag list that follows
|
|
264
|
+
* the binary to open one lucid-owned process serving many turns, or null
|
|
265
|
+
* when the harness has no such mode. This is the entire argv prefix after
|
|
266
|
+
* the binary - no launch flags are prepended. `idFlag` pins the
|
|
267
|
+
* caller-assigned session identity. `turnEnd` is the stdout record that
|
|
268
|
+
* delimits one turn (claude: the `result` record; pi rpc:
|
|
269
|
+
* `agent_settled`). `identityProbe`, when present, names a command the
|
|
270
|
+
* runner writes at spawn whose response carries the session id - pi rpc
|
|
271
|
+
* is identity-silent at startup (spike evidence:
|
|
272
|
+
* test/fixtures/pi-rpc-spike), so identity needs a round trip. */
|
|
250
273
|
readonly sessionMode: {
|
|
251
274
|
readonly flags: readonly string[];
|
|
252
275
|
/** Pin an EXISTING session id; null when the harness only accepts
|
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" },
|
|
@@ -20,7 +20,14 @@ export const SHARED_LIMIT_MATCHERS: ReadonlyArray<LimitMatcher> = [
|
|
|
20
20
|
code: "quota",
|
|
21
21
|
},
|
|
22
22
|
// Rate-limit patterns are last so a line with both a usage wall and a 429 keeps usage-limit (first-match-wins, documented cost)
|
|
23
|
-
|
|
23
|
+
// 429 only next to an HTTP status word: a bare digit run inside an id, a
|
|
24
|
+
// byte count, or a timing must never read as a wall. "429 Too Many
|
|
25
|
+
// Requests" is covered by the next matcher.
|
|
26
|
+
{
|
|
27
|
+
pattern: "\\b(?:HTTP|status(?:[_ ]?code)?|code)\\b\\W*[:=]?\\W*429\\b",
|
|
28
|
+
flags: "i",
|
|
29
|
+
code: "rate-limit",
|
|
30
|
+
},
|
|
24
31
|
{ pattern: "Too Many Requests", flags: "i", code: "rate-limit" },
|
|
25
32
|
{ pattern: "rate limit(?:ed|ing)?", flags: "i", code: "rate-limit" },
|
|
26
33
|
{ pattern: "Retry-After", flags: "i", code: "rate-limit" },
|
|
@@ -30,3 +37,19 @@ export const SHARED_AUTH_MATCHERS: ReadonlyArray<AuthMatcher> = [
|
|
|
30
37
|
{ pattern: "401 unauthorized", flags: "i", kind: "expired" },
|
|
31
38
|
{ pattern: "invalid api key", flags: "i", kind: "invalid-key" },
|
|
32
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/pi.ts
CHANGED
|
@@ -30,6 +30,15 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
30
30
|
// Caller-assigned: the same --session-id re-enters the session. Resume
|
|
31
31
|
// carries the same structured-output flags launch does, or a resumed
|
|
32
32
|
// turn would stream plain text the runner cannot decode.
|
|
33
|
+
// Verified 2026-08-21 on pi 0.84.2: a session id minted by `pi --mode rpc`
|
|
34
|
+
// via the get_state probe (01a022e3-9afb-7ce5-88f5-07ad0e9ac8fa) resumed
|
|
35
|
+
// after close through the one-shot grammar `pi --session-id <id> -p
|
|
36
|
+
// --mode json` (flag --session-id, extraFlags -p --mode json), exit 0,
|
|
37
|
+
// 207 lines, and the reply recalled the codeword "otter" (evidence at
|
|
38
|
+
// test/fixtures/pi-rpc-spike/06-resume-after-close/: rpc-turn.ndjson,
|
|
39
|
+
// resume.argv.json, resume.ndjson, README.md). If the resume had not
|
|
40
|
+
// recalled the codeword, this comment would say so and the descriptor
|
|
41
|
+
// would not be changed here.
|
|
33
42
|
style: "flag",
|
|
34
43
|
flag: "--session-id",
|
|
35
44
|
aliases: [],
|
|
@@ -69,7 +78,10 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
69
78
|
announce: { match: { type: "session" }, idField: "id" },
|
|
70
79
|
},
|
|
71
80
|
limitMatchers: [...SHARED_LIMIT_MATCHERS],
|
|
72
|
-
authMatchers: [
|
|
81
|
+
authMatchers: [
|
|
82
|
+
{ pattern: "No API key found for", flags: "i", kind: "not-logged-in" },
|
|
83
|
+
...SHARED_AUTH_MATCHERS,
|
|
84
|
+
],
|
|
73
85
|
autonomy: null,
|
|
74
86
|
vocabulary: {
|
|
75
87
|
modelFlag: "--model",
|
|
@@ -110,6 +122,17 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
110
122
|
turnOptions: {
|
|
111
123
|
effort: { kind: "effort", render: { kind: "flag-value", flag: "--thinking" } },
|
|
112
124
|
provider: { kind: "selector", render: { kind: "flag-value", flag: "--provider" } },
|
|
125
|
+
// issue #48, live-verified 0.84.2: pi's --system-prompt replaces the
|
|
126
|
+
// default coding-assistant prompt (PI-NAKED probe). No dynamic-section
|
|
127
|
+
// exclusion exists - pi injects no such sections into a replaced prompt.
|
|
128
|
+
systemPrompt: {
|
|
129
|
+
kind: "prompt-text",
|
|
130
|
+
render: { kind: "flag-value", flag: "--system-prompt" },
|
|
131
|
+
},
|
|
132
|
+
appendSystemPrompt: {
|
|
133
|
+
kind: "prompt-text",
|
|
134
|
+
render: { kind: "flag-value", flag: "--append-system-prompt" },
|
|
135
|
+
},
|
|
113
136
|
discovery: {
|
|
114
137
|
kind: "discovery",
|
|
115
138
|
facets: {
|