pi-crew 0.2.2 → 0.2.4

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 (354) hide show
  1. package/AGENTS.md +57 -32
  2. package/CHANGELOG.md +466 -413
  3. package/LICENSE +21 -21
  4. package/NOTICE.md +16 -16
  5. package/README.md +323 -323
  6. package/docs/FEATURE_INTAKE.md +126 -0
  7. package/docs/HARNESS.md +86 -0
  8. package/docs/HARNESS_BACKLOG.md +41 -0
  9. package/docs/TEST_MATRIX.md +49 -0
  10. package/docs/actions-reference.md +595 -595
  11. package/docs/architecture.md +180 -180
  12. package/docs/code-review-2026-05-11.md +592 -0
  13. package/docs/commands-reference.md +347 -347
  14. package/docs/comparison-pi-subagents-vs-pi-crew.md +303 -0
  15. package/docs/decisions/0001-durable-state.md +41 -0
  16. package/docs/decisions/0002-child-process-for-async.md +42 -0
  17. package/docs/decisions/0003-depth-guard.md +36 -0
  18. package/docs/decisions/0004-execfile-over-exec.md +34 -0
  19. package/docs/decisions/0005-no-parameter-properties.md +49 -0
  20. package/docs/decisions/0006-publish-bundled-esm.md +63 -0
  21. package/docs/decisions/0007-active-run-binary-index.md +54 -0
  22. package/docs/decisions/0008-child-pi-warm-pool.md +61 -0
  23. package/docs/decisions/README.md +23 -0
  24. package/docs/followup-plan-2026-05-12.md +463 -0
  25. package/docs/followup-review-2026-05-12.md +297 -0
  26. package/docs/followup-review-round3-2026-05-12.md +342 -0
  27. package/docs/followup-review-round4-2026-05-13.md +107 -0
  28. package/docs/implementation-plan-top3.md +333 -0
  29. package/docs/live-mailbox-runtime.md +36 -36
  30. package/docs/next-upgrade-roadmap.md +808 -808
  31. package/docs/oh-my-pi-research.md +509 -0
  32. package/docs/perf/baseline-2026-05.md +113 -0
  33. package/docs/perf/final-report-2026-05.md +206 -0
  34. package/docs/perf/sprint-1-report.md +71 -0
  35. package/docs/perf/sprint-2-report.md +81 -0
  36. package/docs/perf/sprint-2.5-report.md +53 -0
  37. package/docs/perf/sprint-3-report.md +36 -0
  38. package/docs/perf/sprint-4-report.md +47 -0
  39. package/docs/perf/sprint-5-report.md +51 -0
  40. package/docs/perf/sprint-6-report.md +94 -0
  41. package/docs/perf/sprint-7-report.md +74 -0
  42. package/docs/perf/upgrade-plan-2026-05.md +147 -0
  43. package/docs/pi-subagents3-deep-analysis.md +508 -0
  44. package/docs/product/README.md +31 -0
  45. package/docs/product/platform.md +27 -0
  46. package/docs/product/runtime-safety.md +37 -0
  47. package/docs/product/team-run.md +39 -0
  48. package/docs/product/team-tool.md +37 -0
  49. package/docs/publishing.md +65 -65
  50. package/docs/resource-formats.md +134 -134
  51. package/docs/runtime-analysis-child-vs-live.md +171 -0
  52. package/docs/runtime-flow.md +148 -148
  53. package/docs/runtime-migration-in-process-analysis.md +250 -0
  54. package/docs/stories/README.md +30 -0
  55. package/docs/stories/backlog.md +36 -0
  56. package/docs/templates/decision.md +27 -0
  57. package/docs/templates/story.md +44 -0
  58. package/docs/templates/validation-report.md +32 -0
  59. package/docs/usage.md +238 -238
  60. package/index.ts +7 -6
  61. package/install.mjs +65 -65
  62. package/package.json +107 -99
  63. package/schema.json +222 -222
  64. package/skills/child-pi-spawning/SKILL.md +213 -0
  65. package/skills/context-artifact-hygiene/SKILL.md +32 -0
  66. package/skills/event-log-tracing/SKILL.md +299 -0
  67. package/skills/git-master/SKILL.md +225 -24
  68. package/skills/live-agent-lifecycle/SKILL.md +192 -0
  69. package/skills/mailbox-interactive/SKILL.md +300 -19
  70. package/skills/model-routing-context/SKILL.md +94 -0
  71. package/skills/multi-perspective-review/SKILL.md +88 -0
  72. package/skills/read-only-explorer/SKILL.md +250 -26
  73. package/skills/safe-bash/SKILL.md +307 -21
  74. package/skills/verification-before-done/SKILL.md +11 -2
  75. package/skills/widget-rendering/SKILL.md +258 -0
  76. package/skills/workspace-isolation/SKILL.md +202 -0
  77. package/skills/worktree-isolation/SKILL.md +202 -18
  78. package/src/adapters/claude-adapter.ts +25 -25
  79. package/src/adapters/codex-adapter.ts +21 -21
  80. package/src/adapters/cursor-adapter.ts +17 -17
  81. package/src/adapters/export-util.ts +137 -137
  82. package/src/adapters/index.ts +15 -15
  83. package/src/adapters/registry.ts +18 -18
  84. package/src/adapters/types.ts +23 -23
  85. package/src/agents/agent-config.ts +38 -38
  86. package/src/agents/agent-serializer.ts +38 -38
  87. package/src/agents/discover-agents.ts +121 -118
  88. package/src/config/config.ts +740 -858
  89. package/src/config/defaults.ts +96 -96
  90. package/src/config/drift-detector.ts +211 -211
  91. package/src/config/markers.ts +327 -327
  92. package/src/config/resilient-parser.ts +109 -108
  93. package/src/config/suggestions.ts +74 -74
  94. package/src/config/types.ts +199 -0
  95. package/src/extension/async-notifier.ts +123 -89
  96. package/src/extension/autonomous-policy.ts +169 -169
  97. package/src/extension/cross-extension-rpc.ts +104 -103
  98. package/src/extension/help.ts +47 -47
  99. package/src/extension/import-index.ts +69 -69
  100. package/src/extension/management.ts +395 -382
  101. package/src/extension/notification-router.ts +116 -116
  102. package/src/extension/notification-sink.ts +51 -51
  103. package/src/extension/project-init.ts +168 -168
  104. package/src/extension/register.ts +859 -668
  105. package/src/extension/registration/artifact-cleanup.ts +15 -15
  106. package/src/extension/registration/command-utils.ts +54 -54
  107. package/src/extension/registration/commands.ts +559 -452
  108. package/src/extension/registration/compaction-guard.ts +125 -125
  109. package/src/extension/registration/subagent-helpers.ts +102 -102
  110. package/src/extension/registration/subagent-tools.ts +220 -158
  111. package/src/extension/registration/team-tool.ts +159 -98
  112. package/src/extension/registration/viewers.ts +29 -0
  113. package/src/extension/result-watcher.ts +128 -128
  114. package/src/extension/run-bundle-schema.ts +89 -89
  115. package/src/extension/run-export.ts +73 -73
  116. package/src/extension/run-import.ts +84 -84
  117. package/src/extension/run-index.ts +94 -94
  118. package/src/extension/run-maintenance.ts +142 -142
  119. package/src/extension/session-summary.ts +8 -8
  120. package/src/extension/team-manager-command.ts +96 -95
  121. package/src/extension/team-recommendation.ts +188 -188
  122. package/src/extension/team-tool/api.ts +5 -2
  123. package/src/extension/team-tool/cancel.ts +224 -209
  124. package/src/extension/team-tool/config-patch.ts +36 -36
  125. package/src/extension/team-tool/context.ts +60 -60
  126. package/src/extension/team-tool/doctor.ts +242 -242
  127. package/src/extension/team-tool/handle-settings.ts +421 -195
  128. package/src/extension/team-tool/inspect.ts +41 -41
  129. package/src/extension/team-tool/lifecycle-actions.ts +139 -139
  130. package/src/extension/team-tool/parallel-dispatch.ts +156 -156
  131. package/src/extension/team-tool/plan.ts +19 -19
  132. package/src/extension/team-tool/respond.ts +112 -111
  133. package/src/extension/team-tool/run.ts +246 -228
  134. package/src/extension/team-tool/status.ts +110 -110
  135. package/src/extension/team-tool-types.ts +13 -13
  136. package/src/extension/team-tool.ts +16 -4
  137. package/src/extension/tool-result.ts +16 -16
  138. package/src/extension/validate-resources.ts +77 -77
  139. package/src/hooks/registry.ts +61 -61
  140. package/src/hooks/types.ts +40 -40
  141. package/src/i18n.ts +184 -184
  142. package/src/observability/correlation.ts +35 -35
  143. package/src/observability/event-to-metric.ts +68 -68
  144. package/src/observability/exporters/adapter.ts +30 -30
  145. package/src/observability/exporters/otlp-exporter.ts +106 -92
  146. package/src/observability/exporters/prometheus-exporter.ts +54 -54
  147. package/src/observability/metric-registry.ts +87 -87
  148. package/src/observability/metric-retention.ts +54 -54
  149. package/src/observability/metric-sink.ts +81 -56
  150. package/src/observability/metrics-primitives.ts +167 -167
  151. package/src/prompt/prompt-runtime.ts +72 -72
  152. package/src/runtime/adaptive-plan.ts +338 -0
  153. package/src/runtime/agent-control.ts +169 -169
  154. package/src/runtime/agent-memory.ts +72 -72
  155. package/src/runtime/agent-observability.ts +114 -114
  156. package/src/runtime/async-marker.ts +26 -26
  157. package/src/runtime/async-runner.ts +153 -79
  158. package/src/runtime/attention-events.ts +28 -28
  159. package/src/runtime/auto-resume.ts +100 -100
  160. package/src/runtime/background-runner.ts +122 -88
  161. package/src/runtime/cancellation.ts +61 -61
  162. package/src/runtime/capability-inventory.ts +116 -116
  163. package/src/runtime/child-pi-pool.ts +68 -0
  164. package/src/runtime/child-pi.ts +541 -463
  165. package/src/runtime/code-summary.ts +247 -247
  166. package/src/runtime/compaction-summary.ts +271 -271
  167. package/src/runtime/concurrency.ts +58 -58
  168. package/src/runtime/crash-recovery.ts +317 -301
  169. package/src/runtime/crew-agent-records.ts +379 -281
  170. package/src/runtime/crew-agent-runtime.ts +60 -60
  171. package/src/runtime/cross-extension-rpc.ts +72 -0
  172. package/src/runtime/custom-tools/irc-tool.ts +201 -201
  173. package/src/runtime/custom-tools/submit-result-tool.ts +90 -90
  174. package/src/runtime/deadletter.ts +47 -47
  175. package/src/runtime/delivery-coordinator.ts +176 -176
  176. package/src/runtime/delta-conflict.ts +360 -360
  177. package/src/runtime/diagnostic-export.ts +102 -102
  178. package/src/runtime/direct-run.ts +35 -35
  179. package/src/runtime/effectiveness.ts +82 -81
  180. package/src/runtime/errors/crew-errors.ts +166 -0
  181. package/src/runtime/event-stream-bridge.ts +92 -92
  182. package/src/runtime/foreground-control.ts +82 -82
  183. package/src/runtime/green-contract.ts +46 -46
  184. package/src/runtime/group-join.ts +234 -106
  185. package/src/runtime/heartbeat-watcher.ts +145 -124
  186. package/src/runtime/iteration-hooks.ts +267 -264
  187. package/src/runtime/live-agent-control.ts +88 -88
  188. package/src/runtime/live-agent-manager.ts +377 -179
  189. package/src/runtime/live-control-realtime.ts +36 -36
  190. package/src/runtime/live-session-runtime.ts +676 -599
  191. package/src/runtime/loop-gates.ts +129 -129
  192. package/src/runtime/manifest-cache.ts +263 -263
  193. package/src/runtime/mcp-proxy.ts +113 -113
  194. package/src/runtime/metric-parser.ts +40 -40
  195. package/src/runtime/model-fallback.ts +282 -274
  196. package/src/runtime/model-resolver.ts +118 -0
  197. package/src/runtime/output-validator.ts +187 -187
  198. package/src/runtime/overflow-recovery.ts +175 -175
  199. package/src/runtime/parallel-research.ts +44 -44
  200. package/src/runtime/parallel-utils.ts +156 -156
  201. package/src/runtime/parent-guard.ts +80 -80
  202. package/src/runtime/phase-progress.ts +217 -217
  203. package/src/runtime/pi-args.ts +165 -165
  204. package/src/runtime/pi-json-output.ts +111 -111
  205. package/src/runtime/pi-spawn.ts +167 -167
  206. package/src/runtime/policy-engine.ts +79 -79
  207. package/src/runtime/post-checks.ts +125 -122
  208. package/src/runtime/post-exit-stdio-guard.ts +86 -86
  209. package/src/runtime/process-status.ts +97 -73
  210. package/src/runtime/progress-event-coalescer.ts +43 -43
  211. package/src/runtime/recovery-recipes.ts +74 -74
  212. package/src/runtime/retry-executor.ts +81 -81
  213. package/src/runtime/role-permission.ts +39 -39
  214. package/src/runtime/run-tracker.ts +99 -0
  215. package/src/runtime/runtime-policy.ts +21 -0
  216. package/src/runtime/runtime-resolver.ts +94 -90
  217. package/src/runtime/scheduler.ts +294 -0
  218. package/src/runtime/semaphore.ts +131 -131
  219. package/src/runtime/sensitive-paths.ts +92 -92
  220. package/src/runtime/session-usage.ts +79 -79
  221. package/src/runtime/settings-store.ts +103 -0
  222. package/src/runtime/sidechain-output.ts +29 -29
  223. package/src/runtime/skill-instructions.ts +222 -222
  224. package/src/runtime/stale-reconciler.ts +198 -189
  225. package/src/runtime/streaming-output.ts +47 -0
  226. package/src/runtime/subagent-manager.ts +404 -395
  227. package/src/runtime/subprocess-tool-registry.ts +67 -67
  228. package/src/runtime/task-display.ts +38 -38
  229. package/src/runtime/task-graph-scheduler.ts +122 -122
  230. package/src/runtime/task-graph.ts +207 -207
  231. package/src/runtime/task-output-context.ts +177 -177
  232. package/src/runtime/task-packet.ts +93 -93
  233. package/src/runtime/task-quality.ts +207 -207
  234. package/src/runtime/task-runner/capabilities.ts +78 -78
  235. package/src/runtime/task-runner/live-executor.ts +131 -113
  236. package/src/runtime/task-runner/progress.ts +119 -119
  237. package/src/runtime/task-runner/prompt-builder.ts +139 -139
  238. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  239. package/src/runtime/task-runner/result-utils.ts +14 -14
  240. package/src/runtime/task-runner/run-projection.ts +103 -103
  241. package/src/runtime/task-runner/state-helpers.ts +22 -22
  242. package/src/runtime/task-runner.ts +469 -458
  243. package/src/runtime/team-runner.ts +693 -945
  244. package/src/runtime/usage-tracker.ts +71 -0
  245. package/src/runtime/worker-heartbeat.ts +21 -21
  246. package/src/runtime/worker-startup.ts +57 -57
  247. package/src/runtime/workflow-state.ts +187 -187
  248. package/src/runtime/yield-handler.ts +190 -189
  249. package/src/schema/config-schema.ts +172 -168
  250. package/src/schema/team-tool-schema.ts +126 -125
  251. package/src/schema/validation-types.ts +151 -148
  252. package/src/skills/discover-skills.ts +67 -67
  253. package/src/skills/skill-templates.ts +374 -374
  254. package/src/state/active-run-registry.ts +227 -191
  255. package/src/state/artifact-store.ts +130 -129
  256. package/src/state/atomic-write.ts +262 -178
  257. package/src/state/blob-store.ts +116 -116
  258. package/src/state/contracts.ts +111 -111
  259. package/src/state/event-log-rotation.ts +161 -158
  260. package/src/state/event-log.ts +383 -240
  261. package/src/state/event-reconstructor.ts +217 -217
  262. package/src/state/jsonl-writer.ts +82 -82
  263. package/src/state/locks.ts +146 -148
  264. package/src/state/mailbox.ts +446 -405
  265. package/src/state/state-store.ts +364 -351
  266. package/src/state/task-claims.ts +44 -44
  267. package/src/state/types.ts +285 -285
  268. package/src/state/usage.ts +29 -29
  269. package/src/subagents/async-entry.ts +1 -1
  270. package/src/subagents/index.ts +3 -3
  271. package/src/subagents/live/control.ts +1 -1
  272. package/src/subagents/live/manager.ts +1 -1
  273. package/src/subagents/live/realtime.ts +1 -1
  274. package/src/subagents/live/session-runtime.ts +1 -1
  275. package/src/subagents/manager.ts +1 -1
  276. package/src/subagents/spawn.ts +1 -1
  277. package/src/teams/discover-teams.ts +116 -116
  278. package/src/teams/team-config.ts +27 -27
  279. package/src/teams/team-serializer.ts +38 -38
  280. package/src/types/diff.d.ts +18 -18
  281. package/src/ui/agent-management-overlay.ts +144 -144
  282. package/src/ui/crew-widget.ts +487 -370
  283. package/src/ui/dashboard-panes/agents-pane.ts +109 -28
  284. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  285. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  286. package/src/ui/dashboard-panes/health-pane.ts +30 -30
  287. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  288. package/src/ui/dashboard-panes/progress-pane.ts +30 -30
  289. package/src/ui/dashboard-panes/transcript-pane.ts +10 -10
  290. package/src/ui/heartbeat-aggregator.ts +63 -63
  291. package/src/ui/keybinding-map.ts +97 -94
  292. package/src/ui/live-conversation-overlay.ts +152 -0
  293. package/src/ui/live-run-sidebar.ts +180 -180
  294. package/src/ui/mascot.ts +442 -442
  295. package/src/ui/overlays/agent-picker-overlay.ts +57 -57
  296. package/src/ui/overlays/confirm-overlay.ts +58 -58
  297. package/src/ui/overlays/mailbox-compose-overlay.ts +144 -144
  298. package/src/ui/overlays/mailbox-compose-preview.ts +63 -63
  299. package/src/ui/overlays/mailbox-detail-overlay.ts +122 -122
  300. package/src/ui/pi-ui-compat.ts +57 -57
  301. package/src/ui/powerbar-publisher.ts +221 -197
  302. package/src/ui/render-scheduler.ts +216 -143
  303. package/src/ui/run-action-dispatcher.ts +118 -117
  304. package/src/ui/run-dashboard.ts +526 -464
  305. package/src/ui/run-event-bus.ts +208 -208
  306. package/src/ui/run-snapshot-cache.ts +826 -777
  307. package/src/ui/settings-overlay.ts +721 -0
  308. package/src/ui/snapshot-types.ts +86 -70
  309. package/src/ui/theme-adapter.ts +190 -190
  310. package/src/ui/tool-progress-formatter.ts +89 -0
  311. package/src/ui/transcript-cache.ts +94 -94
  312. package/src/ui/transcript-viewer.ts +335 -335
  313. package/src/utils/conflict-detect.ts +662 -0
  314. package/src/utils/env-filter.ts +30 -0
  315. package/src/utils/file-coalescer.ts +86 -86
  316. package/src/utils/frontmatter.ts +68 -68
  317. package/src/utils/fs-watch.ts +88 -31
  318. package/src/utils/gh-protocol.ts +479 -0
  319. package/src/utils/ids.ts +17 -17
  320. package/src/utils/incremental-reader.ts +104 -104
  321. package/src/utils/internal-error.ts +6 -6
  322. package/src/utils/names.ts +27 -27
  323. package/src/utils/paths.ts +102 -63
  324. package/src/utils/redaction.ts +44 -44
  325. package/src/utils/resolve-shell.ts +34 -0
  326. package/src/utils/safe-paths.ts +47 -47
  327. package/src/utils/scan-cache.ts +136 -136
  328. package/src/utils/sleep.ts +2 -1
  329. package/src/utils/sse-parser.ts +134 -134
  330. package/src/utils/task-name-generator.ts +337 -337
  331. package/src/utils/timings.ts +33 -33
  332. package/src/utils/visual.ts +243 -198
  333. package/src/workflows/discover-workflows.ts +139 -139
  334. package/src/workflows/validate-workflow.ts +40 -40
  335. package/src/workflows/workflow-config.ts +26 -26
  336. package/src/workflows/workflow-serializer.ts +32 -32
  337. package/src/worktree/branch-freshness.ts +45 -45
  338. package/src/worktree/cleanup.ts +75 -72
  339. package/src/worktree/worktree-manager.ts +188 -146
  340. package/teams/default.team.md +12 -12
  341. package/teams/fast-fix.team.md +11 -11
  342. package/teams/implementation.team.md +18 -18
  343. package/teams/parallel-research.team.md +14 -14
  344. package/teams/research.team.md +11 -11
  345. package/teams/review.team.md +12 -12
  346. package/tsconfig.json +19 -19
  347. package/workflows/default.workflow.md +30 -30
  348. package/workflows/fast-fix.workflow.md +23 -23
  349. package/workflows/implementation.workflow.md +43 -43
  350. package/workflows/parallel-research.workflow.md +46 -46
  351. package/workflows/research.workflow.md +22 -22
  352. package/workflows/review.workflow.md +30 -30
  353. package/skills/task-packet/SKILL.md +0 -28
  354. package/skills/verify-evidence/SKILL.md +0 -27
