pi-crew 0.1.46 → 0.1.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/agents/analyst.md +11 -11
  3. package/agents/critic.md +11 -11
  4. package/agents/executor.md +11 -11
  5. package/agents/explorer.md +11 -11
  6. package/agents/planner.md +11 -11
  7. package/agents/reviewer.md +11 -11
  8. package/agents/security-reviewer.md +11 -11
  9. package/agents/test-engineer.md +11 -11
  10. package/agents/verifier.md +11 -11
  11. package/agents/writer.md +11 -11
  12. package/docs/next-upgrade-roadmap.md +117 -42
  13. package/docs/refactor-tasks-phase3.md +394 -394
  14. package/docs/refactor-tasks-phase4.md +564 -564
  15. package/docs/refactor-tasks-phase5.md +402 -402
  16. package/docs/refactor-tasks-phase6.md +662 -662
  17. package/docs/research/AGENT-EXECUTION-ARCHITECTURE.md +261 -0
  18. package/docs/research/AGENT-LIFECYCLE-COMPARISON.md +111 -0
  19. package/docs/research/AUDIT_OH_MY_PI.md +261 -0
  20. package/docs/research/AUDIT_PI_CREW.md +457 -0
  21. package/docs/research/CAVEMAN-DEEP-RESEARCH.md +281 -0
  22. package/docs/research/COMPARISON_OH_MY_PI_VS_PI_CREW.md +264 -0
  23. package/docs/research/DEEP-RESEARCH-PI-POWERBAR.md +343 -0
  24. package/docs/research/DEEP_RESEARCH_SUBAGENT_ARCHITECTURE.md +480 -0
  25. package/docs/research/GAP_CLOSURE_IMPLEMENTATION_PLAN.md +354 -0
  26. package/docs/research/IMPLEMENTATION_PLAN.md +385 -0
  27. package/docs/research/LIVE-SESSION-PRODUCTION-READY-PLAN.md +502 -0
  28. package/docs/research/OH-MY-PI-DEEP-RESEARCH-v14.7.6.md +266 -0
  29. package/docs/research/REMAINING-GAPS-PLAN.md +363 -0
  30. package/docs/research/SESSION-SUMMARY-2026-05-08.md +146 -0
  31. package/docs/research/UI-RESPONSIVENESS-AUDIT.md +173 -0
  32. package/docs/research-awesome-agent-skills-distillation.md +100 -100
  33. package/docs/research-extension-examples.md +297 -297
  34. package/docs/research-extension-system.md +324 -324
  35. package/docs/research-oh-my-pi-distillation.md +56 -9
  36. package/docs/research-optimization-plan.md +548 -548
  37. package/docs/research-phase10-distillation.md +198 -198
  38. package/docs/research-phase11-distillation.md +201 -201
  39. package/docs/research-pi-coding-agent.md +357 -357
  40. package/docs/research-source-pi-crew-reference.md +174 -174
  41. package/docs/runtime-flow.md +148 -148
  42. package/docs/source-runtime-refactor-map.md +107 -107
  43. package/index.ts +6 -6
  44. package/package.json +99 -98
  45. package/schema.json +8 -0
  46. package/skills/async-worker-recovery/SKILL.md +42 -42
  47. package/skills/context-artifact-hygiene/SKILL.md +52 -52
  48. package/skills/delegation-patterns/SKILL.md +54 -54
  49. package/skills/mailbox-interactive/SKILL.md +40 -40
  50. package/skills/model-routing-context/SKILL.md +39 -39
  51. package/skills/multi-perspective-review/SKILL.md +58 -58
  52. package/skills/observability-reliability/SKILL.md +41 -41
  53. package/skills/orchestration/SKILL.md +157 -0
  54. package/skills/ownership-session-security/SKILL.md +41 -41
  55. package/skills/pi-extension-lifecycle/SKILL.md +39 -39
  56. package/skills/requirements-to-task-packet/SKILL.md +63 -63
  57. package/skills/resource-discovery-config/SKILL.md +41 -41
  58. package/skills/runtime-state-reader/SKILL.md +44 -44
  59. package/skills/secure-agent-orchestration-review/SKILL.md +45 -45
  60. package/skills/state-mutation-locking/SKILL.md +42 -42
  61. package/skills/systematic-debugging/SKILL.md +67 -67
  62. package/skills/ui-render-performance/SKILL.md +39 -39
  63. package/skills/verification-before-done/SKILL.md +57 -57
  64. package/skills/worktree-isolation/SKILL.md +39 -39
  65. package/src/agents/agent-config.ts +6 -0
  66. package/src/agents/agent-search.ts +98 -0
  67. package/src/agents/agent-serializer.ts +4 -0
  68. package/src/agents/discover-agents.ts +17 -4
  69. package/src/config/config.ts +24 -0
  70. package/src/config/defaults.ts +11 -0
  71. package/src/extension/autonomous-policy.ts +26 -33
  72. package/src/extension/cross-extension-rpc.ts +82 -82
  73. package/src/extension/help.ts +1 -0
  74. package/src/extension/management.ts +5 -0
  75. package/src/extension/register.ts +58 -13
  76. package/src/extension/registration/commands.ts +33 -1
  77. package/src/extension/registration/compaction-guard.ts +125 -125
  78. package/src/extension/registration/team-tool.ts +6 -4
  79. package/src/extension/run-bundle-schema.ts +89 -89
  80. package/src/extension/run-index.ts +24 -18
  81. package/src/extension/run-maintenance.ts +68 -62
  82. package/src/extension/team-tool/api.ts +23 -2
  83. package/src/extension/team-tool/cancel.ts +86 -11
  84. package/src/extension/team-tool/context.ts +3 -0
  85. package/src/extension/team-tool/handle-settings.ts +188 -188
  86. package/src/extension/team-tool/inspect.ts +41 -41
  87. package/src/extension/team-tool/intent-policy.ts +42 -0
  88. package/src/extension/team-tool/lifecycle-actions.ts +47 -18
  89. package/src/extension/team-tool/parallel-dispatch.ts +156 -0
  90. package/src/extension/team-tool/plan.ts +19 -19
  91. package/src/extension/team-tool/respond.ts +10 -2
  92. package/src/extension/team-tool/run.ts +3 -2
  93. package/src/extension/team-tool/status.ts +1 -1
  94. package/src/extension/team-tool-types.ts +1 -0
  95. package/src/extension/team-tool.ts +13 -3
  96. package/src/hooks/registry.ts +61 -0
  97. package/src/hooks/types.ts +41 -0
  98. package/src/i18n.ts +184 -184
  99. package/src/observability/exporters/otlp-exporter.ts +77 -77
  100. package/src/prompt/prompt-runtime.ts +72 -72
  101. package/src/runtime/agent-control.ts +108 -2
  102. package/src/runtime/agent-memory.ts +72 -72
  103. package/src/runtime/agent-observability.ts +114 -114
  104. package/src/runtime/async-marker.ts +26 -26
  105. package/src/runtime/async-runner.ts +3 -1
  106. package/src/runtime/attention-events.ts +28 -28
  107. package/src/runtime/background-runner.ts +19 -0
  108. package/src/runtime/cancellation-token.ts +89 -0
  109. package/src/runtime/cancellation.ts +61 -51
  110. package/src/runtime/capability-inventory.ts +116 -0
  111. package/src/runtime/child-pi.ts +2 -1
  112. package/src/runtime/code-summary.ts +247 -0
  113. package/src/runtime/completion-guard.ts +190 -190
  114. package/src/runtime/crash-recovery.ts +181 -0
  115. package/src/runtime/crew-agent-records.ts +35 -7
  116. package/src/runtime/crew-agent-runtime.ts +1 -0
  117. package/src/runtime/custom-tools/irc-tool.ts +201 -0
  118. package/src/runtime/custom-tools/submit-result-tool.ts +90 -0
  119. package/src/runtime/delivery-coordinator.ts +3 -1
  120. package/src/runtime/direct-run.ts +35 -35
  121. package/src/runtime/effectiveness.ts +81 -76
  122. package/src/runtime/event-stream-bridge.ts +90 -0
  123. package/src/runtime/foreground-control.ts +82 -82
  124. package/src/runtime/green-contract.ts +46 -46
  125. package/src/runtime/group-join.ts +106 -106
  126. package/src/runtime/heartbeat-gradient.ts +28 -28
  127. package/src/runtime/heartbeat-watcher.ts +124 -124
  128. package/src/runtime/live-agent-control.ts +88 -88
  129. package/src/runtime/live-agent-manager.ts +78 -2
  130. package/src/runtime/live-control-realtime.ts +36 -36
  131. package/src/runtime/live-extension-bridge.ts +150 -0
  132. package/src/runtime/live-irc.ts +92 -0
  133. package/src/runtime/live-session-health.ts +100 -0
  134. package/src/runtime/live-session-runtime.ts +297 -7
  135. package/src/runtime/mcp-proxy.ts +113 -0
  136. package/src/runtime/notebook-helpers.ts +90 -0
  137. package/src/runtime/orphan-sentinel.ts +7 -0
  138. package/src/runtime/output-validator.ts +187 -0
  139. package/src/runtime/parallel-research.ts +44 -44
  140. package/src/runtime/parallel-utils.ts +57 -0
  141. package/src/runtime/parent-guard.ts +80 -0
  142. package/src/runtime/pi-json-output.ts +111 -111
  143. package/src/runtime/policy-engine.ts +79 -79
  144. package/src/runtime/progress-event-coalescer.ts +43 -43
  145. package/src/runtime/prose-compressor.ts +164 -0
  146. package/src/runtime/recovery-recipes.ts +74 -74
  147. package/src/runtime/result-extractor.ts +121 -0
  148. package/src/runtime/role-permission.ts +39 -39
  149. package/src/runtime/runtime-resolver.ts +1 -4
  150. package/src/runtime/semaphore.ts +131 -0
  151. package/src/runtime/sensitive-paths.ts +92 -0
  152. package/src/runtime/session-resources.ts +25 -25
  153. package/src/runtime/session-snapshot.ts +59 -59
  154. package/src/runtime/session-usage.ts +79 -79
  155. package/src/runtime/sidechain-output.ts +29 -29
  156. package/src/runtime/stream-preview.ts +177 -0
  157. package/src/runtime/subagent-manager.ts +3 -2
  158. package/src/runtime/subprocess-tool-registry.ts +67 -0
  159. package/src/runtime/supervisor-contact.ts +59 -59
  160. package/src/runtime/task-display.ts +38 -38
  161. package/src/runtime/task-output-context.ts +59 -9
  162. package/src/runtime/task-runner/capabilities.ts +78 -78
  163. package/src/runtime/task-runner/live-executor.ts +2 -0
  164. package/src/runtime/task-runner/progress.ts +119 -119
  165. package/src/runtime/task-runner/prompt-builder.ts +70 -8
  166. package/src/runtime/task-runner/prompt-pipeline.ts +64 -64
  167. package/src/runtime/task-runner/result-utils.ts +14 -14
  168. package/src/runtime/task-runner/run-projection.ts +104 -0
  169. package/src/runtime/task-runner/state-helpers.ts +22 -22
  170. package/src/runtime/task-runner.ts +75 -4
  171. package/src/runtime/team-runner.ts +60 -8
  172. package/src/runtime/worker-heartbeat.ts +21 -21
  173. package/src/runtime/worker-startup.ts +57 -57
  174. package/src/runtime/workspace-tree.ts +298 -0
  175. package/src/runtime/yield-handler.ts +189 -0
  176. package/src/schema/config-schema.ts +6 -0
  177. package/src/schema/team-tool-schema.ts +11 -1
  178. package/src/skills/discover-skills.ts +67 -0
  179. package/src/state/active-run-registry.ts +4 -2
  180. package/src/state/artifact-store.ts +4 -1
  181. package/src/state/atomic-write.ts +50 -1
  182. package/src/state/blob-store.ts +117 -0
  183. package/src/state/contracts.ts +1 -0
  184. package/src/state/event-log-rotation.ts +158 -0
  185. package/src/state/event-log.ts +52 -2
  186. package/src/state/mailbox.ts +87 -7
  187. package/src/state/state-store.ts +24 -4
  188. package/src/state/task-claims.ts +44 -44
  189. package/src/state/types.ts +20 -0
  190. package/src/state/usage.ts +29 -29
  191. package/src/subagents/async-entry.ts +1 -1
  192. package/src/subagents/index.ts +3 -3
  193. package/src/subagents/live/control.ts +1 -1
  194. package/src/subagents/live/manager.ts +1 -1
  195. package/src/subagents/live/realtime.ts +1 -1
  196. package/src/subagents/live/session-runtime.ts +1 -1
  197. package/src/subagents/manager.ts +1 -1
  198. package/src/subagents/spawn.ts +1 -1
  199. package/src/teams/team-serializer.ts +38 -38
  200. package/src/types/diff.d.ts +18 -18
  201. package/src/ui/agent-management-overlay.ts +144 -0
  202. package/src/ui/crew-footer.ts +101 -101
  203. package/src/ui/crew-select-list.ts +111 -111
  204. package/src/ui/crew-widget.ts +11 -2
  205. package/src/ui/dashboard-panes/cancellation-pane.ts +43 -0
  206. package/src/ui/dashboard-panes/capability-pane.ts +60 -0
  207. package/src/ui/dashboard-panes/mailbox-pane.ts +35 -11
  208. package/src/ui/dashboard-panes/metrics-pane.ts +34 -34
  209. package/src/ui/dynamic-border.ts +25 -25
  210. package/src/ui/layout-primitives.ts +106 -106
  211. package/src/ui/live-run-sidebar.ts +4 -0
  212. package/src/ui/loaders.ts +158 -158
  213. package/src/ui/powerbar-publisher.ts +77 -15
  214. package/src/ui/render-coalescer.ts +51 -0
  215. package/src/ui/render-diff.ts +119 -119
  216. package/src/ui/render-scheduler.ts +143 -143
  217. package/src/ui/run-dashboard.ts +4 -0
  218. package/src/ui/run-event-bus.ts +209 -0
  219. package/src/ui/run-snapshot-cache.ts +68 -16
  220. package/src/ui/snapshot-types.ts +8 -0
  221. package/src/ui/spinner.ts +17 -17
  222. package/src/ui/status-colors.ts +58 -58
  223. package/src/ui/syntax-highlight.ts +116 -116
  224. package/src/ui/transcript-entries.ts +258 -0
  225. package/src/utils/atomic-write.ts +33 -33
  226. package/src/utils/completion-dedupe.ts +63 -63
  227. package/src/utils/frontmatter.ts +68 -68
  228. package/src/utils/git.ts +262 -262
  229. package/src/utils/ids.ts +17 -12
  230. package/src/utils/incremental-reader.ts +104 -0
  231. package/src/utils/names.ts +27 -27
  232. package/src/utils/redaction.ts +44 -44
  233. package/src/utils/safe-paths.ts +47 -47
  234. package/src/utils/scan-cache.ts +137 -0
  235. package/src/utils/sleep.ts +32 -32
  236. package/src/utils/sse-parser.ts +134 -0
  237. package/src/utils/task-name-generator.ts +337 -0
  238. package/src/utils/visual.ts +33 -2
  239. package/src/workflows/validate-workflow.ts +40 -40
  240. package/src/worktree/branch-freshness.ts +45 -45
  241. package/src/worktree/cleanup.ts +2 -1
  242. package/teams/default.team.md +12 -12
  243. package/teams/fast-fix.team.md +11 -11
  244. package/teams/implementation.team.md +18 -18
  245. package/teams/parallel-research.team.md +14 -14
  246. package/teams/research.team.md +11 -11
  247. package/teams/review.team.md +12 -12
  248. package/workflows/default.workflow.md +29 -29
  249. package/workflows/fast-fix.workflow.md +22 -22
  250. package/workflows/implementation.workflow.md +38 -38
  251. package/workflows/parallel-research.workflow.md +46 -46
  252. package/workflows/research.workflow.md +22 -22
  253. package/workflows/review.workflow.md +30 -30
