pi-crew 0.1.49 → 0.2.0

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 (249) hide show
  1. package/CHANGELOG.md +74 -1
  2. package/README.md +176 -781
  3. package/agents/analyst.md +11 -11
  4. package/agents/critic.md +11 -11
  5. package/agents/executor.md +11 -11
  6. package/agents/explorer.md +11 -11
  7. package/agents/planner.md +11 -11
  8. package/agents/reviewer.md +11 -11
  9. package/agents/security-reviewer.md +11 -11
  10. package/agents/test-engineer.md +11 -11
  11. package/agents/verifier.md +70 -11
  12. package/agents/writer.md +11 -11
  13. package/docs/actions-reference.md +595 -0
  14. package/docs/commands-reference.md +347 -0
  15. package/docs/runtime-flow.md +148 -148
  16. package/index.ts +6 -6
  17. package/package.json +99 -99
  18. package/skills/async-worker-recovery/SKILL.md +42 -42
  19. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  20. package/skills/delegation-patterns/SKILL.md +54 -54
  21. package/skills/mailbox-interactive/SKILL.md +40 -40
  22. package/skills/model-routing-context/SKILL.md +39 -39
  23. package/skills/multi-perspective-review/SKILL.md +58 -58
  24. package/skills/observability-reliability/SKILL.md +41 -41
  25. package/skills/orchestration/SKILL.md +157 -157
  26. package/skills/ownership-session-security/SKILL.md +41 -41
  27. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  28. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  29. package/skills/resource-discovery-config/SKILL.md +41 -41
  30. package/skills/runtime-state-reader/SKILL.md +44 -44
  31. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  32. package/skills/state-mutation-locking/SKILL.md +42 -42
  33. package/skills/systematic-debugging/SKILL.md +67 -67
  34. package/skills/ui-render-performance/SKILL.md +39 -39
  35. package/skills/verification-before-done/SKILL.md +57 -57
  36. package/skills/worktree-isolation/SKILL.md +39 -39
  37. package/src/adapters/claude-adapter.ts +25 -0
  38. package/src/adapters/codex-adapter.ts +21 -0
  39. package/src/adapters/cursor-adapter.ts +17 -0
  40. package/src/adapters/export-util.ts +137 -0
  41. package/src/adapters/index.ts +15 -0
  42. package/src/adapters/registry.ts +18 -0
  43. package/src/adapters/types.ts +23 -0
  44. package/src/agents/agent-config.ts +2 -0
  45. package/src/agents/agent-search.ts +98 -98
  46. package/src/agents/discover-agents.ts +2 -1
  47. package/src/config/config.ts +14 -1
  48. package/src/config/defaults.ts +5 -5
  49. package/src/config/drift-detector.ts +211 -0
  50. package/src/config/markers.ts +327 -0
  51. package/src/config/resilient-parser.ts +108 -0
  52. package/src/config/suggestions.ts +74 -0
  53. package/src/extension/cross-extension-rpc.ts +103 -82
  54. package/src/extension/project-init.ts +36 -4
  55. package/src/extension/register.ts +67 -22
  56. package/src/extension/registration/commands.ts +77 -8
  57. package/src/extension/registration/subagent-tools.ts +10 -1
  58. package/src/extension/registration/team-tool.ts +10 -1
  59. package/src/extension/registration/viewers.ts +48 -34
  60. package/src/extension/run-bundle-schema.ts +89 -89
  61. package/src/extension/run-export.ts +26 -12
  62. package/src/extension/run-import.ts +25 -1
  63. package/src/extension/run-index.ts +5 -1
  64. package/src/extension/run-maintenance.ts +142 -68
  65. package/src/extension/team-manager-command.ts +10 -1
  66. package/src/extension/team-tool/context.ts +1 -1
  67. package/src/extension/team-tool/doctor.ts +28 -3
  68. package/src/extension/team-tool/handle-settings.ts +195 -188
  69. package/src/extension/team-tool/inspect.ts +41 -41
  70. package/src/extension/team-tool/intent-policy.ts +42 -42
  71. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  72. package/src/extension/team-tool/plan.ts +19 -19
  73. package/src/extension/team-tool/run.ts +12 -1
  74. package/src/extension/team-tool.ts +14 -3
  75. package/src/i18n.ts +184 -184
  76. package/src/observability/exporters/otlp-exporter.ts +92 -77
  77. package/src/prompt/prompt-runtime.ts +72 -72
  78. package/src/runtime/agent-memory.ts +72 -72
  79. package/src/runtime/agent-observability.ts +114 -114
  80. package/src/runtime/async-marker.ts +26 -26
  81. package/src/runtime/attention-events.ts +28 -28
  82. package/src/runtime/auto-resume.ts +100 -0
  83. package/src/runtime/background-runner.ts +11 -1
  84. package/src/runtime/cancellation-token.ts +89 -89
  85. package/src/runtime/cancellation.ts +61 -61
  86. package/src/runtime/capability-inventory.ts +116 -116
  87. package/src/runtime/child-pi.ts +7 -2
  88. package/src/runtime/compaction-summary.ts +271 -0
  89. package/src/runtime/completion-guard.ts +190 -190
  90. package/src/runtime/concurrency.ts +3 -1
  91. package/src/runtime/crash-recovery.ts +33 -0
  92. package/src/runtime/delta-conflict.ts +360 -0
  93. package/src/runtime/diagnostic-export.ts +3 -1
  94. package/src/runtime/direct-run.ts +35 -35
  95. package/src/runtime/event-stream-bridge.ts +3 -1
  96. package/src/runtime/foreground-control.ts +82 -82
  97. package/src/runtime/green-contract.ts +46 -46
  98. package/src/runtime/group-join.ts +106 -106
  99. package/src/runtime/heartbeat-gradient.ts +28 -28
  100. package/src/runtime/heartbeat-watcher.ts +124 -124
  101. package/src/runtime/iteration-hooks.ts +262 -0
  102. package/src/runtime/live-agent-control.ts +88 -88
  103. package/src/runtime/live-control-realtime.ts +36 -36
  104. package/src/runtime/live-extension-bridge.ts +150 -150
  105. package/src/runtime/live-irc.ts +92 -92
  106. package/src/runtime/live-session-health.ts +100 -100
  107. package/src/runtime/loop-gates.ts +129 -0
  108. package/src/runtime/metric-parser.ts +40 -0
  109. package/src/runtime/notebook-helpers.ts +90 -90
  110. package/src/runtime/orphan-sentinel.ts +7 -7
  111. package/src/runtime/parallel-research.ts +44 -44
  112. package/src/runtime/phase-progress.ts +217 -0
  113. package/src/runtime/pi-args.ts +38 -2
  114. package/src/runtime/pi-json-output.ts +111 -111
  115. package/src/runtime/pi-spawn.ts +74 -6
  116. package/src/runtime/policy-engine.ts +79 -79
  117. package/src/runtime/post-checks.ts +122 -0
  118. package/src/runtime/process-status.ts +14 -1
  119. package/src/runtime/progress-event-coalescer.ts +43 -43
  120. package/src/runtime/prose-compressor.ts +164 -164
  121. package/src/runtime/recovery-recipes.ts +74 -74
  122. package/src/runtime/result-extractor.ts +121 -121
  123. package/src/runtime/role-permission.ts +39 -39
  124. package/src/runtime/sensitive-paths.ts +3 -3
  125. package/src/runtime/session-resources.ts +25 -25
  126. package/src/runtime/session-snapshot.ts +59 -59
  127. package/src/runtime/session-usage.ts +79 -79
  128. package/src/runtime/sidechain-output.ts +29 -29
  129. package/src/runtime/stream-preview.ts +177 -177
  130. package/src/runtime/supervisor-contact.ts +59 -59
  131. package/src/runtime/task-display.ts +38 -38
  132. package/src/runtime/task-graph.ts +207 -0
  133. package/src/runtime/task-quality.ts +207 -0
  134. package/src/runtime/task-runner/capabilities.ts +78 -78
  135. package/src/runtime/task-runner/live-executor.ts +7 -1
  136. package/src/runtime/task-runner/progress.ts +119 -119
  137. package/src/runtime/task-runner/prompt-builder.ts +1 -1
  138. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  139. package/src/runtime/task-runner/result-utils.ts +14 -14
  140. package/src/runtime/task-runner/run-projection.ts +103 -103
  141. package/src/runtime/task-runner/state-helpers.ts +22 -22
  142. package/src/runtime/team-runner.ts +126 -7
  143. package/src/runtime/worker-heartbeat.ts +21 -21
  144. package/src/runtime/worker-startup.ts +57 -57
  145. package/src/runtime/workflow-state.ts +187 -0
  146. package/src/runtime/workspace-tree.ts +298 -298
  147. package/src/schema/config-schema.ts +12 -0
  148. package/src/schema/validation-types.ts +148 -0
  149. package/src/skills/skill-templates.ts +374 -0
  150. package/src/state/active-run-registry.ts +35 -11
  151. package/src/state/atomic-write.ts +33 -26
  152. package/src/state/contracts.ts +1 -0
  153. package/src/state/event-reconstructor.ts +217 -0
  154. package/src/state/locks.ts +2 -11
  155. package/src/state/mailbox.ts +4 -3
  156. package/src/state/state-store.ts +32 -14
  157. package/src/state/task-claims.ts +44 -44
  158. package/src/state/types.ts +9 -0
  159. package/src/state/usage.ts +29 -29
  160. package/src/subagents/async-entry.ts +1 -1
  161. package/src/subagents/index.ts +3 -3
  162. package/src/subagents/live/control.ts +1 -1
  163. package/src/subagents/live/manager.ts +1 -1
  164. package/src/subagents/live/realtime.ts +1 -1
  165. package/src/subagents/live/session-runtime.ts +1 -1
  166. package/src/subagents/manager.ts +1 -1
  167. package/src/subagents/spawn.ts +1 -1
  168. package/src/teams/team-serializer.ts +38 -38
  169. package/src/types/diff.d.ts +18 -18
  170. package/src/ui/crew-footer.ts +101 -101
  171. package/src/ui/crew-select-list.ts +111 -111
  172. package/src/ui/crew-widget.ts +9 -4
  173. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  174. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  175. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  176. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  177. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  178. package/src/ui/dynamic-border.ts +25 -25
  179. package/src/ui/layout-primitives.ts +106 -106
  180. package/src/ui/loaders.ts +158 -158
  181. package/src/ui/powerbar-publisher.ts +6 -0
  182. package/src/ui/render-coalescer.ts +51 -51
  183. package/src/ui/render-diff.ts +119 -119
  184. package/src/ui/render-scheduler.ts +143 -143
  185. package/src/ui/run-action-dispatcher.ts +10 -1
  186. package/src/ui/spinner.ts +17 -17
  187. package/src/ui/status-colors.ts +58 -58
  188. package/src/ui/syntax-highlight.ts +116 -116
  189. package/src/ui/transcript-entries.ts +258 -258
  190. package/src/utils/completion-dedupe.ts +63 -63
  191. package/src/utils/frontmatter.ts +68 -68
  192. package/src/utils/git.ts +262 -262
  193. package/src/utils/ids.ts +17 -17
  194. package/src/utils/incremental-reader.ts +104 -104
  195. package/src/utils/names.ts +27 -27
  196. package/src/utils/redaction.ts +44 -44
  197. package/src/utils/safe-paths.ts +47 -47
  198. package/src/utils/scan-cache.ts +136 -136
  199. package/src/utils/sleep.ts +40 -26
  200. package/src/utils/task-name-generator.ts +337 -337
  201. package/src/workflows/validate-workflow.ts +40 -40
  202. package/src/worktree/branch-freshness.ts +45 -45
  203. package/src/worktree/worktree-manager.ts +11 -3
  204. package/teams/default.team.md +12 -12
  205. package/teams/fast-fix.team.md +11 -11
  206. package/teams/implementation.team.md +18 -18
  207. package/teams/parallel-research.team.md +14 -14
  208. package/teams/research.team.md +11 -11
  209. package/teams/review.team.md +12 -12
  210. package/workflows/default.workflow.md +30 -29
  211. package/workflows/fast-fix.workflow.md +23 -22
  212. package/workflows/implementation.workflow.md +43 -38
  213. package/workflows/parallel-research.workflow.md +46 -46
  214. package/workflows/research.workflow.md +22 -22
  215. package/workflows/review.workflow.md +30 -30
  216. package/docs/refactor-tasks-phase3.md +0 -394
  217. package/docs/refactor-tasks-phase4.md +0 -564
  218. package/docs/refactor-tasks-phase5.md +0 -402
  219. package/docs/refactor-tasks-phase6.md +0 -662
  220. package/docs/refactor-tasks.md +0 -1484
  221. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  222. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  223. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  224. package/docs/research/AUDIT_PI_CREW.md +0 -457
  225. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  226. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  227. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  228. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  229. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  230. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  231. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  232. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  233. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  234. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  235. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  236. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  237. package/docs/research-extension-examples.md +0 -297
  238. package/docs/research-extension-system.md +0 -324
  239. package/docs/research-oh-my-pi-distillation.md +0 -369
  240. package/docs/research-optimization-plan.md +0 -548
  241. package/docs/research-phase10-distillation.md +0 -199
  242. package/docs/research-phase11-distillation.md +0 -201
  243. package/docs/research-phase8-operator-experience-plan.md +0 -819
  244. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  245. package/docs/research-pi-coding-agent.md +0 -357
  246. package/docs/research-source-pi-crew-reference.md +0 -174
  247. package/docs/research-ui-optimization-plan.md +0 -480
  248. package/docs/source-runtime-refactor-map.md +0 -107
  249. package/src/utils/atomic-write.ts +0 -33
