pi-crew 0.9.16 → 0.9.17

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.
Files changed (432) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +19 -0
  3. package/dist/build-meta.json +21790 -0
  4. package/dist/index.mjs +83070 -0
  5. package/dist/index.mjs.map +7 -0
  6. package/docs/A +358 -0
  7. package/docs/M-A +357 -0
  8. package/docs/REVIEW-FINDINGS-2026-06 +357 -0
  9. package/docs/Y +357 -0
  10. package/docs/a +357 -0
  11. package/docs/aA +358 -0
  12. package/docs/archive/README.md +91 -0
  13. package/docs/patterns/command-agent-skill.md +1 -1
  14. package/docs/skills/REFERENCE.md +0 -17
  15. package/docs//303/242mmaAAA/303/242 +357 -0
  16. package/index.bundle.ts +25 -0
  17. package/index.ts +95 -4
  18. package/package.json +15 -4
  19. package/skills/iterative-audit/SKILL.md +0 -1
  20. package/skills/widget-rendering/SKILL.md +17 -0
  21. package/src/adapters/claude-adapter.ts +1 -3
  22. package/src/adapters/export-util.ts +16 -16
  23. package/src/adapters/index.ts +5 -5
  24. package/src/agents/agent-config.ts +20 -14
  25. package/src/agents/agent-serializer.ts +1 -1
  26. package/src/agents/discover-agents.ts +83 -39
  27. package/src/benchmark/benchmark-runner.ts +51 -51
  28. package/src/benchmark/feedback-loop.ts +1 -1
  29. package/src/config/config.ts +166 -590
  30. package/src/config/defaults.ts +28 -2
  31. package/src/config/drift-detector.ts +11 -14
  32. package/src/config/markers.ts +201 -203
  33. package/src/config/resilient-parser.ts +14 -6
  34. package/src/config/role-tools.ts +3 -3
  35. package/src/config/suggestions.ts +5 -12
  36. package/src/config/types.ts +9 -25
  37. package/src/errors.ts +151 -157
  38. package/src/extension/action-suggestions.ts +54 -9
  39. package/src/extension/async-notifier.ts +43 -13
  40. package/src/extension/autonomous-policy.ts +77 -37
  41. package/src/extension/command-completions.ts +9 -8
  42. package/src/extension/context-status-injection.ts +14 -12
  43. package/src/extension/crew-autocomplete.ts +8 -16
  44. package/src/extension/crew-cleanup.ts +2 -1
  45. package/src/extension/crew-shortcuts.ts +9 -6
  46. package/src/extension/cross-extension-rpc.ts +136 -42
  47. package/src/extension/help.ts +1 -1
  48. package/src/extension/import-index.ts +10 -5
  49. package/src/extension/knowledge-injection.ts +88 -15
  50. package/src/extension/management.ts +89 -349
  51. package/src/extension/message-renderers.ts +3 -4
  52. package/src/extension/notification-router.ts +22 -5
  53. package/src/extension/notification-sink.ts +6 -3
  54. package/src/extension/pi-api.ts +17 -8
  55. package/src/extension/plan-orchestrate.ts +8 -27
  56. package/src/extension/project-init.ts +21 -6
  57. package/src/extension/register.ts +329 -975
  58. package/src/extension/registration/artifact-cleanup.ts +9 -4
  59. package/src/extension/registration/command-utils.ts +5 -1
  60. package/src/extension/registration/commands.ts +806 -314
  61. package/src/extension/registration/compaction-guard.ts +15 -15
  62. package/src/extension/registration/lifecycle.ts +259 -0
  63. package/src/extension/registration/observability.ts +293 -0
  64. package/src/extension/registration/subagent-helpers.ts +29 -7
  65. package/src/extension/registration/subagent-tools.ts +253 -44
  66. package/src/extension/registration/team-tool.ts +19 -84
  67. package/src/extension/registration/ui.ts +168 -0
  68. package/src/extension/registration/viewers.ts +64 -24
  69. package/src/extension/result-watcher.ts +18 -7
  70. package/src/extension/run-bundle-schema.ts +21 -6
  71. package/src/extension/run-export.ts +15 -6
  72. package/src/extension/run-import.ts +64 -29
  73. package/src/extension/run-index.ts +28 -9
  74. package/src/extension/run-maintenance.ts +32 -12
  75. package/src/extension/session-summary.ts +1 -1
  76. package/src/extension/team-manager-command.ts +64 -7
  77. package/src/extension/team-onboard.ts +151 -150
  78. package/src/extension/team-recommendation.ts +121 -21
  79. package/src/extension/team-tool/anchor.ts +31 -64
  80. package/src/extension/team-tool/api.ts +929 -141
  81. package/src/extension/team-tool/auto-summarize.ts +14 -26
  82. package/src/extension/team-tool/cache-control.ts +1 -5
  83. package/src/extension/team-tool/cancel.ts +138 -38
  84. package/src/extension/team-tool/chain-dispatch.ts +5 -13
  85. package/src/extension/team-tool/chain-executor.ts +29 -51
  86. package/src/extension/team-tool/config-patch.ts +1 -1
  87. package/src/extension/team-tool/context.ts +40 -20
  88. package/src/extension/team-tool/destructive-gate.ts +10 -5
  89. package/src/extension/team-tool/doctor.ts +170 -51
  90. package/src/extension/team-tool/explain.ts +228 -214
  91. package/src/extension/team-tool/failure-patterns.ts +3 -9
  92. package/src/extension/team-tool/goal-wrap.ts +71 -29
  93. package/src/extension/team-tool/goal.ts +185 -35
  94. package/src/extension/team-tool/handle-schedule.ts +34 -15
  95. package/src/extension/team-tool/handle-settings.ts +94 -56
  96. package/src/extension/team-tool/health-monitor.ts +27 -108
  97. package/src/extension/team-tool/inspect.ts +42 -9
  98. package/src/extension/team-tool/intent-policy.ts +4 -1
  99. package/src/extension/team-tool/lifecycle-actions.ts +244 -48
  100. package/src/extension/team-tool/orchestrate.ts +7 -18
  101. package/src/extension/team-tool/parallel-dispatch.ts +51 -29
  102. package/src/extension/team-tool/plan.ts +29 -5
  103. package/src/extension/team-tool/respond.ts +49 -16
  104. package/src/extension/team-tool/run-not-found.ts +3 -8
  105. package/src/extension/team-tool/run.ts +80 -317
  106. package/src/extension/team-tool/status.ts +135 -43
  107. package/src/extension/team-tool/workflow-manage.ts +92 -25
  108. package/src/extension/team-tool-types.ts +8 -1
  109. package/src/extension/team-tool.ts +142 -551
  110. package/src/extension/validate-resources.ts +39 -8
  111. package/src/hooks/registry.ts +66 -17
  112. package/src/hooks/types.ts +1 -1
  113. package/src/i18n.ts +25 -11
  114. package/src/observability/correlation.ts +17 -3
  115. package/src/observability/event-bus.ts +51 -56
  116. package/src/observability/event-to-metric.ts +117 -15
  117. package/src/observability/exporters/otlp-exporter.ts +56 -17
  118. package/src/observability/exporters/prometheus-exporter.ts +1 -1
  119. package/src/observability/metric-registry.ts +16 -4
  120. package/src/observability/metric-retention.ts +8 -2
  121. package/src/observability/metric-sink.ts +11 -3
  122. package/src/observability/metrics-primitives.ts +42 -9
  123. package/src/plugins/plugin-define.ts +2 -2
  124. package/src/plugins/plugin-registry.ts +25 -25
  125. package/src/plugins/plugins/index.ts +1 -1
  126. package/src/plugins/plugins/nextjs.ts +16 -16
  127. package/src/plugins/plugins/vite.ts +7 -11
  128. package/src/plugins/plugins/vitest.ts +6 -11
  129. package/src/runtime/adaptive-plan.ts +225 -40
  130. package/src/runtime/agent-control.ts +58 -15
  131. package/src/runtime/agent-memory.ts +15 -9
  132. package/src/runtime/agent-observability.ts +10 -2
  133. package/src/runtime/anchor-manager.ts +27 -25
  134. package/src/runtime/async-marker.ts +7 -1
  135. package/src/runtime/async-runner.ts +27 -23
  136. package/src/runtime/auto-summarize.ts +7 -13
  137. package/src/runtime/background-runner.ts +189 -251
  138. package/src/runtime/batch-barrier.ts +18 -16
  139. package/src/runtime/cancellation-token.ts +16 -6
  140. package/src/runtime/cancellation.ts +46 -8
  141. package/src/runtime/capability-inventory.ts +6 -4
  142. package/src/runtime/chain-parser.ts +44 -11
  143. package/src/runtime/chain-runner.ts +63 -69
  144. package/src/runtime/checkpoint.ts +12 -56
  145. package/src/runtime/child-pi.ts +343 -78
  146. package/src/runtime/coalesce-tasks.ts +268 -0
  147. package/src/runtime/code-summary.ts +71 -26
  148. package/src/runtime/compact-stages/index.ts +15 -5
  149. package/src/runtime/compact-stages/tail-capture-stage.ts +7 -2
  150. package/src/runtime/compact-stages/truncation-stage.ts +4 -1
  151. package/src/runtime/compaction-summary.ts +17 -10
  152. package/src/runtime/completion-guard.ts +33 -16
  153. package/src/runtime/crash-classification.ts +28 -7
  154. package/src/runtime/crash-recovery.ts +177 -37
  155. package/src/runtime/crew-agent-records.ts +145 -47
  156. package/src/runtime/crew-hooks.ts +9 -24
  157. package/src/runtime/cross-extension-rpc.ts +25 -23
  158. package/src/runtime/custom-tools/irc-tool.ts +43 -15
  159. package/src/runtime/custom-tools/submit-result-tool.ts +16 -5
  160. package/src/runtime/delivery-coordinator.ts +34 -7
  161. package/src/runtime/delta-conflict.ts +9 -21
  162. package/src/runtime/deterministic-ast.ts +1 -1
  163. package/src/runtime/diagnostic-export.ts +53 -20
  164. package/src/runtime/direct-run.ts +12 -2
  165. package/src/runtime/dwf-state-store.ts +1 -1
  166. package/src/runtime/dynamic-workflow-context.ts +186 -58
  167. package/src/runtime/dynamic-workflow-runner.ts +42 -20
  168. package/src/runtime/effectiveness.ts +16 -8
  169. package/src/runtime/errors/crew-errors.ts +7 -11
  170. package/src/runtime/event-stream-bridge.ts +9 -3
  171. package/src/runtime/foreground-control.ts +54 -14
  172. package/src/runtime/foreground-watchdog.ts +9 -6
  173. package/src/runtime/goal-achievement.ts +27 -10
  174. package/src/runtime/goal-evaluator.ts +54 -19
  175. package/src/runtime/goal-loop-runner.ts +280 -99
  176. package/src/runtime/goal-state-store.ts +27 -17
  177. package/src/runtime/green-contract.ts +11 -2
  178. package/src/runtime/group-join.ts +64 -31
  179. package/src/runtime/handoff-manager.ts +37 -42
  180. package/src/runtime/heartbeat-gradient.ts +10 -2
  181. package/src/runtime/heartbeat-watcher.ts +32 -6
  182. package/src/runtime/hidden-handoff.ts +12 -30
  183. package/src/runtime/important-line-classifier.ts +12 -2
  184. package/src/runtime/iteration-hooks.ts +25 -15
  185. package/src/runtime/live-agent-control.ts +55 -7
  186. package/src/runtime/live-agent-manager.ts +130 -27
  187. package/src/runtime/live-control-realtime.ts +23 -3
  188. package/src/runtime/live-irc.ts +4 -1
  189. package/src/runtime/live-session-health.ts +12 -4
  190. package/src/runtime/live-session-runtime.ts +379 -117
  191. package/src/runtime/manifest-cache.ts +28 -12
  192. package/src/runtime/mcp-proxy.ts +8 -16
  193. package/src/runtime/metric-parser.ts +1 -5
  194. package/src/runtime/model-fallback.ts +55 -18
  195. package/src/runtime/model-resolver.ts +2 -6
  196. package/src/runtime/model-scope.ts +19 -3
  197. package/src/runtime/notebook-helpers.ts +60 -62
  198. package/src/runtime/orphan-worker-registry.ts +37 -26
  199. package/src/runtime/output-validator.ts +18 -3
  200. package/src/runtime/overflow-recovery.ts +5 -4
  201. package/src/runtime/parallel-research.ts +29 -5
  202. package/src/runtime/parallel-utils.ts +9 -11
  203. package/src/runtime/path-overlap.ts +150 -0
  204. package/src/runtime/peer-dep.ts +12 -17
  205. package/src/runtime/per-write-validator.ts +1 -3
  206. package/src/runtime/phase-tracker.ts +342 -330
  207. package/src/runtime/pi-args.ts +26 -8
  208. package/src/runtime/pi-json-output.ts +1 -1
  209. package/src/runtime/pi-spawn.ts +30 -19
  210. package/src/runtime/pipeline-runner.ts +56 -55
  211. package/src/runtime/plan-templates.ts +5 -6
  212. package/src/runtime/policy-engine.ts +43 -7
  213. package/src/runtime/post-checks.ts +12 -4
  214. package/src/runtime/post-exit-stdio-guard.ts +2 -2
  215. package/src/runtime/process-lifecycle.ts +20 -10
  216. package/src/runtime/process-status.ts +16 -5
  217. package/src/runtime/progress-event-coalescer.ts +2 -1
  218. package/src/runtime/progress-tracker.ts +103 -103
  219. package/src/runtime/prose-compressor.ts +9 -11
  220. package/src/runtime/recovery-recipes.ts +118 -24
  221. package/src/runtime/replace.ts +25 -10
  222. package/src/runtime/resilient-edit.ts +10 -25
  223. package/src/runtime/result-extractor.ts +1 -2
  224. package/src/runtime/retry-executor.ts +20 -4
  225. package/src/runtime/retry-runner.ts +26 -50
  226. package/src/runtime/role-permission.ts +6 -1
  227. package/src/runtime/run-coalesced-task-group.ts +256 -0
  228. package/src/runtime/run-drift.ts +14 -15
  229. package/src/runtime/run-tracker.ts +6 -28
  230. package/src/runtime/runtime-policy.ts +1 -6
  231. package/src/runtime/runtime-resolver.ts +80 -15
  232. package/src/runtime/scheduler.ts +47 -18
  233. package/src/runtime/semaphore.ts +5 -7
  234. package/src/runtime/sensitive-paths.ts +2 -1
  235. package/src/runtime/session-usage.ts +1 -1
  236. package/src/runtime/settings-store.ts +16 -12
  237. package/src/runtime/sidechain-output.ts +6 -2
  238. package/src/runtime/single-agent-compose.ts +1 -4
  239. package/src/runtime/skill-effectiveness.ts +29 -97
  240. package/src/runtime/skill-instructions.ts +40 -83
  241. package/src/runtime/stale-reconciler.ts +73 -197
  242. package/src/runtime/stream-preview.ts +1 -1
  243. package/src/runtime/streaming-output.ts +1 -1
  244. package/src/runtime/subagent-manager.ts +44 -169
  245. package/src/runtime/subprocess-tool-registry.ts +4 -1
  246. package/src/runtime/supervisor-contact.ts +10 -5
  247. package/src/runtime/task-display.ts +19 -5
  248. package/src/runtime/task-graph-scheduler.ts +95 -21
  249. package/src/runtime/task-graph.ts +5 -11
  250. package/src/runtime/task-health.ts +54 -47
  251. package/src/runtime/task-id.ts +11 -18
  252. package/src/runtime/task-output-context.ts +265 -81
  253. package/src/runtime/task-packet.ts +12 -20
  254. package/src/runtime/task-quality.ts +6 -14
  255. package/src/runtime/task-runner/context-retrieval.ts +4 -13
  256. package/src/runtime/task-runner/live-executor.ts +114 -36
  257. package/src/runtime/task-runner/output-splitter.ts +152 -0
  258. package/src/runtime/task-runner/progress.ts +50 -12
  259. package/src/runtime/task-runner/prompt-builder.ts +31 -7
  260. package/src/runtime/task-runner/prompt-pipeline.ts +31 -7
  261. package/src/runtime/task-runner/result-utils.ts +3 -1
  262. package/src/runtime/task-runner/retrieval-orchestrator.ts +310 -0
  263. package/src/runtime/task-runner/run-projection.ts +27 -8
  264. package/src/runtime/task-runner/state-helpers.ts +29 -8
  265. package/src/runtime/task-runner/tail-read.ts +2 -6
  266. package/src/runtime/task-runner.ts +180 -326
  267. package/src/runtime/team-runner-artifacts.ts +13 -0
  268. package/src/runtime/team-runner.ts +456 -974
  269. package/src/runtime/tool-output-pruner.ts +6 -9
  270. package/src/runtime/tool-progress.ts +11 -21
  271. package/src/runtime/verification-gates.ts +33 -24
  272. package/src/runtime/verification-integrity.ts +1 -4
  273. package/src/runtime/verification-worktree.ts +62 -12
  274. package/src/runtime/worker-heartbeat.ts +5 -1
  275. package/src/runtime/worker-startup.ts +29 -5
  276. package/src/runtime/workflow-state.ts +17 -9
  277. package/src/runtime/workspace-lock.ts +30 -35
  278. package/src/runtime/workspace-tree.ts +20 -32
  279. package/src/runtime/yield-handler.ts +43 -9
  280. package/src/runtime/zombie-scanner.ts +5 -4
  281. package/src/schema/config-schema.ts +266 -172
  282. package/src/schema/team-tool-schema.ts +29 -74
  283. package/src/schema/validation-types.ts +25 -17
  284. package/src/skills/discover-skills.ts +35 -10
  285. package/src/skills/skill-templates.ts +109 -27
  286. package/src/skills/validate.ts +26 -8
  287. package/src/state/active-run-registry.ts +87 -28
  288. package/src/state/artifact-store.ts +9 -5
  289. package/src/state/atomic-write-v2.ts +85 -63
  290. package/src/state/atomic-write.ts +105 -26
  291. package/src/state/blob-store.ts +47 -20
  292. package/src/state/contracts.ts +20 -5
  293. package/src/state/crew-init.ts +5 -22
  294. package/src/state/decision-ledger.ts +19 -73
  295. package/src/state/event-log-rotation.ts +41 -15
  296. package/src/state/event-log.ts +168 -53
  297. package/src/state/event-reconstructor.ts +11 -1
  298. package/src/state/gitignore-manager.ts +2 -8
  299. package/src/state/health-store.ts +57 -57
  300. package/src/state/hook-instinct-bridge.ts +1 -1
  301. package/src/state/hook-integrations.ts +1 -1
  302. package/src/state/instinct-store.ts +17 -5
  303. package/src/state/jsonl-writer.ts +1 -1
  304. package/src/state/locks.ts +23 -9
  305. package/src/state/mailbox.ts +151 -28
  306. package/src/state/observation-store.ts +20 -14
  307. package/src/state/run-cache.ts +142 -135
  308. package/src/state/run-graph.ts +6 -15
  309. package/src/state/run-metrics.ts +5 -18
  310. package/src/state/schedule.ts +15 -9
  311. package/src/state/state-store.ts +129 -45
  312. package/src/state/task-claims.ts +12 -2
  313. package/src/state/tiered-eval.ts +52 -43
  314. package/src/state/types-eval.ts +2 -2
  315. package/src/state/types.ts +24 -20
  316. package/src/state/usage.ts +20 -4
  317. package/src/state/worker-atomic-writer.ts +6 -3
  318. package/src/subagents/index.ts +2 -2
  319. package/src/teams/discover-teams.ts +21 -6
  320. package/src/tools/safe-bash-extension.ts +5 -6
  321. package/src/tools/safe-bash.ts +10 -11
  322. package/src/types/new-api-types.ts +6 -10
  323. package/src/ui/agent-management-overlay.ts +52 -40
  324. package/src/ui/card-colors.ts +13 -4
  325. package/src/ui/crew-footer.ts +8 -7
  326. package/src/ui/crew-select-list.ts +1 -1
  327. package/src/ui/dashboard-panes/agents-pane.ts +41 -25
  328. package/src/ui/dashboard-panes/cancellation-pane.ts +1 -1
  329. package/src/ui/dashboard-panes/capability-pane.ts +32 -15
  330. package/src/ui/dashboard-panes/health-pane.ts +2 -1
  331. package/src/ui/dashboard-panes/metrics-pane.ts +4 -1
  332. package/src/ui/dashboard-panes/progress-pane.ts +12 -9
  333. package/src/ui/heartbeat-aggregator.ts +15 -4
  334. package/src/ui/keybinding-map.ts +40 -7
  335. package/src/ui/live-conversation-overlay.ts +36 -20
  336. package/src/ui/live-duration.ts +1 -4
  337. package/src/ui/live-run-sidebar.ts +88 -25
  338. package/src/ui/loaders.ts +2 -8
  339. package/src/ui/mascot.ts +20 -36
  340. package/src/ui/overlays/agent-picker-overlay.ts +11 -3
  341. package/src/ui/overlays/confirm-overlay.ts +4 -2
  342. package/src/ui/overlays/help-overlay.ts +25 -14
  343. package/src/ui/overlays/mailbox-compose-overlay.ts +48 -11
  344. package/src/ui/overlays/mailbox-compose-preview.ts +20 -5
  345. package/src/ui/overlays/mailbox-detail-overlay.ts +27 -6
  346. package/src/ui/pi-ui-compat.ts +7 -7
  347. package/src/ui/powerbar-publisher.ts +120 -42
  348. package/src/ui/render-diff.ts +10 -3
  349. package/src/ui/render-scheduler.ts +12 -4
  350. package/src/ui/run-action-dispatcher.ts +81 -18
  351. package/src/ui/run-dashboard.ts +172 -76
  352. package/src/ui/run-event-bus.ts +59 -37
  353. package/src/ui/run-snapshot-cache.ts +276 -86
  354. package/src/ui/settings-overlay.ts +361 -74
  355. package/src/ui/status-colors.ts +12 -1
  356. package/src/ui/syntax-highlight.ts +1 -1
  357. package/src/ui/theme-adapter.ts +16 -14
  358. package/src/ui/theme-discovery.ts +13 -2
  359. package/src/ui/tool-progress-formatter.ts +7 -7
  360. package/src/ui/tool-render.ts +128 -56
  361. package/src/ui/tool-renderers/brief-mode.ts +45 -27
  362. package/src/ui/tool-renderers/index.ts +109 -43
  363. package/src/ui/transcript-cache.ts +32 -6
  364. package/src/ui/transcript-entries.ts +25 -23
  365. package/src/ui/transcript-viewer.ts +78 -29
  366. package/src/ui/widget/index.ts +99 -40
  367. package/src/ui/widget/widget-formatters.ts +13 -6
  368. package/src/ui/widget/widget-model.ts +19 -8
  369. package/src/ui/widget/widget-renderer.ts +23 -13
  370. package/src/ui/widget/widget-types.ts +1 -1
  371. package/src/utils/bm25-search.ts +199 -199
  372. package/src/utils/conflict-detect.ts +22 -21
  373. package/src/utils/env-filter.ts +14 -7
  374. package/src/utils/file-coalescer.ts +5 -1
  375. package/src/utils/fingerprint.ts +3 -6
  376. package/src/utils/frontmatter.ts +3 -1
  377. package/src/utils/fs-watch.ts +2 -6
  378. package/src/utils/gh-protocol.ts +119 -44
  379. package/src/utils/git.ts +13 -15
  380. package/src/utils/guards.ts +2 -5
  381. package/src/utils/ids.ts +9 -2
  382. package/src/utils/incremental-reader.ts +14 -3
  383. package/src/utils/internal-error.ts +2 -1
  384. package/src/utils/names.ts +12 -3
  385. package/src/utils/paths.ts +49 -5
  386. package/src/utils/project-detector.ts +2 -2
  387. package/src/utils/redaction.ts +29 -19
  388. package/src/utils/resolve-shell.ts +9 -7
  389. package/src/utils/run-watcher-registry.ts +19 -31
  390. package/src/utils/safe-paths.ts +45 -24
  391. package/src/utils/scan-cache.ts +9 -2
  392. package/src/utils/session-utils.ts +2 -4
  393. package/src/utils/sleep.ts +12 -5
  394. package/src/utils/sse-parser.ts +5 -17
  395. package/src/utils/visual.ts +52 -32
  396. package/src/workflows/discover-workflows.ts +41 -109
  397. package/src/workflows/intermediate-store.ts +5 -21
  398. package/src/workflows/preflight-validator.ts +6 -33
  399. package/src/workflows/topology-analyzer.ts +5 -21
  400. package/src/workflows/workflow-config.ts +7 -6
  401. package/src/worktree/branch-freshness.ts +66 -8
  402. package/src/worktree/cleanup.ts +130 -20
  403. package/src/worktree/worktree-manager.ts +212 -69
  404. package/skills/artifact-analysis-loop/SKILL.md +0 -303
  405. package/skills/detection-pipeline-design/SKILL.md +0 -286
  406. package/skills/hunting-investigation-loop/SKILL.md +0 -402
  407. package/skills/incident-playbook-construction/SKILL.md +0 -384
  408. package/skills/security-review/SKILL.md +0 -561
  409. package/skills/threat-hypothesis-framework/SKILL.md +0 -176
  410. package/skills/ui-render-performance/SKILL.md +0 -58
  411. /package/docs/{followup-review-round3-2026-05-12.md → archive/followup-review-round3-2026-05-12.md} +0 -0
  412. /package/docs/{followup-review-round4-2026-05-13.md → archive/followup-review-round4-2026-05-13.md} +0 -0
  413. /package/docs/{pi-crew-bugs.md → archive/pi-crew-bugs.md} +0 -0
  414. /package/docs/{pi-crew-test-final.md → archive/pi-crew-test-final.md} +0 -0
  415. /package/docs/{pi-crew-test-results.md → archive/pi-crew-test-results.md} +0 -0
  416. /package/docs/{pi-crew-test-round2.md → archive/pi-crew-test-round2.md} +0 -0
  417. /package/docs/{pi-crew-test-round4.md → archive/pi-crew-test-round4.md} +0 -0
  418. /package/docs/{pi-crew-test-round5.md → archive/pi-crew-test-round5.md} +0 -0
  419. /package/docs/{pi-crew-test-round6.md → archive/pi-crew-test-round6.md} +0 -0
  420. /package/docs/{pi-crew-v0.5.10-audit-fix-plan.md → archive/pi-crew-v0.5.10-audit-fix-plan.md} +0 -0
  421. /package/docs/{pi-crew-v0.5.11-audit-fix-plan.md → archive/pi-crew-v0.5.11-audit-fix-plan.md} +0 -0
  422. /package/docs/{pi-crew-v0.5.12-audit-fix-plan.md → archive/pi-crew-v0.5.12-audit-fix-plan.md} +0 -0
  423. /package/docs/{pi-crew-v0.5.13-audit-fix-plan.md → archive/pi-crew-v0.5.13-audit-fix-plan.md} +0 -0
  424. /package/docs/{pi-crew-v0.5.14-audit-fix-plan.md → archive/pi-crew-v0.5.14-audit-fix-plan.md} +0 -0
  425. /package/docs/{pi-crew-v0.5.16-audit-fix-plan.md → archive/pi-crew-v0.5.16-audit-fix-plan.md} +0 -0
  426. /package/docs/{pi-crew-v0.5.17-audit-fix-plan.md → archive/pi-crew-v0.5.17-audit-fix-plan.md} +0 -0
  427. /package/docs/{pi-crew-v0.5.5-audit-fix-plan.md → archive/pi-crew-v0.5.5-audit-fix-plan.md} +0 -0
  428. /package/docs/{pi-crew-v0.5.9-audit-fix-plan.md → archive/pi-crew-v0.5.9-audit-fix-plan.md} +0 -0
  429. /package/docs/{pi-mono-opportunities.md → archive/pi-mono-opportunities.md} +0 -0
  430. /package/docs/{pi-mono-review.md → archive/pi-mono-review.md} +0 -0
  431. /package/docs/{pi-subagent4-comparison.md → archive/pi-subagent4-comparison.md} +0 -0
  432. /package/docs/{pi-subagents3-deep-analysis.md → archive/pi-subagents3-deep-analysis.md} +0 -0
