@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.
Files changed (59) hide show
  1. package/README.md +2 -1
  2. package/dist/config/config.d.ts +16 -2
  3. package/dist/config/config.js +7 -2
  4. package/dist/config/group-args.d.ts +52 -0
  5. package/dist/config/group-args.js +110 -0
  6. package/dist/config/group-models.d.ts +88 -0
  7. package/dist/config/group-models.js +117 -0
  8. package/dist/config/groups.d.ts +76 -0
  9. package/dist/config/groups.js +110 -0
  10. package/dist/config/option-picker.d.ts +70 -0
  11. package/dist/config/option-picker.js +113 -0
  12. package/dist/config/reasoning.d.ts +26 -64
  13. package/dist/config/reasoning.js +31 -115
  14. package/dist/config/register.d.ts +144 -24
  15. package/dist/config/register.js +345 -56
  16. package/dist/index.js +2 -0
  17. package/dist/remote/push.js +1 -7
  18. package/dist/shared/data-home.d.ts +8 -0
  19. package/dist/shared/data-home.js +14 -0
  20. package/dist/shared/model-endpoint.d.ts +53 -0
  21. package/dist/shared/model-endpoint.js +98 -2
  22. package/dist/shared/reasoning-capability.d.ts +25 -5
  23. package/dist/shared/reasoning-capability.js +18 -9
  24. package/dist/task/child-runner.d.ts +19 -16
  25. package/dist/task/child-runner.js +64 -36
  26. package/dist/task/context-usage.d.ts +46 -0
  27. package/dist/task/context-usage.js +41 -0
  28. package/dist/task/gate-child.d.ts +15 -4
  29. package/dist/task/gate-child.js +2 -2
  30. package/dist/task/gate-deps.js +7 -2
  31. package/dist/task/implementation-hold.d.ts +118 -0
  32. package/dist/task/implementation-hold.js +165 -0
  33. package/dist/task/model-hold-stash.d.ts +43 -0
  34. package/dist/task/model-hold-stash.js +70 -0
  35. package/dist/task/orchestrator.d.ts +18 -5
  36. package/dist/task/orchestrator.js +36 -4
  37. package/dist/task/phases.js +2 -2
  38. package/dist/task/research-worker.d.ts +2 -2
  39. package/dist/task/research-worker.js +1 -1
  40. package/dist/workers/docs-core.js +2 -2
  41. package/dist/workers/docs-lookup.d.ts +4 -3
  42. package/dist/workers/docs-lookup.js +1 -1
  43. package/dist/workers/fetch-core.js +2 -2
  44. package/dist/workers/focused-extractor.d.ts +4 -3
  45. package/dist/workers/focused-extractor.js +5 -4
  46. package/dist/workers/index.js +2 -0
  47. package/dist/workers/model-warning.d.ts +69 -0
  48. package/dist/workers/model-warning.js +113 -0
  49. package/dist/workers/pi-worker-core.d.ts +7 -7
  50. package/dist/workers/pi-worker-core.js +4 -3
  51. package/dist/workers/pi-worker-docs.js +2 -2
  52. package/dist/workers/pi-worker.js +4 -4
  53. package/dist/workers/reasoning-warning.d.ts +17 -9
  54. package/dist/workers/reasoning-warning.js +69 -22
  55. package/package.json +1 -1
  56. package/dist/config/reasoning-args.d.ts +0 -23
  57. package/dist/config/reasoning-args.js +0 -28
  58. package/dist/task/implementation-thinking.d.ts +0 -56
  59. package/dist/task/implementation-thinking.js +0 -32
@@ -1,10 +1,12 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext } from '@earendil-works/pi-coding-agent';
2
2
  import { SettingsList } from '@earendil-works/pi-tui';
3
3
  import type { Component } from '@earendil-works/pi-tui';
4
+ import { type ReasoningModelFacts } from '../shared/reasoning-capability.js';
5
+ import { type PairOptions } from './option-picker.js';
4
6
  import { type PiTaskConfig } from './config.js';
5
7
  import { type InstalledExtension } from './extension-list.js';
6
8
  import { type GuardableTool } from './tool-list.js';
7
- import { type ReasoningGroup } from './reasoning.js';
9
+ import { type GroupSetting, type ChildGroup } from './reasoning.js';
8
10
  type Theme = ExtensionCommandContext['ui']['theme'];
9
11
  /**
10
12
  * Frames a child component (the settings list) in a rounded border with a title
@@ -54,7 +56,7 @@ declare class BorderedBox implements Component {
54
56
  export interface ConfigItem {
55
57
  /**
56
58
  * The row's id. A `keyof PiTaskConfig` for a fixed setting; a prefixed
57
- * string (`reason:`, `tool:`, `ext:`) for a DISCOVERED one.
59
+ * string (`step:`, `tool:`, `ext:`) for a DISCOVERED one.
58
60
  *
59
61
  * It is `string`, not `keyof PiTaskConfig`, and that is what lets the three
60
62
  * dynamic families BE rows instead of bypassing them — so the round-trip
@@ -73,6 +75,17 @@ export interface ConfigItem {
73
75
  description: string;
74
76
  /** Offered values. Omitted for a boolean, which is always on/off. */
