pi-crew 0.1.46 → 0.1.49

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 (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Parallel dispatch handler — accepts an array of independent tasks
3
+ * and spawns them as concurrent background agents.
4
+ *
5
+ * Solves the host-agent limitation of only being able to emit
6
+ * one Agent() call per response turn. By calling `action=parallel`
7
+ * once with multiple tasks, the system handles fanout automatically.
8
+ */
9
+ import { discoverAgents } from "../../agents/discover-agents.ts";
10
+ import { loadConfig } from "../../config/config.ts";
11
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
12
+ import { createRunManifest } from "../../state/state-store.ts";
13
+ import { appendEvent } from "../../state/event-log.ts";
14
+ import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
15
+ import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
16
+ import { resolveCwdOverride } from "../registration/team-tool.ts";
17
+ import { result, type TeamContext } from "./context.ts";
18
+ import type { PiTeamsToolResult } from "../tool-result.ts";
19
+ import { discoverTeams } from "../../teams/discover-teams.ts";
20
+ import { discoverWorkflows } from "../../workflows/discover-workflows.ts";
21
+ import type { TeamConfig } from "../../teams/team-config.ts";
22
+ import type { WorkflowConfig } from "../../workflows/workflow-config.ts";
23
+
24
+ const MAX_CONCURRENCY = 8;
25
+ const DEFAULT_CONCURRENCY = 4;
26
+ const DEFAULT_TEAM = "fast-fix";
27
+ const DEFAULT_AGENT = "explorer";
28
+
29
+ export async function handleParallel(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
30
+ const tasksParam = params.config?.tasks;
31
+ if (!Array.isArray(tasksParam) || tasksParam.length === 0) {
32
+ return result("parallel action requires config.tasks: [{goal, agent?}]", { action: "parallel", status: "error" }, true);
33
+ }
34
+
35
+ const concurrency = Math.min(
36
+ Math.max(1, Math.floor((params.config?.concurrency as number) ?? DEFAULT_CONCURRENCY)),
37
+ MAX_CONCURRENCY,
38
+ );
39
+
40
+ const config = loadConfig(ctx.cwd);
41
+ const agentsResult = discoverAgents(ctx.cwd);
42
+ const allAgentsList = [...agentsResult.builtin, ...agentsResult.user, ...agentsResult.project];
43
+ const teams = discoverTeams(ctx.cwd);
44
+ const workflows = discoverWorkflows(ctx.cwd);
45
+
46
+ const teamName = (params.config?.team as string) ?? DEFAULT_TEAM;
47
+ const team = teams.builtin.find((t) => t.name === teamName)
48
+ ?? teams.user.find((t) => t.name === teamName)
49
+ ?? teams.project.find((t) => t.name === teamName);
50
+ if (!team) {
51
+ return result(`Team '${teamName}' not found`, { action: "parallel", status: "error" }, true);
52
+ }
53
+
54
+ // H2: Use team's defaultWorkflow, fall back to "fast-fix"
55
+ const workflow = workflows.builtin.find((w) => w.name === team.defaultWorkflow)
56
+ ?? workflows.builtin.find((w) => w.name === "fast-fix");
57
+ if (!workflow) {
58
+ return result("No suitable workflow found for parallel dispatch", { action: "parallel", status: "error" }, true);
59
+ }
60
+
61
+ const runtime = await resolveCrewRuntime(config.config);
62
+
63
+ const launched: Array<{ runId: string; goal: string; agent: string }> = [];
64
+ const errors: Array<{ goal: string; error: string }> = [];
65
+
66
+ // C1: Enforce concurrency limit with batched spawning
67
+ for (let batchStart = 0; batchStart < tasksParam.length; batchStart += concurrency) {
68
+ const batch = tasksParam.slice(batchStart, batchStart + concurrency);
69
+ const batchPromises = batch.map((task) => spawnSingleTask(task, ctx, allAgentsList, team, workflow, runtime));
70
+ const batchResults = await Promise.allSettled(batchPromises);
71
+ for (const res of batchResults) {
72
+ if (res.status === "fulfilled" && res.value.ok) {
73
+ launched.push(res.value.value);
74
+ } else if (res.status === "fulfilled" && !res.value.ok) {
75
+ errors.push(res.value.error);
76
+ } else {
77
+ const reason = (res as PromiseRejectedResult).reason;
78
+ errors.push({ goal: "(unknown)", error: reason instanceof Error ? reason.message : String(reason) });
79
+ }
80
+ }
81
+ }
82
+
83
+ const lines: string[] = [
84
+ `Parallel dispatch: ${launched.length}/${tasksParam.length} tasks launched (concurrency: ${concurrency})`,
85
+ "",
86
+ ];
87
+ for (const l of launched) {
88
+ lines.push(` ✅ ${l.runId} — ${l.agent}: ${l.goal.slice(0, 80)}`);
89
+ }
90
+ for (const e of errors) {
91
+ lines.push(` ❌ ${e.goal.slice(0, 80)}: ${e.error}`);
92
+ }
93
+
94
+ return result(lines.join("\n"), {
95
+ action: "parallel",
96
+ status: errors.length === tasksParam.length ? "error" : "ok",
97
+ }, errors.length === tasksParam.length);
98
+ }
99
+
100
+ type SpawnOk = { ok: true; value: { runId: string; goal: string; agent: string } };
101
+ type SpawnErr = { ok: false; error: { goal: string; error: string } };
102
+ type SpawnResult = SpawnOk | SpawnErr;
103
+
104
+ async function spawnSingleTask(
105
+ task: unknown,
106
+ ctx: TeamContext,
107
+ allAgentsList: Array<{ name: string }>,
108
+ team: TeamConfig,
109
+ workflow: WorkflowConfig,
110
+ runtime: { available: boolean; kind: string },
111
+ ): Promise<SpawnResult> {
112
+ try {
113
+ const taskRec = task as Record<string, unknown>;
114
+ const goal = taskRec.goal as string;
115
+ const agentName = (taskRec.agent as string) ?? DEFAULT_AGENT;
116
+ const rawCwd = (taskRec.cwd as string) ?? undefined;
117
+
118
+ if (!goal) {
119
+ return { ok: false, error: { goal: "(missing)", error: "Each task must have a 'goal' field" } };
120
+ }
121
+
122
+ const agent = allAgentsList.find((a) => a.name === agentName);
123
+ if (!agent) {
124
+ return { ok: false, error: { goal, error: `Agent '${agentName}' not found` } };
125
+ }
126
+
127
+ // H1: Validate taskCwd containment against project root
128
+ const cwdResult = resolveCwdOverride(ctx.cwd, rawCwd);
129
+ if (!cwdResult.ok) {
130
+ return { ok: false, error: { goal, error: cwdResult.error } };
131
+ }
132
+ const taskCwd = cwdResult.cwd;
133
+
134
+ const created = createRunManifest({
135
+ cwd: taskCwd,
136
+ team,
137
+ workflow,
138
+ goal,
139
+ });
140
+
141
+ appendEvent(created.manifest.eventsPath, {
142
+ type: "run.started",
143
+ runId: created.manifest.runId,
144
+ message: `Parallel task: ${goal}`,
145
+ });
146
+
147
+ if (runtime.available && runtime.kind === "child-process") {
148
+ spawnBackgroundTeamRun(created.manifest);
149
+ }
150
+
151
+ return { ok: true, value: { runId: created.manifest.runId, goal, agent: agentName } };
152
+ } catch (error) {
153
+ const goal = ((task as Record<string, unknown>).goal as string) ?? "(unknown)";
154
+ return { ok: false, error: { goal, error: error instanceof Error ? error.message : String(error) } };
155
+ }
156
+ }
@@ -1,19 +1,19 @@
1
- import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
2
- import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
3
- import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
4
- import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
5
- import type { PiTeamsToolResult } from "../tool-result.ts";
6
- import { result, type TeamContext } from "./context.ts";
7
-
8
- export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
- const teamName = params.team ?? "default";
10
- const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
11
- if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
12
- const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
13
- const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
14
- if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
15
- const errors = validateWorkflowForTeam(workflow, team);
16
- if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
17
- const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
18
- return result(lines.join("\n"), { action: "plan", status: "ok" });
19
- }
1
+ import { allTeams, discoverTeams } from "../../teams/discover-teams.ts";
2
+ import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workflows.ts";
3
+ import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
4
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
5
+ import type { PiTeamsToolResult } from "../tool-result.ts";
6
+ import { result, type TeamContext } from "./context.ts";
7
+
8
+ export function handlePlan(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
9
+ const teamName = params.team ?? "default";
10
+ const team = allTeams(discoverTeams(ctx.cwd)).find((item) => item.name === teamName);
11
+ if (!team) return result(`Team '${teamName}' not found.`, { action: "plan", status: "error" }, true);
12
+ const workflowName = params.workflow ?? team.defaultWorkflow ?? "default";
13
+ const workflow = allWorkflows(discoverWorkflows(ctx.cwd)).find((item) => item.name === workflowName);
14
+ if (!workflow) return result(`Workflow '${workflowName}' not found.`, { action: "plan", status: "error" }, true);
15
+ const errors = validateWorkflowForTeam(workflow, team);
16
+ if (errors.length > 0) return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...errors.map((error) => `- ${error}`)].join("\n"), { action: "plan", status: "error" }, true);
17
+ const lines = [`Team plan: ${team.name}`, `Workflow: ${workflow.name}`, `Goal: ${params.goal ?? params.task ?? "(not provided)"}`, "", "Steps:", ...workflow.steps.map((step, index) => `${index + 1}. ${step.id} [${step.role}]${step.dependsOn?.length ? ` after ${step.dependsOn.join(", ")}` : ""}`)];
18
+ return result(lines.join("\n"), { action: "plan", status: "ok" });
19
+ }
@@ -2,7 +2,7 @@ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
2
2
  import { withRunLockSync } from "../../state/locks.ts";