@@ -2,9 +2,13 @@ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@e
2
2
  import { loadConfig } from "../../config/config.ts";
3
3
  // Lazy-loaded: team-tool.ts pulls in entire runtime chain (1.4s+).
4
4
  import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
5
+
5
6
  let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
6
7
  let _handleTeamToolPromise: Promise<typeof HandleTeamToolFn> | undefined;
7
- async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
8
+ async function handleTeamTool(
9
+ params: Parameters<typeof HandleTeamToolFn>[0],
10
+ ctx: Parameters<typeof HandleTeamToolFn>[1],
11
+ ): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
8
12
  if (!_cachedHandleTeamTool) {
9
13
  if (!_handleTeamToolPromise) {
10
14
  _handleTeamToolPromise = import("../team-tool.ts").then((mod) => {
@@ -17,41 +21,55 @@ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ct
17
21
  }
18
22
  return _cachedHandleTeamTool(params, ctx);
19
23
  }
20
- import { withSessionId } from "../team-tool/context.ts";
21
- import { piTeamsHelp } from "../help.ts";
22
- import { handleTeamManagerCommand } from "../team-manager-command.ts";
23
- import { loadRunManifestById } from "../../state/state-store.ts";
24
- import type { TeamRunManifest } from "../../state/types.ts";
25
- import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
26
- import { suggestRunIds, suggestTaskIds, suggestTeams } from "../command-completions.ts";
24
+
25
+ import * as fs from "node:fs";
27
26
  import * as path from "node:path";
28
- // Heavy UI modules — lazy-loaded because they're only used in /crew commands.
29
- // RunDashboard (288ms), DurableTextViewer (658ms), Overlays are unnecessary at Pi startup.
30
- import type { RunDashboard as RunDashboardType, RunDashboardSelection } from "../../ui/run-dashboard.ts";
31
- import type { DurableTextViewer as DurableTextViewerType } from "../../ui/transcript-viewer.ts";
32
- import type { ConfirmOverlay as ConfirmOverlayType, ConfirmOptions } from "../../ui/overlays/confirm-overlay.ts";
33
- import type { MailboxDetailOverlay as MailboxDetailOverlayType, MailboxAction } from "../../ui/overlays/mailbox-detail-overlay.ts";
34
- import type { MailboxComposeOverlay as MailboxComposeOverlayType, MailboxComposeResult } from "../../ui/overlays/mailbox-compose-overlay.ts";
35
- import type { AgentPickerOverlay as AgentPickerOverlayType } from "../../ui/overlays/agent-picker-overlay.ts";
36
- import type { AnimatedMascot as AnimatedMascotType } from "../../ui/mascot.ts";
37
- // Eagerly import lightweight modules
38
- import { dispatchDiagnosticExport, dispatchHealthRecovery, dispatchKillStaleWorkers, dispatchMailboxAck, dispatchMailboxAckAll, dispatchMailboxCompose, dispatchMailboxNudge } from "../../ui/run-action-dispatcher.ts";
39
27
  import { DEFAULT_UI } from "../../config/defaults.ts";
28
+ import type { MetricRegistry } from "../../observability/metric-registry.ts";
29
+ import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
40
30
  import { listRecentDiagnostic } from "../../runtime/diagnostic-export.ts";
41
- import { commandText, notifyCommandResult, parseRunArgs, parseScalar, pushUnset, setNestedConfig } from "./command-utils.ts";
42
- import { openTranscriptViewer, selectAgentTask, openLiveConversation } from "./viewers.ts";
43
31
  import { getBuiltinTemplates, instantiateTemplate, listTemplates } from "../../skills/skill-templates.ts";
44
- import * as fs from "node:fs";
45
- import { printTimings, time } from "../../utils/timings.ts";
32
+ import { loadRunManifestById } from "../../state/state-store.ts";
33
+ import type { TeamRunManifest } from "../../state/types.ts";
34
+ import type { AnimatedMascot as AnimatedMascotType } from "../../ui/mascot.ts";
35
+ import type { AgentPickerOverlay as AgentPickerOverlayType } from "../../ui/overlays/agent-picker-overlay.ts";
36
+ import type { ConfirmOptions, ConfirmOverlay as ConfirmOverlayType } from "../../ui/overlays/confirm-overlay.ts";
37
+ import type {
38
+ MailboxComposeOverlay as MailboxComposeOverlayType,
39
+ MailboxComposeResult,
40
+ } from "../../ui/overlays/mailbox-compose-overlay.ts";
41
+ import type { MailboxAction, MailboxDetailOverlay as MailboxDetailOverlayType } from "../../ui/overlays/mailbox-detail-overlay.ts";
46
42
  import { requestRenderTarget } from "../../ui/pi-ui-compat.ts";
43
+ // Eagerly import lightweight modules
44
+ import {
45
+ dispatchDiagnosticExport,
46
+ dispatchHealthRecovery,
47
+ dispatchKillStaleWorkers,
48
+ dispatchMailboxAck,
49
+ dispatchMailboxAckAll,
50
+ dispatchMailboxCompose,
51
+ dispatchMailboxNudge,
52
+ } from "../../ui/run-action-dispatcher.ts";
53
+ // Heavy UI modules — lazy-loaded because they're only used in /crew commands.
54
+ // RunDashboard (288ms), DurableTextViewer (658ms), Overlays are unnecessary at Pi startup.
55
+ import type { RunDashboardSelection, RunDashboard as RunDashboardType } from "../../ui/run-dashboard.ts";
47
56
  import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
48
- import type { MetricRegistry } from "../../observability/metric-registry.ts";
57
+ import type { DurableTextViewer as DurableTextViewerType } from "../../ui/transcript-viewer.ts";
58
+ import { printTimings, time } from "../../utils/timings.ts";
59
+ import { suggestRunIds, suggestTaskIds, suggestTeams } from "../command-completions.ts";
60
+ import { piTeamsHelp } from "../help.ts";
61
+ import { handleTeamManagerCommand } from "../team-manager-command.ts";
62
+ import { withSessionId } from "../team-tool/context.ts";
63
+ import { commandText, notifyCommandResult, parseRunArgs, parseScalar, pushUnset, setNestedConfig } from "./command-utils.ts";
64
+ import { openLiveConversation, openTranscriptViewer, selectAgentTask } from "./viewers.ts";
49
65
 
50
66
  export interface RegisterTeamCommandsDeps {
51
67
  startForegroundRun: (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
52
68
  abortForegroundRun: (runId: string) => boolean;
53
69
  openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
54
- getManifestCache: (cwd: string) => { list(max?: number): TeamRunManifest[] };
70
+ getManifestCache: (cwd: string) => {
71
+ list(max?: number): TeamRunManifest[];
72
+ };
55
73
  getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
56
74
  getMetricRegistry?: () => MetricRegistry | undefined;
57
75
  dismissNotifications?: () => void;
@@ -59,15 +77,17 @@ export interface RegisterTeamCommandsDeps {
59
77
 
60
78
  // Lazy-loaded UI module cache — avoids importing 900ms+ of UI at Pi startup.
61
79
  // These modules are only needed when user invokes /crew commands.
62
- let _uiCache: {
63
- RunDashboard: typeof RunDashboardType;
64
- DurableTextViewer: typeof DurableTextViewerType;
65
- ConfirmOverlay: typeof ConfirmOverlayType;
66
- MailboxDetailOverlay: typeof MailboxDetailOverlayType;
67
- MailboxComposeOverlay: typeof MailboxComposeOverlayType;
68
- AgentPickerOverlay: typeof AgentPickerOverlayType;
69
- AnimatedMascot: typeof AnimatedMascotType;
70
- } | undefined;
80
+ let _uiCache:
81
+ | {
82
+ RunDashboard: typeof RunDashboardType;
83
+ DurableTextViewer: typeof DurableTextViewerType;
84
+ ConfirmOverlay: typeof ConfirmOverlayType;
85
+ MailboxDetailOverlay: typeof MailboxDetailOverlayType;
86
+ MailboxComposeOverlay: typeof MailboxComposeOverlayType;
87
+ AgentPickerOverlay: typeof AgentPickerOverlayType;
88
+ AnimatedMascot: typeof AnimatedMascotType;
89
+ }
90
+ | undefined;
71
91
  let _uiCachePromise: Promise<NonNullable<typeof _uiCache>> | undefined;
72
92
  async function ui(): Promise<NonNullable<typeof _uiCache>> {
73
93
  if (!_uiCache) {
@@ -103,13 +123,26 @@ async function ui(): Promise<NonNullable<typeof _uiCache>> {
103
123
  async function openConfirm(ctx: ExtensionCommandContext, options: ConfirmOptions): Promise<boolean> {
104
124
  if (!ctx.hasUI) return false;
105
125
  const { ConfirmOverlay } = await ui();
106
- return await ctx.ui.custom<boolean>((_tui, theme, _keybindings, done) => new ConfirmOverlay(options, done, theme), { overlay: true, overlayOptions: { width: 64, maxHeight: "70%", anchor: "center" } });
126
+ return await ctx.ui.custom<boolean>((_tui, theme, _keybindings, done) => new ConfirmOverlay(options, done, theme), {
127
+ overlay: true,
128
+ overlayOptions: { width: 64, maxHeight: "70%", anchor: "center" },
129
+ });
107
130
  }
108
131
 
109
132
  async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId: string): Promise<void> {
110
133
  if (!ctx.hasUI) return;
111
134
  const { MailboxDetailOverlay } = await ui();
112
- const action = await ctx.ui.custom<MailboxAction | undefined>((_tui, theme, _keybindings, done) => new MailboxDetailOverlay({ runId, cwd: ctx.cwd, done, theme }), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
135
+ const action = await ctx.ui.custom<MailboxAction | undefined>(
136
+ (_tui, theme, _keybindings, done) => new MailboxDetailOverlay({ runId, cwd: ctx.cwd, done, theme }),
137
+ {
138
+ overlay: true,
139
+ overlayOptions: {
140
+ width: "90%",
141
+ maxHeight: "85%",
142
+ anchor: "center",
143
+ },
144
+ },
145
+ );
113
146
  if (!action || action.type === "close") return;
114
147
  let resultMessage: string | undefined;
115
148
  let ok = true;
@@ -118,14 +151,29 @@ async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId:
118
151
  ok = result.ok;
119
152
  resultMessage = result.message;
120
153
  } else if (action.type === "ackAll") {
121
- const confirmed = await openConfirm(ctx, { title: "Acknowledge all unread messages?", body: "This cannot be undone. Y=ack all, N=cancel.", dangerLevel: "medium", defaultAction: "cancel" });
154
+ const confirmed = await openConfirm(ctx, {
155
+ title: "Acknowledge all unread messages?",
156
+ body: "This cannot be undone. Y=ack all, N=cancel.",
157
+ dangerLevel: "medium",
158
+ defaultAction: "cancel",
159
+ });
122
160
  if (!confirmed) return;
123
161
  const result = await dispatchMailboxAckAll(ctx as ExtensionContext, runId);
124
162
  ok = result.ok;
125
163
  resultMessage = result.message;
126
164
  } else if (action.type === "compose") {
127
165
  const { MailboxComposeOverlay } = await ui();
128
- const compose = await ctx.ui.custom<MailboxComposeResult>((_tui, theme, _keybindings, done) => new MailboxComposeOverlay({ done, theme }), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
166
+ const compose = await ctx.ui.custom<MailboxComposeResult>(
167
+ (_tui, theme, _keybindings, done) => new MailboxComposeOverlay({ done, theme }),
168
+ {
169
+ overlay: true,
170
+ overlayOptions: {
171
+ width: "90%",
172
+ maxHeight: "85%",
173
+ anchor: "center",
174
+ },
175
+ },
176
+ );
129
177
  if (compose.type === "cancel") return;
130
178
  const result = await dispatchMailboxCompose(ctx as ExtensionContext, runId, compose.payload);
131
179
  ok = result.ok;
@@ -134,11 +182,32 @@ async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId:
134
182
  let agentId = action.agentId;
135
183
  if (!agentId) {
136
184
  const { AgentPickerOverlay } = await ui();
137
- const picked = await ctx.ui.custom<{ agentId: string } | undefined>((_tui, theme, _keybindings, done) => new AgentPickerOverlay({ cwd: ctx.cwd, runId, done, theme }), { overlay: true, overlayOptions: { width: 72, maxHeight: "75%", anchor: "center" } });
185
+ const picked = await ctx.ui.custom<{ agentId: string } | undefined>(
186
+ (_tui, theme, _keybindings, done) =>
187
+ new AgentPickerOverlay({
188
+ cwd: ctx.cwd,
189
+ runId,
190
+ done,
191
+ theme,
192
+ }),
193
+ {
194
+ overlay: true,
195
+ overlayOptions: {
196
+ width: 72,
197
+ maxHeight: "75%",
198
+ anchor: "center",
199
+ },
200
+ },
201
+ );
138
202
  agentId = picked?.agentId;
139
203
  }
140
204
  if (!agentId) return;
141
- const result = await dispatchMailboxNudge(ctx as ExtensionContext, runId, agentId, "Please report your current status, blocker, or smallest next step.");
205
+ const result = await dispatchMailboxNudge(
206
+ ctx as ExtensionContext,
207
+ runId,
208
+ agentId,
209
+ "Please report your current status, blocker, or smallest next step.",
210
+ );
142
211
  ok = result.ok;
143
212
  resultMessage = result.message;
144
213
  }
@@ -171,55 +240,77 @@ export async function openTeamSettingsOverlay(ctx: ExtensionContext): Promise<vo
171
240
  ]);
172
241
  const loaded = loadConfig(ctx.cwd);
173
242
  const config = loaded.config as Record<string, unknown>;
174
- await ctx.ui.custom<undefined>((_tui, _theme, _keybindings, done) => {
175
- const theme = asCrewTheme(_theme);
176
- const { overlay } = createSettingsOverlay(config, theme, (id: string, value: unknown) => {
177
- try {
178
- const patch: Record<string, unknown> = {};
179
- const keys = id.split(".");
180
- let target: Record<string, unknown> = patch;
181
- for (let i = 0; i < keys.length - 1; i++) {
182
- if (!target[keys[i]!] || typeof target[keys[i]!] !== "object") target[keys[i]!] = {};
183
- target = target[keys[i]!] as Record<string, unknown>;
184
- }
185
- target[keys[keys.length - 1]!] = value;
186
- if (value === undefined) { updateConfig({}, { unsetPaths: [id] }); }
187
- else { updateConfig(parseConfig(patch)); }
188
- } catch (error) {
189
- ctx.ui.notify(`Failed to save: ${error instanceof Error ? error.message : String(error)}`, "error");
190
- }
191
- }, () => done(undefined), async (action: string, value: unknown) => {
192
- // Action callbacks (Pi theme switch) write to a different store
193
- // than pi-crew config (e.g. ~/.pi/agent/settings.json).
194
- try {
195
- if (action === "piTheme" && typeof value === "string") {
196
- // Live theme switch: ctx.ui.setTheme() swaps the global theme,
197
- // persists it to settings.json, and triggers a UI redraw — no
198
- // restart needed. Falls back to file-write + restart hint if
199
- // the live API is unavailable (e.g. non-TUI mode).
200
- if (typeof ctx.ui.setTheme === "function") {
201
- const res = ctx.ui.setTheme(value);
202
- if (res.success) {
203
- ctx.ui.notify(`Theme: ${value} (applied live)`, "info");
243
+ await ctx.ui.custom<undefined>(
244
+ (_tui, _theme, _keybindings, done) => {
245
+ const theme = asCrewTheme(_theme);
246
+ const { overlay } = createSettingsOverlay(
247
+ config,
248
+ theme,
249
+ (id: string, value: unknown) => {
250
+ try {
251
+ const patch: Record<string, unknown> = {};
252
+ const keys = id.split(".");
253
+ let target: Record<string, unknown> = patch;
254
+ for (let i = 0; i < keys.length - 1; i++) {
255
+ if (!target[keys[i]!] || typeof target[keys[i]!] !== "object") target[keys[i]!] = {};
256
+ target = target[keys[i]!] as Record<string, unknown>;
257
+ }
258
+ target[keys[keys.length - 1]!] = value;
259
+ if (value === undefined) {
260
+ updateConfig({}, { unsetPaths: [id] });
204
261
  } else {
205
- // LAZY: defer dynamic import of ../../ui/theme-discovery.ts to its call site.
206
- const { setPiTheme } = await import("../../ui/theme-discovery.ts");
207
- setPiTheme(value);
208
- ctx.ui.notify(`Theme saved as '${value}' but failed to apply: ${res.error ?? "unknown"}. Restart Pi.`, "warning");
262
+ updateConfig(parseConfig(patch));
209
263
  }
210
- } else {
211
- // LAZY: defer dynamic import of ../../ui/theme-discovery.ts to its call site.
212
- const { setPiTheme } = await import("../../ui/theme-discovery.ts");
213
- setPiTheme(value);
214
- ctx.ui.notify(`Pi theme set to '${value}'. Restart Pi to apply.`, "info");
264
+ } catch (error) {
265
+ ctx.ui.notify(`Failed to save: ${error instanceof Error ? error.message : String(error)}`, "error");
215
266
  }
216
- }
217
- } catch (error) {
218
- ctx.ui.notify(`Failed: ${error instanceof Error ? error.message : String(error)}`, "error");
219
- }
220
- });
221
- return overlay;
222
- }, { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
267
+ },
268
+ () => done(undefined),
269
+ async (action: string, value: unknown) => {
270
+ // Action callbacks (Pi theme switch) write to a different store
271
+ // than pi-crew config (e.g. ~/.pi/agent/settings.json).
272
+ try {
273
+ if (action === "piTheme" && typeof value === "string") {
274
+ // Live theme switch: ctx.ui.setTheme() swaps the global theme,
275
+ // persists it to settings.json, and triggers a UI redraw — no
276
+ // restart needed. Falls back to file-write + restart hint if
277
+ // the live API is unavailable (e.g. non-TUI mode).
278
+ if (typeof ctx.ui.setTheme === "function") {
279
+ const res = ctx.ui.setTheme(value);
280
+ if (res.success) {
281
+ ctx.ui.notify(`Theme: ${value} (applied live)`, "info");
282
+ } else {
283
+ // LAZY: defer dynamic import of ../../ui/theme-discovery.ts to its call site.
284
+ const { setPiTheme } = await import("../../ui/theme-discovery.ts");
285
+ setPiTheme(value);
286
+ ctx.ui.notify(
287
+ `Theme saved as '${value}' but failed to apply: ${res.error ?? "unknown"}. Restart Pi.`,
288
+ "warning",
289
+ );
290
+ }
291
+ } else {
292
+ // LAZY: defer dynamic import of ../../ui/theme-discovery.ts to its call site.
293
+ const { setPiTheme } = await import("../../ui/theme-discovery.ts");
294
+ setPiTheme(value);
295
+ ctx.ui.notify(`Pi theme set to '${value}'. Restart Pi to apply.`, "info");
296
+ }
297
+ }
298
+ } catch (error) {
299
+ ctx.ui.notify(`Failed: ${error instanceof Error ? error.message : String(error)}`, "error");
300
+ }
301
+ },
302
+ );
303
+ return overlay;
304
+ },
305
+ {
306
+ overlay: true,
307
+ overlayOptions: {
308
+ width: "90%",
309
+ maxHeight: "85%",
310
+ anchor: "center",
311
+ },
312
+ },
313
+ );
223
314
  }
224
315
 
225
316
  async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selection: RunDashboardSelection): Promise<void> {
@@ -233,14 +324,24 @@ async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selecti
233
324
  depsNotify(ctx, "Recovery is only available for foreground runs.", "warning");
234
325
  return;
235
326
  }
236
- const confirmed = await openConfirm(ctx, { title: "Interrupt foreground run?", body: "Tasks may be marked failed. Y=interrupt, N=cancel.", dangerLevel: "high", defaultAction: "cancel" });
327
+ const confirmed = await openConfirm(ctx, {
328
+ title: "Interrupt foreground run?",
329
+ body: "Tasks may be marked failed. Y=interrupt, N=cancel.",
330
+ dangerLevel: "high",
331
+ defaultAction: "cancel",
332
+ });
237
333
  if (!confirmed) return;
238
334
  const result = await dispatchHealthRecovery(ctx as ExtensionContext, selection.runId);
239
335
  depsNotify(ctx, result.message, result.ok ? "info" : "error");
240
336
  return;
241
337
  }
242
338
  if (selection.action === "health-kill-stale") {
243
- const confirmed = await openConfirm(ctx, { title: "Mark stale workers dead?", body: "This updates worker heartbeat state. Y=mark dead, N=cancel.", dangerLevel: "medium", defaultAction: "cancel" });
339
+ const confirmed = await openConfirm(ctx, {
340
+ title: "Mark stale workers dead?",
341
+ body: "This updates worker heartbeat state. Y=mark dead, N=cancel.",
342
+ dangerLevel: "medium",
343
+ defaultAction: "cancel",
344
+ });
244
345
  if (!confirmed) return;
245
346
  const result = await dispatchKillStaleWorkers(ctx as ExtensionContext, selection.runId);
246
347
  depsNotify(ctx, result.message, result.ok ? "info" : "error");
@@ -250,10 +351,16 @@ async function handleHealthDashboardAction(ctx: ExtensionCommandContext, selecti
250
351
  const diagDir = path.join(loaded.manifest.artifactsRoot, "diagnostic");
251
352
  const recent = listRecentDiagnostic(diagDir, 60_000);
252
353
  if (recent) {
253
- const confirmed = await openConfirm(ctx, { title: "Recent diagnostic exists", body: `File ${recent} was created <1min ago. Export another diagnostic?`, defaultAction: "cancel" });
354
+ const confirmed = await openConfirm(ctx, {
355
+ title: "Recent diagnostic exists",
356
+ body: `File ${recent} was created <1min ago. Export another diagnostic?`,
357
+ defaultAction: "cancel",
358
+ });
254
359
  if (!confirmed) return;
255
360
  }
256
- const result = await dispatchDiagnosticExport(ctx as ExtensionContext, selection.runId, { registry: depsRef?.getMetricRegistry?.() });
361
+ const result = await dispatchDiagnosticExport(ctx as ExtensionContext, selection.runId, {
362
+ registry: depsRef?.getMetricRegistry?.(),
363
+ });
257
364
  depsNotify(ctx, result.message, result.ok ? "info" : "error");
258
365
  }
259
366
  }
@@ -292,7 +399,34 @@ export async function openTeamDashboard(ctx: ExtensionContext): Promise<void> {
292
399
  const rightPanel = (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) === "right";
293
400
  const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth)) : "90%";
294
401
  const { RunDashboard } = await ui();
295
- const selection = await cmdCtx.ui.custom<RunDashboardSelection | undefined>((tui, theme, _keybindings, done) => new RunDashboard(runs, done, theme, { placement: rightPanel ? "right" : "center", showModel: uiConfig?.showModel, showTokens: uiConfig?.showTokens, showTools: uiConfig?.showTools, snapshotCache: deps.getRunSnapshotCache?.(cmdCtx.cwd), runProvider: () => deps.getManifestCache(cmdCtx.cwd).list(50), registry: deps.getMetricRegistry?.(), workspaceId: sessionId, requestRender: () => requestRenderTarget(tui) }), { overlay: true, overlayOptions: rightPanel ? { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 } } : { width, maxHeight: "90%", anchor: "center", margin: 2 } });
402
+ const selection = await cmdCtx.ui.custom<RunDashboardSelection | undefined>(
403
+ (tui, theme, _keybindings, done) =>
404
+ new RunDashboard(runs, done, theme, {
405
+ placement: rightPanel ? "right" : "center",
406
+ showModel: uiConfig?.showModel,
407
+ showTokens: uiConfig?.showTokens,
408
+ showTools: uiConfig?.showTools,
409
+ snapshotCache: deps.getRunSnapshotCache?.(cmdCtx.cwd),
410
+ runProvider: () => deps.getManifestCache(cmdCtx.cwd).list(50),
411
+ registry: deps.getMetricRegistry?.(),
412
+ workspaceId: sessionId,
413
+ requestRender: () => requestRenderTarget(tui),
414
+ }),
415
+ {
416
+ overlay: true,
417
+ overlayOptions: rightPanel
418
+ ? {
419
+ width,
420
+ minWidth: 40,
421
+ maxHeight: "100%",
422
+ anchor: "top-right",
423
+ offsetX: 0,
424
+ offsetY: 0,
425
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
426
+ }
427
+ : { width, maxHeight: "90%", anchor: "center", margin: 2 },
428
+ },
429
+ );
296
430
  if (!selection) return;
297
431
  if (selection.action === "reload") continue;
298
432
  if (selection.action === "notifications-dismiss") {
@@ -305,16 +439,102 @@ export async function openTeamDashboard(ctx: ExtensionContext): Promise<void> {
305
439
  deps.getRunSnapshotCache?.(cmdCtx.cwd).invalidate(selection.runId);
306
440
  continue;
307
441
  }
308
- if (selection.action === "health-recovery" || selection.action === "health-kill-stale" || selection.action === "health-diagnostic-export") {
442
+ if (
443
+ selection.action === "health-recovery" ||
444
+ selection.action === "health-kill-stale" ||
445
+ selection.action === "health-diagnostic-export"
446
+ ) {
309
447
  await handleHealthDashboardAction(cmdCtx, selection);
310
448
  deps.getRunSnapshotCache?.(cmdCtx.cwd).invalidate(selection.runId);
311
449
  continue;
312
450
  }
313
- if (selection.action === "agent-transcript" && await openTranscriptViewer(cmdCtx, selection.runId)) continue;
314
- if (selection.action === "agent-live" && await openLiveConversation(cmdCtx, selection.runId)) continue;
315
- if (selection.action === "agent-live") { await notifyCommandResult(cmdCtx, commandText({ content: [{ type: "text", text: "No live agent found for this run." }] })); continue; }
316
- const result = selection.action === "api" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-manifest" } }, teamCommandContext(cmdCtx)) : selection.action === "agents" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "agent-dashboard" } }, teamCommandContext(cmdCtx)) : selection.action === "mailbox" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-mailbox" } }, teamCommandContext(cmdCtx)) : selection.action === "agent-events" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-events", limit: 50 } }, teamCommandContext(cmdCtx)) : selection.action === "agent-output" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-output", maxBytes: 32_000 } }, teamCommandContext(cmdCtx)) : selection.action === "agent-transcript" ? await handleTeamTool({ action: "api", runId: selection.runId, config: { operation: "read-agent-transcript" } }, teamCommandContext(cmdCtx)) : // eslint-disable-next-line @typescript-eslint/no-explicit-any
317
- await handleTeamTool({ action: selection.action as any, runId: selection.runId }, teamCommandContext(cmdCtx));
451
+ if (selection.action === "agent-transcript" && (await openTranscriptViewer(cmdCtx, selection.runId))) continue;
452
+ if (selection.action === "agent-live" && (await openLiveConversation(cmdCtx, selection.runId))) continue;
453
+ if (selection.action === "agent-live") {
454
+ await notifyCommandResult(
455
+ cmdCtx,
456
+ commandText({
457
+ content: [
458
+ {
459
+ type: "text",
460
+ text: "No live agent found for this run.",
461
+ },
462
+ ],
463
+ }),
464
+ );
465
+ continue;
466
+ }
467
+ const result =
468
+ selection.action === "api"
469
+ ? await handleTeamTool(
470
+ {
471
+ action: "api",
472
+ runId: selection.runId,
473
+ config: { operation: "read-manifest" },
474
+ },
475
+ teamCommandContext(cmdCtx),
476
+ )
477
+ : selection.action === "agents"
478
+ ? await handleTeamTool(
479
+ {
480
+ action: "api",
481
+ runId: selection.runId,
482
+ config: { operation: "agent-dashboard" },
483
+ },
484
+ teamCommandContext(cmdCtx),
485
+ )
486
+ : selection.action === "mailbox"
487
+ ? await handleTeamTool(
488
+ {
489
+ action: "api",
490
+ runId: selection.runId,
491
+ config: { operation: "read-mailbox" },
492
+ },
493
+ teamCommandContext(cmdCtx),
494
+ )
495
+ : selection.action === "agent-events"
496
+ ? await handleTeamTool(
497
+ {
498
+ action: "api",
499
+ runId: selection.runId,
500
+ config: {
501
+ operation: "read-agent-events",
502
+ limit: 50,
503
+ },
504
+ },
505
+ teamCommandContext(cmdCtx),
506
+ )
507
+ : selection.action === "agent-output"
508
+ ? await handleTeamTool(
509
+ {
510
+ action: "api",
511
+ runId: selection.runId,
512
+ config: {
513
+ operation: "read-agent-output",
514
+ maxBytes: 32_000,
515
+ },
516
+ },
517
+ teamCommandContext(cmdCtx),
518
+ )
519
+ : selection.action === "agent-transcript"
520
+ ? await handleTeamTool(
521
+ {
522
+ action: "api",
523
+ runId: selection.runId,
524
+ config: {
525
+ operation: "read-agent-transcript",
526
+ },
527
+ },
528
+ teamCommandContext(cmdCtx),
529
+ )
530
+ : // eslint-disable-next-line @typescript-eslint/no-explicit-any
531
+ await handleTeamTool(
532
+ {
533
+ action: selection.action as any,
534
+ runId: selection.runId,
535
+ },
536
+ teamCommandContext(cmdCtx),
537
+ );
318
538
  await notifyCommandResult(cmdCtx, commandText(result));
319
539
  return;
320
540
  }
@@ -335,7 +555,13 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
335
555
  // Round 13 UX: suggest team names for Tab-completion of the first positional arg.
336
556
  getArgumentCompletions: (argumentPrefix: string) => suggestTeams(argumentPrefix),
337
557
  handler: async (args: string, ctx: ExtensionCommandContext) => {
338
- const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: undefined });
558
+ const result = await handleTeamTool(parseRunArgs(args), {
559
+ ...teamCommandContext(ctx),
560
+ metricRegistry: deps.getMetricRegistry?.(),
561
+ startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId),
562
+ abortForegroundRun: deps.abortForegroundRun,
563
+ onRunStarted: undefined,
564
+ });
339
565
  await notifyCommandResult(ctx, commandText(result));