75
77
  values?: string[];
78
+ /**
79
+ * Rows whose choice is a LIST, not a cycle. Present ⇒ Enter opens a picker
80
+ * instead of stepping `values`.
81
+ *
82
+ * A FUNCTION of the draft config, evaluated at Enter-time, because what a row
83
+ * may offer can depend on another row the user changed while the panel was
84
+ * open: a group's thinking options narrow to what its chosen model declares.
85
+ * `values` stays the STATIC complete vocabulary, because that is what the
86
+ * round-trip property in config-items.test.ts quantifies over.
87
+ */
88
+ picker?: (cfg: PiTaskConfig) => PairOptions;
76
89
  /** What the panel shows for the current value. */
77
90
  format: (cfg: PiTaskConfig) => string;
78
91
  /** Write the chosen label back. A value it does not recognise is ignored. */
@@ -92,7 +105,7 @@ export interface ConfigItem {
92
105
  * exemptions from it) end up separated by rows that have nothing to do with
93
106
  * them. The headers are inert rows: no `values`, so Enter does nothing on them.
94
107
  */
95
- export type Section = 'session' | 'checks' | 'research' | 'reasoning' | 'timeouts' | 'unattended' | 'logging' | 'extensions';
108
+ export type Section = 'session' | 'checks' | 'research' | 'profile' | 'reasoning' | 'timeouts' | 'unattended' | 'logging' | 'extensions';
96
109
  /** Section order, and the label each header renders. */