@@ -1,82 +1,103 @@
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
+ // Lazy-loaded to avoid pulling team-tool.ts (and its entire runtime chain) into module load.
4
+ import type { handleTeamTool as HandleTeamToolFn } from "./team-tool.ts";
5
+ let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
6
+ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
7
+ if (!_cachedHandleTeamTool) {
8
+ const mod = await import("./team-tool.ts");
9
+ _cachedHandleTeamTool = mod.handleTeamTool;
10
+ }
11
+ return _cachedHandleTeamTool(params, ctx);
12
+ }
13
+ import { parseLiveControlRealtimeMessage, publishLiveControlRealtime } from "../runtime/live-control-realtime.ts";
14
+
15
+ export interface EventBusLike {
16
+ on(event: string, handler: (data: unknown) => void): (() => void) | void;
17
+ emit(event: string, data: unknown): void;
18
+ }
19
+
20
+ export type RpcReply<T = unknown> = { success: true; data?: T } | { success: false; error: string };
21
+ export const PI_CREW_RPC_VERSION = 1;
22
+
23
+ export interface PiCrewRpcHandle {
24
+ unsubscribe(): void;
25
+ }
26
+
27
+ function requestId(raw: unknown): string | undefined {
28
+ return raw && typeof raw === "object" && !Array.isArray(raw) && typeof (raw as { requestId?: unknown }).requestId === "string" ? (raw as { requestId: string }).requestId : undefined;
29
+ }
30
+
31
+ function reply(events: EventBusLike, channel: string, id: string | undefined, payload: RpcReply): void {
32
+ if (!id) return;
33
+ events.emit(`${channel}:reply:${id}`, payload);
34
+ }
35
+
36
+ function textOf(result: Awaited<ReturnType<typeof handleTeamTool>>): string {
37
+ return result.content?.map((item) => item.type === "text" ? item.text : "").join("\n") ?? "";
38
+ }
39
+
40
+ function on(events: EventBusLike, channel: string, handler: (raw: unknown) => void): () => void {
41
+ const unsub = events.on(channel, handler);
42
+ return typeof unsub === "function" ? unsub : () => {};
43
+ }
44
+
45
+ export function registerPiCrewRpc(events: EventBusLike | undefined, getCtx: () => ExtensionContext | undefined): PiCrewRpcHandle | undefined {
46
+ if (!events) return undefined;
47
+ const unsubs = [
48
+ on(events, "pi-crew:rpc:ping", (raw) => reply(events, "pi-crew:rpc:ping", requestId(raw), { success: true, data: { version: PI_CREW_RPC_VERSION } })),
49
+ on(events, "pi-crew:rpc:run", async (raw) => {
50
+ const id = requestId(raw);
51
+ try {
52
+ const ctx = getCtx();
53
+ if (!ctx) throw new Error("No active pi-crew session context.");
54
+ // Validate payload: only allow known fields from TeamToolParamsValue
55
+ const ALLOWED_RPC_RUN_KEYS = new Set(["goal", "team", "workflow", "async", "cwd", "config", "skill", "model"]);
56
+ let params: TeamToolParamsValue;
57
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
58
+ const filtered: Record<string, unknown> = { ...(raw as object) };
59
+ // Strip any keys not in the allowlist to prevent injection of unexpected fields
60
+ for (const key of Object.keys(filtered)) {
61
+ if (!ALLOWED_RPC_RUN_KEYS.has(key)) delete filtered[key];
62
+ }
63
+ params = { ...filtered, action: "run" } as TeamToolParamsValue;
64
+ } else {
65
+ params = { action: "run" };
66
+ }
67
+ const result = await handleTeamTool(params, ctx);
68
+ reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
69
+ } catch (error) {
70
+ reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
71
+ }
72
+ }),
73
+ on(events, "pi-crew:rpc:status", async (raw) => {
74
+ const id = requestId(raw);
75
+ try {
76
+ const ctx = getCtx();
77
+ if (!ctx) throw new Error("No active pi-crew session context.");
78
+ const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
79
+ const result = await handleTeamTool({ action: "status", runId }, ctx);
80
+ reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
81
+ } catch (error) {
82
+ reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
83
+ }
84
+ }),
85
+ on(events, "pi-crew:live-control", (raw) => {
86
+ const request = parseLiveControlRealtimeMessage(raw);
87
+ if (request) publishLiveControlRealtime(request);
88
+ }),
89
+ on(events, "pi-crew:rpc:live-control", async (raw) => {
90
+ const id = requestId(raw);
91
+ try {
92
+ const ctx = getCtx();
93
+ if (!ctx) throw new Error("No active pi-crew session context.");
94
+ const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
95
+ 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);
96
+ reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
97
+ } catch (error) {
98
+ reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
99
+ }
100
+ }),
101
+ ];
102
+ return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
103
+ }
@@ -2,12 +2,14 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { configPath as globalConfigPath } from "../config/config.ts";
4
4
  import { DEFAULT_UI } from "../config/defaults.ts";