340
566
  },
341
567
  });
@@ -356,7 +582,13 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
356
582
  getArgumentCompletions: (argumentPrefix: string) => suggestRunIds(argumentPrefix),
357
583
  handler: async (args: string, ctx: ExtensionCommandContext) => {
358
584
  const runId = args.trim() || undefined;
359
- const result = await handleTeamTool({ action, runId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
585
+ const result = await handleTeamTool(
586
+ { action, runId },
587
+ {
588
+ ...teamCommandContext(ctx),
589
+ getRunSnapshotCache: deps.getRunSnapshotCache,
590
+ },
591
+ );
360
592
  await notifyCommandResult(ctx, commandText(result));
361
593
  },
362
594
  });
@@ -371,7 +603,13 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
371
603
  await notifyCommandResult(ctx, "Usage: /team-invalidate <runId>");
372
604
  return;
373
605
  }
374
- const result = await handleTeamTool({ action: "invalidate", runId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
606
+ const result = await handleTeamTool(
607
+ { action: "invalidate", runId },
608
+ {
609
+ ...teamCommandContext(ctx),
610
+ getRunSnapshotCache: deps.getRunSnapshotCache,
611
+ },
612
+ );
375
613
  await notifyCommandResult(ctx, commandText(result));
376
614
  },
377
615
  });
