pi-crew 0.11.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +116 -0
- package/README.md +0 -1
- package/dist/index.mjs +29936 -29118
- package/package.json +1 -1
- package/schema.json +10 -0
- package/skills/real-test-pi-crew/REPORT-TEMPLATE.md +2 -0
- package/skills/real-test-pi-crew/SKILL.md +181 -1
- package/src/config/config-validation.ts +0 -2
- package/src/config/defaults.ts +0 -1
- package/src/config/types.ts +0 -5
- 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 +0 -2
- package/src/extension/register.ts +7 -2
- package/src/extension/registration/commands/manage.ts +5 -4
- package/src/extension/registration/commands/shared.ts +0 -1
- 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/team-tool/handle-settings.ts +14 -2
- package/src/runtime/task-display.ts +1 -1
- package/src/schema/config-schema.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 +45 -13
- 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/inline-panel/crew-editor.ts +13 -3
- package/src/ui/inline-panel/index.ts +60 -4
- package/src/ui/keybinding-map.ts +230 -32
- package/src/ui/live-conversation-overlay.ts +176 -46
- 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 +95 -74
- package/src/ui/settings-overlay.ts +81 -39
- 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 +126 -8
- package/src/ui/tool-renderers/brief-mode.ts +8 -66
- package/src/ui/tool-renderers/index.ts +372 -521
- 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/visual.ts +0 -4
- 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/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -344,6 +344,11 @@
|
|
|
344
344
|
"minimum": 1,
|
|
345
345
|
"maximum": 50
|
|
346
346
|
},
|
|
347
|
+
"inlinePanel": {
|
|
348
|
+
"type": "boolean",
|
|
349
|
+
"default": true,
|
|
350
|
+
"description": "Show the inline agent panel above the editor (editor-wrapper agent rows)."
|
|
351
|
+
},
|
|
347
352
|
"powerbar": {
|
|
348
353
|
"type": "boolean"
|
|
349
354
|
},
|
|
@@ -377,6 +382,11 @@
|
|
|
377
382
|
"type": "boolean",
|
|
378
383
|
"default": true
|
|
379
384
|
},
|
|
385
|
+
"autoCloseDashboardMs": {
|
|
386
|
+
"type": "integer",
|
|
387
|
+
"minimum": 0,
|
|
388
|
+
"description": "Auto-close delay for the run dashboard once the run reaches a terminal state (ms)."
|
|
389
|
+
},
|
|
380
390
|
"showModel": {
|
|
381
391
|
"type": "boolean",
|
|
382
392
|
"default": true,
|
|
@@ -37,6 +37,8 @@ This artifact exists so past runs are verifiable (see SKILL.md "Output report").
|
|
|
37
37
|
| 10a surface E2E | ✅/❌/⏭️ | tmux 3/3 (spawn+self-close / kill-pane→degrade / doctor orphan) + herdr 3/3 — or skip reason per backend (no `$TMUX`/CI/no socket/in tmux for herdr) |
|
|
38
38
|
| 10b live surface run | ✅/❌/⏭️ | `visibleAgents='<set>'`, pane ids+titles during run, `worker.surface_spawned`/`worker.surface_closed` events (+ no `surface.degraded`), panes auto-closed. NOTE: `manifest.surface.panes` is `{}` at run END even on success (released on pane close) — engage-evidence is the EVENTS + `manifest.surface.provider`/`workerPids` |
|
|
39
39
|
| 10c herdr path | ✅/❌/⏭️ | ran in herdr pane / skipped (not in herdr pane) |
|
|
40
|
+
| 12 resource contracts | ✅/❌/⏭️ | 12a contracts `<pass>/<tests>` · 12b agents `<n>` / bad desc `0` / no routing `0` / strict-YAML fails `0` · 12c rendered agent lines `<n>`, all with `useWhen=` (budget-truncated by design) · 12d `<pass>/<tests>` |
|
|
41
|
+
| 13 real-run UI render | ✅/❌/⏭️ | runId `<id>` (real run, NOT fixtures); surfaces rendered: `<list>`; states: running/done/failed/focused/idle/narrow; invariant sweep `undefined` 0 · retired glyph 0 · `->` 0 · wire-format 0 · invented word 0 · bad plural 0; glyph-vs-state: spinner only while running ✅; hint survives @`<min width>` ✅; formats consistent (duration/usage/runId) ✅; catalog regenerated + 1 PNG inspected ✅ |
|
|
40
42
|
| 11 remediation regression | ✅/❌/⏭️ | sub-checks run + evidence: 11a buffered-site census `<n>` files (full test:unit `<pass>/<tests>` if run) · 11b wc-gate max `<n>`/2000 + in `ci` · 11c validator warn `<key>` → severity · 11d slow tier 3 files disjoint · 11e nightly SMOKE not-set · 11f reject format sites · 11g widgetPlacement bottom · 11h twins 3× green · 11i dead-export 0 + MUST_INCLUDE 5 · 11j committed-hash OK |
|
|
41
43
|
|
|
42
44
|
Legend: ✅ pass with evidence · ❌ fail (root cause below) · ⏭️ skipped (justify why)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: real-test-pi-crew
|
|
3
3
|
description: >
|
|
4
|
-
End-to-end verification for pi-crew changes: fast critical tests, 3-path kill-switch proof, bundle md5 sync, live TUI probing, smoke team runs, a live feature-action battery (team tool + subagent tools), a surface-mode battery (workers in real tmux/herdr panes, degrade-to-headless),
|
|
4
|
+
End-to-end verification for pi-crew changes: fast critical tests, 3-path kill-switch proof, bundle md5 sync, live TUI probing, smoke team runs, a live feature-action battery (team tool + subagent tools), a surface-mode battery (workers in real tmux/herdr panes, degrade-to-headless), a resource-contract battery (agent .md frontmatter dual-parse, routing render, output contracts), and a real-run UI render battery (every surface rendered from a real run's on-disk state: state glyphs, invented strings, pluralisation, truncation priority, usage formats, width survival).
|
|
5
5
|
When NOT to use: unit tests for isolated modules (use test runner directly); pure test execution.
|
|
6
6
|
|
|
7
7
|
origin: pi-crew
|
|
@@ -54,6 +54,17 @@ triggers:
|
|
|
54
54
|
- "silent bash"
|
|
55
55
|
- "worker killed mid command"
|
|
56
56
|
- "tier 12"
|
|
57
|
+
- "tier 13"
|
|
58
|
+
- "check the UI"
|
|
59
|
+
- "run the UI"
|
|
60
|
+
- "render every surface"
|
|
61
|
+
- "full UI"
|
|
62
|
+
- "does the card look right"
|
|
63
|
+
- "undefined in the widget"
|
|
64
|
+
- "spinner keeps spinning"
|
|
65
|
+
- "hint cut off"
|
|
66
|
+
- "tofu glyph"
|
|
67
|
+
- "catalog png"
|
|
57
68
|
---
|
|
58
69
|
|
|
59
70
|
# real-test-pi-crew
|
|
@@ -808,6 +819,129 @@ node --experimental-strip-types --no-warnings --test --test-force-exit \
|
|
|
808
819
|
|
|
809
820
|
---
|
|
810
821
|
|
|
822
|
+
## Tier 13 — Real-run UI render battery (every surface, real state, no fixtures)
|
|
823
|
+
|
|
824
|
+
**What**: run a REAL team run, then render **every UI surface from that run's on-disk state** (`manifest.json` / `agents.json` / `tasks.json` / the snapshot cache / the real widget model) at several widths, and assert the cross-surface invariants that per-module unit tests structurally cannot see.
|
|
825
|
+
|
|
826
|
+
**Why this is its own tier**: unit tests assert whatever strings their author chose with fixtures their author built; executor summaries claim behaviour. Neither can catch a surface painting something that maps to **no data field** or **contradicts the state**. Measured 2026-09-16: a 4-executor parallel UI migration + an independent verifier + **7857 green tests** left **7 real defects** in the UI — one render pass over a real run found all of them in ~2 minutes. The battery is cheap; the class of bug it kills is invisible to everything else.
|
|
827
|
+
|
|
828
|
+
| Defect class | Real example (2026-09-16) | Why unit tests missed it |
|
|
829
|
+
|---|---|---|
|
|
830
|
+
| **Invented string** (hardcoded, maps to no field) | sidebar painted `122ab0dd · completed · right default` | no test asserts the ABSENCE of unexplained words |
|
|
831
|
+
| **Cross-surface inconsistency** | `fast-fix/fast-fix` in sidebar AND dashboard (each site joined `team/workflow` itself) | per-file fixtures used differing team names, so neither site looked wrong |
|
|
832
|
+
| **Wrong state glyph** | dock spun `⠹` on a finished run (`0 running · 3/3 done`) | the header always received a spinner frame; no test crossed "0 running" with the glyph |
|
|
833
|
+
| **Truncation eats the important token** | dashboard run list painted `› ✓ 122ab0dd complete…` — the status cut in half | fixture goals fit the width, so the narrow-width fallback never ran |
|
|
834
|
+
| **Wire format leaking into TUI** | sidebar `input=2780, output=3715, cacheRead=57216, cost=0.000000, turns=0` | the shared `formatUsage` helper IS correct for CLI output; only the TUI usage is wrong |
|
|
835
|
+
| **Unit-less / wrong-scale numbers** | `314.7s` instead of `5m44s` in the agents pane | tests asserted the numbers, never the format |
|
|
836
|
+
| **Pluralisation** | `1 runs`, `3 agents`, `1 tools` | fixtures used plural counts |
|
|
837
|
+
| **Optional segment unguarded** | live `undefined — ↓·enter` after a run finished — the pure builder guarded it, the **component path did not** | the builder test passed; the component path had no zero-runs test |
|
|
838
|
+
|
|
839
|
+
**When required**: ANY change under `src/ui/**`, or any change that alters what a surface prints (a formatter, a status-slot map, a label helper). Also required after parallel/subagent UI work — delegated claims are hypotheses until rendered.
|
|
840
|
+
|
|
841
|
+
**How**:
|
|
842
|
+
|
|
843
|
+
```bash
|
|
844
|
+
# 0. Produce real state — a real run (read-only goal keeps it safe + fast):
|
|
845
|
+
# from the parent Pi session: team action='run' team='fast-fix' goal='<read-only 1-question task>'
|
|
846
|
+
# → note the runId; state lands in <workspace>/.crew/state/runs/<runId>/
|
|
847
|
+
# 1. Write the harness to /tmp (NEVER into the repo) and render every surface.
|
|
848
|
+
# Template (adjust imports to the surfaces you touched):
|
|
849
|
+
cat > /tmp/full-ui.ts <<'EOF'
|
|
850
|
+
import * as fs from "node:fs";
|
|
851
|
+
import { asCrewTheme } from "/ABS/PATH/pi-crew/src/ui/theme-adapter.ts";
|
|
852
|
+
import { formatCompactToolProgress } from "/ABS/PATH/pi-crew/src/ui/tool-progress-formatter.ts";
|
|
853
|
+
import { teamToolRenderer } from "/ABS/PATH/pi-crew/src/ui/tool-renderers/index.ts";
|
|
854
|
+
import { buildWidgetLines } from "/ABS/PATH/pi-crew/src/ui/widget/widget-renderer.ts";
|
|
855
|
+
import { buildTaskListLines } from "/ABS/PATH/pi-crew/src/ui/widget/task-list.ts";
|
|
856
|
+
import { LiveRunSidebar } from "/ABS/PATH/pi-crew/src/ui/live-run-sidebar.ts";
|
|
857
|
+
|
|
858
|
+
const CWD = "/ABS/PATH/WORKSPACE"; // where .crew/state lives
|
|
859
|
+
const RUN = `${CWD}/.crew/state/runs/<runId>`;
|
|
860
|
+
const manifest = JSON.parse(fs.readFileSync(`${RUN}/manifest.json`, "utf8"));
|
|
861
|
+
const agents = JSON.parse(fs.readFileSync(`${RUN}/agents.json`, "utf8"));
|
|
862
|
+
const tasks = JSON.parse(fs.readFileSync(`${RUN}/tasks.json`, "utf8"));
|
|
863
|
+
const theme = asCrewTheme({});
|
|
864
|
+
const R = (c: any, w = 118) => c.render(w).map((l: string) => l.replace(/\s+$/, "")).join("\n");
|
|
865
|
+
const L = (ls: string[]) => ls.map((l) => l.replace(/\s+$/, "")).join("\n");
|
|
866
|
+
const hdr = (t: string) => `\n═══ ${t} ═══`;
|
|
867
|
+
const details = { action: "run", status: manifest.status, runId: manifest.runId, team: manifest.team, agentRecords: agents };
|
|
868
|
+
|
|
869
|
+
console.log(hdr("CALL"), R(teamToolRenderer.renderCall({ action: "run", goal: manifest.goal, team: manifest.team }, theme, { argsComplete: true })));
|
|
870
|
+
// STREAMING must go through the PRODUCER, not a hand-built string:
|
|
871
|
+
const stream = formatCompactToolProgress({ agentId: manifest.runId, status: "running", runId: manifest.runId,
|
|
872
|
+
startedAt: new Date(manifest.createdAt).getTime(), manifest, tasks, agents });
|
|
873
|
+
console.log(hdr("STREAMING"), R(teamToolRenderer.renderResult({ details: { action: "run" }, content: [{ type: "text", text: stream }] }, { isPartial: true }, theme, {})));
|
|
874
|
+
console.log(hdr("COLLAPSED"), R(teamToolRenderer.renderResult({ details }, { action: "run" }, theme, {})));
|
|
875
|
+
console.log(hdr("EXPANDED"), R(teamToolRenderer.renderResult({ details }, { action: "run" }, theme, { expanded: true })));
|
|
876
|
+
console.log(hdr("EXPANDED@80"), R(teamToolRenderer.renderResult({ details }, { action: "run" }, theme, { expanded: true }), 80));
|
|
877
|
+
|
|
878
|
+
// WidgetRun NEEDS `snapshot` (the plan card reads snapshot.tasks) — a bare {run, agents} renders EMPTY and looks like a bug:
|
|
879
|
+
const { createRunSnapshotCache } = await import("/ABS/PATH/pi-crew/src/ui/run-snapshot-cache.ts");
|
|
880
|
+
const snap = createRunSnapshotCache(CWD).refreshIfStale(manifest.runId);
|
|
881
|
+
const done = [{ run: manifest, agents, snapshot: snap }];
|
|
882
|
+
const live = [{ run: { ...manifest, status: "running" }, agents: agents.map((a: any, i: number) => ({ ...a, status: i === 0 ? "running" : a.status })), snapshot: snap }];
|
|
883
|
+
const dead = [{ run: { ...manifest, status: "failed" }, agents, snapshot: snap }];
|
|
884
|
+
for (const [name, runs, w] of [["done", done, 118], ["done@50", done, 50], ["running", live, 118], ["failed", dead, 118]] as const) {
|
|
885
|
+
console.log(hdr(`DOCK ${name}`), L(buildWidgetLines(CWD, 0, 8, runs as never, 0, w, {})));
|
|
886
|
+
}
|
|
887
|
+
console.log(hdr("PLAN CARD"), L(buildTaskListLines(done as never, 118, theme)) || "(empty)");
|
|
888
|
+
|
|
889
|
+
const sidebar = new LiveRunSidebar({ cwd: CWD, runId: manifest.runId, done: () => undefined, theme: {}, config: {} as never });
|
|
890
|
+
console.log(hdr("SIDEBAR"), R(sidebar, 118));
|
|
891
|
+
// Dashboard + browser: see docs/ui-samples/capture.ts sections 13/14 for the exact constructors.
|
|
892
|
+
EOF
|
|
893
|
+
node --experimental-strip-types --no-warnings /tmp/full-ui.ts | tee /tmp/full-ui.txt
|
|
894
|
+
|
|
895
|
+
# 2. Invariant sweep over the rendered text (all of these must be ZERO hits):
|
|
896
|
+
grep -nE "undefined|[╭╮╰╯├┤]|->" /tmp/full-ui.txt # unguarded segment / retired frame / legacy separator
|
|
897
|
+
grep -nE "\b1 (runs|tools|agents|tasks|edits)\b" /tmp/full-ui.txt # pluralisation (note: `11 tools` legitimately contains `1 tools` — anchor the match)
|
|
898
|
+
grep -nE "input=|output=|cacheRead=|cost=[0-9]" /tmp/full-ui.txt # wire format leaked into a TUI surface
|
|
899
|
+
python3 - <<'PY'
|
|
900
|
+
import re
|
|
901
|
+
bad = []
|
|
902
|
+
for ln in open("/tmp/full-ui.txt", encoding="utf-8"):
|
|
903
|
+
if "═══" in ln: continue
|
|
904
|
+
# spinner present while nothing is running?
|
|
905
|
+
if re.search(r"[⠁-⣿]", ln) and re.search(r"0 running", ln): bad.append(("spinner+0-running", ln.rstrip()))
|
|
906
|
+
plain = re.sub(r"\x1b\[[0-9;]*m", "", ln)
|
|
907
|
+
if len(plain.rstrip("\n")) > 120: bad.append(("over-width", plain.rstrip()))
|
|
908
|
+
if "··" in plain and "↓·enter" in plain and plain.rstrip().endswith("…"): bad.append(("hint clipped", plain.rstrip()))
|
|
909
|
+
print("BAD:", bad if bad else "none")
|
|
910
|
+
PY
|
|
911
|
+
|
|
912
|
+
# 3. Narrow-width survival: the actionable hint must be the LAST thing to go.
|
|
913
|
+
# Render the dock at 40/50/60 and assert `↓·enter` (or its tail) is still there.
|
|
914
|
+
|
|
915
|
+
# 4. Cross-surface consistency (same run, every surface):
|
|
916
|
+
# - team label identical everywhere (`fast-fix`, never `fast-fix/fast-fix`)
|
|
917
|
+
# - run id rendered at the same width everywhere (shortId = last 8)
|
|
918
|
+
# - durations in ONE format (`5m44s`, never `344.3s`)
|
|
919
|
+
# - usage in ONE format (`↑2.8k ↓3.7k`, never `input=…`)
|
|
920
|
+
# - status word never truncated (`completed`, never `complete…`)
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
**Mandatory rules for this tier**
|
|
924
|
+
|
|
925
|
+
- **Render REAL state, never hand-built fixtures.** Fixtures are how the seven defects above survived: the author picks values that fit and read well. `team action='run'` costs ~120s and gives you goals, ids, counts and usage that are the wrong length, the wrong shape and the wrong scale — which is the point.
|
|
926
|
+
- **Feed STREAMING through the producer** (`formatCompactToolProgress`), not a string you typed. Half the streaming bugs live in the producer→parser contract (`test/unit/runtime/core/tool-progress-formatter.test.ts`).
|
|
927
|
+
- **Render EVERY state you support**: running / done / failed / focused / idle / narrow. A glyph or fallback that is correct in one state is routinely wrong in another (spinner on a finished run; a hint clipped at 50 columns).
|
|
928
|
+
- **`undefined` sweep must cover the COMPONENT path**, not just the pure builder. `widget-renderer.buildWidgetLines` had `if (!zero) return []` for three weeks while `src/ui/widget/index.ts` composed the same row with an unguarded template literal — the live `undefined — ↓·enter`.
|
|
929
|
+
- **Any word you cannot trace to a data field is a bug.** For each literal in the rendered output ask "which field/derivation produced this?" — `right default` had no answer.
|
|
930
|
+
- Keep the harness in `/tmp`. It is a probe, not a deliverable; if it is worth keeping, it belongs in the repo's catalog script (see below), not in a random test file.
|
|
931
|
+
|
|
932
|
+
**Catalog (if the repo ships one, `docs/ui-samples/`)**
|
|
933
|
+
|
|
934
|
+
- `capture.ts` renders real components into `captures/*.txt`; `render_png.py` turns them into terminal-style PNGs. Re-run BOTH after any UI change, then look at one image — text captures hide font-level breakage.
|
|
935
|
+
- `render_png.py` must **fail loudly when the font has no glyph** for a character (the coverage self-check compares each glyph against the `.notdef` box). DejaVu Sans Mono — the best-covered mono font on a stock Linux box — lacks the **braille spinner range** (U+2800–U+28FF) and `⟳ ⏰ ⎿`, so a naive render ships `□` for every running row. Mappings in use: braille → `◐`, `⟳` → `↻`, `⎿` → `└`, `⏰`/`⏱` → `o`.
|
|
936
|
+
- Set **line-height == font-size**, otherwise box-drawing rails paint as a dashed line.
|
|
937
|
+
- Heavy box glyphs (`┏ ┃ ┗`) render with light strokes in DejaVu/Noto — a font trait, not a capture bug. Document it instead of hunting fonts.
|
|
938
|
+
|
|
939
|
+
**Acceptance**: every surface renders; the invariant sweep returns zero hits; each state (running/done/failed/focused/idle/narrow) renders the right glyph and keeps the actionable hint; no `undefined`, no retired frame glyph, no `->`, no wire format, no invented word; durations/usage/plurals consistent across surfaces; catalog (if present) regenerated and one PNG visually inspected.
|
|
940
|
+
|
|
941
|
+
**Reference implementations**: `docs/ui-samples/capture.ts` (sections 13–18 render dashboard/browser/inline panel/transcript/live-conversation/settings from a REAL temp run written through the state-store APIs), `docs/ui-samples/render_png.py` (glyph coverage self-check + substitutions), `docs/UI-DESIGN-SYSTEM.md` (the grammar each surface must follow), `src/ui/rail.ts` (the single source of glyphs/helpers).
|
|
942
|
+
|
|
943
|
+
---
|
|
944
|
+
|
|
811
945
|
## Anti-patterns (the cost is real, observed in this session)
|
|
812
946
|
|
|
813
947
|
| Anti-pattern | Cost | Where fixed | Reference |
|
|
@@ -844,6 +978,14 @@ node --experimental-strip-types --no-warnings --test --test-force-exit \
|
|
|
844
978
|
| **Test vacuous — assert trên fixture chứ không trên wiring** (P1 remediation, `09dda842`): migration-validator test 2 từng assert key tự chế không có trong registry → luôn pass dù validator chưa được wire vào registerPiTeams. | `09dda842` | Test phải dùng key THẬT từ registry (`PI_CREW_BROKER_DIAG_UI` severity "removed"), và wiring test phải prove call-site (register.ts:68), không chỉ prove pure function. |
|
|
845
979
|
| **Folded YAML scalar (`key: >`) in agent/team/workflow frontmatter** (Batch-9 regression, fixed `aa899a1e`): `utils/frontmatter.ts` is line-based — folded descriptions parsed as literal `">"` for ALL 17 agents while typecheck/lint/test:critical stayed green (skills unaffected: real `yaml` package). Symptom: guidance renders `name (builtin): >`, When-NOT text missing. | `aa899a1e` | Agent/teams/workflows frontmatter values stay single-line; quote values containing `": "` (parser strips symmetric quotes); run the Tier 12b dual-parse probe after EVERY resource `.md` frontmatter edit. Folded scalars remain fine in `skills/*/SKILL.md` only. |
|
|
846
980
|
| **Worker killed mid long-silent-bash** (Batch-1 postmortem): one >5–10 min command (full `npm test` ≈ 10 min) emits no LLM activity → the broker's responsiveness check SIGTERMs the worker mid-run — exit 143 WHILE the command runs, not a 600s response timeout. Work was intact; manual re-run green — the kill WAS the "hang". | n/a (quirk — `CONTEXT.md` Flagged #1; P2 candidate) | Split long suites into <5 min chunks or emit progress between commands. On exit-143-mid-command: re-run manually BEFORE diagnosing a code bug. Postmortem: `postmortem-batch-1-sigterm.md` (workspace root). |
|
|
981
|
+
| **`pkill -f "test:unit"` kills your own shell** (2026-09-16): the pattern matches the `bash -c` cmdline of the tool call that runs it, so the command dies before it starts the replacement suite — and the log file simply never appears. | n/a (self-inflicted) | Kill by pid (`pgrep -f test-runner` → `kill <pid>`), or match a pattern the sink does not contain | Tier 13 "How" step 0 — detached start: `(setsid nohup npm run test:unit > /tmp/suite.log 2>&1 < /dev/null &)` |
|
|
982
|
+
| **`pgrep -f test-runner` read as liveness** (2026-09-16): the same self-match makes it report `RUNNING` forever, so a suite that died 10 minutes ago looks alive and the "result" you read is a truncated log. | n/a (self-inflicted) | Liveness = `ps -eo pid,etime,cmd \| grep -E "test-runner\|node --test"` with the grep itself excluded | Tier 13 harness step 1 — a run is DONE only when the `# tests/# pass/# fail` block exists at EOF |
|
|
983
|
+
| **Reading a truncated suite log as a summary** (2026-09-16): the runner died with `Test runner error: spawnSync … ETIMEDOUT` at subtest 3946/7860 under load; the last numbers looked like a verdict. | n/a (runner) | The verdict is the `# tests/# pass/# fail` block at EOF — absent means the run did not finish; re-run in the foreground with nothing else heavy running | Failure symptoms row "Test runner error: spawnSync … ETIMEDOUT" |
|
|
984
|
+
| **Running the full suite on a tree you are still editing** (2026-09-16): a 12-minute suite started before the last fixes reports failures that no longer exist — and passes fixes that were not in yet. | n/a (process) | Freeze the tree first; if you must edit, the run is VOID — say so instead of quoting its numbers | Done-criteria: "Full `test:unit` fresh-run" |
|
|
985
|
+
| **Grepping the bundle without context or escape-awareness** (2026-09-16): esbuild escapes non-ASCII as `\u250F` (uppercase hex) so glyph greps miss, while `"->"` (the progress **wire format**, `roleSeparator`) and `Crew agents` (error message + task-graph markdown) are permanent false positives. | n/a (permanent) | Grep the codepoint escape (`u250F`, `u258F`) and always print ±90 chars of context before concluding | Tier 3 bundle check + Tier 13 step 2 (escape-aware proof snippet) |
|
|
986
|
+
| **Trusting a delegated UI claim** (2026-09-16): four parallel executors + a verifier reported "dedupe applied" and "hints canonical"; the rendered surfaces still showed `fast-fix/fast-fix` and an invented `· right default`. Summaries describe intent. | n/a (process) | Render the surface with REAL state before believing any UI claim — including your own | Tier 13 (this entire tier exists for this class) |
|
|
987
|
+
| **Declaring a red gate "pre-existing" (or "my regression") from reasoning alone** (2026-09-16): `check:env-vars` was red; the file was untouched, but that is an argument, not evidence. | n/a (process) | Prove it in a clean-HEAD worktree: `git worktree add -q /tmp/pc-head HEAD && (cd /tmp/pc-head && node scripts/check-env-vars.mjs); git worktree remove --force /tmp/pc-head` | Same recipe as the broker-flake proof (Tier 1 / Failure symptoms) |
|
|
988
|
+
| **Shipping a catalog PNG with tofu boxes** (2026-09-16): the catalog rendered the braille spinner + `⟳` + `⎿` as `□` — invisible in the `.txt` captures, obvious the moment the image was opened. | `docs/ui-samples/render_png.py` glyph self-check | Make the renderer FAIL on any character it cannot paint (compare against the `.notdef` box), then map the offender | Tier 13 "Catalog" + `render_png.py` (`_has_glyph`, `BRAILLE_TO`) |
|
|
847
989
|
| **Treating `wait-request-broker.test.ts` load-timeout as a product bug**: the test runner's per-file 180s timeout is below this file's full-suite runtime under parallel load — fails only with the whole suite, passes in isolation. Pre-existing flake, NOT a regression from your change. | n/a (test infra) | Re-run the single file before fixing anything: `node scripts/test-runner.mjs test/unit/runtime/broker/wait-request-broker.test.ts`. Green in isolation = infra flake; move on. |
|
|
848
990
|
|
|
849
991
|
---
|
|
@@ -876,6 +1018,14 @@ When a tier fails, the recovery is usually quick. Match the symptom to the cause
|
|
|
876
1018
|
| Surface run >5 phút bị stale-reconcile giết oan (worker khỏe, pane sống) | F1 (đã fix f12f4f5d + af2f8eb4): recorder chỉ flush ở turn boundary → lastSeen đóng băng giữa turn; reconciler cũ time-based không pid-gate. **Bẫy đa host**: MỘT pi session chạy bundle cũ cũng đủ giết run của session khác (sweep quét mọi runs) — tát cả host phải cùng version | Kiểm tra mọi pi process cùng bundle (`ps` lstart vs dist mtime); `PI_CREW_DEBUG_STALE=1` sidecar /tmp/pi-crew-f1-debug.log ghi mọi verdict STALE để bắt hung thủ; kỳ vọng sidecar rỗng khi mọi host đã fix |
|
|
877
1019
|
| Worker exits 143 WHILE a long bash command is still running (no LLM-activity window before the kill) | Broker responsiveness SIGTERM on silent long commands (`CONTEXT.md` Flagged #1) — distinct from `RESPONSE_TIMEOUT_MS` (600s no-response) | Split the command; emit progress between steps; re-run the suite manually — work is usually intact. See `postmortem-batch-1-sigterm.md` |
|
|
878
1020
|
| Full `test:unit` fails ONLY on `wait-request-broker.test.ts` under parallel load | Per-file 180s runner timeout vs the file's real runtime (passes isolated) | `node scripts/test-runner.mjs test/unit/runtime/broker/wait-request-broker.test.ts` — green in isolation = infra flake, not a regression |
|
|
1021
|
+
| `undefined` (or `undefined — …`) painted in a live surface | an optional segment interpolated into a template literal without a guard. **Check BOTH paths**: the pure builder and the component that composes the same row (2026-09-16: `widget-renderer.buildWidgetLines` guarded it, `src/ui/widget/index.ts` did not). | Guard before composing (`if (!x) return [] / return undefined`), add a regression lock that renders the zero-state through the COMPONENT, and sweep every `${optional}` on the surface. |
|
|
1022
|
+
| Spinner keeps spinning after the run finished (`⠹ … 0 running`) | the surface hardcodes a spinner frame instead of deriving the glyph from state | Derive it: spinner only while an agent/run is actually running, otherwise the outcome glyph (`✓`/`✗`). Assert in the battery that "0 running" never coexists with a braille glyph. |
|
|
1023
|
+
| Actionable hint clipped at a narrow width (`···· ↓…`) | the leader/hint is composed at a pinned budget and the WHOLE line is truncated afterwards, so the tail dies first | Compose the tail with the shared leader helper at `budget = min(pinned, width - 2)` so the LEFT segment is trimmed (with `…`) and the hint survives. Probe at 40/50/60 columns. |
|
|
1024
|
+
| A status/word truncated mid-token (`complete…`, `fast-f…`) | a narrow-width fallback truncating `head · meta` as ONE string | Give the fallback an explicit priority order (status > id > goal > meta) and clip the lowest-priority segment; never `truncate()` a concatenation that mixes a must-survive token with a droppable one. |
|
|
1025
|
+
| A surface shows a word nobody can trace to data (`· right default`) | a hardcoded literal left in a template | Delete it or replace it with the real field (`run.workspaceMode`). Add "every literal traces to a field" to the Tier 13 sweep. |
|
|
1026
|
+
| `input=2780, output=3715, cost=0.000000` inside a TUI panel | a `key=value` formatter (CLI/status output) reused where the compact TUI form belongs | Keep the wire formatter for CLI/log output; add a compact form for the rail (`↑2.8k ↓3.7k`, cost only when > 0). Same for durations: `formatDuration` (`5m44s`), never `(ms/1000).toFixed(1)}s`. |
|
|
1027
|
+
| Catalog PNG shows `□` / dashed rails | the render font lacks the glyph (braille spinner, `⟳ ⏰ ⎿`) or `line-height > font-size` | Re-run with the coverage self-check in `render_png.py`; keep the substitution map up to date; set `LH = FS`. |
|
|
1028
|
+
| `Test runner error: spawnSync … ETIMEDOUT` mid-suite | the runner's own spawn deadline hit under load (a real test spawns node children) | Re-run in the foreground with nothing else heavy running; a truncated log is not a verdict. If it repeats on one file, run that file alone. |
|
|
879
1029
|
| Guidance / `team action='list'` shows an agent description as `>` or missing When-NOT text | Folded-scalar frontmatter (`description: >`) — the line-based parser reads `>` literally (CONTEXT.md Flagged #4) | Restore the single-line value (double-quote it if it contains `": "`); re-run the Tier 12b dual-parse probe |
|
|
880
1030
|
|
|
881
1031
|
## Performance budget (per-tier soft limits)
|
|
@@ -894,6 +1044,7 @@ When a tier fails, the recovery is usually quick. Match the symptom to the cause
|
|
|
894
1044
|
| 10a (surface E2E suite) | 90s | 180s | tmux server issue or a real spawn/degrade regression — investigate, don't bump |
|
|
895
1045
|
| 10b (live surface run) | ~120s (one fast-fix run) | 600s (worker hard limit) | Pane never engaged (check `visibleAgents`) or auto-exit failed leaving panes open |
|
|
896
1046
|
| 10c (herdr path) | ~120s | 600s | herdr socket protocol drift — check `herdr api schema --json` against `src/runtime/surface/herdr-provider.ts` |
|
|
1047
|
+
| 13 (real-run UI render: 1 real fast-fix run + harness + sweep) | 150s | 300s | A surface is reading disk on the paint path, or the run itself hung — the render is <10ms, so a slow Tier 13 means the harness is doing I/O it shouldn't |
|
|
897
1048
|
|
|
898
1049
|
If a tier runs over the hard limit, **stop and investigate** — don't bump the budget silently. The budget exists precisely so regressions in test runtime (which usually means a regression in test setup/teardown) are caught early.
|
|
899
1050
|
|
|
@@ -995,6 +1146,8 @@ Use this to answer "đủ full tính năng chưa?" without re-deriving. Every us
|
|
|
995
1146
|
| Surface panes tmux/herdr (A1) | `src/runtime/surface/` | T10 (10a E2E + 10b live + 10c herdr) |
|
|
996
1147
|
| Broker (mailbox, steer, tokens) | `src/runtime/broker/` | T1/T2 + 9c steer/respond + T10a test #2 |
|
|
997
1148
|
| Dashboard + keybindings + overlays | `src/ui/`, commands `src/extension/registration/commands/` | T5/T6 probe + parity golden test |
|
|
1149
|
+
| **UI surfaces render from real run state** (tool card, dock widget, plan card, sidebar, dashboard, browser, overlays) | `src/ui/**`, grammar in `docs/UI-DESIGN-SYSTEM.md`, primitives in `src/ui/rail.ts` | T13 (real-run render battery: state glyphs, invented strings, pluralisation, truncation priority, usage/duration formats, narrow-width hint survival) |
|
|
1150
|
+
| UI catalog artifacts (`captures/*.txt` + `png/*.png`, regenerated from real components) | `docs/ui-samples/capture.ts`, `docs/ui-samples/render_png.py` | T13 catalog step (regenerate both + open one PNG; renderer fails on a missing glyph) |
|
|
998
1151
|
| Slash commands (8: run/status/doctor/help/dashboard/settings/init/config) | `commands/{run,status,manage,dashboard}.ts` | T5 send-keys một lệnh `/team-*` |
|
|
999
1152
|
| team-settings / config | `src/extension/team-tool/handle-settings.ts` | 9a settings get + 10b set visibleAgents |
|
|
1000
1153
|
| Worktree isolation | `src/worktree/` | 9a worktrees + 9b run `workspaceMode='worktree'` |
|
|
@@ -1111,6 +1264,22 @@ node -e 'const yaml=require("yaml"),fs=require("fs");let ok=0;for(const f of fs.
|
|
|
1111
1264
|
node --experimental-strip-types --no-warnings --test --test-force-exit test/unit/bundle-skill-resolution.test.ts test/unit/extension/registration/tool-loop-guard.test.ts test/unit/runtime/core/skill-instructions.test.ts # 12d
|
|
1112
1265
|
node scripts/check-bundle-staleness.mjs # staleness + ARCH-7 path-leak scan (also after every build:bundle)
|
|
1113
1266
|
node scripts/release-smoke.mjs # release cut: peer install + tarball import + shape check (ARCH-6)
|
|
1267
|
+
# Tier 13 (real-run UI render battery — any src/ui/** change, or after ANY delegated UI work)
|
|
1268
|
+
# from the parent Pi session: team action='run' team='fast-fix' goal='<read-only 1-question task>' # ~120s, gives REAL state
|
|
1269
|
+
# then render every surface from that run's on-disk state (harness template in Tier 13; keep it in /tmp):
|
|
1270
|
+
node --experimental-strip-types --no-warnings /tmp/full-ui.ts | tee /tmp/full-ui.txt
|
|
1271
|
+
grep -nE "undefined|[╭╮╰╯├┤]|->" /tmp/full-ui.txt # 0 hits (unguarded segment / retired frame / legacy separator)
|
|
1272
|
+
grep -nE "(^|[^0-9])1 (runs|tools|agents|tasks)\b" /tmp/full-ui.txt # 0 hits (anchor the plural check — `11 tools` contains `1 tools`)
|
|
1273
|
+
grep -nE "input=|output=|cacheRead=|cost=[0-9]" /tmp/full-ui.txt # 0 hits (wire format leaked into a TUI panel)
|
|
1274
|
+
# state glyph: a braille spinner must never coexist with `0 running`; hint (`↓·enter`) must survive at 40/50/60 cols
|
|
1275
|
+
# catalog: regenerate BOTH artifacts, then LOOK at one image
|
|
1276
|
+
node --experimental-strip-types --no-warnings docs/ui-samples/capture.ts # captures/*.txt
|
|
1277
|
+
python3 docs/ui-samples/render_png.py # png/*.png (fails loudly on a glyph the font lacks)
|
|
1278
|
+
# bundle-side proof (escape-aware — esbuild writes \u250F uppercase):
|
|
1279
|
+
python3 - <<'PY'
|
|
1280
|
+
s=open("dist/index.mjs",encoding="utf-8").read()
|
|
1281
|
+
for k,v in {"rail open u250F":"u250F" in s,"section u2523":"u2523" in s,"retired u256D":"u256D" in s,"raw undefined-hint":"undefined \u2014 " in s}.items(): print(f"{k}: {v}")
|
|
1282
|
+
PY
|
|
1114
1283
|
# 11a full gate (after ANY delayed-write conversion program): npm run test:unit # ~7500 tests, 15-18 min
|
|
1115
1284
|
```
|
|
1116
1285
|
|
|
@@ -1128,6 +1297,7 @@ Before claiming "tested":
|
|
|
1128
1297
|
- [ ] Tier 7: smoke team run for any `src/runtime/goal-workflow/plan-templates.ts` or `workflows/*.workflow.md` change — completed, no hang, verifier output under 60s
|
|
1129
1298
|
- [ ] Tier 8: final md5 sync check passed
|
|
1130
1299
|
- [ ] Tier 9: feature battery — **required if you touched `src/schema/team-tool-schema.ts`, `src/extension/registration/team-tool.ts`, any `Type.Unsafe({...})` schema, or any armed-role tool list (`agents/*.md` / `src/config/role-tools.ts`)**. 9a read-only batch all return clean; one probe per 9b spawn path (sync / async / chain / `Agent` / `crew_agent`+`get_subagent_result`) completes with `consistency=1`. Run 9c–9f only when the change touches their code path; **at least one full 9c/9e/9f sweep per release is recommended so the battery stays proven** (see `real-test-2026-08-11-scratchpad-I-batch.md`); 9d (destructive) requires explicit user confirmation. **After every run: `git status` to catch unauthorized agent edits.**
|
|
1300
|
+
- [ ] Tier 13: real-run render battery — **required for ANY `src/ui/**` change** (and after any parallel/delegated UI work). One real `team action='run'` producing on-disk state; every surface rendered from that state at 118 + a narrow width; invariant sweep clean (no `undefined` / retired frame glyph / `->` / wire format / invented word / bad plural); the correct glyph per state (running vs done vs failed — never a spinner with `0 running`); the actionable hint survives the narrowest width; durations, usage and run ids formatted identically on every surface; catalog regenerated (capture + PNG) and one image visually inspected. **Fixtures do not count** — the defects this tier exists to catch (invented strings, wrong-state glyphs, truncation that eats a must-survive token) are invisible to author-chosen fixture values.
|
|
1131
1301
|
- [ ] **Output report**: save `docs/real-test/reports/real-test-<YYYY-MM-DD>-<slug>.md` from `skills/real-test-pi-crew/REPORT-TEMPLATE.md`, filled DURING the run with per-tier evidence (counts/md5/runId) — not reconstructed from memory afterward. This is what makes past runs verifiable instead of trust-the-summary.
|
|
1132
1302
|
- [ ] Tier 10: surface battery — **required if you touched `src/runtime/surface/**`, `src/prompt/surface-worker.ts`, the surface branch of `src/runtime/child-pi/child-pi.ts`, or the surface config keys**. 10a E2E 3/3 per backend available (tmux trong tmux; herdr ngoài tmux + socket sống — skip vì thiếu mux là correct-by-design nhưng KHÔNG tính pass cho backend đó); 10b live run với session ĐÃ reload bundle mới (xem Anti-patterns "file-md5 only") + `visibleAgents` set + pane-level evidence (pane id/title during run, `worker.surface_spawned`/`worker.surface_closed` events, pane auto-closed after — KHÔNG dùng `manifest.surface.panes` làm evidence engage, xem Anti-patterns "panes == {}"); 10c herdr live chỉ khi pi chạy trong herdr pane (skip kèm lý do nếu không).
|
|
1133
1303
|
- [ ] Tier 11: remediation regression battery — **required if you touched `src/state/**` write paths, `migration-validator.ts`/its wiring, `scripts/wc-gate.mjs` or `ci` scripts, `.github/workflows/*` env, EFFECTIVE_DEFAULTS maps, or you are cutting a release**. Sub-checks a–j per Tier 11; 11a item 4 (full `test:unit`) mandatory after any delayed-write conversion program, skippable for doc-only changes. Record: buffered-site census count, wc-gate max, staleness `--committed-hash` result.
|
|
@@ -1199,6 +1369,16 @@ Workflow files:
|
|
|
1199
1369
|
- `workflows/plan-execute.workflow.md:30` — verifier prompt
|
|
1200
1370
|
- `workflows/review.workflow.md:31` — verifier prompt
|
|
1201
1371
|
|
|
1372
|
+
UI design system + catalog (Tier 13):
|
|
1373
|
+
- `docs/UI-DESIGN-SYSTEM.md` — the RAIL grammar every surface must follow (glyphs `┏ ┣ ┃ ┗`, canopy `NAME ▸ SUBJECT`, dot leaders, eighth-block gauge, cursor `›`, overflow `▲/▼`, hint format) + the 7 surface classes + the width contract
|
|
1374
|
+
- `src/ui/rail.ts` — SINGLE SOURCE of the glyphs/helpers (`RAIL`, `canopyLine`, `sectionLine`, `railLine`, `railLeaders`, `gaugeBar`, `statusSlot/Badge/Icon`, `overflowHint`, `formatHint`/`keyToken`, `CURSOR`/`ACTIVE`, `dedupeAgentLabel`, `padVisual`/`truncVisual`). Surfaces must import from here, never re-declare `┏`/`┃`/`▕` locally.
|
|
1375
|
+
- `src/ui/adaptive-card.ts` — width-deferred wrapper (`render(width)` is the real width; a frame baked for 116 columns tears at 100)
|
|
1376
|
+
- `src/ui/format-helpers.ts` — `formatCount` (pluralisation: `1 tool`), `formatDuration` (`5m44s`, never `314.7s`), `teamWorkflowLabel` (collapses `team/team`), `truncLine`
|
|
1377
|
+
- `src/ui/widget/widget-renderer.ts` — dock row (`buildWidgetLines`, `idleWidgetLine`, `widgetActivityGlyph`, `widgetRailSlot`, `dockTail` hint budget) — the zero-runs branch is the live `undefined — ↓·enter` regression site
|
|
1378
|
+
- `src/ui/live-run-sidebar.ts` / `src/ui/run-dashboard.ts` / `src/ui/agents-jobs-browser.ts` / `src/ui/settings-overlay.ts` / `src/ui/dashboard-panes/*` — surfaces migrated to RAIL 2026-09-16
|
|
1379
|
+
- `docs/ui-samples/capture.ts` (real renders incl. sections 13–18 from a run written through the state-store APIs) + `docs/ui-samples/render_png.py` (glyph coverage self-check + substitution map) + `docs/ui-samples/README.md`
|
|
1380
|
+
- Tests: `test/unit/ui/rail.test.ts`, `dock-rail`, `dashboard-rail`, `panes-rail`, `overlays-rail` (grammar locks), `test/unit/ui/tool-renderers-redesign.test.ts` (card), `test/unit/ui/tool-renderers-frame-width.test.ts` (width invariant)
|
|
1381
|
+
|
|
1202
1382
|
Resource-contract files (Tier 12):
|
|
1203
1383
|
- `src/utils/frontmatter.ts` — LINE-BASED parser (`parseLines`): single-line values, symmetric-quote strip (`aa899a1e`); folded scalars unsupported for agents/teams/workflows (skills use the real `yaml` package — folded OK there)
|
|
1204
1384
|
- `src/agents/discover-agents.ts:388-391, 476` — flat routing keys (`useWhen`/`avoidWhen`/`cost`/`category` as top-level CSV); discovery cache TTL ~30s (`invalidateAgentDiscoveryCache()`)
|
|
@@ -499,11 +499,9 @@ function parseUiConfig(value: unknown): CrewUiConfig | undefined {
|
|
|
499
499
|
obj.widgetPlacement,
|
|
500
500
|
);
|
|
501
501
|
const rawDashboardPlacement = parseWithSchema(Type.Union([Type.Literal("center"), Type.Literal("right")]), obj.dashboardPlacement);
|
|
502
|
-
const rawRowStyle = parseWithSchema(Type.Union([Type.Literal("compact"), Type.Literal("detailed")]), obj.widgetRowStyle);
|
|
503
502
|
const ui: CrewUiConfig = {
|
|
504
503
|
widgetPlacement: rawWidgetPlacement,
|
|
505
504
|
widgetMaxLines: parsePositiveInteger(obj.widgetMaxLines, 50),
|
|
506
|
-
widgetRowStyle: rawRowStyle,
|
|
507
505
|
inlinePanel: parseWithSchema(Type.Boolean(), obj.inlinePanel),
|
|
508
506
|
powerbar: parseWithSchema(Type.Boolean(), obj.powerbar),
|
|
509
507
|
dashboardPlacement: rawDashboardPlacement,
|
package/src/config/defaults.ts
CHANGED
|
@@ -136,7 +136,6 @@ export const DEFAULT_UI = {
|
|
|
136
136
|
// quota/meter footer (falls back to belowEditor when no footer sink exists).
|
|
137
137
|
widgetPlacement: "bottom" as const,
|
|
138
138
|
widgetMaxLines: 8,
|
|
139
|
-
widgetRowStyle: "compact" as const,
|
|
140
139
|
inlinePanel: true,
|
|
141
140
|
powerbar: true,
|
|
142
141
|
dashboardPlacement: "center" as const,
|
package/src/config/types.ts
CHANGED
|
@@ -167,11 +167,6 @@ export interface CrewUiConfig {
|
|
|
167
167
|
*/
|
|
168
168
|
widgetPlacement?: "aboveEditor" | "belowEditor" | "bottom";
|
|
169
169
|
widgetMaxLines?: number;
|
|
170
|
-
/**
|
|
171
|
-
* Per-agent row layout in the widget. `compact` is one width-budgeted line
|
|
172
|
-
* per agent; `detailed` keeps the two-line tree (name row + activity row).
|
|
173
|
-
*/
|
|
174
|
-
widgetRowStyle?: "compact" | "detailed";
|
|
175
170
|
/**
|
|
176
171
|
* Keyboard-navigable agent rows under the prompt (`↓` from an empty prompt).
|
|
177
172
|
* Requires owning pi's editor component, so it yields to any other extension
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { getCrewEnv } from "../../config/env-vars.ts";
|
|
4
|
-
import { hasCrewFontFile, isWebTerminal } from "./font-detect.ts";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Self-contained config for the crew-vibes module (provider quota).
|
|
@@ -52,26 +51,6 @@ export const DEFAULT_CONFIG: CrewVibesConfig = {
|
|
|
52
51
|
},
|
|
53
52
|
};
|
|
54
53
|
|
|
55
|
-
// Fallback capacity icons using standard Unicode characters that render
|
|
56
|
-
// on any terminal without the crew-vibes PUA font.
|
|
57
|
-
const FALLBACK_CAPACITY_ICONS: [string, string, string, string, string, string] = [
|
|
58
|
-
"\u25CB ", // ○ empty circle (lean)
|
|
59
|
-
"\u25D4 ", // ◔ circle with dot (chonking)
|
|
60
|
-
"\u25D1 ", // ◑ circle half filled (chonky)
|
|
61
|
-
"\u25CF ", // ● filled circle (big chonk)
|
|
62
|
-
"\u2B24 ", // ⬤ large filled circle (mega chonk)
|
|
63
|
-
"\u2B22 ", // ⬢ filled hexagon (oh lawd)
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
/** Return capacity icons: standard Unicode glyphs that render on any terminal.
|
|
67
|
-
* PUA glyphs (U+E710..U+E715) require crew-vibes.ttf AND terminal PUA
|
|
68
|
-
* support — many terminals cannot render them even with the font installed. */
|
|
69
|
-
export function capacityIcons(): [string, string, string, string, string, string] {
|
|
70
|
-
// Web terminals cannot render PUA glyphs — use fallback.
|
|
71
|
-
if (isWebTerminal()) return FALLBACK_CAPACITY_ICONS;
|
|
72
|
-
return hasCrewFontFile() ? DEFAULT_CONFIG.capacity.icons : FALLBACK_CAPACITY_ICONS;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
54
|
function asRecord(value: unknown): Record<string, unknown> {
|
|
76
55
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? (value as Record<string, unknown>) : {};
|
|
77
56
|
}
|
|
@@ -19,8 +19,6 @@ import { type CrewVibesConfig, loadConfig, saveConfig } from "./config.ts";
|
|
|
19
19
|
import { clearProviderUsageCache, fetchProviderUsage, type ProviderUsage } from "./provider-usage.ts";
|
|
20
20
|
import { asCrewTheme, clearVibesStatus, renderProviderUsage, setProviderStatus } from "./render.ts";
|
|
21
21
|
|
|
22
|
-
export const CREW_VIBES_STATUS_KEY = "pi-crew-vibes";
|
|
23
|
-
|
|
24
22
|
/**
|
|
25
23
|
* crew-vibes — provider rate-limit quota publisher.
|
|
26
24
|
*
|
|
@@ -1,20 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { CrewTheme } from "../../ui/theme-adapter.ts";
|
|
3
|
-
import { type
|
|
4
|
-
import { capacityIndex, isDangerStage } from "./figures.ts";
|
|
5
|
-
|
|
6
|
-
export type CapacityUsage = {
|
|
7
|
-
tokens: number | null;
|
|
8
|
-
percent: number | null;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function formatCount(value: number): string {
|
|
12
|
-
if (value < 1000) return value.toString();
|
|
13
|
-
if (value < 10_000) return `${(value / 1000).toFixed(1)}k`;
|
|
14
|
-
if (value < 1_000_000) return `${Math.round(value / 1000)}k`;
|
|
15
|
-
if (value < 10_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
|
|
16
|
-
return `${Math.round(value / 1_000_000)}M`;
|
|
17
|
-
}
|
|
3
|
+
import { type CrewVibesConfig, PROVIDER_STATUS_ID } from "./config.ts";
|
|
18
4
|
|
|
19
5
|
function asCrewTheme(theme: unknown): CrewTheme | undefined {
|
|
20
6
|
if (theme && typeof theme === "object" && typeof (theme as CrewTheme).fg === "function") {
|
|
@@ -23,41 +9,6 @@ function asCrewTheme(theme: unknown): CrewTheme | undefined {
|
|
|
23
9
|
return undefined;
|
|
24
10
|
}
|
|
25
11
|
|
|
26
|
-
export function getCapacityUsage(ctx: ExtensionContext): CapacityUsage {
|
|
27
|
-
const fn = (ctx as { getContextUsage?: () => { tokens?: number; percent?: number } | null }).getContextUsage;
|
|
28
|
-
const usage = typeof fn === "function" ? fn.call(ctx) : null;
|
|
29
|
-
return {
|
|
30
|
-
tokens: typeof usage?.tokens === "number" && Number.isFinite(usage.tokens) ? usage.tokens : null,
|
|
31
|
-
percent: typeof usage?.percent === "number" && Number.isFinite(usage.percent) ? usage.percent : null,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatCapacityPrefix(config: CapacityConfig, usage: CapacityUsage): string {
|
|
36
|
-
const display: TokenDisplay = config.tokenDisplay;
|
|
37
|
-
if (display === "off") return "";
|
|
38
|
-
if (display === "percentage") {
|
|
39
|
-
return `${usage.percent === null ? "?" : Math.round(Math.max(0, Math.min(999, usage.percent)))}% `;
|
|
40
|
-
}
|
|
41
|
-
return `${usage.tokens === null ? "?" : formatCount(usage.tokens)} `;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function colorStage(theme: CrewTheme | undefined, index: number, levels: number, text: string): string {
|
|
45
|
-
if (!theme || text.length === 0) return text;
|
|
46
|
-
return theme.fg(isDangerStage(index, levels) ? "error" : "success", text);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function renderCapacity(theme: CrewTheme | undefined, config: CapacityConfig, usage: CapacityUsage): string {
|
|
50
|
-
const icons = capacityIcons();
|
|
51
|
-
const levels = icons.length;
|
|
52
|
-
const index = capacityIndex(usage.percent, levels);
|
|
53
|
-
const icon = icons[index] ?? icons[0];
|
|
54
|
-
const label = config.labels[index] ?? config.labels[0];
|
|
55
|
-
const prefix = theme ? theme.fg("muted", formatCapacityPrefix(config, usage)) : formatCapacityPrefix(config, usage);
|
|
56
|
-
const coloredIcon = colorStage(theme, index, levels, icon);
|
|
57
|
-
const afterIcon = config.showLabel ? ` ${colorStage(theme, index, levels, label)}` : " ";
|
|
58
|
-
return `${prefix}${coloredIcon}${afterIcon}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
12
|
export function clearVibesStatus(ctx: ExtensionContext): void {
|
|
62
13
|
if (!ctx?.hasUI) return;
|
|
63
14
|
ctx.ui.setStatus(PROVIDER_STATUS_ID, undefined);
|
package/src/extension/help.ts
CHANGED
|
@@ -26,7 +26,6 @@ export function piTeamsHelp(): string {
|
|
|
26
26
|
"- /team-manager — interactive menu (alias: /team-cleanup-menu)",
|
|
27
27
|
"",
|
|
28
28
|
"Maintenance:",
|
|
29
|
-
"- /team-cleanup <runId> [--force] (or scope=project/user for uninstall cleanup)",
|
|
30
29
|
"- /team-forget <runId> --confirm [--force]",
|
|
31
30
|
"- /team-prune --keep=20 --confirm",
|
|
32
31
|
"",
|
|
@@ -36,7 +35,6 @@ export function piTeamsHelp(): string {
|
|
|
36
35
|
"- /team-imports",
|
|
37
36
|
"",
|
|
38
37
|
"Diagnostics:",
|
|
39
|
-
"- /team-health",
|
|
40
38
|
"- /team-doctor",
|
|
41
39
|
"- /team-init [--copy-builtins] [--overwrite]",
|
|
42
40
|
"- /team-config [key=value] [--unset=key.path] [--project]",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* • command-registration — registerTeamCommands
|
|
16
16
|
* • crash-recovery-cache — lazy importCrashRecovery
|
|
17
17
|
* • wire-cross-extension — RPC handle + global registry install
|
|
18
|
+
* • terminal-status-wiring — tab title + Ghostty progress (runEventBus)
|
|
18
19
|
*/
|
|
19
20
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
20
21
|
import { loadConfig } from "../config/config.ts";
|
|
@@ -26,7 +27,6 @@ import { installChildProcessAbortShield } from "../utils/child-process-shield.ts
|
|
|
26
27
|
import { resetTimings, time } from "../utils/timings.ts";
|
|
27
28
|
import { registerAutonomousPolicy } from "./autonomous-policy.ts";
|
|
28
29
|
import { registerContextStatusInjection } from "./context-status-injection.ts";
|
|
29
|
-
import { registerCleanupHandler } from "./crew-cleanup.ts";
|
|
30
30
|
import { registerCrewInputRouter } from "./crew-input-router.ts";
|
|
31
31
|
import { registerCrewShortcuts } from "./crew-shortcuts.ts";
|
|
32
32
|
import { registerCrewVibes } from "./crew-vibes/index.ts";
|
|
@@ -43,6 +43,7 @@ import { installCrewBrokerLifecycleController, installSessionLifecycleHandlers }
|
|
|
43
43
|
import { installRuntimeCleanup } from "./registration/runtime-cleanup.ts";
|
|
44
44
|
import { __test__subagentSpawnParams } from "./registration/subagent-helpers.ts";
|
|
45
45
|
import { installSubagentManager } from "./registration/subagent-manager-setup.ts";
|
|
46
|
+
import { installTerminalStatus } from "./registration/terminal-status-wiring.ts";
|
|
46
47
|
import { registerPiTools } from "./registration/tool-registration.ts";
|
|
47
48
|
import { installCrossExtensionWiring } from "./registration/wire-cross-extension.ts";
|
|
48
49
|
|
|
@@ -101,7 +102,11 @@ export async function registerPiTeams(pi: ExtensionAPI): Promise<void> {
|
|
|
101
102
|
// subagents or when the flag is off, it returns a no-op controller.
|
|
102
103
|
ctx.brokerController = installCrewBrokerLifecycleController(pi, ctx);
|
|
103
104
|
|
|
104
|
-
|
|
105
|
+
// M3-1 (UI-AUDIT P0-2/P1-1): wire the tab-title + Ghostty progress
|
|
106
|
+
// controller to runEventBus. Before this the controller was never
|
|
107
|
+
// constructed (dead since v0.8.3). Registers the dispose hook that the
|
|
108
|
+
// SIGTERM/SIGHUP handler calls (crew-cleanup.ts).
|
|
109
|
+
installTerminalStatus(pi, ctx);
|
|
105
110
|
registerCompactionGuard(pi, {
|
|
106
111
|
foregroundControllers: ctx.foregroundControllers,
|
|
107
112
|
foregroundTeamRunControllers: ctx.foregroundTeamRunControllers,
|
|
@@ -278,22 +278,23 @@ export function registerManageCommands(pi: ExtensionAPI, deps: RegisterTeamComma
|
|
|
278
278
|
});
|
|
279
279
|
|
|
280
280
|
pi.registerCommand("crew-brief", {
|
|
281
|
-
description:
|
|
281
|
+
description:
|
|
282
|
+
"Toggle brief mode for crew `team`/`agent` tool output (native read/bash/edit/write/find/grep/ls output is unaffected): on | off | status",
|
|
282
283
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
283
284
|
// LAZY: defer dynamic import of ../../ui/tool-renderers/brief-mode.ts to its call site. Multi-line form breaks scripts/check-lazy-imports.mjs (which does `lines[lineNum - 2]`), so keep destructuring + await import on one line and place this LAZY marker directly above.
|
|
284
|
-
const { isBrief, setBrief, BRIEF_ENTRY_TYPE
|
|
285
|
+
const { isBrief, setBrief, BRIEF_ENTRY_TYPE } = await import("../../../ui/tool-renderers/brief-mode.ts");
|
|
285
286
|
const trimmed = args.trim();
|
|
286
287
|
|
|
287
288
|
if (trimmed === "on") {
|
|
288
289
|
setBrief(true);
|
|
289
290
|
pi.appendEntry(BRIEF_ENTRY_TYPE, { enabled: true });
|
|
290
|
-
ctx.ui.notify("Brief mode: on — tool output will show compact summaries", "info");
|
|
291
|
+
ctx.ui.notify("Brief mode: on — crew team/agent tool output will show compact summaries", "info");
|
|
291
292
|
return;
|
|
292
293
|
}
|
|
293
294
|
if (trimmed === "off") {
|
|
294
295
|
setBrief(false);
|
|
295
296
|
pi.appendEntry(BRIEF_ENTRY_TYPE, { enabled: false });
|
|
296
|
-
ctx.ui.notify("Brief mode: off — full tool output restored", "info");
|
|
297
|
+
ctx.ui.notify("Brief mode: off — full crew team/agent tool output restored", "info");
|
|
297
298
|
return;
|
|
298
299
|
}
|
|
299
300
|
// status (default)
|
|
@@ -531,7 +531,6 @@ export async function openTeamDashboard(ctx: ExtensionContext): Promise<void> {
|
|
|
531
531
|
const selection = await cmdCtx.ui.custom<RunDashboardSelection | undefined>(
|
|
532
532
|
(tui, theme, _keybindings, done) =>
|
|
533
533
|
new RunDashboard(runs, done, theme, {
|
|
534
|
-
placement: rightPanel ? "right" : "center",
|
|
535
534
|
showModel: uiConfig?.showModel,
|
|
536
535
|
showTokens: uiConfig?.showTokens,
|
|
537
536
|
showTools: uiConfig?.showTools,
|