pi-crew 0.1.45 → 0.1.46

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 (198) hide show
  1. package/README.md +5 -5
  2. package/agents/analyst.md +1 -1
  3. package/agents/critic.md +1 -1
  4. package/agents/executor.md +1 -1
  5. package/agents/explorer.md +1 -1
  6. package/agents/planner.md +1 -1
  7. package/agents/reviewer.md +1 -1
  8. package/agents/security-reviewer.md +1 -1
  9. package/agents/test-engineer.md +1 -1
  10. package/agents/verifier.md +1 -1
  11. package/agents/writer.md +1 -1
  12. package/docs/next-upgrade-roadmap.md +733 -0
  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-awesome-agent-skills-distillation.md +100 -0
  18. package/docs/research-extension-examples.md +297 -297
  19. package/docs/research-extension-system.md +324 -324
  20. package/docs/research-oh-my-pi-distillation.md +322 -0
  21. package/docs/research-optimization-plan.md +548 -548
  22. package/docs/research-phase10-distillation.md +198 -198
  23. package/docs/research-phase11-distillation.md +201 -201
  24. package/docs/research-pi-coding-agent.md +357 -357
  25. package/docs/research-source-pi-crew-reference.md +174 -174
  26. package/docs/runtime-flow.md +148 -148
  27. package/docs/source-runtime-refactor-map.md +107 -83
  28. package/docs/usage.md +3 -3
  29. package/index.ts +6 -6
  30. package/install.mjs +52 -8
  31. package/package.json +1 -1
  32. package/schema.json +2 -1
  33. package/skills/async-worker-recovery/SKILL.md +42 -0
  34. package/skills/context-artifact-hygiene/SKILL.md +52 -0
  35. package/skills/delegation-patterns/SKILL.md +54 -0
  36. package/skills/mailbox-interactive/SKILL.md +40 -0
  37. package/skills/model-routing-context/SKILL.md +39 -0
  38. package/skills/multi-perspective-review/SKILL.md +58 -0
  39. package/skills/observability-reliability/SKILL.md +41 -0
  40. package/skills/ownership-session-security/SKILL.md +41 -0
  41. package/skills/pi-extension-lifecycle/SKILL.md +39 -0
  42. package/skills/requirements-to-task-packet/SKILL.md +63 -0
  43. package/skills/resource-discovery-config/SKILL.md +41 -0
  44. package/skills/runtime-state-reader/SKILL.md +44 -0
  45. package/skills/secure-agent-orchestration-review/SKILL.md +45 -0
  46. package/skills/state-mutation-locking/SKILL.md +42 -0
  47. package/skills/systematic-debugging/SKILL.md +67 -0
  48. package/skills/ui-render-performance/SKILL.md +39 -0
  49. package/skills/verification-before-done/SKILL.md +57 -0
  50. package/skills/worktree-isolation/SKILL.md +39 -0
  51. package/src/agents/agent-serializer.ts +34 -34
  52. package/src/agents/discover-agents.ts +12 -11
  53. package/src/config/config.ts +48 -24
  54. package/src/config/defaults.ts +14 -0
  55. package/src/extension/cross-extension-rpc.ts +82 -82
  56. package/src/extension/project-init.ts +62 -2
  57. package/src/extension/register.ts +11 -9
  58. package/src/extension/registration/commands.ts +32 -25
  59. package/src/extension/registration/compaction-guard.ts +125 -125
  60. package/src/extension/registration/subagent-helpers.ts +8 -0
  61. package/src/extension/registration/subagent-tools.ts +149 -148
  62. package/src/extension/registration/team-tool.ts +8 -6
  63. package/src/extension/run-bundle-schema.ts +89 -89
  64. package/src/extension/run-index.ts +13 -5
  65. package/src/extension/run-maintenance.ts +62 -43
  66. package/src/extension/team-tool/api.ts +25 -8
  67. package/src/extension/team-tool/cancel.ts +33 -4
  68. package/src/extension/team-tool/context.ts +5 -0
  69. package/src/extension/team-tool/handle-settings.ts +188 -188
  70. package/src/extension/team-tool/inspect.ts +41 -41
  71. package/src/extension/team-tool/lifecycle-actions.ts +91 -79
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/respond.ts +37 -17
  74. package/src/extension/team-tool/run.ts +52 -10
  75. package/src/extension/team-tool/status.ts +12 -1
  76. package/src/extension/team-tool-types.ts +2 -0
  77. package/src/extension/team-tool.ts +32 -11
  78. package/src/i18n.ts +184 -184
  79. package/src/observability/event-to-metric.ts +8 -1
  80. package/src/observability/exporters/otlp-exporter.ts +77 -77
  81. package/src/prompt/prompt-runtime.ts +72 -72
  82. package/src/runtime/agent-control.ts +63 -63
  83. package/src/runtime/agent-memory.ts +72 -72
  84. package/src/runtime/agent-observability.ts +114 -114
  85. package/src/runtime/async-marker.ts +26 -26
  86. package/src/runtime/attention-events.ts +28 -28
  87. package/src/runtime/background-runner.ts +59 -53
  88. package/src/runtime/cancellation.ts +51 -0
  89. package/src/runtime/child-pi.ts +457 -444
  90. package/src/runtime/completion-guard.ts +190 -190
  91. package/src/runtime/crash-recovery.ts +1 -0
  92. package/src/runtime/crew-agent-records.ts +38 -6
  93. package/src/runtime/deadletter.ts +1 -0
  94. package/src/runtime/delivery-coordinator.ts +46 -25
  95. package/src/runtime/direct-run.ts +35 -35
  96. package/src/runtime/effectiveness.ts +76 -0
  97. package/src/runtime/foreground-control.ts +82 -82
  98. package/src/runtime/green-contract.ts +46 -46
  99. package/src/runtime/group-join.ts +106 -106
  100. package/src/runtime/heartbeat-gradient.ts +28 -28
  101. package/src/runtime/heartbeat-watcher.ts +124 -124
  102. package/src/runtime/live-agent-control.ts +88 -87
  103. package/src/runtime/live-agent-manager.ts +103 -85
  104. package/src/runtime/live-control-realtime.ts +36 -36
  105. package/src/runtime/live-session-runtime.ts +309 -305
  106. package/src/runtime/manifest-cache.ts +17 -2
  107. package/src/runtime/model-fallback.ts +6 -4
  108. package/src/runtime/parallel-research.ts +44 -44
  109. package/src/runtime/pi-args.ts +18 -3
  110. package/src/runtime/pi-json-output.ts +111 -111
  111. package/src/runtime/policy-engine.ts +79 -79
  112. package/src/runtime/process-status.ts +5 -1
  113. package/src/runtime/progress-event-coalescer.ts +43 -43
  114. package/src/runtime/recovery-recipes.ts +74 -74
  115. package/src/runtime/retry-executor.ts +81 -64
  116. package/src/runtime/role-permission.ts +39 -39
  117. package/src/runtime/runtime-resolver.ts +22 -6
  118. package/src/runtime/session-resources.ts +25 -25
  119. package/src/runtime/session-snapshot.ts +59 -59
  120. package/src/runtime/session-usage.ts +79 -79
  121. package/src/runtime/sidechain-output.ts +29 -29
  122. package/src/runtime/skill-instructions.ts +222 -0
  123. package/src/runtime/stale-reconciler.ts +4 -14
  124. package/src/runtime/subagent-manager.ts +3 -0
  125. package/src/runtime/supervisor-contact.ts +59 -59
  126. package/src/runtime/task-display.ts +38 -38
  127. package/src/runtime/task-output-context.ts +127 -127
  128. package/src/runtime/task-runner/capabilities.ts +78 -0
  129. package/src/runtime/task-runner/live-executor.ts +105 -101
  130. package/src/runtime/task-runner/progress.ts +119 -119
  131. package/src/runtime/task-runner/prompt-builder.ts +3 -1
  132. package/src/runtime/task-runner/prompt-pipeline.ts +64 -0
  133. package/src/runtime/task-runner/result-utils.ts +14 -14
  134. package/src/runtime/task-runner/state-helpers.ts +22 -22
  135. package/src/runtime/task-runner.ts +44 -5
  136. package/src/runtime/team-runner.ts +78 -15
  137. package/src/runtime/worker-heartbeat.ts +21 -21
  138. package/src/runtime/worker-startup.ts +57 -57
  139. package/src/schema/config-schema.ts +1 -0
  140. package/src/schema/team-tool-schema.ts +3 -3
  141. package/src/state/active-run-registry.ts +165 -0
  142. package/src/state/contracts.ts +1 -1
  143. package/src/state/mailbox.ts +44 -4
  144. package/src/state/state-store.ts +8 -1
  145. package/src/state/task-claims.ts +44 -44
  146. package/src/state/types.ts +44 -2
  147. package/src/state/usage.ts +29 -29
  148. package/src/subagents/async-entry.ts +1 -1
  149. package/src/subagents/index.ts +3 -3
  150. package/src/subagents/live/control.ts +1 -1
  151. package/src/subagents/live/manager.ts +1 -1
  152. package/src/subagents/live/realtime.ts +1 -1
  153. package/src/subagents/live/session-runtime.ts +1 -1
  154. package/src/subagents/manager.ts +1 -1
  155. package/src/subagents/spawn.ts +1 -1
  156. package/src/teams/team-config.ts +1 -0
  157. package/src/teams/team-serializer.ts +38 -38
  158. package/src/types/diff.d.ts +18 -18
  159. package/src/ui/crew-footer.ts +101 -101
  160. package/src/ui/crew-select-list.ts +111 -111
  161. package/src/ui/crew-widget.ts +4 -3
  162. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  163. package/src/ui/dashboard-panes/progress-pane.ts +2 -0
  164. package/src/ui/dynamic-border.ts +25 -25
  165. package/src/ui/layout-primitives.ts +106 -106
  166. package/src/ui/loaders.ts +158 -158
  167. package/src/ui/render-diff.ts +119 -119
  168. package/src/ui/render-scheduler.ts +143 -143
  169. package/src/ui/run-snapshot-cache.ts +10 -2
  170. package/src/ui/snapshot-types.ts +2 -0
  171. package/src/ui/spinner.ts +17 -17
  172. package/src/ui/status-colors.ts +58 -58
  173. package/src/ui/syntax-highlight.ts +116 -116
  174. package/src/utils/atomic-write.ts +33 -33
  175. package/src/utils/completion-dedupe.ts +63 -63
  176. package/src/utils/frontmatter.ts +68 -68
  177. package/src/utils/git.ts +262 -262
  178. package/src/utils/ids.ts +12 -12
  179. package/src/utils/names.ts +27 -27
  180. package/src/utils/paths.ts +4 -2
  181. package/src/utils/redaction.ts +44 -44
  182. package/src/utils/safe-paths.ts +47 -47
  183. package/src/utils/sleep.ts +32 -32
  184. package/src/workflows/validate-workflow.ts +40 -40
  185. package/src/workflows/workflow-config.ts +1 -0
  186. package/src/worktree/branch-freshness.ts +45 -45
  187. package/teams/default.team.md +12 -12
  188. package/teams/fast-fix.team.md +11 -11
  189. package/teams/implementation.team.md +18 -18
  190. package/teams/parallel-research.team.md +14 -14
  191. package/teams/research.team.md +11 -11
  192. package/teams/review.team.md +12 -12
  193. package/workflows/default.workflow.md +29 -29
  194. package/workflows/fast-fix.workflow.md +22 -22
  195. package/workflows/implementation.workflow.md +38 -38
  196. package/workflows/parallel-research.workflow.md +46 -46
  197. package/workflows/research.workflow.md +22 -22
  198. package/workflows/review.workflow.md +30 -30
