pi-crew 0.1.51 → 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 (239) hide show
  1. package/CHANGELOG.md +56 -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 +13 -1
  48. package/src/config/drift-detector.ts +211 -0
  49. package/src/config/markers.ts +327 -0
  50. package/src/config/resilient-parser.ts +108 -0
  51. package/src/config/suggestions.ts +74 -0
  52. package/src/extension/cross-extension-rpc.ts +103 -94
  53. package/src/extension/project-init.ts +21 -1
  54. package/src/extension/register.ts +45 -14
  55. package/src/extension/registration/commands.ts +77 -8
  56. package/src/extension/registration/subagent-tools.ts +10 -1
  57. package/src/extension/registration/team-tool.ts +10 -1
  58. package/src/extension/registration/viewers.ts +48 -34
  59. package/src/extension/run-bundle-schema.ts +89 -89
  60. package/src/extension/run-import.ts +25 -1
  61. package/src/extension/run-index.ts +5 -1
  62. package/src/extension/run-maintenance.ts +142 -68
  63. package/src/extension/team-manager-command.ts +10 -1
  64. package/src/extension/team-tool/doctor.ts +28 -3
  65. package/src/extension/team-tool/handle-settings.ts +195 -188
  66. package/src/extension/team-tool/inspect.ts +41 -41
  67. package/src/extension/team-tool/intent-policy.ts +42 -42
  68. package/src/extension/team-tool/lifecycle-actions.ts +27 -8
  69. package/src/extension/team-tool/plan.ts +19 -19
  70. package/src/extension/team-tool/run.ts +12 -1
  71. package/src/extension/team-tool.ts +11 -1
  72. package/src/i18n.ts +184 -184
  73. package/src/observability/exporters/otlp-exporter.ts +92 -77
  74. package/src/prompt/prompt-runtime.ts +72 -72
  75. package/src/runtime/agent-memory.ts +72 -72
  76. package/src/runtime/agent-observability.ts +114 -114
  77. package/src/runtime/async-marker.ts +26 -26
  78. package/src/runtime/attention-events.ts +28 -28
  79. package/src/runtime/auto-resume.ts +100 -0
  80. package/src/runtime/background-runner.ts +11 -1
  81. package/src/runtime/cancellation-token.ts +89 -89
  82. package/src/runtime/cancellation.ts +61 -61
  83. package/src/runtime/capability-inventory.ts +116 -116
  84. package/src/runtime/child-pi.ts +7 -2
  85. package/src/runtime/compaction-summary.ts +271 -0
  86. package/src/runtime/completion-guard.ts +190 -190
  87. package/src/runtime/crash-recovery.ts +33 -0
  88. package/src/runtime/delta-conflict.ts +360 -0
  89. package/src/runtime/direct-run.ts +35 -35
  90. package/src/runtime/foreground-control.ts +82 -82
  91. package/src/runtime/green-contract.ts +46 -46
  92. package/src/runtime/group-join.ts +106 -106
  93. package/src/runtime/heartbeat-gradient.ts +28 -28
  94. package/src/runtime/heartbeat-watcher.ts +124 -124
  95. package/src/runtime/iteration-hooks.ts +262 -0
  96. package/src/runtime/live-agent-control.ts +88 -88
  97. package/src/runtime/live-control-realtime.ts +36 -36
  98. package/src/runtime/live-extension-bridge.ts +150 -150
  99. package/src/runtime/live-irc.ts +92 -92
  100. package/src/runtime/live-session-health.ts +100 -100
  101. package/src/runtime/loop-gates.ts +129 -0
  102. package/src/runtime/metric-parser.ts +40 -0
  103. package/src/runtime/notebook-helpers.ts +90 -90
  104. package/src/runtime/orphan-sentinel.ts +7 -7
  105. package/src/runtime/parallel-research.ts +44 -44
  106. package/src/runtime/phase-progress.ts +217 -0
  107. package/src/runtime/pi-args.ts +38 -11
  108. package/src/runtime/pi-json-output.ts +111 -111
  109. package/src/runtime/pi-spawn.ts +57 -7
  110. package/src/runtime/policy-engine.ts +79 -79
  111. package/src/runtime/post-checks.ts +122 -0
  112. package/src/runtime/progress-event-coalescer.ts +43 -43
  113. package/src/runtime/prose-compressor.ts +164 -164
  114. package/src/runtime/recovery-recipes.ts +74 -74
  115. package/src/runtime/result-extractor.ts +121 -121
  116. package/src/runtime/role-permission.ts +39 -39
  117. package/src/runtime/sensitive-paths.ts +2 -2
  118. package/src/runtime/session-resources.ts +25 -25
  119. package/src/runtime/session-snapshot.ts +59 -59
  120. package/src/runtime/session-usage.ts +79 -79
  121. package/src/runtime/sidechain-output.ts +29 -29
  122. package/src/runtime/stream-preview.ts +177 -177
  123. package/src/runtime/supervisor-contact.ts +59 -59
  124. package/src/runtime/task-display.ts +38 -38
  125. package/src/runtime/task-graph.ts +207 -0
  126. package/src/runtime/task-quality.ts +207 -0
  127. package/src/runtime/task-runner/capabilities.ts +78 -78
  128. package/src/runtime/task-runner/live-executor.ts +7 -1
  129. package/src/runtime/task-runner/progress.ts +119 -119
  130. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  131. package/src/runtime/task-runner/result-utils.ts +14 -14
  132. package/src/runtime/task-runner/run-projection.ts +103 -103
  133. package/src/runtime/task-runner/state-helpers.ts +22 -22
  134. package/src/runtime/team-runner.ts +117 -7
  135. package/src/runtime/worker-heartbeat.ts +21 -21
  136. package/src/runtime/worker-startup.ts +57 -57
  137. package/src/runtime/workflow-state.ts +187 -0
  138. package/src/runtime/workspace-tree.ts +298 -298
  139. package/src/schema/config-schema.ts +11 -0
  140. package/src/schema/validation-types.ts +148 -0
  141. package/src/skills/skill-templates.ts +374 -0
  142. package/src/state/active-run-registry.ts +35 -11
  143. package/src/state/atomic-write.ts +33 -26
  144. package/src/state/contracts.ts +1 -0
  145. package/src/state/event-reconstructor.ts +217 -0
  146. package/src/state/locks.ts +2 -13
  147. package/src/state/mailbox.ts +4 -3
  148. package/src/state/state-store.ts +32 -14
  149. package/src/state/task-claims.ts +44 -44
  150. package/src/state/types.ts +9 -0
  151. package/src/state/usage.ts +29 -29
  152. package/src/subagents/async-entry.ts +1 -1
  153. package/src/subagents/index.ts +3 -3
  154. package/src/subagents/live/control.ts +1 -1
  155. package/src/subagents/live/manager.ts +1 -1
  156. package/src/subagents/live/realtime.ts +1 -1
  157. package/src/subagents/live/session-runtime.ts +1 -1
  158. package/src/subagents/manager.ts +1 -1
  159. package/src/subagents/spawn.ts +1 -1
  160. package/src/teams/team-serializer.ts +38 -38
  161. package/src/types/diff.d.ts +18 -18
  162. package/src/ui/crew-footer.ts +101 -101
  163. package/src/ui/crew-select-list.ts +111 -111
  164. package/src/ui/crew-widget.ts +5 -2
  165. package/src/ui/dashboard-panes/cancellation-pane.ts +42 -42
  166. package/src/ui/dashboard-panes/capability-pane.ts +59 -59
  167. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -35
  168. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  169. package/src/ui/dashboard-panes/progress-pane.ts +11 -0
  170. package/src/ui/dynamic-border.ts +25 -25
  171. package/src/ui/layout-primitives.ts +106 -106
  172. package/src/ui/loaders.ts +158 -158
  173. package/src/ui/render-coalescer.ts +51 -51
  174. package/src/ui/render-diff.ts +119 -119
  175. package/src/ui/render-scheduler.ts +143 -143
  176. package/src/ui/run-action-dispatcher.ts +10 -1
  177. package/src/ui/spinner.ts +17 -17
  178. package/src/ui/status-colors.ts +58 -58
  179. package/src/ui/syntax-highlight.ts +116 -116
  180. package/src/ui/transcript-entries.ts +258 -258
  181. package/src/utils/completion-dedupe.ts +63 -63
  182. package/src/utils/frontmatter.ts +68 -68
  183. package/src/utils/git.ts +262 -262
  184. package/src/utils/ids.ts +17 -17
  185. package/src/utils/incremental-reader.ts +104 -104
  186. package/src/utils/names.ts +27 -27
  187. package/src/utils/redaction.ts +44 -44
  188. package/src/utils/safe-paths.ts +47 -47
  189. package/src/utils/scan-cache.ts +136 -136
  190. package/src/utils/sleep.ts +40 -26
  191. package/src/utils/task-name-generator.ts +337 -337
  192. package/src/workflows/validate-workflow.ts +40 -40
  193. package/src/worktree/branch-freshness.ts +45 -45
  194. package/teams/default.team.md +12 -12
  195. package/teams/fast-fix.team.md +11 -11
  196. package/teams/implementation.team.md +18 -18
  197. package/teams/parallel-research.team.md +14 -14
  198. package/teams/research.team.md +11 -11
  199. package/teams/review.team.md +12 -12
  200. package/workflows/default.workflow.md +30 -29
  201. package/workflows/fast-fix.workflow.md +23 -22
  202. package/workflows/implementation.workflow.md +43 -43
  203. package/workflows/parallel-research.workflow.md +46 -46
  204. package/workflows/research.workflow.md +22 -22
  205. package/workflows/review.workflow.md +30 -30
  206. package/docs/refactor-tasks-phase3.md +0 -394
  207. package/docs/refactor-tasks-phase4.md +0 -564
  208. package/docs/refactor-tasks-phase5.md +0 -402
  209. package/docs/refactor-tasks-phase6.md +0 -662
  210. package/docs/refactor-tasks.md +0 -1484
  211. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +0 -261
  212. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +0 -111
  213. package/docs/research/AUDIT_OH_MY_PI.md +0 -261
  214. package/docs/research/AUDIT_PI_CREW.md +0 -457
  215. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +0 -281
  216. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +0 -264
  217. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +0 -343
  218. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +0 -480
  219. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +0 -354
  220. package/docs/research/IMPLEMENTATION_PLAN.md +0 -385
  221. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +0 -502
  222. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +0 -266
  223. package/docs/research/REMAINING-GAPS-PLAN.md +0 -363
  224. package/docs/research/SESSION-SUMMARY-2026-05-08.md +0 -146
  225. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +0 -173
  226. package/docs/research-awesome-agent-skills-distillation.md +0 -100
  227. package/docs/research-extension-examples.md +0 -297
  228. package/docs/research-extension-system.md +0 -324
  229. package/docs/research-oh-my-pi-distillation.md +0 -369
  230. package/docs/research-optimization-plan.md +0 -548
  231. package/docs/research-phase10-distillation.md +0 -199
  232. package/docs/research-phase11-distillation.md +0 -201
  233. package/docs/research-phase8-operator-experience-plan.md +0 -819
  234. package/docs/research-phase9-observability-reliability-plan.md +0 -1190
  235. package/docs/research-pi-coding-agent.md +0 -357
  236. package/docs/research-source-pi-crew-reference.md +0 -174
  237. package/docs/research-ui-optimization-plan.md +0 -480
  238. package/docs/source-runtime-refactor-map.md +0 -107
  239. package/src/utils/atomic-write.ts +0 -33