@@ -387,7 +625,13 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
387
625
  await notifyCommandResult(ctx, "Usage: /team-retry <runId> [taskId]");
388
626
  return;
389
627
  }
390
- const retryResult = await handleTeamTool({ action: "retry", runId, taskId }, { ...teamCommandContext(ctx), getRunSnapshotCache: deps.getRunSnapshotCache });
628
+ const retryResult = await handleTeamTool(
629
+ { action: "retry", runId, taskId },
630
+ {
631
+ ...teamCommandContext(ctx),
632
+ getRunSnapshotCache: deps.getRunSnapshotCache,
633
+ },
634
+ );
391
635
  await notifyCommandResult(ctx, commandText(retryResult));
392
636
  },
393
637
  });
@@ -415,10 +659,20 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
415
659
  const taskId = tokens.shift();
416
660
  const prompt = tokens.join(" ") || undefined;
417
661
  if (!runId || !taskId || !prompt) {
418
- await notifyCommandResult(ctx, "Usage: /team-follow-up <runId> <taskId> <prompt>. Use /team-respond for waiting-task replies.");
662
+ await notifyCommandResult(
663
+ ctx,
664
+ "Usage: /team-follow-up <runId> <taskId> <prompt>. Use /team-respond for waiting-task replies.",
665
+ );
419
666
  return;
420
667
  }
