@mjasnikovs/pi-task 0.38.31 → 0.39.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 +2 -1
- package/dist/config/config.d.ts +16 -2
- package/dist/config/config.js +7 -2
- package/dist/config/group-args.d.ts +52 -0
- package/dist/config/group-args.js +110 -0
- package/dist/config/group-models.d.ts +88 -0
- package/dist/config/group-models.js +117 -0
- package/dist/config/groups.d.ts +76 -0
- package/dist/config/groups.js +110 -0
- package/dist/config/option-picker.d.ts +70 -0
- package/dist/config/option-picker.js +113 -0
- package/dist/config/reasoning.d.ts +26 -64
- package/dist/config/reasoning.js +31 -115
- package/dist/config/register.d.ts +144 -24
- package/dist/config/register.js +345 -56
- package/dist/index.js +2 -0
- package/dist/remote/push.js +1 -7
- package/dist/shared/data-home.d.ts +8 -0
- package/dist/shared/data-home.js +14 -0
- package/dist/shared/model-endpoint.d.ts +53 -0
- package/dist/shared/model-endpoint.js +98 -2
- package/dist/shared/reasoning-capability.d.ts +25 -5
- package/dist/shared/reasoning-capability.js +18 -9
- package/dist/task/child-runner.d.ts +19 -16
- package/dist/task/child-runner.js +64 -36
- package/dist/task/context-usage.d.ts +46 -0
- package/dist/task/context-usage.js +41 -0
- package/dist/task/gate-child.d.ts +15 -4
- package/dist/task/gate-child.js +2 -2
- package/dist/task/gate-deps.js +7 -2
- package/dist/task/implementation-hold.d.ts +118 -0
- package/dist/task/implementation-hold.js +165 -0
- package/dist/task/model-hold-stash.d.ts +43 -0
- package/dist/task/model-hold-stash.js +70 -0
- package/dist/task/orchestrator.d.ts +18 -5
- package/dist/task/orchestrator.js +36 -4
- package/dist/task/phases.js +2 -2
- package/dist/task/research-worker.d.ts +2 -2
- package/dist/task/research-worker.js +1 -1
- package/dist/workers/docs-core.js +2 -2
- package/dist/workers/docs-lookup.d.ts +4 -3
- package/dist/workers/docs-lookup.js +1 -1
- package/dist/workers/fetch-core.js +2 -2
- package/dist/workers/focused-extractor.d.ts +4 -3
- package/dist/workers/focused-extractor.js +5 -4
- package/dist/workers/index.js +2 -0
- package/dist/workers/model-warning.d.ts +69 -0
- package/dist/workers/model-warning.js +113 -0
- package/dist/workers/pi-worker-core.d.ts +7 -7
- package/dist/workers/pi-worker-core.js +4 -3
- package/dist/workers/pi-worker-docs.js +2 -2
- package/dist/workers/pi-worker.js +4 -4
- package/dist/workers/reasoning-warning.d.ts +17 -9
- package/dist/workers/reasoning-warning.js +69 -22
- package/package.json +1 -1
- package/dist/config/reasoning-args.d.ts +0 -23
- package/dist/config/reasoning-args.js +0 -28
- package/dist/task/implementation-thinking.d.ts +0 -56
- package/dist/task/implementation-thinking.js +0 -32
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two-step picker a /task-config step row opens on Enter.
|
|
3
|
+
*
|
|
4
|
+
* WHY A SUBMENU AND NOT `ctx.ui.select`
|
|
5
|
+
* -------------------------------------
|
|
6
|
+
* The settings panel already lives inside `ctx.ui.custom` with `overlay: true`.
|
|
7
|
+
* Opening a second dialog from inside a component's input handler races two
|
|
8
|
+
* overlays for focus. `SettingsList` already solves this: it renders the submenu
|
|
9
|
+
* in place of the list and delegates input to it. And its `done(v)` calls
|
|
10
|
+
* `onChange(id, v)` with exactly the returned value while `done(undefined)`
|
|
11
|
+
* writes nothing — the same one-value contract every cycling row uses, so the
|
|
12
|
+
* panel's dispatch needs no branch and a pair chosen out of two long lists costs
|
|
13
|
+
* ONE write.
|
|
14
|
+
*
|
|
15
|
+
* WHY NOT A BARE `SelectList`
|
|
16
|
+
* ---------------------------
|
|
17
|
+
* `SelectList.handleInput` matches up, down, confirm and cancel and DROPS every
|
|
18
|
+
* other key, so it can never fill its own `setFilter`. `Container` has no
|
|
19
|
+
* `handleInput` at all — it composes `render` and nothing else. pi's own model
|
|
20
|
+
* picker (`modes/interactive/components/model-selector.js`) hand-rolls exactly
|
|
21
|
+
* this: an `Input`, a filtered list, and a `handleInput` that routes between
|
|
22
|
+
* them. The three components that DO use `SelectList` bare — theme, thinking,
|
|
23
|
+
* show-images — are short fixed lists with no filter. A model list is not.
|
|
24
|
+
*/
|
|
25
|
+
import { Container } from '@earendil-works/pi-tui';
|
|
26
|
+
import type { SelectItem } from '@earendil-works/pi-tui';
|
|
27
|
+
import type { ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
28
|
+
type Theme = ExtensionCommandContext['ui']['theme'];
|
|
29
|
+
/** What the second stage offers, once the first has been answered. */
|
|
30
|
+
export interface PairStage {
|
|
31
|
+
options: readonly SelectItem[];
|
|
32
|
+
/**
|
|
33
|
+
* The option to open on — the value that will ACTUALLY run.
|
|
34
|
+
*
|
|
35
|
+
* Supplied by the caller rather than derived here, because deciding it is
|
|
36
|
+
* the clamp, and the clamp belongs to the config layer that owns the model's
|
|
37
|
+
* declared ladder. It is what makes the auto-switch visible: pick a model
|
|
38
|
+
* that cannot do `medium` and this stage opens on `off`.
|
|
39
|
+
*/
|
|
40
|
+
preselect: string;
|
|
41
|
+
}
|
|
42
|
+
export interface PairOptions {
|
|
43
|
+
first: readonly SelectItem[];
|
|
44
|
+
second: (firstValue: string) => PairStage;
|
|
45
|
+
/** Which half of the stored value stage one opens on. */
|
|
46
|
+
firstOf: (value: string) => string;
|
|
47
|
+
/** How the two answers become the one string the row stores. */
|
|
48
|
+
join: (first: string, second: string) => string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Model, then thinking level, as one choice.
|
|
52
|
+
*
|
|
53
|
+
* Two stages rather than one flat list of every legal pair: the pair space is
|
|
54
|
+
* models × levels, and the common edit is "change the model, keep the level" —
|
|
55
|
+
* a flat list would make that re-pick both every time.
|
|
56
|
+
*
|
|
57
|
+
* Escape at EITHER stage cancels the whole thing. Going back one step would be
|
|
58
|
+
* friendlier, but a TUI select owns up, down, enter and escape and has no fifth
|
|
59
|
+
* key to spare, and a half-answered pair must never reach `done`.
|
|
60
|
+
*/
|
|
61
|
+
export declare class PairPicker extends Container {
|
|
62
|
+
private readonly options;
|
|
63
|
+
private readonly theme;
|
|
64
|
+
private readonly done;
|
|
65
|
+
private stage;
|
|
66
|
+
constructor(options: PairOptions, currentValue: string, theme: Theme, done: (value?: string) => void);
|
|
67
|
+
private openSecond;
|
|
68
|
+
handleInput(data: string): void;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two-step picker a /task-config step row opens on Enter.
|
|
3
|
+
*
|
|
4
|
+
* WHY A SUBMENU AND NOT `ctx.ui.select`
|
|
5
|
+
* -------------------------------------
|
|
6
|
+
* The settings panel already lives inside `ctx.ui.custom` with `overlay: true`.
|
|
7
|
+
* Opening a second dialog from inside a component's input handler races two
|
|
8
|
+
* overlays for focus. `SettingsList` already solves this: it renders the submenu
|
|
9
|
+
* in place of the list and delegates input to it. And its `done(v)` calls
|
|
10
|
+
* `onChange(id, v)` with exactly the returned value while `done(undefined)`
|
|
11
|
+
* writes nothing — the same one-value contract every cycling row uses, so the
|
|
12
|
+
* panel's dispatch needs no branch and a pair chosen out of two long lists costs
|
|
13
|
+
* ONE write.
|
|
14
|
+
*
|
|
15
|
+
* WHY NOT A BARE `SelectList`
|
|
16
|
+
* ---------------------------
|
|
17
|
+
* `SelectList.handleInput` matches up, down, confirm and cancel and DROPS every
|
|
18
|
+
* other key, so it can never fill its own `setFilter`. `Container` has no
|
|
19
|
+
* `handleInput` at all — it composes `render` and nothing else. pi's own model
|
|
20
|
+
* picker (`modes/interactive/components/model-selector.js`) hand-rolls exactly
|
|
21
|
+
* this: an `Input`, a filtered list, and a `handleInput` that routes between
|
|
22
|
+
* them. The three components that DO use `SelectList` bare — theme, thinking,
|
|
23
|
+
* show-images — are short fixed lists with no filter. A model list is not.
|
|
24
|
+
*/
|
|
25
|
+
import { Container, getKeybindings, Input, SelectList } from '@earendil-works/pi-tui';
|
|
26
|
+
/** Rows visible before the list scrolls. Matches the panel's own MAX_VISIBLE. */
|
|
27
|
+
const PICKER_VISIBLE = 11;
|
|
28
|
+
function selectTheme(theme) {
|
|
29
|
+
return {
|
|
30
|
+
selectedPrefix: text => theme.fg('accent', text),
|
|
31
|
+
selectedText: text => theme.fg('accent', theme.bold(text)),
|
|
32
|
+
description: text => theme.fg('muted', text),
|
|
33
|
+
scrollInfo: text => theme.fg('dim', text),
|
|
34
|
+
noMatch: text => theme.fg('dim', text)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A filterable list with a text filter above it.
|
|
39
|
+
*
|
|
40
|
+
* Split out because both stages are one of these and the routing is the part
|
|
41
|
+
* that is easy to get wrong: `SelectList` owns four keys and drops the rest, so
|
|
42
|
+
* everything else has to be handed to the `Input` and the filter re-applied.
|
|
43
|
+
*/
|
|
44
|
+
class FilterList extends Container {
|
|
45
|
+
onPick;
|
|
46
|
+
onCancel;
|
|
47
|
+
input = new Input();
|
|
48
|
+
list;
|
|
49
|
+
constructor(options, preselect, theme, onPick, onCancel) {
|
|
50
|
+
super();
|
|
51
|
+
this.onPick = onPick;
|
|
52
|
+
this.onCancel = onCancel;
|
|
53
|
+
this.list = new SelectList([...options], PICKER_VISIBLE, selectTheme(theme));
|
|
54
|
+
// A `preselect` matching no option is normal, not an error: a row can
|
|
55
|
+
// hold a spec the catalog no longer offers, and it must still open.
|
|
56
|
+
const at = options.findIndex(o => o.value === preselect);
|
|
57
|
+
if (at !== -1)
|
|
58
|
+
this.list.setSelectedIndex(at);
|
|
59
|
+
this.list.onSelect = item => this.onPick(item.value);
|
|
60
|
+
this.list.onCancel = () => this.onCancel();
|
|
61
|
+
this.addChild(this.input);
|
|
62
|
+
this.addChild(this.list);
|
|
63
|
+
}
|
|
64
|
+
handleInput(data) {
|
|
65
|
+
const kb = getKeybindings();
|
|
66
|
+
const forList = kb.matches(data, 'tui.select.up')
|
|
67
|
+
|| kb.matches(data, 'tui.select.down')
|
|
68
|
+
|| kb.matches(data, 'tui.select.confirm')
|
|
69
|
+
|| kb.matches(data, 'tui.select.cancel');
|
|
70
|
+
if (forList) {
|
|
71
|
+
this.list.handleInput(data);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// Everything else is typing. The list re-filters on every keystroke,
|
|
75
|
+
// which is what `setFilter` is for and what nothing else calls.
|
|
76
|
+
this.input.handleInput(data);
|
|
77
|
+
this.list.setFilter(this.input.getValue());
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Model, then thinking level, as one choice.
|
|
82
|
+
*
|
|
83
|
+
* Two stages rather than one flat list of every legal pair: the pair space is
|
|
84
|
+
* models × levels, and the common edit is "change the model, keep the level" —
|
|
85
|
+
* a flat list would make that re-pick both every time.
|
|
86
|
+
*
|
|
87
|
+
* Escape at EITHER stage cancels the whole thing. Going back one step would be
|
|
88
|
+
* friendlier, but a TUI select owns up, down, enter and escape and has no fifth
|
|
89
|
+
* key to spare, and a half-answered pair must never reach `done`.
|
|
90
|
+
*/
|
|
91
|
+
export class PairPicker extends Container {
|
|
92
|
+
options;
|
|
93
|
+
theme;
|
|
94
|
+
done;
|
|
95
|
+
stage;
|
|
96
|
+
constructor(options, currentValue, theme, done) {
|
|
97
|
+
super();
|
|
98
|
+
this.options = options;
|
|
99
|
+
this.theme = theme;
|
|
100
|
+
this.done = done;
|
|
101
|
+
this.stage = new FilterList(options.first, options.firstOf(currentValue), theme, first => this.openSecond(first), () => done(undefined));
|
|
102
|
+
this.addChild(this.stage);
|
|
103
|
+
}
|
|
104
|
+
openSecond(first) {
|
|
105
|
+
const { options: second, preselect } = this.options.second(first);
|
|
106
|
+
this.stage = new FilterList(second, preselect, this.theme, level => this.done(this.options.join(first, level)), () => this.done(undefined));
|
|
107
|
+
this.clear();
|
|
108
|
+
this.addChild(this.stage);
|
|
109
|
+
}
|
|
110
|
+
handleInput(data) {
|
|
111
|
+
this.stage.handleInput(data);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -17,35 +17,21 @@
|
|
|
17
17
|
*
|
|
18
18
|
* PURE MODULE — no imports with runtime side effects, so `config.ts` can import
|
|
19
19
|
* the table and the sanitizers during its own module evaluation. `getConfig()`
|
|
20
|
-
* lives one hop away in `
|
|
20
|
+
* lives one hop away in `group-args.ts` for exactly this reason: importing
|
|
21
21
|
* it here would make config.ts ⇄ reasoning.ts a real cycle, and whichever module
|
|
22
22
|
* a caller reached first would decide whether `DEFAULT_REASONING_TABLE` was
|
|
23
23
|
* initialised before `DEFAULT_CONFIG` read it.
|
|
24
24
|
*/
|
|
25
25
|
import type { PiTaskConfig } from './config.js';
|
|
26
|
+
import { CHILD_GROUPS, type ChildGroup } from './groups.js';
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* `
|
|
31
|
-
*
|
|
32
|
-
* want the same amount and share the `phase` cell.
|
|
33
|
-
*
|
|
34
|
-
* - `research` the ad-hoc `pi-worker` subagent tool, and the fallback the four
|
|
35
|
-
* research workers use when their own cell is unset
|
|
36
|
-
* - `research:files` / `research:apis` / `research:context` / `research:tooling`
|
|
37
|
-
* one cell per research worker, so a level can be paid for in
|
|
38
|
-
* one worker without paying for it in the other three
|
|
39
|
-
* - `phase` refine, verify-tooling, grill, compose, critique, compress-label
|
|
40
|
-
* - `planning` /task-auto's planning children — clarify, decompose, and the
|
|
41
|
-
* extract/coverage passes around them
|
|
42
|
-
* - `plan` /task-plan's question and answer children
|
|
43
|
-
* - `gate` enforce, verify, lint-fix, final-fix, recommend
|
|
44
|
-
* - `extraction` the --no-tools focused docs/fetch extractors
|
|
45
|
-
* - `implementation` the host-session turn that writes the code (not a child)
|
|
28
|
+
* Re-exported because this module's whole public surface — the default table,
|
|
29
|
+
* `resolveReasoning`, `effectiveReasoning` — is typed on them, so an importer of
|
|
30
|
+
* those already needs them. The roster itself lives in groups.ts; `GROUP_BY_CHILD`
|
|
31
|
+
* and `groupForChild` are deliberately NOT re-exported, so a caller that wants
|
|
32
|
+
* the roster reaches for the roster.
|
|
46
33
|
*/
|
|
47
|
-
export
|
|
48
|
-
export declare const REASONING_GROUPS: readonly ReasoningGroup[];
|
|
34
|
+
export { CHILD_GROUPS, type ChildGroup };
|
|
49
35
|
/**
|
|
50
36
|
* The four profiles offered by /task-config.
|
|
51
37
|
* - `default` the per-group table below
|
|
@@ -82,44 +68,9 @@ export declare const REASONING_ON_LEVEL: GroupSetting;
|
|
|
82
68
|
* host's level. Any other value is a decision this project made for that
|
|
83
69
|
* group's job, and `/task-config` mode `custom` overrides all of it.
|
|
84
70
|
*/
|
|
85
|
-
export declare const DEFAULT_REASONING_TABLE: Readonly<Record<
|
|
71
|
+
export declare const DEFAULT_REASONING_TABLE: Readonly<Record<ChildGroup, GroupSetting>>;
|
|
86
72
|
/** A stored mode, or `default` when the value is not one. */
|
|
87
73
|
export declare function sanitizeReasoningMode(value: unknown): ReasoningMode;
|
|
88
|
-
/**
|
|
89
|
-
* Child NAME → reasoning group, for every child spawned under a name:
|
|
90
|
-
* `runPhaseChild`, `runPlanningChild`, and the research workers' `spec.label`.
|
|
91
|
-
*
|
|
92
|
-
* WHY KEYED ON THE NAME
|
|
93
|
-
* ---------------------
|
|
94
|
-
* The name is the only identifier in scope at all three spawn paths (phases,
|
|
95
|
-
* /task-auto planning, /task-plan), it is what the loader and the debug trail
|
|
96
|
-
* already print, and it is the one thing a reader can check against the phase
|
|
97
|
-
* list without following the call graph. Threading a group parameter through
|
|
98
|
-
* `PhaseDeps` / `AutoDeps` instead would touch both orchestrators' dep bags to
|
|
99
|
-
* express something the call site already says out loud.
|
|
100
|
-
*
|
|
101
|
-
* AN UNMAPPED NAME IS A BUILD FAILURE, not a silent `inherit`.
|
|
102
|
-
* `reasoning-groups.test.ts` scans every literal child name under `src/task`
|
|
103
|
-
* and fails if it is missing here. A defaulting lookup would let a phase added
|
|
104
|
-
* later opt itself out of the table without anyone deciding to.
|
|
105
|
-
*
|
|
106
|
-
* The gate and extraction groups are NOT here: those children reach the model
|
|
107
|
-
* through `groupThinkingArgs('gate' | 'extraction')` at call sites with no name
|
|
108
|
-
* in scope (gate-deps.ts, fetch-core.ts, docs-core.ts, pi-worker-docs.ts). The
|
|
109
|
-
* four research workers do have a name — their `spec.label` — so they are here.
|
|
110
|
-
*/
|
|
111
|
-
export declare const REASONING_GROUP_BY_CHILD: Readonly<Record<string, ReasoningGroup>>;
|
|
112
|
-
/**
|
|
113
|
-
* The group a named child belongs to.
|
|
114
|
-
*
|
|
115
|
-
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
116
|
-
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
117
|
-
* the model with today's argv is always safe — while the test treats it as a
|
|
118
|
-
* failure. That split is deliberate: the guard belongs at build time, where
|
|
119
|
-
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
120
|
-
* missing table row.
|
|
121
|
-
*/
|
|
122
|
-
export declare function reasoningGroupForChild(name: string): ReasoningGroup | undefined;
|
|
123
74
|
/**
|
|
124
75
|
* Always returns a COMPLETE record, never a partial one.
|
|
125
76
|
*
|
|
@@ -128,16 +79,16 @@ export declare function reasoningGroupForChild(name: string): ReasoningGroup | u
|
|
|
128
79
|
* `undefined`, and every call site would need its own fallback. Filling the gaps
|
|
129
80
|
* here means the type is true at the only place that constructs the value.
|
|
130
81
|
*/
|
|
131
|
-
export declare function sanitizeReasoningLevels(value: unknown): Record<
|
|
82
|
+
export declare function sanitizeReasoningLevels(value: unknown): Record<ChildGroup, GroupSetting>;
|
|
132
83
|
/**
|
|
133
84
|
* What one group is actually set to, given a config. The ONLY place the four
|
|
134
85
|
* modes are interpreted.
|
|
135
86
|
*
|
|
136
87
|
* `cfg` is required rather than defaulted to `getConfig()` so this module stays
|
|
137
88
|
* import-free (see the header). Callers that want the live config use
|
|
138
|
-
* `groupThinkingArgs` from
|
|
89
|
+
* `groupThinkingArgs` from group-args.ts.
|
|
139
90
|
*/
|
|
140
|
-
export declare function resolveReasoning(group:
|
|
91
|
+
export declare function resolveReasoning(group: ChildGroup, cfg: PiTaskConfig): GroupSetting;
|
|
141
92
|
/**
|
|
142
93
|
* The WHOLE table, as this config will actually run it.
|
|
143
94
|
*
|
|
@@ -146,12 +97,23 @@ export declare function resolveReasoning(group: ReasoningGroup, cfg: PiTaskConfi
|
|
|
146
97
|
* asks the single-group question instead, through `resolveReasoning` — which is
|
|
147
98
|
* the only place the four modes are interpreted.
|
|
148
99
|
*/
|
|
149
|
-
export declare function effectiveReasoning(cfg: PiTaskConfig): Record<
|
|
100
|
+
export declare function effectiveReasoning(cfg: PiTaskConfig): Record<ChildGroup, GroupSetting>;
|
|
150
101
|
/**
|
|
151
102
|
* The argv fragment for a setting. `inherit` is the empty fragment — no flag at
|
|
152
103
|
* all — which is what makes an all-`inherit` config byte-identical to the
|
|
153
104
|
* version before this feature existed.
|
|
154
105
|
*/
|
|
155
106
|
export declare function thinkingArgs(setting: GroupSetting): string[];
|
|
156
|
-
/**
|
|
157
|
-
|
|
107
|
+
/**
|
|
108
|
+
* One honest sentence per step, for the merged /task-config rows.
|
|
109
|
+
*
|
|
110
|
+
* ONE table, not the model help and the reasoning help concatenated. The panel
|
|
111
|
+
* sizes every frame from its tallest description (`settingsBodyHeight`), so
|
|
112
|
+
* gluing two paragraphs together for `implementation` would permanently add
|
|
113
|
+
* about six rows to the whole menu — for a row most users never open.
|
|
114
|
+
*
|
|
115
|
+
* What survives the merge is the part that is not guessable: which children the
|
|
116
|
+
* step actually spawns, what was MEASURED about its thinking level, and the one
|
|
117
|
+
* cost that is paid per turn rather than per change.
|
|
118
|
+
*/
|
|
119
|
+
export declare const STEP_GROUP_HELP: Readonly<Record<ChildGroup, string>>;
|
package/dist/config/reasoning.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'gate',
|
|
11
|
-
'extraction',
|
|
12
|
-
'implementation'
|
|
13
|
-
];
|
|
1
|
+
import { CHILD_GROUPS, sanitizeGroupRecord } from './groups.js';
|
|
2
|
+
/**
|
|
3
|
+
* Re-exported because this module's whole public surface — the default table,
|
|
4
|
+
* `resolveReasoning`, `effectiveReasoning` — is typed on them, so an importer of
|
|
5
|
+
* those already needs them. The roster itself lives in groups.ts; `GROUP_BY_CHILD`
|
|
6
|
+
* and `groupForChild` are deliberately NOT re-exported, so a caller that wants
|
|
7
|
+
* the roster reaches for the roster.
|
|
8
|
+
*/
|
|
9
|
+
export { CHILD_GROUPS };
|
|
14
10
|
export const REASONING_MODES = ['default', 'on', 'off', 'custom'];
|
|
15
11
|
/**
|
|
16
12
|
* The settings offered in /task-config: `inherit` plus the standard part of pi's
|
|
@@ -56,80 +52,6 @@ export const DEFAULT_REASONING_TABLE = {
|
|
|
56
52
|
export function sanitizeReasoningMode(value) {
|
|
57
53
|
return REASONING_MODES.includes(value) ? value : 'default';
|
|
58
54
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Child NAME → reasoning group, for every child spawned under a name:
|
|
61
|
-
* `runPhaseChild`, `runPlanningChild`, and the research workers' `spec.label`.
|
|
62
|
-
*
|
|
63
|
-
* WHY KEYED ON THE NAME
|
|
64
|
-
* ---------------------
|
|
65
|
-
* The name is the only identifier in scope at all three spawn paths (phases,
|
|
66
|
-
* /task-auto planning, /task-plan), it is what the loader and the debug trail
|
|
67
|
-
* already print, and it is the one thing a reader can check against the phase
|
|
68
|
-
* list without following the call graph. Threading a group parameter through
|
|
69
|
-
* `PhaseDeps` / `AutoDeps` instead would touch both orchestrators' dep bags to
|
|
70
|
-
* express something the call site already says out loud.
|
|
71
|
-
*
|
|
72
|
-
* AN UNMAPPED NAME IS A BUILD FAILURE, not a silent `inherit`.
|
|
73
|
-
* `reasoning-groups.test.ts` scans every literal child name under `src/task`
|
|
74
|
-
* and fails if it is missing here. A defaulting lookup would let a phase added
|
|
75
|
-
* later opt itself out of the table without anyone deciding to.
|
|
76
|
-
*
|
|
77
|
-
* The gate and extraction groups are NOT here: those children reach the model
|
|
78
|
-
* through `groupThinkingArgs('gate' | 'extraction')` at call sites with no name
|
|
79
|
-
* in scope (gate-deps.ts, fetch-core.ts, docs-core.ts, pi-worker-docs.ts). The
|
|
80
|
-
* four research workers do have a name — their `spec.label` — so they are here.
|
|
81
|
-
*/
|
|
82
|
-
export const REASONING_GROUP_BY_CHILD = {
|
|
83
|
-
// ── phase: task/phases.ts + task/title-label.ts ──────────────────────────
|
|
84
|
-
refine: 'phase',
|
|
85
|
-
'verify-tooling': 'phase',
|
|
86
|
-
'grill-auto': 'phase',
|
|
87
|
-
'grill-gen': 'phase',
|
|
88
|
-
compose: 'phase',
|
|
89
|
-
critique: 'phase',
|
|
90
|
-
'critique-triage': 'phase',
|
|
91
|
-
'compress-label': 'phase',
|
|
92
|
-
// ── planning: task/auto-orchestrator.ts ──────────────────────────────────
|
|
93
|
-
'clarify-triage': 'planning',
|
|
94
|
-
'auto-clarify': 'planning',
|
|
95
|
-
'auto-decompose': 'planning',
|
|
96
|
-
'requirement-extract': 'planning',
|
|
97
|
-
'decompose-coverage': 'planning',
|
|
98
|
-
'coverage-map': 'planning',
|
|
99
|
-
'contract-extract': 'planning',
|
|
100
|
-
'launch-extract': 'planning',
|
|
101
|
-
// ── plan: task/plan-orchestrator.ts ──────────────────────────────────────
|
|
102
|
-
'plan-question': 'plan',
|
|
103
|
-
'plan-answer': 'plan',
|
|
104
|
-
// ── research: task/phases.ts `workerSpecs`, keyed on the spec's LABEL ─────
|
|
105
|
-
'worker:files': 'research:files',
|
|
106
|
-
'worker:apis': 'research:apis',
|
|
107
|
-
'worker:context': 'research:context',
|
|
108
|
-
'worker:tooling': 'research:tooling'
|
|
109
|
-
};
|
|
110
|
-
/**
|
|
111
|
-
* The group a named child belongs to.
|
|
112
|
-
*
|
|
113
|
-
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
114
|
-
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
115
|
-
* the model with today's argv is always safe — while the test treats it as a
|
|
116
|
-
* failure. That split is deliberate: the guard belongs at build time, where
|
|
117
|
-
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
118
|
-
* missing table row.
|
|
119
|
-
*/
|
|
120
|
-
export function reasoningGroupForChild(name) {
|
|
121
|
-
return REASONING_GROUP_BY_CHILD[name];
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* For a `research:*` group, the group a stored config falls back to when its own
|
|
125
|
-
* key is missing. Every other group maps to `undefined`.
|
|
126
|
-
*/
|
|
127
|
-
const RESEARCH_SUBGROUP_PARENT = {
|
|
128
|
-
'research:files': 'research',
|
|
129
|
-
'research:apis': 'research',
|
|
130
|
-
'research:context': 'research',
|
|
131
|
-
'research:tooling': 'research'
|
|
132
|
-
};
|
|
133
55
|
/**
|
|
134
56
|
* Always returns a COMPLETE record, never a partial one.
|
|
135
57
|
*
|
|
@@ -139,25 +61,7 @@ const RESEARCH_SUBGROUP_PARENT = {
|
|
|
139
61
|
* here means the type is true at the only place that constructs the value.
|
|
140
62
|
*/
|
|
141
63
|
export function sanitizeReasoningLevels(value) {
|
|
142
|
-
|
|
143
|
-
value
|
|
144
|
-
: {};
|
|
145
|
-
const valid = (v) => REASONING_SETTINGS.includes(v);
|
|
146
|
-
const out = {};
|
|
147
|
-
for (const group of REASONING_GROUPS) {
|
|
148
|
-
const stored_ = stored[group];
|
|
149
|
-
if (valid(stored_)) {
|
|
150
|
-
out[group] = stored_;
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
// A `research:*` key the stored config never had falls back to its
|
|
154
|
-
// parent `research` value, so a config written before the split keeps
|
|
155
|
-
// meaning what it meant.
|
|
156
|
-
const parent = RESEARCH_SUBGROUP_PARENT[group];
|
|
157
|
-
out[group] =
|
|
158
|
-
parent && valid(stored[parent]) ? stored[parent] : DEFAULT_REASONING_TABLE[group];
|
|
159
|
-
}
|
|
160
|
-
return out;
|
|
64
|
+
return sanitizeGroupRecord(value, (v) => REASONING_SETTINGS.includes(v), group => DEFAULT_REASONING_TABLE[group]);
|
|
161
65
|
}
|
|
162
66
|
/**
|
|
163
67
|
* What one group is actually set to, given a config. The ONLY place the four
|
|
@@ -165,7 +69,7 @@ export function sanitizeReasoningLevels(value) {
|
|
|
165
69
|
*
|
|
166
70
|
* `cfg` is required rather than defaulted to `getConfig()` so this module stays
|
|
167
71
|
* import-free (see the header). Callers that want the live config use
|
|
168
|
-
* `groupThinkingArgs` from
|
|
72
|
+
* `groupThinkingArgs` from group-args.ts.
|
|
169
73
|
*/
|
|
170
74
|
export function resolveReasoning(group, cfg) {
|
|
171
75
|
switch (cfg.reasoningMode) {
|
|
@@ -189,7 +93,7 @@ export function resolveReasoning(group, cfg) {
|
|
|
189
93
|
*/
|
|
190
94
|
export function effectiveReasoning(cfg) {
|
|
191
95
|
const out = {};
|
|
192
|
-
for (const group of
|
|
96
|
+
for (const group of CHILD_GROUPS)
|
|
193
97
|
out[group] = resolveReasoning(group, cfg);
|
|
194
98
|
return out;
|
|
195
99
|
}
|
|
@@ -201,12 +105,23 @@ export function effectiveReasoning(cfg) {
|
|
|
201
105
|
export function thinkingArgs(setting) {
|
|
202
106
|
return setting === 'inherit' ? [] : ['--thinking', setting];
|
|
203
107
|
}
|
|
204
|
-
/**
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
108
|
+
/**
|
|
109
|
+
* One honest sentence per step, for the merged /task-config rows.
|
|
110
|
+
*
|
|
111
|
+
* ONE table, not the model help and the reasoning help concatenated. The panel
|
|
112
|
+
* sizes every frame from its tallest description (`settingsBodyHeight`), so
|
|
113
|
+
* gluing two paragraphs together for `implementation` would permanently add
|
|
114
|
+
* about six rows to the whole menu — for a row most users never open.
|
|
115
|
+
*
|
|
116
|
+
* What survives the merge is the part that is not guessable: which children the
|
|
117
|
+
* step actually spawns, what was MEASURED about its thinking level, and the one
|
|
118
|
+
* cost that is paid per turn rather than per change.
|
|
119
|
+
*/
|
|
120
|
+
export const STEP_GROUP_HELP = {
|
|
121
|
+
research: 'The pi-worker subagent tool, and the fallback for any research worker below. '
|
|
122
|
+
+ 'Long read-only loops, where a cheap fast model pays off.',
|
|
208
123
|
'research:files': 'Research worker 1 of 4: maps which files the task will touch. Read-heavy. '
|
|
209
|
-
+ 'Measured: the two arms tie, so it runs without
|
|
124
|
+
+ 'Measured: the two thinking arms tie, so it runs without.',
|
|
210
125
|
'research:apis': 'Research worker 2 of 4: the symbols and signatures the task must call. '
|
|
211
126
|
+ 'Read-heavy, docs- and search-capable.',
|
|
212
127
|
'research:context': 'Research worker 3 of 4: how the project is put together. One of the two that '
|
|
@@ -221,6 +136,7 @@ export const REASONING_GROUP_HELP = {
|
|
|
221
136
|
gate: 'The checks that run after code is written: verify, enforce, lint-fix, autofix.',
|
|
222
137
|
extraction: 'The small no-tools children that pull one answer out of a fetched page or '
|
|
223
138
|
+ 'a docs chunk.',
|
|
224
|
-
implementation: 'The main session turn that
|
|
225
|
-
+
|
|
139
|
+
implementation: 'The main session turn that writes the code — YOUR session, moved for the turn and '
|
|
140
|
+
+ 'moved back. Unlike every step above, the model half here is not free: a switch '
|
|
141
|
+
+ 're-bills the whole prompt as a cache miss, twice per task.'
|
|
226
142
|
};
|