@@ -1,94 +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
- // Validate payload: only allow known fields from TeamToolParamsValue
46
- const ALLOWED_RPC_RUN_KEYS = new Set(["goal", "team", "workflow", "async", "cwd", "config", "skill", "model"]);
47
- let params: TeamToolParamsValue;
48
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
49
- const filtered: Record<string, unknown> = { ...(raw as object) };
50
- // Strip any keys not in the allowlist to prevent injection of unexpected fields
51
- for (const key of Object.keys(filtered)) {
52
- if (!ALLOWED_RPC_RUN_KEYS.has(key)) delete filtered[key];
53
- }
54
- params = { ...filtered, action: "run" } as TeamToolParamsValue;
55
- } else {
56
- params = { action: "run" };
57
- }
58
- const result = await handleTeamTool(params, ctx);
59
- reply(events, "pi-crew:rpc:run", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: result.details });
60
- } catch (error) {
61
- reply(events, "pi-crew:rpc:run", id, { success: false, error: error instanceof Error ? error.message : String(error) });
62
- }
63
- }),
64
- on(events, "pi-crew:rpc:status", async (raw) => {
65
- const id = requestId(raw);
66
- try {
67
- const ctx = getCtx();
68
- if (!ctx) throw new Error("No active pi-crew session context.");
69
- const runId = raw && typeof raw === "object" && !Array.isArray(raw) ? (raw as { runId?: string }).runId : undefined;
70
- const result = await handleTeamTool({ action: "status", runId }, ctx);
71
- reply(events, "pi-crew:rpc:status", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
72
- } catch (error) {
73
- reply(events, "pi-crew:rpc:status", id, { success: false, error: error instanceof Error ? error.message : String(error) });
74
- }
75
- }),
76
- on(events, "pi-crew:live-control", (raw) => {
77
- const request = parseLiveControlRealtimeMessage(raw);
78
- if (request) publishLiveControlRealtime(request);
79
- }),
80
- on(events, "pi-crew:rpc:live-control", async (raw) => {
81
- const id = requestId(raw);
82
- try {
83
- const ctx = getCtx();
84
- if (!ctx) throw new Error("No active pi-crew session context.");
85
- const obj = raw && typeof raw === "object" && !Array.isArray(raw) ? raw as Record<string, unknown> : {};
86
- 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);
87
- reply(events, "pi-crew:rpc:live-control", id, result.isError ? { success: false, error: textOf(result) } : { success: true, data: { text: textOf(result), details: result.details } });
88
- } catch (error) {
89
- reply(events, "pi-crew:rpc:live-control", id, { success: false, error: error instanceof Error ? error.message : String(error) });
90
- }
91
- }),
92
- ];
93
- return { unsubscribe: () => unsubs.forEach((unsub) => unsub()) };
94
- }
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,6 +2,7 @@ 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 {
@@ -21,6 +22,8 @@ export interface ProjectInitResult {
21
22
  configScope: "global" | "project" | "none";
22
23
  configCreated: boolean;
23
24
  configSkipped: boolean;
25
+ guidancePath: string;
26
+ guidanceModified: boolean;
24
27
  }
25
28
 
26
29
  function ensureDir(dir: string, createdDirs: string[]): void {
@@ -144,5 +147,22 @@ export function initializeProject(cwd: string, options: ProjectInitOptions = {})
144
147
  gitignoreUpdated = true;
145
148
  }
146
149
 
147
- 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
+ }
148
168
  }
