@dungle-scrubs/harness-cli-normalizer 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +29 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +12 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/exit-codes.js +1 -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 +35 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/render.d.ts.map +1 -1
- package/dist/cli/render.js +15 -1
- package/dist/cli/render.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +90 -16
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/skills-root.d.ts +4 -0
- package/dist/cli/skills-root.d.ts.map +1 -0
- package/dist/cli/skills-root.js +45 -0
- package/dist/cli/skills-root.js.map +1 -0
- package/dist/execution/events.d.ts +12 -1
- package/dist/execution/events.d.ts.map +1 -1
- package/dist/execution/events.js.map +1 -1
- package/dist/execution/failure.d.ts +4 -1
- package/dist/execution/failure.d.ts.map +1 -1
- package/dist/execution/failure.js +15 -2
- package/dist/execution/failure.js.map +1 -1
- package/dist/execution/stream-turn.d.ts +7 -0
- package/dist/execution/stream-turn.d.ts.map +1 -1
- package/dist/execution/stream-turn.js +83 -16
- package/dist/execution/stream-turn.js.map +1 -1
- package/dist/interpretation/argv.d.ts +10 -0
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +4 -0
- package/dist/interpretation/argv.js.map +1 -1
- package/dist/interpretation/question.d.ts +46 -0
- package/dist/interpretation/question.d.ts.map +1 -0
- package/dist/interpretation/question.js +110 -0
- package/dist/interpretation/question.js.map +1 -0
- package/dist/interpretation/refusal.d.ts +1 -1
- package/dist/interpretation/refusal.d.ts.map +1 -1
- package/dist/interpretation/refusal.js.map +1 -1
- package/dist/interpretation/resolve-options.d.ts.map +1 -1
- package/dist/interpretation/resolve-options.js +27 -5
- package/dist/interpretation/resolve-options.js.map +1 -1
- package/dist/interpretation/skills-selection.d.ts +22 -0
- package/dist/interpretation/skills-selection.d.ts.map +1 -0
- package/dist/interpretation/skills-selection.js +49 -0
- package/dist/interpretation/skills-selection.js.map +1 -0
- package/dist/knowledge/claude-code.d.ts.map +1 -1
- package/dist/knowledge/claude-code.js +1 -0
- package/dist/knowledge/claude-code.js.map +1 -1
- package/dist/knowledge/codex.d.ts.map +1 -1
- package/dist/knowledge/codex.js +1 -0
- package/dist/knowledge/codex.js.map +1 -1
- package/dist/knowledge/descriptor.d.ts +7 -0
- package/dist/knowledge/descriptor.d.ts.map +1 -1
- package/dist/knowledge/muse.d.ts.map +1 -1
- package/dist/knowledge/muse.js +1 -0
- package/dist/knowledge/muse.js.map +1 -1
- package/dist/knowledge/pi.d.ts.map +1 -1
- package/dist/knowledge/pi.js +1 -0
- package/dist/knowledge/pi.js.map +1 -1
- package/dist/knowledge/profile.d.ts +3 -0
- package/dist/knowledge/profile.d.ts.map +1 -1
- package/dist/knowledge/profile.js +20 -0
- package/dist/knowledge/profile.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/args.ts +39 -2
- package/src/cli/config.ts +14 -1
- package/src/cli/exit-codes.ts +1 -1
- package/src/cli/help.ts +35 -2
- package/src/cli/render.ts +16 -1
- package/src/cli/run.ts +99 -23
- package/src/cli/skills-root.ts +47 -0
- package/src/execution/events.ts +19 -1
- package/src/execution/failure.ts +16 -2
- package/src/execution/stream-turn.ts +91 -15
- package/src/interpretation/argv.ts +14 -0
- package/src/interpretation/question.ts +137 -0
- package/src/interpretation/refusal.ts +1 -0
- package/src/interpretation/resolve-options.ts +29 -5
- package/src/interpretation/skills-selection.ts +73 -0
- package/src/knowledge/claude-code.ts +1 -0
- package/src/knowledge/codex.ts +1 -0
- package/src/knowledge/descriptor.ts +7 -0
- package/src/knowledge/muse.ts +1 -0
- package/src/knowledge/pi.ts +1 -0
- package/src/knowledge/profile.ts +20 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question escalation (issue #41): the protocol that lets a headless
|
|
3
|
+
* worker ask the CALLER's user a question. Transport is the prompt - no
|
|
4
|
+
* harness ships native question conveyance - so hcn prepends a protocol
|
|
5
|
+
* contract (escalateQuestions true, the default) or the state-the-
|
|
6
|
+
* assumption instruction (false). The worker's final message then carries
|
|
7
|
+
* a fenced `hcn-question` block; detection is structured-first (fields
|
|
8
|
+
* parsed from the block, prose rendered downstream from them).
|
|
9
|
+
*
|
|
10
|
+
* Ratified design note (2026-08-19): autonomy and escalateQuestions are
|
|
11
|
+
* independent flags carving the same substrate by ORIGIN - autonomy =
|
|
12
|
+
* interrupts the harness raises (permission gates), escalateQuestions =
|
|
13
|
+
* interrupts the model raises (judgment gaps). The true-mode preamble
|
|
14
|
+
* must therefore never conflate asking with permission: "you may ask" is
|
|
15
|
+
* never "you lack permission." Both preambles open with the same marker
|
|
16
|
+
* so composition is idempotent (a composed prompt is never re-composed).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The shared first line of both preambles - also the idempotence marker
|
|
20
|
+
* for composeEscalatedPrompt. */
|
|
21
|
+
export const QUESTION_PREAMBLE_MARKER = "[hcn question protocol]";
|
|
22
|
+
|
|
23
|
+
export const ESCALATION_PREAMBLE = `${QUESTION_PREAMBLE_MARKER}
|
|
24
|
+
You are running headless: no one is watching this session live, but a caller relays answers between turns. This protocol never changes your permissions - asking is not how you obtain permission, and it never removes any permission this run already has. Use the tools you have exactly as granted.
|
|
25
|
+
If and only if a genuine decision you cannot make defensibly blocks correct progress, ask by ending your turn: emit one fenced code block tagged hcn-question, as the last content of your final message, containing a single JSON object:
|
|
26
|
+
|
|
27
|
+
\`\`\`hcn-question
|
|
28
|
+
{"question": "<the decision you need made>", "options": ["<option 1>", "<option 2>"], "recommended": "<one of options>"}
|
|
29
|
+
\`\`\`
|
|
30
|
+
|
|
31
|
+
Say nothing after the block. Your turn ends there; the caller's user will answer, and your next turn continues from that answer. For every choice you can make defensibly yourself, do not ask - decide, act, and state the decision you made.`;
|
|
32
|
+
|
|
33
|
+
export const NO_ESCALATION_PREAMBLE = `${QUESTION_PREAMBLE_MARKER}
|
|
34
|
+
You are running headless and no one will answer you in this session. Never ask a question, never request input or confirmation, and never end your turn awaiting a reply. When a decision is ambiguous, pick the most defensible reading, state the assumption you proceeded under in one sentence, and continue to completion.`;
|
|
35
|
+
|
|
36
|
+
/** Compose the transport preamble onto a prompt. Idempotent: a prompt
|
|
37
|
+
* that already carries either preamble (re-composition on resume, a
|
|
38
|
+
* caller that pre-composed) passes through unchanged. */
|
|
39
|
+
export const composeEscalatedPrompt = (prompt: string, escalate: boolean): string =>
|
|
40
|
+
prompt.startsWith(QUESTION_PREAMBLE_MARKER)
|
|
41
|
+
? prompt
|
|
42
|
+
: `${escalate ? ESCALATION_PREAMBLE : NO_ESCALATION_PREAMBLE}\n\n${prompt}`;
|
|
43
|
+
|
|
44
|
+
/** The structured question a worker asks (the block's fields). */
|
|
45
|
+
export interface QuestionBlock {
|
|
46
|
+
readonly question: string;
|
|
47
|
+
readonly options: readonly string[];
|
|
48
|
+
readonly recommended?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Detection result: a parsed block, a named malformation (the worker
|
|
52
|
+
* tried to ask but botched the shape - surfaced, never swallowed), or
|
|
53
|
+
* null when no hcn-question block is present. */
|
|
54
|
+
export type QuestionDetection = { readonly block: QuestionBlock } | { readonly malformed: string };
|
|
55
|
+
|
|
56
|
+
/** One fenced-block candidate: the body text plus whether the fence was
|
|
57
|
+
* properly closed (an unclosed opener at end-of-text is still examined -
|
|
58
|
+
* the most likely formatting slip is forgetting the closing fence). */
|
|
59
|
+
interface FenceCandidate {
|
|
60
|
+
readonly body: string;
|
|
61
|
+
readonly closed: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const FENCE_OPEN = /(?:^|\n)[ \t]*```[ \t]*hcn-question[ \t]*(?=\n)/g;
|
|
65
|
+
|
|
66
|
+
/** All hcn-question fence bodies in a text, in order. */
|
|
67
|
+
const fenceBodies = (text: string): FenceCandidate[] => {
|
|
68
|
+
const out: FenceCandidate[] = [];
|
|
69
|
+
FENCE_OPEN.lastIndex = 0;
|
|
70
|
+
for (let m = FENCE_OPEN.exec(text); m !== null; m = FENCE_OPEN.exec(text)) {
|
|
71
|
+
const bodyStart = m.index + m[0].length;
|
|
72
|
+
// The closing fence may be indented like the opener (probe evidence:
|
|
73
|
+
// indented blocks occur), so match newline + optional spaces + ```.
|
|
74
|
+
const close = text.slice(bodyStart).search(/\n[ \t]*```/);
|
|
75
|
+
if (close === -1) {
|
|
76
|
+
// Unclosed fence: the body runs to end-of-text. Only meaningful
|
|
77
|
+
// when nothing follows the opener - take it as a candidate anyway.
|
|
78
|
+
out.push({ body: text.slice(bodyStart), closed: false });
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
const bodyEnd = bodyStart + close;
|
|
82
|
+
out.push({ body: text.slice(bodyStart, bodyEnd), closed: true });
|
|
83
|
+
FENCE_OPEN.lastIndex = bodyEnd;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const parseBlock = (body: string): QuestionDetection => {
|
|
89
|
+
let raw: unknown;
|
|
90
|
+
try {
|
|
91
|
+
raw = JSON.parse(body);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return { malformed: `hcn-question block is not valid JSON: ${(e as Error).message}` };
|
|
94
|
+
}
|
|
95
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
96
|
+
return { malformed: "hcn-question block must be a JSON object" };
|
|
97
|
+
}
|
|
98
|
+
const obj = raw as Record<string, unknown>;
|
|
99
|
+
const { question, options, recommended } = obj;
|
|
100
|
+
if (typeof question !== "string" || question.trim() === "") {
|
|
101
|
+
return { malformed: 'hcn-question block field "question" must be a non-empty string' };
|
|
102
|
+
}
|
|
103
|
+
if (
|
|
104
|
+
!Array.isArray(options) ||
|
|
105
|
+
options.length === 0 ||
|
|
106
|
+
options.some((o) => typeof o !== "string" || o.trim() === "")
|
|
107
|
+
) {
|
|
108
|
+
return {
|
|
109
|
+
malformed: 'hcn-question block field "options" must be an array of non-empty strings',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (recommended !== undefined && typeof recommended !== "string") {
|
|
113
|
+
return { malformed: 'hcn-question block field "recommended" must be a string' };
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
block: {
|
|
117
|
+
question,
|
|
118
|
+
options,
|
|
119
|
+
...(recommended !== undefined ? { recommended } : {}),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** Detect the hcn-question block in a message text. The LAST block wins
|
|
125
|
+
* (the protocol makes the block the turn's final content; a corrected
|
|
126
|
+
* re-emit supersedes an earlier one). An empty candidate body (a bare
|
|
127
|
+
* unclosed opener with nothing after it) is not a detection. */
|
|
128
|
+
export const detectQuestionBlock = (text: string): QuestionDetection | null => {
|
|
129
|
+
const candidates = fenceBodies(text);
|
|
130
|
+
for (let i = candidates.length - 1; i >= 0; i--) {
|
|
131
|
+
const candidate = candidates[i];
|
|
132
|
+
if (candidate === undefined) continue;
|
|
133
|
+
if (candidate.body.trim() === "") continue;
|
|
134
|
+
return parseBlock(candidate.body);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
};
|
|
@@ -54,6 +54,9 @@ const EXPRESSIBLE: Readonly<Record<ProfileKey, (h: HarnessDescriptor) => boolean
|
|
|
54
54
|
sandbox: (h) => h.turnOptions.sandbox !== undefined,
|
|
55
55
|
discovery: () => true,
|
|
56
56
|
autonomy: () => true,
|
|
57
|
+
write: () => true,
|
|
58
|
+
shell: () => true,
|
|
59
|
+
tools: (h) => h.tools.includeFlag !== null || h.tools.excludeFlag !== null,
|
|
57
60
|
};
|
|
58
61
|
|
|
59
62
|
export interface ConfigTiers {
|
|
@@ -155,12 +158,33 @@ export const resolveEffectiveOptions = (
|
|
|
155
158
|
provenance.push({ key, value, tier: "harness" });
|
|
156
159
|
continue;
|
|
157
160
|
}
|
|
161
|
+
// D13: the tools marker expands per descriptor. On a harness whose
|
|
162
|
+
// default is already everything (claude), expansion emits nothing -
|
|
163
|
+
// the emit-nothing rule, recorded in provenance. On a harness with
|
|
164
|
+
// dormant built-ins (pi), it becomes the enabling include list.
|
|
165
|
+
if (key === "tools" && value === "all-known") {
|
|
166
|
+
const enabled = h.tools.builtins.filter((t) => t.defaultEnabled).length;
|
|
167
|
+
const all = h.tools.builtins.length;
|
|
168
|
+
if (enabled === all) {
|
|
169
|
+
provenance.push({ key, value: "all known (already default)", tier: "profile" });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const expanded = h.tools.builtins.map((t) => t.name);
|
|
173
|
+
resolved[key] = expanded;
|
|
174
|
+
provenance.push({ key, value: expanded, tier: "profile" });
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
158
177
|
// Dimensions whose value reduces to "emit nothing" (autonomy false,
|
|
159
|
-
// discovery all-on) stay ABSENT from the resolved
|
|
160
|
-
// harness's default already satisfies the profile, and
|
|
161
|
-
// explicit on-flags would change resume grammar and add
|
|
162
|
-
// surface for no semantic gain. Provenance still records
|
|
163
|
-
|
|
178
|
+
// discovery all-on, write/shell true) stay ABSENT from the resolved
|
|
179
|
+
// options - the harness's default already satisfies the profile, and
|
|
180
|
+
// emitting explicit on-flags would change resume grammar and add
|
|
181
|
+
// breakage surface for no semantic gain. Provenance still records
|
|
182
|
+
// the tier.
|
|
183
|
+
if (
|
|
184
|
+
(key === "autonomy" && value === false) ||
|
|
185
|
+
(key === "discovery" && emitsNothing(value)) ||
|
|
186
|
+
((key === "write" || key === "shell") && value === true)
|
|
187
|
+
) {
|
|
164
188
|
provenance.push({ key, value, tier: "profile" });
|
|
165
189
|
continue;
|
|
166
190
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caller-directed skills allowlist rendering (issue #38). The delegating
|
|
3
|
+
* agent picks the subset from its own registry; this module turns the
|
|
4
|
+
* resolved paths into per-harness argv tokens.
|
|
5
|
+
*
|
|
6
|
+
* - pi: `-ns` (discovery off) + one `--skill <path>` per entry - the
|
|
7
|
+
* allowlist is exact: only the caller's picks load.
|
|
8
|
+
* - claude: no per-skill load flag; the registry is already present via
|
|
9
|
+
* the personal skills dir, so the allowlist renders as the complement
|
|
10
|
+
* OFF - `--settings '{"skillOverrides":{"<name>":"off",...}}'` for every
|
|
11
|
+
* known skill except the picks. Known set comes from the caller's root
|
|
12
|
+
* listing (same source that resolved the names).
|
|
13
|
+
* - codex/muse: refuse (structural) with the standard hint shape.
|
|
14
|
+
*/
|
|
15
|
+
import type { HarnessDescriptor } from "../knowledge/descriptor.js";
|
|
16
|
+
import { ArgvRefusalError } from "./refusal.js";
|
|
17
|
+
|
|
18
|
+
export const basenameOf = (p: string): string => {
|
|
19
|
+
const i = p.lastIndexOf("/");
|
|
20
|
+
return i === -1 ? p : p.slice(i + 1);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const renderSkillsSelection = (
|
|
24
|
+
h: HarnessDescriptor,
|
|
25
|
+
skills: readonly string[],
|
|
26
|
+
): readonly string[] => {
|
|
27
|
+
if (skills.length === 0) return [];
|
|
28
|
+
|
|
29
|
+
if (h.skills === null) {
|
|
30
|
+
throw new ArgvRefusalError({
|
|
31
|
+
issue: "unsupported-option",
|
|
32
|
+
harness: h.name,
|
|
33
|
+
option: "skills",
|
|
34
|
+
supported: ["caller-directed skill sets"],
|
|
35
|
+
supportedBy: [
|
|
36
|
+
{ harness: "pi", spelling: "--skill" },
|
|
37
|
+
{ harness: "claude", spelling: "skillOverrides" },
|
|
38
|
+
],
|
|
39
|
+
hint:
|
|
40
|
+
h.name === "codex"
|
|
41
|
+
? "codex discovers skills from its own directory with no call-time surface - stage the skills into $CODEX_HOME/skills or pass their content in the prompt"
|
|
42
|
+
: "muse scopes skills by workspace trust with no per-skill surface - include the skill content in the prompt or use --trust-workspace for the whole registry",
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (h.skills.loadFlag !== null) {
|
|
47
|
+
// pi: discovery off so ONLY the picks load.
|
|
48
|
+
const tokens: string[] = ["-ns"];
|
|
49
|
+
for (const path of skills) tokens.push(h.skills.loadFlag, path);
|
|
50
|
+
return tokens;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// claude: complement-off via settings JSON. Names are the skill dir
|
|
54
|
+
// basenames; unknown names in knownSkills would be turned off
|
|
55
|
+
// pointlessly, so the caller passes exactly the known set.
|
|
56
|
+
return [];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** The claude complement form, given the full known registry: every known
|
|
60
|
+
* name except the picks gets "off". Exported for the CLI layer, which owns
|
|
61
|
+
* the registry listing (an fs read - never in interpretation). */
|
|
62
|
+
export const claudeSkillOverridesArg = (
|
|
63
|
+
knownSkills: readonly string[],
|
|
64
|
+
pickedPaths: readonly string[],
|
|
65
|
+
): string[] => {
|
|
66
|
+
const picks = new Set(pickedPaths.map(basenameOf));
|
|
67
|
+
const offs: Record<string, string> = {};
|
|
68
|
+
for (const name of knownSkills) {
|
|
69
|
+
if (!picks.has(name)) offs[name] = "off";
|
|
70
|
+
}
|
|
71
|
+
const json = JSON.stringify({ skillOverrides: offs });
|
|
72
|
+
return ["--settings", json];
|
|
73
|
+
};
|
|
@@ -161,6 +161,7 @@ export const claudeCode: HarnessDescriptor = deepFreeze({
|
|
|
161
161
|
// compose, deny winning on overlap. Patterns (Bash(git *)) valid in both
|
|
162
162
|
// lists; unknown PATTERN spellings warn on stderr, unknown exact names
|
|
163
163
|
// are the silent-acceptance hazard the curated vocabulary guards.
|
|
164
|
+
skills: { loadFlag: null, overridesVia: "settings-skilloverrides" },
|
|
164
165
|
tools: {
|
|
165
166
|
includeFlag: "--allowedTools",
|
|
166
167
|
excludeFlag: "--disallowedTools",
|
package/src/knowledge/codex.ts
CHANGED
|
@@ -119,6 +119,7 @@ export const codexCli: HarnessDescriptor = deepFreeze({
|
|
|
119
119
|
// (reachable per-call via -c key=value / --enable/--disable FEATURE),
|
|
120
120
|
// sandbox, and approval policy. MCP servers do have per-tool keys
|
|
121
121
|
// (mcp_servers.<id>.tools.<tool>) but built-ins do not.
|
|
122
|
+
skills: null,
|
|
122
123
|
tools: {
|
|
123
124
|
includeFlag: null,
|
|
124
125
|
excludeFlag: null,
|
|
@@ -356,6 +356,13 @@ export interface HarnessDescriptor {
|
|
|
356
356
|
* tools register at runtime and are NEVER enumerated here - name
|
|
357
357
|
* validation is a default, not a refusal authority (same stance as the
|
|
358
358
|
* pi model registry, D-008). */
|
|
359
|
+
/** Caller-directed skills allowlist surface (issue #38). pi: repeatable
|
|
360
|
+
* load flag. claude: per-name "off" overrides via settings JSON.
|
|
361
|
+
* codex/muse: null (structural gap - trust/config scoped only). */
|
|
362
|
+
readonly skills: {
|
|
363
|
+
readonly loadFlag: string | null;
|
|
364
|
+
readonly overridesVia: "settings-skilloverrides" | null;
|
|
365
|
+
} | null;
|
|
359
366
|
readonly tools: {
|
|
360
367
|
readonly includeFlag: string | null;
|
|
361
368
|
readonly excludeFlag: string | null;
|
package/src/knowledge/muse.ts
CHANGED
|
@@ -116,6 +116,7 @@ export const museCode: HarnessDescriptor = deepFreeze({
|
|
|
116
116
|
// Tool names follow muse.<name> (write_file, edit_file, bash, bash_input,
|
|
117
117
|
// add_memory, edit_memory, web_search). --disable-web-tools has no
|
|
118
118
|
// normalized turnOption yet - candidate for a web toggle or passthrough.
|
|
119
|
+
skills: null,
|
|
119
120
|
tools: {
|
|
120
121
|
includeFlag: null,
|
|
121
122
|
excludeFlag: null,
|
package/src/knowledge/pi.ts
CHANGED
|
@@ -122,6 +122,7 @@ export const piCli: HarnessDescriptor = deepFreeze({
|
|
|
122
122
|
// exclude subtracts from include. --tools is strict over BUILT-INS but
|
|
123
123
|
// does not strip MCP/extension registrations (additive over them);
|
|
124
124
|
// -nbt (built-ins only off) exists but has no normalized spelling yet.
|
|
125
|
+
skills: { loadFlag: "--skill", overridesVia: null },
|
|
125
126
|
tools: {
|
|
126
127
|
includeFlag: "--tools",
|
|
127
128
|
excludeFlag: "--exclude-tools",
|
package/src/knowledge/profile.ts
CHANGED
|
@@ -28,6 +28,26 @@ export const DEFAULT_TURN_PROFILE = deepFreeze({
|
|
|
28
28
|
// Ratified: autonomy OFF. No bare run is unattended; --autonomy or a
|
|
29
29
|
// config must say so deliberately.
|
|
30
30
|
autonomy: false,
|
|
31
|
+
// D9/D10, ratified round 2: write and shell ON - names current
|
|
32
|
+
// behavior (emit-nothing on claude/codex/pi; muse omits its disable
|
|
33
|
+
// flags). Completes the "what can a bare run do to the machine" row of
|
|
34
|
+
// provenance.
|
|
35
|
+
write: true,
|
|
36
|
+
shell: true,
|
|
37
|
+
// D13, ratified: equivalent-as-possible tool defaults. The marker
|
|
38
|
+
// expands per descriptor at resolve time - pi gains its dormant
|
|
39
|
+
// grep/find/ls; claude emits nothing (already everything); codex/muse
|
|
40
|
+
// report divergence (no list surface). Project floors narrow it by
|
|
41
|
+
// the normal precedence chain.
|
|
42
|
+
tools: "all-known",
|
|
43
|
+
// Model is PERMANENTLY out of profile scope (ratified round 2): no
|
|
44
|
+
// cross-harness model namespace exists, pi's registry is
|
|
45
|
+
// runtime-extensible and environment-dependent, and per-harness config
|
|
46
|
+
// already covers "claude on X, pi on Y". Do not add a model entry.
|
|
47
|
+
// timeout and maxSteps are likewise opt-in-only dimensions (D11/D12):
|
|
48
|
+
// no harness ships a wall-clock cap, one prompt expands into an
|
|
49
|
+
// unbounded turn loop, and a fixed default kills legitimate work. They
|
|
50
|
+
// live in args/config only, never here.
|
|
31
51
|
} as const);
|
|
32
52
|
|
|
33
53
|
export type ProfileKey = keyof typeof DEFAULT_TURN_PROFILE;
|