pi-crew 0.1.45 → 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 (178) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/README.md +5 -5
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +11 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/next-upgrade-roadmap.md +808 -0
  14. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  15. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  16. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  17. package/docs/research/AUDIT_PI_CREW.md +457 -0
  18. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  19. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  20. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  21. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  22. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  23. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  24. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  25. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  26. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  27. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  28. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  29. package/docs/research-awesome-agent-skills-distillation.md +100 -0
  30. package/docs/research-oh-my-pi-distillation.md +369 -0
  31. package/docs/source-runtime-refactor-map.md +24 -0
  32. package/docs/usage.md +3 -3
  33. package/install.mjs +52 -8
  34. package/package.json +99 -98
  35. package/schema.json +10 -1
  36. package/skills/async-worker-recovery/SKILL.md +42 -0
  37. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  38. package/skills/delegation-patterns/SKILL.md +54 -0
  39. package/skills/mailbox-interactive/SKILL.md +40 -0
  40. package/skills/model-routing-context/SKILL.md +39 -0
  41. package/skills/multi-perspective-review/SKILL.md +58 -0
  42. package/skills/observability-reliability/SKILL.md +41 -0
  43. package/skills/orchestration/SKILL.md +157 -0
  44. package/skills/ownership-session-security/SKILL.md +41 -0
  45. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  46. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  47. package/skills/resource-discovery-config/SKILL.md +41 -0
  48. package/skills/runtime-state-reader/SKILL.md +44 -0
  49. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  50. package/skills/state-mutation-locking/SKILL.md +42 -0
  51. package/skills/systematic-debugging/SKILL.md +67 -0
  52. package/skills/ui-render-performance/SKILL.md +39 -0
  53. package/skills/verification-before-done/SKILL.md +57 -0
  54. package/skills/worktree-isolation/SKILL.md +39 -0
  55. package/src/agents/agent-config.ts +6 -0
  56. package/src/agents/agent-search.ts +98 -0
  57. package/src/agents/agent-serializer.ts +38 -34
  58. package/src/agents/discover-agents.ts +29 -15
  59. package/src/config/config.ts +72 -24
  60. package/src/config/defaults.ts +25 -0
  61. package/src/extension/autonomous-policy.ts +26 -33
  62. package/src/extension/help.ts +1 -0
  63. package/src/extension/management.ts +5 -0
  64. package/src/extension/project-init.ts +62 -2
  65. package/src/extension/register.ts +69 -22
  66. package/src/extension/registration/commands.ts +64 -25
  67. package/src/extension/registration/compaction-guard.ts +1 -1
  68. package/src/extension/registration/subagent-helpers.ts +8 -0
  69. package/src/extension/registration/subagent-tools.ts +149 -148
  70. package/src/extension/registration/team-tool.ts +14 -10
  71. package/src/extension/run-index.ts +35 -21
  72. package/src/extension/run-maintenance.ts +30 -5
  73. package/src/extension/team-tool/api.ts +47 -9
  74. package/src/extension/team-tool/cancel.ts +109 -5
  75. package/src/extension/team-tool/context.ts +8 -0
  76. package/src/extension/team-tool/intent-policy.ts +42 -0
  77. package/src/extension/team-tool/lifecycle-actions.ts +120 -79
  78. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  79. package/src/extension/team-tool/respond.ts +46 -18
  80. package/src/extension/team-tool/run.ts +55 -12
  81. package/src/extension/team-tool/status.ts +13 -2
  82. package/src/extension/team-tool-types.ts +3 -0
  83. package/src/extension/team-tool.ts +45 -14
  84. package/src/hooks/registry.ts +61 -0
  85. package/src/hooks/types.ts +41 -0
  86. package/src/observability/event-to-metric.ts +8 -1
  87. package/src/runtime/agent-control.ts +169 -63
  88. package/src/runtime/async-runner.ts +3 -1
  89. package/src/runtime/background-runner.ts +78 -53
  90. package/src/runtime/cancellation-token.ts +89 -0
  91. package/src/runtime/cancellation.ts +61 -0
  92. package/src/runtime/capability-inventory.ts +116 -0
  93. package/src/runtime/child-pi.ts +458 -444
  94. package/src/runtime/code-summary.ts +247 -0
  95. package/src/runtime/crash-recovery.ts +182 -0
  96. package/src/runtime/crew-agent-records.ts +70 -10
  97. package/src/runtime/crew-agent-runtime.ts +1 -0
  98. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  99. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  100. package/src/runtime/deadletter.ts +1 -0
  101. package/src/runtime/delivery-coordinator.ts +48 -25
  102. package/src/runtime/effectiveness.ts +81 -0
  103. package/src/runtime/event-stream-bridge.ts +90 -0
  104. package/src/runtime/live-agent-control.ts +2 -1
  105. package/src/runtime/live-agent-manager.ts +179 -85
  106. package/src/runtime/live-control-realtime.ts +1 -1
  107. package/src/runtime/live-extension-bridge.ts +150 -0
  108. package/src/runtime/live-irc.ts +92 -0
  109. package/src/runtime/live-session-health.ts +100 -0
  110. package/src/runtime/live-session-runtime.ts +599 -305
  111. package/src/runtime/manifest-cache.ts +17 -2
  112. package/src/runtime/mcp-proxy.ts +113 -0
  113. package/src/runtime/model-fallback.ts +6 -4
  114. package/src/runtime/notebook-helpers.ts +90 -0
  115. package/src/runtime/orphan-sentinel.ts +7 -0
  116. package/src/runtime/output-validator.ts +187 -0
  117. package/src/runtime/parallel-utils.ts +57 -0
  118. package/src/runtime/parent-guard.ts +80 -0
  119. package/src/runtime/pi-args.ts +18 -3
  120. package/src/runtime/process-status.ts +5 -1
  121. package/src/runtime/prose-compressor.ts +164 -0
  122. package/src/runtime/result-extractor.ts +121 -0
  123. package/src/runtime/retry-executor.ts +81 -64
  124. package/src/runtime/runtime-resolver.ts +23 -10
  125. package/src/runtime/semaphore.ts +131 -0
  126. package/src/runtime/sensitive-paths.ts +92 -0
  127. package/src/runtime/skill-instructions.ts +222 -0
  128. package/src/runtime/stale-reconciler.ts +4 -14
  129. package/src/runtime/stream-preview.ts +177 -0
  130. package/src/runtime/subagent-manager.ts +6 -2
  131. package/src/runtime/subprocess-tool-registry.ts +67 -0
  132. package/src/runtime/task-output-context.ts +177 -127
  133. package/src/runtime/task-runner/capabilities.ts +78 -0
  134. package/src/runtime/task-runner/live-executor.ts +107 -101
  135. package/src/runtime/task-runner/prompt-builder.ts +72 -8
  136. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  137. package/src/runtime/task-runner/run-projection.ts +104 -0
  138. package/src/runtime/task-runner.ts +115 -5
  139. package/src/runtime/team-runner.ts +134 -19
  140. package/src/runtime/workspace-tree.ts +298 -0
  141. package/src/runtime/yield-handler.ts +189 -0
  142. package/src/schema/config-schema.ts +7 -0
  143. package/src/schema/team-tool-schema.ts +14 -4
  144. package/src/skills/discover-skills.ts +67 -0
  145. package/src/state/active-run-registry.ts +167 -0
  146. package/src/state/artifact-store.ts +4 -1
  147. package/src/state/atomic-write.ts +50 -1
  148. package/src/state/blob-store.ts +117 -0
  149. package/src/state/contracts.ts +2 -1
  150. package/src/state/event-log-rotation.ts +158 -0
  151. package/src/state/event-log.ts +52 -2
  152. package/src/state/mailbox.ts +129 -9
  153. package/src/state/state-store.ts +32 -5
  154. package/src/state/types.ts +64 -2
  155. package/src/teams/team-config.ts +1 -0
  156. package/src/ui/agent-management-overlay.ts +144 -0
  157. package/src/ui/crew-widget.ts +15 -5
  158. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  159. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  160. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  161. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  162. package/src/ui/live-run-sidebar.ts +4 -0
  163. package/src/ui/powerbar-publisher.ts +77 -15
  164. package/src/ui/render-coalescer.ts +51 -0
  165. package/src/ui/run-dashboard.ts +4 -0
  166. package/src/ui/run-event-bus.ts +209 -0
  167. package/src/ui/run-snapshot-cache.ts +78 -18
  168. package/src/ui/snapshot-types.ts +10 -0
  169. package/src/ui/transcript-entries.ts +258 -0
  170. package/src/utils/ids.ts +5 -0
  171. package/src/utils/incremental-reader.ts +104 -0
  172. package/src/utils/paths.ts +4 -2
  173. package/src/utils/scan-cache.ts +137 -0
  174. package/src/utils/sse-parser.ts +134 -0
  175. package/src/utils/task-name-generator.ts +337 -0
  176. package/src/utils/visual.ts +33 -2
  177. package/src/workflows/workflow-config.ts +1 -0
  178. package/src/worktree/cleanup.ts +2 -1