421
- const result = await handleTeamTool({ action: "api", runId, config: { operation: "follow-up-agent", taskId, prompt } }, teamCommandContext(ctx));
668
+ const result = await handleTeamTool(
669
+ {
670
+ action: "api",
671
+ runId,
672
+ config: { operation: "follow-up-agent", taskId, prompt },
673
+ },
674
+ teamCommandContext(ctx),
675
+ );
422
676
  await notifyCommandResult(ctx, commandText(result));
423
677
  },
424
678
  });
@@ -444,7 +698,8 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
444
698
  });
445
699
 
446
700
  pi.registerCommand("team-goal", {
447
- description: "Autonomous goal loop control: [start|status|pause|resume|stop|step|clear] [goalId] [--objective=...] [--evaluatorModel=...] [--maxTurns=N]",
701
+ description:
702
+ "Autonomous goal loop control: [start|status|pause|resume|stop|step|clear] [goalId] [--objective=...] [--evaluatorModel=...] [--maxTurns=N]",
448
703
  handler: async (args: string, ctx: ExtensionCommandContext) => {
449
704
  const tokens = args.trim().split(/\s+/).filter(Boolean);
450
705
  const knownSubs = new Set(["start", "status", "pause", "resume", "stop", "step", "clear", "cancel", "reset"]);
@@ -470,238 +725,475 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
470
725
  },
471
726
  });