@@ -1,79 +1,91 @@
1
- import * as fs from "node:fs";
2
- import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
3
- import { appendEvent } from "../../state/event-log.ts";
4
- import { loadRunManifestById } from "../../state/state-store.ts";
5
- import { cleanupRunWorktrees } from "../../worktree/cleanup.ts";
6
- import { listImportedRuns } from "../import-index.ts";
7
- import { exportRunBundle } from "../run-export.ts";
8
- import { importRunBundle } from "../run-import.ts";
9
- import { pruneFinishedRuns } from "../run-maintenance.ts";
10
- import type { PiTeamsToolResult } from "../tool-result.ts";
11
- import { configRecord, result, type TeamContext } from "./context.ts";
12
-
13
- export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
14
- if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
15
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
16
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "worktrees", status: "error" }, true);
17
- const withWorktrees = loaded.tasks.filter((task) => task.worktree);
18
- const lines = [`Worktrees for ${loaded.manifest.runId}:`, ...(withWorktrees.length ? withWorktrees.map((task) => `- ${task.id}: ${task.worktree!.path} branch=${task.worktree!.branch} reused=${task.worktree!.reused ? "true" : "false"}`) : ["- (none)"])];
19
- return result(lines.join("\n"), { action: "worktrees", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
20
- }
21
-
22
- export function handleImports(_params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
23
- const imports = listImportedRuns(ctx.cwd);
24
- const lines = ["Imported pi-crew runs:", ...(imports.length ? imports.map((entry) => `- ${entry.runId} (${entry.scope})${entry.status ? ` [${entry.status}]` : ""} ${entry.team ?? "unknown"}/${entry.workflow ?? "none"}: ${entry.goal ?? ""}\n Bundle: ${entry.bundlePath}\n Summary: ${entry.summaryPath}`) : ["- (none)"])];
25
- return result(lines.join("\n"), { action: "imports", status: "ok" });
26
- }
27
-
28
- export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
29
- const cfg = configRecord(params.config);
30
- const bundlePath = typeof cfg.path === "string" ? cfg.path : typeof cfg.bundlePath === "string" ? cfg.bundlePath : undefined;
31
- if (!bundlePath) return result("Import requires config.path pointing at run-export.json.", { action: "import", status: "error" }, true);
32
- const scope = cfg.scope === "user" ? "user" : "project";
33
- try {
34
- const imported = importRunBundle(ctx.cwd, bundlePath, scope);
35
- return result([`Imported run bundle ${imported.runId}.`, `Bundle: ${imported.bundlePath}`, `Summary: ${imported.summaryPath}`].join("\n"), { action: "import", status: "ok" });
36
- } catch (error) {
37
- const message = error instanceof Error ? error.message : String(error);
38
- return result(`Import failed: ${message}`, { action: "import", status: "error" }, true);
39
- }
40
- }
41
-
42
- export function handleExport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
43
- if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
44
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
45
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
46
- const exported = exportRunBundle(loaded.manifest, loaded.tasks);
47
- appendEvent(loaded.manifest.eventsPath, { type: "run.exported", runId: loaded.manifest.runId, data: exported });
48
- return result([`Exported run ${loaded.manifest.runId}.`, `JSON: ${exported.jsonPath}`, `Markdown: ${exported.markdownPath}`].join("\n"), { action: "export", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
49
- }
50
-
51
- export function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
52
- const keep = params.keep ?? 20;
53
- if (!params.confirm) return result("prune requires confirm: true.", { action: "prune", status: "error" }, true);
54
- if (keep < 0 || !Number.isInteger(keep)) return result("keep must be an integer >= 0.", { action: "prune", status: "error" }, true);
55
- const pruned = pruneFinishedRuns(ctx.cwd, keep);
56
- return result([`Pruned finished pi-crew runs.`, `Kept: ${pruned.kept.length}`, `Removed: ${pruned.removed.length}`, ...(pruned.removed.length ? ["Removed runs:", ...pruned.removed.map((runId) => `- ${runId}`)] : [])].join("\n"), { action: "prune", status: "ok" });
57
- }
58
-
59
- export function handleForget(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
60
- if (!params.runId) return result("Forget requires runId.", { action: "forget", status: "error" }, true);
61
- if (!params.confirm) return result("forget requires confirm: true.", { action: "forget", status: "error" }, true);
62
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
63
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
64
- const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
65
- if (cleanup.preserved.length > 0 && !params.force) return result([`Run '${params.runId}' has preserved worktrees. Use force: true to forget anyway.`, ...cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`)].join("\n"), { action: "forget", status: "error", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot }, true);
66
- fs.rmSync(loaded.manifest.stateRoot, { recursive: true, force: true });
67
- fs.rmSync(loaded.manifest.artifactsRoot, { recursive: true, force: true });
68
- return result([`Forgot run ${loaded.manifest.runId}.`, `Removed state: ${loaded.manifest.stateRoot}`, `Removed artifacts: ${loaded.manifest.artifactsRoot}`, ...(cleanup.removed.length ? ["Removed worktrees:", ...cleanup.removed.map((item) => `- ${item}`)] : [])].join("\n"), { action: "forget", status: "ok", runId: loaded.manifest.runId });
69
- }
70
-
71
- export function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
72
- if (!params.runId) return result("Cleanup requires runId.", { action: "cleanup", status: "error" }, true);
73
- const loaded = loadRunManifestById(ctx.cwd, params.runId);
74
- if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
75
- const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
76
- appendEvent(loaded.manifest.eventsPath, { type: "worktree.cleanup", runId: loaded.manifest.runId, data: { removed: cleanup.removed, preserved: cleanup.preserved, artifacts: cleanup.artifactPaths } });
77
- const lines = [`Worktree cleanup for ${loaded.manifest.runId}:`, "Removed:", ...(cleanup.removed.length ? cleanup.removed.map((item) => `- ${item}`) : ["- (none)"]), "Preserved:", ...(cleanup.preserved.length ? cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`) : ["- (none)"]), "Artifacts:", ...(cleanup.artifactPaths.length ? cleanup.artifactPaths.map((item) => `- ${item}`) : ["- (none)"])];
78
- return result(lines.join("\n"), { action: "cleanup", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
79
- }
1
+ import * as fs from "node:fs";
2
+ import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
3
+ import { appendEvent } from "../../state/event-log.ts";
4
+ import { loadRunManifestById } from "../../state/state-store.ts";
5
+ import { cleanupRunWorktrees } from "../../worktree/cleanup.ts";
6
+ import { listImportedRuns } from "../import-index.ts";
7
+ import { exportRunBundle } from "../run-export.ts";
8
+ import { importRunBundle } from "../run-import.ts";
9
+ import { pruneFinishedRuns } from "../run-maintenance.ts";
10
+ import type { PiTeamsToolResult } from "../tool-result.ts";
11
+ import { configRecord, result, type TeamContext } from "./context.ts";
12
+
13
+ function intentFromParams(params: TeamToolParamsValue): string | undefined {
14
+ const cfg = configRecord(params.config);
15
+ const rawIntent = cfg.intent ?? cfg._intent;
16
+ if (typeof rawIntent !== "string") return undefined;
17
+ const intent = rawIntent.replace(/\s+/g, " ").trim();
18
+ return intent ? intent.slice(0, 500) : undefined;
19
+ }
20
+
21
+ export function handleWorktrees(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
22
+ if (!params.runId) return result("Worktrees requires runId.", { action: "worktrees", status: "error" }, true);
23
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
24
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "worktrees", status: "error" }, true);
25
+ const withWorktrees = loaded.tasks.filter((task) => task.worktree);
26
+ const lines = [`Worktrees for ${loaded.manifest.runId}:`, ...(withWorktrees.length ? withWorktrees.map((task) => `- ${task.id}: ${task.worktree!.path} branch=${task.worktree!.branch} reused=${task.worktree!.reused ? "true" : "false"}`) : ["- (none)"])];
27
+ return result(lines.join("\n"), { action: "worktrees", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
28
+ }
29
+
30
+ export function handleImports(_params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
31
+ const imports = listImportedRuns(ctx.cwd);
32
+ const lines = ["Imported pi-crew runs:", ...(imports.length ? imports.map((entry) => `- ${entry.runId} (${entry.scope})${entry.status ? ` [${entry.status}]` : ""} ${entry.team ?? "unknown"}/${entry.workflow ?? "none"}: ${entry.goal ?? ""}\n Bundle: ${entry.bundlePath}\n Summary: ${entry.summaryPath}`) : ["- (none)"])];
33
+ return result(lines.join("\n"), { action: "imports", status: "ok" });
34
+ }
35
+
36
+ export function handleImport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
37
+ const cfg = configRecord(params.config);
38
+ const bundlePath = typeof cfg.path === "string" ? cfg.path : typeof cfg.bundlePath === "string" ? cfg.bundlePath : undefined;
39
+ if (!bundlePath) return result("Import requires config.path pointing at run-export.json.", { action: "import", status: "error" }, true);
40
+ const scope = cfg.scope === "user" ? "user" : "project";
41
+ try {
42
+ const imported = importRunBundle(ctx.cwd, bundlePath, scope);
43
+ return result([`Imported run bundle ${imported.runId}.`, `Bundle: ${imported.bundlePath}`, `Summary: ${imported.summaryPath}`].join("\n"), { action: "import", status: "ok" });
44
+ } catch (error) {
45
+ const message = error instanceof Error ? error.message : String(error);
46
+ return result(`Import failed: ${message}`, { action: "import", status: "error" }, true);
47
+ }
48
+ }
49
+
50
+ export function handleExport(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
51
+ if (!params.runId) return result("Export requires runId.", { action: "export", status: "error" }, true);
52
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
53
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "export", status: "error" }, true);
54
+ const exported = exportRunBundle(loaded.manifest, loaded.tasks);
55
+ appendEvent(loaded.manifest.eventsPath, { type: "run.exported", runId: loaded.manifest.runId, data: exported });
56
+ return result([`Exported run ${loaded.manifest.runId}.`, `JSON: ${exported.jsonPath}`, `Markdown: ${exported.markdownPath}`].join("\n"), { action: "export", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
57
+ }
58
+
59
+ export function handlePrune(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
60
+ const keep = params.keep ?? 20;
61
+ if (!params.confirm) return result("prune requires confirm: true.", { action: "prune", status: "error" }, true);
62
+ if (keep < 0 || !Number.isInteger(keep)) return result("keep must be an integer >= 0.", { action: "prune", status: "error" }, true);
63
+ const intent = intentFromParams(params);
64
+ const pruned = pruneFinishedRuns(ctx.cwd, keep, { intent });
65
+ return result([`Pruned finished pi-crew runs.`, `Kept: ${pruned.kept.length}`, `Removed: ${pruned.removed.length}`, ...(pruned.auditPath ? [`Audit: ${pruned.auditPath}`] : []), ...(pruned.removed.length ? ["Removed runs:", ...pruned.removed.map((runId) => `- ${runId}`)] : [])].join("\n"), { action: "prune", status: "ok", intent });
66
+ }
67
+
68
+ export function handleForget(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
69
+ if (!params.runId) return result("Forget requires runId.", { action: "forget", status: "error" }, true);
70
+ if (!params.confirm) return result("forget requires confirm: true.", { action: "forget", status: "error" }, true);
71
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
72
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "forget", status: "error" }, true);
73
+ const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
74
+ if (cleanup.preserved.length > 0 && !params.force) return result([`Run '${params.runId}' has preserved worktrees. Use force: true to forget anyway.`, ...cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`)].join("\n"), { action: "forget", status: "error", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot }, true);
75
+ const intent = intentFromParams(params);
76
+ appendEvent(loaded.manifest.eventsPath, { type: "run.forget_requested", runId: loaded.manifest.runId, message: "Run state and artifacts are being forgotten.", data: { force: params.force === true, removedWorktrees: cleanup.removed, preservedWorktrees: cleanup.preserved, intent } });
77
+ fs.rmSync(loaded.manifest.stateRoot, { recursive: true, force: true });
78
+ fs.rmSync(loaded.manifest.artifactsRoot, { recursive: true, force: true });
79
+ return result([`Forgot run ${loaded.manifest.runId}.`, `Removed state: ${loaded.manifest.stateRoot}`, `Removed artifacts: ${loaded.manifest.artifactsRoot}`, ...(cleanup.removed.length ? ["Removed worktrees:", ...cleanup.removed.map((item) => `- ${item}`)] : [])].join("\n"), { action: "forget", status: "ok", runId: loaded.manifest.runId, intent });
80
+ }
81
+
82
+ export function handleCleanup(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
83
+ if (!params.runId) return result("Cleanup requires runId.", { action: "cleanup", status: "error" }, true);
84
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
85
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cleanup", status: "error" }, true);
86
+ const cleanup = cleanupRunWorktrees(loaded.manifest, { force: params.force });
87
+ const intent = intentFromParams(params);
88
+ appendEvent(loaded.manifest.eventsPath, { type: "worktree.cleanup", runId: loaded.manifest.runId, data: { removed: cleanup.removed, preserved: cleanup.preserved, artifacts: cleanup.artifactPaths, intent } });
89
+ const lines = [`Worktree cleanup for ${loaded.manifest.runId}:`, "Removed:", ...(cleanup.removed.length ? cleanup.removed.map((item) => `- ${item}`) : ["- (none)"]), "Preserved:", ...(cleanup.preserved.length ? cleanup.preserved.map((item) => `- ${item.path}: ${item.reason}`) : ["- (none)"]), "Artifacts:", ...(cleanup.artifactPaths.length ? cleanup.artifactPaths.map((item) => `- ${item}`) : ["- (none)"])];
90
+ return result(lines.join("\n"), { action: "cleanup", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot, intent });
91
+ }
@@ -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
+ }
@@ -1,6 +1,7 @@
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";
3
+ import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
4
+ import { appendEvent } from "../../state/event-log.ts";
4
5
  import { appendMailboxMessage } from "../../state/mailbox.ts";
5
6
  import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
6
7
  import { logInternalError } from "../../utils/internal-error.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,29 @@ 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" },
53
63
  });
54
64
  mailboxIds.push(mailbox.id);
55
65
  }
56
66
 
57
- // Transition waiting tasks back to running
58
- const updatedTasks = loaded.tasks.map((task) => {
67
+ // Re-queue waiting tasks so durable scheduler/resume can pick them up again.
68
+ const updatedTasks = fresh.tasks.map((task) => {
59
69
  if (!resumed.has(task.id)) return task;
60
70
  return {
61
71
  ...task,
62
- status: "running" as const,
72
+ status: "queued" as const,
73
+ startedAt: undefined,
74
+ finishedAt: undefined,
75
+ error: undefined,
63
76
  adaptive: {
64
77
  ...task.adaptive,
65
78
  phase: "resumed",
@@ -68,17 +81,24 @@ export function handleRespond(params: TeamToolParamsValue, ctx: TeamContext): Pi
68
81
  };
69
82
  });
70
83
 
71
- saveRunTasks(loaded.manifest, updatedTasks);
84
+ saveRunTasks(fresh.manifest, updatedTasks);
85
+ let manifest = fresh.manifest;
86
+ if (manifest.status === "blocked" || manifest.status === "completed" || manifest.status === "failed" || manifest.status === "cancelled") {
87
+ manifest = updateRunStatus(manifest, "running", `Resumed ${resumed.size} waiting task(s).`);
88
+ }
89
+ for (const taskId of resumed) {
90
+ appendEvent(manifest.eventsPath, { type: "task.resumed", runId: manifest.runId, taskId, message: message || "Task re-queued after respond.", data: { mailboxIds } });
91
+ }
72
92
  try {
73
- saveCrewAgents(loaded.manifest, updatedTasks.map((task) => recordFromTask(loaded.manifest, task, "child-process")));
93
+ saveCrewAgents(fresh.manifest, updatedTasks.map((task) => recordFromTask(fresh.manifest, task, "child-process")));
74
94
  } catch (error) {
75
- logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${loaded.manifest.runId}`);
95
+ logInternalError("team-tool.handleRespond.crewAgents", error, `runId=${fresh.manifest.runId}`);
76
96
  }
