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,82 +1,82 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
- import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
3
- import { handleTeamTool } from "./team-tool.ts";
4
- import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
5
-
6
- export interface EventBusLike {
7
- on(event: string, handler: (data: unknown) => void): (() => void) | void;
8
- emit(event: string, data: unknown): void;
9
- }
10
-
11
- export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
12
- export const PI_CREW_RPC_VERSION = 1;
13
-
14
- export interface PiCrewRpcHandle {
15
- unsubscribe(): void;
16
- }
17
-
18
- function requestId(raw: unknown): string | undefined {
19
- return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
20
- }
21
-
22
- function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
23
- if (!id) return;
24
- events.emit(`${channel}:reply:${id}`, payload);
25
- }
26
-
27
- function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
28
- return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
29
- }
30
-
31
- function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
32
- const unsub = events.on(channel, handler);
33
- return typeof unsub === "function" ? unsub : () => {};
34
- }
35
-
36
- export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
37
- if (!events) return undefined;
38
- const unsubs = [
39
- on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
40
- on(events, "pi-crew:rpc:run", async (raw) => {
41
- const id = requestId(raw);
42
- try {
43
- const ctx = getCtx();
44
- if (!ctx) throw new Error("No active pi-crew session context.");
45
- const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
46
- const result = await handleTeamTool(params, ctx);
47
- reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
48
- } catch (error) {
49
- reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
50
- }
51
- }),
52
- on(events, "pi-crew:rpc:status", async (raw) => {
53
- const id = requestId(raw);
54
- try {
55
- const ctx = getCtx();
56
- if (!ctx) throw new Error("No active pi-crew session context.");
57
- const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
58
- const result = await handleTeamTool({ action: "status", runId }, ctx);
59
- reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
60
- } catch (error) {
61
- reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
62
- }
63
- }),
64
- on(events, "pi-crew:live-control", (raw) => {
65
- const request = parseLiveControlRealtimeMessage(raw);
66
- if (request) publishLiveControlRealtime(request);
67
- }),
68
- on(events, "pi-crew:rpc:live-control", async (raw) => {
69
- const id = requestId(raw);
70
- try {
71
- const ctx = getCtx();
72
- if (!ctx) throw new Error("No active pi-crew session context.");
73
- const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
74
- const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
75
- reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
76
- } catch (error) {
77
- reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
78
- }
79
- }),
80
- ];
81
- return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
82
- }
1
+ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+ import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
3
+ import { handleTeamTool } from "./team-tool.ts";
4
+ import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
5
+
6
+ export interface EventBusLike {
7
+ on(event: string, handler: (data: unknown) => void): (() => void) | void;
8
+ emit(event: string, data: unknown): void;
9
+ }
10
+
11
+ export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
12
+ export const PI_CREW_RPC_VERSION = 1;
13
+
14
+ export interface PiCrewRpcHandle {
15
+ unsubscribe(): void;
16
+ }
17
+
18
+ function requestId(raw: unknown): string | undefined {
19
+ return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
20
+ }
21
+
22
+ function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
23
+ if (!id) return;
24
+ events.emit(`${channel}:reply:${id}`, payload);
25
+ }
26
+
27
+ function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
28
+ return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
29
+ }
30
+
31
+ function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
32
+ const unsub = events.on(channel, handler);
33
+ return typeof unsub === "function" ? unsub : () => {};
34
+ }
35
+
36
+ export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
37
+ if (!events) return undefined;
38
+ const unsubs = [
39
+ on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
40
+ on(events, "pi-crew:rpc:run", async (raw) => {
41
+ const id = requestId(raw);
42
+ try {
43
+ const ctx = getCtx();
44
+ if (!ctx) throw new Error("No active pi-crew session context.");
45
+ const params: TeamToolParamsValue = raw && typeof raw === "object" && !Array.isArray(raw) ? { ...(raw as object), action: "run" } as TeamToolParamsValue : { action: "run" };
46
+ const result = await handleTeamTool(params, ctx);
47
+ reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
48
+ } catch (error) {
49
+ reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
50
+ }
51
+ }),
52
+ on(events, "pi-crew:rpc:status", async (raw) => {
53
+ const id = requestId(raw);
54
+ try {
55
+ const ctx = getCtx();
56
+ if (!ctx) throw new Error("No active pi-crew session context.");
57
+ const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
58
+ const result = await handleTeamTool({ action: "status", runId }, ctx);
59
+ reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
60
+ } catch (error) {
61
+ reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
62
+ }
63
+ }),
64
+ on(events, "pi-crew:live-control", (raw) => {
65
+ const request = parseLiveControlRealtimeMessage(raw);
66
+ if (request) publishLiveControlRealtime(request);
67
+ }),
68
+ on(events, "pi-crew:rpc:live-control", async (raw) => {
69
+ const id = requestId(raw);
70
+ try {
71
+ const ctx = getCtx();
72
+ if (!ctx) throw new Error("No active pi-crew session context.");
73
+ const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
74
+ const result = await handleTeamTool({ action: "api", runId: typeof obj.runId === "string" ? obj.runId : undefined, config: { operation: typeof obj.operation === "string" ? obj.operation : "steer-agent", agentId: obj.agentId, message: obj.message, prompt: obj.prompt } }, ctx);
75
+ reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
76
+ } catch (error) {
77
+ reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
78
+ }
79
+ }),
80
+ ];
81
+ return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
82
+ }
@@ -11,6 +11,7 @@ export function piTeamsHelp(): string {
11
11
  "- /team-summary <runId>",
12
12
  "- /team-resume <runId>",
13
13
  "- /team-cancel <runId>",
14
+ "- /team-retry <runId> [taskId]",
14
15
  "",
15
16
  "Inspection:",
16
17
  "- /team-events <runId>",
@@ -6,6 +6,8 @@ import { allAgents, discoverAgents } from "../agents/discover-agents.ts";
6
6
  import type { TeamToolDetails } from "./team-tool-types.ts";
7
7
  import { toolResult, type PiTeamsToolResult } from "./tool-result.ts";
8
8
  import type { TeamToolParamsValue } from "../schema/team-tool-schema.ts";
9
+ import type { PiTeamsConfig } from "../config/config.ts";
10
+ import { enforceDestructiveIntent } from "./team-tool/intent-policy.ts";
9
11
  import type { TeamConfig, TeamRole } from "../teams/team-config.ts";
10
12
  import { serializeTeam } from "../teams/team-serializer.ts";
11
13
  import { allTeams, discoverTeams } from "../teams/discover-teams.ts";
@@ -17,6 +19,7 @@ import { hasOwn, parseConfigObject, requireString, sanitizeName } from "../utils
17
19
 
18
20
  interface ManagementContext {
19
21
  cwd: string;
22
+ config?: PiTeamsConfig;
20
23
  }
21
24
 
22
25
  type MutableSource = "user" | "project";
@@ -359,6 +362,8 @@ export function handleUpdate(params: TeamToolParamsValue, ctx: ManagementContext
359
362
  }
360
363
 
361
364
  export function handleDelete(params: TeamToolParamsValue, ctx: ManagementContext): PiTeamsToolResult {
365
+ const intentError = enforceDestructiveIntent("delete", params, ctx.config);
366
+ if (intentError) return intentError;
362
367
  if (!params.confirm) return result("delete requires confirm: true.", "error", true);
363
368
  const resolved = resolveMutable(ctx, params);
364
369
  if (resolved.error) return resolved.error;
@@ -9,7 +9,7 @@ import { notifyActiveRuns } from "./session-summary.ts";
9
9
  import { LiveRunSidebar } from "../ui/live-run-sidebar.ts";
10
10
  import { registerPiCrewRpc, type PiCrewRpcHandle } from "./cross-extension-rpc.ts";
11
11
  import { stopCrewWidget, updateCrewWidget, type CrewWidgetState } from "../ui/crew-widget.ts";
12
- import { clearPiCrewPowerbar, registerPiCrewPowerbarSegments, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
12
+ import { clearPiCrewPowerbar, disposePowerbarCoalescer, registerPiCrewPowerbarSegments, requestPowerbarUpdate, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
13
13
  import { loadRunManifestById, updateRunStatus } from "../state/state-store.ts";
14
14
  import type { TeamRunManifest } from "../state/types.ts";
15
15
  import { terminateActiveChildPiProcesses } from "../subagents/spawn.ts";
@@ -37,7 +37,7 @@ import { createMetricFileSink, type MetricSink } from "../observability/metric-s
37
37
  import { OTLPExporter } from "../observability/exporters/otlp-exporter.ts";
38
38
  import { HeartbeatWatcher } from "../runtime/heartbeat-watcher.ts";
39
39
  import { appendDeadletter } from "../runtime/deadletter.ts";
40
- import { detectInterruptedRuns } from "../runtime/crash-recovery.ts";
40
+ import { cancelOrphanedRuns, detectInterruptedRuns, purgeStaleActiveRunIndex } from "../runtime/crash-recovery.ts";
41
41
  import { DeliveryCoordinator } from "../runtime/delivery-coordinator.ts";
42
42
  import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
43
43
  import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
@@ -113,7 +113,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
113
113
  if (currentCtx) {
114
114
  const uiConfig = loadConfig(currentCtx.cwd).config.ui;
115
115
  updateCrewWidget(currentCtx, widgetState, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd));
116
- updatePiCrewPowerbar(pi.events, currentCtx.cwd, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
116
+ requestPowerbarUpdate(pi.events, currentCtx.cwd, uiConfig, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
117
117
  }
118
118
  });
119
119
  };
@@ -236,12 +236,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
236
236
  pi.events?.emit?.(event, payload);
237
237
  },
238
238
  );