@@ -1,88 +1,88 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import type { TeamRunManifest } from "../state/types.ts";
4
- import { agentStateFile, ensureAgentStateDir } from "./crew-agent-records.ts";
5
-
6
- export type LiveAgentControlOperation = "steer" | "follow-up" | "stop" | "resume";
7
-
8
- export interface LiveAgentControlRequest {
9
- id: string;
10
- runId: string;
11
- taskId: string;
12
- agentId?: string;
13
- operation: LiveAgentControlOperation;
14
- message?: string;
15
- createdAt: string;
16
- processedAt?: string;
17
- error?: string;
18
- }
19
-
20
- export interface LiveAgentControlCursor {
21
- offset: number;
22
- }
23
-
24
- export function liveAgentControlPath(manifest: TeamRunManifest, taskId: string): string {
25
- return path.join(ensureAgentStateDir(manifest, taskId), "live-control.jsonl");
26
- }
27
-
28
- function liveAgentControlFile(manifest: TeamRunManifest, taskId: string): string {
29
- return agentStateFile(manifest, taskId, "live-control.jsonl");
30
- }
31
-
32
- function requestId(): string {
33
- return `ctrl_${Date.now().toString(36)}_${Math.random().toString(16).slice(2, 10)}`;
34
- }
35
-
36
- export function appendLiveAgentControlRequest(manifest: TeamRunManifest, input: { taskId: string; agentId?: string; operation: LiveAgentControlOperation; message?: string }): LiveAgentControlRequest {
37
- const request: LiveAgentControlRequest = {
38
- id: requestId(),
39
- runId: manifest.runId,
40
- taskId: input.taskId,
41
- agentId: input.agentId,
42
- operation: input.operation,
43
- message: input.message,
44
- createdAt: new Date().toISOString(),
45
- };
46
- const filePath = liveAgentControlFile(manifest, input.taskId);
47
- fs.appendFileSync(filePath, `${JSON.stringify(request)}\n`, "utf-8");
48
- return request;
49
- }
50
-
51
- export function readLiveAgentControlRequests(manifest: TeamRunManifest, taskId: string, cursor: LiveAgentControlCursor = { offset: 0 }): { requests: LiveAgentControlRequest[]; cursor: LiveAgentControlCursor } {
52
- let filePath: string;
53
- try {
54
- filePath = liveAgentControlFile(manifest, taskId);
55
- } catch {
56
- return { requests: [], cursor };
57
- }
58
- if (!fs.existsSync(filePath)) return { requests: [], cursor };
59
- const text = fs.readFileSync(filePath, "utf-8");
60
- const lines = text.split(/\r?\n/).filter(Boolean);
61
- const requests = lines.slice(cursor.offset).flatMap((line) => {
62
- try {
63
- const parsed = JSON.parse(line) as LiveAgentControlRequest;
64
- return parsed && parsed.runId === manifest.runId && parsed.taskId === taskId ? [parsed] : [];
65
- } catch {
66
- return [];
67
- }
68
- });
69
- return { requests, cursor: { offset: lines.length } };
70
- }
71
-
72
- export async function applyLiveAgentControlRequest(input: { request: LiveAgentControlRequest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; seenRequestIds?: Set<string> }): Promise<boolean> {
73
- const { request, taskId, agentId, session, seenRequestIds } = input;
74
- if (seenRequestIds?.has(request.id)) return false;
75
- if (request.agentId && request.agentId !== agentId && request.agentId !== taskId) return false;
76
- seenRequestIds?.add(request.id);
77
- if (request.operation === "steer") await session.steer?.(request.message ?? "Please report current status and wrap up if possible.");
78
- else if (request.operation === "follow-up") await session.prompt?.(request.message ?? "Please continue with the follow-up request.", { source: "api", expandPromptTemplates: false });
79
- else if (request.operation === "resume") await session.prompt?.(request.message ?? "Please resume and report final status.", { source: "api", expandPromptTemplates: false });
80
- else if (request.operation === "stop") await session.abort?.();
81
- return true;
82
- }
83
-
84
- export async function applyLiveAgentControlRequests(input: { manifest: TeamRunManifest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; cursor: LiveAgentControlCursor; seenRequestIds?: Set<string> }): Promise<LiveAgentControlCursor> {
85
- const batch = readLiveAgentControlRequests(input.manifest, input.taskId, input.cursor);
86
- for (const request of batch.requests) await applyLiveAgentControlRequest({ request, taskId: input.taskId, agentId: input.agentId, session: input.session, seenRequestIds: input.seenRequestIds });
87
- return batch.cursor;
88
- }
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import type { TeamRunManifest } from "../state/types.ts";
4
+ import { agentStateFile, ensureAgentStateDir } from "./crew-agent-records.ts";
5
+
6
+ export type LiveAgentControlOperation = "steer" | "follow-up" | "stop" | "resume";
7
+
8
+ export interface LiveAgentControlRequest {
9
+ id: string;
10
+ runId: string;
11
+ taskId: string;
12
+ agentId?: string;
13
+ operation: LiveAgentControlOperation;
14
+ message?: string;
15
+ createdAt: string;
16
+ processedAt?: string;
17
+ error?: string;
18
+ }
19
+
20
+ export interface LiveAgentControlCursor {
21
+ offset: number;
22
+ }
23
+
24
+ export function liveAgentControlPath(manifest: TeamRunManifest, taskId: string): string {
25
+ return path.join(ensureAgentStateDir(manifest, taskId), "live-control.jsonl");
26
+ }
27
+
28
+ function liveAgentControlFile(manifest: TeamRunManifest, taskId: string): string {
29
+ return agentStateFile(manifest, taskId, "live-control.jsonl");
30
+ }
31
+
32
+ function requestId(): string {
33
+ return `ctrl_${Date.now().toString(36)}_${Math.random().toString(16).slice(2, 10)}`;
34
+ }
35
+
36
+ export function appendLiveAgentControlRequest(manifest: TeamRunManifest, input: { taskId: string; agentId?: string; operation: LiveAgentControlOperation; message?: string }): LiveAgentControlRequest {
37
+ const request: LiveAgentControlRequest = {
38
+ id: requestId(),
39
+ runId: manifest.runId,
40
+ taskId: input.taskId,
41
+ agentId: input.agentId,
42
+ operation: input.operation,
43
+ message: input.message,
44
+ createdAt: new Date().toISOString(),
45
+ };
46
+ const filePath = liveAgentControlFile(manifest, input.taskId);
47
+ fs.appendFileSync(filePath, `${JSON.stringify(request)}\n`, "utf-8");
48
+ return request;
49
+ }
50
+
51
+ export function readLiveAgentControlRequests(manifest: TeamRunManifest, taskId: string, cursor: LiveAgentControlCursor = { offset: 0 }): { requests: LiveAgentControlRequest[]; cursor: LiveAgentControlCursor } {
52
+ let filePath: string;
53
+ try {
54
+ filePath = liveAgentControlFile(manifest, taskId);
55
+ } catch {
56
+ return { requests: [], cursor };
57
+ }
58
+ if (!fs.existsSync(filePath)) return { requests: [], cursor };
59
+ const text = fs.readFileSync(filePath, "utf-8");
60
+ const lines = text.split(/\r?\n/).filter(Boolean);
61
+ const requests = lines.slice(cursor.offset).flatMap((line) => {
62
+ try {
63
+ const parsed = JSON.parse(line) as LiveAgentControlRequest;
64
+ return parsed && parsed.runId === manifest.runId && parsed.taskId === taskId ? [parsed] : [];
65
+ } catch {
66
+ return [];
67
+ }
68
+ });
69
+ return { requests, cursor: { offset: lines.length } };
70
+ }
71
+
72
+ export async function applyLiveAgentControlRequest(input: { request: LiveAgentControlRequest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; seenRequestIds?: Set<string> }): Promise<boolean> {
73
+ const { request, taskId, agentId, session, seenRequestIds } = input;
74
+ if (seenRequestIds?.has(request.id)) return false;
75
+ if (request.agentId && request.agentId !== agentId && request.agentId !== taskId) return false;
76
+ seenRequestIds?.add(request.id);
77
+ if (request.operation === "steer") await session.steer?.(request.message ?? "Please report current status and wrap up if possible.");
78
+ else if (request.operation === "follow-up") await session.prompt?.(request.message ?? "Please continue with the follow-up request.", { source: "api", expandPromptTemplates: false });
79
+ else if (request.operation === "resume") await session.prompt?.(request.message ?? "Please resume and report final status.", { source: "api", expandPromptTemplates: false });
80
+ else if (request.operation === "stop") await session.abort?.();
81
+ return true;
82
+ }
83
+
84
+ export async function applyLiveAgentControlRequests(input: { manifest: TeamRunManifest; taskId: string; agentId: string; session: { steer?: (text: string) => Promise<void>; prompt?: (text: string, options?: Record<string, unknown>) => Promise<void>; abort?: () => Promise<void> | void }; cursor: LiveAgentControlCursor; seenRequestIds?: Set<string> }): Promise<LiveAgentControlCursor> {
85
+ const batch = readLiveAgentControlRequests(input.manifest, input.taskId, input.cursor);
86
+ for (const request of batch.requests) await applyLiveAgentControlRequest({ request, taskId: input.taskId, agentId: input.agentId, session: input.session, seenRequestIds: input.seenRequestIds });
87
+ return batch.cursor;
88
+ }
@@ -1,4 +1,5 @@
1
1
  import type { CrewAgentRecord } from "./crew-agent-runtime.ts";
