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
|
@@ -3,8 +3,9 @@ import { matchesKey } from "@earendil-works/pi-tui";
|
|
|
3
3
|
import { agentOutputPath, readCrewAgents } from "../runtime/crew-agent-records.ts";
|
|
4
4
|
import type { TeamRunManifest } from "../state/types.ts";
|
|
5
5
|
import { resolveRealContainedPath } from "../utils/safe-paths.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { truncate, truncateToVisualLines, truncateToVisualLinesTail } from "../utils/visual.ts";
|
|
7
7
|
import type { InteractiveComponent } from "./component.ts";
|
|
8
|
+
import { canopyLine, formatHint, overflowHint, RAIL, railLine } from "./rail.ts";
|
|
8
9
|
import { renderDiff } from "./render-diff.ts";
|
|
9
10
|
import { colorForStatus, iconForStatus, type RunStatus } from "./status-colors.ts";
|
|
10
11
|
import { highlightCode, highlightJson } from "./syntax-highlight.ts";
|
|
@@ -244,6 +245,21 @@ interface ViewerState {
|
|
|
244
245
|
sourceLen: number;
|
|
245
246
|
}
|
|
246
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Overlay footer hints in the ONE rail hint format (`formatHint`): keys `label`
|
|
250
|
+
* pairs joined by ` · `, close action LAST, `Esc` spelled via `keyToken`.
|
|
251
|
+
* The bound keys/actions are unchanged — `handleInput` still reads j/k, PgUp/
|
|
252
|
+
* PgDn, g/G, a, f and q/Esc exactly as before.
|
|
253
|
+
*/
|
|
254
|
+
const VIEWER_HINTS = formatHint([
|
|
255
|
+
[["j", "k"], "scroll"],
|
|
256
|
+
[["pageup", "pagedown"], "page"],
|
|
257
|
+
["g/G", "top/bottom"],
|
|
258
|
+
["a", "auto"],
|
|
259
|
+
["f", "full/tail"],
|
|
260
|
+
[["q", "escape"], "close"],
|
|
261
|
+
]);
|
|
262
|
+
|
|
247
263
|
function renderViewerBase(state: ViewerState, width: number, lines: string[], title: string, subtitle: string): string[] {
|
|
248
264
|
const inner = Math.max(20, width - 4);
|
|
249
265
|
// PERF (2026-08-24): transcripts grow to thousands of lines; wrapping every
|
|
@@ -289,21 +305,23 @@ function renderViewerBase(state: ViewerState, width: number, lines: string[], ti
|
|
|
289
305
|
const totalLines = tailWindow ? skippedCount + visible.length : visualLines.length;
|
|
290
306
|
const totalLabel = tailWindow && skippedCount > 0 ? `≥ ${totalLines}` : `${totalLines}`;
|
|
291
307
|
const statusLine = `${totalLabel} lines · ${totalLines ? Math.round(((state.scroll + visible.length) / totalLines) * 100) : 100}% · auto-scroll ${state.autoScroll ? "on" : "off"}`;
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
// RAIL frame (design system §2.E): `┏ NAME ▸ SUBJECT` canopy, `┃` body rows,
|
|
309
|
+
// `┗ <outcome/hint>` close. The rounded `╭─╮│╰─╯` box is retired. This
|
|
310
|
+
// viewer is a standalone overlay that paints its own frame, so the rail
|
|
311
|
+
// glyphs are correct here (dashboard PANES must not draw them).
|
|
312
|
+
const budget = Math.max(18, width - 2);
|
|
313
|
+
const row = (text: string) => railLine(RAIL.body, "border", text, state.theme, budget);
|
|
294
314
|
const linesOut: string[] = [
|
|
295
|
-
|
|
296
|
-
row(
|
|
297
|
-
row(fg("dim", "j/k scroll · PgUp/PgDn · g/G top/bottom · a auto · f full/tail · q close")),
|
|
298
|
-
fg("border", `├${"─".repeat(inner + 2)}┤`),
|
|
299
|
-
...visible.map(row),
|
|
300
|
-
fg("border", `├${"─".repeat(inner + 2)}┤`),
|
|
301
|
-
row(fg("dim", statusLine)),
|
|
302
|
-
fg("border", `╰${"─".repeat(inner + 2)}╯`),
|
|
315
|
+
canopyLine({ word: "TRANSCRIPT", subject: title, theme: state.theme, budget, right: subtitle }),
|
|
316
|
+
row(state.theme.fg("dim", VIEWER_HINTS)),
|
|
303
317
|
];
|
|
304
318
|
if (skippedCount > 0) {
|
|
305
|
-
|
|
319
|
+
// ONE overflow dialect (`▲ n above` / `▼ m below`) instead of the legacy
|
|
320
|
+
// `… (n lines truncated above` (which also never closed its paren).
|
|
321
|
+
linesOut.push(row(state.theme.fg("muted", overflowHint(skippedCount, 0, state.theme))));
|
|
306
322
|
}
|
|
323
|
+
for (const visibleLine of visible) linesOut.push(row(visibleLine));
|
|
324
|
+
linesOut.push(railLine(RAIL.close, "border", state.theme.fg("dim", statusLine), state.theme, budget));
|
|
307
325
|
return linesOut.map((line) => truncate(line, width));
|
|
308
326
|
}
|
|
309
327
|
|
package/src/ui/widget/index.ts
CHANGED
|
@@ -14,17 +14,22 @@ import { truncate } from "../../utils/visual.ts";
|
|
|
14
14
|
import { panelRowsFromRuns } from "../inline-panel/panel-rows.ts";
|
|
15
15
|
import { panelDisplayState, setPanelRowsProvider, subscribePanelChange } from "../inline-panel/panel-store.ts";
|
|
16
16
|
import { requestRender, requestRenderTarget, setExtensionWidget } from "../pi-ui-compat.ts";
|
|
17
|
+
import type { RailSlot } from "../rail.ts";
|
|
17
18
|
import type { OverlaySchedulerHandle } from "../shared-overlay-scheduler.ts";
|
|
18
19
|
import { registerOverlayScheduler } from "../shared-overlay-scheduler.ts";
|
|
19
20
|
import type { RunSnapshotCache } from "../snapshot-types.ts";
|
|
20
|
-
import { spinnerBucket
|
|
21
|
+
import { spinnerBucket } from "../spinner.ts";
|
|
21
22
|
import type { CrewTheme } from "../theme-adapter.ts";
|
|
22
23
|
import { asCrewTheme, subscribeThemeChange } from "../theme-adapter.ts";
|
|
23
24
|
import { buildTaskListLines } from "./task-list.ts";
|
|
24
25
|
import { activeWidgetRuns, statusSummary } from "./widget-model.ts";
|
|
25
|
-
import { buildWidgetLines, colorWidgetLine,
|
|
26
|
+
import { buildWidgetLines, colorWidgetLine, idleWidgetLine, renderLines, schedulesWidgetLine, widgetRailSlot } from "./widget-renderer.ts";
|
|
26
27
|
import type { CrewWidgetModel, CrewWidgetState, WidgetRun } from "./widget-types.ts";
|
|
27
28
|
|
|
29
|
+
export {
|
|
30
|
+
NOTIFICATION_BADGE_CAP,
|
|
31
|
+
notificationBadge,
|
|
32
|
+
} from "./widget-formatters.ts";
|
|
28
33
|
export { activeWidgetRuns, statusSummary } from "./widget-model.ts";
|
|
29
34
|
export {
|
|
30
35
|
buildWidgetLines as buildCrewWidgetLines,
|
|
@@ -39,27 +44,6 @@ export type {
|
|
|
39
44
|
WidgetRun,
|
|
40
45
|
} from "./widget-types.ts";
|
|
41
46
|
|
|
42
|
-
/**
|
|
43
|
-
* Resolve the real render width for widget lines, in priority order:
|
|
44
|
-
* 1. explicit `width` argument (e.g. from caller that already knows terminal width)
|
|
45
|
-
* 2. `process.stdout.columns` (works in Node when stdout is a TTY)
|
|
46
|
-
* 3. `DEFAULT_WIDGET_WIDTH` (100) — last-resort fallback so we never paint
|
|
47
|
-
* a line wider than the smallest expected TUI.
|
|
48
|
-
*
|
|
49
|
-
* Callers SHOULD pass the width they already hold (e.g. `WidgetRender.render(width)`
|
|
50
|
-
* in this file already receives one). This helper exists for paths that don't.
|
|
51
|
-
*/
|
|
52
|
-
export function getRenderWidth(width?: number): number {
|
|
53
|
-
if (Number.isFinite(width) && width! > 0) return Math.floor(width!);
|
|
54
|
-
const stdoutCols = (globalThis as { process?: { stdout?: { columns?: number } } }).process?.stdout?.columns;
|
|
55
|
-
if (Number.isFinite(stdoutCols) && stdoutCols! > 0) return Math.floor(stdoutCols!);
|
|
56
|
-
return DEFAULT_WIDGET_WIDTH;
|
|
57
|
-
}
|
|
58
|
-
export {
|
|
59
|
-
NOTIFICATION_BADGE_CAP,
|
|
60
|
-
notificationBadge,
|
|
61
|
-
} from "./widget-formatters.ts";
|
|
62
|
-
|
|
63
47
|
// ── Constants ─────────────────────────────────────────────────────────
|
|
64
48
|
|
|
65
49
|
const MAX_LINES_DEFAULT = DEFAULT_UI.widgetMaxLines;
|
|
@@ -270,9 +254,9 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
270
254
|
return sig;
|
|
271
255
|
}
|
|
272
256
|
|
|
273
|
-
private colorize(lines: string[], width: number): string[] {
|
|
257
|
+
private colorize(lines: string[], width: number, slot: RailSlot = "border"): string[] {
|
|
274
258
|
return renderLines(
|
|
275
|
-
lines.map((line, index) => colorWidgetLine(line, index, this.theme)),
|
|
259
|
+
lines.map((line, index) => colorWidgetLine(line, index, this.theme, slot)),
|
|
276
260
|
width,
|
|
277
261
|
);
|
|
278
262
|
}
|
|
@@ -334,14 +318,13 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
334
318
|
this.cachedBuildSignatureAt = now;
|
|
335
319
|
}
|
|
336
320
|
const signature = `${sigBase}:${this.model.notificationCount ?? 0}`;
|
|
337
|
-
const runningGlyph = spinnerFrame("widget-header");
|
|
338
321
|
|
|
339
322
|
// Task-list variant: the run's plan above the editor (task-list.ts).
|
|
340
323
|
// No panel state, no spinner glyph — the list changes only on task
|
|
341
324
|
// transitions, which the run signature already covers.
|
|
342
325
|
if (this.variant === "tasks") {
|
|
343
326
|
if (this.cacheSignature !== signature || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
|
|
344
|
-
this.cachedBaseLines = buildTaskListLines(runs, width);
|
|
327
|
+
this.cachedBaseLines = buildTaskListLines(runs, width, this.theme);
|
|
345
328
|
this.cachedLines = this.colorize(this.cachedBaseLines, width);
|
|
346
329
|
this.cachedWidth = width;
|
|
347
330
|
this.cachedTheme = this.theme;
|
|
@@ -372,11 +355,12 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
372
355
|
const schedLine = schedulesWidgetLine(this.model.cwd, schedNow);
|
|
373
356
|
const signatureWithPanel = `${signature}|panel:${panel.selectedTaskId ?? ""}/${panel.viewedTaskId ?? ""}/${panel.focused ? 1 : 0}|sched:${schedLine ?? ""}`;
|
|
374
357
|
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
358
|
+
// RAIL (§2.B): the dock is ONE row whose identity is `CREW ▸ <subject>`
|
|
359
|
+
// and whose leading `┃` carries the AGGREGATE run state as its colour.
|
|
360
|
+
// The spinner rides INSIDE the row (buildWidgetLines composes it), so the
|
|
361
|
+
// old "swap line[0]'s first character" hack is gone — it would eat the
|
|
362
|
+
// rail glyph. The cache key still carries the spinner bucket when a worker
|
|
363
|
+
// is running, so the row rebuilds once per frame and animates.
|
|
380
364
|
if (this.cacheSignature !== signatureWithPanel || width !== this.cachedWidth || this.cachedTheme !== this.theme) {
|
|
381
365
|
this.cachedBaseLines = buildWidgetLines(
|
|
382
366
|
this.model.cwd,
|
|
@@ -385,12 +369,9 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
385
369
|
runs,
|
|
386
370
|
this.model.notificationCount ?? 0,
|
|
387
371
|
width,
|
|
388
|
-
{
|
|
389
|
-
)
|
|
390
|
-
|
|
391
|
-
return line;
|
|
392
|
-
});
|
|
393
|
-
this.cachedLines = this.colorize(this.cachedBaseLines, width);
|
|
372
|
+
{ now: schedNow, ...panel },
|
|
373
|
+
);
|
|
374
|
+
this.cachedLines = this.colorize(this.cachedBaseLines, width, widgetRailSlot(runs));
|
|
394
375
|
this.cachedWidth = width;
|
|
395
376
|
this.cachedTheme = this.theme;
|
|
396
377
|
this.cacheSignature = signatureWithPanel;
|
|
@@ -408,20 +389,22 @@ class CrewWidgetComponent implements WidgetComponent {
|
|
|
408
389
|
// meaningful on run-transition frames, which always have runs>0 and take
|
|
409
390
|
// a different branch. At zero runs the only paintable content is the
|
|
410
391
|
// schedules line (or nothing).
|
|
411
|
-
// Single-line contract (round 3): the keep-alive row
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
392
|
+
// Single-line contract (round 3): the keep-alive row is the SAME
|
|
393
|
+
// `┃ CREW ▸ idle · ⏰ … ···· ↓·enter` row buildWidgetLines paints at zero
|
|
394
|
+
// runs (idleWidgetLine) — one helper, one row shape.
|
|
395
|
+
// Zero runs + no schedules = nothing to paint. Guard BEFORE composing:
|
|
396
|
+
// without it the row painted the literal string `undefined — ↓·enter`
|
|
397
|
+
// (live bug 2026-09-16: right after a team run finished,
|
|
398
|
+
// activeWidgetRuns() empties and schedulesWidgetLine() returns
|
|
399
|
+
// undefined). widget-renderer.buildWidgetLines has always guarded this
|
|
400
|
+
// branch; the component path did not.
|
|
401
|
+
if (!schedLine) return [];
|
|
402
|
+
const zeroLine = idleWidgetLine(schedLine, panel.focused === true, width);
|
|
403
|
+
if (!zeroLine) return [];
|
|
404
|
+
return [truncate(colorWidgetLine(zeroLine, 0, this.theme), width)];
|
|
417
405
|
}
|
|
418
406
|
|
|
419
407
|
this.ensureTruncated(width);
|
|
420
|
-
if (!compactDock) {
|
|
421
|
-
const updatedHeader = `${runningGlyph}${this.cachedBaseLines[0]?.slice(1) ?? ""}`;
|
|
422
|
-
this.cachedLines[0] = truncate(colorWidgetLine(updatedHeader, 0, this.theme), width);
|
|
423
|
-
this.truncatedLines[0] = truncate(this.cachedLines[0], width);
|
|
424
|
-
}
|
|
425
408
|
return this.truncatedLines;
|
|
426
409
|
}
|
|
427
410
|
}
|
|
@@ -451,7 +434,6 @@ export function updateCrewWidget(
|
|
|
451
434
|
}
|
|
452
435
|
|
|
453
436
|
const runs = activeWidgetRuns(ctx.cwd, manifestCache, snapshotCache, preloadedManifests, workspaceId);
|
|
454
|
-
const rowStyle = config?.widgetRowStyle ?? DEFAULT_UI.widgetRowStyle;
|
|
455
437
|
// The inline panel navigates the same run list the widget paints, and this is
|
|
456
438
|
// the only place already holding the manifest/snapshot caches — so the row
|
|
457
439
|
// projection is registered here instead of re-reading state on every keypress.
|
|
@@ -524,7 +506,6 @@ export function updateCrewWidget(
|
|
|
524
506
|
snapshotCache,
|
|
525
507
|
preloadManifests: preloadedManifests,
|
|
526
508
|
workspaceId,
|
|
527
|
-
rowStyle,
|
|
528
509
|
};
|
|
529
510
|
else {
|
|
530
511
|
state.model.cwd = ctx.cwd;
|
|
@@ -535,7 +516,6 @@ export function updateCrewWidget(
|
|
|
535
516
|
state.model.snapshotCache = snapshotCache;
|
|
536
517
|
state.model.preloadManifests = preloadedManifests;
|
|
537
518
|
state.model.workspaceId = workspaceId;
|
|
538
|
-
state.model.rowStyle = rowStyle;
|
|
539
519
|
}
|
|
540
520
|
|
|
541
521
|
if (needsWidgetInstall) {
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* task-list.ts — the run's plan, painted above the editor
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* task-list.ts — the run's plan, painted above the editor, in the RAIL card
|
|
3
|
+
* grammar (design system §2.A): a `┏ PLAN ▸ <team/workflow>` canopy carrying
|
|
4
|
+
* the progress gauge, one `┃` row per task in PLAN ORDER with task numbers
|
|
5
|
+
* (#1, #2, …), and a `┗ <counts>` cap.
|
|
5
6
|
* Completed rows are dimmed and struck through; the task actively executing
|
|
6
7
|
* carries a spinner frame with elapsed time and token counts; queued rows
|
|
7
8
|
* name the dependencies they wait on.
|
|
8
9
|
*
|
|
9
10
|
* Example:
|
|
10
11
|
* ```
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* ┏ PLAN ▸ default/build ···················· ▕████▎░░░░░▏ 2/5
|
|
13
|
+
* ┃ ✔ #1 Design the flux capacitor
|
|
14
|
+
* ┃ ⠙ #2 Wire the overlay scroll (2m 49s · ↑ 4.1k ↓ 1.2k)
|
|
15
|
+
* ┃ ◻ #3 Run the suite › blocked by #2
|
|
16
|
+
* ┃ ◻ #4 Report the result
|
|
17
|
+
* ┗ 2 done · 2 in progress
|
|
16
18
|
* ```
|
|
17
19
|
*
|
|
18
20
|
* Pure plan surface: which worker/agent/model runs a task is the dock's
|
|
@@ -24,11 +26,21 @@
|
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
import type { TeamTaskState } from "../../state/types.ts";
|
|
27
|
-
import {
|
|
29
|
+
import { visibleWidth } from "../../utils/visual.ts";
|
|
30
|
+
import { formatCount } from "../format-helpers.ts";
|
|
31
|
+
import { canopyLine, gaugeBar, overflowHint, RAIL, railLine, statusSlot } from "../rail.ts";
|
|
28
32
|
import { spinnerFrame } from "../spinner.ts";
|
|
33
|
+
import { asCrewTheme, type CrewTheme } from "../theme-adapter.ts";
|
|
29
34
|
import { shortRunLabel } from "./widget-model.ts";
|
|
30
35
|
import type { WidgetRun } from "./widget-types.ts";
|
|
31
36
|
|
|
37
|
+
/** Plain (uncoloured) default — the widget component passes the real theme. */
|
|
38
|
+
const PLAIN_THEME = asCrewTheme(undefined);
|
|
39
|
+
|
|
40
|
+
/** Gauge width on the canopy line (eighth-block precision, so it stays
|
|
41
|
+
* truthful even at 10 cells). */
|
|
42
|
+
const GAUGE_WIDTH = 12;
|
|
43
|
+
|
|
32
44
|
/** Task rows painted before the "… and N more" overflow line (pi-tasks
|
|
33
45
|
* defaults its `maxVisible` to 10). */
|
|
34
46
|
const MAX_TASK_ROWS = 10;
|
|
@@ -125,21 +137,16 @@ function blockedSuffix(task: TeamTaskState, tasks: readonly TeamTaskState[], num
|
|
|
125
137
|
}
|
|
126
138
|
|
|
127
139
|
/** One line per task: `#n title` behind its status glyph — pi-tasks style.
|
|
128
|
-
* Completed titles are dimmed and struck through.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
taskNumber: number,
|
|
132
|
-
tasks: readonly TeamTaskState[],
|
|
133
|
-
numberById: Map<string, number>,
|
|
134
|
-
width: number,
|
|
135
|
-
): string {
|
|
140
|
+
* Completed titles are dimmed and struck through. Returns the ROW CONTENT:
|
|
141
|
+
* the rail glyph (`┃ `) is owned by `railLine`. */
|
|
142
|
+
function taskRow(task: TeamTaskState, taskNumber: number, tasks: readonly TeamTaskState[], numberById: Map<string, number>): string {
|
|
136
143
|
const title = taskTitle(task);
|
|
137
144
|
const suffix = task.status === "running" ? runningSuffix(task) : blockedSuffix(task, tasks, numberById);
|
|
138
145
|
if (task.status === "completed") {
|
|
139
146
|
const struck = `${STRIKE_ON}${DIM_ON}#${taskNumber} ${title}${DIM_OFF}${STRIKE_OFF}`;
|
|
140
|
-
return
|
|
147
|
+
return `${taskStatusIcon(task)} ${struck}`;
|
|
141
148
|
}
|
|
142
|
-
return
|
|
149
|
+
return `${taskStatusIcon(task)} #${taskNumber} ${title}${suffix}`.trimEnd();
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
/** The run whose plan is shown: the first with unfinished work, else the first. */
|
|
@@ -163,15 +170,17 @@ function visibleTasks(tasks: readonly TeamTaskState[]): TeamTaskState[] {
|
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
/**
|
|
166
|
-
* Build the
|
|
167
|
-
*
|
|
173
|
+
* Build the plan card for the current runs: canopy (identity + gauge), one
|
|
174
|
+
* `┃` row per task, the overflow hint, and the `┗` counts cap. Empty when no
|
|
175
|
+
* run carries a tasks slice (nothing to say — the dock alone stays).
|
|
168
176
|
*/
|
|
169
|
-
export function buildTaskListLines(runs: readonly WidgetRun[], width: number): string[] {
|
|
177
|
+
export function buildTaskListLines(runs: readonly WidgetRun[], width: number, theme: CrewTheme = PLAIN_THEME): string[] {
|
|
170
178
|
const entry = primaryRun(runs);
|
|
171
179
|
if (!entry) return [];
|
|
172
180
|
const tasks = entry.snapshot?.tasks ?? [];
|
|
173
181
|
if (tasks.length === 0) return [];
|
|
174
182
|
|
|
183
|
+
const budget = Math.max(8, width - 2);
|
|
175
184
|
const numberById = new Map(tasks.map((task, index) => [task.id, index + 1]));
|
|
176
185
|
const done = tasks.filter((task) => task.status === "completed").length;
|
|
177
186
|
const dead = tasks.filter((task) => isDoneStatus(task.status) && task.status !== "completed").length;
|
|
@@ -179,20 +188,43 @@ export function buildTaskListLines(runs: readonly WidgetRun[], width: number): s
|
|
|
179
188
|
(task) => task.status === "running" || task.status === "waiting" || task.status === "needs_attention",
|
|
180
189
|
).length;
|
|
181
190
|
const open = tasks.filter((task) => task.status === "queued").length;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
const
|
|
191
|
+
const slot = statusSlot(entry.run.status);
|
|
192
|
+
|
|
193
|
+
// Canopy: `┏ PLAN ▸ <title>` + the progress gauge on the right, where a bar
|
|
194
|
+
// fits (a 12-cell bar plus its tally needs ~6 columns of clearance).
|
|
195
|
+
const title = shortRunLabel(entry.run);
|
|
196
|
+
const ratio = done / tasks.length;
|
|
197
|
+
const tally = `${done}/${tasks.length}`;
|
|
198
|
+
const gauge = `${gaugeBar(ratio, GAUGE_WIDTH, theme, dead > 0 ? "error" : "success")} ${tally}`;
|
|
199
|
+
const labelWidth = visibleWidth(`PLAN ${title}`) + 5; // word + ` ▸ `
|
|
200
|
+
const right = budget - labelWidth - 6 >= GAUGE_WIDTH + tally.length ? gauge : undefined;
|
|
201
|
+
const lines = [
|
|
202
|
+
canopyLine({
|
|
203
|
+
word: "PLAN",
|
|
204
|
+
subject: title,
|
|
205
|
+
theme,
|
|
206
|
+
budget,
|
|
207
|
+
slot,
|
|
208
|
+
right,
|
|
209
|
+
}),
|
|
210
|
+
];
|
|
189
211
|
|
|
190
212
|
const visible = visibleTasks(tasks);
|
|
191
213
|
for (const task of visible) {
|
|
192
|
-
lines.push(taskRow(task, numberById.get(task.id) ?? 0, tasks, numberById,
|
|
214
|
+
lines.push(railLine(RAIL.body, "border", taskRow(task, numberById.get(task.id) ?? 0, tasks, numberById), theme, budget));
|
|
193
215
|
}
|
|
194
|
-
|
|
195
|
-
|
|
216
|
+
const hidden = tasks.length - visible.length;
|
|
217
|
+
if (hidden > 0) {
|
|
218
|
+
// Canonical overflow dialect (RAIL §1) — the legacy `… and N more` is
|
|
219
|
+
// retired with the other three overflow spellings.
|
|
220
|
+
lines.push(railLine(RAIL.body, "border", overflowHint(0, hidden, theme), theme, budget));
|
|
196
221
|
}
|
|
222
|
+
|
|
223
|
+
// Close cap: the counts (formatCount → singular/plural; the participle
|
|
224
|
+
// labels pass an explicit plural so nothing renders as `2 dones`).
|
|
225
|
+
const counts = [formatCount(done, "done", "done")];
|
|
226
|
+
if (dead) counts.push(formatCount(dead, "failed", "failed"));
|
|
227
|
+
counts.push(formatCount(inProgress, "in progress", "in progress"), formatCount(open, "open", "open"));
|
|
228
|
+
lines.push(railLine(RAIL.close, slot, counts.join(" · "), theme, budget));
|
|
197
229
|
return lines;
|
|
198
230
|
}
|