@@ -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";
@@ -415,26 +416,56 @@ export function registerPiTeams(pi: ExtensionAPI): void {
415
416
 
416
417
  // Auto-cancel orphaned runs from dead sessions
417
418
  const currentSessionId = (typeof ctx === "object" && ctx !== null && "sessionId" in ctx ? (ctx as Record<string, unknown>).sessionId : undefined) as string | undefined;
418
- if (currentSessionId) {
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
419
439
  try {
420
- const { cancelled } = cancelOrphanedRuns(ctx.cwd, getManifestCache(ctx.cwd), currentSessionId);
421
- if (cancelled.length > 0) {
422
- 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` });
423
443
  }
424
444
  } catch (error) {
425
- logInternalError("register.sessionStart.orphanCleanup", error);
445
+ logInternalError("register.sessionStart.globalIndexPurge", error);
426
446
  }
427
- }
428
447
 
429
- // Global purge of stale active-run-index entries (temp dirs, dead workers, etc.)
430
- try {
431
- const { purged } = purgeStaleActiveRunIndex();
432
- if (purged.length > 0) {
433
- 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);
434
456
  }
435
- } catch (error) {
436
- logInternalError("register.sessionStart.globalIndexPurge", error);
437
- }
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
+
438
469
 
439
470
  const loadedConfig = loadConfig(ctx.cwd);
440
471
  autoRecoveryLast.clear();
@@ -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
 
@@ -1,34 +1,48 @@
1
- import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
- import { loadRunManifestById } from "../../state/state-store.ts";
3
- import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
4
- import { loadConfig } from "../../config/config.ts";
5
- import { DurableTranscriptViewer } from "../../ui/transcript-viewer.ts";
6
-
7
- export async function selectAgentTask(ctx: ExtensionCommandContext, runId: string | undefined, taskId?: string): Promise<{ runId: string; taskId?: string } | undefined> {
8
- if (!runId) return undefined;
9
- if (taskId) return { runId, taskId };
10
- const loaded = loadRunManifestById(ctx.cwd, runId);
11
- if (!loaded) return { runId };
12
- const agents = readCrewAgents(loaded.manifest);
13
- if (ctx.hasUI && agents.length > 1) {
14
- const choice = await ctx.ui.select("Select pi-crew agent", agents.map((agent) => `${agent.taskId} ${agent.role}→${agent.agent} [${agent.status}]`));
15
- return { runId, taskId: choice?.split(" ")[0] };
16
- }
17
- return { runId, taskId: agents[0]?.taskId };
18
- }
19
-
20
- export async function openTranscriptViewer(ctx: ExtensionCommandContext, initialRunId: string | undefined, initialTaskId?: string): Promise<boolean> {
21
- const selected = await selectAgentTask(ctx, initialRunId, initialTaskId);
22
- if (!selected) return false;
23
- const runId = selected.runId;
24
- const taskId = selected.taskId;
25
- if (!runId || !ctx.hasUI) return false;
26
- const loaded = loadRunManifestById(ctx.cwd, runId);
27
- if (!loaded) return false;
28
- const uiConfig = loadConfig(ctx.cwd).config.ui;
29
- await ctx.ui.custom<undefined>((_tui, theme, _keybindings, done) => new DurableTranscriptViewer(loaded.manifest, theme, done, taskId, { maxTailBytes: uiConfig?.transcriptTailBytes }), {
30
- overlay: true,
31
- overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" },
32
- });
33
- return true;
34
- }
1
+ import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
2
+ import { loadRunManifestById } from "../../state/state-store.ts";
3
+ import { readCrewAgents } from "../../runtime/crew-agent-records.ts";
4
+ import { loadConfig } from "../../config/config.ts";
5
+ // Lazy-loaded: DurableTranscriptViewer is 658ms — only needed for /crew transcript command
6
+ import type { DurableTranscriptViewer as DurableTranscriptViewerType } from "../../ui/transcript-viewer.ts";
7
+ let _cachedViewer: typeof DurableTranscriptViewerType | undefined;
8
+ let _viewerPromise: Promise<typeof DurableTranscriptViewerType> | undefined;
9
+ async function getViewer(): Promise<typeof DurableTranscriptViewerType> {
10
+ if (_cachedViewer) return _cachedViewer;
11
+ if (!_viewerPromise) {
12
+ _viewerPromise = import("../../ui/transcript-viewer.ts").then((mod) => {
13
+ _cachedViewer = mod.DurableTranscriptViewer;
14
+ return mod.DurableTranscriptViewer;
15
+ });
16
+ }
17
+ return _viewerPromise;
18
+ }
19
+
20
+ export async function selectAgentTask(ctx: ExtensionCommandContext, runId: string | undefined, taskId?: string): Promise<{ runId: string; taskId?: string } | undefined> {
21
+ if (!runId) return undefined;
22
+ if (taskId) return { runId, taskId };
23
+ const loaded = loadRunManifestById(ctx.cwd, runId);
24
+ if (!loaded) return { runId };
25
+ const agents = readCrewAgents(loaded.manifest);
26
+ if (ctx.hasUI && agents.length > 1) {
27
+ const choice = await ctx.ui.select("Select pi-crew agent", agents.map((agent) => `${agent.taskId} ${agent.role}→${agent.agent} [${agent.status}]`));
28
+ return { runId, taskId: choice?.split(" ")[0] };
29
+ }
30
+ return { runId, taskId: agents[0]?.taskId };
31
+ }
32
+
33
+ export async function openTranscriptViewer(ctx: ExtensionCommandContext, initialRunId: string | undefined, initialTaskId?: string): Promise<boolean> {
34
+ const selected = await selectAgentTask(ctx, initialRunId, initialTaskId);
35
+ if (!selected) return false;
36
+ const runId = selected.runId;
37
+ const taskId = selected.taskId;
38
+ if (!runId || !ctx.hasUI) return false;
39
+ const loaded = loadRunManifestById(ctx.cwd, runId);
40
+ if (!loaded) return false;
41
+ const uiConfig = loadConfig(ctx.cwd).config.ui;
42
+ const DurableTranscriptViewer = await getViewer();
43
+ await ctx.ui.custom<undefined>((_tui, theme, _keybindings, done) => new DurableTranscriptViewer(loaded.manifest, theme, done, taskId, { maxTailBytes: uiConfig?.transcriptTailBytes }), {
44
+ overlay: true,
45
+ overlayOptions: { width: "90%", maxHeight: "85%", anchor: "center" },
46
+ });
47
+ return true;
48
+ }