pi-crew 0.1.49 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -1
- package/README.md +176 -781
- package/agents/analyst.md +11 -11
- package/agents/critic.md +11 -11
- package/agents/executor.md +11 -11
- package/agents/explorer.md +11 -11
- package/agents/planner.md +11 -11
- package/agents/reviewer.md +11 -11
- package/agents/security-reviewer.md +11 -11
- package/agents/test-engineer.md +11 -11
- package/agents/verifier.md +70 -11
- package/agents/writer.md +11 -11
- package/docs/actions-reference.md +595 -0
- package/docs/commands-reference.md +347 -0
- package/docs/runtime-flow.md +148 -148
- package/index.ts +6 -6
- package/package.json +99 -99
- package/skills/async-worker-recovery/SKILL.md +42 -42
- package/skills/context-artifact-hygiene/SKILL.md +52 -52
- package/skills/delegation-patterns/SKILL.md +54 -54
- package/skills/mailbox-interactive/SKILL.md +40 -40
- package/skills/model-routing-context/SKILL.md +39 -39
- package/skills/multi-perspective-review/SKILL.md +58 -58
- package/skills/observability-reliability/SKILL.md +41 -41
- package/skills/orchestration/SKILL.md +157 -157
- package/skills/ownership-session-security/SKILL.md +41 -41
- package/skills/pi-extension-lifecycle/SKILL.md +39 -39
- package/skills/requirements-to-task-packet/SKILL.md +63 -63
- package/skills/resource-discovery-config/SKILL.md +41 -41
- package/skills/runtime-state-reader/SKILL.md +44 -44
- package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
- package/skills/state-mutation-locking/SKILL.md +42 -42
- package/skills/systematic-debugging/SKILL.md +67 -67
- package/skills/ui-render-performance/SKILL.md +39 -39
- package/skills/verification-before-done/SKILL.md +57 -57
- package/skills/worktree-isolation/SKILL.md +39 -39
- package/src/adapters/claude-adapter.ts +25 -0
- package/src/adapters/codex-adapter.ts +21 -0
- package/src/adapters/cursor-adapter.ts +17 -0
- package/src/adapters/export-util.ts +137 -0
- package/src/adapters/index.ts +15 -0
- package/src/adapters/registry.ts +18 -0
- package/src/adapters/types.ts +23 -0
- package/src/agents/agent-config.ts +2 -0
- package/src/agents/agent-search.ts +98 -98
- package/src/agents/discover-agents.ts +2 -1
- package/src/config/config.ts +14 -1
- package/src/config/defaults.ts +5 -5
- package/src/config/drift-detector.ts +211 -0
- package/src/config/markers.ts +327 -0
- package/src/config/resilient-parser.ts +108 -0
- package/src/config/suggestions.ts +74 -0
- package/src/extension/cross-extension-rpc.ts +103 -82
- package/src/extension/project-init.ts +36 -4
- package/src/extension/register.ts +67 -22
- package/src/extension/registration/commands.ts +77 -8
- package/src/extension/registration/subagent-tools.ts +10 -1
- package/src/extension/registration/team-tool.ts +10 -1
- package/src/extension/registration/viewers.ts +48 -34
- package/src/extension/run-bundle-schema.ts +89 -89
- package/src/extension/run-export.ts +26 -12
- package/src/extension/run-import.ts +25 -1
- package/src/extension/run-index.ts +5 -1
- package/src/extension/run-maintenance.ts +142 -68
- package/src/extension/team-manager-command.ts +10 -1
- package/src/extension/team-tool/context.ts +1 -1
- package/src/extension/team-tool/doctor.ts +28 -3
- package/src/extension/team-tool/handle-settings.ts +195 -188
- package/src/extension/team-tool/inspect.ts +41 -41
- package/src/extension/team-tool/intent-policy.ts +42 -42
- package/src/extension/team-tool/lifecycle-actions.ts +27 -8
- package/src/extension/team-tool/plan.ts +19 -19
- package/src/extension/team-tool/run.ts +12 -1
- package/src/extension/team-tool.ts +14 -3
- package/src/i18n.ts +184 -184
- package/src/observability/exporters/otlp-exporter.ts +92 -77
- package/src/prompt/prompt-runtime.ts +72 -72
- package/src/runtime/agent-memory.ts +72 -72
- package/src/runtime/agent-observability.ts +114 -114
- package/src/runtime/async-marker.ts +26 -26
- package/src/runtime/attention-events.ts +28 -28
- package/src/runtime/auto-resume.ts +100 -0
- package/src/runtime/background-runner.ts +11 -1
- package/src/runtime/cancellation-token.ts +89 -89
- package/src/runtime/cancellation.ts +61 -61
- package/src/runtime/capability-inventory.ts +116 -116
- package/src/runtime/child-pi.ts +7 -2
- package/src/runtime/compaction-summary.ts +271 -0
- package/src/runtime/completion-guard.ts +190 -190
- package/src/runtime/concurrency.ts +3 -1
- package/src/runtime/crash-recovery.ts +33 -0
- package/src/runtime/delta-conflict.ts +360 -0
- package/src/runtime/diagnostic-export.ts +3 -1
- package/src/runtime/direct-run.ts +35 -35
- package/src/runtime/event-stream-bridge.ts +3 -1
- package/src/runtime/foreground-control.ts +82 -82
- package/src/runtime/green-contract.ts +46 -46
- package/src/runtime/group-join.ts +106 -106
- package/src/runtime/heartbeat-gradient.ts +28 -28
- package/src/runtime/heartbeat-watcher.ts +124 -124
- package/src/runtime/iteration-hooks.ts +262 -0
- package/src/runtime/live-agent-control.ts +88 -88
- package/src/runtime/live-control-realtime.ts +36 -36
- package/src/runtime/live-extension-bridge.ts +150 -150
- package/src/runtime/live-irc.ts +92 -92
- package/src/runtime/live-session-health.ts +100 -100
- package/src/runtime/loop-gates.ts +129 -0
- package/src/runtime/metric-parser.ts +40 -0
- package/src/runtime/notebook-helpers.ts +90 -90
- package/src/runtime/orphan-sentinel.ts +7 -7
- package/src/runtime/parallel-research.ts +44 -44
- package/src/runtime/phase-progress.ts +217 -0
- package/src/runtime/pi-args.ts +38 -2
- package/src/runtime/pi-json-output.ts +111 -111
- package/src/runtime/pi-spawn.ts +74 -6
- package/src/runtime/policy-engine.ts +79 -79
- package/src/runtime/post-checks.ts +122 -0
- package/src/runtime/process-status.ts +14 -1
- package/src/runtime/progress-event-coalescer.ts +43 -43
- package/src/runtime/prose-compressor.ts +164 -164
- package/src/runtime/recovery-recipes.ts +74 -74
- package/src/runtime/result-extractor.ts +121 -121
- package/src/runtime/role-permission.ts +39 -39
- package/src/runtime/sensitive-paths.ts +3 -3
- package/src/runtime/session-resources.ts +25 -25
- package/src/runtime/session-snapshot.ts +59 -59
- package/src/runtime/session-usage.ts +79 -79
- package/src/runtime/sidechain-output.ts +29 -29
- package/src/runtime/stream-preview.ts +177 -177
- package/src/runtime/supervisor-contact.ts +59 -59
- package/src/runtime/task-display.ts +38 -38
- package/src/runtime/task-graph.ts +207 -0
- package/src/runtime/task-quality.ts +207 -0
- package/src/runtime/task-runner/capabilities.ts +78 -78
- package/src/runtime/task-runner/live-executor.ts +7 -1
- package/src/runtime/task-runner/progress.ts +119 -119
- package/src/runtime/task-runner/prompt-builder.ts +1 -1
- package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
- package/src/runtime/task-runner/result-utils.ts +14 -14
- package/src/runtime/task-runner/run-projection.ts +103 -103
- package/src/runtime/task-runner/state-helpers.ts +22 -22
- package/src/runtime/team-runner.ts +126 -7
- package/src/runtime/worker-heartbeat.ts +21 -21
- package/src/runtime/worker-startup.ts +57 -57
- package/src/runtime/workflow-state.ts +187 -0
- package/src/runtime/workspace-tree.ts +298 -298
- package/src/schema/config-schema.ts +12 -0
- package/src/schema/validation-types.ts +148 -0
- package/src/skills/skill-templates.ts +374 -0
- package/src/state/active-run-registry.ts +35 -11
- package/src/state/atomic-write.ts +33 -26
- package/src/state/contracts.ts +1 -0
- package/src/state/event-reconstructor.ts +217 -0
- package/src/state/locks.ts +2 -11
- package/src/state/mailbox.ts +4 -3
- package/src/state/state-store.ts +32 -14
- package/src/state/task-claims.ts +44 -44
- package/src/state/types.ts +9 -0
- package/src/state/usage.ts +29 -29
- package/src/subagents/async-entry.ts +1 -1
- package/src/subagents/index.ts +3 -3
- package/src/subagents/live/control.ts +1 -1
- package/src/subagents/live/manager.ts +1 -1
- package/src/subagents/live/realtime.ts +1 -1
- package/src/subagents/live/session-runtime.ts +1 -1
- package/src/subagents/manager.ts +1 -1
- package/src/subagents/spawn.ts +1 -1
- package/src/teams/team-serializer.ts +38 -38
- package/src/types/diff.d.ts +18 -18
- package/src/ui/crew-footer.ts +101 -101
- package/src/ui/crew-select-list.ts +111 -111
- package/src/ui/crew-widget.ts +9 -4
- package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
- package/src/ui/dashboard-panes/capability-pane.ts +59 -59
- package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
- package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
- package/src/ui/dashboard-panes/progress-pane.ts +11 -0
- package/src/ui/dynamic-border.ts +25 -25
- package/src/ui/layout-primitives.ts +106 -106
- package/src/ui/loaders.ts +158 -158
- package/src/ui/powerbar-publisher.ts +6 -0
- package/src/ui/render-coalescer.ts +51 -51
- package/src/ui/render-diff.ts +119 -119
- package/src/ui/render-scheduler.ts +143 -143
- package/src/ui/run-action-dispatcher.ts +10 -1
- package/src/ui/spinner.ts +17 -17
- package/src/ui/status-colors.ts +58 -58
- package/src/ui/syntax-highlight.ts +116 -116
- package/src/ui/transcript-entries.ts +258 -258
- package/src/utils/completion-dedupe.ts +63 -63
- package/src/utils/frontmatter.ts +68 -68
- package/src/utils/git.ts +262 -262
- package/src/utils/ids.ts +17 -17
- package/src/utils/incremental-reader.ts +104 -104
- package/src/utils/names.ts +27 -27
- package/src/utils/redaction.ts +44 -44
- package/src/utils/safe-paths.ts +47 -47
- package/src/utils/scan-cache.ts +136 -136
- package/src/utils/sleep.ts +40 -26
- package/src/utils/task-name-generator.ts +337 -337
- package/src/workflows/validate-workflow.ts +40 -40
- package/src/worktree/branch-freshness.ts +45 -45
- package/src/worktree/worktree-manager.ts +11 -3
- package/teams/default.team.md +12 -12
- package/teams/fast-fix.team.md +11 -11
- package/teams/implementation.team.md +18 -18
- package/teams/parallel-research.team.md +14 -14
- package/teams/research.team.md +11 -11
- package/teams/review.team.md +12 -12
- package/workflows/default.workflow.md +30 -29
- package/workflows/fast-fix.workflow.md +23 -22
- package/workflows/implementation.workflow.md +43 -38
- package/workflows/parallel-research.workflow.md +46 -46
- package/workflows/research.workflow.md +22 -22
- package/workflows/review.workflow.md +30 -30
- package/docs/refactor-tasks-phase3.md +0 -394
- package/docs/refactor-tasks-phase4.md +0 -564
- package/docs/refactor-tasks-phase5.md +0 -402
- package/docs/refactor-tasks-phase6.md +0 -662
- package/docs/refactor-tasks.md +0 -1484
- package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
- package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
- package/docs/research/AUDIT_OH_MY_PI.md +0 -261
- package/docs/research/AUDIT_PI_CREW.md +0 -457
- package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
- package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
- package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
- package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
- package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
- package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
- package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
- package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
- package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
- package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
- package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
- package/docs/research-awesome-agent-skills-distillation.md +0 -100
- package/docs/research-extension-examples.md +0 -297
- package/docs/research-extension-system.md +0 -324
- package/docs/research-oh-my-pi-distillation.md +0 -369
- package/docs/research-optimization-plan.md +0 -548
- package/docs/research-phase10-distillation.md +0 -199
- package/docs/research-phase11-distillation.md +0 -201
- package/docs/research-phase8-operator-experience-plan.md +0 -819
- package/docs/research-phase9-observability-reliability-plan.md +0 -1190
- package/docs/research-pi-coding-agent.md +0 -357
- package/docs/research-source-pi-crew-reference.md +0 -174
- package/docs/research-ui-optimization-plan.md +0 -480
- package/docs/source-runtime-refactor-map.md +0 -107
- package/src/utils/atomic-write.ts +0 -33
|
@@ -1,819 +0,0 @@
|
|
|
1
|
-
# Phase 8 — Operator Experience: Interactive Mailbox, Health Pane, Smart Notifications
|
|
2
|
-
|
|
3
|
-
> Tiếp nối tự nhiên của Phase 7 (UI Optimization). Mục tiêu: biến dashboard từ "viewer" thành "operator console" — actions thực hiện được trực tiếp từ UI, không phải toggle CLI. Path X chosen (Phase 8 = Theme A, Phase 9 = Theme B+C Observability+Reliability deferred).
|
|
4
|
-
|
|
5
|
-
**Open Questions Resolution (Q1-Q6 đã chốt — xem Section 7 chi tiết):**
|
|
6
|
-
- Q1=(b) Có preview compose pane | Q2=(c) Sink JSONL khi `telemetry.enabled` | Q3=(b) Cross-day quiet-hours wrap
|
|
7
|
-
- Q4=(c) Full action menu R/K/D trên health pane | Q5=(c) Confirm chỉ destructive | Q6=(a) ESC discard + confirm-if-long guard
|
|
8
|
-
|
|
9
|
-
## 0. Implementation Status
|
|
10
|
-
|
|
11
|
-
- [x] 8.0 Foundation: keybinding contract + action dispatcher + RunActionResult shape + ConfirmOverlay primitive
|
|
12
|
-
- [x] 8.1.A Mailbox detail overlay (passive list view, no actions yet)
|
|
13
|
-
- [x] 8.1.B Mailbox ack action (hotkey `A` trên message đang chọn)
|
|
14
|
-
- [x] 8.1.C Mailbox nudge action (hotkey `N` + agent picker)
|
|
15
|
-
- [x] 8.1.D Mailbox compose action (hotkey `C` + form overlay) — Q6: ESC discard + confirm-if-long (>50 chars)
|
|
16
|
-
- [x] 8.1.E Mailbox compose preview pane (key `P` toggle, render markdown read-only) — Q1
|
|
17
|
-
- [x] 8.1.F Mailbox ackAll destructive action (hotkey `Shift+X`) — Q5: requires confirm overlay
|
|
18
|
-
- [x] 8.2.A Heartbeat aggregator (`heartbeat-aggregator.ts`)
|
|
19
|
-
- [x] 8.2.B Health pane (pane index `5`) trong dashboard
|
|
20
|
-
- [x] 8.2.C Auto-recovery prompt (stuck worker > N minutes → toast + confirm) — throttled 5min/run
|
|
21
|
-
- [x] 8.2.D Health pane action menu — `R` recovery (foreground only), `K` kill stale workers, `D` diagnostic export — Q4
|
|
22
|
-
- [x] 8.3.A Notification router (severity classifier + dedup window)
|
|
23
|
-
- [x] 8.3.B Notification quiet-hours (cross-day wrap parser) + batching config — Q3
|
|
24
|
-
- [x] 8.3.C Toast badge counter trong widget/powerbar (đếm số notification chưa ack)
|
|
25
|
-
- [x] 8.3.D Notification JSONL sink rotate 7 ngày, gated bởi `telemetry.enabled` — Q2
|
|
26
|
-
- [x] 8.4 Wire `register.ts` + `commands.ts`
|
|
27
|
-
- [x] 8.5 Tests: unit + integration
|
|
28
|
-
|
|
29
|
-
## 1. Roadmap-Level Decisions
|
|
30
|
-
|
|
31
|
-
| # | Decision | Chosen | Rationale |
|
|
32
|
-
|---|---|---|---|
|
|
33
|
-
| D1 | Mailbox actions chạy trực tiếp hay dispatch về team API? | **Dispatch** qua `handleTeamTool({action:"api", config:{operation:...}})` | Tận dụng API hiện có (`ack-message`, `send-message`, `nudge-agent`); zero state-machine duplication; locks/events được giữ nguyên |
|
|
34
|
-
| D2 | Overlay form vs inline edit? | **Overlay form** (modal-like, anchor center) | Dashboard sidebar quá hẹp cho text input; overlay tách biệt focus; ESC dễ cancel |
|
|
35
|
-
| D3 | Health pane là pane mới (`5`) hay tab trong progress? | **Pane mới `5`** | Tránh pollute progress pane; cho user toggle độc lập; consistent với existing 1-4 |
|
|
36
|
-
| D4 | Notification sink: optional opt-in hay default-on? | **Default-on khi `telemetry.enabled !== false`** (Q2=c) | Đồng nhất pattern Phase 6 telemetry; debug-friendly; user opt-out qua telemetry config chung. Path: `<crewRoot>/state/notifications/{YYYY-MM-DD}.jsonl`, rotate 7 ngày |
|
|
37
|
-
| D5 | Quiet-hours format + cross-day? | **HH:MM-HH:MM trong config local timezone, support cross-day wrap** (Q3=b) | Single range `"22:00-07:00"` parser tự nhận diện wrap-around; intuitive vs multi-range array |
|
|
38
|
-
| D6 | Compose-form fields scope? | **Phase 8: from/to/body/taskId + preview pane** (Q1=b) | Preview key `P` toggle render markdown read-only; thread/attachment defer Phase 9 |
|
|
39
|
-
| D7 | Action mới có break keybinding cũ? | **No** — phím mới: `A/N/C/P/Shift+X` (mailbox), `R/K/D` (health), `H/X` (notification); phím hiện hành (`s/u/a/i/d/m/e/o/v/r/p/1-4/k/j`) giữ nguyên (lowercase `r` vẫn = reload root, uppercase `R` = recovery in health pane only) | Backward-compat; context-scoped uppercase |
|
|
40
|
-
| D8 | Mailbox detail panel: inline expand hay separate overlay? | **Separate overlay** (mở khi nhấn Enter trên pane mailbox) | Pane chính giữ nguyên density; overlay scrollable |
|
|
41
|
-
| D9 | Health pane action mode: prompt-only vs full menu? | **Full action menu (Q4=c)**: `R` recovery (foreground-only), `K` kill stale workers, `D` diagnostic export | Operator power-user toolkit; async runs `R/K` disabled with hint; `D` cực hữu ích cho bug report |
|
|
42
|
-
| D10 | Foundation 8.0: tách RunActionDispatcher hay inline? | **Tách module** `src/ui/run-action-dispatcher.ts` | Reuse cho overlay con; dễ test; không bloat dashboard |
|
|
43
|
-
| D11 | Compose ESC behavior? | **Discard + confirm-if-long** (Q6=a) | ESC không lưu draft; nếu body > 50 ký tự → confirm overlay `Y=discard, N=continue editing`; defer draft persistence Phase 9 |
|
|
44
|
-
| D12 | Confirm overlay: per-action ad-hoc hay reusable primitive? | **Reusable primitive** `src/ui/overlays/confirm-overlay.ts` | Q5=c destructive (ackAll/recovery/diagnostic-export-with-secrets) cần consistent UX; reuse cho mọi confirm |
|
|
45
|
-
| D13 | Auto-recovery throttle window? | **5 phút/run/condition-type** | Tránh notification storm khi run dead lâu; `recovery_dead_workers` riêng biệt với `recovery_missing_heartbeat` |
|
|
46
|
-
| D14 | Diagnostic export `D` format & destination? | **JSON + redact secrets** vào `<crewRoot>/artifacts/{runId}/diagnostic-{timestamp}.json` | Self-contained snapshot (manifest + tasks + recent events + heartbeat summary); confirm before write nếu artifact-dir đã có file diag cũ < 1 phút |
|
|
47
|
-
| D15 | Preview pane render scope (Q1=b)? | **Read-only markdown render**: bold/italic/code-block/list — no images/links | Đủ cho operator đọc nội dung trước khi gửi; không cần markdown engine đầy đủ; reuse từ existing transcript-viewer markdown helper nếu có |
|
|
48
|
-
|
|
49
|
-
## 2. Phase Breakdown
|
|
50
|
-
|
|
51
|
-
### Phase 8.0 — Foundation (2 dev-day, +0.5 cho ConfirmOverlay)
|
|
52
|
-
|
|
53
|
-
**File mới:**
|
|
54
|
-
- `src/ui/run-action-dispatcher.ts` — wrapper gọi `handleTeamTool` với `runId` + `operation`, normalize result thành `{ ok, message, data }`.
|
|
55
|
-
- `src/ui/keybinding-map.ts` — central registry mapping `data` (raw stdin) → action name; export `KEY_RESERVED` để overlay con check conflict.
|
|
56
|
-
- `src/ui/overlays/confirm-overlay.ts` — **(Q5)** reusable confirm primitive, anchor center, auto-focus `N` (safe default), Y/Enter=confirm, N/ESC=cancel. ~80 LOC.
|
|
57
|
-
|
|
58
|
-
**Sửa:**
|
|
59
|
-
- `src/ui/run-dashboard.ts` — refactor `handleInput` dùng `keybinding-map`; không thay đổi behavior cũ.
|
|
60
|
-
|
|
61
|
-
**Skeleton:**
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
// run-action-dispatcher.ts
|
|
65
|
-
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
66
|
-
import { handleTeamTool } from "../extension/team-tool.ts";
|
|
67
|
-
|
|
68
|
-
export interface RunActionResult {
|
|
69
|
-
ok: boolean;
|
|
70
|
-
message: string;
|
|
71
|
-
data?: unknown;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function dispatchMailboxAck(ctx: ExtensionContext, runId: string, messageId: string): Promise<RunActionResult> {
|
|
75
|
-
try {
|
|
76
|
-
const r = await handleTeamTool({ action: "api", runId, config: { operation: "ack-message", messageId } }, ctx);
|
|
77
|
-
return { ok: r.metadata?.status === "ok", message: r.text, data: r };
|
|
78
|
-
} catch (error) {
|
|
79
|
-
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export async function dispatchMailboxNudge(ctx: ExtensionContext, runId: string, agentId: string, message: string): Promise<RunActionResult> { /* ... */ }
|
|
84
|
-
export async function dispatchMailboxCompose(ctx: ExtensionContext, runId: string, payload: { from: string; to: string; body: string; taskId?: string; direction: "inbox" | "outbox" }): Promise<RunActionResult> { /* ... */ }
|
|
85
|
-
export async function dispatchMailboxAckAll(ctx: ExtensionContext, runId: string): Promise<RunActionResult> { /* read-mailbox → loop ack-message */ }
|
|
86
|
-
export async function dispatchHealthRecovery(ctx: ExtensionContext, runId: string): Promise<RunActionResult> { /* foreground-interrupt API */ }
|
|
87
|
-
export async function dispatchKillStaleWorkers(ctx: ExtensionContext, runId: string): Promise<RunActionResult> { /* mark dead heartbeats; emit event */ }
|
|
88
|
-
export async function dispatchDiagnosticExport(ctx: ExtensionContext, runId: string): Promise<RunActionResult> { /* read-manifest + list-tasks + read-events limit=200 + heartbeat summary → write artifact */ }
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
// keybinding-map.ts (Q4 + Q5 expanded)
|
|
93
|
-
export const DASHBOARD_KEYS = {
|
|
94
|
-
close: ["q", "\u001b"],
|
|
95
|
-
select: ["\r", "\n", "s"],
|
|
96
|
-
pane: { agents: ["1"], progress: ["2"], mailbox: ["3"], output: ["4"], health: ["5"] },
|
|
97
|
-
// Mailbox detail overlay context
|
|
98
|
-
mailbox: { ack: ["A"], nudge: ["N"], compose: ["C"], preview: ["P"], ackAll: ["X"], openDetail: ["\r", "\n"] },
|
|
99
|
-
// Health pane context (Q4=c full menu)
|
|
100
|
-
health: { recovery: ["R"], killStale: ["K"], diagnosticExport: ["D"] },
|
|
101
|
-
// Notification context
|
|
102
|
-
notification: { dismissAll: ["H"] }, // 'H' for Hush
|
|
103
|
-
} as const;
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
// confirm-overlay.ts
|
|
108
|
-
export interface ConfirmOptions {
|
|
109
|
-
title: string;
|
|
110
|
-
body?: string;
|
|
111
|
-
dangerLevel?: "low" | "medium" | "high"; // colors theme accent
|
|
112
|
-
defaultAction?: "confirm" | "cancel"; // default "cancel"
|
|
113
|
-
}
|
|
114
|
-
export class ConfirmOverlay {
|
|
115
|
-
constructor(private opts: ConfirmOptions, private done: (confirmed: boolean) => void, private theme: unknown) {}
|
|
116
|
-
render(width: number): string[] { /* anchor-center box, dim Y/N hint */ }
|
|
117
|
-
handleInput(data: string): void {
|
|
118
|
-
if (data === "y" || data === "Y" || data === "\r" || data === "\n") return this.done(true);
|
|
119
|
-
if (data === "n" || data === "N" || data === "\u001b" || data === "q") return this.done(false);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
**Tests:**
|
|
125
|
-
- `test/unit/run-action-dispatcher.test.ts` (7 test cases — 4 mailbox dispatchers + 3 health dispatchers, mock `handleTeamTool`).
|
|
126
|
-
- `test/unit/confirm-overlay.test.ts` (4 cases: render, Y confirms, N cancels, default cancel safety).
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
### Phase 8.1 — Mailbox Interactivity
|
|
131
|
-
|
|
132
|
-
#### 8.1.A Mailbox detail overlay (1 dev-day)
|
|
133
|
-
|
|
134
|
-
**File mới:**
|
|
135
|
-
- `src/ui/overlays/mailbox-detail-overlay.ts` — class `MailboxDetailOverlay` implement Pi UI custom widget; render 2-column (inbox | outbox); ↑/↓ select; Enter expand body; ESC/q close.
|
|
136
|
-
|
|
137
|
-
**Cập nhật:**
|
|
138
|
-
- `src/ui/dashboard-panes/mailbox-pane.ts` — line cuối cùng đổi từ "use /team-api ..." thành `"Press Enter on mailbox pane to open detail (A=ack, N=nudge, C=compose)"`.
|
|
139
|
-
- `src/ui/run-dashboard.ts` — khi `activePane === "mailbox"` và user nhấn Enter, return `{action: "mailbox-detail"}` thay vì close.
|
|
140
|
-
- `src/extension/registration/commands.ts` — handle `selection.action === "mailbox-detail"` → mở `MailboxDetailOverlay` qua `ctx.ui.custom`.
|
|
141
|
-
|
|
142
|
-
**Skeleton:**
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
export class MailboxDetailOverlay {
|
|
146
|
-
private inbox: MailboxMessage[] = [];
|
|
147
|
-
private outbox: MailboxMessage[] = [];
|
|
148
|
-
private selected = 0;
|
|
149
|
-
private side: "inbox" | "outbox" = "inbox";
|
|
150
|
-
constructor(private opts: { runId: string; cwd: string; ctx: ExtensionContext; done: (sel?: MailboxAction) => void; theme: unknown }) {
|
|
151
|
-
this.refresh();
|
|
152
|
-
}
|
|
153
|
-
private refresh(): void { /* read mailbox via team api */ }
|
|
154
|
-
render(width: number): string[] { /* 2-col layout, highlight selected */ }
|
|
155
|
-
handleInput(data: string): void { /* arrow nav, A/N/C dispatch via this.opts.done */ }
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface MailboxAction {
|
|
159
|
-
type: "ack" | "nudge" | "compose" | "reply";
|
|
160
|
-
messageId?: string;
|
|
161
|
-
agentId?: string;
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
**Tests:** `test/unit/mailbox-detail-overlay.test.ts` — 4 cases (render empty, render with items, key navigation, action dispatch).
|
|
166
|
-
|
|
167
|
-
#### 8.1.B Ack action (0.75 dev-day)
|
|
168
|
-
|
|
169
|
-
**Logic:** trong `MailboxDetailOverlay.handleInput`, key `A` (uppercase, để tránh conflict với `a`=artifacts ở dashboard root) → `done({type:"ack", messageId: selectedMessage.id})`.
|
|
170
|
-
|
|
171
|
-
**Update `commands.ts`:** sau khi overlay close, nếu action.type === "ack" → call `dispatchMailboxAck(ctx, runId, action.messageId!)` → toast result.
|
|
172
|
-
|
|
173
|
-
**Acceptance:** ack thành công → mailbox pane re-render với attention count giảm trong < 250ms (snapshot cache invalidate khi `crew.mailbox.acknowledged` event).
|
|
174
|
-
|
|
175
|
-
#### 8.1.C Nudge action (0.75 dev-day)
|
|
176
|
-
|
|
177
|
-
**Logic:** key `N` → mở agent picker overlay (reuse pattern từ existing `LiveRunSidebar`); chọn xong → message input → dispatch `dispatchMailboxNudge`.
|
|
178
|
-
|
|
179
|
-
**File mới:** `src/ui/overlays/agent-picker-overlay.ts` (nhỏ, 80-120 LOC).
|
|
180
|
-
|
|
181
|
-
**Acceptance:** nudge → `crew.mailbox.message` event fire → snapshot invalidate → mailbox pane attention count tăng đúng.
|
|
182
|
-
|
|
183
|
-
#### 8.1.D Compose form (1.25 dev-day)
|
|
184
|
-
|
|
185
|
-
**File mới:** `src/ui/overlays/mailbox-compose-overlay.ts` — form 4 field (from/to/body/taskId), Tab navigation, Enter submit, ESC cancel.
|
|
186
|
-
|
|
187
|
-
**Behavior chi tiết (Q6=a):**
|
|
188
|
-
- Tab/Shift+Tab: cycle giữa các field.
|
|
189
|
-
- Body multi-line: Ctrl+Enter → newline; Enter trên field body với content non-empty → submit.
|
|
190
|
-
- ESC khi body ≤ 50 ký tự → discard immediately, close overlay.
|
|
191
|
-
- ESC khi body > 50 ký tự → mở `ConfirmOverlay` với title `"Discard draft?"` body `"Body has N chars. Y=discard, N=continue editing"`. Cancel default = continue editing (safe).
|
|
192
|
-
- Submit validate: body required (non-whitespace), to required, from default `"operator"` if empty.
|
|
193
|
-
- Direction toggle: Tab vào checkbox `[ ] Send to outbox` → Space toggle.
|
|
194
|
-
|
|
195
|
-
**Logic dispatch:** `dispatchMailboxCompose` với `direction` từ checkbox (default `"inbox"` — operator gửi vào inbox của run).
|
|
196
|
-
|
|
197
|
-
**Tests:** `test/unit/mailbox-compose-overlay.test.ts` — 8 cases (render, tab nav, ESC short discard, ESC long → confirm overlay, confirm overlay cancel = stay editing, confirm overlay confirm = discard, Enter submit, validation empty body, validation empty to).
|
|
198
|
-
|
|
199
|
-
#### 8.1.E Compose preview pane (0.75 dev-day) — Q1=b
|
|
200
|
-
|
|
201
|
-
**File mới:** `src/ui/overlays/mailbox-compose-preview.ts` — read-only render markdown của body field hiện tại; share state với `mailbox-compose-overlay.ts`.
|
|
202
|
-
|
|
203
|
-
**Layout:** compose overlay split horizontal khi preview active — 60% form / 40% preview pane (pane render markdown read-only, không cho focus).
|
|
204
|
-
|
|
205
|
-
**Render scope (D15):** bold (`**`), italic (`*`), code-block (`` ``` ``), inline code (`` ` ``), unordered list (`-`), numbered list (`1.`), heading (`#`/`##`/`###`). Skip images/links (out of scope; render link text only).
|
|
206
|
-
|
|
207
|
-
**Behavior:**
|
|
208
|
-
- Key `P` toggle preview on/off (state in compose overlay).
|
|
209
|
-
- Preview cập nhật real-time khi body thay đổi (debounce 100ms để tránh re-render mỗi keystroke).
|
|
210
|
-
- Khi preview active, header help line update: `"P close preview · Tab cycle · Enter submit · ESC discard"`.
|
|
211
|
-
|
|
212
|
-
**Skeleton:**
|
|
213
|
-
|
|
214
|
-
```ts
|
|
215
|
-
// mailbox-compose-preview.ts
|
|
216
|
-
export function renderComposePreview(body: string, width: number, theme: CrewTheme): string[] {
|
|
217
|
-
const tokens = tokenizeMarkdown(body); // simple tokenizer ~80 LOC
|
|
218
|
-
return tokens.flatMap((t) => renderToken(t, width, theme));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function tokenizeMarkdown(body: string): MdToken[] { /* line-by-line scan */ }
|
|
222
|
-
type MdToken = { type: "heading" | "code-block" | "list-item" | "paragraph"; level?: number; text: string };
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Tests:** `test/unit/mailbox-compose-preview.test.ts` — 6 cases (plain text, bold/italic, code block, list, heading, mixed content).
|
|
226
|
-
|
|
227
|
-
#### 8.1.F Mailbox ackAll (0.5 dev-day) — Q5=c destructive
|
|
228
|
-
|
|
229
|
-
**Logic:** trong `MailboxDetailOverlay.handleInput`, key `Shift+X` (raw stdin `"X"` uppercase) → mở `ConfirmOverlay`:
|
|
230
|
-
- Title: `"Acknowledge all N unread messages?"`
|
|
231
|
-
- Body: `"This cannot be undone. Y=ack all, N=cancel."`
|
|
232
|
-
- DangerLevel: `"medium"`.
|
|
233
|
-
|
|
234
|
-
Confirm `Y` → `dispatchMailboxAckAll(ctx, runId)` (dispatcher loop ack-message từng id) → toast result `"Acknowledged N messages."`.
|
|
235
|
-
|
|
236
|
-
**Acceptance:** ackAll trong run với 10 unread → all marked acknowledged trong < 2s; mailbox pane attention → 0; emit 10x `crew.mailbox.acknowledged` event.
|
|
237
|
-
|
|
238
|
-
**Tests:** `test/unit/mailbox-detail-overlay.test.ts` thêm 3 cases (Shift+X opens confirm, confirm Y dispatches loop, confirm N stays).
|
|
239
|
-
|
|
240
|
-
---
|
|
241
|
-
|
|
242
|
-
### Phase 8.2 — Health Pane & Recovery
|
|
243
|
-
|
|
244
|
-
#### 8.2.A Heartbeat aggregator (1 dev-day)
|
|
245
|
-
|
|
246
|
-
**File mới:** `src/ui/heartbeat-aggregator.ts`
|
|
247
|
-
|
|
248
|
-
```ts
|
|
249
|
-
export interface HeartbeatSummary {
|
|
250
|
-
runId: string;
|
|
251
|
-
totalTasks: number;
|
|
252
|
-
healthy: number; // alive=true, lastSeenAt < threshold
|
|
253
|
-
stale: number; // lastSeenAt > stale threshold (default 60s)
|
|
254
|
-
dead: number; // lastSeenAt > dead threshold (default 5min) hoặc alive=false
|
|
255
|
-
missing: number; // task running nhưng no heartbeat record
|
|
256
|
-
worstStaleMs: number;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function summarizeHeartbeats(snapshot: RunUiSnapshot, opts?: { staleMs?: number; deadMs?: number; now?: number }): HeartbeatSummary { /* ... */ }
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
**Tests:** `test/unit/heartbeat-aggregator.test.ts` — 6 cases (all healthy, mixed, all dead, missing record, custom threshold, edge `lastSeenAt=now`).
|
|
263
|
-
|
|
264
|
-
#### 8.2.B Health pane (0.75 dev-day)
|
|
265
|
-
|
|
266
|
-
**File mới:** `src/ui/dashboard-panes/health-pane.ts`
|
|
267
|
-
|
|
268
|
-
```ts
|
|
269
|
-
export function renderHealthPane(snapshot: RunUiSnapshot | undefined, opts?: { staleMs?: number; deadMs?: number; isForeground?: boolean }): string[] {
|
|
270
|
-
if (!snapshot) return ["Health pane: snapshot unavailable"];
|
|
271
|
-
const summary = summarizeHeartbeats(snapshot, opts);
|
|
272
|
-
const lines: string[] = [
|
|
273
|
-
`Health: ${summary.healthy}/${summary.totalTasks} healthy · stale=${summary.stale} · dead=${summary.dead} · missing=${summary.missing}`,
|
|
274
|
-
];
|
|
275
|
-
if (summary.worstStaleMs > 0) lines.push(`Worst stale: ${Math.round(summary.worstStaleMs / 1000)}s ago`);
|
|
276
|
-
// Q4=c: show full action menu hint
|
|
277
|
-
const actionHints: string[] = [];
|
|
278
|
-
if ((summary.dead > 0 || summary.missing > 0) && opts?.isForeground !== false) actionHints.push("R recovery");
|
|
279
|
-
if (summary.dead > 0 || summary.stale > 0) actionHints.push("K kill stale");
|
|
280
|
-
actionHints.push("D diagnostic export");
|
|
281
|
-
if (actionHints.length > 0) lines.push(`Actions: ${actionHints.join(" · ")}`);
|
|
282
|
-
if (summary.dead > 0 && opts?.isForeground === false) lines.push("(Async run: R/K disabled — use kill <pid> manually)");
|
|
283
|
-
return lines;
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
**Update `run-dashboard.ts`:**
|
|
288
|
-
- Thêm `"health"` vào type `Pane`.
|
|
289
|
-
- Key `5` → `activePane = "health"`.
|
|
290
|
-
- Switch case render `renderHealthPane` với `isForeground` từ `selectedRun.async ? false : true`.
|
|
291
|
-
- Trong `handleInput`: nếu `activePane === "health"`:
|
|
292
|
-
- `R` → emit `{action: "health-recovery", runId}` (handler sẽ check foreground + ConfirmOverlay).
|
|
293
|
-
- `K` → emit `{action: "health-kill-stale", runId}` (handler ConfirmOverlay if dead > 5).
|
|
294
|
-
- `D` → emit `{action: "health-diagnostic-export", runId}` (handler check existing diag < 1min → confirm overwrite).
|
|
295
|
-
- Header help line update: `"1 agents 2 progress 3 mailbox 4 output 5 health • s/u/a/i actions • R/K/D health"`.
|
|
296
|
-
|
|
297
|
-
**Tests:** `test/unit/health-pane.test.ts` — 6 cases (no snapshot, all healthy → only D hint, dead foreground → R+K+D, dead async → only D + warning, mixed states, foreground false hint visible).
|
|
298
|
-
|
|
299
|
-
#### 8.2.C Auto-recovery toast (0.5 dev-day) — Q4 simplified
|
|
300
|
-
|
|
301
|
-
**Logic:** `RenderScheduler.tick` callback (đã có) gọi `summarizeHeartbeats`; nếu `dead > 0` hoặc `missing > 0` lần đầu → fire toast qua `notification-router` (8.3.A) với severity `"warning"`:
|
|
302
|
-
- Title: `"Run {runId} has {N} dead workers"`.
|
|
303
|
-
- Body: `"Open dashboard → 5 health → R recovery / K kill stale / D diagnostic"`.
|
|
304
|
-
|
|
305
|
-
**Throttle (D13):** dedup id = `recovery_dead_workers_${runId}` — router dedup 5 phút/run/condition-type. Riêng `recovery_missing_heartbeat` có id khác để alert song song nếu cả hai cùng xảy ra.
|
|
306
|
-
|
|
307
|
-
**Tests:** `test/integration/health-recovery.test.ts` — simulate stale heartbeat, verify single toast emitted; emit lần 2 trong window → drop; emit lần 2 sau 5min → fire lại.
|
|
308
|
-
|
|
309
|
-
#### 8.2.D Health action handlers (1.5 dev-day) — Q4=c full menu
|
|
310
|
-
|
|
311
|
-
**Update `src/extension/registration/commands.ts`:** handle 3 new actions từ dashboard:
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
// pseudo-code
|
|
315
|
-
if (selection.action === "health-recovery") {
|
|
316
|
-
const run = manifestCache.get(selection.runId);
|
|
317
|
-
if (run?.async) { ctx.ui.notify("Recovery only available for foreground runs.", "warning"); return; }
|
|
318
|
-
const confirmed = await openConfirmOverlay(ctx, { title: "Interrupt foreground run?", body: "Tasks will be marked failed. Y=interrupt, N=cancel.", dangerLevel: "high" });
|
|
319
|
-
if (!confirmed) return;
|
|
320
|
-
const r = await dispatchHealthRecovery(ctx, selection.runId);
|
|
321
|
-
ctx.ui.notify(r.message, r.ok ? "info" : "error");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
if (selection.action === "health-kill-stale") {
|
|
325
|
-
const summary = summarizeHeartbeats(snapshotCache.get(selection.runId)!);
|
|
326
|
-
if (summary.dead + summary.stale > 5) {
|
|
327
|
-
const confirmed = await openConfirmOverlay(ctx, { title: `Kill ${summary.dead + summary.stale} stale workers?`, dangerLevel: "medium" });
|
|
328
|
-
if (!confirmed) return;
|
|
329
|
-
}
|
|
330
|
-
const r = await dispatchKillStaleWorkers(ctx, selection.runId);
|
|
331
|
-
ctx.ui.notify(r.message, r.ok ? "info" : "error");
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (selection.action === "health-diagnostic-export") {
|
|
335
|
-
// D14: check existing diag in last 1min
|
|
336
|
-
const diagDir = path.join(run.artifactsRoot, "diagnostic");
|
|
337
|
-
const recentDiag = listRecentDiagnostic(diagDir, 60_000);
|
|
338
|
-
if (recentDiag) {
|
|
339
|
-
const confirmed = await openConfirmOverlay(ctx, { title: "Recent diagnostic exists", body: `File ${recentDiag} created < 1min ago. Overwrite?`, defaultAction: "cancel" });
|
|
340
|
-
if (!confirmed) return;
|
|
341
|
-
}
|
|
342
|
-
const r = await dispatchDiagnosticExport(ctx, selection.runId);
|
|
343
|
-
ctx.ui.notify(`Diagnostic exported to ${r.data}`, r.ok ? "info" : "error");
|
|
344
|
-
}
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
**File mới:** `src/runtime/diagnostic-export.ts` — collect manifest + tasks + recent events (limit 200) + heartbeat summary + agent status snapshot; redact secrets từ env/config (block list: `*token*`, `*key*`, `*password*`, `*secret*`); write JSON vào `<crewRoot>/artifacts/{runId}/diagnostic-{ISO-timestamp}.json`.
|
|
348
|
-
|
|
349
|
-
**Skeleton:**
|
|
350
|
-
|
|
351
|
-
```ts
|
|
352
|
-
// diagnostic-export.ts
|
|
353
|
-
export interface DiagnosticReport {
|
|
354
|
-
runId: string;
|
|
355
|
-
exportedAt: string;
|
|
356
|
-
manifest: TeamRunManifest;
|
|
357
|
-
tasks: TeamTaskState[];
|
|
358
|
-
recentEvents: TeamEvent[];
|
|
359
|
-
heartbeat: HeartbeatSummary;
|
|
360
|
-
agents: { taskId: string; status: AgentStatus }[];
|
|
361
|
-
envRedacted: Record<string, string>; // env vars with secrets masked as "***"
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function exportDiagnostic(ctx: ExtensionContext, runId: string): Promise<{ path: string; report: DiagnosticReport }> { /* ... */ }
|
|
365
|
-
|
|
366
|
-
function redactSecrets(obj: unknown): unknown { /* recursive replace values where key matches block list */ }
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
**Tests:**
|
|
370
|
-
- `test/unit/diagnostic-export.test.ts` — 5 cases (basic export, secret redaction, missing run errors, file path generation, JSON validity).
|
|
371
|
-
- Smoke: export → open file → verify đầy đủ field + 0 secrets.
|
|
372
|
-
|
|
373
|
-
---
|
|
374
|
-
|
|
375
|
-
### Phase 8.3 — Smart Notifications
|
|
376
|
-
|
|
377
|
-
#### 8.3.A Notification router (1 dev-day)
|
|
378
|
-
|
|
379
|
-
**File mới:** `src/extension/notification-router.ts`
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
export type Severity = "info" | "warning" | "error" | "critical";
|
|
383
|
-
|
|
384
|
-
export interface NotificationDescriptor {
|
|
385
|
-
id?: string; // dedup key; nếu cùng id trong window → drop
|
|
386
|
-
severity: Severity;
|
|
387
|
-
source: string; // "run-completed" | "subagent-stuck" | "health" | ...
|
|
388
|
-
runId?: string;
|
|
389
|
-
title: string;
|
|
390
|
-
body?: string;
|
|
391
|
-
timestamp?: number;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export interface NotificationRouterOptions {
|
|
395
|
-
dedupWindowMs?: number; // default 30000
|
|
396
|
-
batchWindowMs?: number; // default 0 (no batching by default)
|
|
397
|
-
quietHours?: string; // "22:00-07:00" local
|
|
398
|
-
severityFilter?: Severity[]; // default: ["warning", "error", "critical"]
|
|
399
|
-
sink?: (n: NotificationDescriptor) => void; // optional file/stream sink
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
export class NotificationRouter {
|
|
403
|
-
constructor(private opts: NotificationRouterOptions = {}, private deliver: (n: NotificationDescriptor) => void) {}
|
|
404
|
-
enqueue(n: NotificationDescriptor): void { /* dedup check, severity filter, quiet-hours skip, batch buffer, sink */ }
|
|
405
|
-
flush(): void { /* deliver batched */ }
|
|
406
|
-
dispose(): void { /* clear timers */ }
|
|
407
|
-
}
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
**Wrap `sendFollowUp`:** trong `register.ts`, thay 2 call sites `sendFollowUp(...)` thành `notificationRouter.enqueue({...})`. Router decides có deliver qua `sendFollowUp` hay không.
|
|
411
|
-
|
|
412
|
-
**Tests:** `test/unit/notification-router.test.ts` — 8 cases (dedup, severity filter, quiet hours mock clock, batch, sink invocation, dispose cleanup).
|
|
413
|
-
|
|
414
|
-
#### 8.3.B Quiet-hours + batching config (0.75 dev-day) — Q3=b cross-day wrap
|
|
415
|
-
|
|
416
|
-
**Update `src/schema/config-schema.ts`:**
|
|
417
|
-
```ts
|
|
418
|
-
notifications: Type.Optional(Type.Object({
|
|
419
|
-
enabled: Type.Optional(Type.Boolean()),
|
|
420
|
-
severityFilter: Type.Optional(Type.Array(Type.Union([Type.Literal("info"), Type.Literal("warning"), Type.Literal("error"), Type.Literal("critical")]))),
|
|
421
|
-
dedupWindowMs: Type.Optional(Type.Integer({ minimum: 1000 })),
|
|
422
|
-
batchWindowMs: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
423
|
-
quietHours: Type.Optional(Type.String({ pattern: "^\\d{2}:\\d{2}-\\d{2}:\\d{2}$" })),
|
|
424
|
-
sinkRetentionDays: Type.Optional(Type.Integer({ minimum: 1, maximum: 90 })), // Q2=c, default 7
|
|
425
|
-
})),
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
**Update `src/config/defaults.ts`:** sane defaults (`severityFilter: ["warning","error","critical"]`, `dedupWindowMs: 30_000`, `batchWindowMs: 0`, `sinkRetentionDays: 7`).
|
|
429
|
-
|
|
430
|
-
**Update `src/config/config.ts`:** parse + merge giống các section khác.
|
|
431
|
-
|
|
432
|
-
**Cross-day parser (Q3=b):** trong `notification-router.ts`, helper isolated cho easy testing:
|
|
433
|
-
|
|
434
|
-
```ts
|
|
435
|
-
// notification-router.ts (excerpt)
|
|
436
|
-
export function parseHHMMRange(range: string): { startMin: number; endMin: number } {
|
|
437
|
-
const [s, e] = range.split("-").map((part) => {
|
|
438
|
-
const [hh, mm] = part.split(":").map(Number);
|
|
439
|
-
return hh * 60 + mm;
|
|
440
|
-
});
|
|
441
|
-
return { startMin: s, endMin: e };
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export function isInQuietHours(range: string, now: Date = new Date()): boolean {
|
|
445
|
-
const { startMin, endMin } = parseHHMMRange(range);
|
|
446
|
-
const cur = now.getHours() * 60 + now.getMinutes();
|
|
447
|
-
if (startMin === endMin) return false; // empty range
|
|
448
|
-
// Q3=b: cross-day wrap when start > end
|
|
449
|
-
return startMin <= endMin
|
|
450
|
-
? (cur >= startMin && cur < endMin)
|
|
451
|
-
: (cur >= startMin || cur < endMin);
|
|
452
|
-
}
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
**Tests:** `test/unit/notification-router.test.ts` thêm 4 cases parser:
|
|
456
|
-
- `"09:00-17:00"` ở 12:00 → quiet (true).
|
|
457
|
-
- `"09:00-17:00"` ở 22:00 → not quiet (false).
|
|
458
|
-
- `"22:00-07:00"` ở 23:30 → quiet (cross-day true).
|
|
459
|
-
- `"22:00-07:00"` ở 03:00 → quiet (cross-day true).
|
|
460
|
-
- `"22:00-07:00"` ở 12:00 → not quiet (false).
|
|
461
|
-
- Edge: `"00:00-23:59"` ở 12:00 → quiet (always-quiet within day).
|
|
462
|
-
- Edge: `"00:00-00:00"` → always not quiet (empty range).
|
|
463
|
-
|
|
464
|
-
#### 8.3.C Toast badge integration (0.75 dev-day)
|
|
465
|
-
|
|
466
|
-
**Logic:** `NotificationRouter.deliver` → ngoài `sendFollowUp`, cộng `unreadCount++` trong `widgetState.notificationCount`. Reset khi user mở mailbox detail hoặc nhấn `H` (Hush — dismiss-all notifications visible badge).
|
|
467
|
-
|
|
468
|
-
**Update `crew-widget.ts`:** model render thêm `🔔${count}` nếu `count > 0`. Để tránh emoji compatibility issue → fallback `[!${count}]` khi terminal không support emoji (detect qua `process.env.TERM`).
|
|
469
|
-
|
|
470
|
-
**Update `powerbar-publisher.ts`:** segment `pi-crew-active` text append ` 🔔${count}` (hoặc fallback) khi active.
|
|
471
|
-
|
|
472
|
-
**Tests:** `test/unit/widget-notification-badge.test.ts` — 5 cases (no count, count=1, count>9, dismiss reset, terminal fallback).
|
|
473
|
-
|
|
474
|
-
#### 8.3.D Notification JSONL sink (0.5 dev-day) — Q2=c
|
|
475
|
-
|
|
476
|
-
**File mới:** `src/extension/notification-sink.ts`
|
|
477
|
-
|
|
478
|
-
**Logic:** khi config `telemetry.enabled !== false`, NotificationRouter delivery cũng gọi `sink.write(descriptor)`. Sink writes vào `<crewRoot>/state/notifications/{YYYY-MM-DD}.jsonl` (1 file/day, append-only).
|
|
479
|
-
|
|
480
|
-
**Rotation:** start-of-day check (lazy, khi write đầu tiên) → delete files cũ hơn `notifications.sinkRetentionDays` (default 7).
|
|
481
|
-
|
|
482
|
-
**Skeleton:**
|
|
483
|
-
|
|
484
|
-
```ts
|
|
485
|
-
// notification-sink.ts
|
|
486
|
-
export interface NotificationSink {
|
|
487
|
-
write(n: NotificationDescriptor): void;
|
|
488
|
-
dispose(): void;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
export function createJsonlSink(crewRoot: string, retentionDays: number): NotificationSink {
|
|
492
|
-
const dir = path.join(crewRoot, "state", "notifications");
|
|
493
|
-
let lastRotateDate = "";
|
|
494
|
-
return {
|
|
495
|
-
write(n) {
|
|
496
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
497
|
-
if (today !== lastRotateDate) {
|
|
498
|
-
rotateOldFiles(dir, retentionDays);
|
|
499
|
-
lastRotateDate = today;
|
|
500
|
-
}
|
|
501
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
502
|
-
fs.appendFileSync(path.join(dir, `${today}.jsonl`), JSON.stringify({ ...n, timestamp: n.timestamp ?? Date.now() }) + "\n");
|
|
503
|
-
},
|
|
504
|
-
dispose() { /* no-op */ },
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function rotateOldFiles(dir: string, retentionDays: number): void {
|
|
509
|
-
if (!fs.existsSync(dir)) return;
|
|
510
|
-
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
511
|
-
for (const file of fs.readdirSync(dir)) {
|
|
512
|
-
if (!file.endsWith(".jsonl")) continue;
|
|
513
|
-
const stat = fs.statSync(path.join(dir, file));
|
|
514
|
-
if (stat.mtimeMs < cutoff) fs.unlinkSync(path.join(dir, file));
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
**Wire trong `register.ts`:** instantiate sink khi `telemetry.enabled !== false`, pass vào `NotificationRouter` options. Dispose trong `cleanupRuntime`.
|
|
520
|
-
|
|
521
|
-
**Tests:** `test/unit/notification-sink.test.ts` — 5 cases (write basic, daily rotation, retention prune, no rotation cùng ngày, telemetry disabled = no-op).
|
|
522
|
-
|
|
523
|
-
---
|
|
524
|
-
|
|
525
|
-
### Phase 8.4 — Wiring (0.75 dev-day)
|
|
526
|
-
|
|
527
|
-
**Update `src/extension/register.ts`:**
|
|
528
|
-
- Instantiate `NotificationRouter` cùng cấp với `runSnapshotCache`; check `loadConfig.telemetry?.enabled !== false` để decide có pass `JsonlSink` không.
|
|
529
|
-
- Pass router vào `subagentManager` callback (line 64-86) thay vì gọi trực tiếp `sendFollowUp`.
|
|
530
|
-
- Pass router vào `RenderScheduler` callback cho 8.2.C auto-recovery alert.
|
|
531
|
-
- Pass `getRunSnapshotCache` + `notificationRouter` vào `commands.ts` deps.
|
|
532
|
-
- Dispose router + sink trong `cleanupRuntime`.
|
|
533
|
-
|
|
534
|
-
**Update `src/extension/registration/commands.ts`:**
|
|
535
|
-
- Handle `selection.action === "mailbox-detail"` → mở `MailboxDetailOverlay`, dispatch action result, toast.
|
|
536
|
-
- Handle `selection.action === "health-recovery" | "health-kill-stale" | "health-diagnostic-export"` (Q4=c) — flow chi tiết 8.2.D.
|
|
537
|
-
- Pass `getRunSnapshotCache` cho overlay (cần để re-render sau action).
|
|
538
|
-
- Pass `confirmOverlayFactory` để các handler reuse `ConfirmOverlay`.
|
|
539
|
-
|
|
540
|
-
---
|
|
541
|
-
|
|
542
|
-
### Phase 8.5 — Tests + Validation (2 dev-day)
|
|
543
|
-
|
|
544
|
-
**Unit (mới ~52 cases):**
|
|
545
|
-
- `run-action-dispatcher.test.ts` (7)
|
|
546
|
-
- `confirm-overlay.test.ts` (4)
|
|
547
|
-
- `mailbox-detail-overlay.test.ts` (7 — bao gồm 3 cases ackAll Shift+X)
|
|
548
|
-
- `mailbox-compose-overlay.test.ts` (8)
|
|
549
|
-
- `mailbox-compose-preview.test.ts` (6) — Q1
|
|
550
|
-
- `agent-picker-overlay.test.ts` (4)
|
|
551
|
-
- `heartbeat-aggregator.test.ts` (6)
|
|
552
|
-
- `health-pane.test.ts` (6) — Q4 expanded
|
|
553
|
-
- `diagnostic-export.test.ts` (5) — Q4
|
|
554
|
-
- `notification-router.test.ts` (8 + 4 quiet-hours parser cases = 12) — Q3
|
|
555
|
-
- `notification-sink.test.ts` (5) — Q2
|
|
556
|
-
- `widget-notification-badge.test.ts` (5)
|
|
557
|
-
|
|
558
|
-
**Integration (mới ~6 cases):**
|
|
559
|
-
- `test/integration/mailbox-action-roundtrip.test.ts` — open dashboard → ack → snapshot invalidate → count giảm.
|
|
560
|
-
- `test/integration/mailbox-ackall-confirm.test.ts` — ackAll trigger ConfirmOverlay → confirm → loop ack 10 messages.
|
|
561
|
-
- `test/integration/notification-dedup.test.ts` — emit cùng event 5 lần trong 30s → 1 toast.
|
|
562
|
-
- `test/integration/notification-quiet-hours.test.ts` — set quietHours `"22:00-07:00"`, mock now=23:30 → 0 toast; mock now=12:00 → 1 toast.
|
|
563
|
-
- `test/integration/notification-sink-rotation.test.ts` — write 8 days → oldest file deleted on day 8.
|
|
564
|
-
- `test/integration/health-recovery-foreground.test.ts` — foreground run dead → R action → ConfirmOverlay → confirm → foreground-interrupt fired.
|
|
565
|
-
- `test/integration/health-diagnostic-export.test.ts` — D action → diagnostic file written với secrets redacted; emit lần 2 trong 1min → ConfirmOverlay overwrite.
|
|
566
|
-
|
|
567
|
-
**Acceptance trước commit:**
|
|
568
|
-
- `npm test` ≥ 351 unit (current 299 + 52), 35 integration (current 29 + 6); 0 fail. Verified current suite: 351 unit + 44 integration.
|
|
569
|
-
- `npm run typecheck` clean.
|
|
570
|
-
- Manual smoke coverage (8 scenarios — mục 6) captured as automated smoke in `test/integration/phase8-smoke.test.ts`.
|
|
571
|
-
|
|
572
|
-
## 3. Wave Organization (parallel-friendly) — Updated với Q1-Q6
|
|
573
|
-
|
|
574
|
-
```
|
|
575
|
-
Wave 1 (parallel, 2.5 days)
|
|
576
|
-
├─ 8.0 Foundation (dispatcher + keybinding-map + ConfirmOverlay)
|
|
577
|
-
├─ 8.3.A NotificationRouter primitive
|
|
578
|
-
└─ 8.2.A Heartbeat aggregator
|
|
579
|
-
|
|
580
|
-
Wave 2 (sequential, 5 days) — depends on Wave 1
|
|
581
|
-
├─ 8.1.A Mailbox detail overlay
|
|
582
|
-
├─ 8.1.B Ack action
|
|
583
|
-
├─ 8.1.C Nudge action
|
|
584
|
-
├─ 8.1.D Compose form (Q6 ESC discard + confirm-if-long)
|
|
585
|
-
├─ 8.1.E Compose preview pane (Q1)
|
|
586
|
-
└─ 8.1.F ackAll Shift+X destructive (Q5)
|
|
587
|
-
|
|
588
|
-
Wave 3 (parallel, 4 days) — depends on Wave 1
|
|
589
|
-
├─ 8.2.B Health pane
|
|
590
|
-
├─ 8.2.C Auto-recovery toast (throttled 5min D13)
|
|
591
|
-
├─ 8.2.D Health action handlers R/K/D (Q4) + diagnostic-export module
|
|
592
|
-
├─ 8.3.B Quiet-hours cross-day parser (Q3) + batching config
|
|
593
|
-
├─ 8.3.C Toast badge widget/powerbar
|
|
594
|
-
└─ 8.3.D JSONL sink + retention (Q2)
|
|
595
|
-
|
|
596
|
-
Wave 4 (sequential, 2.75 days)
|
|
597
|
-
├─ 8.4 Wire register.ts + commands.ts (router, sink, action handlers)
|
|
598
|
-
└─ 8.5 Tests + smoke validation (52 unit + 6 integration mới)
|
|
599
|
-
```
|
|
600
|
-
|
|
601
|
-
**Total estimate: 14-18 dev-days** (vs Phase 7 baseline 18 days). Effort tăng 3.35 day so với plan gốc 11-14d do Q1-Q6 chosen options enrich scope. Phase 8 vẫn smaller hơn Phase 7 vì chủ yếu UI overlay + event router, không động state machine.
|
|
602
|
-
|
|
603
|
-
## 4. Files Affected — Updated với Q1-Q6
|
|
604
|
-
|
|
605
|
-
### New (24 files)
|
|
606
|
-
| Path | Purpose | Est LOC |
|
|
607
|
-
|---|---|---|
|
|
608
|
-
| `src/ui/run-action-dispatcher.ts` | Wrapper team-tool calls (7 dispatchers) | ~140 |
|
|
609
|
-
| `src/ui/keybinding-map.ts` | Key registry (mailbox/health/notification scopes) | ~70 |
|
|
610
|
-
| `src/ui/overlays/confirm-overlay.ts` | **(Q5)** Reusable confirm primitive | ~80 |
|
|
611
|
-
| `src/ui/overlays/mailbox-detail-overlay.ts` | 2-col mailbox view + ackAll | ~250 |
|
|
612
|
-
| `src/ui/overlays/mailbox-compose-overlay.ts` | Compose form + ESC guard | ~210 |
|
|
613
|
-
| `src/ui/overlays/mailbox-compose-preview.ts` | **(Q1)** Markdown preview pane | ~120 |
|
|
614
|
-
| `src/ui/overlays/agent-picker-overlay.ts` | Agent selector | ~110 |
|
|
615
|
-
| `src/ui/heartbeat-aggregator.ts` | Heartbeat summary fn | ~70 |
|
|
616
|
-
| `src/ui/dashboard-panes/health-pane.ts` | Health pane renderer with action hints | ~80 |
|
|
617
|
-
| `src/extension/notification-router.ts` | Router + dedup + quiet-hours parser **(Q3)** | ~220 |
|
|
618
|
-
| `src/extension/notification-sink.ts` | **(Q2)** JSONL sink + retention rotation | ~100 |
|
|
619
|
-
| `src/runtime/diagnostic-export.ts` | **(Q4)** Diagnostic JSON exporter + secret redaction | ~140 |
|
|
620
|
-
| `test/unit/run-action-dispatcher.test.ts` | | ~140 |
|
|
621
|
-
| `test/unit/confirm-overlay.test.ts` | | ~80 |
|
|
622
|
-
| `test/unit/mailbox-detail-overlay.test.ts` | | ~180 |
|
|
623
|
-
| `test/unit/mailbox-compose-overlay.test.ts` | | ~180 |
|
|
624
|
-
| `test/unit/mailbox-compose-preview.test.ts` | **(Q1)** | ~120 |
|
|
625
|
-
| `test/unit/agent-picker-overlay.test.ts` | | ~80 |
|
|
626
|
-
| `test/unit/heartbeat-aggregator.test.ts` | | ~120 |
|
|
627
|
-
| `test/unit/health-pane.test.ts` | Q4 expanded scenarios | ~140 |
|
|
628
|
-
| `test/unit/diagnostic-export.test.ts` | **(Q4)** | ~110 |
|
|
629
|
-
| `test/unit/notification-router.test.ts` | + 4 cross-day parser cases | ~260 |
|
|
630
|
-
| `test/unit/notification-sink.test.ts` | **(Q2)** | ~100 |
|
|
631
|
-
| `test/unit/widget-notification-badge.test.ts` | | ~80 |
|
|
632
|
-
| `test/integration/mailbox-action-roundtrip.test.ts` | | ~120 |
|
|
633
|
-
| `test/integration/mailbox-ackall-confirm.test.ts` | **(Q5)** | ~100 |
|
|
634
|
-
| `test/integration/notification-dedup.test.ts` | | ~90 |
|
|
635
|
-
| `test/integration/notification-quiet-hours.test.ts` | **(Q3)** mock clock | ~110 |
|
|
636
|
-
| `test/integration/notification-sink-rotation.test.ts` | **(Q2)** | ~110 |
|
|
637
|
-
| `test/integration/health-recovery-foreground.test.ts` | **(Q4)** | ~120 |
|
|
638
|
-
| `test/integration/health-diagnostic-export.test.ts` | **(Q4)** | ~120 |
|
|
639
|
-
|
|
640
|
-
### Modified (10 files)
|
|
641
|
-
| Path | Change |
|
|
642
|
-
|---|---|
|
|
643
|
-
| `src/ui/run-dashboard.ts` | Refactor `handleInput` dùng keybinding-map; thêm pane "health" key `5`; help line; emit `health-recovery/health-kill-stale/health-diagnostic-export` actions (Q4) |
|
|
644
|
-
| `src/ui/dashboard-panes/mailbox-pane.ts` | Update help text gợi ý A/N/C/Enter/Shift+X (ackAll) |
|
|
645
|
-
| `src/ui/crew-widget.ts` | Render notification badge `🔔N` (fallback `[!N]` cho terminal không support emoji) |
|
|
646
|
-
| `src/ui/powerbar-publisher.ts` | Append badge cho `pi-crew-active` segment |
|
|
647
|
-
| `src/extension/register.ts` | Instantiate NotificationRouter + JsonlSink (gated bởi telemetry); wrap `sendFollowUp`; pass vào RenderScheduler + commands deps |
|
|
648
|
-
| `src/extension/registration/commands.ts` | Handle `mailbox-detail` + 3 health actions (Q4); mở overlay; reuse ConfirmOverlay (Q5) |
|
|
649
|
-
| `src/extension/team-tool/api.ts` | (no change) — dispatchers reuse existing operations |
|
|
650
|
-
| `src/schema/config-schema.ts` | Thêm `notifications` section + `sinkRetentionDays` (Q2) |
|
|
651
|
-
| `src/config/{config.ts,defaults.ts}` | Parse + default cho notifications (severityFilter, dedupWindowMs, batchWindowMs, quietHours, sinkRetentionDays) |
|
|
652
|
-
| `package.json` | Bump version `0.1.33` → `0.1.34` |
|
|
653
|
-
|
|
654
|
-
### Docs (chỉ update khi user yêu cầu, theo project rule)
|
|
655
|
-
- `docs/architecture.md` — bổ sung mục "Operator Actions", "Notification Router", "Diagnostic Export".
|
|
656
|
-
|
|
657
|
-
## 5. Risk Assessment — Updated với Q1-Q6
|
|
658
|
-
|
|
659
|
-
| Risk | Likelihood | Impact | Mitigation |
|
|
660
|
-
|---|---|---|---|
|
|
661
|
-
| Overlay hijack stdin của Pi UI | Med | High | Reuse pattern `LiveRunSidebar` (đã hoạt động); test với `pi-ui-compat.ts` shim |
|
|
662
|
-
| Keybinding conflict với Pi global hotkeys | Low | Med | Uppercase `A/N/C/P/H/X` (mailbox), `R/K/D` (health) — context-scoped; lowercase Pi defaults không đụng |
|
|
663
|
-
| Notification spam khi nhiều run concurrent | Med | Low-Med | Dedup window 30s default; severity filter excludes "info"; quiet-hours wrap (Q3) |
|
|
664
|
-
| Quiet-hours cross-day parser bug | Low | Med | Q3=b: 7 unit test cases bao gồm cross-midnight; mock clock pattern |
|
|
665
|
-
| MailboxDetailOverlay re-render slow | Low | Low | Reuse signature pattern từ `RunDashboard`; cache lines |
|
|
666
|
-
| Race khi ack trong khi snapshot đang refresh | Low | Med | Dispatch awaits then invalidate cache; render scheduler debounce 75ms |
|
|
667
|
-
| `sendFollowUp` swap break existing flow | Low | High | Wrap không thay; router default-on chỉ khi `notifications.enabled !== false`; fallback gọi `sendFollowUp` raw nếu router throws |
|
|
668
|
-
| Config schema breaking change | Low | High | New section `notifications` purely optional; missing → defaults |
|
|
669
|
-
| **(Q1)** Compose preview pane re-render bottleneck (debounce miss) | Low | Low | Debounce 100ms; cache last rendered tokens; tokenizer < 1ms cho 5KB body |
|
|
670
|
-
| **(Q1)** Markdown tokenizer edge case (nested code in list) | Med | Low | Reuse pattern parser nếu có; 6 unit test edge cases; preview "best-effort" |
|
|
671
|
-
| **(Q2)** Sink disk full / write fail | Low | Low | `appendFileSync` swallow errors qua `logInternalError`; sink failure không crash router |
|
|
672
|
-
| **(Q2)** Retention prune deletes file đang được tail | Low | Low | Chỉ prune `.jsonl` cũ hơn cutoff; daily rotation đảm bảo file hôm nay không bị touch |
|
|
673
|
-
| **(Q2)** PII trong notification body leak vào sink | Med | Med | Sink reuse secret redactor từ `diagnostic-export.ts` (Q4); router tag PII fields nếu cần |
|
|
674
|
-
| **(Q4)** `R` recovery accidentally interrupt healthy run | Low | High | ConfirmOverlay với `dangerLevel: "high"` + default cancel; foreground-only check; clear "tasks marked failed" warning |
|
|
675
|
-
| **(Q4)** `K` kill stale workers race với worker self-recovery | Low | Med | Mark dead heartbeats first → emit event → giải phóng claims; worker tự detect token mismatch sẽ exit |
|
|
676
|
-
| **(Q4)** Diagnostic export ghi đè artifact dir đang dùng | Low | Med | D14: check existing diag < 1min → ConfirmOverlay overwrite; timestamp suffix unique |
|
|
677
|
-
| **(Q4)** Diagnostic secret redaction miss key pattern mới | Med | High | Block list: `*token*`, `*key*`, `*password*`, `*secret*`, `*credential*`, `*auth*`; review qua test fixture với 20 key patterns |
|
|
678
|
-
| **(Q5)** ConfirmOverlay default `Y` accidentally confirms destructive | Low | High | Default action = "cancel"; first focus là `[N]`; ESC = cancel; UI hint underlined N |
|
|
679
|
-
| **(Q6)** ESC discard confirm fatigue (user complain phải confirm mỗi ESC) | Low | Low | Threshold 50 ký tự (configurable nếu user feedback); short body → discard ngay |
|
|
680
|
-
| **(Q6)** Body multi-line Ctrl+Enter not detected on Windows | Med | Low | Test với `pi-ui-compat.ts`; fallback `Alt+Enter` if Ctrl+Enter fails detection |
|
|
681
|
-
|
|
682
|
-
## 6. Testing Strategy — Updated với Q1-Q6
|
|
683
|
-
|
|
684
|
-
**Unit-level (Wave 1-3):**
|
|
685
|
-
- Mock `handleTeamTool` → assert dispatcher returns đúng `{ok, message}` cho 7 dispatchers.
|
|
686
|
-
- Render overlay với fixture snapshot → assert lines layout.
|
|
687
|
-
- Heartbeat aggregator: parameterized test với fixture timestamps (6 cases).
|
|
688
|
-
- Health pane: 6 cases bao phủ foreground/async/healthy/dead/stale variations (Q4).
|
|
689
|
-
- Notification router: mock clock (`globalThis.Date.now` override theo pattern Phase 7); 8 base cases + 4 cross-day parser (Q3).
|
|
690
|
-
- Sink: rotation, retention, telemetry-disabled no-op (Q2).
|
|
691
|
-
- Diagnostic export: secret redaction với 20-key fixture; JSON schema validate (Q4).
|
|
692
|
-
- Confirm overlay: 4 cases verify default-cancel safety (Q5).
|
|
693
|
-
- Compose preview: 6 cases markdown render (Q1).
|
|
694
|
-
|
|
695
|
-
**Integration (Wave 4) — 7 scenarios:**
|
|
696
|
-
- `mailbox-action-roundtrip.test.ts`: open dashboard → ack → snapshot invalidate → count giảm.
|
|
697
|
-
- `mailbox-ackall-confirm.test.ts` (Q5): Shift+X → ConfirmOverlay → confirm → loop ack 10 messages → all `acknowledged`.
|
|
698
|
-
- `notification-dedup.test.ts`: emit 5x cùng `crew.run.failed` trong 30s → `sendFollowUp` mock called once.
|
|
699
|
-
- `notification-quiet-hours.test.ts` (Q3): quiet `"22:00-07:00"` mock now=23:30 → 0 toast; mock now=12:00 → 1 toast.
|
|
700
|
-
- `notification-sink-rotation.test.ts` (Q2): write 8 ngày fake mtime → oldest deleted on day 8.
|
|
701
|
-
- `health-recovery-foreground.test.ts` (Q4): foreground run với 2 dead workers → R action → ConfirmOverlay confirm → `foreground-interrupt` API called → tasks marked failed.
|
|
702
|
-
- `health-diagnostic-export.test.ts` (Q4): D action → file written với 0 secrets in JSON; emit lần 2 trong 1min → ConfirmOverlay overwrite.
|
|
703
|
-
|
|
704
|
-
**Smoke manual (8 scenarios):**
|
|
705
|
-
1. Chạy `team run` 1 task foreground → mở `/team-dashboard` → key `3` mailbox → Enter → key `N` nudge → verify `events.jsonl` có `agent.nudged`.
|
|
706
|
-
2. Chạy 2 run, đợi xong → verify nhận 1-2 toast (dedup).
|
|
707
|
-
3. Set `notifications.quietHours = "00:00-23:59"` → verify 0 toast.
|
|
708
|
-
4. **(Q1)** Compose form, gõ markdown body với bold/list/code → key `P` preview → verify render đúng.
|
|
709
|
-
5. **(Q5)** ackAll trên run với 5 unread → ConfirmOverlay xuất hiện → N cancel → 0 message acked.
|
|
710
|
-
6. **(Q4)** Foreground run với worker stuck > 1min → key `5` health → key `R` → ConfirmOverlay → Y → tasks failed; key `D` → diagnostic file viết.
|
|
711
|
-
7. **(Q2)** Disable telemetry → run + emit notification → verify `<crewRoot>/state/notifications/` không tồn tại.
|
|
712
|
-
8. **(Q6)** Compose body 100 chars → ESC → ConfirmOverlay xuất hiện → N → vẫn editing.
|
|
713
|
-
|
|
714
|
-
**Performance budget:**
|
|
715
|
-
- Mailbox overlay first render < 50ms với 100 messages.
|
|
716
|
-
- Compose preview render < 30ms với 5KB markdown body (Q1).
|
|
717
|
-
- Notification router enqueue overhead < 1ms.
|
|
718
|
-
- Sink write < 5ms (single append) (Q2).
|
|
719
|
-
- Health pane render < 5ms cho 50 tasks.
|
|
720
|
-
- Diagnostic export complete < 200ms cho run với 50 tasks + 200 events (Q4).
|
|
721
|
-
|
|
722
|
-
## 7. Open Questions — RESOLVED (Path X chosen)
|
|
723
|
-
|
|
724
|
-
| Q | Câu hỏi | Lựa chọn | Implementation reference |
|
|
725
|
-
|---|---|---|---|
|
|
726
|
-
| **Q1** | Compose form có cần preview render trước khi submit? | **(b) Có preview pane** | 8.1.E `mailbox-compose-preview.ts`, key `P` toggle, render markdown read-only (bold/italic/code/list/heading), debounce 100ms. D15. +0.75d |
|
|
727
|
-
| **Q2** | Notification sink default ghi `<crewRoot>/state/notifications.jsonl`? | **(c) Sink khi `telemetry.enabled !== false`** | 8.3.D `notification-sink.ts`, JSONL `<crewRoot>/state/notifications/{YYYY-MM-DD}.jsonl`, rotate `sinkRetentionDays` default 7. D4. +0.5d |
|
|
728
|
-
| **Q3** | Quiet-hours cross-day wrap? | **(b) Wrap parser** | 8.3.B `parseHHMMRange` + `isInQuietHours` cross-day logic; 7 unit cases bao gồm `"22:00-07:00"`. D5. +0.25d |
|
|
729
|
-
| **Q4** | Health pane recovery action button inline? | **(c) Full action menu R/K/D** | 8.2.D `R` recovery (foreground-only), `K` kill stale workers, `D` diagnostic export với secret redaction; 3 confirm flows. D9, D14. +1.5d |
|
|
730
|
-
| **Q5** | Ack/nudge confirm cho destructive? | **(c) Confirm chỉ destructive (ackAll/recovery/diag-overwrite)** | 8.0 `ConfirmOverlay` reusable primitive; 8.1.F ackAll Shift+X with confirm. D12. +0.25d |
|
|
731
|
-
| **Q6** | Compose form persist draft khi ESC? | **(a) ESC discard + confirm-if-long** | 8.1.D ESC behavior: body ≤ 50 chars → discard; > 50 → ConfirmOverlay. Defer draft persistence Phase 9. D11. +0.1d |
|
|
732
|
-
|
|
733
|
-
**Tổng effort delta từ Q1-Q6: ~3.35 dev-day** → bump từ 11-14d → 14-18d.
|
|
734
|
-
|
|
735
|
-
**Mục tiêu Q1-Q6 đã đạt:** mọi quyết định scope-shaping đã chốt; team có thể start Wave 1 mà không bị blocked clarification giữa chừng.
|
|
736
|
-
|
|
737
|
-
## 8. Dependencies & Sequencing
|
|
738
|
-
|
|
739
|
-
```
|
|
740
|
-
Phase 7 (DONE) ─────► Phase 8.0 Foundation
|
|
741
|
-
│
|
|
742
|
-
┌──────────┼──────────┐
|
|
743
|
-
▼ ▼ ▼
|
|
744
|
-
8.1 Mailbox 8.2 Health 8.3 Notif
|
|
745
|
-
│ │ │
|
|
746
|
-
└──────────┼──────────┘
|
|
747
|
-
▼
|
|
748
|
-
8.4 Wiring
|
|
749
|
-
│
|
|
750
|
-
▼
|
|
751
|
-
8.5 Tests
|
|
752
|
-
```
|
|
753
|
-
|
|
754
|
-
**Hard prerequisites Phase 7:** ✅ `RunSnapshotCache`, `RenderScheduler`, dashboard panes — đã có.
|
|
755
|
-
|
|
756
|
-
## 9. Effort Summary — Updated với Q1-Q6
|
|
757
|
-
|
|
758
|
-
| Wave | Items | Dev-days | Parallelizable |
|
|
759
|
-
|---|---|---|---|
|
|
760
|
-
| 1 | 8.0 (Foundation + ConfirmOverlay Q5) + 8.3.A (Router) + 8.2.A (Heartbeat) | 2.5 | Yes (3 streams) |
|
|
761
|
-
| 2 | 8.1.A → B → C → D (Q6) → E (Q1) → F (Q5) | 5 | No (sequential UX, share overlay state) |
|
|
762
|
-
| 3 | 8.2.B + 8.2.C + 8.2.D (Q4 R/K/D) + 8.3.B (Q3) + 8.3.C + 8.3.D (Q2) | 4 | Yes (5 streams) |
|
|
763
|
-
| 4 | 8.4 (Wire) + 8.5 (Tests) | 2.75 | No |
|
|
764
|
-
| **Total** | **17 sub-phases** | **14-18** | — |
|
|
765
|
-
|
|
766
|
-
**So với plan gốc:** +3.35 dev-day, +5 sub-phases, +8 file mới, +27 unit case, +4 integration case.
|
|
767
|
-
|
|
768
|
-
## 10. Acceptance Checklist (Wave 4 exit criteria) — Updated
|
|
769
|
-
|
|
770
|
-
- [x] Tất cả checkbox 8.0 → 8.5 ở mục 0 (Implementation Status) tick `[x]`.
|
|
771
|
-
- [x] `npm test` ≥ **351 unit** (current 299 + 52 mới), ≥ **35 integration** (current 29 + 6 mới), 0 fail. Verified: 351 unit + 44 integration pass.
|
|
772
|
-
- [x] `npm run typecheck` clean.
|
|
773
|
-
- [x] Manual smoke **8 scenarios** pass (mục 6). Verified via automated smoke suite `test/integration/phase8-smoke.test.ts`.
|
|
774
|
-
- [x] Performance budget thỏa: mailbox overlay <50ms, compose preview <30ms, sink write <5ms, diagnostic export <200ms. Verified microbench: mailbox 6.39ms, preview 1.61ms, health 0.29ms, sink 2.12ms, diagnostic 4.83ms.
|
|
775
|
-
- [x] No regression: 299 unit + 29 integration cũ vẫn pass.
|
|
776
|
-
- [x] Config breaking? **No.** Schema additive (`notifications` section optional).
|
|
777
|
-
- [x] Bump `package.json` version `0.1.33` → `0.1.34`.
|
|
778
|
-
- [x] Q1-Q6 implementations match decisions table mục 7.
|
|
779
|
-
- [x] Secret redaction (Q4): test fixture with recursive key/value redaction pass; audit log avoids known token fixture.
|
|
780
|
-
|
|
781
|
-
## 11. Out of Scope (defer Phase 9+)
|
|
782
|
-
|
|
783
|
-
> Phase 9 plan đã được tạo riêng tại [`research-phase9-observability-reliability-plan.md`](./research-phase9-observability-reliability-plan.md).
|
|
784
|
-
|
|
785
|
-
- **Telemetry/Metrics backbone** (Counter/Gauge/Histogram + correlation ID + OTLP/Prometheus export) → **Phase 9 (Theme B)** per Path X plan.
|
|
786
|
-
- **Run reliability** — auto-retry executor + crash recovery + deadletter + heartbeat watcher → **Phase 9 (Theme C)**.
|
|
787
|
-
- Cross-run mailbox routing (operator-broadcast) — **Phase 10+**.
|
|
788
|
-
- Mailbox threading / reply chains — **Phase 10+**.
|
|
789
|
-
- **Compose draft persistence (Q6 b/c options)** — defer Phase 9 nếu user feedback than.
|
|
790
|
-
- Multi-host run aggregation — **Phase 10+**.
|
|
791
|
-
- Slack/Discord webhook sink (router supports it via custom sink, but no built-in adapter) — **Phase 10+**.
|
|
792
|
-
- Markdown preview với images/links rendered (Q1 D15 skip) — **Phase 10+**.
|
|
793
|
-
|
|
794
|
-
### Path X roadmap summary
|
|
795
|
-
|
|
796
|
-
| Phase | Theme | Effort | Plan file |
|
|
797
|
-
|---|---|---|---|
|
|
798
|
-
| 6 | `.crew/` migration + autonomous policy | ~12d | `refactor-tasks-phase6.md` (DONE) |
|
|
799
|
-
| 7 | UI Optimization | ~18d | `research-ui-optimization-plan.md` (DONE) |
|
|
800
|
-
| **8** | **Operator Experience (Theme A)** | **14-18d** | **THIS FILE — ✅ DONE (verified 351 unit + 44 integration pass, version 0.1.34)** |
|
|
801
|
-
| **9** | **Observability + Reliability (B+C)** | **19.5-22.5d** | `research-phase9-observability-reliability-plan.md` (post-review updated 2026-04-29) |
|
|
802
|
-
| 10+ | TBD: Perf baseline, distributed | — | Future |
|
|
803
|
-
|
|
804
|
-
---
|
|
805
|
-
|
|
806
|
-
## 12. Implementation Kickoff Checklist (Pre-Wave 1)
|
|
807
|
-
|
|
808
|
-
Trước khi bắt đầu Wave 1, verify:
|
|
809
|
-
|
|
810
|
-
- [x] Phase 7 đã commit (snapshot cache + render scheduler + 4 panes). Included in `phase-8-operator-experience` release commit.
|
|
811
|
-
- [x] `npm test` baseline pass (299 unit + 29 integration). Verified current suite: 351 unit + 44 integration pass.
|
|
812
|
-
- [x] `npm run typecheck` clean.
|
|
813
|
-
- [x] Q1-Q6 đã chốt (đã làm — table mục 7).
|
|
814
|
-
- [x] Branch mới `phase-8-operator-experience` từ main.
|
|
815
|
-
- [x] Read once: `src/extension/team-tool/api.ts` (đã có ack-message/send-message/nudge-agent operations — KHÔNG cần modify).
|
|
816
|
-
- [x] Read once: `src/ui/run-dashboard.ts:handleInput` để hiểu pattern key dispatch hiện tại.
|
|
817
|
-
- [x] Read once: `src/ui/live-run-sidebar.ts` để có template cho overlay implementation.
|
|
818
|
-
|
|
819
|
-
**Sẵn sàng triển khai Phase 8 Path X.**
|