239
- const foregroundControllers = new Set<AbortController>();
239
+ const foregroundControllers = new Map<string | symbol, AbortController>();
240
240
  let liveSidebarRunId: string | undefined;
241
241
  let renderScheduler: RenderScheduler | undefined;
242
242
  let preloadTimer: ReturnType<typeof setTimeout> | undefined;
243
243
  const stopSessionBoundSubagents = (): void => {
244
- for (const controller of foregroundControllers) controller.abort();
244
+ for (const controller of foregroundControllers.values()) controller.abort();
245
245
  foregroundControllers.clear();
246
246
  subagentManager.abortAll();
247
247
  terminateActiveChildPiProcesses();
@@ -277,7 +277,8 @@ export function registerPiTeams(pi: ExtensionAPI): void {
277
277
  const startForegroundRun = (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string): void => {
278
278
  const ownerGeneration = captureSessionGeneration();
279
279
  const controller = new AbortController();
280
- foregroundControllers.add(controller);
280
+ const key = runId ?? Symbol();
281
+ foregroundControllers.set(key, controller);
281
282
  if (ctx.hasUI) {
282
283
  setWorkingIndicator(ctx, { frames: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"], intervalMs: 80 });
283
284
  ctx.ui.setWorkingMessage(runId ? `pi-crew foreground run ${runId}...` : "pi-crew foreground run...");
@@ -295,9 +296,10 @@ export function registerPiTeams(pi: ExtensionAPI): void {
295
296
  }
296
297
  }
297
298
  if (isContextCurrent(ctx, ownerGeneration)) ctx.ui.notify(`pi-crew foreground run failed: ${message}`, "error");
299
+ else logInternalError("register.foreground-run-failure", error, `runId=${runId} context disposed`);
298
300
  })
299
301
  .finally(() => {
300
- foregroundControllers.delete(controller);
302
+ foregroundControllers.delete(key);
301
303
  const ownerCurrent = isContextCurrent(ctx, ownerGeneration);
302
304
  if (ownerCurrent && ctx.hasUI) {
303
305
  setWorkingIndicator(ctx);
@@ -334,7 +336,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
334
336
  if (ownerCurrent && currentCtx) {
335
337
  const config = loadConfig(currentCtx.cwd).config.ui;
336
338
  updateCrewWidget(currentCtx, widgetState, config, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd));
337
- updatePiCrewPowerbar(pi.events, currentCtx.cwd, config, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
339
+ requestPowerbarUpdate(pi.events, currentCtx.cwd, config, getManifestCache(currentCtx.cwd), getRunSnapshotCache(currentCtx.cwd), currentCtx, widgetState.notificationCount ?? 0);
338
340
  }
339
341
  });
340
342
  });
@@ -350,8 +352,20 @@ export function registerPiTeams(pi: ExtensionAPI): void {
350
352
  if (preloadTimer) { clearTimeout(preloadTimer); preloadTimer = undefined; }
351
353
  stopSessionBoundSubagents();
352
354
  stopAsyncRunNotifier(notifierState);
355
+
356
+ // P0: Purge all stale active-run-index entries on session cleanup.
357
+ // This handles: normal exit, SIGTERM, Ctrl+C — any case where cleanupRuntime fires.
358
+ // For SIGKILL / crash / SIGHUP (where cleanupRuntime does NOT fire),
359
+ // purgeStaleActiveRunIndex() runs at next session_start instead.
360
+ try {
361
+ purgeStaleActiveRunIndex();
362
+ } catch (error) {
363
+ logInternalError("register.cleanupRuntime.purgeStale", error);
364
+ }
365
+
353
366
  stopCrewWidget(currentCtx, widgetState, currentCtx ? loadConfig(currentCtx.cwd).config.ui : undefined);
354
367
  clearPiCrewPowerbar(pi.events, currentCtx);
368
+ disposePowerbarCoalescer();
355
369
  heartbeatWatcher?.dispose();
356
370
  metricSink?.dispose();
357
371
  eventMetricSub?.dispose();
@@ -394,6 +408,30 @@ export function registerPiTeams(pi: ExtensionAPI): void {
394
408
  if (widgetState.interval) clearInterval(widgetState.interval);
395
409
  widgetState.interval = undefined;
396
410
  notifyActiveRuns(ctx);
411
+
412
+ // Auto-cancel orphaned runs from dead sessions
413
+ const currentSessionId = (ctx as unknown as Record<string, unknown>).sessionId as string | undefined;
414
+ if (currentSessionId) {
415
+ try {
416
+ const { cancelled } = cancelOrphanedRuns(ctx.cwd, getManifestCache(ctx.cwd), currentSessionId);
417
+ if (cancelled.length > 0) {
418
+ notifyOperator({ id: `orphan_cleanup`, severity: "info", source: "crash-recovery", title: `Cleaned up ${cancelled.length} orphaned run(s)`, body: `Runs from previous sessions were auto-cancelled: ${cancelled.join(", ")}` });
419
+ }
420
+ } catch (error) {
421
+ logInternalError("register.sessionStart.orphanCleanup", error);
422
+ }
423
+ }
424
+
425
+ // Global purge of stale active-run-index entries (temp dirs, dead workers, etc.)
426
+ try {
427
+ const { purged } = purgeStaleActiveRunIndex();
428
+ if (purged.length > 0) {
429
+ notifyOperator({ id: `active_index_purge`, severity: "info", source: "crash-recovery", title: `Purged ${purged.length} stale active-run-index entr${purged.length === 1 ? "y" : "ies"}`, body: `Cleaned up global active run index` });
430
+ }
431
+ } catch (error) {
432
+ logInternalError("register.sessionStart.globalIndexPurge", error);
433
+ }
434
+
397
435
  const loadedConfig = loadConfig(ctx.cwd);
398
436
  autoRecoveryLast.clear();
399
437
  configureNotifications(ctx);
@@ -474,7 +512,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
474
512
  } else {
475
513
  updateCrewWidget(currentCtx, widgetState, config, activeCache, snapshotCache, manifests);
476
514
  }
477
- updatePiCrewPowerbar(pi.events, currentCtx.cwd, config, activeCache, snapshotCache, currentCtx, widgetState.notificationCount ?? 0, manifests);
515
+ requestPowerbarUpdate(pi.events, currentCtx.cwd, config, activeCache, snapshotCache, currentCtx, widgetState.notificationCount ?? 0, manifests);
478
516
  // Health notifications: only warn about genuinely running runs
479
517
  const now = Date.now();
480
518
  for (const run of manifests) {
@@ -541,6 +579,12 @@ export function registerPiTeams(pi: ExtensionAPI): void {
541
579
  });
542
580
  } catch { /* older Pi without resources_discover */ }
543
581
 
582
+ const abortForegroundRun = (runId: string): boolean => {
583
+ const controller = foregroundControllers.get(runId);
584
+ if (!controller) return false;
585
+ controller.abort();
586
+ return true;
587
+ };
544
588
  registerCompactionGuard(pi, { foregroundControllers });
545
589
 
546
590
  // Phase 1.4: Permission gate for destructive team actions.
@@ -552,14 +596,15 @@ export function registerPiTeams(pi: ExtensionAPI): void {
552
596
  const action = typeof input.action === "string" ? input.action : undefined;
553
597
  const destructiveActions = new Set(["delete", "forget", "prune", "cleanup"]);
554
598
  if (!action || !destructiveActions.has(action)) return;
555
- if (input.confirm === true || input.force === true) return;
599
+ const forceBypassesReferenceChecks = action === "delete" && input.force === true;
600
+ if (input.confirm === true || forceBypassesReferenceChecks) return;
556
601
  return {
557
602
  block: true,
558
- reason: `Destructive action '${action}' requires confirm=true (or force=true to bypass reference checks).`,
603
+ reason: `Destructive action '${action}' requires confirm=true${action === "delete" ? " (or force=true to bypass reference checks)" : ""}.`,
559
604
  };
560
605
  });
561
606
 
562
- registerTeamTool(pi, { foregroundControllers, startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, widgetState, onJsonEvent: (taskId, runId, event) => {
607
+ registerTeamTool(pi, { foregroundControllers, startForegroundRun, abortForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, widgetState, onJsonEvent: (taskId, runId, event) => {
563
608
  const record = event as Record<string, unknown>;
564
609
  const eventType = typeof record.type === "string" ? record.type : undefined;
565
610
  if (eventType) overflowTracker?.feedEvent(taskId, runId, eventType);
@@ -567,7 +612,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
567
612
  registerSubagentTools(pi, subagentManager, { ownerSessionGeneration: captureSessionGeneration });
568
613
  time("register.tools");
569
614
 
570
- registerTeamCommands(pi, { startForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, dismissNotifications: () => {
615
+ registerTeamCommands(pi, { startForegroundRun, abortForegroundRun, openLiveSidebar, getManifestCache, getRunSnapshotCache, getMetricRegistry: () => metricRegistry, dismissNotifications: () => {
571
616
  widgetState.notificationCount = 0;
572
617
  if (currentCtx) {
573
618
  const uiConfig = loadConfig(currentCtx.cwd).config.ui;
@@ -27,6 +27,7 @@ import type { MetricRegistry } from "../../observability/metric-registry.ts";
27
27
 
28
28
  export interface RegisterTeamCommandsDeps {
29
29
  startForegroundRun: (ctx: ExtensionContext, runner: (signal?: AbortSignal) => Promise<void>, runId?: string) => void;
30
+ abortForegroundRun: (runId: string) => boolean;
30
31
  openLiveSidebar: (ctx: ExtensionContext, runId: string) => void;
31
32
  getManifestCache: (cwd: string) => { list(max?: number): TeamRunManifest[] };
32
33
  getRunSnapshotCache?: (cwd: string) => ReturnType<typeof createRunSnapshotCache>;
@@ -135,7 +136,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
135
136
  pi.registerCommand("team-run", {
136
137
  description: "Manually start a pi-crew run (agent may also use the team tool autonomously)",
137
138
  handler: async (args: string, ctx: ExtensionCommandContext) => {
138
- const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
139
+ const result = await handleTeamTool(parseRunArgs(args), { ...teamCommandContext(ctx), metricRegistry: deps.getMetricRegistry?.(), startForegroundRun: (runner, runId) => deps.startForegroundRun(ctx as ExtensionContext, runner, runId), abortForegroundRun: deps.abortForegroundRun, onRunStarted: (runId) => deps.openLiveSidebar(ctx as ExtensionContext, runId) });
139
140
  await notifyCommandResult(ctx, commandText(result));
140
141
  },
141
142
  });
@@ -157,6 +158,21 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
157
158
  } });
158
159
  }
159
160
 
161
+ pi.registerCommand("team-retry", {
162
+ description: "Retry failed/cancelled pi-crew tasks: <runId> [taskId]",
163
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
164
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
165
+ const runId = tokens.shift();
166
+ const taskId = tokens.shift();
167
+ if (!runId) {
168
+ await notifyCommandResult(ctx, "Usage: /team-retry <runId> [taskId]");
169
+ return;
170
+ }
171
+ const retryResult = await handleTeamTool({ action: "retry", runId, taskId }, teamCommandContext(ctx));
172
+ await notifyCommandResult(ctx, commandText(retryResult));
173
+ },
174
+ });
175
+
160
176
  pi.registerCommand("team-respond", {
161
177
  description: "Respond to a waiting pi-crew task: <runId> <taskId|--all> <message>",
162
178
  handler: async (args: string, ctx: ExtensionCommandContext) => {
@@ -170,6 +186,22 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
170
186
  },
171
187
  });
172
188
 
189
+ pi.registerCommand("team-follow-up", {
190
+ description: "Send a follow-up prompt to a pi-crew task: <runId> <taskId> <prompt>",
191
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
192
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
193
+ const runId = tokens.shift();
194
+ const taskId = tokens.shift();
195
+ const prompt = tokens.join(" ") || undefined;
196
+ if (!runId || !taskId || !prompt) {
197
+ await notifyCommandResult(ctx, "Usage: /team-follow-up <runId> <taskId> <prompt>. Use /team-respond for waiting-task replies.");
198
+ return;
199
+ }
200
+ const result = await handleTeamTool({ action: "api", runId, config: { operation: "follow-up-agent", taskId, prompt } }, teamCommandContext(ctx));
201
+ await notifyCommandResult(ctx, commandText(result));
202
+ },
203
+ });
204
+
173
205
  pi.registerCommand("team-api", {
174
206
  description: "Run safe pi-crew API interop operations: <runId> <operation> [key=value]",
175
207
  handler: async (args: string, ctx: ExtensionCommandContext) => {