472
727
 
473
- pi.registerCommand("team-metrics", { description: "Show pi-crew metrics snapshot: [filter]", handler: async (args: string, ctx: ExtensionCommandContext) => {
474
- const filter = args.trim() || undefined;
475
- const result = await handleTeamTool({ action: "api", config: { operation: "metrics-snapshot", filter } }, { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.() });
476
- await notifyCommandResult(ctx, commandText(result));
477
- } });
478
-
479
- pi.registerCommand("team-imports", { description: "List imported pi-crew run bundles", handler: async (_args: string, ctx: ExtensionCommandContext) => {
480
- const result = await handleTeamTool({ action: "imports" }, teamCommandContext(ctx));
481
- await notifyCommandResult(ctx, commandText(result));
482
- } });
483
-
484
- pi.registerCommand("team-import", { description: "Import a pi-crew run-export.json bundle into local imports", handler: async (args: string, ctx: ExtensionCommandContext) => {
485
- const tokens = args.trim().split(/\s+/).filter(Boolean);
486
- const pathArg = tokens.find((token) => !token.startsWith("--"));
487
- const scope = tokens.includes("--user") ? "user" : "project";
488
- const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, teamCommandContext(ctx));
489
- await notifyCommandResult(ctx, commandText(result));
490
- } });
491
-
492
- pi.registerCommand("team-prune", { description: "Prune old finished pi-crew runs, keeping the newest N", handler: async (args: string, ctx: ExtensionCommandContext) => {
493
- const tokens = args.trim().split(/\s+/).filter(Boolean);
494
- const keepToken = tokens.find((token) => token.startsWith("--keep="));
495
- const keep = keepToken ? Number.parseInt(keepToken.slice("--keep=".length), 10) : undefined;
496
- const result = await handleTeamTool({ action: "prune", keep, confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
497
- await notifyCommandResult(ctx, commandText(result));
498
- } });
499
-
500
- pi.registerCommand("team-forget", { description: "Forget a pi-crew run by deleting its state and artifacts", getArgumentCompletions: (argumentPrefix: string) => suggestRunIds(argumentPrefix), handler: async (args: string, ctx: ExtensionCommandContext) => {
501
- const tokens = args.trim().split(/\s+/).filter(Boolean);
502
- const runId = tokens.find((token) => !token.startsWith("--"));
503
- const result = await handleTeamTool({ action: "forget", runId, force: tokens.includes("--force"), confirm: tokens.includes("--confirm") }, teamCommandContext(ctx));
504
- await notifyCommandResult(ctx, commandText(result));
505
- } });
728
+ pi.registerCommand("team-metrics", {
729
+ description: "Show pi-crew metrics snapshot: [filter]",
730
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
731
+ const filter = args.trim() || undefined;
732
+ const result = await handleTeamTool(
733
+ {
734
+ action: "api",
735
+ config: { operation: "metrics-snapshot", filter },
736
+ },
737
+ {
738
+ ...teamCommandContext(ctx),
739
+ metricRegistry: deps.getMetricRegistry?.(),
740
+ },
741
+ );
742
+ await notifyCommandResult(ctx, commandText(result));
743
+ },
744
+ });
745
+
746
+ pi.registerCommand("team-imports", {
747
+ description: "List imported pi-crew run bundles",
748
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
749
+ const result = await handleTeamTool({ action: "imports" }, teamCommandContext(ctx));
750
+ await notifyCommandResult(ctx, commandText(result));
751
+ },
752
+ });
753
+
754
+ pi.registerCommand("team-import", {
755
+ description: "Import a pi-crew run-export.json bundle into local imports",
756
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
757
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
758
+ const pathArg = tokens.find((token) => !token.startsWith("--"));
759
+ const scope = tokens.includes("--user") ? "user" : "project";
760
+ const result = await handleTeamTool({ action: "import", config: { path: pathArg, scope } }, teamCommandContext(ctx));
761
+ await notifyCommandResult(ctx, commandText(result));
762
+ },
763
+ });
764
+
765
+ pi.registerCommand("team-prune", {
766
+ description: "Prune old finished pi-crew runs, keeping the newest N",
767
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
768
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
769
+ const keepToken = tokens.find((token) => token.startsWith("--keep="));
770
+ const keep = keepToken ? Number.parseInt(keepToken.slice("--keep=".length), 10) : undefined;
771
+ const result = await handleTeamTool(
772
+ {
773
+ action: "prune",
774
+ keep,
775
+ confirm: tokens.includes("--confirm"),
776
+ },
777
+ teamCommandContext(ctx),
778
+ );
779
+ await notifyCommandResult(ctx, commandText(result));
780
+ },
781
+ });
782
+
783
+ pi.registerCommand("team-forget", {
784
+ description: "Forget a pi-crew run by deleting its state and artifacts",
785
+ getArgumentCompletions: (argumentPrefix: string) => suggestRunIds(argumentPrefix),
786
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
787
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
788
+ const runId = tokens.find((token) => !token.startsWith("--"));
789
+ const result = await handleTeamTool(
790
+ {
791
+ action: "forget",
792
+ runId,
793
+ force: tokens.includes("--force"),
794
+ confirm: tokens.includes("--confirm"),
795
+ },
796
+ teamCommandContext(ctx),
797
+ );
798
+ await notifyCommandResult(ctx, commandText(result));
799
+ },
800
+ });
506
801
 
507
802
  pi.registerCommand("team-settings", {
508
- description: "View or update pi-crew settings: interactive UI or [list|get <key>|set <key> <value>|unset <key>|path|scope]",
509
- handler: async (args: string, ctx: ExtensionCommandContext) => {
510
- if (ctx.hasUI && !args.trim()) {
511
- await openTeamSettingsOverlay(ctx);
512
- return;
513
- }
514
- const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, teamCommandContext(ctx));
515
- await notifyCommandResult(ctx, commandText(result));
516
- // Live-switch hook: when the text subcommand 'theme <name>' succeeds,
517
- // also apply the theme live via ctx.ui.setTheme() (no restart). The
518
- // handler above only writes to settings.json.
519
- const trimmed = args.trim();
520
- if (trimmed.startsWith("theme ")) {
521
- const themeName = trimmed.slice(6).trim();
522
- if (themeName && typeof ctx.ui.setTheme === "function") {
523
- const res = ctx.ui.setTheme(themeName);
524
- if (res.success) ctx.ui.notify(`Theme: ${themeName} (applied live)`, "info");
525
- else ctx.ui.notify(`Saved but live-switch failed: ${res.error ?? "unknown"}. Restart Pi.`, "warning");
803
+ description: "View or update pi-crew settings: interactive UI or [list|get <key>|set <key> <value>|unset <key>|path|scope]",
804
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
805
+ if (ctx.hasUI && !args.trim()) {
806
+ await openTeamSettingsOverlay(ctx);
807
+ return;
526
808
  }
527
- }
528
- },
529
- })
809
+ const result = await handleTeamTool({ action: "settings", config: { args: args.trim() } }, teamCommandContext(ctx));
810
+ await notifyCommandResult(ctx, commandText(result));
811
+ // Live-switch hook: when the text subcommand 'theme <name>' succeeds,
812
+ // also apply the theme live via ctx.ui.setTheme() (no restart). The
813
+ // handler above only writes to settings.json.
814
+ const trimmed = args.trim();
815
+ if (trimmed.startsWith("theme ")) {
816
+ const themeName = trimmed.slice(6).trim();
817
+ if (themeName && typeof ctx.ui.setTheme === "function") {
818
+ const res = ctx.ui.setTheme(themeName);
819
+ if (res.success) ctx.ui.notify(`Theme: ${themeName} (applied live)`, "info");
820
+ else ctx.ui.notify(`Saved but live-switch failed: ${res.error ?? "unknown"}. Restart Pi.`, "warning");
821
+ }
822
+ }
823
+ },
824
+ });
530
825
 
531
- pi.registerCommand("team-manager", { description: "Open the pi-crew interactive menu (list/run/status/cleanup/manage resources/doctor)", handler: handleTeamManagerCommand });
826
+ pi.registerCommand("team-manager", {
827
+ description: "Open the pi-crew interactive menu (list/run/status/cleanup/manage resources/doctor)",
828
+ handler: handleTeamManagerCommand,
829
+ });
532
830
  // Backward-compat alias: this command was originally registered as "team-cleanup"
533
831
  // (the interactive menu predates the runId-targeted cleanup action). Keep both so
534
832
  // existing muscle memory + help docs both work.