77
97
 
78
98
  const resumedIds = targetTasks.map((t) => t.id);
79
99
  return result(
80
100
  `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,
101
+ { action: "respond", status: "ok", runId: fresh.manifest.runId, resumedIds, mailboxIds },
82
102
  );
83
103
  });
84
104
  }
@@ -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";
@@ -91,6 +93,7 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
91
93
  return result([`Workflow '${workflow.name}' is not valid for team '${team.name}':`, ...validationErrors.map((error) => `- ${error}`)].join("\n"), { action: "run", status: "error" }, true);
92
94
  }
93
95
 
96
+ const skillOverride = normalizeSkillOverride(params.skill);
94
97
  const { manifest, tasks, paths } = createRunManifest({
95
98
  cwd: ctx.cwd,
96
99
  team,
@@ -105,17 +108,35 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
105
108
  content: `${goal}\n`,
106
109
  producer: "team-tool",
107
110
  });
108
- const updatedManifest = { ...manifest, artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
111
+ const updatedManifest = { ...manifest, ...(skillOverride !== undefined ? { skillOverride } : {}), artifacts: [goalArtifact], summary: "Run manifest created; worker execution is not implemented yet." };
109
112
  atomicWriteJson(paths.manifestPath, updatedManifest);
113
+ registerActiveRun(updatedManifest);
110
114
 
111
115
  const loadedConfig = loadConfig(ctx.cwd);
116
+ const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
117
+ const runtime = await resolveCrewRuntime(executedConfig);
118
+ const runtimeResolution = runtimeResolutionState(runtime);
119
+ const executionManifest = { ...updatedManifest, runtimeResolution, runConfig: executedConfig, updatedAt: new Date().toISOString() };
120
+ atomicWriteJson(paths.manifestPath, executionManifest);
121
+ appendEvent(executionManifest.eventsPath, { type: "runtime.resolved", runId: executionManifest.runId, message: `Runtime resolved: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution } });
112
122
  const runAsync = params.async ?? loadedConfig.config.asyncByDefault ?? false;
