pi-crew 0.1.46 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -1,62 +1,68 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import type { TeamRunManifest } from "../state/types.ts";
4
- import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
- import { projectCrewRoot } from "../utils/paths.ts";
6
- import { listRuns } from "./run-index.ts";
7
- import { logInternalError } from "../utils/internal-error.ts";
8
- import { redactSecrets } from "../utils/redaction.ts";
9
-
10
- export interface PruneRunsResult {
11
- kept: string[];
12
- removed: string[];
13
- auditPath?: string;
14
- }
15
-
16
- export interface PruneRunsOptions {
17
- intent?: string;
18
- }
19
-
20
- function isFinished(run: TeamRunManifest): boolean {
21
- return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
22
- }
23
-
24
- function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
25
- try {
26
- const crewRoot = projectCrewRoot(cwd);
27
- resolveRealContainedPath(crewRoot, run.stateRoot);
28
- resolveRealContainedPath(crewRoot, run.artifactsRoot);
29
- return true;
30
- } catch {
31
- return false;
32
- }
33
- }
34
-
35
- function appendPruneAudit(cwd: string, payload: Record<string, unknown>): string | undefined {
36
- try {
37
- const filePath = path.join(projectCrewRoot(cwd), "audit", "prune.jsonl");
38
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
39
- fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ ...payload, auditedAt: new Date().toISOString() }))}\n`, "utf-8");
40
- return filePath;
41
- } catch (error) {
42
- logInternalError("prune.audit-write", error, `cwd=${cwd}`);
43
- return undefined;
44
- }
45
- }
46
-
47
- export function pruneFinishedRuns(cwd: string, keep: number, options: PruneRunsOptions = {}): PruneRunsResult {
48
- const finished = listRuns(cwd).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
49
- const kept = finished.slice(0, keep).map((run) => run.runId);
50
- const removed: string[] = [];
51
- for (const run of finished.slice(keep)) {
52
- if (!isSafeToPrune(cwd, run)) {
53
- logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
54
- continue;
55
- }
56
- fs.rmSync(run.stateRoot, { recursive: true, force: true });
57
- fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
58
- removed.push(run.runId);
59
- }
60
- const auditPath = appendPruneAudit(cwd, { action: "prune", keep, intent: options.intent, kept, removed });
61
- return { kept, removed, auditPath };
62
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { TeamRunManifest } from "../state/types.ts";
4
+ import { resolveRealContainedPath } from "../utils/safe-paths.ts";
5
+ import { projectCrewRoot } from "../utils/paths.ts";
6
+ import { listRuns } from "./run-index.ts";
7
+ import { logInternalError } from "../utils/internal-error.ts";
8
+ import { redactSecrets } from "../utils/redaction.ts";
9
+ import { createCancellationToken } from "../runtime/cancellation-token.ts";
10
+
11
+ export interface PruneRunsResult {
12
+ kept: string[];
13
+ removed: string[];
14
+ auditPath?: string;
15
+ }
16
+
17
+ export interface PruneRunsOptions {
18
+ intent?: string;
19
+ signal?: AbortSignal;
20
+ }
21
+
22
+ function isFinished(run: TeamRunManifest): boolean {
23
+ return run.status === "completed" || run.status === "failed" || run.status === "cancelled" || run.status === "blocked";
24
+ }
25
+
26
+ function isSafeToPrune(cwd: string, run: TeamRunManifest): boolean {
27
+ try {
28
+ const crewRoot = projectCrewRoot(cwd);
29
+ resolveRealContainedPath(crewRoot, run.stateRoot);
30
+ resolveRealContainedPath(crewRoot, run.artifactsRoot);
31
+ return true;
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ function appendPruneAudit(cwd: string, payload: Record<string, unknown>): string | undefined {
38
+ try {
39
+ const filePath = path.join(projectCrewRoot(cwd), "audit", "prune.jsonl");
40
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
41
+ fs.appendFileSync(filePath, `${JSON.stringify(redactSecrets({ ...payload, auditedAt: new Date().toISOString() }))}\n`, "utf-8");
42
+ return filePath;
43
+ } catch (error) {
44
+ logInternalError("prune.audit-write", error, `cwd=${cwd}`);
45
+ return undefined;
46
+ }
47
+ }
48
+
49
+ export function pruneFinishedRuns(cwd: string, keep: number, options: PruneRunsOptions = {}): PruneRunsResult {
50
+ const token = createCancellationToken({ signal: options.signal });
51
+ const finished = listRuns(cwd, options.signal).filter((run) => run.cwd === cwd && isFinished(run)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
52
+ const kept = finished.slice(0, keep).map((run) => run.runId);
53
+ const removed: string[] = [];
54
+ const toRemove = finished.slice(keep);
55
+ for (let i = 0; i < toRemove.length; i++) {
56
+ if (i % 5 === 0) token.heartbeat(`prune:${i}/${toRemove.length}`);
57
+ const run = toRemove[i];
58
+ if (!isSafeToPrune(cwd, run)) {
59
+ logInternalError("prune.path-unsafe", new Error(`Skipping unsafe prune: stateRoot=${run.stateRoot}, artifactsRoot=${run.artifactsRoot}`), `runId=${run.runId}`);
60
+ continue;
61
+ }
62
+ fs.rmSync(run.stateRoot, { recursive: true, force: true });
63
+ fs.rmSync(run.artifactsRoot, { recursive: true, force: true });
64
+ removed.push(run.runId);
65
+ }
66
+ const auditPath = appendPruneAudit(cwd, { action: "prune", keep, intent: options.intent, kept, removed });
67
+ return { kept, removed, auditPath };
68
+ }
@@ -5,7 +5,7 @@ import { loadRunManifestById, saveRunManifest, saveRunTasks, updateRunStatus } f
5
5
  import { withRunLockSync } from "../../state/locks.ts";
6
6
  import { canTransitionTaskStatus, isTeamTaskStatus } from "../../state/contracts.ts";
7
7
  import { claimTask, releaseTaskClaim, transitionClaimedTaskStatus } from "../../state/task-claims.ts";
8
- import { acknowledgeMailboxMessage, appendFollowUpMessage, appendMailboxMessage, appendSteeringMessage, readDeliveryState, readMailbox, readMailboxMessage, validateMailbox, type MailboxDirection } from "../../state/mailbox.ts";
8
+ import { acknowledgeMailboxMessage, appendFollowUpMessage, appendMailboxMessage, appendSteeringMessage, readDeliveryState, readMailbox, readMailboxMessage, validateMailbox, type MailboxDirection, type MailboxMessageKind } from "../../state/mailbox.ts";
9
9
  import { appendEvent, readEvents, readEventsCursor } from "../../state/event-log.ts";
10
10
  import { resolveCrewRuntime } from "../../runtime/runtime-resolver.ts";
11
11
  import { probeLiveSessionRuntime } from "../../subagents/live/session-runtime.ts";
@@ -17,6 +17,7 @@ import { readForegroundControlStatus, writeForegroundInterruptRequest } from "..
17
17
  import { followUpLiveAgent, getLiveAgent, listLiveAgents, resumeLiveAgent, steerLiveAgent, stopLiveAgent } from "../../subagents/live/manager.ts";
18
18
  import { appendLiveAgentControlRequest } from "../../subagents/live/control.ts";
19
19
  import { liveControlRealtimeMessage, publishLiveControlRealtime } from "../../subagents/live/realtime.ts";
20
+ import { buildCapabilityInventory } from "../../runtime/capability-inventory.ts";
20
21
  import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
21
22
  import type { PiTeamsToolResult } from "../tool-result.ts";
22
23
  import { configRecord, result, type TeamContext } from "./context.ts";
@@ -76,6 +77,10 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
76
77
  });
77
78
  return result(JSON.stringify(filtered, null, 2), { action: "api", status: "ok", ...(runIdFilter ? { runId: runIdFilter } : {}) });
78
79
  }
80
+ if (operation === "inventory") {
81
+ const inventory = buildCapabilityInventory(ctx.cwd, ctx.config);
82
+ return result(JSON.stringify(inventory, null, 2), { action: "api", status: "ok" });
83
+ }
79
84
  if (!params.runId) return result("API requires runId.", { action: "api", status: "error" }, true);
80
85
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
81
86
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "api", status: "error" }, true);
@@ -275,9 +280,10 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
275
280
  if (operation === "read-mailbox") {
276
281
  const direction = cfg.direction === "inbox" || cfg.direction === "outbox" ? cfg.direction as MailboxDirection : undefined;
277
282
  const taskId = typeof cfg.taskId === "string" ? cfg.taskId : undefined;
283
+ const kind = typeof cfg.kind === "string" && ["message", "steer", "follow-up", "response", "group_join"].includes(cfg.kind) ? cfg.kind as MailboxMessageKind : undefined;
278
284
  if (taskId && !loaded.tasks.some((task) => task.id === taskId)) return result(`API read-mailbox taskId '${taskId}' does not match a run task.`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
279
285
  try {
280
- return result(JSON.stringify(readMailbox(loaded.manifest, direction, taskId), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
286
+ return result(JSON.stringify(readMailbox(loaded.manifest, direction, taskId, kind), null, 2), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
281
287
  } catch (error) {
282
288
  const message = error instanceof Error ? error.message : String(error);
283
289
  return result(message, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
@@ -416,5 +422,20 @@ export async function handleApi(params: TeamToolParamsValue, ctx: TeamContext):
416
422
  return result(message, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
417
423
  }
418
424
  }
425
+ if (operation === "diff") {
426
+ const diffArtifacts = loaded.manifest.artifacts.filter(a => a.kind === "diff" || a.kind === "patch");
427
+ if (diffArtifacts.length === 0) {
428
+ return result(`No diff artifacts found for run ${loaded.manifest.runId}. Diffs are captured in worktree mode.`, { action: "api", status: "ok", runId: loaded.manifest.runId, intent: `diff ${loaded.manifest.runId}: no diffs` });
429
+ }
430
+ const parts: string[] = [`Diff artifacts for run ${loaded.manifest.runId}:`];
431
+ for (const artifact of diffArtifacts) {
432
+ const content = safeReadContainedFile(loaded.manifest.artifactsRoot, artifact.path);
433
+ if (content) {
434
+ const display = content.length > 4000 ? content.slice(0, 4000) + "\n... (truncated)" : content;
435
+ parts.push(`\n--- ${artifact.path} ---\n${display}`);
436
+ }
437
+ }
438
+ return result(parts.join("\n"), { action: "api", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot, intent: `diff ${loaded.manifest.runId}` });
439
+ }
419
440
  return result(`Unknown API operation: ${operation}`, { action: "api", status: "error", runId: loaded.manifest.runId }, true);
420
441
  }
@@ -3,11 +3,13 @@ import { withRunLockSync } from "../../state/locks.ts";
3
3
  import { loadRunManifestById, saveRunTasks, updateRunStatus } from "../../state/state-store.ts";
4
4
  import { saveCrewAgents, recordFromTask } from "../../runtime/crew-agent-records.ts";
5
5
  import { writeForegroundInterruptRequest } from "../../runtime/foreground-control.ts";
6
- import { cancellationReasonFromUnknown } from "../../runtime/cancellation.ts";
6
+ import { cancellationReasonFromUnknown, buildSyntheticTerminalEvidence, type CancellationReason } from "../../runtime/cancellation.ts";
7
7
  import { appendEvent } from "../../state/event-log.ts";
8
8
  import { logInternalError } from "../../utils/internal-error.ts";
9
+ import { executeHook, appendHookEvent } from "../../hooks/registry.ts";
9
10
  import type { PiTeamsToolResult } from "../tool-result.ts";
10
11
  import { result, type TeamContext } from "./context.ts";
12
+ import { enforceDestructiveIntent, intentFromConfig } from "./intent-policy.ts";
11
13
 
12
14
  export interface AbortOwnedResult {
13
15
  abortedIds: string[];
@@ -61,25 +63,93 @@ function configFromParams(params: TeamToolParamsValue): Record<string, unknown>
61
63
  return params.config && typeof params.config === "object" && !Array.isArray(params.config) ? params.config : undefined;
62
64
  }
63
65
 
64
- function cancelReasonFromParams(params: TeamToolParamsValue): { code: string; message: string } {
66
+ function cancelReasonFromParams(params: TeamToolParamsValue): CancellationReason {
65
67
  const config = configFromParams(params);
66
68
  const rawReason = config?.reason ?? config?.cancelReason;
67
69
  const reason = rawReason === undefined ? { code: "caller_cancelled" as const, message: "Run cancelled by user request." } : cancellationReasonFromUnknown(rawReason);
68
70
  return { code: reason.code, message: reason.message };
69
71
  }
70
72
 
71
- function intentFromParams(params: TeamToolParamsValue): string | undefined {
72
- const config = configFromParams(params);
73
- const rawIntent = config?.intent ?? config?._intent;
74
- if (typeof rawIntent !== "string") return undefined;
75
- const intent = rawIntent.replace(/\s+/g, " ").trim();
76
- return intent ? intent.slice(0, 500) : undefined;
73
+ export async function handleRetry(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
74
+ if (!params.runId) return result("Retry requires runId.", { action: "retry", status: "error" }, true);
75
+ const loaded = loadRunManifestById(ctx.cwd, params.runId);
76
+ if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "retry", status: "error" }, true);
77
+
78
+ // Pre-lock ownership check: reject foreign-owned runs
79
+ const foreignRun = typeof loaded.manifest.ownerSessionId === "string" && loaded.manifest.ownerSessionId !== ctx.sessionId;
80
+ if (foreignRun) {
81
+ return result(`Run ${loaded.manifest.runId} belongs to another session; not retried.`, { action: "retry", status: "error", runId: loaded.manifest.runId }, true);
82
+ }
83
+
84
+ // Execute before_retry hook after ownership confirmed, before mutation lock
85
+ const hookReport = await executeHook("before_retry", { runId: loaded.manifest.runId, cwd: ctx.cwd });
86
+ appendHookEvent(loaded.manifest, hookReport);
87
+ if (hookReport.outcome === "block") {
88
+ return result(`Retry blocked by hook: ${hookReport.reason ?? "before_retry hook blocked the operation."}`, { action: "retry", status: "error", runId: loaded.manifest.runId }, true);
89
+ }
90
+
91
+ const targetTaskId = typeof params.taskId === "string" ? params.taskId : undefined;
92
+
93
+ return withRunLockSync(loaded.manifest, () => {
94
+ const retryableStatuses: ReadonlySet<string> = new Set(["failed", "cancelled"]);
95
+
96
+ const matchingTasks = loaded.tasks.filter((task) => {
97
+ if (targetTaskId && task.id !== targetTaskId) return false;
98
+ return retryableStatuses.has(task.status);
99
+ });
100
+
101
+ if (matchingTasks.length === 0) {
102
+ return result(targetTaskId ? `Task '${targetTaskId}' is not failed/cancelled; nothing to retry.` : "No failed/cancelled tasks to retry.", { action: "retry", status: "error", runId: loaded.manifest.runId }, true);
103
+ }
104
+
105
+ const retriedIds = new Set(matchingTasks.map((t) => t.id));
106
+ const tasks = loaded.tasks.map((task) => {
107
+ if (!retriedIds.has(task.id)) return task;
108
+ const { error: _error, finishedAt: _finishedAt, terminalEvidence: _terminalEvidence, ...rest } = task;
109
+ return { ...rest, status: "queued" as const };
110
+ });
111
+ saveRunTasks(loaded.manifest, tasks);
112
+ try {
113
+ saveCrewAgents(loaded.manifest, tasks.map((task) => recordFromTask(loaded.manifest, task, "child-process")));
114
+ } catch (error) {
115
+ logInternalError("team-tool.handleRetry.crewAgents", error, `runId=${loaded.manifest.runId}`);
116
+ }
117
+
118
+ const retriedTaskIds = [...retriedIds];
119
+ for (const taskId of retriedTaskIds) {
120
+ appendEvent(loaded.manifest.eventsPath, { type: "task.retried", runId: loaded.manifest.runId, taskId, message: `Task ${taskId} queued for retry.` });
121
+ }
122
+
123
+ return result(`Retried ${retriedTaskIds.length} task(s) in run ${loaded.manifest.runId}.`, {
124
+ action: "retry",
125
+ status: "ok",
126
+ runId: loaded.manifest.runId,
127
+ retriedTaskIds: retriedTaskIds,
128
+ intent: `retrying ${retriedTaskIds.length} task(s) in ${loaded.manifest.runId}`,
129
+ });
130
+ });
77
131
  }
78
132
 
79
- export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiTeamsToolResult {
133
+ export async function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): Promise<PiTeamsToolResult> {
134
+ const intentError = enforceDestructiveIntent("cancel", params, ctx.config);
135
+ if (intentError) return intentError;
80
136
  if (!params.runId) return result("Cancel requires runId.", { action: "cancel", status: "error" }, true);
81
137
  const loaded = loadRunManifestById(ctx.cwd, params.runId);
82
138
  if (!loaded) return result(`Run '${params.runId}' not found.`, { action: "cancel", status: "error" }, true);
139
+
140
+ // Pre-lock ownership check: reject foreign-owned runs before executing hooks
141
+ const preCheck = abortOwned(loaded.manifest.runId, undefined, ctx);
142
+ if (preCheck.abortedIds.length === 0 && preCheck.foreignIds.length > 0) {
143
+ return result(`Run ${loaded.manifest.runId} belongs to another session; not cancelled.`, { action: "cancel", status: "error", runId: loaded.manifest.runId, foreignIds: preCheck.foreignIds }, true);
144
+ }
145
+
146
+ // Execute before_cancel hook after ownership confirmed, before mutation lock
147
+ const hookReport = await executeHook("before_cancel", { runId: loaded.manifest.runId, cwd: ctx.cwd });
148
+ appendHookEvent(loaded.manifest, hookReport);
149
+ if (hookReport.outcome === "block") {
150
+ return result(`Cancel blocked by hook: ${hookReport.reason ?? "before_cancel hook blocked the operation."}`, { action: "cancel", status: "error", runId: loaded.manifest.runId }, true);
151
+ }
152
+
83
153
  return withRunLockSync(loaded.manifest, () => {
84
154
  if ((loaded.manifest.status === "completed" || loaded.manifest.status === "cancelled") && !params.force) return result(`Run ${loaded.manifest.runId} is already ${loaded.manifest.status}; nothing to cancel. Use force: true to mark it cancelled anyway.`, { action: "cancel", status: "ok", runId: loaded.manifest.runId, artifactsRoot: loaded.manifest.artifactsRoot });
85
155
 
@@ -90,13 +160,17 @@ export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiT
90
160
  }
91
161
  const cancellableIds = new Set(abortResult.abortedIds);
92
162
  const cancelReason = cancelReasonFromParams(params);
93
- const cancelIntent = intentFromParams(params);
163
+ const cancelIntent = intentFromConfig(params.config);
94
164
  const cancelData = cancelIntent ? { reason: cancelReason.code, intent: cancelIntent } : { reason: cancelReason.code };
95
165
  const cancelMessage = `${cancelReason.message} (${cancelReason.code})`;
96
166
 
97
167
  const tasks = loaded.tasks.map((task) => {
98
168
  if (cancellableIds.has(task.id) && (task.status === "queued" || task.status === "running" || task.status === "waiting")) {
99
- return { ...task, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: cancelMessage };
169
+ const base = { ...task, status: "cancelled" as const, finishedAt: new Date().toISOString(), error: cancelMessage };
170
+ if (task.status === "running") {
171
+ return { ...base, terminalEvidence: [...(task.terminalEvidence ?? []), buildSyntheticTerminalEvidence("worker", cancelReason, task.startedAt)] };
172
+ }
173
+ return base;
100
174
  }
101
175
  return task;
102
176
  });
@@ -111,6 +185,7 @@ export function handleCancel(params: TeamToolParamsValue, ctx: TeamContext): PiT
111
185
  } catch (error) {
112
186
  logInternalError("team-tool.handleCancel.interruptRequest", error, `runId=${loaded.manifest.runId}`);
113
187
  }
188
+ ctx.abortForegroundRun?.(loaded.manifest.runId);
114
189
  for (const taskId of abortResult.abortedIds) {
115
190
  appendEvent(loaded.manifest.eventsPath, { type: "task.cancelled", runId: loaded.manifest.runId, taskId, message: cancelMessage, data: cancelData });
116
191
  }
@@ -1,4 +1,5 @@
1
1
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import type { PiTeamsConfig } from "../../config/config.ts";
2
3
  import type { MetricRegistry } from "../../observability/metric-registry.ts";
3
4
  import type { TeamToolDetails } from "../team-tool-types.ts";
4
5
  import { toolResult, type PiTeamsToolResult } from "../tool-result.ts";
@@ -11,8 +12,10 @@ export type TeamContext = Pick<ExtensionContext, "cwd"> & Partial<Pick<Extension
11
12
  metricRegistry?: MetricRegistry;
12
13
  signal?: AbortSignal;
13
14
  startForegroundRun?: (runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
15
+ abortForegroundRun?: (runId: string) => boolean;
14
16
  onRunStarted?: (runId: string) => void;
15
17
  onJsonEvent?: (taskId: string, runId: string, event: unknown) => void;
18
+ config?: PiTeamsConfig;
16
19
  };
17
20
 
18
21
  export function withSessionId<T extends Pick<ExtensionContext, "sessionManager">>(ctx: T): T & { sessionId?: string } {