5
+ import { injectGuidance, standardGuidanceBlocks } from "../config/markers.ts";
5
6
  import { packageRoot, projectCrewRoot, projectPiRoot } from "../utils/paths.ts";
6
7
 
7
8
  export interface ProjectInitOptions {
8
9
  copyBuiltins?: boolean;
9
10
  overwrite?: boolean;
10
11
  configScope?: "global" | "project" | "none";
12
+ ignoreMethod?: "gitignore" | "exclude";
11
13
  }
12
14
 
13
15
  export interface ProjectInitResult {
@@ -20,6 +22,8 @@ export interface ProjectInitResult {
20
22
  configScope: "global" | "project" | "none";
21
23
  configCreated: boolean;
22
24
  configSkipped: boolean;
25
+ guidancePath: string;
26
+ guidanceModified: boolean;
23
27
  }
24
28
 
25
29
  function ensureDir(dir: string, createdDirs: string[]): void {
@@ -121,16 +125,44 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
121
125
  copyBuiltinDir("workflows", workflowsDir, options.overwrite === true, copiedFiles, skippedFiles);
122
126
  }
123
127
 
124
- const gitignorePath = path.join(cwd, ".gitignore");
128
+ const ignoreMethod = options.ignoreMethod ?? "gitignore";
125
129
  const desired = [`${ignorePrefix}/state/`, `${ignorePrefix}/artifacts/`, `${ignorePrefix}/worktrees/`, `${ignorePrefix}/imports/`];
130
+ const gitignorePath = ignoreMethod === "exclude"
131
+ ? path.join(cwd, ".git", "info", "exclude")
132
+ : path.join(cwd, ".gitignore");
133
+ let gitignoreUpdated = false;
134
+ if (ignoreMethod === "exclude") {
135
+ // Ensure .git/info/ directory exists
136
+ const infoDir = path.dirname(gitignorePath);
137
+ if (!fs.existsSync(infoDir)) {
138
+ fs.mkdirSync(infoDir, { recursive: true });
139
+ }
140
+ }
126
141
  const existing = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, "utf-8") : "";