113
123
  if (runAsync) {
114
- const spawned = spawnBackgroundTeamRun(updatedManifest);
115
- const asyncManifest = { ...updatedManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
124
+ if (runtime.safety === "blocked") {
125
+ const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
126
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
127
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution, async: true } });
128
+ unregisterActiveRun(blocked.runId);
129
+ return result([
130
+ `Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
131
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
132
+ runtime.reason ?? "Child worker execution is disabled.",
133
+ ].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
134
+ }
135
+ const spawned = spawnBackgroundTeamRun(executionManifest);
136
+ const asyncManifest = { ...executionManifest, async: { pid: spawned.pid, logPath: spawned.logPath, spawnedAt: new Date().toISOString() } };
116
137
  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);
138
+ appendEvent(executionManifest.eventsPath, { type: "async.spawned", runId: executionManifest.runId, data: { pid: spawned.pid, logPath: spawned.logPath } });
139
+ scheduleBackgroundEarlyExitGuard(ctx.cwd, executionManifest.runId, spawned.pid, spawned.logPath);
119
140
  const text = [
120
141
  `Started async pi-crew run ${updatedManifest.runId}.`,
121
142
  `Team: ${team.name}`,
@@ -131,13 +152,29 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
131
152
  return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
132
153
  }
133
154
 
134
- const runtime = await resolveCrewRuntime(effectiveRunConfig(loadedConfig.config, params.config));
155
+ if (runtime.safety === "blocked") {
156
+ const runningManifest = updateRunStatus(executionManifest, "running", "Checking worker runtime availability.");
157
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to create no-op scaffold subagents.");
158
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, runtimeResolution } });
159
+ unregisterActiveRun(blocked.runId);
160
+ return result([
161
+ `Blocked pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
162
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
163
+ runtime.reason ?? "Child worker execution is disabled.",
164
+ "",
165
+ "To run effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
166
+ "Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
167
+ ].join("\n"), { action: "run", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
168
+ }
135
169
  const executeWorkers = runtime.kind !== "scaffold";
136
- const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
137
170
  if (executeWorkers && ctx.startForegroundRun) {
138
171
  ctx.onRunStarted?.(updatedManifest.runId);
139
172
  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 });
