pi-crew 0.11.0 → 0.11.2
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/CHANGELOG.md +155 -9
- package/README.md +161 -1037
- package/agents/verifier.md +18 -7
- package/dist/index.mjs +744 -90644
- package/docs/README.md +57 -46
- package/docs/architecture.md +87 -33
- package/docs/commands-reference.md +9 -5
- package/docs/troubleshooting.md +3 -2
- package/package.json +1 -3
- package/schema.json +39 -0
- package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +2 -0
- package/skills/real-test-pi-crew/SKILL.md +371 -34
- package/src/agents/agent-config.ts +1 -1
- package/src/agents/discover-agents.ts +1 -1
- package/src/config/config-validation.ts +15 -2
- package/src/config/config.ts +47 -13
- package/src/config/defaults.ts +0 -1
- package/src/config/env-vars.ts +35 -0
- package/src/config/types.ts +19 -5
- package/src/errors.ts +2 -2
- package/src/extension/async-notifier.ts +23 -0
- package/src/extension/crew-vibes/config.ts +0 -21
- package/src/extension/crew-vibes/index.ts +0 -2
- package/src/extension/crew-vibes/render.ts +1 -50
- package/src/extension/help.ts +21 -12
- package/src/extension/knowledge-injection.ts +2 -1
- package/src/extension/management.ts +8 -3
- package/src/extension/notification-sink.ts +17 -0
- package/src/extension/register.ts +7 -2
- package/src/extension/registration/command-utils.ts +28 -2
- package/src/extension/registration/commands/dashboard.ts +11 -1
- package/src/extension/registration/commands/manage.ts +36 -19
- package/src/extension/registration/commands/run.ts +24 -2
- package/src/extension/registration/commands/shared.ts +23 -1
- package/src/extension/registration/commands/status.ts +25 -2
- package/src/extension/registration/context-builder.ts +8 -2
- package/src/extension/registration/health-notify-policy.ts +100 -0
- package/src/extension/registration/lazy-configurers.ts +35 -0
- package/src/extension/registration/lifecycle-handlers.ts +91 -30
- package/src/extension/registration/lifecycle.ts +75 -10
- package/src/extension/registration/observability.ts +98 -35
- package/src/extension/registration/registration-types.ts +7 -5
- package/src/extension/registration/runtime-cleanup.ts +9 -3
- package/src/extension/registration/subagent-helpers.ts +38 -0
- package/src/extension/registration/subagent-tools.ts +16 -6
- package/src/extension/registration/team-tool.ts +10 -3
- package/src/extension/registration/terminal-status-wiring.ts +172 -0
- package/src/extension/registration/viewers.ts +6 -0
- package/src/extension/registration/wire-cross-extension.ts +28 -0
- package/src/extension/run-compare.ts +220 -0
- package/src/extension/run-export.ts +37 -5
- package/src/extension/run-maintenance.ts +155 -5
- package/src/extension/team-tool/dispatch/index.ts +3 -2
- package/src/extension/team-tool/dispatch/manage.ts +5 -2
- package/src/extension/team-tool/goal.ts +4 -1
- package/src/extension/team-tool/handle-settings.ts +33 -4
- package/src/extension/team-tool/health-monitor.ts +21 -7
- package/src/extension/team-tool/lifecycle-actions.ts +49 -1
- package/src/extension/team-tool/plan.ts +10 -0
- package/src/extension/team-tool/routing-hint.ts +63 -0
- package/src/extension/team-tool/status.ts +4 -0
- package/src/extension/team-tool.ts +52 -6
- package/src/extension/webhook-notify.ts +382 -0
- package/src/observability/metric-sink.ts +12 -2
- package/src/prompt/prompt-runtime.ts +82 -31
- package/src/prompt/worker-events-channel.ts +12 -0
- package/src/runtime/README.md +1 -1
- package/src/runtime/async-runner.ts +87 -1
- package/src/runtime/background-runner.ts +313 -234
- package/src/runtime/broker/crew-broker.ts +17 -11
- package/src/runtime/broker/delegate/shadow-lifecycle.ts +92 -0
- package/src/runtime/broker/wait-status-cache.ts +1 -1
- package/src/runtime/child-pi/child-pi-timers.ts +1 -1
- package/src/runtime/child-pi/mock-fixtures.ts +48 -0
- package/src/runtime/crew-agent-records.ts +337 -45
- package/src/runtime/deadletter.ts +43 -1
- package/src/runtime/delegate-spawn.ts +5 -1
- package/src/runtime/dispatch-batch.ts +72 -5
- package/src/runtime/goal-workflow/goal-loop-runner.ts +73 -4
- package/src/runtime/heartbeat/heartbeat-watcher.ts +7 -0
- package/src/runtime/model/model-fallback.ts +21 -1
- package/src/runtime/model/pi-args.ts +8 -10
- package/src/runtime/recovery/crash-recovery.ts +25 -1
- package/src/runtime/run-worker.ts +12 -1
- package/src/runtime/scheduling/global-worker-cap.ts +13 -6
- package/src/runtime/scheduling/run-coalesced-task-group.ts +27 -1
- package/src/runtime/scheduling/scheduler.ts +49 -13
- package/src/runtime/scheduling/semaphore.ts +148 -20
- package/src/runtime/scratchpad/README.md +1 -1
- package/src/runtime/scratchpad/protocol.ts +1 -1
- package/src/runtime/settings-store.ts +1 -1
- package/src/runtime/skill-instructions.ts +22 -0
- package/src/runtime/stale-reconciler.ts +85 -13
- package/src/runtime/task-display.ts +1 -1
- package/src/runtime/task-runner/pre-execution.ts +26 -2
- package/src/runtime/task-runner/prompt-builder.ts +142 -45
- package/src/runtime/task-runner.ts +21 -1
- package/src/runtime/team-runner.ts +38 -1
- package/src/runtime/workspace-lock.ts +4 -1
- package/src/schema/config-schema.ts +18 -0
- package/src/schema/team-tool-schema.ts +17 -0
- package/src/state/atomic-write.ts +53 -0
- package/src/state/contracts.ts +109 -0
- package/src/state/coordination/locks.ts +191 -33
- package/src/state/coordination/mailbox.ts +140 -15
- package/src/state/crew-init.ts +87 -12
- package/src/state/event-log/cursor.ts +37 -1
- package/src/state/event-log/event-log-rotation.ts +72 -7
- package/src/state/stores/active-run-registry.ts +13 -1
- package/src/state/stores/state-store.ts +112 -22
- package/src/state/types.ts +4 -0
- package/src/ui/adaptive-card.ts +65 -0
- package/src/ui/agents-jobs-browser.ts +70 -64
- package/src/ui/card-colors.ts +36 -7
- package/src/ui/dashboard-panes/agents-pane.ts +55 -14
- package/src/ui/dashboard-panes/cancellation-pane.ts +0 -42
- package/src/ui/dashboard-panes/health-pane.ts +7 -5
- package/src/ui/dashboard-panes/mailbox-pane.ts +22 -6
- package/src/ui/dashboard-panes/metrics-pane.ts +15 -7
- package/src/ui/dashboard-panes/pane-theme.ts +21 -0
- package/src/ui/dashboard-panes/plan-pane.ts +63 -30
- package/src/ui/dashboard-panes/progress-pane.ts +3 -2
- package/src/ui/dashboard-panes/schedules-pane.ts +44 -21
- package/src/ui/dashboard-panes/transcript-pane.ts +11 -5
- package/src/ui/dwf-phase-display.ts +3 -20
- package/src/ui/format-helpers.ts +22 -0
- package/src/ui/heartbeat-aggregator.ts +34 -0
- package/src/ui/inline-panel/crew-editor.ts +13 -3
- package/src/ui/inline-panel/index.ts +60 -4
- package/src/ui/keybinding-map.ts +251 -35
- package/src/ui/live-conversation-overlay.ts +180 -47
- package/src/ui/live-run-sidebar.ts +134 -55
- package/src/ui/mascot.ts +32 -16
- package/src/ui/overlays/agent-picker-overlay.ts +81 -26
- package/src/ui/overlays/confirm-overlay.ts +55 -29
- package/src/ui/overlays/help-overlay.ts +108 -53
- package/src/ui/overlays/mailbox-compose-overlay.ts +89 -50
- package/src/ui/overlays/mailbox-detail-overlay.ts +137 -57
- package/src/ui/powerbar-publisher.ts +0 -1
- package/src/ui/rail.ts +333 -0
- package/src/ui/run-dashboard.ts +193 -79
- package/src/ui/run-snapshot-cache.ts +18 -1
- package/src/ui/settings-overlay.ts +81 -39
- package/src/ui/spinner.ts +26 -2
- package/src/ui/terminal-status.ts +7 -1
- package/src/ui/theme-adapter.ts +0 -45
- package/src/ui/theme-discovery.ts +12 -6
- package/src/ui/tool-progress-formatter.ts +128 -9
- package/src/ui/tool-renderers/brief-mode.ts +10 -67
- package/src/ui/tool-renderers/index.ts +374 -523
- package/src/ui/transcript-viewer.ts +30 -12
- package/src/ui/widget/index.ts +32 -52
- package/src/ui/widget/task-list.ts +64 -32
- package/src/ui/widget/widget-formatters.ts +3 -402
- package/src/ui/widget/widget-model.ts +28 -7
- package/src/ui/widget/widget-renderer.ts +201 -128
- package/src/ui/widget/widget-types.ts +0 -2
- package/src/utils/incremental-reader.ts +11 -3
- package/src/utils/paths.ts +94 -12
- package/src/utils/project-markers.ts +40 -0
- package/src/utils/visual.ts +0 -4
- package/src/worktree/worktree-manager.ts +206 -26
- package/workflows/distill.workflow.md +3 -3
- package/workflows/fast-fix.workflow.md +1 -1
- package/workflows/plan-execute.workflow.md +1 -1
- package/workflows/review.workflow.md +1 -1
- package/workflows/strict-fast-fix.workflow.md +1 -1
- package/docs/migration-v0.4-v0.5.md +0 -208
- package/docs/runtime-flow.md +0 -148
- package/src/extension/crew-vibes/figures.ts +0 -22
- package/src/extension/crew-vibes/font-detect.ts +0 -71
- package/src/ui/dynamic-border.ts +0 -35
- package/src/ui/loaders.ts +0 -6
- package/src/ui/overlay-stack.ts +0 -148
package/src/ui/mascot.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { matchesKey } from "@earendil-works/pi-tui";
|
|
2
|
-
import {
|
|
3
|
-
import { DynamicCrewBorder } from "./dynamic-border.ts";
|
|
2
|
+
import { canopyLine, formatHint, RAIL, railLeaders, railLine } from "./rail.ts";
|
|
4
3
|
import type { CrewTheme } from "./theme-adapter.ts";
|
|
5
4
|
import { asCrewTheme } from "./theme-adapter.ts";
|
|
6
5
|
|
|
@@ -356,10 +355,8 @@ export class AnimatedMascot {
|
|
|
356
355
|
this.onDone();
|
|
357
356
|
}
|
|
358
357
|
|
|
359
|
-
private formatLine(line: string,
|
|
360
|
-
|
|
361
|
-
const themed = this.theme.fg(color, line);
|
|
362
|
-
return `│ ${pad(themed, contentWidth)} │`;
|
|
358
|
+
private formatLine(line: string, budget: number, color: Parameters<CrewTheme["fg"]>[0] = "accent"): string {
|
|
359
|
+
return railLine(RAIL.body, "border", this.theme.fg(color, line), this.theme, budget);
|
|
363
360
|
}
|
|
364
361
|
|
|
365
362
|
private currentCatFrame(): readonly string[] {
|
|
@@ -397,25 +394,44 @@ export class AnimatedMascot {
|
|
|
397
394
|
return this.cachedLines;
|
|
398
395
|
}
|
|
399
396
|
const safeWidth = Math.max(20, width);
|
|
400
|
-
|
|
397
|
+
// M4/RAIL (2026-09-16): the rounded box + DynamicCrewBorder were retired;
|
|
398
|
+
// this surface paints the same rail frame as every other pi-crew surface.
|
|
399
|
+
const budget = Math.max(4, safeWidth - 2);
|
|
401
400
|
const result: string[] = [
|
|
402
|
-
|
|
403
|
-
this.formatLine(this.theme.bold("
|
|
404
|
-
this.formatLine("",
|
|
401
|
+
canopyLine({ word: "MASCOT", subject: this.style, theme: this.theme, budget }),
|
|
402
|
+
this.formatLine(this.theme.bold("ARMIN SAYS HI"), budget),
|
|
403
|
+
this.formatLine("", budget),
|
|
405
404
|
];
|
|
406
405
|
if (this.style === "armin") {
|
|
407
406
|
for (const row of this.currentArminGrid) {
|
|
408
407
|
const text = row.join("");
|
|
409
|
-
result.push(this.formatLine(text,
|
|
408
|
+
result.push(this.formatLine(text, budget));
|
|
410
409
|
}
|
|
411
410
|
} else {
|
|
412
411
|
const frameLines = this.applyCatEffect(this.currentCatFrame());
|
|
413
|
-
for (const line of frameLines) result.push(this.formatLine(line,
|
|
412
|
+
for (const line of frameLines) result.push(this.formatLine(line, budget));
|
|
414
413
|
}
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
414
|
+
const state = this.style === "armin" ? `effect: ${this.effect}` : "animated preview";
|
|
415
|
+
result.push(
|
|
416
|
+
railLine(
|
|
417
|
+
RAIL.close,
|
|
418
|
+
"border",
|
|
419
|
+
railLeaders(
|
|
420
|
+
this.theme.fg(
|
|
421
|
+
"dim",
|
|
422
|
+
formatHint([
|
|
423
|
+
["esc", "close"],
|
|
424
|
+
["q", "close"],
|
|
425
|
+
]),
|
|
426
|
+
),
|
|
427
|
+
this.theme.fg("dim", state),
|
|
428
|
+
budget,
|
|
429
|
+
this.theme,
|
|
430
|
+
),
|
|
431
|
+
this.theme,
|
|
432
|
+
budget,
|
|
433
|
+
),
|
|
434
|
+
);
|
|
419
435
|
this.cachedWidth = safeWidth;
|
|
420
436
|
this.cachedVersion = this.gridVersion;
|
|
421
437
|
this.cachedFrame = this.frame;
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent picker overlay — RAIL design language (M4/E1, 2026-09-16).
|
|
3
|
+
*
|
|
4
|
+
* ┏ AGENTS ▸ <runId8>
|
|
5
|
+
* ┃ › explorer · running · executor ▸ worker
|
|
6
|
+
* ┗ ↑/↓ move · Enter select · Esc cancel
|
|
7
|
+
*
|
|
8
|
+
* Grammar: `canopyLine` opens the surface, every body row is a `┃` line built
|
|
9
|
+
* by `railLine` (which owns the width budget), the close cap carries the hint
|
|
10
|
+
* built by `formatHint` (close LAST, keys through `keyToken`). The rounded
|
|
11
|
+
* `╭─╮│╰─╯` frame and the hand-typed "↑/↓ move · Enter select · ESC cancel"
|
|
12
|
+
* string are retired.
|
|
13
|
+
*
|
|
14
|
+
* Keys/behaviour are unchanged: same `overlay:*` keyspace dispatch, same
|
|
15
|
+
* selection and clamping.
|
|
16
|
+
*
|
|
17
|
+
* File-sourced risk (audit §4): `agents.json` is NOT schema-validated at read
|
|
18
|
+
* time, so every interpolated record field is guarded (`?? "?"`) before it can
|
|
19
|
+
* reach a line — a missing field must never print as `undefined`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
1
22
|
import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
|
|
2
23
|
import type { CrewAgentRecord } from "../../runtime/crew-agent-runtime.ts";
|
|
3
24
|
import { loadRunManifestById } from "../../state/stores/state-store.ts";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
25
|
+
import { truncate } from "../../utils/visual.ts";
|
|
26
|
+
import { overlayActionForKey } from "../keybinding-map.ts";
|
|
27
|
+
import { ACTIVE, CURSOR, canopyLine, formatHint, RAIL, railLine, shortId } from "../rail.ts";
|
|
6
28
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
7
29
|
|
|
8
30
|
export interface AgentPickerSelection {
|
|
@@ -13,6 +35,7 @@ export class AgentPickerOverlay {
|
|
|
13
35
|
private readonly agents: CrewAgentRecord[];
|
|
14
36
|
private readonly done: (selection: AgentPickerSelection | undefined) => void;
|
|
15
37
|
private readonly theme: CrewTheme;
|
|
38
|
+
private readonly runId: string;
|
|
16
39
|
private selected = 0;
|
|
17
40
|
|
|
18
41
|
constructor(opts: {
|
|
@@ -25,6 +48,7 @@ export class AgentPickerOverlay {
|
|
|
25
48
|
this.agents = loaded ? readCrewAgents(loaded.manifest) : [];
|
|
26
49
|
this.done = opts.done;
|
|
27
50
|
this.theme = asCrewTheme(opts.theme ?? {});
|
|
51
|
+
this.runId = opts.runId;
|
|
28
52
|
}
|
|
29
53
|
|
|
30
54
|
invalidate(): void {
|
|
@@ -32,35 +56,66 @@ export class AgentPickerOverlay {
|
|
|
32
56
|
}
|
|
33
57
|
|
|
34
58
|
render(width: number): string[] {
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
if (width < 6) return [];
|
|
60
|
+
const theme = this.theme;
|
|
61
|
+
const budget = width - 2;
|
|
62
|
+
const lines: string[] = [
|
|
63
|
+
canopyLine({ word: "AGENTS", subject: shortId(this.runId), theme, budget }),
|
|
64
|
+
...this.agents.map((agent, index) =>
|
|
65
|
+
railLine(
|
|
66
|
+
RAIL.body,
|
|
67
|
+
"border",
|
|
68
|
+
truncate(
|
|
69
|
+
`${index === this.selected ? CURSOR : " "} ${agent.taskId ?? "?"} · ${agent.status ?? "?"} · ${
|
|
70
|
+
agent.role ?? "?"
|
|
71
|
+
} ${ACTIVE} ${agent.agent ?? "?"}`,
|
|
72
|
+
budget,
|
|
73
|
+
),
|
|
74
|
+
theme,
|
|
75
|
+
budget,
|
|
76
|
+
),
|
|
42
77
|
),
|
|
43
78
|
];
|
|
44
|
-
if (!this.agents.length)
|
|
45
|
-
|
|
79
|
+
if (!this.agents.length) {
|
|
80
|
+
lines.push(railLine(RAIL.body, "border", theme.fg("dim", "No agents found."), theme, budget));
|
|
81
|
+
}
|
|
82
|
+
lines.push(
|
|
83
|
+
railLine(
|
|
84
|
+
RAIL.close,
|
|
85
|
+
"border",
|
|
86
|
+
theme.fg(
|
|
87
|
+
"dim",
|
|
88
|
+
formatHint([
|
|
89
|
+
[["up", "down"], "move"],
|
|
90
|
+
["enter", "select"],
|
|
91
|
+
["escape", "cancel"],
|
|
92
|
+
]),
|
|
93
|
+
),
|
|
94
|
+
theme,
|
|
95
|
+
budget,
|
|
96
|
+
),
|
|
97
|
+
);
|
|
98
|
+
return lines;
|
|
46
99
|
}
|
|
47
100
|
|
|
48
101
|
handleInput(data: string): void {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
102
|
+
// M2-1: keys come from the central `overlay:*` keyspace (remappable via
|
|
103
|
+
// `.crew/config.json` → keybindings["overlay:agent-picker:<action>"]).
|
|
104
|
+
switch (overlayActionForKey("agent-picker", data)) {
|
|
105
|
+
case "close":
|
|
106
|
+
this.done(undefined);
|
|
107
|
+
return;
|
|
108
|
+
case "up":
|
|
109
|
+
this.selected = Math.max(0, this.selected - 1);
|
|
110
|
+
return;
|
|
111
|
+
case "down":
|
|
112
|
+
this.selected = Math.min(Math.max(0, this.agents.length - 1), this.selected + 1);
|
|
113
|
+
return;
|
|
114
|
+
case "select": {
|
|
115
|
+
const agent = this.agents[this.selected];
|
|
116
|
+
this.done(agent ? { agentId: agent.taskId } : undefined);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
64
119
|
}
|
|
65
120
|
}
|
|
66
121
|
}
|
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Confirm dialog overlay — RAIL design language (M4/E1, 2026-09-16).
|
|
3
|
+
*
|
|
4
|
+
* ┏ CONFIRM ▸ Delete?
|
|
5
|
+
* ┃ Danger
|
|
6
|
+
* ┗ Y confirm · N/Esc cancel
|
|
7
|
+
*
|
|
8
|
+
* The rounded `╭─╮├─┤│╰─╯` box (and its inline `├───┤` rules) is retired; the
|
|
9
|
+
* surface is now canopy / `┃` body / `┗` close cap. The rail colour carries the
|
|
10
|
+
* danger level (high → error, medium → warning, else neutral border) instead of
|
|
11
|
+
* colouring only the title, and the hint is built by `formatHint` (cancel LAST,
|
|
12
|
+
* keys through `keyToken` → `Esc`, never `ESC`).
|
|
13
|
+
*
|
|
14
|
+
* Keys/behaviour are unchanged: same `overlay:*` keyspace dispatch, Enter stays
|
|
15
|
+
* dual-role exactly as before.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { truncate } from "../../utils/visual.ts";
|
|
19
|
+
import { overlayActionForKey } from "../keybinding-map.ts";
|
|
20
|
+
import { canopyLine, formatHint, RAIL, type RailSlot, railLine } from "../rail.ts";
|
|
4
21
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
5
22
|
|
|
6
23
|
export interface ConfirmOptions {
|
|
@@ -26,36 +43,45 @@ export class ConfirmOverlay {
|
|
|
26
43
|
}
|
|
27
44
|
|
|
28
45
|
render(width: number): string[] {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
46
|
+
if (width < 6) return [];
|
|
47
|
+
const theme = this.theme;
|
|
48
|
+
const budget = width - 2;
|
|
49
|
+
// Rail colour = state (§1): the danger level is the state of a confirm.
|
|
50
|
+
const slot: RailSlot = this.opts.dangerLevel === "high" ? "error" : this.opts.dangerLevel === "medium" ? "warning" : "border";
|
|
51
|
+
const hint =
|
|
52
|
+
this.opts.defaultAction === "confirm"
|
|
53
|
+
? formatHint([
|
|
54
|
+
[["enter", "y"], "confirm"],
|
|
55
|
+
[["n", "escape"], "cancel"],
|
|
56
|
+
])
|
|
57
|
+
: formatHint([
|
|
58
|
+
["y", "confirm"],
|
|
59
|
+
[["enter", "n", "escape"], "cancel"],
|
|
60
|
+
]);
|
|
33
61
|
const bodyLines = (this.opts.body ?? "").split(/\r?\n/).filter(Boolean);
|
|
34
|
-
const lines = [
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
),
|
|
41
|
-
`├${"─".repeat(innerWidth)}┤`,
|
|
42
|
-
`│ ${pad(truncate(this.theme.fg("dim", hint), innerWidth - 1), innerWidth - 1)}│`,
|
|
43
|
-
`╰${"─".repeat(innerWidth)}╯`,
|
|
44
|
-
];
|
|
45
|
-
const box = new Box(0, 0);
|
|
46
|
-
for (const line of lines) box.addChild(new Text(line));
|
|
47
|
-
return box.render(width);
|
|
62
|
+
const lines: string[] = [canopyLine({ word: "CONFIRM", subject: this.opts.title ?? "?", theme, budget, slot })];
|
|
63
|
+
for (const line of bodyLines.length ? bodyLines : ["Are you sure?"]) {
|
|
64
|
+
lines.push(railLine(RAIL.body, slot, truncate(line, budget), theme, budget));
|
|
65
|
+
}
|
|
66
|
+
lines.push(railLine(RAIL.close, slot, theme.fg("dim", hint), theme, budget));
|
|
67
|
+
return lines;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
70
|
handleInput(data: string): void {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
// M2-1: keys come from the central `overlay:*` keyspace (remappable via
|
|
72
|
+
// `.crew/config.json` → keybindings["overlay:confirm:<action>"]).
|
|
73
|
+
switch (overlayActionForKey("confirm", data)) {
|
|
74
|
+
case "confirm":
|
|
75
|
+
this.done(true);
|
|
76
|
+
return;
|
|
77
|
+
// Enter is dual-role: it confirms when the overlay defaults to confirm
|
|
78
|
+
// and cancels otherwise (byte-identical to the pre-M2-1 chain).
|
|
79
|
+
case "submit":
|
|
80
|
+
this.done(this.opts.defaultAction === "confirm");
|
|
81
|
+
return;
|
|
82
|
+
case "cancel":
|
|
83
|
+
this.done(false);
|
|
84
|
+
return;
|
|
58
85
|
}
|
|
59
|
-
if (data === "n" || data === "N" || matchesKey(data, "escape") || data === "q" || matchesKey(data, "return")) this.done(false);
|
|
60
86
|
}
|
|
61
87
|
}
|
|
@@ -1,52 +1,51 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* K-1 — dashboard keybinding cheatsheet overlay.
|
|
2
|
+
* K-1 — dashboard keybinding cheatsheet overlay (RAIL design language, M4/E1).
|
|
3
3
|
*
|
|
4
4
|
* Toggled by `?` (bound in keybinding-map.ts). Renders the dashboard's
|
|
5
|
-
* keybindings grouped by scope (general / navigation / panes /
|
|
6
|
-
* mailbox / health) directly from `DASHBOARD_KEYS`, so
|
|
7
|
-
* place is reflected here automatically.
|
|
5
|
+
* keybindings grouped by scope (general / navigation / panes / schedules /
|
|
6
|
+
* plan / run actions / mailbox / health) directly from `DASHBOARD_KEYS`, so
|
|
7
|
+
* adding a key in one place is reflected here automatically.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* so the overlay's border column aligns with the dashboard's stable-height
|
|
11
|
-
* blank-padding rows (preserves the "stable overlay height" strength — the
|
|
12
|
-
* blank rows injected by the dashboard's pad/trim must land in the same
|
|
13
|
-
* column as this overlay's `│` borders).
|
|
9
|
+
* Grammar (docs/UI-DESIGN-SYSTEM.md §2.E):
|
|
14
10
|
*
|
|
15
|
-
*
|
|
11
|
+
* ┏ HELP ▸ dashboard
|
|
12
|
+
* ┣ GENERAL
|
|
13
|
+
* ┃ Q/Esc close dashboard Enter/S/Tab/Space open run status
|
|
14
|
+
* ┗ ? toggle · Esc dismiss
|
|
15
|
+
*
|
|
16
|
+
* The legacy rounded box (`╭─╮│╰─╯├─┤`) is retired: the overlay is a canopy +
|
|
17
|
+
* `┣ SECTION` group headers + `┃` rows. Each group title is a RAIL section
|
|
18
|
+
* (replacing the old accent-only row), and the always-visible column key/token
|
|
19
|
+
* pair now goes through the SHARED `keyToken()` from `src/ui/rail.ts`.
|
|
20
|
+
*
|
|
21
|
+
* Why that mattered: the old file-local `keyToken` had no `case "\t"` branch, so
|
|
22
|
+
* `DASHBOARD_KEYS.select`'s raw tab byte was rendered verbatim into the table
|
|
23
|
+
* and knocked the row's alignment out by one column (audit §4). The shared
|
|
24
|
+
* tokenizer maps it to `Tab` (and `Esc`/`↑/↓`/`PgUp`/`Space` consistently).
|
|
25
|
+
*
|
|
26
|
+
* Width contract: `render(width)` — every line is built through `railLine`,
|
|
27
|
+
* which owns the `glyph + space` separator and never emits a line wider than
|
|
28
|
+
* the render width. Nothing is baked at build time.
|
|
16
29
|
*/
|
|
17
30
|
|
|
18
|
-
import { pad, truncate } from "../../utils/visual.ts";
|
|
31
|
+
import { pad, truncate, visibleWidth } from "../../utils/visual.ts";
|
|
19
32
|
import { DASHBOARD_KEYS } from "../keybinding-map.ts";
|
|
20
|
-
import {
|
|
33
|
+
import { canopyLine, formatHint, keyToken, RAIL, railLine, sectionLine } from "../rail.ts";
|
|
21
34
|
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
22
35
|
|
|
23
|
-
/** Translate a raw key sequence into a readable token for the cheatsheet.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
case "\u001b":
|
|
27
|
-
return "Esc";
|
|
28
|
-
case "\u001b[A":
|
|
29
|
-
return "↑";
|
|
30
|
-
case "\u001b[B":
|
|
31
|
-
return "↓";
|
|
32
|
-
case "\r":
|
|
33
|
-
case "\n":
|
|
34
|
-
return "Enter";
|
|
35
|
-
default:
|
|
36
|
-
return key;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
36
|
+
/** Translate a raw key sequence into a readable token for the cheatsheet.
|
|
37
|
+
* Delegates to the SHARED `keyToken` (rail.ts) so the cheatsheet, the footer
|
|
38
|
+
* hints and the tool cards can never drift apart. */
|
|
40
39
|
function keyList(keys: readonly string[]): string {
|
|
41
|
-
return [...keys
|
|
40
|
+
return [...new Set(keys.map((key) => keyToken(key)))].join("/");
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
interface HelpEntry {
|
|
43
|
+
export interface HelpEntry {
|
|
45
44
|
readonly keys: string;
|
|
46
45
|
readonly label: string;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
interface HelpGroup {
|
|
48
|
+
export interface HelpGroup {
|
|
50
49
|
readonly title: string;
|
|
51
50
|
readonly entries: readonly HelpEntry[];
|
|
52
51
|
}
|
|
@@ -62,7 +61,24 @@ const ROOT_LABELS: Record<string, string> = {
|
|
|
62
61
|
transcript: "transcript",
|
|
63
62
|
liveConversation: "live conv",
|
|
64
63
|
reload: "reload",
|
|
65
|
-
|
|
64
|
+
// NOTE: `progressToggle` deliberately has no label here — the `p` binding
|
|
65
|
+
// had no visible effect (P1-5, phantom key) and is being removed from
|
|
66
|
+
// keybinding-map.ts. Entries derive from Object.entries(DASHBOARD_KEYS.root),
|
|
67
|
+
// so the row disappears entirely once that key is gone.
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const SCHEDULES_LABELS: Record<string, string> = {
|
|
71
|
+
toggle: "toggle",
|
|
72
|
+
runNow: "run now",
|
|
73
|
+
details: "details",
|
|
74
|
+
delete: "delete",
|
|
75
|
+
refresh: "refresh",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const PLAN_LABELS: Record<string, string> = {
|
|
79
|
+
approve: "approve",
|
|
80
|
+
deny: "deny",
|
|
81
|
+
diff: "diff",
|
|
66
82
|
};
|
|
67
83
|
|
|
68
84
|
const MAILBOX_LABELS: Record<string, string> = {
|
|
@@ -88,6 +104,16 @@ function buildHelpGroups(): HelpGroup[] {
|
|
|
88
104
|
keys: keyList(keys),
|
|
89
105
|
label: ROOT_LABELS[name] ?? name,
|
|
90
106
|
}));
|
|
107
|
+
// P1-14: pane 8 (schedules) and pane 7 (plan) have pane-scoped action keys
|
|
108
|
+
// that were missing from this cheatsheet entirely.
|
|
109
|
+
const scheduleEntries: HelpEntry[] = Object.entries(DASHBOARD_KEYS.schedules).map(([name, keys]) => ({
|
|
110
|
+
keys: keyList(keys),
|
|
111
|
+
label: SCHEDULES_LABELS[name] ?? name,
|
|
112
|
+
}));
|
|
113
|
+
const planEntries: HelpEntry[] = Object.entries(DASHBOARD_KEYS.plan).map(([name, keys]) => ({
|
|
114
|
+
keys: keyList(keys),
|
|
115
|
+
label: PLAN_LABELS[name] ?? name,
|
|
116
|
+
}));
|
|
91
117
|
const mailboxEntries: HelpEntry[] = Object.entries(DASHBOARD_KEYS.mailbox)
|
|
92
118
|
.filter(([name]) => name !== "openDetail")
|
|
93
119
|
.map(([name, keys]) => ({
|
|
@@ -117,12 +143,18 @@ function buildHelpGroups(): HelpGroup[] {
|
|
|
117
143
|
title: "Navigation",
|
|
118
144
|
entries: [
|
|
119
145
|
{
|
|
120
|
-
|
|
146
|
+
// Union of the up/down aliases, deduped through the shared
|
|
147
|
+
// `keyToken` (`k`/`up` and `j`/`down` collapse to one `↑/↓`).
|
|
148
|
+
keys: [
|
|
149
|
+
...new Set([...DASHBOARD_KEYS.navigation.up, ...DASHBOARD_KEYS.navigation.down].map((key) => keyToken(key))),
|
|
150
|
+
].join("/"),
|
|
121
151
|
label: "move selection",
|
|
122
152
|
},
|
|
123
153
|
],
|
|
124
154
|
},
|
|
125
155
|
{ title: "Panes", entries: paneEntries },
|
|
156
|
+
{ title: "Schedules (pane 8)", entries: scheduleEntries },
|
|
157
|
+
{ title: "Plan (pane 7)", entries: planEntries },
|
|
126
158
|
{ title: "Run actions", entries: rootEntries },
|
|
127
159
|
{ title: "Mailbox (pane 3)", entries: mailboxEntries },
|
|
128
160
|
{
|
|
@@ -138,6 +170,21 @@ function buildHelpGroups(): HelpGroup[] {
|
|
|
138
170
|
];
|
|
139
171
|
}
|
|
140
172
|
|
|
173
|
+
/** Test seam (mirrors `__test__openPane` / `__resetInlinePanelForTest`): the
|
|
174
|
+
* generated groups, so tests can assert keys/labels without parsing the
|
|
175
|
+
* rendered surface. The overlay itself renders through `buildHelpGroups()`. */
|
|
176
|
+
export function __test__buildHelpGroups(): HelpGroup[] {
|
|
177
|
+
return buildHelpGroups();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Width of the key column: the widest rendered token list, capped at 20 so a
|
|
181
|
+
* future key set cannot push the labels off the row. Derived from the data —
|
|
182
|
+
* never a hand-tuned constant that silently misaligns when a key is added. */
|
|
183
|
+
function keyColumnWidth(groups: readonly HelpGroup[]): number {
|
|
184
|
+
const widths = groups.flatMap((group) => group.entries.map((entry) => visibleWidth(entry.keys)));
|
|
185
|
+
return Math.min(20, Math.max(1, ...widths));
|
|
186
|
+
}
|
|
187
|
+
|
|
141
188
|
export class HelpOverlay {
|
|
142
189
|
private readonly theme: CrewTheme;
|
|
143
190
|
|
|
@@ -150,28 +197,36 @@ export class HelpOverlay {
|
|
|
150
197
|
}
|
|
151
198
|
|
|
152
199
|
render(width: number): string[] {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
const bot = fg("border", `╰${bar}╯`);
|
|
162
|
-
const keyCol = 9;
|
|
163
|
-
const lines: string[] = [top, row(`${fg("accent", "pi-crew dashboard")} ${fg("dim", "— key reference (press ? to close)")}`), mid];
|
|
164
|
-
for (const group of buildHelpGroups()) {
|
|
165
|
-
lines.push(row(fg("accent", group.title)));
|
|
200
|
+
if (width < 6) return [];
|
|
201
|
+
const theme = this.theme;
|
|
202
|
+
const budget = width - 2;
|
|
203
|
+
const groups = buildHelpGroups();
|
|
204
|
+
const keyCol = keyColumnWidth(groups);
|
|
205
|
+
const lines: string[] = [canopyLine({ word: "HELP", subject: "dashboard", theme, budget })];
|
|
206
|
+
for (const group of groups) {
|
|
207
|
+
lines.push(sectionLine({ name: group.title, theme, budget }));
|
|
166
208
|
for (let i = 0; i < group.entries.length; i += 2) {
|
|
167
209
|
const pair = group.entries.slice(i, i + 2);
|
|
168
|
-
const cell = (entry: HelpEntry) =>
|
|
169
|
-
|
|
210
|
+
const cell = (entry: HelpEntry) =>
|
|
211
|
+
`${theme.bold(pad(truncate(entry.keys, keyCol), keyCol))} ${theme.fg("dim", truncate(entry.label, 16))}`;
|
|
212
|
+
lines.push(railLine(RAIL.body, "border", truncate(pair.map(cell).join(" "), budget), theme, budget));
|
|
170
213
|
}
|
|
171
214
|
}
|
|
172
|
-
lines.push(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
215
|
+
lines.push(
|
|
216
|
+
railLine(
|
|
217
|
+
RAIL.close,
|
|
218
|
+
"border",
|
|
219
|
+
theme.fg(
|
|
220
|
+
"dim",
|
|
221
|
+
formatHint([
|
|
222
|
+
["?", "toggle"],
|
|
223
|
+
["escape", "dismiss"],
|
|
224
|
+
]),
|
|
225
|
+
),
|
|
226
|
+
theme,
|
|
227
|
+
budget,
|
|
228
|
+
),
|
|
229
|
+
);
|
|
230
|
+
return lines;
|
|
176
231
|
}
|
|
177
232
|
}
|