97
110
  export declare const SECTIONS: ReadonlyArray<{
98
111
  key: Section;
@@ -100,6 +113,18 @@ export declare const SECTIONS: ReadonlyArray<{
100
113
  }>;
101
114
  /** Marks a header row, so onChange can ignore one and tests can find them. */
102
115
  export declare const SECTION_ID_PREFIX = "section:";
116
+ /**
117
+ * Is this row scenery rather than a setting?
118
+ *
119
+ * Reads the ID, not `values`. Those two agreed only while every real row cycled
120
+ * a list: a picker row has a `submenu` and may carry `values` purely for the
121
+ * round-trip contract, so "no values" stopped meaning "not a row". Both
122
+ * `sectionHeader` and `sectionGap` already stamp the prefix, so this is exact
123
+ * rather than a heuristic, and all three consumers ask the same question.
124
+ */
125
+ export declare const isSectionRow: (item: {
126
+ id: string;
127
+ }) => boolean;
103
128
  /**
104
129
  * Every setting rendered by /task-config, in display order.
105
130
  *
@@ -114,7 +139,7 @@ export declare function toolItems(tools: readonly GuardableTool[]): ConfigItem[]
114
139
  /** Apply a per-tool watchdog toggle to the exemption list (idempotent both ways). */
115
140
  export declare function applyToolToggle(exempt: readonly string[], toolName: string, watched: boolean): string[];
116
141
  /**
117
- * The label for one `think:` row.
142
+ * The label for one step row.
118
143
  *
119
144
  * A group whose name carries a colon is a CHILD of the group before the colon —
120
145
  * `research:files` is one of the four workers `research` fans out to. Rendered
@@ -123,15 +148,95 @@ export declare function applyToolToggle(exempt: readonly string[], toolName: str
123
148
  * parent is also a row.
124
149
  *
125
150
  * So a child is drawn as a tree branch under its parent and loses the repeated
126
- * `think: research:` prefix, the same text at the head of four consecutive
127
- * lines. `└─` on the last child, `├─` on the rest, decided from the group's
128
- * position in {@link REASONING_GROUPS} rather than a hand-kept list — adding a
129
- * fifth worker moves the corner on its own.
151
+ * `research:` prefix, the same text at the head of four consecutive lines. `└─`
152
+ * on the last child, `├─` on the rest, decided from the group's position in
153
+ * {@link CHILD_GROUPS} rather than a hand-kept list — adding a fifth worker
154
+ * moves the corner on its own.
155
+ *
156
+ * A parentless group is its own bare name. It carried a `think: ` prefix while
157
+ * there were two families to tell apart; with one row per step there is nothing
158
+ * to disambiguate, and the prefix was the widest thing in the column.
130
159
  *
131
160
  * Leading spaces survive: SettingsList pads the label right, never trims it.
132
161
  */
133
- export declare function reasoningRowLabel(group: ReasoningGroup): string;
134
- export declare function reasoningItems(): ConfigItem[];
162
+ export declare function stepRowLabel(group: ChildGroup): string;
163
+ /**
164
+ * What this machine can offer a model row, and what each offer can DO.
165
+ *
166
+ * `facts` is what lets the thinking rows narrow: a group pinned to a
167
+ * `reasoning: false` model may only be offered `off`. It answers `undefined` for
168
+ * a spec it cannot resolve — a vanished model, or `inherit` before a session
169
+ * exists — and every consumer reads that as "offer everything", which is exactly
170
+ * today's behaviour.
171
+ */
172
+ export interface ModelCatalog {
173
+ /** Offerable `provider/id` specs, without `inherit`. */
174
+ specs: readonly string[];
175
+ /** Human note per spec, e.g. an extension-provided provider's warning. */
176
+ note?: (spec: string) => string | undefined;
177
+ facts: (spec: string) => ReasoningModelFacts | undefined;
178
+ }
179
+ /** No registry reachable. Every row still renders; nothing narrows. */
180
+ export declare const EMPTY_CATALOG: ModelCatalog;
181
+ /**
182
+ * The levels a row may offer, given the model that row's group will run on.
183
+ *
184
+ * The INTERSECTION with `REASONING_SETTINGS`, not `supportedThinkingLevels`
185
+ * directly: that returns the whole ladder including `xhigh` and `max`, which
186
+ * this menu excludes on purpose (see reasoning.ts) because pi's own UI may not
187
+ * offer them. A model declaring `xhigh` must not smuggle it in here.
188
+ */
189
+ export declare function offeredLevels(facts: ReasoningModelFacts | undefined): GroupSetting[];
190
+ /**
191
+ * `level · provider/id`, the one string a step row shows and accepts.
192
+ *
193
+ * THE LEVEL COMES FIRST, and that is a display decision with teeth.
194
+ * `SettingsList` truncates a value from the RIGHT, so whatever is last is what
195
+ * silently disappears — and a real local model id (`local/Qwen3.8-27B-UD-Q4_K_XL
196
+ * .gguf`) is wide enough to consume the whole column on its own. Level-first
197
+ * means the half that falls off is the one still identifiable from its head, and
198
+ * the levels line up as a column you can read down.
199
+ */
200
+ export declare function formatStepValue(spec: string, level: GroupSetting): string;
201
+ /**
202
+ * The two halves back out, or `undefined` for anything not of that shape.
203
+ *
204
+ * Split on the FIRST separator, because the level leads and is a closed set,
205
+ * while a `provider/id` could conceivably contain one.
206
+ */
207
+ export declare function parseStepValue(value: string): {
208
+ spec: string;
209
+ level: GroupSetting;
210
+ } | undefined;
211
+ /**
212
+ * One row per step, carrying BOTH dials.
213
+ *
214
+ * They were two parallel blocks of eleven, and the coupling between them was
215
+ * invisible: choosing a model re-clamps that step's thinking level, but the row
216
+ * that moved was eleven rows away from the row you touched. One row shows the
217
+ * pair, and the two-step picker shows the clamp happening.
218
+ *
219
+ * `values` is the LEGAL cross product — every model against only the levels that
220
+ * model declares. Nothing renders it: the picker offers two short lists, and the
221
+ * round-trip property in config-items.test.ts is its only reader. Building it
222
+ * from `offeredLevels` rather than the full ladder is what makes the property
223
+ * true, because a pair the model cannot honour would be clamped by `apply` and
224
+ * would not round-trip.
225
+ */
226
+ export declare function stepItems(catalog?: ModelCatalog): ConfigItem[];
227
+ /**
228
+ * Write both halves of a step row, atomically.
229
+ *
230
+ * Atomically matters for the round-trip property, which starts from a FRESH
231
+ * config every iteration: a value that wrote only one half would leave the other
232
+ * at its default and render as something else.
233
+ *
234
+ * The level is re-clamped even though the picker only ever offers legal pairs.
235
+ * The picker is not the only door — `values` is built when the panel opens, and
236
+ * a registry that moved underneath it would otherwise let an unhonourable level
237
+ * through.
238
+ */
239
+ export declare function applyStepValue(cfg: PiTaskConfig, group: ChildGroup, chosen: string, catalog: ModelCatalog): void;
135
240
  /**
136
241
  * Apply one group row's new value.
137
242
  *
@@ -142,7 +247,7 @@ export declare function reasoningItems(): ConfigItem[];
142
247
  * it, nudging `research` while in `off` would silently return every other group
143
248
  * to whatever the stored table happened to hold.
144
249
  */
145
- export declare function applyReasoningLevel(cfg: PiTaskConfig, group: ReasoningGroup, chosen: string): void;
250
+ export declare function applyReasoningLevel(cfg: PiTaskConfig, group: ChildGroup, chosen: string): void;
146
251
  /**
147
252
  * Tallest body the settings list can render, so {@link BorderedBox} can pad
148
253
  * every frame to it and hold the border still. Mirrors SettingsList's own
@@ -157,17 +262,32 @@ export type PanelItem = {
157
262
  description: string;
158
263
  currentValue: string;
159
264
  /**
160
- * Omitted ONLY by a section header. SettingsList cycles a row on Enter when
161
- * this is a non-empty array, so leaving it off is what makes a header inert
162
- * the header does not need its own branch anywhere.
265
+ * The values a cycling row steps through. A section header carries none.
266
+ *
267
+ * It is NOT what marks a header any more {@link isSectionRow} reads the id
268
+ * for that. A model row legitimately has both a `values` list and a
269
+ * `submenu`, so "no values" and "not a row" stopped being the same fact the
270
+ * moment pickers existed.
163
271
  */
164
272
  values?: string[];
273
+ /**
274
+ * Enter-time options for a picker row, already closed over the draft config
275
+ * by {@link renderRows}.
276
+ *
277
+ * Deliberately NOT called `submenu`: pi-tui's `SettingItem.submenu` is a
278
+ * COMPONENT FACTORY, and a PanelItem is handed to `SettingsList` directly by
279
+ * tests and by the headless path. Two different things under one name would
280
+ * make PanelItem stop being assignable to SettingItem, for no gain.
281
+ * {@link createSettingsPanel} is where a theme exists, so it is where these
282
+ * options become a component.
283
+ */
284
+ pickerOptions?: () => PairOptions;
165
285
  /**
166
286
  * What the headless one-line rendering calls this row, when `label` reads
167
287
  * only in the panel. A tree branch means nothing on a line of `|`-joined
168
- * rows: `├─ files` there names no parent, where `think: research:files`
169
- * does. Set by the reasoning rows; every other row leaves it off and its
170
- * `label` is used.
288
+ * rows: `├─ files` there names no parent, where `step: research:files`
289
+ * does. Set by the step rows; every other row leaves it off and its `label`
290
+ * is used.
171
291
  */
172
292
  headlessLabel?: string;
173
293
  };
