@mjasnikovs/pi-task 0.38.32 → 0.39.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 +2 -2
- package/dist/config/group-args.d.ts +24 -9
- package/dist/config/group-args.js +38 -28
- package/dist/config/option-picker.d.ts +39 -11
- package/dist/config/option-picker.js +52 -12
- package/dist/config/reasoning.d.ts +10 -7
- package/dist/config/reasoning.js +19 -32
- package/dist/config/register.d.ts +66 -41
- package/dist/config/register.js +201 -162
- package/dist/shared/child-process.d.ts +34 -32
- package/dist/shared/child-process.js +44 -58
- package/dist/shared/command-watchdog.d.ts +12 -4
- package/dist/shared/command-watchdog.js +6 -7
- package/dist/shared/connection-error.d.ts +7 -0
- package/dist/shared/connection-error.js +65 -0
- package/dist/shared/model-endpoint.d.ts +12 -24
- package/dist/shared/model-endpoint.js +32 -82
- package/dist/shared/model-resolve.d.ts +105 -0
- package/dist/shared/model-resolve.js +97 -0
- package/dist/shared/reasoning-capability.d.ts +20 -0
- package/dist/shared/reasoning-capability.js +32 -1
- package/dist/shared/stall-probe.d.ts +51 -0
- package/dist/shared/stall-probe.js +79 -0
- package/dist/task/child-runner.d.ts +76 -278
- package/dist/task/child-runner.js +186 -722
- package/dist/task/context-usage.js +2 -7
- package/dist/task/failure-classifier.js +53 -81
- package/dist/task/gate-child.js +1 -1
- package/dist/task/impl-widget.d.ts +2 -0
- package/dist/task/impl-widget.js +4 -0
- package/dist/task/implementation-hold.d.ts +11 -0
- package/dist/task/implementation-hold.js +20 -0
- package/dist/task/implementation-scope.d.ts +24 -0
- package/dist/task/implementation-scope.js +34 -0
- package/dist/task/loop-detector.d.ts +13 -5
- package/dist/task/loop-detector.js +11 -5
- package/dist/task/model-hold-stash.js +4 -14
- package/dist/task/orchestrator.d.ts +1 -8
- package/dist/task/orchestrator.js +11 -34
- package/dist/task/phases.js +2 -2
- package/dist/task/stall-detector.d.ts +1 -1
- package/dist/task/stall-detector.js +1 -1
- package/dist/workers/model-warning.d.ts +4 -16
- package/dist/workers/model-warning.js +14 -70
- package/dist/workers/pi-worker-core.d.ts +65 -20
- package/dist/workers/pi-worker-core.js +109 -50
- package/dist/workers/reasoning-warning.js +2 -24
- package/dist/workers/worker-failure.d.ts +2 -0
- package/dist/workers/worker-failure.js +2 -1
- package/dist/workers/worker-kill.d.ts +30 -11
- package/dist/workers/worker-kill.js +68 -20
- package/dist/workers/worker-profiles.d.ts +20 -0
- package/dist/workers/worker-profiles.js +22 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -214,8 +214,8 @@ Run `/task-config` to toggle pi-task's behavior in an editor dialog. Settings pe
|
|
|
214
214
|
| **command timeout** | 15 min | Wall-clock ceiling on a **single** tool execution. Local models routinely run a command that never returns (a hung build, a dev server, a check with no timeout) and the run wedges until you abort by hand — pi's bash tool has an optional timeout with no default, so this is the missing one. One knob, two surfaces: in the main session the overrun call is cancelled (killing the tool's whole process tree) plus a reminder turn; in the verify/fix gate children the child is killed and re-spawned with a hint, halving the ceiling on repeat hangs. Choices: 5/10/15/30 min or **off** — off unguards both surfaces, gates included. |
|
|
215
215
|
| **stuck reply retry** | 10 min | Inactivity ceiling on the **model stream**. A hung or silently-dropped stream throws nothing at all, so neither the connection-error retry (it needs a reported error) nor the **command timeout** (tool calls only) nor the dead-backend stall guard (a reachable endpoint reads as proof of life) can see it — an mx5 run lost ~2.9h to three of them while the model server stayed healthy. Measured as time since the **last stream event of any kind**, so a slow model emitting one token every 30s is never touched, and it pauses while a tool runs. On expiry the main session aborts the turn (through the same channel the command watchdog uses) and posts a resume reminder; a child is killed and routed into the existing connection-error retry. Choices: 5/10/20/30 min or **off**. Keep it generous on local backends — prompt processing on a large context legitimately emits nothing for minutes. |
|
|
216
216
|
| **yolo mode** | off | **Unattended runs.** Wherever pi-task would stop and ask, it takes the option already marked RECOMMENDED, stamps the artifact `(YOLO)` so an audit can tell a machine decided, and shows no prompt at all — clarify/grill answers, the verify-FAIL picker (auto-**Accept**, recorded as a yolo debt), and the final-gate picker (autofix while the budget lasts, then leave the run FAILED). A question with no recommendation is **skipped**, never invented. For throwaway/test projects nobody is watching; a real run should decide these itself. |
|
|
217
|
-
| **
|
|
218
|
-
| **
|
|
217
|
+
| **profile** | default | How much the helper sessions think, in one word, for every step at once. Local models differ sharply here: some break without reasoning, some waste minutes with it, and some cannot do it at all. **default** uses the per-step table pi-task has measured, **on** and **off** force one answer everywhere and ignore that table, and **custom** is whatever the step rows say — changing any of them switches this to custom. A step on **inherit** passes no flag at all, so it uses whatever thinking level pi itself is set to, which is what every step did before this setting existed. |
|
|
218
|
+
| **steps: …** | all `inherit` | One row per group of steps, carrying BOTH dials: the model those children run on and the level they think at, shown as `level · model`. Enter walks a two-step picker — model first, then level — and **the level step offers only what that model declares, opening on the one that will actually run**. That is the whole point of the merge: pi silently CLAMPS a level a model cannot do (a level you set can be erased, and an `off` can be clamped back up to `medium`), so instead of discovering that later you watch the cursor land on the level you are really getting. Models are offered from `pi.modelRegistry.getAvailable()` and stored as the canonical `provider/id` that pi's own `--model` takes. **inherit** on the model half emits no flag, so an all-inherit table is byte-identical to a build without this feature; that is the shipped default, because which models exist is a property of your machine and nothing here can be measured for you. A stored model this machine cannot resolve is never erased (you may have set it on another machine): the flag is dropped, the step runs on pi's default, and a startup hint names the step. Two need care — a provider registered by a host **extension** needs that extension enabled under **ext: …** or those children exit 1; and **implementation** is not free, because it is *your* session moved for the turn and moved back, and a model switch re-bills the whole prompt as a cache miss, twice per task. |
|
|
219
219
|
| **debug logs** | events | How much of a run is written to `.pi-tasks/*-debug.log`. **`events`** keeps decisions and guard actions — which phase ran, why a worker was retried, what the git-state guard restored, what a write-capable child changed on disk, why a gate returned FAIL — a few lines per task. **`full`** adds every line the child model emitted and every tool result; that's ~85% of the bytes (a real 247 KB `verify-debug.log` is 1315 lines, 521 of them tool dumps) and is what you want while actively debugging. **`off`** writes nothing. Nothing in pi-task ever reads these files back, so the setting cannot change how a run behaves — only whether you can explain it afterwards, and a log not written can't be recovered later. |
|
|
220
220
|
| **watch: …** | all on | One toggle per tool in the live session, deciding whether **command timeout** applies to it. The list is discovered from `pi.getAllTools()` when the menu opens — built-ins first, then each extension's tools with the owning entry-point path in the description — so nothing is typed by hand and an uninstalled tool just stops being listed. Turn one **off** only for a tool that already owns a longer bounded, cancellable contract of its own (the guard exists because pi's `bash` has an optional timeout with *no* default — that reasoning doesn't transfer to a tool that has one). Two things to know before you do: a genuine hang in an unwatched tool is caught by nothing, since **stuck reply retry** is paused for the whole time any tool runs; and an unwatched tool is still killed as collateral if a *watched* sibling in the same turn overruns, because pi runs sibling tool calls concurrently and the abort ends the whole turn. Stored as exemptions, so the default and every tool pi-task has never seen stay guarded. |
|
|
221
221
|
| **ext: …** | all off | One toggle per installed host `pi` extension, loading it into every child session by explicit path. Children otherwise run with extensions off, so a provider registered by an extension (e.g. `pi-lmstudio`) doesn't exist in them and they can't resolve the default model. Children also inherit the extension's tools and hooks, so only enable ones you trust. The list is strictly additive (discovery stays off), and an entry whose file is gone is skipped at spawn time, never fatal. |
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The live-config bridge for per-group child settings: group in, argv fragment
|
|
2
|
+
* The live-config bridge for per-group child settings: group in, argv fragment
|
|
3
|
+
* out — plus the session's model snapshot, which the argv needs and which only a
|
|
4
|
+
* session_start with a `ctx` can produce.
|
|
3
5
|
*
|
|
4
6
|
* Separate from reasoning.ts and group-models.ts because those must take no
|
|
5
7
|
* import with a runtime side effect — see their headers. The `getConfig()` read
|
|
6
8
|
* lives here instead: this file imports them and nothing in config/ imports it
|
|
7
9
|
* back, so the graph stays a tree.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
* the next child without a restart. Same contract
|
|
11
|
+
* Config is read PER CALL, never cached at module scope, so a /task-config
|
|
12
|
+
* change lands on the next child without a restart. Same contract
|
|
13
|
+
* `childBaseArgs` keeps. The snapshot is the one deliberate exception, and it is
|
|
14
|
+
* a snapshot of the REGISTRY, not of config.
|
|
11
15
|
*/
|
|
12
16
|
import { type PiTaskConfig } from './config.js';
|
|
13
17
|
import type { ChildGroup } from './groups.js';
|
|
14
|
-
|
|
15
|
-
export declare function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
import type { GroupModelSnapshot } from '../shared/model-resolve.js';
|
|
19
|
+
export declare function setGroupModels(snapshot: Readonly<Partial<Record<ChildGroup, GroupModelSnapshot>>>): void;
|
|
20
|
+
/**
|
|
21
|
+
* The group's own window, or `undefined` for "caller keeps its fallback".
|
|
22
|
+
*
|
|
23
|
+
* The number drives `StallDetector`'s churn rule, where the two error directions
|
|
24
|
+
* are NOT symmetric: too large fires late (degraded, and the no-new-ground rule
|
|
25
|
+
* still covers it), too small fires early and KILLS A HEALTHY CHILD. So an
|
|
26
|
+
* absent answer means "use the parent's", never a guess.
|
|
27
|
+
*/
|
|
18
28
|
export declare function groupWindow(group: ChildGroup): number | undefined;
|
|
29
|
+
export declare function setModelEndpoints(endpoints: ReadonlyMap<string, string>): void;
|
|
30
|
+
export declare function modelEndpoint(spec: string): string | undefined;
|
|
19
31
|
/**
|
|
20
32
|
* The `['--model', spec]` fragment for a group, or `[]` for `inherit` and for a
|
|
21
33
|
* spec this session proved unresolvable.
|
|
@@ -26,14 +38,17 @@ export declare function groupWindow(group: ChildGroup): number | undefined;
|
|
|
26
38
|
* `reasoning: true` onto it, inherits the provider's default baseUrl and answers
|
|
27
39
|
* at exit 0. Dropping the flag runs the same child the user got last week and
|
|
28
40
|
* says so out loud; passing it runs a model nobody chose and says nothing.
|
|
41
|
+
*
|
|
42
|
+
* The verdict applies to the spec it was proven on: a cell changed since
|
|
43
|
+
* session_start is emitted, and pi decides.
|
|
29
44
|
*/
|
|
30
45
|
export declare function groupModelArgs(group: ChildGroup, cfg?: PiTaskConfig): string[];
|
|
31
46
|
/**
|
|
32
47
|
* The `['--thinking', level]` fragment for a group, or `[]` when the group is
|
|
33
48
|
* `inherit` and the child should keep falling back to settings.json.
|
|
34
49
|
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
50
|
+
* Exported for its own tests only: the per-call config read is a contract on
|
|
51
|
+
* each half, and only the half on its own can assert it.
|
|
37
52
|
*/
|
|
38
53
|
export declare function groupThinkingArgs(group: ChildGroup, cfg?: PiTaskConfig): string[];
|
|
39
54
|
/**
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The live-config bridge for per-group child settings: group in, argv fragment
|
|
2
|
+
* The live-config bridge for per-group child settings: group in, argv fragment
|
|
3
|
+
* out — plus the session's model snapshot, which the argv needs and which only a
|
|
4
|
+
* session_start with a `ctx` can produce.
|
|
3
5
|
*
|
|
4
6
|
* Separate from reasoning.ts and group-models.ts because those must take no
|
|
5
7
|
* import with a runtime side effect — see their headers. The `getConfig()` read
|
|
6
8
|
* lives here instead: this file imports them and nothing in config/ imports it
|
|
7
9
|
* back, so the graph stays a tree.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
* the next child without a restart. Same contract
|
|
11
|
+
* Config is read PER CALL, never cached at module scope, so a /task-config
|
|
12
|
+
* change lands on the next child without a restart. Same contract
|
|
13
|
+
* `childBaseArgs` keeps. The snapshot is the one deliberate exception, and it is
|
|
14
|
+
* a snapshot of the REGISTRY, not of config.
|
|
11
15
|
*/
|
|
12
16
|
import { getConfig } from './config.js';
|
|
13
17
|
import { MODEL_INHERIT, modelArgs } from './group-models.js';
|
|
14
18
|
import { resolveReasoning, thinkingArgs } from './reasoning.js';
|
|
15
19
|
/**
|
|
16
|
-
*
|
|
20
|
+
* What this session resolved each group's model cell to.
|
|
17
21
|
*
|
|
18
|
-
* WHY A SESSION-SCOPED
|
|
19
|
-
*
|
|
22
|
+
* WHY A SESSION-SCOPED SNAPSHOT AND NOT A LOOKUP
|
|
23
|
+
* ----------------------------------------------
|
|
20
24
|
* The honest question is "can a `--no-extensions` child resolve this spec?", and
|
|
21
25
|
* only `ctx.modelRegistry` can answer it. Five of the six argv producers have no
|
|
22
26
|
* `ctx` — `pi-worker`, `pi-worker-docs`, `docs-core`, `fetch-core` and
|
|
@@ -26,41 +30,43 @@ import { resolveReasoning, thinkingArgs } from './reasoning.js';
|
|
|
26
30
|
* this project does not depend on pi-ai.
|
|
27
31
|
*
|
|
28
32
|
* So it is answered ONCE, at session_start, where ctx exists and every task is
|
|
29
|
-
* still in the future, and the verdict is left here
|
|
33
|
+
* still in the future, and the whole verdict is left here — usability and the
|
|
34
|
+
* context window from ONE walk, so the argv and the churn rule can never
|
|
35
|
+
* disagree about which model a group runs on.
|
|
30
36
|
*
|
|
31
37
|
* EMPTY MEANS EMIT. A host that never fires session_start therefore behaves
|
|
32
38
|
* exactly as it does today — the failure direction is "pi decides", never "we
|
|
33
39
|
* silently dropped a flag nobody checked".
|
|
34
40
|
*/
|
|
35
|
-
let
|
|
36
|
-
export function
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
export function isSpecUsable(spec) {
|
|
40
|
-
return !unusableSpecs.has(spec);
|
|
41
|
+
let groupModels = {};
|
|
42
|
+
export function setGroupModels(snapshot) {
|
|
43
|
+
groupModels = { ...snapshot };
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
|
-
* The
|
|
44
|
-
* that filled {@link setUnusableSpecs}.
|
|
45
|
-
*
|
|
46
|
-
* It lives here for the same reason that set does — `child-runner` and the
|
|
47
|
-
* workers have no `ctx`, so they cannot ask a registry — and it is filled by the
|
|
48
|
-
* same walk, so the two can never disagree about which model a group runs on.
|
|
46
|
+
* The group's own window, or `undefined` for "caller keeps its fallback".
|
|
49
47
|
*
|
|
50
48
|
* The number drives `StallDetector`'s churn rule, where the two error directions
|
|
51
49
|
* are NOT symmetric: too large fires late (degraded, and the no-new-ground rule
|
|
52
50
|
* still covers it), too small fires early and KILLS A HEALTHY CHILD. So an
|
|
53
51
|
* absent answer means "use the parent's", never a guess.
|
|
54
52
|
*/
|
|
55
|
-
let groupWindows = {};
|
|
56
|
-
export function setGroupWindows(windows) {
|
|
57
|
-
groupWindows = { ...windows };
|
|
58
|
-
}
|
|
59
|
-
/** The group's own window, or `undefined` for "caller keeps its fallback". */
|
|
60
53
|
export function groupWindow(group) {
|
|
61
|
-
const w =
|
|
54
|
+
const w = groupModels[group]?.contextWindow;
|
|
62
55
|
return w !== undefined && w > 0 ? w : undefined;
|
|
63
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* `spec → baseUrl` for every model the session can use, from the same
|
|
59
|
+
* session_start pass. Read by the dead-backend probe (shared/model-endpoint.ts),
|
|
60
|
+
* which runs where no `ctx` exists. Empty until a session starts, and the probe
|
|
61
|
+
* reads "no url" as "cannot see this server, so never kill".
|
|
62
|
+
*/
|
|
63
|
+
let modelEndpoints = new Map();
|
|
64
|
+
export function setModelEndpoints(endpoints) {
|
|
65
|
+
modelEndpoints = new Map(endpoints);
|
|
66
|
+
}
|
|
67
|
+
export function modelEndpoint(spec) {
|
|
68
|
+
return modelEndpoints.get(spec);
|
|
69
|
+
}
|
|
64
70
|
/**
|
|
65
71
|
* The `['--model', spec]` fragment for a group, or `[]` for `inherit` and for a
|
|
66
72
|
* spec this session proved unresolvable.
|
|
@@ -71,19 +77,23 @@ export function groupWindow(group) {
|
|
|
71
77
|
* `reasoning: true` onto it, inherits the provider's default baseUrl and answers
|
|
72
78
|
* at exit 0. Dropping the flag runs the same child the user got last week and
|
|
73
79
|
* says so out loud; passing it runs a model nobody chose and says nothing.
|
|
80
|
+
*
|
|
81
|
+
* The verdict applies to the spec it was proven on: a cell changed since
|
|
82
|
+
* session_start is emitted, and pi decides.
|
|
74
83
|
*/
|
|
75
84
|
export function groupModelArgs(group, cfg) {
|
|
76
85
|
const spec = (cfg ?? getConfig()).groupModels[group];
|
|
77
86
|
if (spec === undefined || spec === MODEL_INHERIT)
|
|
78
87
|
return [];
|
|
79
|
-
|
|
88
|
+
const proven = groupModels[group];
|
|
89
|
+
return proven?.spec === spec && !proven.usable ? [] : modelArgs(spec);
|
|
80
90
|
}
|
|
81
91
|
/**
|
|
82
92
|
* The `['--thinking', level]` fragment for a group, or `[]` when the group is
|
|
83
93
|
* `inherit` and the child should keep falling back to settings.json.
|
|
84
94
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
95
|
+
* Exported for its own tests only: the per-call config read is a contract on
|
|
96
|
+
* each half, and only the half on its own can assert it.
|
|
87
97
|
*/
|
|
88
98
|
export function groupThinkingArgs(group, cfg) {
|
|
89
99
|
// The default is evaluated HERE, per call. Hoisting the read to module scope
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The two-step picker a /task-config step row opens on Enter.
|
|
3
3
|
*
|
|
4
4
|
* WHY A SUBMENU AND NOT `ctx.ui.select`
|
|
5
5
|
* -------------------------------------
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* in place of the list and delegates input to it. And its `done(v)` calls
|
|
10
10
|
* `onChange(id, v)` with exactly the returned value while `done(undefined)`
|
|
11
11
|
* writes nothing — the same one-value contract every cycling row uses, so the
|
|
12
|
-
* panel's dispatch needs no branch and a
|
|
13
|
-
*
|
|
12
|
+
* panel's dispatch needs no branch and a pair chosen out of two long lists costs
|
|
13
|
+
* ONE write.
|
|
14
14
|
*
|
|
15
15
|
* WHY NOT A BARE `SelectList`
|
|
16
16
|
* ---------------------------
|
|
@@ -26,17 +26,45 @@ import { Container } from '@earendil-works/pi-tui';
|
|
|
26
26
|
import type { SelectItem } from '@earendil-works/pi-tui';
|
|
27
27
|
import type { ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
|
|
28
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
|
+
}
|
|
29
50
|
/**
|
|
30
|
-
*
|
|
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.
|
|
31
56
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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`.
|
|
35
60
|
*/
|
|
36
|
-
export declare class
|
|
37
|
-
private readonly
|
|
38
|
-
private readonly
|
|
39
|
-
|
|
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;
|
|
40
68
|
handleInput(data: string): void;
|
|
41
69
|
}
|
|
42
70
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The two-step picker a /task-config step row opens on Enter.
|
|
3
3
|
*
|
|
4
4
|
* WHY A SUBMENU AND NOT `ctx.ui.select`
|
|
5
5
|
* -------------------------------------
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* in place of the list and delegates input to it. And its `done(v)` calls
|
|
10
10
|
* `onChange(id, v)` with exactly the returned value while `done(undefined)`
|
|
11
11
|
* writes nothing — the same one-value contract every cycling row uses, so the
|
|
12
|
-
* panel's dispatch needs no branch and a
|
|
13
|
-
*
|
|
12
|
+
* panel's dispatch needs no branch and a pair chosen out of two long lists costs
|
|
13
|
+
* ONE write.
|
|
14
14
|
*
|
|
15
15
|
* WHY NOT A BARE `SelectList`
|
|
16
16
|
* ---------------------------
|
|
@@ -35,23 +35,29 @@ function selectTheme(theme) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* A filterable
|
|
38
|
+
* A filterable list with a text filter above it.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
43
|
*/
|
|
44
|
-
|
|
44
|
+
class FilterList extends Container {
|
|
45
|
+
onPick;
|
|
46
|
+
onCancel;
|
|
45
47
|
input = new Input();
|
|
46
48
|
list;
|
|
47
|
-
constructor(options,
|
|
49
|
+
constructor(options, preselect, theme, onPick, onCancel) {
|
|
48
50
|
super();
|
|
51
|
+
this.onPick = onPick;
|
|
52
|
+
this.onCancel = onCancel;
|
|
49
53
|
this.list = new SelectList([...options], PICKER_VISIBLE, selectTheme(theme));
|
|
50
|
-
|
|
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);
|
|
51
57
|
if (at !== -1)
|
|
52
58
|
this.list.setSelectedIndex(at);
|
|
53
|
-
this.list.onSelect = item =>
|
|
54
|
-
this.list.onCancel = () =>
|
|
59
|
+
this.list.onSelect = item => this.onPick(item.value);
|
|
60
|
+
this.list.onCancel = () => this.onCancel();
|
|
55
61
|
this.addChild(this.input);
|
|
56
62
|
this.addChild(this.list);
|
|
57
63
|
}
|
|
@@ -71,3 +77,37 @@ export class OptionPicker extends Container {
|
|
|
71
77
|
this.list.setFilter(this.input.getValue());
|
|
72
78
|
}
|
|
73
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
|
+
}
|
|
@@ -104,13 +104,16 @@ export declare function effectiveReasoning(cfg: PiTaskConfig): Record<ChildGroup
|
|
|
104
104
|
* version before this feature existed.
|
|
105
105
|
*/
|
|
106
106
|
export declare function thinkingArgs(setting: GroupSetting): string[];
|
|
107
|
-
/** One honest sentence per group, for the /task-config rows. */
|
|
108
|
-
export declare const REASONING_GROUP_HELP: Readonly<Record<ChildGroup, string>>;
|
|
109
107
|
/**
|
|
110
|
-
* One honest sentence per
|
|
108
|
+
* One honest sentence per step, for the merged /task-config rows.
|
|
111
109
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
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.
|
|
115
118
|
*/
|
|
116
|
-
export declare const
|
|
119
|
+
export declare const STEP_GROUP_HELP: Readonly<Record<ChildGroup, string>>;
|
package/dist/config/reasoning.js
CHANGED
|
@@ -105,12 +105,23 @@ export function effectiveReasoning(cfg) {
|
|
|
105
105
|
export function thinkingArgs(setting) {
|
|
106
106
|
return setting === 'inherit' ? [] : ['--thinking', setting];
|
|
107
107
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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.',
|
|
112
123
|
'research:files': 'Research worker 1 of 4: maps which files the task will touch. Read-heavy. '
|
|
113
|
-
+ 'Measured: the two arms tie, so it runs without
|
|
124
|
+
+ 'Measured: the two thinking arms tie, so it runs without.',
|
|
114
125
|
'research:apis': 'Research worker 2 of 4: the symbols and signatures the task must call. '
|
|
115
126
|
+ 'Read-heavy, docs- and search-capable.',
|
|
116
127
|
'research:context': 'Research worker 3 of 4: how the project is put together. One of the two that '
|
|
@@ -125,31 +136,7 @@ export const REASONING_GROUP_HELP = {
|
|
|
125
136
|
gate: 'The checks that run after code is written: verify, enforce, lint-fix, autofix.',
|
|
126
137
|
extraction: 'The small no-tools children that pull one answer out of a fetched page or '
|
|
127
138
|
+ 'a docs chunk.',
|
|
128
|
-
implementation: 'The main session turn that
|
|
129
|
-
+
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* One honest sentence per model row.
|
|
133
|
-
*
|
|
134
|
-
* They are NOT the reasoning help reworded. A model cell answers a different
|
|
135
|
-
* question — which machine does this work — and one of them costs money every
|
|
136
|
-
* turn rather than once per change, which is a thing the row has to say.
|
|
137
|
-
*/
|
|
138
|
-
export const MODEL_GROUP_HELP = {
|
|
139
|
-
research: 'The pi-worker subagent tool, and the fallback for any research worker below. '
|
|
140
|
-
+ 'Long read-only loops: a cheap fast model pays off here.',
|
|
141
|
-
'research:files': 'Research worker 1 of 4: maps which files the task will touch.',
|
|
142
|
-
'research:apis': 'Research worker 2 of 4: the symbols and signatures the task must call.',
|
|
143
|
-
'research:context': 'Research worker 3 of 4: how the project is put together.',
|
|
144
|
-
'research:tooling': 'Research worker 4 of 4: the commands that build, test and run it.',
|
|
145
|
-
phase: 'Refining your request, generating and answering the clarifying questions, '
|
|
146
|
-
+ 'writing the spec, and critiquing it.',
|
|
147
|
-
planning: "/task-auto's planners: splitting a design document into tasks.",
|
|
148
|
-
plan: "/task-plan's interactive question-and-answer children.",
|
|
149
|
-
gate: 'The checks that run after code is written: verify, enforce, lint-fix, autofix.',
|
|
150
|
-
extraction: 'The small no-tools children that pull one answer out of a page or a docs chunk.',
|
|
151
|
-
implementation: 'The main session turn that writes the code — YOUR session, switched for the turn and '
|
|
152
|
-
+ 'switched back. Unlike every row above, this one is not free: a model switch re-bills '
|
|
153
|
-
+ 'the whole prompt as a cache miss, twice per task. Leave it on inherit unless you '
|
|
154
|
-
+ 'want a different model than the one you are reading this in.'
|
|
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.'
|
|
155
142
|
};
|