@@ -1,95 +1,96 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import { listRuns } from "./run-index.ts";
3
- // Lazy-loaded: team-tool.ts pulls in entire runtime chain.
4
- import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
5
- let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
6
- async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
7
- if (!_cachedHandleTeamTool) {
8
- const mod = await import("./team-tool.ts");
9
- _cachedHandleTeamTool = mod.handleTeamTool;
10
- }
11
- return _cachedHandleTeamTool(params, ctx);
12
- }
13
- import { isToolError, textFromToolResult } from "./tool-result.ts";
14
-
15
- async function notifyResult(ctx: ExtensionCommandContext, result: Awaited<ReturnType<typeof handleTeamTool>>): Promise<void> {
16
- const text = textFromToolResult(result);
17
- ctx.ui.notify(text.length > 1000 ? `${text.slice(0, 997)}...` : text, isToolError(result) ? "error" : "info");
18
- }
19
-
20
- export async function handleTeamManagerCommand(_args: string, ctx: ExtensionCommandContext): Promise<void> {
21
- const action = await ctx.ui.select("pi-crew", [
22
- "List teams/workflows/agents/runs",
23
- "Run team",
24
- "Show run status",
25
- "Cleanup run worktrees",
26
- "Create routed resource",
27
- "Update routed resource",
28
- "Doctor",
29
- ]);
30
- if (!action) return;
31
-
32
- if (action.startsWith("List")) {
33
- await notifyResult(ctx, await handleTeamTool({ action: "list" }, ctx));
34
- return;
35
- }
36
-
37
- if (action === "Doctor") {
38
- await notifyResult(ctx, await handleTeamTool({ action: "doctor" }, ctx));
39
- return;
40
- }
41
-
42
- if (action === "Create routed resource" || action === "Update routed resource") {
43
- const isUpdate = action === "Update routed resource";
44
- const resource = await ctx.ui.select("Resource type", ["agent", "team"]);
45
- if (resource !== "agent" && resource !== "team") return;
46
- const name = await ctx.ui.input("Name", resource === "agent" ? "custom-agent" : "custom-team");
47
- if (!name) return;
48
- const description = await ctx.ui.input("Description", "When to use this resource");
49
- if (!description) return;
50
- const triggers = await ctx.ui.input("Triggers (comma-separated)", "");
51
- const useWhen = await ctx.ui.input("Use when (comma-separated)", "");
52
- const avoidWhen = await ctx.ui.input("Avoid when (comma-separated)", "");
53
- const cost = await ctx.ui.select("Cost", ["cheap", "free", "expensive"]);
54
- const category = await ctx.ui.input("Category", "custom");
55
- const baseConfig = { name, description, scope: "project", triggers, useWhen, avoidWhen, cost, category };
56
- if (resource === "agent") {
57
- const systemPrompt = isUpdate ? undefined : `You are ${name}.`;
58
- await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, agent: name, config: { ...baseConfig, systemPrompt } }, ctx));
59
- return;
60
- }
61
- const agent = await ctx.ui.input("Role agent", "executor");
62
- await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, team: name, config: { ...baseConfig, roles: [{ name: "executor", agent: agent || "executor" }] } }, ctx));
63
- return;
64
- }
65
-
66
- if (action === "Run team") {
67
- const team = await ctx.ui.input("Team name", "default");
68
- if (team === undefined) return;
69
- const goal = await ctx.ui.input("Goal", "Describe the team objective");
70
- if (!goal) return;
71
- const asyncRun = await ctx.ui.confirm("Async run?", "Run in detached background mode?");
72
- const worktree = await ctx.ui.confirm("Worktree mode?", "Use git worktrees for task workspaces? Requires a clean repo by default.");
73
- await notifyResult(ctx, await handleTeamTool({ action: "run", team: team || "default", goal, async: asyncRun, workspaceMode: worktree ? "worktree" : "single" }, ctx));
74
- return;
75
- }
76
-
77
- const runs = listRuns(ctx.cwd).slice(0, 20);
78
- if (runs.length === 0) {
79
- ctx.ui.notify("No pi-crew runs found.", "info");
80
- return;
81
- }
82
- const selected = await ctx.ui.select("Select run", runs.map((run) => `${run.runId} [${run.status}] ${run.team}/${run.workflow ?? "none"}`));
83
- if (!selected) return;
84
- const runId = selected.split(" ")[0];
85
- if (!runId) return;
86
-
87
- if (action === "Show run status") {
88
- await notifyResult(ctx, await handleTeamTool({ action: "status", runId }, ctx));
89
- return;
90
- }
91
- if (action === "Cleanup run worktrees") {
92
- const force = await ctx.ui.confirm("Force cleanup?", "Force may remove dirty worktrees. Choose false to preserve dirty worktrees and capture cleanup diffs.");
93
- await notifyResult(ctx, await handleTeamTool({ action: "cleanup", runId, force }, ctx));
94
- }
95
- }
1
+ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
+ import { listRuns } from "./run-index.ts";
3
+ // Lazy-loaded: team-tool.ts pulls in entire runtime chain.
4
+ import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
5
+ let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
6
+ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
7
+ if (!_cachedHandleTeamTool) {
8
+ // LAZY: team-tool.ts pulls in the entire runtime chain.
9
+ const mod = await import("./team-tool.ts");
10
+ _cachedHandleTeamTool = mod.handleTeamTool;
11
+ }
12
+ return _cachedHandleTeamTool(params, ctx);
13
+ }
14
+ import { isToolError, textFromToolResult } from "./tool-result.ts";
15
+
16
+ async function notifyResult(ctx: ExtensionCommandContext, result: Awaited<ReturnType<typeof handleTeamTool>>): Promise<void> {
17
+ const text = textFromToolResult(result);
18
+ ctx.ui.notify(text.length > 1000 ? `${text.slice(0, 997)}...` : text, isToolError(result) ? "error" : "info");
19
+ }
20
+
21
+ export async function handleTeamManagerCommand(_args: string, ctx: ExtensionCommandContext): Promise<void> {
22
+ const action = await ctx.ui.select("pi-crew", [
23
+ "List teams/workflows/agents/runs",
24
+ "Run team",
25
+ "Show run status",
26
+ "Cleanup run worktrees",
27
+ "Create routed resource",
28
+ "Update routed resource",
29
+ "Doctor",
30
+ ]);
31
+ if (!action) return;
32
+
33
+ if (action.startsWith("List")) {
34
+ await notifyResult(ctx, await handleTeamTool({ action: "list" }, ctx));
35
+ return;
36
+ }
37
+
38
+ if (action === "Doctor") {
39
+ await notifyResult(ctx, await handleTeamTool({ action: "doctor" }, ctx));
40
+ return;
41
+ }
42
+
43
+ if (action === "Create routed resource" || action === "Update routed resource") {
44
+ const isUpdate = action === "Update routed resource";
45
+ const resource = await ctx.ui.select("Resource type", ["agent", "team"]);
46
+ if (resource !== "agent" && resource !== "team") return;
47
+ const name = await ctx.ui.input("Name", resource === "agent" ? "custom-agent" : "custom-team");
48
+ if (!name) return;
49
+ const description = await ctx.ui.input("Description", "When to use this resource");
50
+ if (!description) return;
51
+ const triggers = await ctx.ui.input("Triggers (comma-separated)", "");
52
+ const useWhen = await ctx.ui.input("Use when (comma-separated)", "");
53
+ const avoidWhen = await ctx.ui.input("Avoid when (comma-separated)", "");
54
+ const cost = await ctx.ui.select("Cost", ["cheap", "free", "expensive"]);
55
+ const category = await ctx.ui.input("Category", "custom");
56
+ const baseConfig = { name, description, scope: "project", triggers, useWhen, avoidWhen, cost, category };
57
+ if (resource === "agent") {
58
+ const systemPrompt = isUpdate ? undefined : `You are ${name}.`;
59
+ await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, agent: name, config: { ...baseConfig, systemPrompt } }, ctx));
60
+ return;
61
+ }
62
+ const agent = await ctx.ui.input("Role agent", "executor");
63
+ await notifyResult(ctx, await handleTeamTool({ action: isUpdate ? "update" : "create", resource, team: name, config: { ...baseConfig, roles: [{ name: "executor", agent: agent || "executor" }] } }, ctx));
64
+ return;
65
+ }
66
+
67
+ if (action === "Run team") {
68
+ const team = await ctx.ui.input("Team name", "default");
69
+ if (team === undefined) return;
70
+ const goal = await ctx.ui.input("Goal", "Describe the team objective");
71
+ if (!goal) return;
72
+ const asyncRun = await ctx.ui.confirm("Async run?", "Run in detached background mode?");
73
+ const worktree = await ctx.ui.confirm("Worktree mode?", "Use git worktrees for task workspaces? Requires a clean repo by default.");
74
+ await notifyResult(ctx, await handleTeamTool({ action: "run", team: team || "default", goal, async: asyncRun, workspaceMode: worktree ? "worktree" : "single" }, ctx));
75
+ return;
76
+ }
77
+
78
+ const runs = listRuns(ctx.cwd).slice(0, 20);
79
+ if (runs.length === 0) {
80
+ ctx.ui.notify("No pi-crew runs found.", "info");
81
+ return;
82
+ }
83
+ const selected = await ctx.ui.select("Select run", runs.map((run) => `${run.runId} [${run.status}] ${run.team}/${run.workflow ?? "none"}`));
84
+ if (!selected) return;
85
+ const runId = selected.split(" ")[0];
86
+ if (!runId) return;
87
+
88
+ if (action === "Show run status") {
89
+ await notifyResult(ctx, await handleTeamTool({ action: "status", runId }, ctx));
90
+ return;
91
+ }
92
+ if (action === "Cleanup run worktrees") {
93
+ const force = await ctx.ui.confirm("Force cleanup?", "Force may remove dirty worktrees. Choose false to preserve dirty worktrees and capture cleanup diffs.");
94
+ await notifyResult(ctx, await handleTeamTool({ action: "cleanup", runId, force }, ctx));
95
+ }
96
+ }
@@ -1,188 +1,188 @@
1
- import { detectTeamIntent } from "./autonomous-policy.ts";
2
- import type { AgentConfig } from "../agents/agent-config.ts";
3
- import type { TeamConfig } from "../teams/team-config.ts";
4
- import type { PiTeamsAutonomousConfig } from "../config/config.ts";
5
-
6
- export type DecompositionStrategy = "numbered" | "bulleted" | "conjunction" | "atomic";
7
-
8
- export interface RecommendedSubtask {
9
- subject: string;
10
- description: string;
11
- role: string;
12
- }
13
-
14
- export interface TeamRecommendation {
15
- team: string;
16
- workflow: string;
17
- action: "plan" | "run";
18
- async: boolean;
19
- workspaceMode: "single" | "worktree";
20
- confidence: "low" | "medium" | "high";
21
- decomposition: { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number };
22
- reasons: string[];
23
- }
24
-
25
- const REVIEW_TERMS = ["review", "audit", "security", "vulnerability", "diff", "pr", "pull request"];
26
- const RESEARCH_TERMS = ["research", "investigate", "compare", "analyze", "document", "docs", "explain", "architecture", "đọc sâu", "source", "projects"];
27
- const PARALLEL_RESEARCH_RE = /(?:đọc sâu|deep read|deep research|source audit|multiple projects|các project|pi-\*|source\/|@source)/i;
28
- const FAST_FIX_TERMS = ["quick fix", "fast-fix", "small bug", "typo", "one-line", "minor", "lint"];
29
- const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add", "fix", "update", "sửa", "thêm", "cập nhật", "kiểm thử"];
30
- const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical", "nhiều file", "nhiều task"];
31
- const NUMBERED_LINE_RE = /^\s*\d+[.)]\s+(.+)$/;
32
- const BULLETED_LINE_RE = /^\s*[-*•]\s+(.+)$/;
33
- const CONJUNCTION_SPLIT_RE = /\s+(?:and|,\s*and|,)\s+/i;
34
- const FILE_REF_RE = /\b\S+\.\w{1,8}\b/g;
35
- const CODE_SYMBOL_RE = /`[^`]+`/g;
36
-
37
- function includesAny(text: string, terms: string[]): string[] {
38
- return terms.filter((term) => text.includes(term));
39
- }
40
-
41
- function wordCount(text: string): number {
42
- return text.trim().split(/\s+/).filter(Boolean).length;
43
- }
44
-
45
- function recommendRole(text: string): string {
46
- const lower = text.toLowerCase();
47
- if (includesAny(lower, ["test", "spec", "coverage", "verify"]).length > 0) return "test-engineer";
48
- if (includesAny(lower, ["security", "vulnerability", "auth", "owasp"]).length > 0) return "security-reviewer";
49
- if (includesAny(lower, ["review", "audit", "diff"]).length > 0) return "reviewer";
50
- if (includesAny(lower, ["doc", "readme", "guide", "write"]).length > 0) return "writer";
51
- if (includesAny(lower, ["research", "investigate", "explore", "find", "trace"]).length > 0) return "explorer";
52
- if (includesAny(lower, ["plan", "design", "architecture"]).length > 0) return "planner";
53
- return "executor";
54
- }
55
-
56
- function makeSubtask(text: string): RecommendedSubtask {
57
- const subject = text.trim().slice(0, 80) || "Task";
58
- return { subject, description: text.trim(), role: recommendRole(text) };
59
- }
60
-
61
- export function decomposeGoal(goal: string): { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number } {
62
- const lines = goal.split("\n").map((line) => line.trim()).filter(Boolean);
63
- const fileRefs = goal.match(FILE_REF_RE)?.length ?? 0;
64
- const codeSymbols = goal.match(CODE_SYMBOL_RE)?.length ?? 0;
65
- const hasParallelKeyword = /\b(?:parallel|concurrently|simultaneously|independently)\b/i.test(goal);
66
- if (fileRefs >= 3 || codeSymbols >= 3 || hasParallelKeyword) {
67
- const subtask = makeSubtask(goal);
68
- return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
69
- }
70
- const numberedLines = lines.map((line) => line.match(NUMBERED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
71
- if (numberedLines.length >= 2 && numberedLines.length >= lines.length - 1) {
72
- const subtasks = numberedLines.map((line) => makeSubtask(line));
73
- return { strategy: "numbered", subtasks, fanout: subtasks.length };
74
- }
75
- const bulletedLines = lines.map((line) => line.match(BULLETED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
76
- if (bulletedLines.length >= 2 && bulletedLines.length >= lines.length - 1) {
77
- const subtasks = bulletedLines.map((line) => makeSubtask(line));
78
- return { strategy: "bulleted", subtasks, fanout: subtasks.length };
79
- }
80
- if (lines.length === 1) {
81
- const parts = lines[0].split(CONJUNCTION_SPLIT_RE).map((part) => part.trim()).filter(Boolean);
82
- if (parts.length >= 2) {
83
- const subtasks = parts.map((part) => makeSubtask(part));
84
- return { strategy: "conjunction", subtasks, fanout: subtasks.length };
85
- }
86
- }
87
- const subtask = makeSubtask(goal);
88
- return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
89
- }
90
-
91
- function metadataMatches(goal: string, values: string[] | undefined): string[] {
92
- const lower = goal.toLowerCase();
93
- return (values ?? []).filter((value) => lower.includes(value.toLowerCase()));
94
- }
95
-
96
- export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}, resources?: { teams?: TeamConfig[]; agents?: AgentConfig[] }): TeamRecommendation {
97
- const normalized = goal.toLowerCase();
98
- const intents = detectTeamIntent(goal, config);
99
- const decomposition = decomposeGoal(goal);
100
- const reasons: string[] = [];
101
- let team: TeamRecommendation["team"] = "default";
102
- let workflow: TeamRecommendation["workflow"] = "default";
103
- let action: TeamRecommendation["action"] = "run";
104
- let confidence: TeamRecommendation["confidence"] = "medium";
105
-
106
- if (intents.length > 0) reasons.push(`Matched explicit intent keyword(s): ${intents.join(", ")}.`);
107
-
108
- const metadataTeamMatches = (resources?.teams ?? [])
109
- .map((candidate) => ({ team: candidate, matches: [...metadataMatches(goal, candidate.routing?.triggers), ...metadataMatches(goal, candidate.routing?.useWhen)] }))
110
- .filter((candidate) => candidate.matches.length > 0)
111
- .sort((a, b) => b.matches.length - a.matches.length);
112
-
113
- const reviewMatches = includesAny(normalized, REVIEW_TERMS);
114
- const researchMatches = includesAny(normalized, RESEARCH_TERMS);
115
- const fastFixMatches = includesAny(normalized, FAST_FIX_TERMS);
116
- const implementationMatches = includesAny(normalized, IMPLEMENTATION_TERMS);
117
- const riskyMatches = includesAny(normalized, RISKY_TERMS);
118
-
119
- if (metadataTeamMatches[0]) {
120
- team = metadataTeamMatches[0].team.name as TeamRecommendation["team"];
121
- workflow = (metadataTeamMatches[0].team.defaultWorkflow ?? metadataTeamMatches[0].team.name) as TeamRecommendation["workflow"];
122
- confidence = "high";
123
- reasons.push(`Matched team routing metadata for '${metadataTeamMatches[0].team.name}': ${metadataTeamMatches[0].matches.join(", ")}.`);
124
- } else if (intents.includes("review") || reviewMatches.length >= 2 || normalized.includes("security review")) {
125
- team = "review";
126
- workflow = "review";
127
- confidence = "high";
128
- reasons.push(`Review/audit terms detected: ${reviewMatches.join(", ") || "explicit review intent"}.`);
129
- } else if (PARALLEL_RESEARCH_RE.test(goal) || (researchMatches.length >= 2 && (normalized.includes("multiple") || normalized.includes("source") || normalized.includes("project") || normalized.includes("pi-")))) {
130
- team = "parallel-research";
131
- workflow = "parallel-research";
132
- confidence = "high";
133
- reasons.push("Deep/multi-source research detected; use parallel shard exploration.");
134
- } else if (intents.includes("research") || (researchMatches.length > 0 && implementationMatches.length === 0)) {
135
- team = "research";
136
- workflow = "research";
137
- confidence = researchMatches.length >= 2 ? "high" : "medium";
138
- reasons.push(`Research/analysis terms detected: ${researchMatches.join(", ")}.`);
139
- } else if (intents.includes("fastFix") || fastFixMatches.length > 0) {
140
- team = "fast-fix";
141
- workflow = "fast-fix";
142
- confidence = "high";
143
- reasons.push(`Small fix terms detected: ${fastFixMatches.join(", ") || "fast-fix intent"}.`);
144
- } else if (intents.includes("taskList")) {
145
- team = "implementation";
146
- workflow = "implementation";
147
- confidence = "high";
148
- reasons.push(`Actionable multi-item task list detected (${decomposition.fanout} bullet${decomposition.fanout === 1 ? "" : "s"}); use coordinated implementation planning.`);
149
- } else if (intents.includes("implementation") || implementationMatches.length > 0) {
150
- team = "implementation";
151
- workflow = "implementation";
152
- confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 || decomposition.fanout >= 2 ? "high" : "medium";
153
- reasons.push(`Implementation terms detected: ${implementationMatches.join(", ") || "implementation intent"}.`);
154
- } else {
155
- action = "plan";
156
- confidence = wordCount(goal) < 8 ? "low" : "medium";
157
- reasons.push("No strong team-specific intent detected; start with planning/default discovery.");
158
- }
159
-
160
-
161
- if (decomposition.strategy !== "atomic") reasons.push(`Goal decomposes into ${decomposition.subtasks.length} subtasks using ${decomposition.strategy} parsing.`);
162
- const async = config.preferAsyncForLongTasks === true && (wordCount(goal) > 24 || riskyMatches.length > 0 || implementationMatches.length >= 2 || decomposition.fanout >= 3);
163
- const workspaceMode = config.allowWorktreeSuggestion === false ? "single" : (riskyMatches.length > 0 && team === "implementation" ? "worktree" : "single");
164
- if (async) reasons.push("Task appears long/risky and config prefers async for long tasks.");
165
- if (workspaceMode === "worktree") reasons.push(`Risk/isolation terms detected: ${riskyMatches.join(", ")}.`);
166
-
167
- return { team, workflow, action, async, workspaceMode, confidence, decomposition, reasons };
168
- }
169
-
170
- export function formatRecommendation(goal: string, recommendation: TeamRecommendation): string {
171
- return [
172
- "pi-crew recommendation:",
173
- `Goal: ${goal}`,
174
- `Action: ${recommendation.action}`,
175
- `Team: ${recommendation.team}`,
176
- `Workflow: ${recommendation.workflow}`,
177
- `Async: ${recommendation.async}`,
178
- `Workspace mode: ${recommendation.workspaceMode}`,
179
- `Confidence: ${recommendation.confidence}`,
180
- `Decomposition: ${recommendation.decomposition.strategy} (${recommendation.decomposition.fanout} lane${recommendation.decomposition.fanout === 1 ? "" : "s"})`,
181
- "Subtasks:",
182
- ...recommendation.decomposition.subtasks.map((task, index) => `- ${index + 1}. [${task.role}] ${task.subject}`),
183
- "Reasons:",
184
- ...recommendation.reasons.map((reason) => `- ${reason}`),
185
- "Suggested tool call:",
186
- JSON.stringify({ action: recommendation.action, team: recommendation.team, workflow: recommendation.workflow, goal, async: recommendation.async, workspaceMode: recommendation.workspaceMode }, null, 2),
187
- ].join("\n");
188
- }
1
+ import { detectTeamIntent } from "./autonomous-policy.ts";
2
+ import type { AgentConfig } from "../agents/agent-config.ts";
3
+ import type { TeamConfig } from "../teams/team-config.ts";
4
+ import type { PiTeamsAutonomousConfig } from "../config/config.ts";
5
+
6
+ export type DecompositionStrategy = "numbered" | "bulleted" | "conjunction" | "atomic";
7
+
8
+ export interface RecommendedSubtask {
9
+ subject: string;
10
+ description: string;
11
+ role: string;
12
+ }
13
+
14
+ export interface TeamRecommendation {
15
+ team: string;
16
+ workflow: string;
17
+ action: "plan" | "run";
18
+ async: boolean;
19
+ workspaceMode: "single" | "worktree";
20
+ confidence: "low" | "medium" | "high";
21
+ decomposition: { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number };
22
+ reasons: string[];
23
+ }
24
+
25
+ const REVIEW_TERMS = ["review", "audit", "security", "vulnerability", "diff", "pr", "pull request"];
26
+ const RESEARCH_TERMS = ["research", "investigate", "compare", "analyze", "document", "docs", "explain", "architecture", "đọc sâu", "source", "projects"];
27
+ const PARALLEL_RESEARCH_RE = /(?:đọc sâu|deep read|deep research|source audit|multiple projects|các project|pi-\*|source\/|@source)/i;
28
+ const FAST_FIX_TERMS = ["quick fix", "fast-fix", "small bug", "typo", "one-line", "minor", "lint"];
29
+ const IMPLEMENTATION_TERMS = ["implement", "refactor", "migrate", "feature", "tests", "test", "integration", "upgrade", "build", "create", "add", "fix", "update", "sửa", "thêm", "cập nhật", "kiểm thử"];
30
+ const RISKY_TERMS = ["migration", "refactor", "large", "multiple", "parallel", "concurrent", "risky", "critical", "nhiều file", "nhiều task"];
31
+ const NUMBERED_LINE_RE = /^\s*\d+[.)]\s+(.+)$/;
32
+ const BULLETED_LINE_RE = /^\s*[-*•]\s+(.+)$/;
33
+ const CONJUNCTION_SPLIT_RE = /\s+(?:and|,\s*and|,)\s+/i;
34
+ const FILE_REF_RE = /\b\S+\.\w{1,8}\b/g;
35
+ const CODE_SYMBOL_RE = /`[^`]+`/g;
36
+
37
+ function includesAny(text: string, terms: string[]): string[] {
38
+ return terms.filter((term) => text.includes(term));
39
+ }
40
+
41
+ function wordCount(text: string): number {
42
+ return text.trim().split(/\s+/).filter(Boolean).length;
43
+ }
44
+
45
+ function recommendRole(text: string): string {
46
+ const lower = text.toLowerCase();
47
+ if (includesAny(lower, ["test", "spec", "coverage", "verify"]).length > 0) return "test-engineer";
48
+ if (includesAny(lower, ["security", "vulnerability", "auth", "owasp"]).length > 0) return "security-reviewer";
49
+ if (includesAny(lower, ["review", "audit", "diff"]).length > 0) return "reviewer";
50
+ if (includesAny(lower, ["doc", "readme", "guide", "write"]).length > 0) return "writer";
51
+ if (includesAny(lower, ["research", "investigate", "explore", "find", "trace"]).length > 0) return "explorer";
52
+ if (includesAny(lower, ["plan", "design", "architecture"]).length > 0) return "planner";
53
+ return "executor";
54
+ }
55
+
56
+ function makeSubtask(text: string): RecommendedSubtask {
57
+ const subject = text.trim().slice(0, 80) || "Task";
58
+ return { subject, description: text.trim(), role: recommendRole(text) };
59
+ }
60
+
61
+ export function decomposeGoal(goal: string): { strategy: DecompositionStrategy; subtasks: RecommendedSubtask[]; fanout: number } {
62
+ const lines = goal.split("\n").map((line) => line.trim()).filter(Boolean);
63
+ const fileRefs = goal.match(FILE_REF_RE)?.length ?? 0;
64
+ const codeSymbols = goal.match(CODE_SYMBOL_RE)?.length ?? 0;
65
+ const hasParallelKeyword = /\b(?:parallel|concurrently|simultaneously|independently)\b/i.test(goal);
66
+ if (fileRefs >= 3 || codeSymbols >= 3 || hasParallelKeyword) {
67
+ const subtask = makeSubtask(goal);
68
+ return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
69
+ }
70
+ const numberedLines = lines.map((line) => line.match(NUMBERED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
71
+ if (numberedLines.length >= 2 && numberedLines.length >= lines.length - 1) {
72
+ const subtasks = numberedLines.map((line) => makeSubtask(line));
73
+ return { strategy: "numbered", subtasks, fanout: subtasks.length };
74
+ }
75
+ const bulletedLines = lines.map((line) => line.match(BULLETED_LINE_RE)?.[1]).filter((line): line is string => line !== undefined);
76
+ if (bulletedLines.length >= 2 && bulletedLines.length >= lines.length - 1) {
77
+ const subtasks = bulletedLines.map((line) => makeSubtask(line));
78
+ return { strategy: "bulleted", subtasks, fanout: subtasks.length };
79
+ }
80
+ if (lines.length === 1) {
81
+ const parts = lines[0].split(CONJUNCTION_SPLIT_RE).map((part) => part.trim()).filter(Boolean);
82
+ if (parts.length >= 2) {
83
+ const subtasks = parts.map((part) => makeSubtask(part));
84
+ return { strategy: "conjunction", subtasks, fanout: subtasks.length };
85
+ }
86
+ }
87
+ const subtask = makeSubtask(goal);
88
+ return { strategy: "atomic", subtasks: [subtask], fanout: 1 };
89
+ }
90
+
91
+ function metadataMatches(goal: string, values: string[] | undefined): string[] {
92
+ const lower = goal.toLowerCase();
93
+ return (values ?? []).filter((value) => lower.includes(value.toLowerCase()));
94
+ }
95
+
96
+ export function recommendTeam(goal: string, config: PiTeamsAutonomousConfig = {}, resources?: { teams?: TeamConfig[]; agents?: AgentConfig[] }): TeamRecommendation {
97
+ const normalized = goal.toLowerCase();
98
+ const intents = detectTeamIntent(goal, config);
99
+ const decomposition = decomposeGoal(goal);
100
+ const reasons: string[] = [];
101
+ let team: TeamRecommendation["team"] = "default";
102
+ let workflow: TeamRecommendation["workflow"] = "default";
103
+ let action: TeamRecommendation["action"] = "run";
104
+ let confidence: TeamRecommendation["confidence"] = "medium";
105
+
106
+ if (intents.length > 0) reasons.push(`Matched explicit intent keyword(s): ${intents.join(", ")}.`);
107
+
108
+ const metadataTeamMatches = (resources?.teams ?? [])
109
+ .map((candidate) => ({ team: candidate, matches: [...metadataMatches(goal, candidate.routing?.triggers), ...metadataMatches(goal, candidate.routing?.useWhen)] }))
110
+ .filter((candidate) => candidate.matches.length > 0)
111
+ .sort((a, b) => b.matches.length - a.matches.length);
112
+
113
+ const reviewMatches = includesAny(normalized, REVIEW_TERMS);
114
+ const researchMatches = includesAny(normalized, RESEARCH_TERMS);
115
+ const fastFixMatches = includesAny(normalized, FAST_FIX_TERMS);
116
+ const implementationMatches = includesAny(normalized, IMPLEMENTATION_TERMS);
117
+ const riskyMatches = includesAny(normalized, RISKY_TERMS);
118
+
119
+ if (metadataTeamMatches[0]) {
120
+ team = metadataTeamMatches[0].team.name as TeamRecommendation["team"];
121
+ workflow = (metadataTeamMatches[0].team.defaultWorkflow ?? metadataTeamMatches[0].team.name) as TeamRecommendation["workflow"];
122
+ confidence = "high";
123
+ reasons.push(`Matched team routing metadata for '${metadataTeamMatches[0].team.name}': ${metadataTeamMatches[0].matches.join(", ")}.`);
124
+ } else if (intents.includes("review") || reviewMatches.length >= 2 || normalized.includes("security review")) {
125
+ team = "review";
126
+ workflow = "review";
127
+ confidence = "high";
128
+ reasons.push(`Review/audit terms detected: ${reviewMatches.join(", ") || "explicit review intent"}.`);
129
+ } else if (PARALLEL_RESEARCH_RE.test(goal) || (researchMatches.length >= 2 && (normalized.includes("multiple") || normalized.includes("source") || normalized.includes("project") || normalized.includes("pi-")))) {
130
+ team = "parallel-research";
131
+ workflow = "parallel-research";
132
+ confidence = "high";
133
+ reasons.push("Deep/multi-source research detected; use parallel shard exploration.");
134
+ } else if (intents.includes("research") || (researchMatches.length > 0 && implementationMatches.length === 0)) {
135
+ team = "research";
136
+ workflow = "research";
137
+ confidence = researchMatches.length >= 2 ? "high" : "medium";
138
+ reasons.push(`Research/analysis terms detected: ${researchMatches.join(", ")}.`);
139
+ } else if (intents.includes("fastFix") || fastFixMatches.length > 0) {
140
+ team = "fast-fix";
141
+ workflow = "fast-fix";
142
+ confidence = "high";
143
+ reasons.push(`Small fix terms detected: ${fastFixMatches.join(", ") || "fast-fix intent"}.`);
144
+ } else if (intents.includes("taskList")) {
145
+ team = "implementation";
146
+ workflow = "implementation";
147
+ confidence = "high";
148
+ reasons.push(`Actionable multi-item task list detected (${decomposition.fanout} bullet${decomposition.fanout === 1 ? "" : "s"}); use coordinated implementation planning.`);
149
+ } else if (intents.includes("implementation") || implementationMatches.length > 0) {
150
+ team = "implementation";
151
+ workflow = "implementation";
152
+ confidence = implementationMatches.length >= 2 || riskyMatches.length > 0 || decomposition.fanout >= 2 ? "high" : "medium";
153
+ reasons.push(`Implementation terms detected: ${implementationMatches.join(", ") || "implementation intent"}.`);
154
+ } else {
155
+ action = "plan";
156
+ confidence = wordCount(goal) < 8 ? "low" : "medium";
157
+ reasons.push("No strong team-specific intent detected; start with planning/default discovery.");
158
+ }
159
+
160
+
161
+ if (decomposition.strategy !== "atomic") reasons.push(`Goal decomposes into ${decomposition.subtasks.length} subtasks using ${decomposition.strategy} parsing.`);
162
+ const async = config.preferAsyncForLongTasks === true && (wordCount(goal) > 24 || riskyMatches.length > 0 || implementationMatches.length >= 2 || decomposition.fanout >= 3);
163
+ const workspaceMode = config.allowWorktreeSuggestion === false ? "single" : (riskyMatches.length > 0 && team === "implementation" ? "worktree" : "single");
164
+ if (async) reasons.push("Task appears long/risky and config prefers async for long tasks.");
165
+ if (workspaceMode === "worktree") reasons.push(`Risk/isolation terms detected: ${riskyMatches.join(", ")}.`);
166
+
167
+ return { team, workflow, action, async, workspaceMode, confidence, decomposition, reasons };
168
+ }
169
+
170
+ export function formatRecommendation(goal: string, recommendation: TeamRecommendation): string {
171
+ return [
172
+ "pi-crew recommendation:",
173
+ `Goal: ${goal}`,
174
+ `Action: ${recommendation.action}`,
175
+ `Team: ${recommendation.team}`,
176
+ `Workflow: ${recommendation.workflow}`,
177
+ `Async: ${recommendation.async}`,
178
+ `Workspace mode: ${recommendation.workspaceMode}`,
179
+ `Confidence: ${recommendation.confidence}`,
180
+ `Decomposition: ${recommendation.decomposition.strategy} (${recommendation.decomposition.fanout} lane${recommendation.decomposition.fanout === 1 ? "" : "s"})`,
181
+ "Subtasks:",
182
+ ...recommendation.decomposition.subtasks.map((task, index) => `- ${index + 1}. [${task.role}] ${task.subject}`),
183
+ "Reasons:",
184
+ ...recommendation.reasons.map((reason) => `- ${reason}`),
185
+ "Suggested tool call:",
186
+ JSON.stringify({ action: recommendation.action, team: recommendation.team, workflow: recommendation.workflow, goal, async: recommendation.async, workspaceMode: recommendation.workspaceMode }, null, 2),
187
+ ].join("\n");
188
+ }
@@ -12,9 +12,10 @@ import { probeLiveSessionRuntime } from "../../subagents/live/session-runtime.ts
12
12
  import { currentCrewRole, permissionForRole } from "../../runtime/role-permission.ts";
13
13
  import { touchWorkerHeartbeat } from "../../runtime/worker-heartbeat.ts";
14
14
  import { agentOutputPath, readCrewAgentEventsCursor, readCrewAgentStatus, readCrewAgents } from "../../runtime/crew-agent-records.ts";
15
+ import { terminateLiveAgentsForRun } from "../../runtime/live-agent-manager.ts";
15
16
  import { buildAgentDashboard, readAgentOutput } from "../../runtime/agent-observability.ts";
16
17
  import { readForegroundControlStatus, writeForegroundInterruptRequest } from "../../runtime/foreground-control.ts";
17
- import { followUpLiveAgent, getLiveAgent, listLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
18
+ import { followUpLiveAgent, getLiveAgent, listActiveLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
18
19
  import { appendLiveAgentControlRequest } from "../../subagents/live/control.ts";
19
20
  import { liveControlRealtimeMessage, publishLiveControlRealtime } from "../../subagents/live/realtime.ts";
20
21
  import { buildCapabilityInventory } from "../../runtime/capability-inventory.ts";
@@ -121,6 +122,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
121
122
  saveRunTasks(manifest, tasks);
122
123
  appendEvent(manifest.eventsPath, { type: "plan.cancelled", runId: manifest.runId, taskId: approval.planTaskId, message: "Adaptive implementation plan was cancelled.", metadata: { provenance: "api" } });
123
124
  manifest = updateRunStatus(manifest, "cancelled", "Plan approval was cancelled.");
125
+ void terminateLiveAgentsForRun(manifest.runId, "cancelled", appendEvent, manifest.eventsPath).catch(() => {});
124
126
  return result(JSON.stringify({ planApproval: manifest.planApproval, cancelledTasks: tasks.filter((task) => task.status === "cancelled").map((task) => task.id) }, null, 2), { action: "api", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot });
125
127
  });
126
128
  } catch (error) {
@@ -223,7 +225,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
223
225
  return result(JSON.stringify({ agentId: agent.id, mailboxMessage: message }, null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
224
226
  }
225
227
  if (operation === "list-live-agents") {
226
- return result(JSON.stringify(listLiveAgents().filter((agent) => agent.runId === loaded.manifest.runId), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
228
+ return result(JSON.stringify(listActiveLiveAgents().filter((agent) => agent.runId === loaded.manifest.runId), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
227
229
  }
228
230
  if (operation === "steer-agent" || operation === "follow-up-agent" || operation === "stop-agent" || operation === "resume-agent" || operation === "interrupt-agent") {
229
231
  const agentId = typeof cfg.agentId === "string" ? cfg.agentId : undefined;
@@ -233,6 +235,7 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
233
235
  try {
234
236
  const live = getLiveAgent(agentId);
235
237
  if (live && live.runId !== loaded.manifest.runId) return result(`Live agent '${agentId}' does not belong to run ${loaded.manifest.runId}.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
238
+ if (live && live.workspaceId !== loaded.manifest.cwd) return result(`Live agent '${agentId}' does not belong to workspace ${loaded.manifest.cwd}.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
236
239
  if (!live && (operation === "steer-agent" || operation === "follow-up-agent")) throw new Error(`Live agent '${agentId}' not found.`);
237
240
  const liveTaskId = live?.taskId;
238
241
  if ((operation === "steer-agent" || operation === "follow-up-agent") && !liveTaskId) throw new Error(`Live agent '${agentId}' not found.`);