@@ -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,7 +1,8 @@
1
1
  import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
2
2
  import { withRunLockSync } from "../../state/locks.ts";
3
- import { loadRunManifestById, saveRunTasks } from "../../state/state-store.ts";
4
- import { appendMailboxMessage } from "../../state/mailbox.ts";
3
+ import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
4
+ import { appendEvent } from "../../state/event-log.ts";
5
+ import { appendMailboxMessage, updateMailboxMessageReply } from "../../state/mailbox.ts";
5
6
  import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
6
7
  import { logInternalError } from "../../utils/internal-error.ts";
7
8
  import type { PiTeamsToolResult } from "../tool-result.ts";
@@ -10,7 +11,7 @@ import { result, type TeamContext } from "./context.ts";
10
11
  /**
11
12
  * Handle `respond` action: send a message to a waiting (interactive) task.
12
13
  * The task must be in "waiting" status. The message is stored in the task's
13
- * mailbox and the task is transitioned back to "running".
14
+ * mailbox and the task is re-queued for durable scheduler resume.
14
15
  */
15
16
  export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
16
17
  if (!params.runId) return result("Respond requires runId.", { action: "respond", status: "error" }, true);
@@ -20,22 +21,28 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
20
21
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
21
22
 
22
23
  return withRunLockSync(loaded.manifest, () => {
24
+ const fresh = loadRunManifestById(ctx.cwd, params.runId!);
25
+ if (!fresh) return result(`Run '${params.runId}' not found.`, { action: "respond", status: "error" }, true);
26
+ const foreignRun = typeof fresh.manifest.ownerSessionId === "string" && fresh.manifest.ownerSessionId !== ctx.sessionId;
27
+ if (foreignRun) return result(`Run ${fresh.manifest.runId} belongs to another session; not responding.`, { action: "respond", status: "error", runId: fresh.manifest.runId }, true);
28
+
23
29
  const taskId = params.taskId;
24
30
  const message = params.message ?? "";
25
31
 
26
32
  const targetTasks = taskId
27
- ? loaded.tasks.filter((t) => t.id === taskId && t.status === "waiting")
28
- : loaded.tasks.filter((t) => t.status === "waiting");
33
+ ? fresh.tasks.filter((t) => t.id === taskId && t.status === "waiting")
34
+ : fresh.tasks.filter((t) => t.status === "waiting");
29
35
 
30
36
  if (targetTasks.length === 0) {
31
- const existing = taskId ? loaded.tasks.find((t) => t.id === taskId) : undefined;
37
+ const existing = taskId ? fresh.tasks.find((t) => t.id === taskId) : undefined;
38
+ const hint = " Use api operation=follow-up-agent for continuation prompts or api operation=steer-agent to interrupt active work.";
32
39
  return result(
33
- taskId
40
+ (taskId
34
41
  ? existing
35
42
  ? `Task '${taskId}' is ${existing.status}, not waiting.`
36
43
  : `Task '${taskId}' not found.`
37
- : `No waiting tasks in run ${loaded.manifest.runId}.`,
38
- { action: "respond", status: "error", runId: loaded.manifest.runId },
44
+ : `No waiting tasks in run ${fresh.manifest.runId}.`) + hint,
45
+ { action: "respond", status: "error", runId: fresh.manifest.runId },
39
46
  true,
40
47
  );
41
48
  }
@@ -43,23 +50,37 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
43
50
  const resumed = new Set(targetTasks.map((t) => t.id));
44
51
  const mailboxIds: string[] = [];
45
52
  for (const task of targetTasks) {
46
- const mailbox = appendMailboxMessage(loaded.manifest, {
53
+ const mailbox = appendMailboxMessage(fresh.manifest, {
47
54
  direction: "inbox",
48
55
  from: "leader",
49
56
  to: task.id,
50
57
  taskId: task.id,
51
58
  body: message || "(resume)",
52
- data: { action: "respond" },
59
+ kind: "response",
60
+ priority: "normal",
61
+ deliveryMode: "next_turn",
62
+ data: { action: "respond", kind: "response" },
63
+ replyTo: params.replyTo,
64
+ replyFrom: params.replyFrom,
65
+ replyDeadline: params.replyDeadline,
53
66
  });
54
67
  mailboxIds.push(mailbox.id);
55
68
  }
56
69
 
57
- // Transition waiting tasks back to running
58
- const updatedTasks = loaded.tasks.map((task) => {
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
+
75
+ // Re-queue waiting tasks so durable scheduler/resume can pick them up again.
76
+ const updatedTasks = fresh.tasks.map((task) => {
59
77
  if (!resumed.has(task.id)) return task;
60
78
  return {
61
79
  ...task,
62
- status: "running" as const,
80
+ status: "queued" as const,
81
+ startedAt: undefined,
82
+ finishedAt: undefined,
83
+ error: undefined,
63
84
  adaptive: {
64
85
  ...task.adaptive,
65
86
  phase: "resumed",
@@ -68,17 +89,24 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
68
89
  };
69
90
  });
70
91
 
71
- saveRunTasks(loaded.manifest, updatedTasks);
92
+ saveRunTasks(fresh.manifest, updatedTasks);
93
+ let manifest = fresh.manifest;
94
+ if (manifest.status === "blocked" || manifest.status === "completed" || manifest.status === "failed" || manifest.status === "cancelled") {
95
+ manifest = updateRunStatus(manifest, "running", `Resumed ${resumed.size} waiting task(s).`);
96
+ }
97
+ for (const taskId of resumed) {
98
+ appendEvent(manifest.eventsPath, { type: "task.resumed", runId: manifest.runId, taskId, message: message || "Task re-queued after respond.", data: { mailboxIds } });
99
+ }
72
100
  try {
73
- saveCrewAgents(loaded.manifest, updatedTasks.map((task) => recordFromTask(loaded.manifest, task, "child-process")));
101
+ saveCrewAgents(fresh.manifest, updatedTasks.map((task) => recordFromTask(fresh.manifest, task, "child-process")));
74
102
  } catch (error) {
75
- logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${loaded.manifest.runId}`);
103
+ logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${fresh.manifest.runId}`);
76
104
  }
77
105
 
78
106
  const resumedIds = targetTasks.map((t) => t.id);
79
107
  return result(
80
108
  `Resumed ${resumedIds.length} waiting task(s): ${resumedIds.join(", ")}. Message: ${message || "(no message)"}`,
81
- { action: "respond", status: "ok", runId: loaded.manifest.runId, resumedIds, mailboxIds } as never,
109
+ { action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds, intent: `responding to ${resumedIds.join(", ")} in ${fresh.manifest.runId}` },
82
110
  );
83
111
  });
84
112
  }
@@ -4,13 +4,15 @@ import { allWorkflows, discoverWorkflows } from "../../workflows/discover-workfl
4
4
  import { loadConfig } from "../../config/config.ts";
5
5
  import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
6
6
  import { writeArtifact } from "../../state/artifact-store.ts";
7
+ import { registerActiveRun, unregisterActiveRun } from "../../state/active-run-registry.ts";
7
8
  import { createRunManifest, loadRunManifestById, updateRunStatus } from "../../state/state-store.ts";
8
9
  import { atomicWriteJson } from "../../state/atomic-write.ts";
9
10
  import { validateWorkflowForTeam } from "../../workflows/validate-workflow.ts";
10
11
  import { executeTeamRun } from "../../runtime/team-runner.ts";
11
12
  import { spawnBackgroundTeamRun } from "../../subagents/async-entry.ts";
12
13
  import { appendEvent, readEvents } from "../../state/event-log.ts";
13
- import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
14
+ import { resolveCrewRuntime, runtimeResolutionState } from "../../runtime/runtime-resolver.ts";
15
+ import { normalizeSkillOverride } from "../../runtime/skill-instructions.ts";
14
16
  import { expandParallelResearchWorkflow } from "../../runtime/parallel-research.ts";
15
17
  import { checkProcessLiveness, isActiveRunStatus } from "../../runtime/process-status.ts";
16
18
  import { hasAsyncStartMarker } from "../../runtime/async-marker.ts";
@@ -58,6 +60,7 @@ function scheduleBackgroundEarlyExitGuard(cwd: string, runId: string, pid: numbe
58
60
  export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
59
61
  const goal = params.goal ?? params.task;
60
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;
61
64
 
62
65
  const teams = allTeams(discoverTeams(ctx.cwd));
63
66
  const workflows = allWorkflows(discoverWorkflows(ctx.cwd));
@@ -91,6 +94,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
91
94
  return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
92
95
  }
93
96
 
97
+ const skillOverride = normalizeSkillOverride(params.skill);
94
98
  const { manifest, tasks, paths } = createRunManifest({
95
99
  cwd: ctx.cwd,
96
100
  team,
@@ -105,17 +109,35 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
105
109
  content: `${goal}\n`,
106
110
  producer: "team-tool",
107
111
  });
108
- const updatedManifest = { ...manifest, artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
112
+ const updatedManifest = { ...manifest, ...(skillOverride !== undefined ? { skillOverride } : {}), artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
109
113
  atomicWriteJson(paths.manifestPath, updatedManifest);
114
+ registerActiveRun(updatedManifest);
110
115
 
111
116
  const loadedConfig = loadConfig(ctx.cwd);
117
+ const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
118
+ const runtime = await resolveCrewRuntime(executedConfig);
119
+ const runtimeResolution = runtimeResolutionState(runtime);
120
+ const executionManifest = { ...updatedManifest, runtimeResolution, runConfig: executedConfig, updatedAt: new Date().toISOString() };
121
+ atomicWriteJson(paths.manifestPath, executionManifest);
122
+ appendEvent(executionManifest.eventsPath, { type: "runtime.resolved", runId: executionManifest.runId, message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution } });
112
123
  const runAsync = params.async ?? loadedConfig.config.asyncByDefault ?? false;
113
124
  if (runAsync) {
114
- const spawned = spawnBackgroundTeamRun(updatedManifest);
115
- const asyncManifest = { ...updatedManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
125
+ if (runtime.safety === "blocked") {
126
+ const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
127
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
128
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution, async: true } });
129
+ unregisterActiveRun(blocked.runId);
130
+ return result([
131
+ `Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
132
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
133
+ runtime.reason ?? "Child worker execution is disabled.",
134
+ ].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
135
+ }
136
+ const spawned = spawnBackgroundTeamRun(executionManifest);
137
+ const asyncManifest = { ...executionManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
116
138
  atomicWriteJson(paths.manifestPath, asyncManifest);
117
- appendEvent(updatedManifest.eventsPath, { type: "async.spawned", runId: updatedManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
118
- scheduleBackgroundEarlyExitGuard(ctx.cwd, updatedManifest.runId, spawned.pid, spawned.logPath);
139
+ appendEvent(executionManifest.eventsPath, { type: "async.spawned", runId: executionManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
140
+ scheduleBackgroundEarlyExitGuard(ctx.cwd, executionManifest.runId, spawned.pid, spawned.logPath);
119
141
  const text = [
120
142
  `Started async pi-crew run ${updatedManifest.runId}.`,
121
143
  `Team: ${team.name}`,
@@ -128,16 +150,32 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
128
150
  "",
129
151
  `Check status with: team status runId=${updatedManifest.runId}`,
130
152
  ].join("\n");
131
- 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}` });
132
154
  }
133
155
 
134
- const runtime = await resolveCrewRuntime(effectiveRunConfig(loadedConfig.config, params.config));
156
+ if (runtime.safety === "blocked") {
157
+ const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
158
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
159
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution } });
160
+ unregisterActiveRun(blocked.runId);
161
+ return result([
162
+ `Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
163
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
164
+ runtime.reason ?? "Child worker execution is disabled.",
165
+ "",
166
+ "To run effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
167
+ "Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
168
+ ].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
169
+ }
135
170
  const executeWorkers = runtime.kind !== "scaffold";
136
- const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
137
171
  if (executeWorkers && ctx.startForegroundRun) {
138
172
  ctx.onRunStarted?.(updatedManifest.runId);
139
173
  ctx.startForegroundRun(async (signal) => {
140
- await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
174
+ try {
175
+ await executeTeamRun({ manifest: executionManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride, signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
176
+ } finally {
177
+ unregisterActiveRun(updatedManifest.runId);
178
+ }
141
179
  }, updatedManifest.runId);
142
180
  const text = [
143
181
  `Started foreground pi-crew run ${updatedManifest.runId}.`,
@@ -151,9 +189,14 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
151
189
  "",
152
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.",
153
191
  ].join("\n");
154
- 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}` });
193
+ }
194
+ let executed: Awaited<ReturnType<typeof executeTeamRun>>;
195
+ try {
196
+ executed = await executeTeamRun({ manifest: executionManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
197
+ } finally {
198
+ unregisterActiveRun(updatedManifest.runId);
155
199
  }
156
- const executed = await executeTeamRun({ manifest: updatedManifest, tasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry, onJsonEvent: ctx.onJsonEvent });
157
200
  const text = [
158
201
  `Created pi-crew run ${executed.manifest.runId}.`,
159
202
  `Team: ${team.name}`,
@@ -9,6 +9,7 @@ import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
9
9
  import { checkProcessLiveness, isActiveRunStatus } from "../../runtime/process-status.ts";
10
10
  import { formatTaskGraphLines, waitingReason } from "../../runtime/task-display.ts";
11
11
  import { verifyTaskCompletion, formatOutputPreview } from "../../runtime/completion-guard.ts";
12
+ import { evaluateRunEffectiveness } from "../../runtime/effectiveness.ts";
12
13
  import type { PiTeamsToolResult } from "../tool-result.ts";
13
14
  import { result, type TeamContext } from "./context.ts";
14
15
 
@@ -51,6 +52,10 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
51
52
  groupJoinLines.push(`- ${String(message.data?.partial) === "true" ? "partial" : "completed"} request=${requestId} message=${message.id} ack=${timedOut ? "timeout" : ack}`);
52
53
  }
53
54
  const totalUsage = aggregateUsage(tasks);
55
+ const completedTasks = tasks.filter((task) => task.status === "completed");
56
+ const effectiveness = evaluateRunEffectiveness({ manifest, tasks, executeWorkers: manifest.runtimeResolution?.kind !== "scaffold", runtimeConfig: loadConfig(ctx.cwd).config.runtime });
57
+ const noObservedWorkTasks = effectiveness.noObservedWorkTaskIds.map((id) => tasks.find((task) => task.id === id)).filter((task): task is typeof tasks[number] => task !== undefined);
58
+ const attentionTasks = effectiveness.needsAttentionTaskIds.map((id) => tasks.find((task) => task.id === id)).filter((task): task is typeof tasks[number] => task !== undefined);
54
59
  const activeAgents = crewAgents.filter((agent) => agent.status === "running");
55
60
  const completedAgents = crewAgents.filter((agent) => agent.status !== "running");
56
61
  const waitingTasks = tasks.filter((task) => task.status === "queued" || task.status === "waiting");
@@ -61,6 +66,7 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
61
66
  `Workflow: ${manifest.workflow ?? "(none)"}`,
62
67
  `Status: ${manifest.status}`,
63
68
  `Workspace mode: ${manifest.workspaceMode}`,
69
+ ...(manifest.runtimeResolution ? [`Runtime: ${manifest.runtimeResolution.kind}`, `Runtime safety: ${manifest.runtimeResolution.safety}`, `Runtime requested: ${manifest.runtimeResolution.requestedMode}${manifest.runtimeResolution.reason ? ` (${manifest.runtimeResolution.reason})` : ""}`] : []),
64
70
  `Goal: ${manifest.goal}`,
65
71
  `Created: ${manifest.createdAt}`,
66
72
  `Updated: ${manifest.updatedAt}`,
@@ -72,7 +78,12 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
72
78
  "Tasks:",
73
79
  ...(tasks.length ? tasks.map((task) => `- ${task.id} [${task.status}] ${task.role} -> ${task.agent}${task.taskPacket ? ` scope=${task.taskPacket.scope}` : ""}${task.verification ? ` green=${task.verification.observedGreenLevel}/${task.verification.requiredGreenLevel}` : ""}${task.modelAttempts?.length ? ` attempts=${task.modelAttempts.length}` : ""}${task.modelRouting ? ` modelRouting=${task.modelRouting.requested ? `${task.modelRouting.requested}->` : ""}${task.modelRouting.resolved}${task.modelRouting.usedAttempt ? ` attempt=${task.modelRouting.usedAttempt + 1}` : ""}` : ""}${task.agentProgress?.activityState ? ` activityState=${task.agentProgress.activityState}` : ""}${attentionByTask.get(task.id)?.data?.reason ? ` attention=${String(attentionByTask.get(task.id)?.data?.reason)}` : ""}${task.jsonEvents !== undefined ? ` jsonEvents=${task.jsonEvents}` : ""}${task.usage ? ` usage=${JSON.stringify(task.usage)}` : ""}${task.resultArtifact ? ` result=${task.resultArtifact.path}` : ""}${task.transcriptArtifact ? ` transcript=${task.transcriptArtifact.path}` : ""}${task.worktree ? ` worktree=${task.worktree.path}` : ""}${task.error ? ` error=${task.error}` : ""}`) : ["- (none)"]),
74
80
  `Task counts: ${[...counts.entries()].map(([status, count]) => `${status}=${count}`).join(", ") || "none"}`,
75
- "Completion verification:",
81
+ "Effectiveness:",
82
+ `- observable=${effectiveness.observable}/${Math.max(1, effectiveness.completed)} completed tasks`,
83
+ `- workerExecution=${effectiveness.workerExecution} guard=${effectiveness.guardMode} severity=${effectiveness.severity}`,
84
+ `- noObservedWork=${effectiveness.noObservedWorkTaskIds.length ? effectiveness.noObservedWorkTaskIds.join(",") : "none"}`,
85
+ `- needsAttention=${effectiveness.needsAttentionTaskIds.length ? effectiveness.needsAttentionTaskIds.join(",") : "none"}`,
86
+ "Completion verification",
76
87
  ...(tasks.filter((t) => t.status === "completed").length ? tasks.filter((t) => t.status === "completed").map((t) => {
77
88
  const guard = verifyTaskCompletion(t, manifest);
78
89
  return `- ${t.id} green=${guard.greenLevel}/3${guard.warnings.length ? ` warnings=[${guard.warnings.join(", ")}]` : ""}`;
@@ -95,5 +106,5 @@ export function handleStatus(params: TeamToolParamsValue, ctx: TeamContext): PiT
95
106
  "Recent events:",
96
107
  ...(events.length ? events.map((event) => `- ${event.time} ${event.type}${event.taskId ? ` ${event.taskId}` : ""}${event.message ? `: ${event.message}` : ""}`) : ["- (none)"]),
97
108
  ];
98
- 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}` });
99
110
  }
@@ -6,5 +6,8 @@ export interface TeamToolDetails {
6
6
  abortedIds?: string[];
7
7
  missingIds?: string[];
8
8
  foreignIds?: string[];
9
+ intent?: string;
9
10
  resumedIds?: string[];
11
+ retriedTaskIds?: string[];
12
+ mailboxIds?: string[];
10
13
  }
@@ -29,24 +29,26 @@ import type { ArtifactDescriptor, TeamRunManifest, TeamTaskState } from "../stat
29
29
  import { executeTeamRun } from "../runtime/team-runner.ts";
30
30
  import { checkProcessLiveness, isActiveRunStatus } from "../runtime/process-status.ts";
31
31
  import { saveCrewAgents, readCrewAgents, recordFromTask } from "../runtime/crew-agent-records.ts";
32
- import { resolveCrewRuntime } from "../runtime/runtime-resolver.ts";
32
+ import { resolveCrewRuntime, runtimeResolutionState } from "../runtime/runtime-resolver.ts";
33
33
  import { applyAttentionState, formatActivityAge, resolveCrewControlConfig } from "../runtime/agent-control.ts";
34
34
  import { writeForegroundInterruptRequest } from "../runtime/foreground-control.ts";
35
35
  import { formatTaskGraphLines, waitingReason } from "../runtime/task-display.ts";
36
36
  import { directTeamAndWorkflowFromRun } from "../runtime/direct-run.ts";
37
37
  import { parsePiJsonOutput } from "../runtime/pi-json-output.ts";
38
38
  import { buildParentContext, configRecord, formatScoped, result, type TeamContext } from "./team-tool/context.ts";
39
- import { autonomousPatchFromConfig, configPatchFromConfig, formatAutonomyStatus } from "./team-tool/config-patch.ts";
39
+ import { autonomousPatchFromConfig, configPatchFromConfig, effectiveRunConfig, formatAutonomyStatus } from "./team-tool/config-patch.ts";
40
40
  import { handleApi } from "./team-tool/api.ts";
41
41
  import { handleRun } from "./team-tool/run.ts";
42
42
  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";
51
+ import { normalizeSkillOverride } from "../runtime/skill-instructions.ts";
50
52
 
51
53
  export type { TeamToolDetails } from "./team-tool-types.ts";
52
54
  export type { TeamContext } from "./team-tool/context.ts";
@@ -55,7 +57,7 @@ export { handleDoctor } from "./team-tool/doctor.ts";
55
57
  export { handleStatus } from "./team-tool/status.ts";
56
58
  export { handleArtifacts, handleEvents, handleSummary } from "./team-tool/inspect.ts";
57
59
  export { handleCleanup, handleExport, handleForget, handleImport, handleImports, handlePrune, handleWorktrees } from "./team-tool/lifecycle-actions.ts";
58
- export { handleCancel } from "./team-tool/cancel.ts";
60
+ export { handleRetry } from "./team-tool/cancel.ts";
59
61
  export { handlePlan } from "./team-tool/plan.ts";
60
62
  export { handleApi } from "./team-tool/api.ts";
61
63
 
@@ -176,18 +178,44 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
176
178
  const workflow = direct?.workflow ?? allWorkflows(discoverWorkflows(ctx.cwd)).find((candidate) => candidate.name === loaded.manifest.workflow);
177
179
  if (!workflow) return result(`Workflow '${loaded.manifest.workflow}' not found.`, { action: "resume", status: "error" }, true);
178
180
  return await withRunLock(loaded.manifest, async () => {
181
+ const loadedConfig = loadConfig(ctx.cwd);
179
182
  const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
180
183
  const resumeManifest = recovered.manifest;
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
+ }
192
+ const runtime = await resolveCrewRuntime(executedConfig);
193
+ const runtimeResolution = runtimeResolutionState(runtime);
194
+ const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
195
+ saveRunManifest(runtimeManifest);
196
+ appendEvent(runtimeManifest.eventsPath, { type: "runtime.resolved", runId: runtimeManifest.runId, message: `Runtime resolved for resume: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution, action: "resume" } });
197
+ if (runtime.safety === "blocked") {
198
+ const runningManifest = updateRunStatus(runtimeManifest, "running", "Checking worker runtime availability before resume.");
199
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to resume with no-op scaffold subagents.");
200
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, action: "resume" } });
201
+ return result([
202
+ `Blocked resume for pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
203
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
204
+ runtime.reason ?? "Child worker execution is disabled.",
205
+ "",
206
+ "To resume effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
207
+ "Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
208
+ ].join("\n"), { action: "resume", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
209
+ }
181
210
  const resetTasks = recovered.tasks.map((task) => task.status === "failed" || task.status === "cancelled" || task.status === "skipped" || task.status === "running" ? { ...task, status: "queued" as const, error: undefined, startedAt: undefined, finishedAt: undefined, claim: undefined } : task);
182
- saveRunTasks(resumeManifest, resetTasks);
183
- const replay = replayPendingMailboxMessages(resumeManifest);
184
- appendEvent(resumeManifest.eventsPath, { type: "run.resume_requested", runId: resumeManifest.runId, data: { replayedMailboxMessages: replay.messages.length, recoveredCheckpointTasks: recovered.recovered } });
185
- if (recovered.recovered.length) appendEvent(resumeManifest.eventsPath, { type: "task.checkpoint_recovered", runId: resumeManifest.runId, message: `Recovered ${recovered.recovered.length} task(s) from artifact-written checkpoints.`, data: { taskIds: recovered.recovered } });
186
- if (replay.messages.length) appendEvent(resumeManifest.eventsPath, { type: "mailbox.replayed", runId: resumeManifest.runId, message: `Replayed ${replay.messages.length} pending inbox message(s).`, data: { messageIds: replay.messages.map((message) => message.id), taskIds: replay.messages.map((message) => message.taskId).filter(Boolean) } });
187
- const loadedConfig = loadConfig(ctx.cwd);
188
- const runtime = await resolveCrewRuntime(loadedConfig.config);
211
+ saveRunTasks(runtimeManifest, resetTasks);
212
+ const replay = replayPendingMailboxMessages(runtimeManifest);
213
+ appendEvent(runtimeManifest.eventsPath, { type: "run.resume_requested", runId: runtimeManifest.runId, data: { replayedMailboxMessages: replay.messages.length, recoveredCheckpointTasks: recovered.recovered } });
214
+ if (recovered.recovered.length) appendEvent(runtimeManifest.eventsPath, { type: "task.checkpoint_recovered", runId: runtimeManifest.runId, message: `Recovered ${recovered.recovered.length} task(s) from artifact-written checkpoints.`, data: { taskIds: recovered.recovered } });
215
+ if (replay.messages.length) appendEvent(runtimeManifest.eventsPath, { type: "mailbox.replayed", runId: runtimeManifest.runId, message: `Replayed ${replay.messages.length} pending inbox message(s).`, data: { messageIds: replay.messages.map((message) => message.id), taskIds: replay.messages.map((message) => message.taskId).filter(Boolean) } });
189
216
  const executeWorkers = runtime.kind !== "scaffold";
190
- const executed = await executeTeamRun({ manifest: resumeManifest, tasks: resetTasks, team, workflow, agents, executeWorkers, limits: loadedConfig.config.limits, runtime, runtimeConfig: loadedConfig.config.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, signal: ctx.signal, reliability: loadedConfig.config.reliability, metricRegistry: ctx.metricRegistry });
217
+ const resumeSkillOverride = normalizeSkillOverride(params.skill) ?? runtimeManifest.skillOverride;
218
+ const executed = await executeTeamRun({ manifest: runtimeManifest, tasks: resetTasks, team, workflow, agents, executeWorkers, limits: executedConfig.limits, runtime, runtimeConfig: executedConfig.runtime, parentContext: buildParentContext(ctx), parentModel: ctx.model, modelRegistry: ctx.modelRegistry, modelOverride: params.model, skillOverride: resumeSkillOverride, signal: ctx.signal, reliability: executedConfig.reliability, metricRegistry: ctx.metricRegistry });
191
219
  return result([`Resumed run ${executed.manifest.runId}.`, `Status: ${executed.manifest.status}`, `Tasks: ${executed.tasks.length}`, `Artifacts: ${executed.manifest.artifactsRoot}`].join("\n"), { action: "resume", status: executed.manifest.status === "failed" ? "error" : "ok", runId: executed.manifest.runId, artifactsRoot: executed.manifest.artifactsRoot }, executed.manifest.status === "failed");
192
220
  });
193
221
  }
@@ -199,7 +227,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
199
227
  case "get": return handleGet(params, ctx);
200
228
  case "init": {
201
229
  const cfg = configRecord(params.config);
202
- const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true });
230
+ const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true, configScope: cfg.configScope === "project" || cfg.scope === "project" ? "project" : cfg.configScope === "none" || cfg.scope === "none" ? "none" : "global" });
203
231
  return result([
204
232
  "Initialized pi-crew project layout.",
205
233
  "Directories:",
@@ -207,6 +235,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
207
235
  "Copied builtin files:",
208
236
  ...(initialized.copiedFiles.length ? initialized.copiedFiles.map((file) => `- ${file}`) : ["- (none)"]),
209
237
  ...(initialized.skippedFiles.length ? ["Skipped existing files:", ...initialized.skippedFiles.map((file) => `- ${file}`)] : []),
238
+ `Config: ${initialized.configPath || "(none)"} (${initialized.configScope}${initialized.configCreated ? "; created" : initialized.configSkipped ? "; already existed" : "; unchanged"})`,
210
239
  `Gitignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
211
240
  ].join("\n"), { action: "init", status: "ok" });
212
241
  }
@@ -274,12 +303,14 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
274
303
  case "import": return handleImport(params, ctx);
275
304
  case "imports": return handleImports(params, ctx);
276
305
  case "settings": return handleSettings(params, ctx);
277
- case "prune": return handlePrune(params, ctx);
306
+ case "prune": return handlePrune(params, ctx);
278
307
  case "forget": return handleForget(params, ctx);
279
308
  case "run": return handleRun(params, ctx);
280
309
  case "status": return handleStatus(params, ctx);
281
310
  case "cancel": return handleCancel(params, ctx);
311
+ case "retry": return handleRetry(params, ctx);
282
312
  case "respond": return handleRespond(params, ctx);
313
+ case "parallel": return await handleParallel(params, ctx);
283
314
  case "plan": return handlePlan(params, ctx);
284
315
  case "resume": return handleResume(params, ctx);
285
316
  case "create": return handleCreate(params, ctx);