127
142
  const missing = desired.filter((entry) => !existing.split(/\r?\n/).includes(entry));
128
- let gitignoreUpdated = false;
129
143
  if (missing.length > 0) {
130
144
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
131
- fs.writeFileSync(gitignorePath, `${existing}${prefix}\n# pi-crew runtime state\n${missing.join("\n")}\n`, "utf-8");
145
+ const comment = "# pi-crew runtime state";
146
+ fs.writeFileSync(gitignorePath, `${existing}${prefix}\n${comment}\n${missing.join("\n")}\n`, "utf-8");
132
147
  gitignoreUpdated = true;
133
148
  }
134
149
 
135
- return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated, configPath, configScope, configCreated, configSkipped };
150
+ // Inject guidance into project AGENTS.md (or similar) using marker-based injection.
151
+ const guidancePath = path.join(cwd, "AGENTS.md");
152
+ const version = getPackageVersion();
153
+ const guidanceResult = injectGuidance(guidancePath, standardGuidanceBlocks(version));
154
+
155
+ return { createdDirs, copiedFiles, skippedFiles, gitignorePath, gitignoreUpdated, configPath, configScope, configCreated, configSkipped, guidancePath, guidanceModified: guidanceResult.modified };
156
+ }
157
+
158
+ /** Read the current package version from the nearest package.json. */
159
+ function getPackageVersion(): string {
160
+ try {
161
+ const pkgPath = path.join(packageRoot(), "package.json");
162
+ const raw = fs.readFileSync(pkgPath, "utf-8");
163
+ const parsed: { version?: string } = JSON.parse(raw) as { version?: string };
164
+ return parsed.version ?? "0.0.0";
165
+ } catch {
166
+ return "0.0.0";
167
+ }
136
168
  }
