@dungle-scrubs/harness-cli-normalizer 0.5.7 → 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 +25 -8
- 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 +3 -3
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +9 -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 +27 -45
- 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 +6 -1
- package/dist/execution/open-session.d.ts.map +1 -1
- package/dist/execution/open-session.js +151 -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 +44 -12
- package/dist/interpretation/argv.d.ts.map +1 -1
- package/dist/interpretation/argv.js +38 -24
- 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 +9 -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 +36 -45
- 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 +160 -228
- package/src/execution/stream-turn.ts +90 -193
- package/src/execution/supervisor.ts +199 -0
- package/src/interpretation/argv.ts +86 -38
- 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
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* planTurn: the one owner of the parse-refuse-resolve-build protocol
|
|
3
|
+
* (RFC-02 change 10). Raw arguments and config tiers in; the argv a turn
|
|
4
|
+
* spawns, the runner options, the provenance, and the resolved behaviour
|
|
5
|
+
* out - or one structured refusal. The run command spawns from the plan
|
|
6
|
+
* and the inspect command previews from it, so the spawn line and the
|
|
7
|
+
* preview agree by construction, skill tokens and passthrough included.
|
|
8
|
+
*/
|
|
9
|
+
import { redactArgv, type TurnRunOptions } from "../execution/stream-turn.js";
|
|
10
|
+
import { buildSpawnArgv, promptTextOf } from "../interpretation/argv.js";
|
|
11
|
+
import { composeEscalatedPrompt } from "../interpretation/question.js";
|
|
12
|
+
import { ArgvRefusalError } from "../interpretation/refusal.js";
|
|
13
|
+
import {
|
|
14
|
+
type ConfigTier,
|
|
15
|
+
type ConfigTiers,
|
|
16
|
+
FloorExceededError,
|
|
17
|
+
type ProvenanceEntry,
|
|
18
|
+
type ResolvedBehavior,
|
|
19
|
+
resolveBehavior,
|
|
20
|
+
resolveEffectiveOptions,
|
|
21
|
+
} from "../interpretation/resolve-options.js";
|
|
22
|
+
import { recognizeNativeSpelling, supportedBy } from "../interpretation/support.js";
|
|
23
|
+
import { renderToolSelection } from "../interpretation/tool-selection.js";
|
|
24
|
+
import type { HarnessDescriptor, HarnessName } from "../knowledge/descriptor.js";
|
|
25
|
+
import { defaultDescriptors } from "../knowledge/overrides.js";
|
|
26
|
+
import {
|
|
27
|
+
detectPositionalPromptInjection,
|
|
28
|
+
parseCommonFlags,
|
|
29
|
+
parseRunExtra,
|
|
30
|
+
parseTurnOptions,
|
|
31
|
+
resolvePromptAsync,
|
|
32
|
+
splitPassthrough,
|
|
33
|
+
} from "./args.js";
|
|
34
|
+
import { ConfigError, loadProjectConfig, loadUserConfig } from "./config.js";
|
|
35
|
+
import { writeProvenance } from "./provenance.js";
|
|
36
|
+
import { type Refusal, refusalOf } from "./refuse.js";
|
|
37
|
+
import { listKnownSkills, resolveSkillNames } from "./skills-root.js";
|
|
38
|
+
|
|
39
|
+
/** The impure edges a plan reads through, injectable for tests. */
|
|
40
|
+
export interface PlanDeps {
|
|
41
|
+
readonly loadUserConfig: () => { readonly config: ConfigTier } | null;
|
|
42
|
+
readonly loadProjectConfig: () => { readonly config: ConfigTier } | null;
|
|
43
|
+
readonly listKnownSkills: () => readonly string[];
|
|
44
|
+
readonly resolveSkillNames: (names: readonly string[], harness: HarnessName) => string[];
|
|
45
|
+
readonly readPrompt: (args: {
|
|
46
|
+
positionalPrompt?: string;
|
|
47
|
+
promptFlag?: string;
|
|
48
|
+
promptFile?: string;
|
|
49
|
+
}) => Promise<{ prompt: string; source: string }>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const defaultPlanDeps: PlanDeps = {
|
|
53
|
+
loadUserConfig,
|
|
54
|
+
loadProjectConfig,
|
|
55
|
+
listKnownSkills,
|
|
56
|
+
resolveSkillNames,
|
|
57
|
+
readPrompt: resolvePromptAsync,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export interface TurnPlan {
|
|
61
|
+
/** Decided before any refusal can fire: a refused --json run still owes
|
|
62
|
+
* the stream its terminal pair. */
|
|
63
|
+
readonly wantJson: boolean;
|
|
64
|
+
/** Everything the runner takes: the composed prompt, the resolved turn
|
|
65
|
+
* options (raw ones on resume), cwd, env, resume, question mode, and
|
|
66
|
+
* the passthrough tail. */
|
|
67
|
+
readonly options: TurnRunOptions;
|
|
68
|
+
/** The argv the runner will spawn, built by the same owner it uses. */
|
|
69
|
+
readonly argv: readonly string[];
|
|
70
|
+
readonly redactedArgv: readonly string[];
|
|
71
|
+
/** Launch-only; empty on resume, where a session keeps its settings. */
|
|
72
|
+
readonly provenance: readonly ProvenanceEntry[];
|
|
73
|
+
readonly unrenderable: readonly string[];
|
|
74
|
+
readonly behavior: ResolvedBehavior;
|
|
75
|
+
/** Tool names the grant passes through ungated, for the provenance line. */
|
|
76
|
+
readonly nativeTools: readonly string[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type PlanOutcome =
|
|
80
|
+
| { readonly kind: "plan"; readonly plan: TurnPlan }
|
|
81
|
+
| { readonly kind: "refusal"; readonly refusal: Refusal; readonly wantJson: boolean };
|
|
82
|
+
|
|
83
|
+
export interface PlanRequest {
|
|
84
|
+
/** Names the command in usage trailers. */
|
|
85
|
+
readonly command: "run" | "inspect";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The refusal for an unknown flag: a native spelling typed before the
|
|
89
|
+
* separator is recognized and redirected to the normalized flag (ADR 0003,
|
|
90
|
+
* D7 part B); anything else keeps the plain unknown-flag error. */
|
|
91
|
+
const unknownFlagRefusal = (
|
|
92
|
+
message: string,
|
|
93
|
+
rawArgs: readonly string[],
|
|
94
|
+
command: PlanRequest["command"],
|
|
95
|
+
): Refusal => {
|
|
96
|
+
// parseArgs reports unknown long flags as "Unknown option '--x'" but
|
|
97
|
+
// splits bundled short flags ("-nt" -> "Unknown option 'n'"). Match the
|
|
98
|
+
// reported token back against the ORIGINAL argv: a short-flag bundle
|
|
99
|
+
// that some descriptor spells exactly (pi's -nt) is recognizable; a
|
|
100
|
+
// lone unknown token keeps the plain error.
|
|
101
|
+
const flagMatch = message.match(/Unknown option '([A-Za-z0-9_-]+)'/);
|
|
102
|
+
let rawFlag: string | undefined;
|
|
103
|
+
if (flagMatch?.[1] !== undefined) {
|
|
104
|
+
const reported = flagMatch[1].startsWith("-") ? flagMatch[1] : `-${flagMatch[1]}`;
|
|
105
|
+
const fromArgv =
|
|
106
|
+
rawArgs.find((a) => a === reported) ??
|
|
107
|
+
(reported.length === 2
|
|
108
|
+
? rawArgs.find((a) => a.length > 2 && a.startsWith(reported))
|
|
109
|
+
: undefined);
|
|
110
|
+
rawFlag = fromArgv ?? reported;
|
|
111
|
+
}
|
|
112
|
+
const trailer = [`Run 'hcn ${command} --help' for usage.`];
|
|
113
|
+
const native =
|
|
114
|
+
rawFlag !== undefined ? recognizeNativeSpelling(defaultDescriptors(), rawFlag) : null;
|
|
115
|
+
if (native === null) {
|
|
116
|
+
return { message: `unknown flag: ${message}`, issue: "invalid-option-value", trailer };
|
|
117
|
+
}
|
|
118
|
+
const by = native.option.startsWith("discovery.")
|
|
119
|
+
? native.entries
|
|
120
|
+
: supportedBy(defaultDescriptors(), native.option);
|
|
121
|
+
const facet = native.option.split(".")[1];
|
|
122
|
+
const normalizedSpelling =
|
|
123
|
+
native.option === "excludeTools"
|
|
124
|
+
? "--exclude-tools"
|
|
125
|
+
: native.option.startsWith("discovery.")
|
|
126
|
+
? `--no-${facet === "instructionFiles" ? "instruction-files" : facet}`
|
|
127
|
+
: `--${native.option}`;
|
|
128
|
+
return {
|
|
129
|
+
message: `unknown flag: ${rawFlag} is a native spelling (used by ${native.entries.map((e) => e.harness).join(", ")}) - use the normalized ${normalizedSpelling} flag instead`,
|
|
130
|
+
issue: "invalid-option-value",
|
|
131
|
+
supportedBy: by,
|
|
132
|
+
trailer,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const planTurn = async (
|
|
137
|
+
h: HarnessDescriptor,
|
|
138
|
+
rawArgs: readonly string[],
|
|
139
|
+
request: PlanRequest,
|
|
140
|
+
deps: PlanDeps = defaultPlanDeps,
|
|
141
|
+
): Promise<PlanOutcome> => {
|
|
142
|
+
const { normalized, passthrough } = splitPassthrough(rawArgs);
|
|
143
|
+
const wantJson = normalized.includes("--json");
|
|
144
|
+
const refusal = (r: Refusal): PlanOutcome => ({ kind: "refusal", refusal: r, wantJson });
|
|
145
|
+
const refused = (err: ArgvRefusalError): PlanOutcome => refusal(refusalOf(err));
|
|
146
|
+
|
|
147
|
+
const injection = detectPositionalPromptInjection([...rawArgs]);
|
|
148
|
+
if (injection) {
|
|
149
|
+
return refused(
|
|
150
|
+
new ArgvRefusalError({
|
|
151
|
+
issue: "prompt-flag-injection",
|
|
152
|
+
harness: h.name,
|
|
153
|
+
supported: ["prompt must not start with '-'"],
|
|
154
|
+
detail: `it would be parsed as a flag by ${h.bin}`,
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let parsed: ReturnType<typeof parseCommonFlags>;
|
|
160
|
+
try {
|
|
161
|
+
parsed = parseCommonFlags([...rawArgs]);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
164
|
+
return refusal(unknownFlagRefusal(message, rawArgs, request.command));
|
|
165
|
+
}
|
|
166
|
+
const values = parsed.values as Record<string, unknown>;
|
|
167
|
+
const positionals = parsed.positionals as string[];
|
|
168
|
+
if (positionals.length > 1) {
|
|
169
|
+
return refusal({
|
|
170
|
+
message: `too many positionals for ${request.command}; expected one prompt`,
|
|
171
|
+
issue: "invalid-option-value",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (passthrough.length === 0 && rawArgs.includes("--")) {
|
|
175
|
+
return refusal({
|
|
176
|
+
message: "-- separator given but no passthrough tokens followed it",
|
|
177
|
+
issue: "invalid-option-value",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let prompt: string;
|
|
182
|
+
let explicitPrompt: boolean;
|
|
183
|
+
try {
|
|
184
|
+
const resolved = await deps.readPrompt({
|
|
185
|
+
positionalPrompt: positionals[0],
|
|
186
|
+
promptFlag: values.prompt as string | undefined,
|
|
187
|
+
promptFile: values["prompt-file"] as string | undefined,
|
|
188
|
+
});
|
|
189
|
+
prompt = resolved.prompt;
|
|
190
|
+
explicitPrompt = resolved.source !== "positional";
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (err instanceof ArgvRefusalError) return refused(err);
|
|
193
|
+
if (err instanceof Error && (err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
194
|
+
return refusal({
|
|
195
|
+
message: `prompt file not found: ${err.message}`,
|
|
196
|
+
issue: "invalid-option-value",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
throw err;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let turnOpts: ReturnType<typeof parseTurnOptions>;
|
|
203
|
+
let extra: ReturnType<typeof parseRunExtra>;
|
|
204
|
+
try {
|
|
205
|
+
turnOpts = parseTurnOptions(values);
|
|
206
|
+
extra = parseRunExtra(values);
|
|
207
|
+
} catch (err) {
|
|
208
|
+
if (err instanceof ArgvRefusalError) return refused(err);
|
|
209
|
+
throw err;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// issue #38: skill names resolve against the caller's registry root (an
|
|
213
|
+
// fs read, the CLI's); the descriptor renders them (RFC-02 change 2).
|
|
214
|
+
const { skillNames, ...turnOptsSansNames } = turnOpts;
|
|
215
|
+
turnOpts = turnOptsSansNames;
|
|
216
|
+
if (skillNames !== undefined && skillNames.length > 0) {
|
|
217
|
+
try {
|
|
218
|
+
turnOpts = {
|
|
219
|
+
...turnOpts,
|
|
220
|
+
skills: {
|
|
221
|
+
picks: deps.resolveSkillNames(skillNames, h.name),
|
|
222
|
+
known: deps.listKnownSkills(),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
} catch (err) {
|
|
226
|
+
if (err instanceof ArgvRefusalError) return refused(err);
|
|
227
|
+
throw err;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Config files load on EVERY run, launch or resume: the tiers feed the
|
|
232
|
+
// defaults profile on launch, and hcn-owned behaviour (question mode,
|
|
233
|
+
// timeout) resolves from them on resume too - otherwise a no-ask
|
|
234
|
+
// session would flip its preamble on the answer turn.
|
|
235
|
+
const tiers: { user?: ConfigTier; project?: ConfigTier } = {};
|
|
236
|
+
try {
|
|
237
|
+
const user = deps.loadUserConfig();
|
|
238
|
+
if (user !== null) tiers.user = user.config;
|
|
239
|
+
const project = deps.loadProjectConfig();
|
|
240
|
+
if (project !== null) tiers.project = project.config;
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (err instanceof ConfigError) {
|
|
243
|
+
return refusal({ message: `config error: ${err.message}`, issue: "invalid-option-value" });
|
|
244
|
+
}
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Defaults profile + config: LAUNCH-ONLY. A resumed session keeps its own
|
|
249
|
+
// settings; the resolver never runs on resume paths.
|
|
250
|
+
let effectiveTurnOpts = turnOpts;
|
|
251
|
+
let provenance: readonly ProvenanceEntry[] = [];
|
|
252
|
+
let unrenderable: readonly string[] = [];
|
|
253
|
+
if (extra.resume === undefined) {
|
|
254
|
+
try {
|
|
255
|
+
const resolved = resolveEffectiveOptions(h, { ...turnOpts, prompt }, tiers as ConfigTiers);
|
|
256
|
+
const { prompt: _prompt, ...rest } = resolved.options;
|
|
257
|
+
effectiveTurnOpts = rest as typeof turnOpts;
|
|
258
|
+
provenance = resolved.provenance;
|
|
259
|
+
unrenderable = resolved.unrenderable;
|
|
260
|
+
} catch (err) {
|
|
261
|
+
if (err instanceof FloorExceededError) {
|
|
262
|
+
return refusal({ message: err.message, issue: "invalid-tool-grant" });
|
|
263
|
+
}
|
|
264
|
+
if (err instanceof ArgvRefusalError) return refused(err);
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const behavior = resolveBehavior(
|
|
270
|
+
{ questions: turnOpts.questions, timeoutSeconds: extra.timeoutSeconds },
|
|
271
|
+
tiers as ConfigTiers,
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// The prompt here is the COMPOSED one (escalation preamble included):
|
|
275
|
+
// redactArgv masks by position, so an argv built from the raw prompt
|
|
276
|
+
// would leak it into the spawn line. It carries its provenance: an
|
|
277
|
+
// explicit prompt (flag or file) may start with a dash.
|
|
278
|
+
const options: TurnRunOptions = {
|
|
279
|
+
...effectiveTurnOpts,
|
|
280
|
+
prompt: {
|
|
281
|
+
text: composeEscalatedPrompt(prompt, behavior.questions.value),
|
|
282
|
+
explicit: explicitPrompt,
|
|
283
|
+
},
|
|
284
|
+
...(extra.cwd !== undefined ? { cwd: extra.cwd } : {}),
|
|
285
|
+
...(extra.env !== undefined ? { env: extra.env } : {}),
|
|
286
|
+
...(extra.resume !== undefined ? { resume: extra.resume } : {}),
|
|
287
|
+
questions: behavior.questions.value,
|
|
288
|
+
...(passthrough.length > 0 ? { passthrough } : {}),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
let argv: string[];
|
|
292
|
+
try {
|
|
293
|
+
argv = buildSpawnArgv(h, options);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
if (err instanceof ArgvRefusalError) return refused(err);
|
|
296
|
+
throw err;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// On a harness whose include flag is not a strict allowlist (claude), a
|
|
300
|
+
// name outside the curated set passes through ungated; say which ones so
|
|
301
|
+
// a wrong-case name is visible.
|
|
302
|
+
let nativeTools: readonly string[] = [];
|
|
303
|
+
const grant = effectiveTurnOpts.tools;
|
|
304
|
+
if (grant !== undefined && grant.length > 0) {
|
|
305
|
+
try {
|
|
306
|
+
nativeTools = renderToolSelection(h, {
|
|
307
|
+
include: [...grant],
|
|
308
|
+
toolMap: options.toolMap,
|
|
309
|
+
}).passthrough;
|
|
310
|
+
} catch {
|
|
311
|
+
// the builder above already refused
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
kind: "plan",
|
|
317
|
+
plan: {
|
|
318
|
+
wantJson,
|
|
319
|
+
options,
|
|
320
|
+
argv,
|
|
321
|
+
redactedArgv: redactArgv(argv, promptTextOf(options)),
|
|
322
|
+
provenance,
|
|
323
|
+
unrenderable,
|
|
324
|
+
behavior,
|
|
325
|
+
nativeTools,
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/** The diagnostics a plan prints to stderr - never stdout, which carries
|
|
331
|
+
* the NDJSON contract: provenance and divergence, the argv line under the
|
|
332
|
+
* given label, the question-mode provenance, and the native tool names. */
|
|
333
|
+
export const writePlanDiagnostics = (
|
|
334
|
+
h: HarnessDescriptor,
|
|
335
|
+
plan: TurnPlan,
|
|
336
|
+
label: "spawn" | "argv",
|
|
337
|
+
): void => {
|
|
338
|
+
writeProvenance(h.name, plan.provenance, plan.unrenderable);
|
|
339
|
+
process.stderr.write(`${label}: ${plan.redactedArgv.join(" ")}\n`);
|
|
340
|
+
process.stderr.write(
|
|
341
|
+
`provenance: questions = ${plan.behavior.questions.value} (${plan.behavior.questions.tier})\n`,
|
|
342
|
+
);
|
|
343
|
+
if (plan.nativeTools.length > 0) {
|
|
344
|
+
process.stderr.write(`provenance: native tools = ${JSON.stringify(plan.nativeTools)}\n`);
|
|
345
|
+
}
|
|
346
|
+
};
|
package/src/cli/refuse.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FailureSummary } from "../execution/failure.js";
|
|
2
2
|
import { failureFromRejected } from "../execution/failure.js";
|
|
3
3
|
import type { ArgvRefusalError, RefusalIssue } from "../interpretation/refusal.js";
|
|
4
|
+
import { EXIT_REFUSAL } from "./exit-codes.js";
|
|
4
5
|
import { writeEventNdjson } from "./render.js";
|
|
5
6
|
|
|
6
7
|
export interface Refusal {
|
|
@@ -65,5 +66,5 @@ export const refuse = (r: Refusal, json: boolean, terminal: "done" | "closed" =
|
|
|
65
66
|
terminal,
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
|
-
process.exitCode =
|
|
69
|
+
process.exitCode = EXIT_REFUSAL;
|
|
69
70
|
};
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { ArgvRefusalError } from "../interpretation/refusal.js";
|
|
2
|
-
import type { HarnessDescriptor } from "../knowledge/descriptor.js";
|
|
2
|
+
import type { HarnessDescriptor, HarnessName } from "../knowledge/descriptor.js";
|
|
3
3
|
import { HARNESS_NAMES } from "../knowledge/descriptor.js";
|
|
4
4
|
import { defaultDescriptors } from "../knowledge/overrides.js";
|
|
5
5
|
|
|
6
6
|
export const resolveHarness = (name: string): HarnessDescriptor => {
|
|
7
7
|
if (!(HARNESS_NAMES as readonly string[]).includes(name)) {
|
|
8
|
-
//
|
|
9
|
-
//
|
|
8
|
+
// No harness was chosen, so the refusal names none; the caller maps
|
|
9
|
+
// it to exit 2 with the supported list.
|
|
10
10
|
const err = new ArgvRefusalError({
|
|
11
11
|
issue: "unsupported-option",
|
|
12
|
-
harness: "claude",
|
|
13
12
|
supported: [...HARNESS_NAMES],
|
|
14
13
|
detail: `unknown harness ${JSON.stringify(name)}`,
|
|
15
14
|
});
|
|
@@ -22,7 +21,7 @@ export const resolveHarness = (name: string): HarnessDescriptor => {
|
|
|
22
21
|
if (!h) {
|
|
23
22
|
throw new ArgvRefusalError({
|
|
24
23
|
issue: "unsupported-option",
|
|
25
|
-
harness: name as
|
|
24
|
+
harness: name as HarnessName,
|
|
26
25
|
supported: [...HARNESS_NAMES],
|
|
27
26
|
detail: `harness ${name} not found`,
|
|
28
27
|
});
|