2
+ import type { IrcMessage } from "./live-irc.ts";
2
3
 
3
4
  type LiveSessionHandle = {
4
5
  steer?: (text: string) => Promise<void>;
@@ -16,14 +17,16 @@ export interface LiveAgentHandle {
16
17
  status: CrewAgentRecord["status"];
17
18
  pendingSteers: string[];
18
19
  pendingFollowUps: string[];
20
+ /** Phase 7: Pending IRC messages for this agent. */
21
+ pendingMessages: IrcMessage[];
19
22
  }
20
23
 
21
24
  const liveAgents = new Map<string, LiveAgentHandle>();
22
25
 
23
- export function registerLiveAgent(input: Omit<LiveAgentHandle, "createdAt" | "updatedAt" | "pendingSteers" | "pendingFollowUps">): LiveAgentHandle {
26
+ export function registerLiveAgent(input: Omit<LiveAgentHandle, "createdAt" | "updatedAt" | "pendingSteers" | "pendingFollowUps" | "pendingMessages">): LiveAgentHandle {
24
27
  const now = new Date().toISOString();
25
28
  const existing = liveAgents.get(input.agentId);
26
- const handle: LiveAgentHandle = { ...input, createdAt: existing?.createdAt ?? now, updatedAt: now, pendingSteers: existing?.pendingSteers ?? [], pendingFollowUps: existing?.pendingFollowUps ?? [] };
29
+ const handle: LiveAgentHandle = { ...input, createdAt: existing?.createdAt ?? now, updatedAt: now, pendingSteers: existing?.pendingSteers ?? [], pendingFollowUps: existing?.pendingFollowUps ?? [], pendingMessages: existing?.pendingMessages ?? [] };
27
30
  liveAgents.set(input.agentId, handle);
28
31
  if (handle.pendingSteers.length && typeof handle.session.steer === "function") {
29
32
  const pending = [...handle.pendingSteers];
@@ -101,3 +104,76 @@ export async function resumeLiveAgent(agentIdOrTaskId: string, prompt: string):
101
104
  export function clearLiveAgentsForTest(): void {
102
105
  liveAgents.clear();
103
106
  }
107
+
108
+ /** Phase 7/G4: Send an IRC message to a specific live agent (DM).
109
+ * Uses non-blocking delivery via sendCustomMessage when available.
110
+ * Falls back to session.prompt (blocking) when not.
111
+ */
112
+ export function sendIrcMessage(targetAgentId: string, message: IrcMessage): void {
113
+ const handle = getLiveAgent(targetAgentId);
114
+ if (!handle) return;
115
+ handle.pendingMessages.push(message);
116
+ handle.updatedAt = new Date().toISOString();
117
+ // G4: Try non-blocking delivery via sendCustomMessage
118
+ const session = handle.session as Record<string, unknown>;
119
+ if (typeof session.sendCustomMessage === "function") {
120
+ try {
121
+ (session.sendCustomMessage as (msg: unknown, opts?: unknown) => void)(
122
+ { customType: "irc", content: `[DM from ${message.from}] ${message.content}`, display: "collapsed" },
123
+ { deliverAs: "followUp", triggerTurn: false },
124
+ );
125
+ return;
126
+ } catch {
127
+ // Fall through to prompt-based delivery
128
+ }
129
+ }
130
+ // Fallback: inject as prompt (blocking)
131
+ if (typeof handle.session.prompt === "function") {
132
+ const ircPrompt = `[Message from ${message.from}] ${message.content}`;
133
+ void handle.session.prompt(ircPrompt, { source: "api", expandPromptTemplates: false }).catch(() => {});
134
+ }
135
+ }
136
+
137
+ /** Phase 7/G4: Broadcast an IRC message to all live agents except the sender.
138
+ * Uses non-blocking delivery via sendCustomMessage when available.
139
+ * Returns recipient IDs.
140
+ */
141
+ export function broadcastIrcMessage(fromAgentId: string, message: IrcMessage): string[] {
142
+ const recipients: string[] = [];
143
+ for (const handle of liveAgents.values()) {
144
+ if (handle.agentId === fromAgentId) continue;
145
+ if (handle.status !== "running" && handle.status !== "queued") continue;
146
+ handle.pendingMessages.push(message);
147
+ handle.updatedAt = new Date().toISOString();
148
+ // G4: Try non-blocking delivery
149
+ const session = handle.session as Record<string, unknown>;
150
+ if (typeof session.sendCustomMessage === "function") {
151
+ try {
152
+ (session.sendCustomMessage as (msg: unknown, opts?: unknown) => void)(
153
+ { customType: "irc", content: `[Broadcast from ${message.from}] ${message.content}`, display: "collapsed" },
154
+ { deliverAs: "followUp", triggerTurn: false },
155
+ );
156
+ recipients.push(handle.agentId);
157
+ continue;
158
+ } catch {
159
+ // Fall through to prompt-based delivery
160
+ }
161
+ }
162
+ // Fallback: inject as prompt
163
+ if (typeof handle.session.prompt === "function") {
164
+ const ircPrompt = `[Broadcast from ${message.from}] ${message.content}`;
165
+ void handle.session.prompt(ircPrompt, { source: "api", expandPromptTemplates: false }).catch(() => {});
166
+ }
167
+ recipients.push(handle.agentId);
168
+ }
169
+ return recipients;
170
+ }
171
+
172
+ /** Phase 7: Get pending IRC messages for an agent (and clear them). */
173
+ export function drainIrcMessages(agentIdOrTaskId: string): IrcMessage[] {
174
+ const handle = getLiveAgent(agentIdOrTaskId);
175
+ if (!handle) return [];
176
+ const messages = [...handle.pendingMessages];
177
+ handle.pendingMessages.length = 0;
178
+ return messages;
179
+ }
@@ -1,36 +1,36 @@
1
- import type { LiveAgentControlRequest } from "./live-agent-control.ts";
2
-
3
- export interface LiveControlRealtimeMessage {
4
- type: "live-control";
5
- version: 1;
6
- request: LiveAgentControlRequest;
7
- }
8
-
9
- type Listener = (request: LiveAgentControlRequest) => void | Promise<void>;
10
-
11
- const listeners = new Set<Listener>();
12
-
13
- export function publishLiveControlRealtime(request: LiveAgentControlRequest): void {
14
- for (const listener of [...listeners]) void listener(request);
15
- }
16
-
17
- export function subscribeLiveControlRealtime(listener: Listener): () => void {
18
- listeners.add(listener);
19
- return () => listeners.delete(listener);
20
- }
21
-
22
- export function liveControlRealtimeMessage(request: LiveAgentControlRequest): LiveControlRealtimeMessage {
23
- return { type: "live-control", version: 1, request };
24
- }
25
-
26
- export function parseLiveControlRealtimeMessage(raw: unknown): LiveAgentControlRequest | undefined {
27
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
28
- const message = raw as { type?: unknown; version?: unknown; request?: unknown };
29
- if (message.type !== "live-control" || message.version !== 1 || !message.request || typeof message.request !== "object" || Array.isArray(message.request)) return undefined;
30
- const request = message.request as Partial<LiveAgentControlRequest>;
31
- return typeof request.id === "string" && typeof request.runId === "string" && typeof request.taskId === "string" && (request.operation === "steer" || request.operation === "follow-up" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
32
- }
33
-
34
- export function clearLiveControlRealtimeForTest(): void {
35
- listeners.clear();
36
- }
1
+ import type { LiveAgentControlRequest } from "./live-agent-control.ts";
2
+
3
+ export interface LiveControlRealtimeMessage {
4
+ type: "live-control";
5
+ version: 1;
6
+ request: LiveAgentControlRequest;
7
+ }
8
+
9
+ type Listener = (request: LiveAgentControlRequest) => void | Promise<void>;
10
+
11
+ const listeners = new Set<Listener>();
12
+
13
+ export function publishLiveControlRealtime(request: LiveAgentControlRequest): void {
14
+ for (const listener of [...listeners]) void listener(request);
15
+ }
16
+
17
+ export function subscribeLiveControlRealtime(listener: Listener): () => void {
18
+ listeners.add(listener);
19
+ return () => listeners.delete(listener);
20
+ }
21
+
22
+ export function liveControlRealtimeMessage(request: LiveAgentControlRequest): LiveControlRealtimeMessage {
23
+ return { type: "live-control", version: 1, request };
24
+ }
25
+
26
+ export function parseLiveControlRealtimeMessage(raw: unknown): LiveAgentControlRequest | undefined {
27
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
28
+ const message = raw as { type?: unknown; version?: unknown; request?: unknown };
29
+ if (message.type !== "live-control" || message.version !== 1 || !message.request || typeof message.request !== "object" || Array.isArray(message.request)) return undefined;
30
+ const request = message.request as Partial<LiveAgentControlRequest>;
31
+ return typeof request.id === "string" && typeof request.runId === "string" && typeof request.taskId === "string" && (request.operation === "steer" || request.operation === "follow-up" || request.operation === "stop" || request.operation === "resume") && typeof request.createdAt === "string" ? request as LiveAgentControlRequest : undefined;
32
+ }
33
+
34
+ export function clearLiveControlRealtimeForTest(): void {
35
+ listeners.clear();
36
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * G5: Extension runner bridge for live-session workers.
3
+ *
4
+ * Bridges pi-crew's extension lifecycle with the Pi SDK session's
5
+ * extension runner. Verified against actual SDK API surface:
6
+ *
7
+ * Session methods:
8
+ * - sendCustomMessage(message, options?)
9
+ * - sendUserMessage(content, options?)
10
+ * - getActiveToolNames() / setActiveToolsByName()
11
+ * - getAllTools() / getToolDefinition()
12
+ * - steer(text) / prompt(text, options?)
13
+ * - abort()
14
+ * - getContextUsage()
15
+ * - bindExtensions()
16
+ * - compact()
17
+ * - getSessionStats()
18
+ *
19
+ * ExtensionRunner methods:
20
+ * - initialize(apis, host)
21
+ * - emit(event)
22
+ * - hasHandlers(eventType)
23
+ * - getAllRegisteredTools()
24
+ * - onError(listener)
25
+ * - shutdown()
26
+ *
27
+ * ExtensionContext actions (via registerTool):
28
+ * - sendMessage / sendUserMessage / appendEntry / setLabel
29
+ * - setActiveTools / getActiveTools / getAllTools
30
+ */
31
+
32
+ import type { YieldResult } from "./yield-handler.ts";
33
+
34
+ export interface ExtensionBridgeApis {
35
+ sendMessage: (message: unknown, options?: Record<string, unknown>) => void;
36
+ sendUserMessage: (content: unknown, options?: Record<string, unknown>) => void;
37
+ appendEntry: (customType: string, data: unknown) => void;
38
+ setLabel: (targetId: string, label: string) => void;
39
+ getActiveTools: () => string[];
40
+ getAllTools: () => string[];
41
+ setActiveTools: (toolNames: string[]) => void;
42
+ }
43
+
44
+ export interface ExtensionHostApis {
45
+ getModel: () => unknown;
46
+ isIdle: () => boolean;
47
+ abort: () => void;
48
+ hasPendingMessages: () => boolean;
49
+ shutdown: () => void;
50
+ getContextUsage: () => unknown;
51
+ getSystemPrompt: () => string;
52
+ }
53
+
54
+ /**
55
+ * Pi SDK session-like object with the methods we need.
56
+ * Verified against actual `createAgentSession().session` prototype.
57
+ */
58
+ interface PiSdkSession {
59
+ sendCustomMessage: (message: unknown, options?: Record<string, unknown>) => void;
60
+ sendUserMessage: (content: unknown, options?: Record<string, unknown>) => void;
61
+ getActiveToolNames: () => string[];
62
+ getAllTools: () => string[];
63
+ setActiveToolsByName: (toolNames: string[]) => void;
64
+ steer: (text: string) => Promise<void>;
65
+ prompt: (text: string, options?: Record<string, unknown>) => Promise<void>;
66
+ abort: () => void | Promise<void>;
67
+ getContextUsage: () => unknown;
68
+ subscribe: (listener: (event: unknown) => void) => () => void;
69
+ bindExtensions: (bindings?: Record<string, unknown>) => Promise<void>;
70
+ compact: (options?: unknown) => void;
71
+ getSessionStats: () => unknown;
72
+ isStreaming?: boolean;
73
+ model?: unknown;
74
+ systemPrompt?: string;
75
+ pendingMessageCount?: number;
76
+ }
77
+
78
+ /**
79
+ * Build extension bridge APIs from a Pi SDK session.
80
+ * Returns null if the session doesn't support extension running.
81
+ */
82
+ export function buildExtensionBridge(session: PiSdkSession): { apis: ExtensionBridgeApis; host: ExtensionHostApis } | null {
83
+ if (typeof session.sendCustomMessage !== "function") return null;
84
+
85
+ return {
86
+ apis: {
87
+ sendMessage: (message, options) => {
88
+ try {
89
+ session.sendCustomMessage(message, options);
90
+ } catch {
91
+ /* non-blocking */
92
+ }
93
+ },
94
+ sendUserMessage: (content, options) => {
95
+ try {
96
+ session.sendUserMessage(content, options);
97
+ } catch {
98
+ /* non-blocking */
99
+ }
100
+ },
101
+ appendEntry: () => {
102
+ // appendEntry requires sessionManager access which isn't directly on session
103
+ // This is a no-op placeholder; extensions that rely on it will gracefully degrade
104
+ },
105
+ setLabel: () => {
106
+ // setLabel requires sessionManager access — no-op placeholder
107
+ },
108
+ getActiveTools: () => {
109
+ try {
110
+ return session.getActiveToolNames();
111
+ } catch {
112
+ return [];
113
+ }
114
+ },
115
+ getAllTools: () => {
116
+ try {
117
+ return session.getAllTools();
118
+ } catch {
119
+ return session.getActiveToolNames();
120
+ }
121
+ },
122
+ setActiveTools: (toolNames) => {
123
+ try {
124
+ session.setActiveToolsByName(toolNames);
125
+ } catch {
126
+ /* ignore */
127
+ }
128
+ },
129
+ },
130
+ host: {
131
+ getModel: () => session.model,
132
+ isIdle: () => !session.isStreaming,
133
+ abort: () => {
134
+ void session.abort();
135
+ },
136
+ hasPendingMessages: () => (session.pendingMessageCount ?? 0) > 0,
137
+ shutdown: () => {
138
+ /* no-op for live-session — caller manages session lifecycle */
139
+ },
140
+ getContextUsage: () => {
141
+ try {
142
+ return session.getContextUsage();
143
+ } catch {
144
+ return undefined;
145
+ }
146
+ },
147
+ getSystemPrompt: () => session.systemPrompt ?? "",
148
+ },
149
+ };
150
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Phase 7: Inter-agent communication for live-session workers.
3
+ *
4
+ * Provides IRC-like messaging between live-session workers, adapted from
5
+ * oh-my-pi's IrcTool pattern. Uses the existing LiveAgentHandle manager
6
+ * for message routing.
7
+ *
8
+ * Features:
9
+ * - DM: Send a message to a specific agent
10
+ * - Broadcast: Send a message to all live agents
11
+ * - Side-channel: Non-blocking message injection (via pendingFollowUps)
12
+ *
13
+ * For child-process workers, messages fall back to file-based mailbox.
14
+ */
15
+
16
+ export type IrcOperation = "send" | "list";
17
+
18
+ export interface IrcMessage {
19
+ from: string;
20
+ to: string;
21
+ content: string;
22
+ timestamp: string;
23
+ /** Whether the sender expects a reply. */
24
+ awaitReply?: boolean;
25
+ }
26
+
27
+ export interface IrcSendMessage {
28
+ op: IrcOperation;
29
+ /** Target agent ID or "all" for broadcast. */
30
+ to: string;
31
+ /** Message content. */
32
+ message: string;
33
+ /** Whether to wait for a reply (default: true for DM, false for broadcast). */
34
+ awaitReply?: boolean;
35
+ }
36
+
37
+ export interface IrcListResult {
38
+ peers: Array<{ id: string; name: string; status: string }>;
39
+ }
40
+
41
+ /**
42
+ * Build IRC peer roster for injection into system prompt.
43
+ * Lists all currently live agents except the caller.
44
+ */
45
+ export function renderIrcPeerRoster(selfId: string, peers: Array<{ agentId: string; status: string }>): string {
46
+ const visible = peers.filter((p) => p.agentId !== selfId && (p.status === "running" || p.status === "idle"));
47
+ if (visible.length === 0) return "- (no other live agents)";
48
+ return visible.map((peer) => `- \`${peer.agentId}\` (${peer.status})`).join("\n");
49
+ }
50
+
51
+ /**
52
+ * Build the IRC system prompt section for a live-session worker.
53
+ */
54
+ export function buildIrcSystemSection(selfId: string, peers: Array<{ agentId: string; status: string }>): string {
55
+ const roster = renderIrcPeerRoster(selfId, peers);
56
+ return [
57
+ "## Inter-Agent Communication",
58
+ `Your agent ID: \`${selfId}\``,
59
+ "You can send messages to other live agents via the `irc` tool.",
60
+ "Available peers:",
61
+ roster,
62
+ ].join("\n");
63
+ }
64
+
65
+ /**
66
+ * Route an IRC message to the appropriate agent(s).
67
+ * Returns the list of agent IDs that received the message.
68
+ */
69
+ export function routeIrcMessage(
70
+ message: IrcSendMessage,
71
+ selfId: string,
72
+ routing: {
73
+ sendDm: (agentId: string, content: string) => void;
74
+ broadcast: (content: string, excludeId: string) => string[];
75
+ },
76
+ ): { deliveredTo: string[]; error?: string } {
77
+ if (!message.to || !message.message?.trim()) {
78
+ return { deliveredTo: [], error: "Missing 'to' (agent ID or 'all') and 'message' fields." };
79
+ }
80
+ if (message.to === selfId) {
81
+ return { deliveredTo: [], error: "Cannot send a message to yourself." };
82
+ }
83
+
84
+ if (message.to === "all") {
85
+ const recipients = routing.broadcast(message.message, selfId);
86
+ return { deliveredTo: recipients };
87
+ }
88
+
89
+ // DM to specific agent
90
+ routing.sendDm(message.to, message.message);
91
+ return { deliveredTo: [message.to] };
92
+ }