@@ -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, disposePowerbarCoalescer, registerPiCrewPowerbarSegments, requestPowerbarUpdate, updatePiCrewPowerbar } from "../ui/powerbar-publisher.ts";
12
+ import { clearPiCrewPowerbar, disposePowerbarCoalescer, registerPiCrewPowerbarSegments, requestPowerbarUpdate, resetPowerbarDedupState, 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";
@@ -38,6 +38,7 @@ 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
40
  import { cancelOrphanedRuns, detectInterruptedRuns, purgeStaleActiveRunIndex } from "../runtime/crash-recovery.ts";
41
+ import { pruneFinishedRuns, pruneUserLevelRuns } from "../extension/run-maintenance.ts";
41
42
  import { DeliveryCoordinator } from "../runtime/delivery-coordinator.ts";
42
43
  import { OverflowRecoveryTracker } from "../runtime/overflow-recovery.ts";
43
44
  import { tryRegisterSessionCleanup } from "../runtime/session-resources.ts";
@@ -301,9 +302,9 @@ export function registerPiTeams(pi: ExtensionAPI): void {
301
302
  .finally(() => {
302
303
  foregroundControllers.delete(key);
303
304
  const ownerCurrent = isContextCurrent(ctx, ownerGeneration);
304
- if (ownerCurrent && ctx.hasUI) {
305
- setWorkingIndicator(ctx);
306
- ctx.ui.setWorkingMessage();
305
+ if (ctx.hasUI) {
306
+ // Always clear working message/spinner — stale spinners for completed runs are confusing.
307
+ try { setWorkingIndicator(ctx); ctx.ui.setWorkingMessage(); } catch { /* ignore */ }
307
308
  }
308
309
  if (ownerCurrent && runId) {
309
310
  const loaded = loadRunManifestById(ctx.cwd, runId);
@@ -344,7 +345,11 @@ export function registerPiTeams(pi: ExtensionAPI): void {
344
345
  time("register.policy");
345
346
  registerAutonomousPolicy(pi);
346
347
  time("register.rpc");
347
- rpcHandle = registerPiCrewRpc((pi as unknown as { events?: Parameters<typeof registerPiCrewRpc>[0] }).events, () => currentCtx);
348
+ function getPiEvents(): Parameters<typeof registerPiCrewRpc>[0] | undefined {
349
+ if (pi && typeof pi === "object" && "events" in pi) return (pi as unknown as Record<string, unknown>).events as Parameters<typeof registerPiCrewRpc>[0];
350
+ return undefined;
351
+ }
352
+ rpcHandle = registerPiCrewRpc(getPiEvents(), () => currentCtx);
348
353
 
349
354
  const cleanupRuntime = (): void => {
350
355
  if (cleanedUp) return;
@@ -410,34 +415,64 @@ export function registerPiTeams(pi: ExtensionAPI): void {
410
415
  notifyActiveRuns(ctx);
411
416
 
412
417
  // Auto-cancel orphaned runs from dead sessions
413
- const currentSessionId = (ctx as unknown as Record<string, unknown>).sessionId as string | undefined;
414
- if (currentSessionId) {
418
+ const currentSessionId = (typeof ctx === "object" && ctx !== null && "sessionId" in ctx ? (ctx as Record<string, unknown>).sessionId : undefined) as string | undefined;
419
+
420
+ // Defer ALL heavy cleanup to after the session_start handler returns.
421
+ // These operations involve synchronous directory scanning (readdirSync, readFileSync)
422
+ // which can take 100ms–1s+ on Windows. They MUST NOT block the session_start event.
423
+ setTimeout(() => {
424
+ if (cleanedUp || sessionGeneration !== ownerGeneration) return; // session switched while we waited
425
+
426
+ // Auto-cancel orphaned runs
427
+ if (currentSessionId) {
428
+ try {
429
+ const { cancelled } = cancelOrphanedRuns(ctx.cwd, getManifestCache(ctx.cwd), currentSessionId);
430
+ if (cancelled.length > 0) {
431
+ 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(", ")}` });
432
+ }
433
+ } catch (error) {
434
+ logInternalError("register.sessionStart.orphanCleanup", error);
435
+ }
436
+ }
437
+
438
+ // Global purge of stale active-run-index entries
415
439
  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(", ")}` });
440
+ const { purged } = purgeStaleActiveRunIndex();
441
+ if (purged.length > 0) {
442
+ 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` });
419
443
  }
420
444
  } catch (error) {
421
- logInternalError("register.sessionStart.orphanCleanup", error);
445
+ logInternalError("register.sessionStart.globalIndexPurge", error);
422
446
  }
423
- }
424
447
 
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` });
448
+ // Auto-prune finished project-level run directories (keep 10 most recent)
449
+ try {
450
+ const { removed } = pruneFinishedRuns(ctx.cwd, 10);
451
+ if (removed.length > 0) {
452
+ notifyOperator({ id: `auto_prune_project`, severity: "info", source: "run-maintenance", title: `Auto-pruned ${removed.length} finished project run(s)`, body: `Removed old finished runs: ${removed.join(", ")}` });
453
+ }
454
+ } catch (error) {
455
+ logInternalError("register.sessionStart.autoPruneProject", error);
430
456
  }
431
- } catch (error) {
432
- logInternalError("register.sessionStart.globalIndexPurge", error);
433
- }
457
+
458
+ // Auto-prune finished user-level run directories (keep 10 most recent)
459
+ try {
460
+ const { removed } = pruneUserLevelRuns(10);
461
+ if (removed.length > 0) {
462
+ notifyOperator({ id: `auto_prune_user`, severity: "info", source: "run-maintenance", title: `Auto-pruned ${removed.length} finished user-level run(s)`, body: `Removed old finished runs: ${removed.join(", ")}` });
463
+ }
464
+ } catch (error) {
465
+ logInternalError("register.sessionStart.autoPruneUser", error);
466
+ }
467
+ }, 0);
468
+
434
469
 
435
470
  const loadedConfig = loadConfig(ctx.cwd);
436
471
  autoRecoveryLast.clear();
437
472
  configureNotifications(ctx);
438
473
  configureObservability(ctx);
439
474
  configureDeliveryCoordinator();
440
- const sessionId = ctx.sessionManager?.getSessionId?.() ?? (ctx as unknown as Record<string, unknown>).sessionId;
475
+ const sessionId = ctx.sessionManager?.getSessionId?.() ?? (typeof ctx === "object" && ctx !== null && "sessionId" in ctx ? (ctx as Record<string, unknown>).sessionId : undefined);
441
476
  if (typeof sessionId === "string" && sessionId) deliveryCoordinator?.activate(sessionId);
442
477
  tryRegisterSessionCleanup(pi, () => { terminateActiveChildPiProcesses(); cleanupRuntime(); });
443
478
  registerPiCrewPowerbarSegments(pi.events, loadedConfig.config.ui);
@@ -542,7 +577,16 @@ export function registerPiTeams(pi: ExtensionAPI): void {
542
577
  const fallbackMs = loadedConfig.config.ui?.dashboardLiveRefreshMs ?? DEFAULT_UI.refreshMs;
543
578
  renderScheduler = new RenderScheduler(pi.events, renderTick, {
544
579
  fallbackMs,
545
- onInvalidate: () => getRunSnapshotCache(ctx.cwd).invalidate(),
580
+ onInvalidate: (payload: unknown) => {
581
+ // Invalidate only the specific run, not the entire cache.
582
+ // Full cache.clear() causes widget flicker — the widget component's
583
+ // render() may run before renderTick rebuilds the preloaded frame,
584
+ // seeing an empty cache and returning no agents.
585
+ const runId = typeof payload === "object" && payload !== null && "runId" in payload && typeof (payload as { runId: unknown }).runId === "string"
586
+ ? (payload as { runId: string }).runId
587
+ : undefined;
588
+ getRunSnapshotCache(ctx.cwd).invalidate(runId);
589
+ },
546
590
  });
547
591
  // Start async preload loop — refreshes snapshot cache in background
548
592
  startPreloadLoop(fallbackMs);
@@ -561,6 +605,7 @@ export function registerPiTeams(pi: ExtensionAPI): void {
561
605
  logInternalError("register.session-before-switch", `Switching session with ${pendingCount} pending deliveries`);
562
606
  }
563
607
  deliveryCoordinator?.deactivate();
608
+ resetPowerbarDedupState();
564
609
  stopAsyncRunNotifier(notifierState);
565
610
  stopSessionBoundSubagents();
566
611
  });
@@ -1,20 +1,39 @@
1
1
  import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
  import { loadConfig } from "../../config/config.ts";
3
- import { handleTeamTool } from "../team-tool.ts";
3
+ // Lazy-loaded: team-tool.ts pulls in entire runtime chain (1.4s+).
4
+ import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
5
+ let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
6
+ let _handleTeamToolPromise: Promise<typeof HandleTeamToolFn> | undefined;
7
+ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
8
+ if (!_cachedHandleTeamTool) {
9
+ if (!_handleTeamToolPromise) {
10
+ _handleTeamToolPromise = import("../team-tool.ts").then((mod) => {
11
+ _cachedHandleTeamTool = mod.handleTeamTool;
12
+ return mod.handleTeamTool;
13
+ });
14
+ }
15
+ const fn = await _handleTeamToolPromise;
16
+ return fn(params, ctx);
17
+ }
18
+ return _cachedHandleTeamTool(params, ctx);
19
+ }
4
20
  import { withSessionId } from "../team-tool/context.ts";
5
21
  import { piTeamsHelp } from "../help.ts";
6
22
  import { handleTeamManagerCommand } from "../team-manager-command.ts";
7
23
  import { loadRunManifestById } from "../../state/state-store.ts";
8
24
  import type { TeamRunManifest } from "../../state/types.ts";
9
25
  import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
10
- import { AnimatedMascot } from "../../ui/mascot.ts";
11
26
  import * as path from "node:path";
12
- import { RunDashboard, type RunDashboardSelection } from "../../ui/run-dashboard.ts";
13
- import { DurableTextViewer } from "../../ui/transcript-viewer.ts";
14
- import { ConfirmOverlay, type ConfirmOptions } from "../../ui/overlays/confirm-overlay.ts";
15
- import { MailboxDetailOverlay, type MailboxAction } from "../../ui/overlays/mailbox-detail-overlay.ts";
16
- import { MailboxComposeOverlay, type MailboxComposeResult } from "../../ui/overlays/mailbox-compose-overlay.ts";
17
- import { AgentPickerOverlay } from "../../ui/overlays/agent-picker-overlay.ts";
27
+ // Heavy UI modules lazy-loaded because they're only used in /crew commands.
28
+ // RunDashboard (288ms), DurableTextViewer (658ms), Overlays are unnecessary at Pi startup.
29
+ import type { RunDashboard as RunDashboardType, RunDashboardSelection } from "../../ui/run-dashboard.ts";
30
+ import type { DurableTextViewer as DurableTextViewerType } from "../../ui/transcript-viewer.ts";
31
+ import type { ConfirmOverlay as ConfirmOverlayType, ConfirmOptions } from "../../ui/overlays/confirm-overlay.ts";
32
+ import type { MailboxDetailOverlay as MailboxDetailOverlayType, MailboxAction } from "../../ui/overlays/mailbox-detail-overlay.ts";
33
+ import type { MailboxComposeOverlay as MailboxComposeOverlayType, MailboxComposeResult } from "../../ui/overlays/mailbox-compose-overlay.ts";
34
+ import type { AgentPickerOverlay as AgentPickerOverlayType } from "../../ui/overlays/agent-picker-overlay.ts";
35
+ import type { AnimatedMascot as AnimatedMascotType } from "../../ui/mascot.ts";
36
+ // Eagerly import lightweight modules
18
37
  import { dispatchDiagnosticExport, dispatchHealthRecovery, dispatchKillStaleWorkers, dispatchMailboxAck, dispatchMailboxAckAll, dispatchMailboxCompose, dispatchMailboxNudge } from "../../ui/run-action-dispatcher.ts";
19
38
  import { DEFAULT_UI } from "../../config/defaults.ts";
20
39
  import { listRecentDiagnostic } from "../../runtime/diagnostic-export.ts";
@@ -35,13 +54,58 @@ export interface RegisterTeamCommandsDeps {
35
54
  dismissNotifications?: () => void;
36
55
  }
37
56
 
57
+ // Lazy-loaded UI module cache — avoids importing 900ms+ of UI at Pi startup.
58
+ // These modules are only needed when user invokes /crew commands.
59
+ let _uiCache: {
60
+ RunDashboard: typeof RunDashboardType;
61
+ DurableTextViewer: typeof DurableTextViewerType;
62
+ ConfirmOverlay: typeof ConfirmOverlayType;
63
+ MailboxDetailOverlay: typeof MailboxDetailOverlayType;
64
+ MailboxComposeOverlay: typeof MailboxComposeOverlayType;
65
+ AgentPickerOverlay: typeof AgentPickerOverlayType;
66
+ AnimatedMascot: typeof AnimatedMascotType;
67
+ } | undefined;
68
+ let _uiCachePromise: Promise<NonNullable<typeof _uiCache>> | undefined;
69
+ async function ui(): Promise<NonNullable<typeof _uiCache>> {
70
+ if (!_uiCache) {
71
+ if (!_uiCachePromise) {
72
+ _uiCachePromise = (async () => {
73
+ const [rd, tv, co, md, mc, ap, ma] = await Promise.all([
74
+ import("../../ui/run-dashboard.ts"),
75
+ import("../../ui/transcript-viewer.ts"),
76
+ import("../../ui/overlays/confirm-overlay.ts"),
77
+ import("../../ui/overlays/mailbox-detail-overlay.ts"),
78
+ import("../../ui/overlays/mailbox-compose-overlay.ts"),
79
+ import("../../ui/overlays/agent-picker-overlay.ts"),
80
+ import("../../ui/mascot.ts"),
81
+ ]);
82
+ const cache = {
83
+ RunDashboard: rd.RunDashboard,
84
+ DurableTextViewer: tv.DurableTextViewer,
85
+ ConfirmOverlay: co.ConfirmOverlay,
86
+ MailboxDetailOverlay: md.MailboxDetailOverlay,
87
+ MailboxComposeOverlay: mc.MailboxComposeOverlay,
88
+ AgentPickerOverlay: ap.AgentPickerOverlay,
89
+ AnimatedMascot: ma.AnimatedMascot,
90
+ };
91
+ _uiCache = cache;
92
+ return cache;
93
+ })();
94
+ }
95
+ return _uiCachePromise;
96
+ }
97
+ return _uiCache;
98
+ }
99
+
38
100
  async function openConfirm(ctx: ExtensionCommandContext, options: ConfirmOptions): Promise<boolean> {
39
101
  if (!ctx.hasUI) return false;
102
+ const { ConfirmOverlay } = await ui();
40
103
  return await ctx.ui.custom<boolean>((_tui, theme, _keybindings, done) => new ConfirmOverlay(options, done, theme), { overlay: true, overlayOptions: { width: 64, maxHeight: "70%", anchor: "center" } });
41
104
  }
42
105
 
43
106
  async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId: string): Promise<void> {
44
107
  if (!ctx.hasUI) return;
108
+ const { MailboxDetailOverlay } = await ui();
45
109
  const action = await ctx.ui.custom<MailboxAction | undefined>((_tui, theme, _keybindings, done) => new MailboxDetailOverlay({ runId, cwd: ctx.cwd, done, theme }), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
46
110
  if (!action || action.type === "close") return;
47
111
  let resultMessage: string | undefined;
@@ -57,6 +121,7 @@ async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId:
57
121
  ok = result.ok;
58
122
  resultMessage = result.message;
59
123
  } else if (action.type === "compose") {
124
+ const { MailboxComposeOverlay } = await ui();
60
125
  const compose = await ctx.ui.custom<MailboxComposeResult>((_tui, theme, _keybindings, done) => new MailboxComposeOverlay({ done, theme }), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
61
126
  if (compose.type === "cancel") return;
62
127
  const result = await dispatchMailboxCompose(ctx as ExtensionContext, runId, compose.payload);
@@ -65,6 +130,7 @@ async function handleMailboxDashboardAction(ctx: ExtensionCommandContext, runId:
65
130
  } else if (action.type === "nudge") {
66
131
  let agentId = action.agentId;
67
132
  if (!agentId) {
133
+ const { AgentPickerOverlay } = await ui();
68
134
  const picked = await ctx.ui.custom<{ agentId: string } | undefined>((_tui, theme, _keybindings, done) => new AgentPickerOverlay({ cwd: ctx.cwd, runId, done, theme }), { overlay: true, overlayOptions: { width: 72, maxHeight: "75%", anchor: "center" } });
69
135
  agentId = picked?.agentId;
70
136
  }
@@ -272,6 +338,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
272
338
  if (ctx.hasUI && loaded) {
273
339
  const agent = readCrewAgents(loaded.manifest).find((item) => item.taskId === selected?.taskId || item.id === selected?.taskId) ?? readCrewAgents(loaded.manifest)[0];
274
340
  const resultText = agent?.resultArtifactPath ? commandText(await handleTeamTool({ action: "api", runId: selected?.runId ?? "", config: { operation: "read-agent-output", agentId: agent.taskId, maxBytes: 64_000 } }, teamCommandContext(ctx))) : "(no result)";
341
+ const { DurableTextViewer } = await ui();
275
342
  await ctx.ui.custom<undefined>((_tui, theme, _keybindings, done) => new DurableTextViewer("pi-crew result", `${selected?.runId ?? ""}:${agent?.taskId ?? "unknown"}`, resultText.split(/\r?\n/), theme, done), { overlay: true, overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" } });
276
343
  return;
277
344
  }
@@ -292,6 +359,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
292
359
  const uiConfig = loadConfig(ctx.cwd).config.ui;
293
360
  const rightPanel = (uiConfig?.dashboardPlacement ?? DEFAULT_UI.dashboardPlacement) === "right";
294
361
  const width = rightPanel ? Math.min(90, Math.max(40, uiConfig?.dashboardWidth ?? DEFAULT_UI.dashboardWidth)) : "90%";
362
+ const { RunDashboard } = await ui();
295
363
  const selection = await ctx.ui.custom<RunDashboardSelection | undefined>((_tui, theme, _keybindings, done) => new RunDashboard(runs, done, theme, { placement: rightPanel ? "right" : "center", showModel: uiConfig?.showModel, showTokens: uiConfig?.showTokens, showTools: uiConfig?.showTools, snapshotCache: deps.getRunSnapshotCache?.(ctx.cwd), runProvider: () => deps.getManifestCache(ctx.cwd).list(50), registry: deps.getMetricRegistry?.() }), { overlay: true, overlayOptions: rightPanel ? { width, minWidth: 40, maxHeight: "100%", anchor: "top-right", offsetX: 0, offsetY: 0, margin: { top: 0, right: 0, bottom: 0, left: 0 } } : { width, maxHeight: "90%", anchor: "center", margin: 2 } });
296
364
  if (!selection) return;
297
365
  if (selection.action === "reload") continue;
@@ -325,6 +393,7 @@ export function registerTeamCommands(pi: ExtensionAPI, deps: RegisterTeamCommand
325
393
  const effectArg = tokens.find((t) => ["random", "none", "typewriter", "scanline", "rain", "fade", "crt", "glitch", "dissolve"].includes(t));
326
394
  const style = (styleArg as "cat" | "armin" | undefined) ?? uiConfig?.mascotStyle ?? DEFAULT_UI.mascotStyle;
327
395
  const effect = (effectArg as "random" | "none" | "typewriter" | "scanline" | "rain" | "fade" | "crt" | "glitch" | "dissolve" | undefined) ?? uiConfig?.mascotEffect ?? DEFAULT_UI.mascotEffect;
396
+ const { AnimatedMascot } = await ui();
328
397
  await ctx.ui.custom<undefined>((tui, theme, _keybindings, done) => new AnimatedMascot(theme, () => done(undefined), { frameIntervalMs: style === "armin" ? 33 : 180, autoCloseMs: 7000, requestRender: () => requestRenderTarget(tui), style, effect }), { overlay: true, overlayOptions: { width: style === "armin" ? 48 : 62, maxHeight: "85%", anchor: "center" } });
329
398
  } });
330
399
 
@@ -1,7 +1,16 @@
1
1
  import type { ExtensionAPI, ToolDefinition } from "@mariozechner/pi-coding-agent";
2
2
  import { Type } from "typebox";
3
3
  import type { TeamToolParamsValue } from "../../schema/team-tool-schema.ts";
4
- import { handleTeamTool } from "../team-tool.ts";
4
+ // Lazy-loaded: team-tool.ts pulls in entire runtime chain.
5
+ import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
6
+ let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
7
+ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<Awaited<ReturnType<typeof HandleTeamToolFn>>> {
8
+ if (!_cachedHandleTeamTool) {
9
+ const mod = await import("../team-tool.ts");
10
+ _cachedHandleTeamTool = mod.handleTeamTool;
11
+ }
12
+ return _cachedHandleTeamTool(params, ctx);
13
+ }
5
14
  import { checkSubagentSpawnPermission, currentCrewRole } from "../../runtime/role-permission.ts";
6
15
  import { readPersistedSubagentRecord, savePersistedSubagentRecord, type SubagentManager, type SubagentSpawnOptions } from "../../subagents/manager.ts";
7
16
  import { loadConfig } from "../../config/config.ts";
@@ -9,7 +9,16 @@ import type { createManifestCache } from "../../runtime/manifest-cache.ts";
9
9
  import type { createRunSnapshotCache } from "../../ui/run-snapshot-cache.ts";
10
10
  import type { MetricRegistry } from "../../observability/metric-registry.ts";
11
11
  import { resolveRealContainedPath } from "../../utils/safe-paths.ts";
12
- import { handleTeamTool } from "../team-tool.ts";
12
+ // Team tool handler lazy-loaded because team-tool.ts imports many modules
13
+ import type { handleTeamTool as HandleTeamToolFn } from "../team-tool.ts";
14
+ let _cachedHandleTeamTool: typeof HandleTeamToolFn | undefined;
15
+ async function handleTeamTool(params: Parameters<typeof HandleTeamToolFn>[0], ctx: Parameters<typeof HandleTeamToolFn>[1]): Promise<ReturnType<typeof HandleTeamToolFn>> {
16
+ if (!_cachedHandleTeamTool) {
17
+ const mod = await import("../team-tool.ts");
18
+ _cachedHandleTeamTool = mod.handleTeamTool;
19
+ }
20
+ return _cachedHandleTeamTool(params, ctx);
21
+ }
13
22
  import { withSessionId } from "../team-tool/context.ts";
14
23
  import { toolResult } from "../tool-result.ts";
15
24