535
- pi.registerCommand("team-cleanup-menu", { description: "Alias for /team-manager (pi-crew interactive menu)", handler: handleTeamManagerCommand });
536
-
537
- pi.registerCommand("team-result", { description: "Open a pi-crew agent result viewer: <runId> [taskId]", getArgumentCompletions: async (argumentPrefix: string) => { const parts = argumentPrefix.trim().split(/\s+/); return parts.length <= 1 ? suggestRunIds(parts[0] ?? "") : suggestTaskIds(parts[0] ?? "", parts[1] ?? ""); }, handler: async (args: string, ctx: ExtensionCommandContext) => {
538
- const [runId, rawTaskId] = args.trim().split(/\s+/).filter(Boolean);
539
- const selected = await selectAgentTask(ctx, runId, rawTaskId);
540
- const loaded = selected ? loadRunManifestById(ctx.cwd, selected.runId) : undefined; // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
541
- if (ctx.hasUI && loaded) {
542
- const agent = readCrewAgents(loaded.manifest).find((item) => item.taskId === selected?.taskId || item.id === selected?.taskId) ?? readCrewAgents(loaded.manifest)[0];
543
- const resultText = agent?.resultArtifactPath ? commandText(await handleTeamTool({ action: "api", runId: selected?.runId ?? "", config: { operation: "read-agent-output", agentId: agent.taskId, maxBytes: 64_000 } }, teamCommandContext(ctx))) : "(no result)";
544
- const { DurableTextViewer } = await ui();
545
- await ctx.ui.custom<undefined>((_tui, theme, _keybindings, done) => new DurableTextViewer("pi-crew result", `${selected?.runId ?? ""}:${agent?.taskId ?? "unknown"}`, resultText.split(/\r?\n/), theme, done), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
546
- return;
547
- }
548
- const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-output", agentId: rawTaskId, maxBytes: 64_000 } }, teamCommandContext(ctx));
549
- await notifyCommandResult(ctx, commandText(result));
550
- } });
551
-
552
- pi.registerCommand("team-transcript", { description: "Open a pi-crew transcript viewer: <runId> [taskId]", getArgumentCompletions: async (argumentPrefix: string) => { const parts = argumentPrefix.trim().split(/\s+/); return parts.length <= 1 ? suggestRunIds(parts[0] ?? "") : suggestTaskIds(parts[0] ?? "", parts[1] ?? ""); }, handler: async (args: string, ctx: ExtensionCommandContext) => {
553
- const [runId, taskId] = args.trim().split(/\s+/).filter(Boolean);
554
- if (await openTranscriptViewer(ctx, runId, taskId)) return;
555
- const result = await handleTeamTool({ action: "api", runId, config: { operation: "read-agent-transcript", agentId: taskId } }, teamCommandContext(ctx));
556
- await notifyCommandResult(ctx, commandText(result));
557
- } });
558
-
559
- pi.registerCommand("team-dashboard", { description: "Open a pi-crew run dashboard overlay", handler: async (_args: string, ctx: ExtensionCommandContext) => {
560
- await openTeamDashboard(ctx);
561
- } });
562
-
563
- pi.registerCommand("team-mascot", { description: "Show an animated mascot splash", handler: async (args: string, ctx: ExtensionCommandContext) => {
564
- if (!ctx.hasUI) return;
565
- const tokens = args.trim().split(/\s+/).filter(Boolean);
566
- const uiConfig = loadConfig(ctx.cwd).config.ui;
567
- const styleArg = tokens.find((t) => t === "cat" || t === "armin");
568
- const effectArg = tokens.find((t) => ["random", "none", "typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"].includes(t));
569
- const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ?? DEFAULT_UI.mascotStyle;
570
- const effect = (effectArg as "random" | "none" | "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve" | undefined) ?? uiConfig?.mascotEffect ?? DEFAULT_UI.mascotEffect;
571
- const { AnimatedMascot } = await ui();
572
- await ctx.ui.custom<undefined>((tui, theme, _keybindings, done) => new AnimatedMascot(theme, () => done(undefined), { frameIntervalMs: style === "armin" ? 33 : 180, autoCloseMs: 7000, requestRender: () => requestRenderTarget(tui), style, effect }), { overlay: true, overlayOptions: { width: style === "armin" ? 48 : 62, maxHeight: "85%", anchor: "center" } });
573
- } });
574
-
575
- pi.registerCommand("team-init", { description: "Initialize pi-crew layout and global config. Use --project-config to write .pi/pi-crew.json.", handler: async (args: string, ctx: ExtensionCommandContext) => {
576
- const tokens = args.trim().split(/\s+/).filter(Boolean);
577
- const configScope = tokens.includes("--project-config") || tokens.includes("--project") ? "project" : tokens.includes("--no-config") ? "none" : "global";
578
- const result = await handleTeamTool({ action: "init", config: { copyBuiltins: tokens.includes("--copy-builtins"), overwrite: tokens.includes("--overwrite"), configScope } }, teamCommandContext(ctx));
579
- await notifyCommandResult(ctx, commandText(result));
580
- } });
581
-
582
- pi.registerCommand("team-autonomy", { description: "Show or toggle pi-crew autonomous delegation policy: status|on|off", handler: async (args: string, ctx: ExtensionCommandContext) => {
583
- const tokens = args.trim().split(/\s+/).filter(Boolean);
584
- const mode = tokens[0]?.toLowerCase();
585
- const config = mode === "on" ? { profile: "suggested", enabled: true, injectPolicy: true } : mode === "off" ? { profile: "manual", enabled: false } : mode === "manual" || mode === "suggested" || mode === "assisted" || mode === "aggressive" ? { profile: mode, enabled: mode !== "manual", injectPolicy: mode !== "manual" } : { preferAsyncForLongTasks: tokens.includes("--prefer-async") ? true : undefined, allowWorktreeSuggestion: tokens.includes("--no-worktree-suggest") ? false : undefined };
586
- const result = await handleTeamTool({ action: "autonomy", config }, teamCommandContext(ctx));
587
- await notifyCommandResult(ctx, commandText(result));
588
- } });
589
-
590
- pi.registerCommand("team-config", { description: "Show or update pi-crew config. Use key=value [--project] to update.", handler: async (args: string, ctx: ExtensionCommandContext) => {
591
- const tokens = args.trim().split(/\s+/).filter(Boolean);
592
- if (tokens.length === 0) {
593
- const result = await handleTeamTool({ action: "config" }, teamCommandContext(ctx));
833
+ pi.registerCommand("team-cleanup-menu", {
834
+ description: "Alias for /team-manager (pi-crew interactive menu)",
835
+ handler: handleTeamManagerCommand,
836
+ });
837
+
838
+ pi.registerCommand("team-result", {
839
+ description: "Open a pi-crew agent result viewer: <runId> [taskId]",
840
+ getArgumentCompletions: async (argumentPrefix: string) => {
841
+ const parts = argumentPrefix.trim().split(/\s+/);
842
+ return parts.length <= 1 ? suggestRunIds(parts[0] ?? "") : suggestTaskIds(parts[0] ?? "", parts[1] ?? "");
843
+ },
844
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
845
+ const [runId, rawTaskId] = args.trim().split(/\s+/).filter(Boolean);
846
+ const selected = await selectAgentTask(ctx, runId, rawTaskId);
847
+ const loaded = selected ? loadRunManifestById(ctx.cwd, selected.runId) : undefined; // NOTE: no withRunLock - best-effort only; concurrent writes may cause inconsistency
848
+ if (ctx.hasUI && loaded) {
849
+ const agent =
850
+ readCrewAgents(loaded.manifest).find((item) => item.taskId === selected?.taskId || item.id === selected?.taskId) ??
851
+ readCrewAgents(loaded.manifest)[0];
852
+ const resultText = agent?.resultArtifactPath
853
+ ? commandText(
854
+ await handleTeamTool(
855
+ {
856
+ action: "api",
857
+ runId: selected?.runId ?? "",
858
+ config: {
859
+ operation: "read-agent-output",
860
+ agentId: agent.taskId,
861
+ maxBytes: 64_000,
862
+ },
863
+ },
864
+ teamCommandContext(ctx),
865
+ ),
866
+ )
867
+ : "(no result)";
868
+ const { DurableTextViewer } = await ui();
869
+ await ctx.ui.custom<undefined>(
870
+ (_tui, theme, _keybindings, done) =>
871
+ new DurableTextViewer(
872
+ "pi-crew result",
873
+ `${selected?.runId ?? ""}:${agent?.taskId ?? "unknown"}`,
874
+ resultText.split(/\r?\n/),
875
+ theme,
876
+ done,
877
+ ),
878
+ {
879
+ overlay: true,
880
+ overlayOptions: {
881
+ width: "90%",
882
+ maxHeight: "85%",
883
+ anchor: "center",
884
+ },
885
+ },
886
+ );
887
+ return;
888
+ }
889
+ const result = await handleTeamTool(
890
+ {
891
+ action: "api",
892
+ runId,
893
+ config: {
894
+ operation: "read-agent-output",
895
+ agentId: rawTaskId,
896
+ maxBytes: 64_000,
897
+ },
898
+ },
899
+ teamCommandContext(ctx),
900
+ );
594
901
  await notifyCommandResult(ctx, commandText(result));
595
- return;
596
- }
597
- const config: Record<string, unknown> = { scope: tokens.includes("--project") ? "project" : "user" };
598
- for (const token of tokens) {
599
- if (token.startsWith("--unset=")) {
600
- pushUnset(config, token.slice("--unset=".length));
601
- continue;
902
+ },
903
+ });
904
+
905
+ pi.registerCommand("team-transcript", {
906
+ description: "Open a pi-crew transcript viewer: <runId> [taskId]",
907
+ getArgumentCompletions: async (argumentPrefix: string) => {
908
+ const parts = argumentPrefix.trim().split(/\s+/);
909
+ return parts.length <= 1 ? suggestRunIds(parts[0] ?? "") : suggestTaskIds(parts[0] ?? "", parts[1] ?? "");
910
+ },
911
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
912
+ const [runId, taskId] = args.trim().split(/\s+/).filter(Boolean);
913
+ if (await openTranscriptViewer(ctx, runId, taskId)) return;
914
+ const result = await handleTeamTool(
915
+ {
916
+ action: "api",
917
+ runId,
918
+ config: {
919
+ operation: "read-agent-transcript",
920
+ agentId: taskId,
921
+ },
922
+ },
923
+ teamCommandContext(ctx),
924
+ );
925
+ await notifyCommandResult(ctx, commandText(result));
926
+ },
927
+ });
928
+
929
+ pi.registerCommand("team-dashboard", {
930
+ description: "Open a pi-crew run dashboard overlay",
931
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
932
+ await openTeamDashboard(ctx);
933
+ },
934
+ });
935
+
936
+ pi.registerCommand("team-mascot", {
937
+ description: "Show an animated mascot splash",
938
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
939
+ if (!ctx.hasUI) return;
940
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
941
+ const uiConfig = loadConfig(ctx.cwd).config.ui;
942
+ const styleArg = tokens.find((t) => t === "cat" || t === "armin");
943
+ const effectArg = tokens.find((t) =>
944
+ ["random", "none", "typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"].includes(t),
945
+ );
946
+ const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ?? DEFAULT_UI.mascotStyle;
947
+ const effect =
948
+ (effectArg as
949
+ | "random"
950
+ | "none"
951
+ | "typewriter"
952
+ | "scanline"
953
+ | "rain"
954
+ | "fade"
955
+ | "crt"
956
+ | "glitch"
957
+ | "dissolve"
958
+ | undefined) ??
959
+ uiConfig?.mascotEffect ??
960
+ DEFAULT_UI.mascotEffect;
961
+ const { AnimatedMascot } = await ui();
962
+ await ctx.ui.custom<undefined>(
963
+ (tui, theme, _keybindings, done) =>
964
+ new AnimatedMascot(theme, () => done(undefined), {
965
+ frameIntervalMs: style === "armin" ? 33 : 180,
966
+ autoCloseMs: 7000,
967
+ requestRender: () => requestRenderTarget(tui),
968
+ style,
969
+ effect,
970
+ }),
971
+ {
972
+ overlay: true,
973
+ overlayOptions: {
974
+ width: style === "armin" ? 48 : 62,
975
+ maxHeight: "85%",
976
+ anchor: "center",
977
+ },
978
+ },
979
+ );
980
+ },
981
+ });
982
+
983
+ pi.registerCommand("team-init", {
984
+ description: "Initialize pi-crew layout and global config. Use --project-config to write .pi/pi-crew.json.",
985
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
986
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
987
+ const configScope =
988
+ tokens.includes("--project-config") || tokens.includes("--project")
989
+ ? "project"
990
+ : tokens.includes("--no-config")
991
+ ? "none"
992
+ : "global";
993
+ const result = await handleTeamTool(
994
+ {
995
+ action: "init",
996
+ config: {
997
+ copyBuiltins: tokens.includes("--copy-builtins"),
998
+ overwrite: tokens.includes("--overwrite"),
999
+ configScope,
1000
+ },
1001
+ },
1002
+ teamCommandContext(ctx),
1003
+ );
1004
+ await notifyCommandResult(ctx, commandText(result));
1005
+ },
1006
+ });
1007
+
1008
+ pi.registerCommand("team-autonomy", {
1009
+ description: "Show or toggle pi-crew autonomous delegation policy: status|on|off",
1010
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
1011
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
1012
+ const mode = tokens[0]?.toLowerCase();
1013
+ const config =
1014
+ mode === "on"
1015
+ ? {
1016
+ profile: "suggested",
1017
+ enabled: true,
1018
+ injectPolicy: true,
1019
+ }
1020
+ : mode === "off"
1021
+ ? { profile: "manual", enabled: false }
1022
+ : mode === "manual" || mode === "suggested" || mode === "assisted" || mode === "aggressive"
1023
+ ? {
1024
+ profile: mode,
1025
+ enabled: mode !== "manual",
1026
+ injectPolicy: mode !== "manual",
1027
+ }
1028
+ : {
1029
+ preferAsyncForLongTasks: tokens.includes("--prefer-async") ? true : undefined,
1030
+ allowWorktreeSuggestion: tokens.includes("--no-worktree-suggest") ? false : undefined,
1031
+ };
1032
+ const result = await handleTeamTool({ action: "autonomy", config }, teamCommandContext(ctx));
1033
+ await notifyCommandResult(ctx, commandText(result));
1034
+ },
1035
+ });
1036
+
1037
+ pi.registerCommand("team-config", {
1038
+ description: "Show or update pi-crew config. Use key=value [--project] to update.",
1039
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
1040
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
1041
+ if (tokens.length === 0) {
1042
+ const result = await handleTeamTool({ action: "config" }, teamCommandContext(ctx));
1043
+ await notifyCommandResult(ctx, commandText(result));
1044
+ return;
602
1045
  }
603
- if (!token.includes("=")) continue;
604
- const [key, ...rest] = token.split("=");
605
- if (!key) continue;
606
- const raw = rest.join("=");
607
- if (raw === "unset" || raw === "null") pushUnset(config, key);
608
- else setNestedConfig(config, key, parseScalar(raw));
609
- }
610
- const result = await handleTeamTool({ action: "config", config }, teamCommandContext(ctx));
611
- await notifyCommandResult(ctx, commandText(result));
612
- } });
1046
+ const config: Record<string, unknown> = {
1047
+ scope: tokens.includes("--project") ? "project" : "user",
1048
+ };
1049
+ for (const token of tokens) {
1050
+ if (token.startsWith("--unset=")) {
1051
+ pushUnset(config, token.slice("--unset=".length));
1052
+ continue;
1053
+ }
1054
+ if (!token.includes("=")) continue;
1055
+ const [key, ...rest] = token.split("=");
1056
+ if (!key) continue;
1057
+ const raw = rest.join("=");
1058
+ if (raw === "unset" || raw === "null") pushUnset(config, key);
1059
+ else setNestedConfig(config, key, parseScalar(raw));
1060
+ }
1061
+ const result = await handleTeamTool({ action: "config", config }, teamCommandContext(ctx));
1062
+ await notifyCommandResult(ctx, commandText(result));
1063
+ },
1064
+ });
613
1065
 