@@ -181,10 +301,10 @@ export declare function createSettingsPanel(items: PanelItem[], theme: Theme,
181
301
  * Called with the row's id, its new value, and the LIST ITSELF.
182
302
  *
183
303
  * The list is handed back because some rows change what OTHER rows display:
184
- * flipping `reasoning` to off means every `think:` row now runs at off, and
185
- * a row's `currentValue` is a snapshot taken when the panel was built.
186
- * Without a way to write the others back, the menu would show
187
- * `reasoning off` beside rows still claiming `inherit`.
304
+ * flipping `profile` to off means every `step:` row now runs at off, and a
305
+ * row's `currentValue` is a snapshot taken when the panel was built. Without
306
+ * a way to write the others back, the menu would show `profile off` beside
307
+ * rows still claiming `inherit`.
188
308
  */
189
309
  onChange: (id: string, newValue: string, list: SettingsList) => void, onCancel: () => void): BorderedBox;
190
310
  /**
@@ -198,17 +318,17 @@ onChange: (id: string, newValue: string, list: SettingsList) => void, onCancel:
198
318
  * Fixed rows come before discovered ones within a section, so a freshly
199
319
  * installed extension appends rather than reshuffling the menu.
200
320
  */
201
- export declare function configRows(installed: InstalledExtension[], tools?: readonly GuardableTool[]): ConfigItem[];
321
+ export declare function configRows(installed: InstalledExtension[], tools?: readonly GuardableTool[], catalog?: ModelCatalog): ConfigItem[];
202
322
  /** Render `rows` for the current config, grouped under their section headers. */
203
323
  export declare function renderRows(cfg: PiTaskConfig, rows: readonly ConfigItem[]): PanelItem[];
204
324
  /** The full settings row list for the current config, in menu order. */
205
- export declare function panelItems(cfg: PiTaskConfig, installed: InstalledExtension[], tools?: readonly GuardableTool[]): PanelItem[];
325
+ export declare function panelItems(cfg: PiTaskConfig, installed: InstalledExtension[], tools?: readonly GuardableTool[], catalog?: ModelCatalog): PanelItem[];
206
326
  /**
207
327
  * Re-ask every row what it now displays, and write the answers back.
208
328
  *
209
329
  * A row's `currentValue` in the live list is a snapshot taken when the panel was
210
330
  * built, and rows describe each other: cycling `reasoning` to `off` changes what
211
- * every `think:` row runs at, and cycling one group row flips the mode, which
331
+ * every `step:` row runs at, and setting one step row flips the profile, which
212
332
  * changes all the others.
213
333
  *
214
334
  * This runs after ANY change, over EVERY row. Re-reading a `format` costs