pi-goal-list-loop-audit 0.34.50 → 0.34.79
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 +53 -27
- package/docs/DESIGN.md +57 -0
- package/docs/RELEASING.md +6 -0
- package/docs/VISION-ASSIST.md +73 -0
- package/extensions/confirm-draft.ts +130 -0
- package/extensions/goal-loop-auditor-process.ts +176 -25
- package/extensions/goal-loop-auditor.ts +98 -22
- package/extensions/goal-loop-core.ts +387 -5
- package/extensions/goal-loop-display.ts +209 -70
- package/extensions/goal-loop-shield.ts +21 -4
- package/extensions/goal-settings.ts +39 -0
- package/extensions/loops/goal.ts +1495 -312
- package/extensions/main-model-recovery.ts +60 -13
- package/extensions/reviewer.ts +24 -0
- package/extensions/settings-menu.ts +33 -0
- package/extensions/vision-assist.ts +117 -0
- package/package.json +1 -1
- package/scripts/goal-auditor-worker.mjs +53 -5
package/README.md
CHANGED
|
@@ -248,33 +248,49 @@ Activity is otherwise intentionally honest:
|
|
|
248
248
|
| `QUEUED` | A continuation is waiting to start; no work is fabricated. |
|
|
249
249
|
| `IDLE` | The durable item remains active, but no recent work is observed. |
|
|
250
250
|
| `auditor …` | A detached, extension-less verifier is queued, running, quiet, or waiting for its verdict. |
|
|
251
|
-
| `QUOTA WALL` | The provider rejected the request for a quota/plan window; saved work is waiting for a durable probe. |
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
251
|
+
| `QUOTA WALL` | The provider rejected the request for a quota/plan window (by its own wording); saved work is waiting for a durable probe. |
|
|
252
|
+
|
|
253
|
+
## Provider failures: one retry envelope, bounded (v0.34.51)
|
|
254
|
+
|
|
255
|
+
Error text is **not trusted** to pick a retry policy: we only know that an
|
|
256
|
+
error came, and provider messages vary. Every main-model failure — quota,
|
|
257
|
+
rate limit, billing/credits, auth, transient, or unclassifiable — rides the
|
|
258
|
+
same durable recovery envelope `15m → 30m → 1h → 2h → 4h → 5h` (probe cap
|
|
259
|
+
5h, automatic window 24h, then an explicit `/goal resume`/`/list resume`/
|
|
260
|
+
`/loop resume` starts a fresh window). The only failures that do not auto-retry
|
|
261
|
+
are the ones identified by *positive evidence* as futile: context/output-token
|
|
262
|
+
limits and user aborts (`non-recoverable`), plus auditor watchdog timeouts
|
|
263
|
+
(a hanging verification command will hang again — the stored claim waits for
|
|
264
|
+
an explicit resume).
|
|
265
|
+
A provider hint (`retry_after`/`reset_at`) is honored when it fits the
|
|
266
|
+
five-hour probe budget; an over-budget hint (e.g. a week-long reset) never
|
|
267
|
+
parks the goal — the bounded cadence owns the wait, and only the 24h horizon
|
|
268
|
+
ends automatic probes (a `/goal resume`/`/list resume`/`/loop resume` then
|
|
269
|
+
starts a fresh window). With global `autoResume=on`, pending
|
|
270
|
+
probes survive a session reload. For continuous work, configure ordered
|
|
271
|
+
**Main model backups** in `/glla` using a model from a different provider or
|
|
272
|
+
billing/quota pool — another model on the same exhausted plan is not a real
|
|
273
|
+
fallback.
|
|
274
|
+
|
|
275
|
+
**Quota walls engage fast** (v0.34.57): a surfaced long-lived failure
|
|
276
|
+
(quota / billing / auth) records a 30-minute knowledge window; a send-rearm
|
|
277
|
+
storm inside that window escalates into the recovery envelope after **3
|
|
278
|
+
minutes** of failed sends instead of the generic 15 — a wedge right after a
|
|
279
|
+
quota wall is almost always the same wall. Transient (5xx/stream/network)
|
|
280
|
+
failures never record the signal and keep the fast error ladder. The
|
|
281
|
+
envelope is armed by configuration: an empty `mainModelFallbacks` list means
|
|
282
|
+
"park and probe the same model" rather than switching pools — the
|
|
283
|
+
never-switch posture is a first-class policy, not an accident.
|
|
284
|
+
|
|
285
|
+
Classification still exists, but it only *labels*: the card and badge show
|
|
286
|
+
what the provider said (quota wall, billing, rate limit) so the reason is
|
|
287
|
+
diagnosable, and `QUOTA WALL` is only shown when the provider's own words say
|
|
288
|
+
quota — ambiguous prose is never relabeled. The raw provider message stays in
|
|
289
|
+
the ledger/durable state; the card shows the classified reason and recovery
|
|
290
|
+
action. Detached-auditor failures get the same treatment: any infrastructure
|
|
291
|
+
error on a stored completion claim pauses the goal with a durable bounded
|
|
292
|
+
one-shot retry (`auditor retry: …`), and only the plan's 24h horizon stops
|
|
293
|
+
automatic probes.
|
|
278
294
|
|
|
279
295
|
The quota-specific card hides raw provider JSON while preserving it in durable
|
|
280
296
|
state and the ledger:
|
|
@@ -337,6 +353,16 @@ warning says no replacement arrived, restart pi normally and let the saved
|
|
|
337
353
|
mux dependency. The legacy `autoReloadOnStale` and `autoRecovery` fields remain
|
|
338
354
|
only as deprecated settings-file compatibility fields; they are ignored.
|
|
339
355
|
|
|
356
|
+
**A stale handle never mutates** (v0.34.51–v0.34.54): every `/list` mutation
|
|
357
|
+
(add/remove/next/clear/cancel) and the bare `/glla` settings surface probe at
|
|
358
|
+
entry and refuse with the standard recovery message on a stale extension
|
|
359
|
+
context — a session that cannot announce or run its writes must not make them.
|
|
360
|
+
Mutating `/glla` actions (wipe/cancel/reviewer/postaudit/tooloverride) leave a
|
|
361
|
+
`settings_mutation_refused_stale` ledger trail; read-only surfaces (`/list
|
|
362
|
+
show`, `/goal status`) stay usable with the warning. Once the replacement
|
|
363
|
+
`session_start` arrives, both surfaces render cleanly with no stale residue
|
|
364
|
+
(the lifecycle-recovery harness proves the two-phase contract).
|
|
365
|
+
|
|
340
366
|
**User aborts mean STOP** (v0.29.4): Esc-aborting a turn stands the chain
|
|
341
367
|
down with a named notify (`/goal resume` to continue) — it does NOT count
|
|
342
368
|
toward stall warnings, does NOT auto re-fire, and the stand-down survives
|
package/docs/DESIGN.md
CHANGED
|
@@ -197,6 +197,63 @@ architectural decisions that changed the SHAPE of the system:
|
|
|
197
197
|
cycle. Manual model selection cancels it; goal/list/loop cancellation clears
|
|
198
198
|
its timer and durable state.
|
|
199
199
|
|
|
200
|
+
## Addendum v0.34.48–v0.34.56 (lifecycle/recovery hardening — the stale-handle era)
|
|
201
|
+
|
|
202
|
+
Between the v0.34.31 recovery envelope and the v0.34.57 quota fast-engagement, the
|
|
203
|
+
recovery story hardened around the **stale extension handle** — the field-observed
|
|
204
|
+
shape (hegemon/polis 2026-07-26+) where pi invalidates the extension API on session
|
|
205
|
+
replacement without delivering a successor `session_start`:
|
|
206
|
+
|
|
207
|
+
- **Honest stale entry everywhere** (v0.34.51/52): `warnIfStaleAtEntry` probes at
|
|
208
|
+
entry (since v0.28.1), but the probe's return value used to be discarded by the
|
|
209
|
+
mutation paths. Now every `/list` mutation (add/remove/next/clear/cancel) and the
|
|
210
|
+
bare `/glla` settings surface refuse on a stale handle with the standard recovery
|
|
211
|
+
message — a session that cannot announce or run its writes must not make them.
|
|
212
|
+
Mutating `/glla` actions (wipe/cancel/reviewer/postaudit/tooloverride) refuse with
|
|
213
|
+
a `settings_mutation_refused_stale` ledger trail; read-only surfaces stay usable
|
|
214
|
+
with the warning. This is the plugin side of the missing-replacement contract: fail
|
|
215
|
+
closed, never guess, never pretend success.
|
|
216
|
+
- **Settings routing clarity** (v0.34.53): `/list settings` is a verb, handled
|
|
217
|
+
explicitly BEFORE the natural-language dump fallthrough — a ledgered redirect to
|
|
218
|
+
`/glla`, never a drafting seed. `/list add settings …` remains the only way an item
|
|
219
|
+
literally named "settings" enters the queue.
|
|
220
|
+
- **Lifecycle-recovery harness** (v0.34.54): a behavioral suite proves the two-phase
|
|
221
|
+
contract — stale handle: `/list show` warns and does not pretend, settings refuse
|
|
222
|
+
wholesale; fresh `session_start`: both render cleanly with no stale residue.
|
|
223
|
+
- **Command-registration collision model** (v0.34.55): pi's `resolveRegisteredCommands`
|
|
224
|
+
flattens extensions in load order and suffixes EVERY registration of a duplicated
|
|
225
|
+
command name (`name:1`, `name:2`, …) — the bare name becomes owned by nobody and
|
|
226
|
+
dispatch stops routing it while a collision exists. The model is read from the
|
|
227
|
+
installed pi core (hermetic, never modified) and auto-records the routing table to
|
|
228
|
+
`audit/command-registration-routing.md`, making collisions reproducible and
|
|
229
|
+
diagnosable without touching pi.
|
|
230
|
+
- **Unmatched telemetry stays unmatched** (v0.34.56): tool starts/ends without a
|
|
231
|
+
counterpart are represented as explicitly unmatched facts, never falsely paired —
|
|
232
|
+
the report surface stays truthful (the AuditProgress/AuditorProgress dual-interface
|
|
233
|
+
rule: display evidence-gates on `unmatchedStarts + unmatchedEnds > 0`).
|
|
234
|
+
- **Uniform retry envelope, no text-trust** (v0.34.51): error text is not trusted to
|
|
235
|
+
pick a retry policy. Quota, billing, auth, transient, and unknown failures all ride
|
|
236
|
+
ONE bounded durable envelope `15m → 30m → 1h → 2h → 4h → 5h` (cap 5h, automatic
|
|
237
|
+
window 24h); classification only labels the display. The billing-hold special case
|
|
238
|
+
is removed (`main_model_billing_hold` is legacy). Positive-evidence futile classes
|
|
239
|
+
(context/output-token limits, user aborts) plus auditor watchdog timeouts never
|
|
240
|
+
auto-retry; provider hints are honored only within the 5h probe budget.
|
|
241
|
+
|
|
242
|
+
## Addendum v0.34.57 (quota walls engage recovery fast)
|
|
243
|
+
|
|
244
|
+
- **Knowledge-window escalation**: a surfaced long-lived failure (quota /
|
|
245
|
+
billing / auth) records a 30-minute knowledge window. A send-rearm storm
|
|
246
|
+
inside that window escalates into the recovery envelope after 3 minutes of
|
|
247
|
+
failed sends (plus the unchanged 5-minute activity silence gate) instead of
|
|
248
|
+
the generic 15 minutes — a wedge right after a quota wall is almost always
|
|
249
|
+
the same wall, and blind re-sends into it are pure waste.
|
|
250
|
+
- **Transient failures stay fast**: 5xx/stream/network failures are
|
|
251
|
+
short-lived by definition and never record the knowledge signal; they keep
|
|
252
|
+
the 5s→3m error ladder and the pi-core retry budget.
|
|
253
|
+
- **Armed by configuration**: the envelope is inert without
|
|
254
|
+
`mainModelFallbacks` (rotation) — an empty list means "park and probe the
|
|
255
|
+
same model" instead of switching pools.
|
|
256
|
+
|
|
200
257
|
## Addendum v0.4.0 (completion)
|
|
201
258
|
|
|
202
259
|
- **Auditor compaction enabled** (flaw #3 — the last open one). Safety:
|
package/docs/RELEASING.md
CHANGED
|
@@ -17,6 +17,12 @@ the repository.
|
|
|
17
17
|
|
|
18
18
|
## Release checklist
|
|
19
19
|
|
|
20
|
+
Accumulated changes since the last release live under an `## Unreleased`
|
|
21
|
+
section at the top of `CHANGELOG.md` (with the in-repo milestone labels such
|
|
22
|
+
as `### 0.34.51`); the release commit renames that section to the released
|
|
23
|
+
version. Do not invent version headers for work that was never tagged —
|
|
24
|
+
untagged work stays under `Unreleased` until the release commit.
|
|
25
|
+
|
|
20
26
|
```bash
|
|
21
27
|
npm version <major.minor.patch> --no-git-tag-version
|
|
22
28
|
npm run release:check
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Vision Assist — see with mmx, not a model switch
|
|
2
|
+
|
|
3
|
+
**v0.34.72** · note.md 2026-08-07: *"the agent is too eager when couldnt see it
|
|
4
|
+
tried to use expensive mdoels. we need to special a vision setting where it
|
|
5
|
+
called another model or cli like mmx vision to see if stuck. but not just this
|
|
6
|
+
we need to specify that it cant be too eager to switch only preapproved."*
|
|
7
|
+
|
|
8
|
+
## Policy
|
|
9
|
+
|
|
10
|
+
The executor (pi's main agent) has no eyes. When a task needs it to **look**
|
|
11
|
+
at something — a screenshot, a UI state, an error dialog, a rendered mockup —
|
|
12
|
+
it must NOT switch models to get vision. The check routes to the **mmx vision
|
|
13
|
+
CLI** (the `mmx-cli` skill, MiniMax VLM):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
mmx vision describe --image <path-or-url> --prompt "<question>" --quiet --non-interactive
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- The image is usually a screenshot the user already pasted into the
|
|
20
|
+
conversation (e.g. `/home/dracon/Pictures/Screenshots/...`). Pass its path
|
|
21
|
+
straight through.
|
|
22
|
+
- Keep the question short and specific: *"What does this screenshot show?"*,
|
|
23
|
+
*"Is there an error dialog?"*, *"What is the terminal output?"*.
|
|
24
|
+
- Reading the returned description is the agent's job — no model switch
|
|
25
|
+
needed. (Verified 2026-08-07: `mmx vision describe` returns clean JSON/text
|
|
26
|
+
with `status_code: 0`.)
|
|
27
|
+
|
|
28
|
+
## The preapproval gate (model switches)
|
|
29
|
+
|
|
30
|
+
A model switch is sanctioned **only when the target is preapproved** — i.e.
|
|
31
|
+
NOT in the `forbiddenModels` policy:
|
|
32
|
+
|
|
33
|
+
- Default forbidden list: `gpt-5.5`, `sonnet`, `opus` (matched
|
|
34
|
+
case-insensitively as a substring against the `provider/id` ref).
|
|
35
|
+
- `/glla forbiddenModels=...` edits the list; `blockForbiddenModelSwitches`
|
|
36
|
+
(default on) reverts a forbidden selection to the previous model.
|
|
37
|
+
- Every switch to a forbidden model is ledgered as `forbidden_model_switch`
|
|
38
|
+
(with `blocked: true|false`).
|
|
39
|
+
- With vision assist on (default), the same event also appends a
|
|
40
|
+
`vision_assist` ledger entry — the routing alternative: `{ route:
|
|
41
|
+
"mmx-vision", blockedSwitch: <ref>, reason: "forbidden_model_switch" }`.
|
|
42
|
+
|
|
43
|
+
Even a preapproved vision-capable model is a second choice: mmx vision is the
|
|
44
|
+
default for every vision check.
|
|
45
|
+
|
|
46
|
+
## The setting
|
|
47
|
+
|
|
48
|
+
`visionAssist` (default **on** — opt-out):
|
|
49
|
+
|
|
50
|
+
- **on** → every continuation prompt carries the `## VISION-ASSIST — SEE WITH
|
|
51
|
+
MMX, NOT A MODEL SWITCH` directive (`extensions/vision-assist.ts`
|
|
52
|
+
`VISION_ASSIST_GUIDANCE`), and a forbidden switch also records the
|
|
53
|
+
`vision_assist` routing entry.
|
|
54
|
+
- **off** → no vision guidance is injected; the `forbiddenModels` gate still
|
|
55
|
+
stands (forbidden switches remain blocked/ledgered).
|
|
56
|
+
|
|
57
|
+
Edit: `/glla` → Keep-going → Vision assist, or `/glla visionAssist=off`.
|
|
58
|
+
|
|
59
|
+
## Implementation map
|
|
60
|
+
|
|
61
|
+
| Piece | Where |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Guidance block (single source of truth) | `extensions/vision-assist.ts` → `VISION_ASSIST_GUIDANCE` |
|
|
64
|
+
| Command builder | `visionDescribeCommand(imagePath, question?)` |
|
|
65
|
+
| Routing rule (pure) | `routeVisionCheck(request)` — mmx by default; forbidden target → mmx + `blockedSwitch`; preapproved target → `model-switch` allowed |
|
|
66
|
+
| Ledger payload builder | `visionAssistLedger(route, request)` |
|
|
67
|
+
| Continuation injection | `extensions/loops/goal.ts` `continuationPrompt()` (gated on `visionAssist !== false`) |
|
|
68
|
+
| Forbidden-switch hook | `observeModelChange()` forbidden branch → `vision_assist` entry |
|
|
69
|
+
| Setting | `extensions/goal-settings.ts` (default true), menu row in `extensions/settings-menu.ts`, editor + `/glla` row in `extensions/loops/goal.ts` |
|
|
70
|
+
| Tests | `tests/vision-assist.test.ts` |
|
|
71
|
+
|
|
72
|
+
The `vision_assist` ledger type is the audit trail: every entry says where the
|
|
73
|
+
check routed and (when a switch was blocked) which model was refused.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// pi-goal-list-loop-audit — v0.2.0
|
|
2
|
+
// extensions/confirm-draft.ts
|
|
3
|
+
//
|
|
4
|
+
// v0.34.78 (GitHub #4): the draft-class confirm dialog as a real TUI
|
|
5
|
+
// component. ctx.ui.select renders plain text with no wrapping; this
|
|
6
|
+
// component renders the SAME title/body as Markdown (objective + contract
|
|
7
|
+
// readable at full width) with a SelectList for the Yes / Yes-and-always /
|
|
8
|
+
// No choices. Kept in its own file so tests can construct and render it
|
|
9
|
+
// without dragging in the whole goal loop.
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
type Component,
|
|
13
|
+
Container,
|
|
14
|
+
Markdown,
|
|
15
|
+
type MarkdownTheme,
|
|
16
|
+
type SelectItem,
|
|
17
|
+
SelectList,
|
|
18
|
+
type SelectListTheme,
|
|
19
|
+
Spacer,
|
|
20
|
+
Text,
|
|
21
|
+
} from "@earendil-works/pi-tui";
|
|
22
|
+
import { DynamicBorder, type Theme } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
|
|
24
|
+
export interface ConfirmDraftFactoryDeps {
|
|
25
|
+
title: string;
|
|
26
|
+
body: string;
|
|
27
|
+
options: string[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Structural type for the KeybindingsManager — mirrors settings-menu.ts. */
|
|
31
|
+
export interface KeybindingsManagerLike {
|
|
32
|
+
matches(data: string, key: string): boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Pure: the markdown rendered in the dialog. The title is the H1, the
|
|
36
|
+
* body (objective + verification contract) is the content. */
|
|
37
|
+
export function buildConfirmDraftMarkdown(title: string, body: string): string {
|
|
38
|
+
return `# ${title}\n\n${body}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Build a MarkdownTheme from the runtime Theme's fg()/bold() primitives.
|
|
42
|
+
* Uses the theme's own md* colors so the dialog follows the active theme. */
|
|
43
|
+
function markdownTheme(theme: Theme): MarkdownTheme {
|
|
44
|
+
const fg = (color: Parameters<Theme["fg"]>[0]) => (t: string) => theme.fg(color, t);
|
|
45
|
+
return {
|
|
46
|
+
heading: (t) => theme.bold(fg("mdHeading")(t)),
|
|
47
|
+
link: (t) => fg("mdLink")(t),
|
|
48
|
+
linkUrl: (t) => fg("mdLinkUrl")(t),
|
|
49
|
+
code: (t) => fg("mdCode")(t),
|
|
50
|
+
codeBlock: (t) => fg("mdCodeBlock")(t),
|
|
51
|
+
codeBlockBorder: (t) => fg("mdCodeBlockBorder")(t),
|
|
52
|
+
quote: (t) => fg("mdQuote")(t),
|
|
53
|
+
quoteBorder: (t) => fg("mdQuoteBorder")(t),
|
|
54
|
+
hr: (t) => fg("mdHr")(t),
|
|
55
|
+
listBullet: (t) => fg("mdListBullet")(t),
|
|
56
|
+
bold: (t) => theme.bold(t),
|
|
57
|
+
italic: (t) => t,
|
|
58
|
+
strikethrough: (t) => t,
|
|
59
|
+
underline: (t) => t,
|
|
60
|
+
codeBlockIndent: " ",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function selectListTheme(theme: Theme): SelectListTheme {
|
|
65
|
+
return {
|
|
66
|
+
selectedPrefix: (t) => theme.fg("accent", t),
|
|
67
|
+
selectedText: (t) => theme.fg("accent", t),
|
|
68
|
+
description: (t) => theme.fg("muted", t),
|
|
69
|
+
scrollInfo: (t) => theme.fg("dim", t),
|
|
70
|
+
noMatch: (t) => theme.fg("warning", t),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The confirm dialog: DynamicBorder frame, markdown title+body, spacer, the
|
|
76
|
+
* three-choice SelectList, and a help line. Exported so tests can construct
|
|
77
|
+
* it with a fake theme and assert the rendered lines.
|
|
78
|
+
*/
|
|
79
|
+
export class ConfirmDraftComponent implements Component {
|
|
80
|
+
private readonly md: Markdown;
|
|
81
|
+
private readonly selectList: SelectList;
|
|
82
|
+
private readonly requestRender: () => void;
|
|
83
|
+
private readonly theme: Theme;
|
|
84
|
+
private readonly keybindings: KeybindingsManagerLike;
|
|
85
|
+
|
|
86
|
+
constructor(
|
|
87
|
+
deps: ConfirmDraftFactoryDeps,
|
|
88
|
+
requestRender: () => void,
|
|
89
|
+
theme: Theme,
|
|
90
|
+
keybindings: KeybindingsManagerLike,
|
|
91
|
+
done: (value: string | undefined) => void,
|
|
92
|
+
) {
|
|
93
|
+
this.requestRender = requestRender;
|
|
94
|
+
this.theme = theme;
|
|
95
|
+
this.keybindings = keybindings;
|
|
96
|
+
this.md = new Markdown(buildConfirmDraftMarkdown(deps.title, deps.body), 1, 1, markdownTheme(theme));
|
|
97
|
+
const items: SelectItem[] = deps.options.map((o) => ({ value: o, label: o }));
|
|
98
|
+
this.selectList = new SelectList(items, Math.min(items.length, 10), selectListTheme(theme));
|
|
99
|
+
this.selectList.onSelect = (item) => done(item.value);
|
|
100
|
+
this.selectList.onCancel = () => done(undefined);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
render(width: number): string[] {
|
|
104
|
+
const container = new Container();
|
|
105
|
+
container.addChild(new DynamicBorder((s: string) => this.theme.fg("borderAccent", s)));
|
|
106
|
+
container.addChild(this.md);
|
|
107
|
+
container.addChild(new Spacer(1));
|
|
108
|
+
container.addChild(this.selectList);
|
|
109
|
+
container.addChild(new Spacer(1));
|
|
110
|
+
container.addChild(new Text(this.theme.fg("dim", "↑↓ navigate • enter select • esc cancel"), 1, 0));
|
|
111
|
+
container.addChild(new DynamicBorder((s: string) => this.theme.fg("borderAccent", s)));
|
|
112
|
+
return container.render(width);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
invalidate(): void {
|
|
116
|
+
this.md.invalidate();
|
|
117
|
+
this.selectList.invalidate();
|
|
118
|
+
this.requestRender();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
handleInput(data: string): void {
|
|
122
|
+
this.selectList.handleInput(data);
|
|
123
|
+
this.requestRender();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Exposed for tests. */
|
|
127
|
+
getSelectedItem(): string | null {
|
|
128
|
+
return this.selectList.getSelectedItem()?.value ?? null;
|
|
129
|
+
}
|
|
130
|
+
}
|