173
+ try {
174
+ 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 });
175
+ } finally {
176
+ unregisterActiveRun(updatedManifest.runId);
177
+ }
141
178
  }, updatedManifest.runId);
142
179
  const text = [
143
180
  `Started foreground pi-crew run ${updatedManifest.runId}.`,
@@ -153,7 +190,12 @@ export async function handleRun(params: TeamToolParamsValue, ctx: TeamContext):
153
190
  ].join("\n");
154
191
  return result(text, { action: "run", status: "ok", runId: updatedManifest.runId, artifactsRoot: updatedManifest.artifactsRoot });
155
192
  }
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 });
193
+ let executed: Awaited<ReturnType<typeof executeTeamRun>>;
194
+ try {
195
+ 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 });
196
+ } finally {
197
+ unregisterActiveRun(updatedManifest.runId);
198
+ }
157
199
  const text = [
158
200
  `Created pi-crew run ${executed.manifest.runId}.`,
159
201
  `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(", ")}]` : ""}`;
@@ -6,5 +6,7 @@ export interface TeamToolDetails {
6
6
  abortedIds?: string[];
7
7
  missingIds?: string[];
8
8
  foreignIds?: string[];
9
+ intent?: string;
9
10
  resumedIds?: string[];
11
+ mailboxIds?: string[];
10
12
  }
@@ -29,14 +29,14 @@ 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";
@@ -47,6 +47,7 @@ import { handleCancel } from "./team-tool/cancel.ts";
47
47
  import { handleRespond } from "./team-tool/respond.ts";
48
48
  import { handlePlan } from "./team-tool/plan.ts";
49
49
  import { logInternalError } from "../utils/internal-error.ts";
50
+ import { normalizeSkillOverride } from "../runtime/skill-instructions.ts";
50
51
 
51
52
  export type { TeamToolDetails } from "./team-tool-types.ts";
52
53
  export type { TeamContext } from "./team-tool/context.ts";
@@ -176,18 +177,37 @@ export async function handleResume(params: TeamToolParamsValue, ctx: TeamContext
176
177
  const workflow = direct?.workflow ?? allWorkflows(discoverWorkflows(ctx.cwd)).find((candidate) => candidate.name === loaded.manifest.workflow);
177
178
  if (!workflow) return result(`Workflow '${loaded.manifest.workflow}' not found.`, { action: "resume", status: "error" }, true);
178
179
  return await withRunLock(loaded.manifest, async () => {
180
+ const loadedConfig = loadConfig(ctx.cwd);
179
181
  const recovered = recoverCheckpointedTasks(loaded.manifest, loaded.tasks);
180
182
  const resumeManifest = recovered.manifest;
183
+ const executedConfig = effectiveRunConfig(loadedConfig.config, params.config);
184
+ const runtime = await resolveCrewRuntime(executedConfig);
185
+ const runtimeResolution = runtimeResolutionState(runtime);
186
+ const runtimeManifest = { ...resumeManifest, runtimeResolution, updatedAt: new Date().toISOString() };
187
+ saveRunManifest(runtimeManifest);
188
+ appendEvent(runtimeManifest.eventsPath, { type: "runtime.resolved", runId: runtimeManifest.runId, message: `Runtime resolved for resume: ${runtime.kind} safety=${runtime.safety}`, data: { runtimeResolution, action: "resume" } });
189
+ if (runtime.safety === "blocked") {
190
+ const runningManifest = updateRunStatus(runtimeManifest, "running", "Checking worker runtime availability before resume.");
191
+ const blocked = updateRunStatus(runningManifest, "blocked", runtime.reason ?? "Child worker execution is disabled; refusing to resume with no-op scaffold subagents.");
192
+ appendEvent(blocked.eventsPath, { type: "run.blocked", runId: blocked.runId, message: blocked.summary, data: { runtime, action: "resume" } });
193
+ return result([
194
+ `Blocked resume for pi-crew run ${blocked.runId}: real subagent workers are disabled.`,
195
+ `Runtime: ${runtime.kind} (requested ${runtime.requestedMode})`,
196
+ runtime.reason ?? "Child worker execution is disabled.",
197
+ "",
198
+ "To resume effective subagents, remove executeWorkers=false / PI_CREW_EXECUTE_WORKERS=0 / PI_TEAMS_EXECUTE_WORKERS=0 or set runtime.mode=child-process.",
199
+ "Use runtime.mode=scaffold only for explicit dry-run prompt/artifact generation.",
200
+ ].join("\n"), { action: "resume", status: "error", runId: blocked.runId, artifactsRoot: blocked.artifactsRoot }, true);
201
+ }
181
202
  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);
203
+ saveRunTasks(runtimeManifest, resetTasks);
204
+ const replay = replayPendingMailboxMessages(runtimeManifest);
205
+ appendEvent(runtimeManifest.eventsPath, { type: "run.resume_requested", runId: runtimeManifest.runId, data: { replayedMailboxMessages: replay.messages.length, recoveredCheckpointTasks: recovered.recovered } });
206
+ 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 } });
207
+ 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
208
  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 });
209
+ const resumeSkillOverride = normalizeSkillOverride(params.skill) ?? runtimeManifest.skillOverride;
210
+ 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
211
  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
212
  });
193
213
  }
@@ -199,7 +219,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
199
219
  case "get": return handleGet(params, ctx);
200
220
  case "init": {
201
221
  const cfg = configRecord(params.config);
202
- const initialized = initializeProject(ctx.cwd, { copyBuiltins: cfg.copyBuiltins === true, overwrite: cfg.overwrite === true });
222
+ 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
223
  return result([
204
224
  "Initialized pi-crew project layout.",
205
225
  "Directories:",
@@ -207,6 +227,7 @@ export async function handleTeamTool(params: TeamToolParamsValue, ctx: TeamConte
207
227
  "Copied builtin files:",
208
228
  ...(initialized.copiedFiles.length ? initialized.copiedFiles.map((file) => `- ${file}`) : ["- (none)"]),
209
229
  ...(initialized.skippedFiles.length ? ["Skipped existing files:", ...initialized.skippedFiles.map((file) => `- ${file}`)] : []),
230
+ `Config: ${initialized.configPath || "(none)"} (${initialized.configScope}${initialized.configCreated ? "; created" : initialized.configSkipped ? "; already existed" : "; unchanged"})`,
210
231
  `Gitignore: ${initialized.gitignorePath} (${initialized.gitignoreUpdated ? "updated" : "already configured"})`,
211
232
  ].join("\n"), { action: "init", status: "ok" });
212
233
  }