614
1066
  for (const [name, action, description] of [
615
1067
  ["team-validate", "validate", "Validate pi-crew agents, teams, and workflows"],
616
1068
  ["team-doctor", "doctor", "Check pi-crew installation and discovery readiness"],
617
- ] as const) pi.registerCommand(name, { description, handler: async (_args: string, ctx: ExtensionCommandContext) => {
618
- const result = await handleTeamTool({ action }, teamCommandContext(ctx));
619
- await notifyCommandResult(ctx, commandText(result));
620
- } });
621
-
622
- pi.registerCommand("skill-list", { description: "List available builtin skill templates. Use --json for machine-readable output.", handler: async (args: string, ctx: ExtensionCommandContext) => {
623
- const asJson = args.trim().split(/\s+/).includes("--json");
624
- const templates = listTemplates();
625
- if (asJson) {
626
- await notifyCommandResult(ctx, JSON.stringify(templates, null, 2));
627
- } else {
628
- const lines = ["Available builtin skill templates:", ""];
629
- for (const t of templates) {
630
- lines.push(` ${t.id.padEnd(20)} ${t.description}`);
631
- lines.push(` Variables: ${t.variables.map((v) => (v.required ? "[required] " : "[optional] ") + v.name).join(", ")}`);
1069
+ ] as const)
1070
+ pi.registerCommand(name, {
1071
+ description,
1072
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
1073
+ const result = await handleTeamTool({ action }, teamCommandContext(ctx));
1074
+ await notifyCommandResult(ctx, commandText(result));
1075
+ },
1076
+ });
1077
+
1078
+ pi.registerCommand("skill-list", {
1079
+ description: "List available builtin skill templates. Use --json for machine-readable output.",
1080
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
1081
+ const asJson = args.trim().split(/\s+/).includes("--json");
1082
+ const templates = listTemplates();
1083
+ if (asJson) {
1084
+ await notifyCommandResult(ctx, JSON.stringify(templates, null, 2));
1085
+ } else {
1086
+ const lines = ["Available builtin skill templates:", ""];
1087
+ for (const t of templates) {
1088
+ lines.push(` ${t.id.padEnd(20)} ${t.description}`);
1089
+ lines.push(
1090
+ ` Variables: ${t.variables.map((v) => (v.required ? "[required] " : "[optional] ") + v.name).join(", ")}`,
1091
+ );
1092
+ }
1093
+ lines.push("");
1094
+ lines.push("Create a skill: /skill-create <template-id> --var key=value [--var ...]");
1095
+ await notifyCommandResult(ctx, lines.join("\n"));
632
1096
  }
633
- lines.push("");
634
- lines.push("Create a skill: /skill-create <template-id> --var key=value [--var ...]");
635
- await notifyCommandResult(ctx, lines.join("\n"));
636
- }
637
- } });
638
-
639
- pi.registerCommand("skill-create", { description: "Create a skill from a builtin template: <template-id> [--var key=value...] [--project]", handler: async (args: string, ctx: ExtensionCommandContext) => {
640
- // LAZY: load withSessionId only when needed for skill-create command
641
- const { withSessionId } = await import("../team-tool/context.ts");
642
- const sessionId = withSessionId(ctx);
643
- const cwd = (ctx as unknown as { workspaceFolder?: { uri: { fsPath: string } } }).workspaceFolder?.uri?.fsPath ?? process.cwd();
644
- const tokens = args.trim().split(/\s+/).filter(Boolean);
645
- const useProject = tokens.includes("--project");
646
- const varEntries = tokens.filter((t) => t.startsWith("--var=") || t.startsWith("--var ")).map((t) => t.replace(/^--var(?:\s+|=)/, "").split("=", 2) as [string, string]);
647
- const templateId = tokens.find((t) => !t.startsWith("--") && !t.includes("="));
648
- if (!templateId) {
649
- await notifyCommandResult(ctx, "Usage: /skill-create <template-id> [--var key=value...] [--project]\nRun /skill-list to see available templates.");
650
- return;
651
- }
652
- const template = getBuiltinTemplates().find((t) => t.id === templateId);
653
- if (!template) {
654
- await notifyCommandResult(ctx, `Unknown template '${templateId}'. Run /skill-list to see available templates.`);
655
- return;
656
- }
657
- const variables: Record<string, string> = {};
658
- const errors: string[] = [];
659
- for (const v of template.variables) {
660
- const entry = varEntries.find(([k]) => k === v.name);
661
- if (!entry) {
662
- if (v.required) errors.push(`Missing required variable: ${v.name} (${v.description})`);
663
- else if (v.defaultValue !== undefined) variables[v.name] = v.defaultValue;
664
- continue;
1097
+ },
1098
+ });
1099
+
1100
+ pi.registerCommand("skill-create", {
1101
+ description: "Create a skill from a builtin template: <template-id> [--var key=value...] [--project]",
1102
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
1103
+ // LAZY: load withSessionId only when needed for skill-create command
1104
+ const { withSessionId } = await import("../team-tool/context.ts");
1105
+ const sessionId = withSessionId(ctx);
1106
+ const cwd =
1107
+ (
1108
+ ctx as unknown as {
1109
+ workspaceFolder?: { uri: { fsPath: string } };
1110
+ }
1111
+ ).workspaceFolder?.uri?.fsPath ?? process.cwd();
1112
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
1113
+ const useProject = tokens.includes("--project");
1114
+ const varEntries = tokens
1115
+ .filter((t) => t.startsWith("--var=") || t.startsWith("--var "))
1116
+ .map((t) => t.replace(/^--var(?:\s+|=)/, "").split("=", 2) as [string, string]);
1117
+ const templateId = tokens.find((t) => !t.startsWith("--") && !t.includes("="));
1118
+ if (!templateId) {
1119
+ await notifyCommandResult(
1120
+ ctx,
1121
+ "Usage: /skill-create <template-id> [--var key=value...] [--project]\nRun /skill-list to see available templates.",
1122
+ );
1123
+ return;
665
1124
  }
666
- const [, value] = entry;
667
- if (v.options && !v.options.includes(value)) {
668
- errors.push(`Invalid value '${value}' for '${v.name}'. Allowed: ${v.options.join(", ")}`);
669
- continue;
1125
+ const template = getBuiltinTemplates().find((t) => t.id === templateId);
1126
+ if (!template) {
1127
+ await notifyCommandResult(ctx, `Unknown template '${templateId}'. Run /skill-list to see available templates.`);
1128
+ return;
670
1129
  }
671
- variables[v.name] = value;
672
- }
673
- if (errors.length > 0) {
674
- await notifyCommandResult(ctx, errors.join("\n"));
675
- return;
676
- }
677
- let instantiated: { filename: string; content: string };
678
- try {
679
- instantiated = instantiateTemplate(template, variables);
680
- } catch (error) {
681
- await notifyCommandResult(ctx, error instanceof Error ? error.message : String(error));
682
- return;
683
- }
684
- const skillsDir = path.resolve(cwd, useProject ? "skills" : path.join(path.dirname(require.resolve("../../../package.json", { paths: [__dirname] })), "skills"));
685
- const skillDir = path.join(skillsDir, template.id);
686
- const skillPath = path.join(skillDir, "SKILL.md");
687
- try {
688
- fs.mkdirSync(skillDir, { recursive: true });
689
- fs.writeFileSync(skillPath, instantiated.content, "utf-8");
690
- await notifyCommandResult(ctx, `Created skill '${template.id}' at:\n${skillPath}\n\n${instantiated.content.slice(0, 200)}...`);
691
- } catch (error) {
692
- await notifyCommandResult(ctx, `Failed to write skill: ${error instanceof Error ? error.message : String(error)}`);
693
- }
694
- } });
1130
+ const variables: Record<string, string> = {};
1131
+ const errors: string[] = [];
1132
+ for (const v of template.variables) {
1133
+ const entry = varEntries.find(([k]) => k === v.name);
1134
+ if (!entry) {
1135
+ if (v.required) errors.push(`Missing required variable: ${v.name} (${v.description})`);
1136
+ else if (v.defaultValue !== undefined) variables[v.name] = v.defaultValue;
1137
+ continue;
1138
+ }
1139
+ const [, value] = entry;
1140
+ if (v.options && !v.options.includes(value)) {
1141
+ errors.push(`Invalid value '${value}' for '${v.name}'. Allowed: ${v.options.join(", ")}`);
1142
+ continue;
1143
+ }
1144
+ variables[v.name] = value;
1145
+ }
1146
+ if (errors.length > 0) {
1147
+ await notifyCommandResult(ctx, errors.join("\n"));
1148
+ return;
1149
+ }
1150
+ let instantiated: { filename: string; content: string };
1151
+ try {
1152
+ instantiated = instantiateTemplate(template, variables);
1153
+ } catch (error) {
1154
+ await notifyCommandResult(ctx, error instanceof Error ? error.message : String(error));
1155
+ return;
1156
+ }
1157
+ const skillsDir = path.resolve(
1158
+ cwd,
1159
+ useProject
1160
+ ? "skills"
1161
+ : path.join(
1162
+ path.dirname(
1163
+ require.resolve("../../../package.json", {
1164
+ paths: [__dirname],
1165
+ }),
1166
+ ),
1167
+ "skills",
1168
+ ),
1169
+ );
1170
+ const skillDir = path.join(skillsDir, template.id);
1171
+ const skillPath = path.join(skillDir, "SKILL.md");
1172
+ try {
1173
+ fs.mkdirSync(skillDir, { recursive: true });
1174
+ fs.writeFileSync(skillPath, instantiated.content, "utf-8");
1175
+ await notifyCommandResult(
1176
+ ctx,
1177
+ `Created skill '${template.id}' at:\n${skillPath}\n\n${instantiated.content.slice(0, 200)}...`,
1178
+ );
1179
+ } catch (error) {
1180
+ await notifyCommandResult(ctx, `Failed to write skill: ${error instanceof Error ? error.message : String(error)}`);
1181
+ }
1182
+ },
1183
+ });
695
1184
 
696
- pi.registerCommand("team-help", { description: "Show pi-crew command help", handler: async (_args: string, ctx: ExtensionCommandContext) => {
697
- await notifyCommandResult(ctx, piTeamsHelp());
698
- } });
1185
+ pi.registerCommand("team-help", {
1186
+ description: "Show pi-crew command help",
1187
+ handler: async (_args: string, ctx: ExtensionCommandContext) => {
1188
+ await notifyCommandResult(ctx, piTeamsHelp());
1189
+ },
1190
+ });
699
1191
 
700
1192
  // Brief mode command — toggle compact tool output display
701
1193
  pi.registerCommand("crew-brief", {
702
1194
  description: "Toggle brief tool output mode: on | off | status",
703
1195
  handler: async (args: string, ctx: ExtensionCommandContext) => {
704
- // LAZY: defer dynamic import of ../../ui/tool-renderers/brief-mode.ts to its call site.
1196
+ // 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.
705
1197
  const { isBrief, setBrief, BRIEF_ENTRY_TYPE, makeBriefEntry } = await import("../../ui/tool-renderers/brief-mode.ts");
706
1198
  const trimmed = args.trim();
707
1199