3
3
  import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
4
4
  import { appendEvent } from "../../state/event-log.ts";
5
- import { appendMailboxMessage } from "../../state/mailbox.ts";
5
+ import { appendMailboxMessage, updateMailboxMessageReply } from "../../state/mailbox.ts";
6
6
  import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
7
7
  import { logInternalError } from "../../utils/internal-error.ts";
8
8
  import type { PiTeamsToolResult } from "../tool-result.ts";
@@ -60,10 +60,18 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
60
60
  priority: "normal",
61
61
  deliveryMode: "next_turn",
62
62
  data: { action: "respond", kind: "response" },
63
+ replyTo: params.replyTo,
64
+ replyFrom: params.replyFrom,
65
+ replyDeadline: params.replyDeadline,
63
66
  });
64
67
  mailboxIds.push(mailbox.id);
65
68
  }
66
69
 
70
+ // If this respond includes a replyTo, update the original message with reply metadata.
71
+ if (params.replyTo) {
72
+ updateMailboxMessageReply(fresh.manifest, params.replyTo, message || "(resume)");
73
+ }
74
+
67
75
  // Re-queue waiting tasks so durable scheduler/resume can pick them up again.
68
76
  const updatedTasks = fresh.tasks.map((task) => {
69
77
  if (!resumed.has(task.id)) return task;
@@ -98,7 +106,7 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
98
106
  const resumedIds = targetTasks.map((t) => t.id);
99
107
  return result(
100
108
  `Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
101
- { action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds },
109
+ { action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds, intent: `responding to ${resumedIds.join(", ")} in ${fresh.manifest.runId}` },
102
110
  );
103
111
  });
104
112
  }
@@ -60,6 +60,7 @@ function scheduleBackgroundEarlyExitGuard(cwd: string, runId: string, pid: numbe
60
60
  export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
61
61
  const goal = params.goal ?? params.task;
62
62
  if (!goal) return result("Run requires goal or task.", { action: "run", status: "error" }, true);
63
+ const intentPrefix = goal.length > 60 ? `${goal.slice(0, 57)}...` : goal;
63
64
 
64
65
  const teams = allTeams(discoverTeams(ctx.cwd));
65
66
  const workflows = allWorkflows(discoverWorkflows(ctx.cwd));
@@ -149,7 +150,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
149
150
  "",
150
151
  `Check status with: team status runId=${updatedManifest.runId}`,
151
152
  ].join("\n");
152
- return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
153
+ return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot, intent: `running ${team.name}: ${intentPrefix}` });
153
154
  }
154
155
 
155
156
  if (runtime.safety === "blocked") {
@@ -188,7 +189,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
188
189
  "",
189
190
  "The run continues in this Pi session without blocking the chat. It will be interrupted on session shutdown. Use /team-dashboard or /team-status to watch it.",
190
191
  ].join("\n");
191
- return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
192
+ return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot, intent: `running ${team.name}: ${intentPrefix}` });
192
193
  }
193
194
  let executed: Awaited<ReturnType<typeof executeTeamRun>>;
194
195
  try {
@@ -106,5 +106,5 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
106
106
  "Recent events:",
107
107
  ...(events.length ? events.map((event) => `- ${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}`) : ["- (none)"]),
108
108
  ];
109
- return result(lines.join("\n"), { action: "status", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot });
109
+ return result(lines.join("\n"), { action: "status", status: "ok", runId: manifest.runId, artifactsRoot: manifest.artifactsRoot, intent: `status ${manifest.runId}: ${manifest.status}` });
110
110
  }
@@ -8,5 +8,6 @@ export interface TeamToolDetails {
8
8
  foreignIds?: string[];
9
9
  intent?: string;
10
10
  resumedIds?: string[];
11
+ retriedTaskIds?: string[];
11
12
  mailboxIds?: string[];
12
13
  }
@@ -43,7 +43,8 @@ import { handleDoctor } from "./team-tool/doctor.ts";
43
43
  import { handleStatus } from "./team-tool/status.ts";
44
44
  import { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
45
45
  import { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
46
- import { handleCancel } from "./team-tool/cancel.ts";
46
+ import { handleCancel, handleRetry } from "./team-tool/cancel.ts";
47
+ import { handleParallel } from "./team-tool/parallel-dispatch.ts";
47
48
  import { handleRespond } from "./team-tool/respond.ts";
48
49
  import { handlePlan } from "./team-tool/plan.ts";
49
50
  import { logInternalError } from "../utils/internal-error.ts";
@@ -56,7 +57,7 @@ export { handleDoctor } from "./team-tool/doctor.ts";
56
57
  export { handleStatus } from "./team-tool/status.ts";
57
58
  export { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
58
59
  export { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
59
- export { handleCancel } from "./team-tool/cancel.ts";
60
+ export { handleRetry } from "./team-tool/cancel.ts";
60
61
  export { handlePlan } from "./team-tool/plan.ts";
61
62
  export { handleApi } from "./team-tool/api.ts";
62
63
 
@@ -181,6 +182,13 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
181
182
  const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
182
183
  const resumeManifest = recovered.manifest;
183
184
  const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
185
+ // Preserve original manifest scaffold mode when resume has no explicit mode override
186
+ // AND workers are not explicitly disabled. If workers are disabled, let
187
+ // resolveCrewRuntime detect it and return blocked safety.
188
+ if (!executedConfig.runtime?.mode && resumeManifest.runtimeResolution?.safety === "explicit_dry_run") {
189
+ const workersDisabled = executedConfig.executeWorkers === false || process.env.PI_CREW_EXECUTE_WORKERS === "0" || process.env.PI_TEAMS_EXECUTE_WORKERS === "0";
190
+ if (!workersDisabled) executedConfig.runtime = { ...executedConfig.runtime, mode: "scaffold" };
191
+ }
184
192
  const runtime = await resolveCrewRuntime(executedConfig);
185
193
  const runtimeResolution = runtimeResolutionState(runtime);
186
194
  const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
@@ -295,12 +303,14 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
295
303
  case "import": return handleImport(params, ctx);
296
304
  case "imports": return handleImports(params, ctx);
297
305
  case "settings": return handleSettings(params, ctx);
298
- case "prune": return handlePrune(params, ctx);
306
+ case "prune": return handlePrune(params, ctx);
299
307
  case "forget": return handleForget(params, ctx);
300
308
  case "run": return handleRun(params, ctx);
301
309
  case "status": return handleStatus(params, ctx);
302
310
  case "cancel": return handleCancel(params, ctx);
311
+ case "retry": return handleRetry(params, ctx);
303
312
  case "respond": return handleRespond(params, ctx);
313
+ case "parallel": return await handleParallel(params, ctx);
304
314
  case "plan": return handlePlan(params, ctx);
305
315
  case "resume": return handleResume(params, ctx);
306
316
  case "create": return handleCreate(params, ctx);
@@ -0,0 +1,61 @@
1
+ import type { HookDefinition, HookName, HookContext, HookResult, HookExecutionReport } from "./types.ts";
2
+ import { appendEvent } from "../state/event-log.ts";
3
+ import type { TeamRunManifest } from "../state/types.ts";
4
+ import { runEventBus } from "../ui/run-event-bus.ts";
5
+
6
+ const registry = new Map<HookName, HookDefinition[]>();
7
+
8
+ export function registerHook(definition: HookDefinition): void {
9
+ const hooks = registry.get(definition.name) ?? [];
10
+ hooks.push(definition);
11
+ registry.set(definition.name, hooks);
12
+ }
13
+
14
+ export function clearHooks(): void {
15
+ registry.clear();
16
+ }
17
+
18
+ export function getHooks(name: HookName): HookDefinition[] {
19
+ return registry.get(name) ?? [];
20
+ }
21
+
22
+ export async function executeHook(name: HookName, ctx: HookContext): Promise<HookExecutionReport> {
23
+ const hooks = getHooks(name);
24
+ if (hooks.length === 0) return { hookName: name, outcome: "allow", durationMs: 0 };
25
+ const start = Date.now();
26
+ const diagnostics: string[] = [];
27
+ let capturedModifications: Record<string, unknown> | undefined;
28
+ for (const hook of hooks) {
29
+ try {
30
+ const result: HookResult = await hook.handler(ctx);
31
+ if (hook.mode === "blocking" && result.outcome === "block") {
32
+ return { hookName: name, outcome: "block", durationMs: Date.now() - start, reason: result.reason };
33
+ }
34
+ if (result.outcome === "modify" && result.data) {
35
+ Object.assign(ctx, result.data);
36
+ capturedModifications = { ...result.data };
37
+ }
38
+ } catch (error) {
39
+ const message = error instanceof Error ? error.message : String(error);
40
+ if (hook.mode === "blocking") {
41
+ return { hookName: name, outcome: "block", durationMs: Date.now() - start, reason: `Hook error: ${message}` };
42
+ }
43
+ // Non-blocking hook errors are accumulated as diagnostics; continue to next hook
44
+ diagnostics.push(message);
45
+ }
46
+ }
47
+ if (diagnostics.length > 0) {
48
+ return { hookName: name, outcome: "diagnostic", durationMs: Date.now() - start, reason: diagnostics.join("; "), modifiedData: capturedModifications };
49
+ }
50
+ return { hookName: name, outcome: "allow", durationMs: Date.now() - start, modifiedData: capturedModifications };
51
+ }
52
+
53
+ export function appendHookEvent(manifest: TeamRunManifest, report: HookExecutionReport): void {
54
+ appendEvent(manifest.eventsPath, {
55
+ type: "hook.executed",
56
+ runId: manifest.runId,
57
+ message: `Hook ${report.hookName} completed with outcome=${report.outcome}${report.reason ? `: ${report.reason}` : ""}`,
58
+ data: { hookName: report.hookName, outcome: report.outcome, durationMs: report.durationMs, reason: report.reason },
59
+ });
60
+ runEventBus.emit({ type: "effectiveness_changed", runId: manifest.runId, data: { hookName: report.hookName, outcome: report.outcome } });
61
+ }
@@ -0,0 +1,41 @@
1
+ export type HookName =
2
+ | "before_run_start"
3
+ | "before_task_start"
4
+ | "task_result"
5
+ | "before_cancel"
6
+ | "before_retry"
7
+ | "before_forget"
8
+ | "before_cleanup"
9
+ | "before_publish"
10
+ | "session_before_switch"
11
+ | "run_recovery";
12
+
13
+ export type HookMode = "blocking" | "non_blocking";
14
+ export type HookOutcome = "allow" | "block" | "modify" | "diagnostic";
15
+
16
+ export interface HookContext {
17
+ runId: string;
18
+ taskId?: string;
19
+ cwd: string;
20
+ [key: string]: unknown;
21
+ }
22
+
23
+ export interface HookResult {
24
+ outcome: HookOutcome;
25
+ reason?: string;
26
+ data?: Record<string, unknown>;
27
+ }
28
+
29
+ export interface HookDefinition {
30
+ name: HookName;
31
+ mode: HookMode;
32
+ handler: (ctx: HookContext) => HookResult | Promise<HookResult>;
33
+ }
34
+
35
+ export interface HookExecutionReport {
36
+ hookName: HookName;
37
+ outcome: HookOutcome;
38
+ durationMs: number;
39
+ reason?: string;
40
+ modifiedData?: Record<string, unknown>;
41
+ }