@mjasnikovs/pi-task 0.38.23 → 0.38.25
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 -2
- package/dist/config/reasoning-args.d.ts +12 -1
- package/dist/config/reasoning-args.js +5 -2
- package/dist/config/reasoning.d.ts +47 -6
- package/dist/config/reasoning.js +84 -9
- package/dist/config/register.d.ts +50 -26
- package/dist/config/register.js +96 -80
- package/dist/shared/reasoning-capability.d.ts +2 -5
- package/dist/shared/reasoning-capability.js +31 -4
- package/dist/task/auto-orchestrator.d.ts +2 -0
- package/dist/task/auto-orchestrator.js +28 -41
- package/dist/task/child-runner.d.ts +89 -24
- package/dist/task/child-runner.js +67 -46
- package/dist/task/gate-child.js +11 -11
- package/dist/task/orchestrator.d.ts +14 -20
- package/dist/task/orchestrator.js +12 -9
- package/dist/task/phases.d.ts +0 -23
- package/dist/task/phases.js +48 -464
- package/dist/task/question-dialog.d.ts +56 -0
- package/dist/task/question-dialog.js +53 -0
- package/dist/task/research-fanout-budget.d.ts +20 -0
- package/dist/task/research-fanout-budget.js +29 -0
- package/dist/task/research-worker.d.ts +183 -0
- package/dist/task/research-worker.js +429 -0
- package/dist/workers/brave-warning.js +4 -30
- package/dist/workers/docs-core.d.ts +8 -4
- package/dist/workers/docs-core.js +30 -21
- package/dist/workers/docs-lookup.d.ts +72 -0
- package/dist/workers/docs-lookup.js +53 -0
- package/dist/workers/docs-project.d.ts +9 -0
- package/dist/workers/docs-project.js +15 -0
- package/dist/workers/pi-worker-core.d.ts +112 -109
- package/dist/workers/pi-worker-core.js +33 -48
- package/dist/workers/pi-worker-docs.js +27 -31
- package/dist/workers/pi-worker.js +6 -0
- package/dist/workers/reasoning-warning.d.ts +10 -16
- package/dist/workers/reasoning-warning.js +25 -57
- package/dist/workers/session-hint.d.ts +37 -0
- package/dist/workers/session-hint.js +82 -0
- package/dist/workers/worker-failure.d.ts +34 -0
- package/dist/workers/worker-failure.js +27 -16
- package/dist/workers/worker-kill.d.ts +84 -0
- package/dist/workers/worker-kill.js +124 -0
- package/dist/workers/worker-profiles.d.ts +314 -0
- package/dist/workers/worker-profiles.js +220 -0
- package/package.json +1 -1
- package/dist/task/reasoning-groups.d.ts +0 -36
- package/dist/task/reasoning-groups.js +0 -36
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/@mjasnikovs/pi-task)
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
[](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
|
|
12
|
-
[](#development)
|
|
13
13
|
[](./tsconfig.json)
|
|
14
14
|
|
|
15
15
|
</div>
|
|
@@ -243,7 +243,7 @@ them checked in.
|
|
|
243
243
|
|
|
244
244
|
```sh
|
|
245
245
|
bun install
|
|
246
|
-
bun run test #
|
|
246
|
+
bun run test # 4321 tests across 242 files
|
|
247
247
|
bun run lint # prettier + eslint + tsc --noEmit
|
|
248
248
|
bun run build # tsc → dist/
|
|
249
249
|
```
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live-config bridge for reasoning profiles: group in, argv fragment out.
|
|
3
|
+
*
|
|
4
|
+
* Separate from reasoning.ts because that module must stay import-free — see its
|
|
5
|
+
* header. This file is the one hop that reads `getConfig()`, so it imports both
|
|
6
|
+
* and neither imports it back: a tree, not a cycle.
|
|
7
|
+
*
|
|
8
|
+
* Read PER CALL, never cached at module scope, so a /task-config change lands on
|
|
9
|
+
* the next child without a restart — the same contract childBaseArgs states.
|
|
10
|
+
*/
|
|
11
|
+
import { type PiTaskConfig } from './config.js';
|
|
1
12
|
import { type ReasoningGroup } from './reasoning.js';
|
|
2
13
|
/**
|
|
3
14
|
* The `['--thinking', level]` fragment for a group, or `[]` when the group is
|
|
@@ -7,4 +18,4 @@ import { type ReasoningGroup } from './reasoning.js';
|
|
|
7
18
|
* themselves — an argv builder that resolves its own policy is one that cannot
|
|
8
19
|
* be told to do something else, which is how childBaseArgs became universal.
|
|
9
20
|
*/
|
|
10
|
-
export declare function groupThinkingArgs(group: ReasoningGroup): string[];
|
|
21
|
+
export declare function groupThinkingArgs(group: ReasoningGroup, cfg?: PiTaskConfig): string[];
|
|
@@ -18,6 +18,9 @@ import { resolveReasoning, thinkingArgs } from './reasoning.js';
|
|
|
18
18
|
* themselves — an argv builder that resolves its own policy is one that cannot
|
|
19
19
|
* be told to do something else, which is how childBaseArgs became universal.
|
|
20
20
|
*/
|
|
21
|
-
export function groupThinkingArgs(group) {
|
|
22
|
-
|
|
21
|
+
export function groupThinkingArgs(group, cfg) {
|
|
22
|
+
// The default is EVALUATED HERE, per call, which is the contract this module
|
|
23
|
+
// exists to keep. Hoisting the read to module scope would leave every test
|
|
24
|
+
// green, so the parameter is what makes the contract assertable at all.
|
|
25
|
+
return thinkingArgs(resolveReasoning(group, cfg ?? getConfig()));
|
|
23
26
|
}
|
|
@@ -142,13 +142,41 @@ export declare const DEFAULT_REASONING_TABLE: Readonly<Record<ReasoningGroup, Gr
|
|
|
142
142
|
*/
|
|
143
143
|
export declare function sanitizeReasoningMode(value: unknown): ReasoningMode;
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
145
|
+
* Child NAME → reasoning group, for every child that goes through
|
|
146
|
+
* `runPhaseChild` / `runPlanningChild`.
|
|
147
|
+
*
|
|
148
|
+
* WHY KEYED ON THE NAME
|
|
149
|
+
* ---------------------
|
|
150
|
+
* The name is the only identifier in scope at all three spawn paths (phases,
|
|
151
|
+
* /task-auto planning, /task-plan), it is what the loader and the debug trail
|
|
152
|
+
* already print, and it is the one thing a reader can check against the phase
|
|
153
|
+
* list without following the call graph. Threading a group parameter through
|
|
154
|
+
* `PhaseDeps` / `AutoDeps` instead would touch both orchestrators' dep bags to
|
|
155
|
+
* express something the call site already says out loud.
|
|
156
|
+
*
|
|
157
|
+
* AN UNMAPPED NAME IS A BUILD FAILURE, not a silent `inherit`.
|
|
158
|
+
* `reasoning-groups.test.ts` scans every literal child name in src/ and fails if
|
|
159
|
+
* it is missing here. A defaulting lookup would let a phase added next year opt
|
|
160
|
+
* itself out of a measured setting without anyone deciding to — which is exactly
|
|
161
|
+
* how `/no_think` ended up applied to eight prompts and read by none of them.
|
|
162
|
+
*
|
|
163
|
+
* The gate and extraction groups are NOT here: those children reach the model
|
|
164
|
+
* through `runWorker` / `focusedChildArgs` at a site with no name in scope, so
|
|
165
|
+
* the group is passed directly. The four RESEARCH workers do have a name — their
|
|
166
|
+
* `spec.label` — and so they are here.
|
|
167
|
+
*/
|
|
168
|
+
export declare const REASONING_GROUP_BY_CHILD: Readonly<Record<string, ReasoningGroup>>;
|
|
169
|
+
/**
|
|
170
|
+
* The group a named child belongs to.
|
|
171
|
+
*
|
|
172
|
+
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
173
|
+
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
174
|
+
* the model with today's argv is always safe — while the test treats it as a
|
|
175
|
+
* failure. That split is deliberate: the guard belongs at build time, where
|
|
176
|
+
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
177
|
+
* missing table row.
|
|
150
178
|
*/
|
|
151
|
-
export declare
|
|
179
|
+
export declare function reasoningGroupForChild(name: string): ReasoningGroup | undefined;
|
|
152
180
|
/**
|
|
153
181
|
* Always returns a COMPLETE record, never a partial one.
|
|
154
182
|
*
|
|
@@ -167,6 +195,19 @@ export declare function sanitizeReasoningLevels(value: unknown): Record<Reasonin
|
|
|
167
195
|
* `groupThinkingArgs` from reasoning-args.ts.
|
|
168
196
|
*/
|
|
169
197
|
export declare function resolveReasoning(group: ReasoningGroup, cfg: PiTaskConfig): GroupSetting;
|
|
198
|
+
/**
|
|
199
|
+
* The WHOLE table, as this config will actually run it.
|
|
200
|
+
*
|
|
201
|
+
* This is the question every caller has — the settings menu when it repaints,
|
|
202
|
+
* the mismatch warning when it scans, the custom-mode seeder when it freezes the
|
|
203
|
+
* table — and each of them used to write the same loop over `resolveReasoning`.
|
|
204
|
+
* A per-key accessor with no whole-table companion is also how a THIRD shape got
|
|
205
|
+
* invented (`Array<{group, setting}>`) and leaked into `reasoningMismatches`.
|
|
206
|
+
*
|
|
207
|
+
* `resolveReasoning` stays: one group is still a fair question, and it is the
|
|
208
|
+
* only place the four modes are interpreted.
|
|
209
|
+
*/
|
|
210
|
+
export declare function effectiveReasoning(cfg: PiTaskConfig): Record<ReasoningGroup, GroupSetting>;
|
|
170
211
|
/**
|
|
171
212
|
* The argv fragment for a setting. `inherit` is the empty fragment — no flag at
|
|
172
213
|
* all — which is what makes an all-`inherit` config byte-identical to the
|
package/dist/config/reasoning.js
CHANGED
|
@@ -711,18 +711,75 @@ export function sanitizeReasoningMode(value) {
|
|
|
711
711
|
return REASONING_MODES.includes(value) ? value : 'default';
|
|
712
712
|
}
|
|
713
713
|
/**
|
|
714
|
-
*
|
|
715
|
-
*
|
|
714
|
+
* Child NAME → reasoning group, for every child that goes through
|
|
715
|
+
* `runPhaseChild` / `runPlanningChild`.
|
|
716
716
|
*
|
|
717
|
-
*
|
|
718
|
-
*
|
|
717
|
+
* WHY KEYED ON THE NAME
|
|
718
|
+
* ---------------------
|
|
719
|
+
* The name is the only identifier in scope at all three spawn paths (phases,
|
|
720
|
+
* /task-auto planning, /task-plan), it is what the loader and the debug trail
|
|
721
|
+
* already print, and it is the one thing a reader can check against the phase
|
|
722
|
+
* list without following the call graph. Threading a group parameter through
|
|
723
|
+
* `PhaseDeps` / `AutoDeps` instead would touch both orchestrators' dep bags to
|
|
724
|
+
* express something the call site already says out loud.
|
|
725
|
+
*
|
|
726
|
+
* AN UNMAPPED NAME IS A BUILD FAILURE, not a silent `inherit`.
|
|
727
|
+
* `reasoning-groups.test.ts` scans every literal child name in src/ and fails if
|
|
728
|
+
* it is missing here. A defaulting lookup would let a phase added next year opt
|
|
729
|
+
* itself out of a measured setting without anyone deciding to — which is exactly
|
|
730
|
+
* how `/no_think` ended up applied to eight prompts and read by none of them.
|
|
731
|
+
*
|
|
732
|
+
* The gate and extraction groups are NOT here: those children reach the model
|
|
733
|
+
* through `runWorker` / `focusedChildArgs` at a site with no name in scope, so
|
|
734
|
+
* the group is passed directly. The four RESEARCH workers do have a name — their
|
|
735
|
+
* `spec.label` — and so they are here.
|
|
719
736
|
*/
|
|
720
|
-
export const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
737
|
+
export const REASONING_GROUP_BY_CHILD = {
|
|
738
|
+
// ── phase: task/phases.ts + task/title-label.ts ──────────────────────────
|
|
739
|
+
refine: 'phase',
|
|
740
|
+
'verify-tooling': 'phase',
|
|
741
|
+
'grill-auto': 'phase',
|
|
742
|
+
'grill-gen': 'phase',
|
|
743
|
+
compose: 'phase',
|
|
744
|
+
critique: 'phase',
|
|
745
|
+
'critique-triage': 'phase',
|
|
746
|
+
'compress-label': 'phase',
|
|
747
|
+
// ── planning: task/auto-orchestrator.ts ──────────────────────────────────
|
|
748
|
+
'clarify-triage': 'planning',
|
|
749
|
+
'auto-clarify': 'planning',
|
|
750
|
+
'auto-decompose': 'planning',
|
|
751
|
+
'requirement-extract': 'planning',
|
|
752
|
+
'decompose-coverage': 'planning',
|
|
753
|
+
'coverage-map': 'planning',
|
|
754
|
+
'contract-extract': 'planning',
|
|
755
|
+
'launch-extract': 'planning',
|
|
756
|
+
// ── plan: task/plan-orchestrator.ts ──────────────────────────────────────
|
|
757
|
+
'plan-question': 'plan',
|
|
758
|
+
'plan-answer': 'plan',
|
|
759
|
+
// ── research: task/phases.ts `workerSpecs`, keyed on the spec's LABEL ─────
|
|
760
|
+
// These were a second table (`RESEARCH_WORKER_GROUPS`) keyed on the section
|
|
761
|
+
// heading, with a silent `?? 'research'` fallback and a guard that sliced
|
|
762
|
+
// phases.ts source between two string offsets from the config directory.
|
|
763
|
+
// The label is the same name the loader, the debug trail and the A/B ledgers
|
|
764
|
+
// already print, so they belong here with every other named child.
|
|
765
|
+
'worker:files': 'research:files',
|
|
766
|
+
'worker:apis': 'research:apis',
|
|
767
|
+
'worker:context': 'research:context',
|
|
768
|
+
'worker:tooling': 'research:tooling'
|
|
725
769
|
};
|
|
770
|
+
/**
|
|
771
|
+
* The group a named child belongs to.
|
|
772
|
+
*
|
|
773
|
+
* Returns `undefined` for a name the table does not know, and the CALLER decides
|
|
774
|
+
* what that means. `runPhaseChild` treats it as `inherit` — a child that reaches
|
|
775
|
+
* the model with today's argv is always safe — while the test treats it as a
|
|
776
|
+
* failure. That split is deliberate: the guard belongs at build time, where
|
|
777
|
+
* someone can fix it, not at run time, where it would abort a user's task over a
|
|
778
|
+
* missing table row.
|
|
779
|
+
*/
|
|
780
|
+
export function reasoningGroupForChild(name) {
|
|
781
|
+
return REASONING_GROUP_BY_CHILD[name];
|
|
782
|
+
}
|
|
726
783
|
/**
|
|
727
784
|
* For a `research:*` group, the group a stored config falls back to when its own
|
|
728
785
|
* key is missing. Every other group maps to `undefined`.
|
|
@@ -795,6 +852,24 @@ export function resolveReasoning(group, cfg) {
|
|
|
795
852
|
return DEFAULT_REASONING_TABLE[group];
|
|
796
853
|
}
|
|
797
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* The WHOLE table, as this config will actually run it.
|
|
857
|
+
*
|
|
858
|
+
* This is the question every caller has — the settings menu when it repaints,
|
|
859
|
+
* the mismatch warning when it scans, the custom-mode seeder when it freezes the
|
|
860
|
+
* table — and each of them used to write the same loop over `resolveReasoning`.
|
|
861
|
+
* A per-key accessor with no whole-table companion is also how a THIRD shape got
|
|
862
|
+
* invented (`Array<{group, setting}>`) and leaked into `reasoningMismatches`.
|
|
863
|
+
*
|
|
864
|
+
* `resolveReasoning` stays: one group is still a fair question, and it is the
|
|
865
|
+
* only place the four modes are interpreted.
|
|
866
|
+
*/
|
|
867
|
+
export function effectiveReasoning(cfg) {
|
|
868
|
+
const out = {};
|
|
869
|
+
for (const group of REASONING_GROUPS)
|
|
870
|
+
out[group] = resolveReasoning(group, cfg);
|
|
871
|
+
return out;
|
|
872
|
+
}
|
|
798
873
|
/**
|
|
799
874
|
* The argv fragment for a setting. `inherit` is the empty fragment — no flag at
|
|
800
875
|
* all — which is what makes an all-`inherit` config byte-identical to the
|
|
@@ -59,7 +59,18 @@ declare class BorderedBox implements Component {
|
|
|
59
59
|
* because both read `format`.
|
|
60
60
|
*/
|
|
61
61
|
export interface ConfigItem {
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* The row's id. A `keyof PiTaskConfig` for a fixed setting; a prefixed
|
|
64
|
+
* string (`reason:`, `tool:`, `ext:`) for a DISCOVERED one.
|
|
65
|
+
*
|
|
66
|
+
* It is `string`, not `keyof PiTaskConfig`, and that is what lets the three
|
|
67
|
+
* dynamic families BE rows instead of bypassing them. While the id was
|
|
68
|
+
* narrow, each family re-invented both directions by hand — a builder, an
|
|
69
|
+
* apply function, and an arm in a four-way prefix ladder 300 lines away —
|
|
70
|
+
* and the round-trip property `config-items.test.ts` runs over `ITEMS`
|
|
71
|
+
* covered none of them.
|
|
72
|
+
*/
|
|
73
|
+
id: string;
|
|
63
74
|
/**
|
|
64
75
|
* Which titled block of the menu this row sits under. Rows are grouped by
|
|
65
76
|
* section in {@link panelItems}, in the order the sections first appear in
|
|
@@ -75,6 +86,11 @@ export interface ConfigItem {
|
|
|
75
86
|
format: (cfg: PiTaskConfig) => string;
|
|
76
87
|
/** Write the chosen label back. A value it does not recognise is ignored. */
|
|
77
88
|
apply: (cfg: PiTaskConfig, chosen: string) => void;
|
|
89
|
+
/**
|
|
90
|
+
* What the headless one-line rendering calls this row, when `label` reads
|
|
91
|
+
* only in the panel. See {@link PanelItem.headlessLabel}.
|
|
92
|
+
*/
|
|
93
|
+
headlessLabel?: string;
|
|
78
94
|
}
|
|
79
95
|
/**
|
|
80
96
|
* The titled blocks the settings menu is divided into.
|
|
@@ -102,22 +118,10 @@ export declare const SECTION_ID_PREFIX = "section:";
|
|
|
102
118
|
* either of the two ladders this replaced.
|
|
103
119
|
*/
|
|
104
120
|
export declare const ITEMS: ConfigItem[];
|
|
105
|
-
export declare function extensionItems(extensions: InstalledExtension[]
|
|
106
|
-
id: string;
|
|
107
|
-
label: string;
|
|
108
|
-
description: string;
|
|
109
|
-
currentValue: string;
|
|
110
|
-
values: string[];
|
|
111
|
-
}[];
|
|
121
|
+
export declare function extensionItems(extensions: InstalledExtension[]): ConfigItem[];
|
|
112
122
|
/** Apply an extension toggle to the config's whitelist (idempotent both ways). */
|
|
113
123
|
export declare function applyExtensionToggle(whitelist: readonly string[], entryPath: string, on: boolean): string[];
|
|
114
|
-
export declare function toolItems(tools: readonly GuardableTool[]
|
|
115
|
-
id: string;
|
|
116
|
-
label: string;
|
|
117
|
-
description: string;
|
|
118
|
-
currentValue: string;
|
|
119
|
-
values: string[];
|
|
120
|
-
}[];
|
|
124
|
+
export declare function toolItems(tools: readonly GuardableTool[]): ConfigItem[];
|
|
121
125
|
/** Apply a per-tool watchdog toggle to the exemption list (idempotent both ways). */
|
|
122
126
|
export declare function applyToolToggle(exempt: readonly string[], toolName: string, watched: boolean): string[];
|
|
123
127
|
/**
|
|
@@ -138,7 +142,7 @@ export declare function applyToolToggle(exempt: readonly string[], toolName: str
|
|
|
138
142
|
* Leading spaces survive: SettingsList pads the label right, never trims it.
|
|
139
143
|
*/
|
|
140
144
|
export declare function reasoningRowLabel(group: ReasoningGroup): string;
|
|
141
|
-
export declare function reasoningItems(
|
|
145
|
+
export declare function reasoningItems(): ConfigItem[];
|
|
142
146
|
/**
|
|
143
147
|
* Apply one group row's new value.
|
|
144
148
|
*
|
|
@@ -149,16 +153,6 @@ export declare function reasoningItems(cfg: PiTaskConfig): PanelItem[];
|
|
|
149
153
|
* it, nudging `research` while in `off` would silently return the other six to
|
|
150
154
|
* whatever the stored table happened to hold.
|
|
151
155
|
*/
|
|
152
|
-
/**
|
|
153
|
-
* Write every `think:` row's displayed value back from the config.
|
|
154
|
-
*
|
|
155
|
-
* Called after ANY change, not just a reasoning one, because the mode row and
|
|
156
|
-
* the seven group rows are one control split across eight lines: cycling
|
|
157
|
-
* `reasoning` to `off` changes what all seven of them run at, and cycling one
|
|
158
|
-
* group row flips the mode, which changes the other six. A row showing a level
|
|
159
|
-
* the run will not use is worse than no row.
|
|
160
|
-
*/
|
|
161
|
-
export declare function refreshReasoningRows(cfg: PiTaskConfig, list: SettingsList): void;
|
|
162
156
|
export declare function applyReasoningLevel(cfg: PiTaskConfig, group: ReasoningGroup, chosen: string): void;
|
|
163
157
|
/**
|
|
164
158
|
* Tallest body the settings list can render, so {@link BorderedBox} can pad
|
|
@@ -204,7 +198,37 @@ export declare function createSettingsPanel(items: PanelItem[], theme: Theme,
|
|
|
204
198
|
* beside seven rows still claiming `inherit` — which is what it did.
|
|
205
199
|
*/
|
|
206
200
|
onChange: (id: string, newValue: string, list: SettingsList) => void, onCancel: () => void): BorderedBox;
|
|
201
|
+
/**
|
|
202
|
+
* Every settings row for this session, fixed and DISCOVERED, in menu order.
|
|
203
|
+
*
|
|
204
|
+
* One list of `ConfigItem`s — so a row's display, its write-back and its section
|
|
205
|
+
* are one object, the dispatch is a lookup by id, and the round-trip properties
|
|
206
|
+
* in `config-items.test.ts` cover the reasoning, tool and extension families
|
|
207
|
+
* that used to bypass the row type entirely.
|
|
208
|
+
*
|
|
209
|
+
* Fixed rows come before discovered ones within a section, which is the order
|
|
210
|
+
* the hand-written `extra` map produced.
|
|
211
|
+
*/
|
|
212
|
+
export declare function configRows(installed: InstalledExtension[], tools?: readonly GuardableTool[]): ConfigItem[];
|
|
213
|
+
/** Render `rows` for the current config, grouped under their section headers. */
|
|
214
|
+
export declare function renderRows(cfg: PiTaskConfig, rows: readonly ConfigItem[]): PanelItem[];
|
|
207
215
|
/** The full settings row list for the current config, in menu order. */
|
|
208
216
|
export declare function panelItems(cfg: PiTaskConfig, installed: InstalledExtension[], tools?: readonly GuardableTool[]): PanelItem[];
|
|
217
|
+
/**
|
|
218
|
+
* Re-ask every row what it now displays, and write the answers back.
|
|
219
|
+
*
|
|
220
|
+
* A row's `currentValue` in the live list is a snapshot taken when the panel was
|
|
221
|
+
* built, and rows describe each other: cycling `reasoning` to `off` changes what
|
|
222
|
+
* all seven `think:` rows run at, and cycling one group row flips the mode,
|
|
223
|
+
* which changes the other six.
|
|
224
|
+
*
|
|
225
|
+
* This runs after ANY change, over EVERY row. Its predecessor,
|
|
226
|
+
* `refreshReasoningRows`, ran after any change too — but hard-coded the seven
|
|
227
|
+
* reasoning ids plus `reasoningMode` and touched none of the other ~30 rows, so
|
|
228
|
+
* the next cross-row dependency would have needed a fifth function. Re-reading a
|
|
229
|
+
* `format` costs nothing, which is why there is still no list of "changes that
|
|
230
|
+
* need a refresh" to keep correct.
|
|
231
|
+
*/
|
|
232
|
+
export declare function syncRows(cfg: PiTaskConfig, rows: readonly ConfigItem[], list: SettingsList): void;
|
|
209
233
|
export declare function registerConfig(pi: ExtensionAPI): void;
|
|
210
234
|
export {};
|
package/dist/config/register.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SEARCH_PROVIDERS, SEARCH_PROVIDER_LABELS, providerForLabel } from '../w
|
|
|
5
5
|
import { COMMAND_TIMEOUT_OPTIONS, DEBUG_LOG_OPTIONS, getConfig, sanitizeDebugLogs, saveConfig, STREAM_INACTIVITY_OPTIONS } from './config.js';
|
|
6
6
|
import { listInstalledExtensions } from './extension-list.js';
|
|
7
7
|
import { listGuardableTools } from './tool-list.js';
|
|
8
|
-
import { REASONING_GROUPS, REASONING_MODES, sanitizeReasoningMode, REASONING_GROUP_HELP, REASONING_SETTINGS, resolveReasoning } from './reasoning.js';
|
|
8
|
+
import { REASONING_GROUPS, REASONING_MODES, sanitizeReasoningMode, REASONING_GROUP_HELP, REASONING_SETTINGS, effectiveReasoning, resolveReasoning } from './reasoning.js';
|
|
9
9
|
// Version in the title so a bug report or screenshot says which build it came
|
|
10
10
|
// from without anyone having to go look it up.
|
|
11
11
|
const CONFIG_TITLE = `pi-task ${readPkgVersion()} settings`;
|
|
@@ -253,16 +253,22 @@ export const ITEMS = [
|
|
|
253
253
|
* can tell an extension toggle from a PiTaskConfig field.
|
|
254
254
|
*/
|
|
255
255
|
const EXT_ID_PREFIX = 'ext:';
|
|
256
|
-
export function extensionItems(extensions
|
|
256
|
+
export function extensionItems(extensions) {
|
|
257
257
|
return extensions.map(e => ({
|
|
258
258
|
id: EXT_ID_PREFIX + e.path,
|
|
259
|
+
section: 'extensions',
|
|
259
260
|
label: `ext: ${e.label}`,
|
|
260
261
|
description: `Load this ${e.origin} extension in the helper sessions pi-task spawns. They run `
|
|
261
262
|
+ 'with extensions off by default, so turn this on when the extension provides the '
|
|
262
263
|
+ 'model they need (pi-lmstudio, for example). They also inherit its tools and '
|
|
263
264
|
+ `hooks, so only enable ones you trust. ${e.path}`,
|
|
264
|
-
|
|
265
|
-
|
|
265
|
+
values: ['on', 'off'],
|
|
266
|
+
format: cfg => (cfg.extensionWhitelist.includes(e.path) ? 'on' : 'off'),
|
|
267
|
+
apply: (cfg, chosen) => {
|
|
268
|
+
if (chosen !== 'on' && chosen !== 'off')
|
|
269
|
+
return;
|
|
270
|
+
cfg.extensionWhitelist = applyExtensionToggle(cfg.extensionWhitelist, e.path, chosen === 'on');
|
|
271
|
+
}
|
|
266
272
|
}));
|
|
267
273
|
}
|
|
268
274
|
/** Apply an extension toggle to the config's whitelist (idempotent both ways). */
|
|
@@ -280,18 +286,24 @@ export function applyExtensionToggle(whitelist, entryPath, on) {
|
|
|
280
286
|
* the config matches nothing.
|
|
281
287
|
*/
|
|
282
288
|
const TOOL_ID_PREFIX = 'tool:';
|
|
283
|
-
export function toolItems(tools
|
|
289
|
+
export function toolItems(tools) {
|
|
284
290
|
return tools.map(t => ({
|
|
285
291
|
id: TOOL_ID_PREFIX + t.name,
|
|
292
|
+
section: 'timeouts',
|
|
286
293
|
label: `watch: ${t.name}`,
|
|
287
294
|
description: `Apply the command timeout to this tool. Leave it on unless the tool runs its own `
|
|
288
295
|
+ `bounded, cancellable work for longer than the timeout — turning it off means a `
|
|
289
296
|
+ `genuine hang in this tool will never be caught, and nothing else is watching `
|
|
290
297
|
+ `while a tool runs. ${t.origin}`,
|
|
298
|
+
values: ['on', 'off'],
|
|
291
299
|
// Stored inverted: the config records the EXEMPTIONS, so an empty list
|
|
292
300
|
// (and any tool pi-task has never heard of) stays guarded by default.
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
format: cfg => (cfg.commandTimeoutExemptTools.includes(t.name) ? 'off' : 'on'),
|
|
302
|
+
apply: (cfg, chosen) => {
|
|
303
|
+
if (chosen !== 'on' && chosen !== 'off')
|
|
304
|
+
return;
|
|
305
|
+
cfg.commandTimeoutExemptTools = applyToolToggle(cfg.commandTimeoutExemptTools, t.name, chosen === 'on');
|
|
306
|
+
}
|
|
295
307
|
}));
|
|
296
308
|
}
|
|
297
309
|
/** Apply a per-tool watchdog toggle to the exemption list (idempotent both ways). */
|
|
@@ -339,17 +351,20 @@ export function reasoningRowLabel(group) {
|
|
|
339
351
|
const nextIsSibling = REASONING_GROUPS[REASONING_GROUPS.indexOf(group) + 1]?.startsWith(`${parent}:`);
|
|
340
352
|
return ` ${nextIsSibling ? '├─' : '└─'} ${group.slice(colon + 1)}`;
|
|
341
353
|
}
|
|
342
|
-
export function reasoningItems(
|
|
354
|
+
export function reasoningItems() {
|
|
343
355
|
return REASONING_GROUPS.map(group => ({
|
|
344
356
|
id: REASON_ID_PREFIX + group,
|
|
357
|
+
section: 'reasoning',
|
|
345
358
|
label: reasoningRowLabel(group),
|
|
346
359
|
headlessLabel: `think: ${group}`,
|
|
347
360
|
description: REASONING_GROUP_HELP[group],
|
|
361
|
+
values: [...REASONING_SETTINGS],
|
|
348
362
|
// The EFFECTIVE level, not cfg.reasoningLevels[group]: in default/on/off
|
|
349
363
|
// the stored table is not what runs, and a row that shows a value the
|
|
350
|
-
// run does not use is worse than no row.
|
|
351
|
-
|
|
352
|
-
|
|
364
|
+
// run does not use is worse than no row. As a FUNCTION rather than a
|
|
365
|
+
// snapshot, so `syncRows` can re-ask after any change.
|
|
366
|
+
format: cfg => resolveReasoning(group, cfg),
|
|
367
|
+
apply: (cfg, chosen) => applyReasoningLevel(cfg, group, chosen)
|
|
353
368
|
}));
|
|
354
369
|
}
|
|
355
370
|
/**
|
|
@@ -362,29 +377,13 @@ export function reasoningItems(cfg) {
|
|
|
362
377
|
* it, nudging `research` while in `off` would silently return the other six to
|
|
363
378
|
* whatever the stored table happened to hold.
|
|
364
379
|
*/
|
|
365
|
-
/**
|
|
366
|
-
* Write every `think:` row's displayed value back from the config.
|
|
367
|
-
*
|
|
368
|
-
* Called after ANY change, not just a reasoning one, because the mode row and
|
|
369
|
-
* the seven group rows are one control split across eight lines: cycling
|
|
370
|
-
* `reasoning` to `off` changes what all seven of them run at, and cycling one
|
|
371
|
-
* group row flips the mode, which changes the other six. A row showing a level
|
|
372
|
-
* the run will not use is worse than no row.
|
|
373
|
-
*/
|
|
374
|
-
export function refreshReasoningRows(cfg, list) {
|
|
375
|
-
for (const group of REASONING_GROUPS) {
|
|
376
|
-
list.updateValue(REASON_ID_PREFIX + group, resolveReasoning(group, cfg));
|
|
377
|
-
}
|
|
378
|
-
list.updateValue('reasoningMode', cfg.reasoningMode);
|
|
379
|
-
}
|
|
380
380
|
export function applyReasoningLevel(cfg, group, chosen) {
|
|
381
381
|
if (!REASONING_SETTINGS.includes(chosen))
|
|
382
382
|
return;
|
|
383
383
|
if (cfg.reasoningMode !== 'custom') {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
cfg.reasoningLevels = seeded;
|
|
384
|
+
// Freeze the table exactly as it runs today, then switch to custom, so
|
|
385
|
+
// opening one row cannot silently move the other six.
|
|
386
|
+
cfg.reasoningLevels = effectiveReasoning(cfg);
|
|
388
387
|
cfg.reasoningMode = 'custom';
|
|
389
388
|
}
|
|
390
389
|
cfg.reasoningLevels = { ...cfg.reasoningLevels, [group]: chosen };
|
|
@@ -518,38 +517,70 @@ onChange, onCancel) {
|
|
|
518
517
|
const list = new SettingsList(items, MAX_VISIBLE, makeTheme(theme, label => headerLabels.has(label.trimEnd())), (id, newValue) => onChange(id, newValue, list), onCancel);
|
|
519
518
|
return new BorderedBox(new SkipInertRows(list, items.map(i => (i.values?.length ?? 0) > 0)), CONFIG_TITLE, s => theme.fg('borderMuted', s), s => theme.fg('accent', theme.bold(s)), settingsBodyHeight(items.map(i => i.description), MAX_VISIBLE, OVERLAY_WIDTH - 8));
|
|
520
519
|
}
|
|
521
|
-
/**
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Every settings row for this session, fixed and DISCOVERED, in menu order.
|
|
522
|
+
*
|
|
523
|
+
* One list of `ConfigItem`s — so a row's display, its write-back and its section
|
|
524
|
+
* are one object, the dispatch is a lookup by id, and the round-trip properties
|
|
525
|
+
* in `config-items.test.ts` cover the reasoning, tool and extension families
|
|
526
|
+
* that used to bypass the row type entirely.
|
|
527
|
+
*
|
|
528
|
+
* Fixed rows come before discovered ones within a section, which is the order
|
|
529
|
+
* the hand-written `extra` map produced.
|
|
530
|
+
*/
|
|
531
|
+
export function configRows(installed, tools = []) {
|
|
532
|
+
// The discovered rows carry a section like every other row — the per-tool
|
|
533
|
+
// watchdog exemptions under `timeouts` (they are exemptions FROM that
|
|
534
|
+
// timeout), and the per-extension toggles under their own heading.
|
|
535
|
+
return [...ITEMS, ...reasoningItems(), ...toolItems(tools), ...extensionItems(installed)];
|
|
536
|
+
}
|
|
537
|
+
/** Render `rows` for the current config, grouped under their section headers. */
|
|
538
|
+
export function renderRows(cfg, rows) {
|
|
531
539
|
const out = [];
|
|
532
540
|
for (const { key, title } of SECTIONS) {
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
...(
|
|
542
|
-
|
|
541
|
+
const inSection = rows
|
|
542
|
+
.filter(i => i.section === key)
|
|
543
|
+
.map(i => ({
|
|
544
|
+
id: i.id,
|
|
545
|
+
label: i.label,
|
|
546
|
+
description: i.description,
|
|
547
|
+
currentValue: i.format(cfg),
|
|
548
|
+
values: i.values ?? ['on', 'off'],
|
|
549
|
+
...(i.headlessLabel === undefined ? {} : { headlessLabel: i.headlessLabel })
|
|
550
|
+
}));
|
|
543
551
|
// An empty section prints no header. `extensions` has no fixed rows at
|
|
544
552
|
// all, so with nothing installed the heading would otherwise sit alone.
|
|
545
|
-
if (
|
|
553
|
+
if (inSection.length === 0)
|
|
546
554
|
continue;
|
|
547
555
|
if (out.length > 0)
|
|
548
556
|
out.push(sectionGap(title));
|
|
549
|
-
out.push(sectionHeader(title), ...
|
|
557
|
+
out.push(sectionHeader(title), ...inSection);
|
|
550
558
|
}
|
|
551
559
|
return out;
|
|
552
560
|
}
|
|
561
|
+
/** The full settings row list for the current config, in menu order. */
|
|
562
|
+
export function panelItems(cfg, installed, tools = []) {
|
|
563
|
+
return renderRows(cfg, configRows(installed, tools));
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Re-ask every row what it now displays, and write the answers back.
|
|
567
|
+
*
|
|
568
|
+
* A row's `currentValue` in the live list is a snapshot taken when the panel was
|
|
569
|
+
* built, and rows describe each other: cycling `reasoning` to `off` changes what
|
|
570
|
+
* all seven `think:` rows run at, and cycling one group row flips the mode,
|
|
571
|
+
* which changes the other six.
|
|
572
|
+
*
|
|
573
|
+
* This runs after ANY change, over EVERY row. Its predecessor,
|
|
574
|
+
* `refreshReasoningRows`, ran after any change too — but hard-coded the seven
|
|
575
|
+
* reasoning ids plus `reasoningMode` and touched none of the other ~30 rows, so
|
|
576
|
+
* the next cross-row dependency would have needed a fifth function. Re-reading a
|
|
577
|
+
* `format` costs nothing, which is why there is still no list of "changes that
|
|
578
|
+
* need a refresh" to keep correct.
|
|
579
|
+
*/
|
|
580
|
+
export function syncRows(cfg, rows, list) {
|
|
581
|
+
for (const row of rows)
|
|
582
|
+
list.updateValue(row.id, row.format(cfg));
|
|
583
|
+
}
|
|
553
584
|
async function handleTaskConfig(_args, ctx, getTools = () => []) {
|
|
554
585
|
const cfg = {
|
|
555
586
|
...getConfig(),
|
|
@@ -585,34 +616,19 @@ async function handleTaskConfig(_args, ctx, getTools = () => []) {
|
|
|
585
616
|
ctx.ui.notify(lines.join(' | '), 'info');
|
|
586
617
|
return;
|
|
587
618
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
else {
|
|
603
|
-
// Every setting parses its own value. There is no generic
|
|
604
|
-
// fallback: the ladder this replaces ended in one that
|
|
605
|
-
// wrote `newValue === 'on'` into whatever field it was
|
|
606
|
-
// handed, so a new enum setting silently became a boolean
|
|
607
|
-
// until someone noticed.
|
|
608
|
-
ITEMS.find(item => item.id === id)?.apply(cfg, newValue);
|
|
609
|
-
}
|
|
610
|
-
// The reasoning rows describe each other, so they are
|
|
611
|
-
// re-read from the config after every change — including
|
|
612
|
-
// changes to unrelated rows, which costs nothing and means
|
|
613
|
-
// there is no list of "changes that need a refresh" to keep
|
|
614
|
-
// correct.
|
|
615
|
-
refreshReasoningRows(cfg, list);
|
|
619
|
+
// Built ONCE and shared by the renderer, the dispatch and the refresh, so
|
|
620
|
+
// all three necessarily agree about which rows exist.
|
|
621
|
+
const rows = configRows(installed, tools);
|
|
622
|
+
await ctx.ui.custom((_tui, theme, _kb, done) => createSettingsPanel(renderRows(cfg, rows), theme, (id, newValue, list) => {
|
|
623
|
+
// Every row parses its own value. There is no generic
|
|
624
|
+
// fallback and no prefix ladder: the ladder this replaces
|
|
625
|
+
// ended in one that wrote `newValue === 'on'` into whatever
|
|
626
|
+
// field it was handed, so a new enum setting silently became
|
|
627
|
+
// a boolean until someone noticed. A header row carries no
|
|
628
|
+
// `values`, so SettingsList never cycles it and it matches
|
|
629
|
+
// no row here anyway.
|
|
630
|
+
rows.find(row => row.id === id)?.apply(cfg, newValue);
|
|
631
|
+
syncRows(cfg, rows, list);
|
|
616
632
|
saveConfig(cfg).catch(() => { });
|
|
617
633
|
}, () => done(undefined)), { overlay: true, overlayOptions: { width: OVERLAY_WIDTH } });
|
|
618
634
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* transitive package to get twenty lines of arithmetic. SOURCE OF TRUTH is that
|
|
25
25
|
* module; `reasoning-capability.test.ts` is where a change upstream shows up.
|
|
26
26
|
*/
|
|
27
|
-
import type
|
|
27
|
+
import { type ReasoningGroup, type GroupSetting } from '../config/reasoning.js';
|
|
28
28
|
/**
|
|
29
29
|
* pi's own level ladder, in order. The order is the whole algorithm: an
|
|
30
30
|
* unsupported level is resolved by walking UP first, then down.
|
|
@@ -80,7 +80,4 @@ export interface ReasoningMismatch {
|
|
|
80
80
|
* about one direction while staying silent about the other would ship this
|
|
81
81
|
* feature with its own measured failure mode unreported.
|
|
82
82
|
*/
|
|
83
|
-
export declare function reasoningMismatches(model: ReasoningModelFacts | undefined,
|
|
84
|
-
group: ReasoningGroup;
|
|
85
|
-
setting: GroupSetting;
|
|
86
|
-
}>): ReasoningMismatch[];
|
|
83
|
+
export declare function reasoningMismatches(model: ReasoningModelFacts | undefined, levels: Readonly<Record<ReasoningGroup, GroupSetting>>): ReasoningMismatch[];
|