crewswarm 0.9.2 → 0.9.4

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 (228) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  3. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  5. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  6. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  8. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  9. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  10. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  11. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  12. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  14. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  18. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  22. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  23. package/apps/dashboard/dist/index.html +135 -15
  24. package/apps/dashboard/dist/index.html.br +0 -0
  25. package/apps/dashboard/dist/index.html.gz +0 -0
  26. package/apps/vibe/README.md +2 -2
  27. package/apps/vibe/package.json +1 -1
  28. package/apps/vibe/server.mjs +101 -56
  29. package/crew-lead.mjs +34 -4
  30. package/lib/bridges/cli-executor.mjs +1 -1
  31. package/lib/bridges/gateway-ws.mjs +4 -0
  32. package/lib/browser/passthrough-stderr.js +1 -0
  33. package/lib/chat/project-messages.mjs +3 -5
  34. package/lib/cli-process-tracker.mjs +3 -2
  35. package/lib/contacts/identity-linker.mjs +1 -0
  36. package/lib/crew-judge/judge.mjs +19 -18
  37. package/lib/crew-lead/agent-manager.mjs +1 -1
  38. package/lib/crew-lead/background.mjs +14 -1
  39. package/lib/crew-lead/chat-handler.mjs +38 -1
  40. package/lib/crew-lead/http-server.mjs +106 -57
  41. package/lib/crew-lead/llm-caller.mjs +24 -8
  42. package/lib/crew-lead/prompts.mjs +14 -1
  43. package/lib/crew-lead/tools.mjs +3 -2
  44. package/lib/crew-lead/wave-dispatcher.mjs +19 -5
  45. package/lib/crew-lead/ws-router.mjs +219 -27
  46. package/lib/engines/crew-cli.mjs +1 -1
  47. package/lib/engines/engine-registry.mjs +14 -3
  48. package/lib/engines/rt-envelope.mjs +1 -0
  49. package/lib/engines/runners.mjs +28 -4
  50. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  51. package/lib/integrations/code-search.mjs +4 -3
  52. package/lib/memory/shared-adapter.mjs +23 -10
  53. package/lib/pipeline/manager.mjs +2 -1
  54. package/lib/runtime/config.mjs +1 -1
  55. package/lib/runtime/paths.mjs +12 -8
  56. package/lib/runtime/spending.mjs +2 -1
  57. package/package.json +42 -14
  58. package/scripts/capture-build-flow.mjs +118 -0
  59. package/scripts/coverage-report.mjs +209 -0
  60. package/scripts/coverage-summary.mjs +47 -0
  61. package/scripts/dashboard-validation.mjs +76 -0
  62. package/scripts/dashboard.mjs +1667 -551
  63. package/scripts/generate-openapi.mjs +683 -277
  64. package/scripts/live-bridge-matrix.mjs +79 -0
  65. package/scripts/live-cli-matrix.mjs +166 -0
  66. package/scripts/live-crewchat-check.mjs +42 -0
  67. package/scripts/live-engine-matrix.mjs +50 -0
  68. package/scripts/live-provider-failover-matrix.mjs +107 -0
  69. package/scripts/live-provider-matrix.mjs +228 -0
  70. package/scripts/restart-all-from-repo.sh +4 -4
  71. package/scripts/restart-service.sh +12 -9
  72. package/scripts/smoke-dispatch.mjs +4 -1
  73. package/scripts/test-blast-radius.mjs +204 -0
  74. package/scripts/test-report-summary.mjs +88 -0
  75. package/scripts/test-reporter.mjs +651 -0
  76. package/scripts/test-rerun.mjs +136 -0
  77. package/scripts/tmux-bridge +130 -0
  78. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
  79. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  80. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  81. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  82. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  83. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  84. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  85. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  86. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  87. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  88. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  89. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  90. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  91. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
  92. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  93. package/apps/dashboard/index.html +0 -6529
  94. package/apps/dashboard/package.json +0 -15
  95. package/apps/dashboard/src/app.js +0 -2828
  96. package/apps/dashboard/src/app.js.br +0 -0
  97. package/apps/dashboard/src/app.js.gz +0 -0
  98. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  99. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  100. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  101. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  102. package/apps/dashboard/src/cli-process.js +0 -208
  103. package/apps/dashboard/src/cli-process.js.br +0 -0
  104. package/apps/dashboard/src/cli-process.js.gz +0 -0
  105. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  106. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  107. package/apps/dashboard/src/core/api.js +0 -18
  108. package/apps/dashboard/src/core/api.js.br +0 -0
  109. package/apps/dashboard/src/core/dom.js +0 -228
  110. package/apps/dashboard/src/core/dom.js.br +0 -0
  111. package/apps/dashboard/src/core/state.js +0 -91
  112. package/apps/dashboard/src/core/state.js.br +0 -0
  113. package/apps/dashboard/src/core/task-manager.js +0 -134
  114. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  115. package/apps/dashboard/src/orchestration-status.js +0 -127
  116. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  117. package/apps/dashboard/src/setup-wizard.js +0 -562
  118. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  119. package/apps/dashboard/src/styles.css +0 -2085
  120. package/apps/dashboard/src/styles.css.br +0 -0
  121. package/apps/dashboard/src/styles.css.gz +0 -0
  122. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  123. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  125. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  127. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  129. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  131. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  133. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  135. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  137. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  139. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  140. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  141. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  142. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  143. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  144. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  145. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  146. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  147. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  148. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  149. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  150. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  151. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  152. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  153. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  154. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  155. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  156. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  157. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  158. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  159. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  160. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  161. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  162. package/apps/vibe/.crew/cost.json +0 -17
  163. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  164. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  172. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  173. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  174. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  175. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  176. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  177. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  178. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  179. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  180. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  181. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  182. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  183. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  184. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  185. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  186. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  187. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  188. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  189. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  190. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  191. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  192. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  193. package/apps/vibe/.crew/sandbox.json +0 -7
  194. package/apps/vibe/.crew/session.json +0 -330
  195. package/apps/vibe/.crew/training-data.jsonl +0 -0
  196. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  197. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  198. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  199. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  200. package/apps/vibe/ARCHITECTURE.md +0 -3393
  201. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  202. package/apps/vibe/ROADMAP.md +0 -41
  203. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  204. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  205. package/apps/vibe/capture-demo.mjs +0 -160
  206. package/apps/vibe/capture-full-demo.mjs +0 -255
  207. package/apps/vibe/capture-quickstart.mjs +0 -256
  208. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  209. package/apps/vibe/capture-vibe-video.mjs +0 -260
  210. package/apps/vibe/check-buttons.js +0 -41
  211. package/apps/vibe/diagnose.html +0 -106
  212. package/apps/vibe/fix-buttons.js +0 -103
  213. package/apps/vibe/index.html +0 -3404
  214. package/apps/vibe/package-lock.json +0 -920
  215. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  216. package/apps/vibe/src/main.js +0 -2940
  217. package/apps/vibe/src/register-all-languages.js +0 -98
  218. package/apps/vibe/start-studio.sh +0 -11
  219. package/apps/vibe/test/accessibility-tests.js +0 -77
  220. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  221. package/apps/vibe/test/performance-tests.js +0 -120
  222. package/apps/vibe/test/security-tests.js +0 -213
  223. package/apps/vibe/tests/e2e.local.mjs +0 -54
  224. package/apps/vibe/tests/server.smoke.mjs +0 -106
  225. package/apps/vibe/update_website.mjs +0 -74
  226. package/apps/vibe/vite.config.js +0 -19
  227. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  228. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -1,870 +0,0 @@
1
- /**
2
- * Real-time envelope handler — processes incoming RT bus commands and tasks.
3
- * All dependencies are injected via initRtEnvelope(deps).
4
- */
5
- import crypto from "node:crypto";
6
- import path from "node:path";
7
- import os from "node:os";
8
- import fs from "node:fs";
9
- import { recordTaskMemory, isSharedMemoryAvailable } from "../memory/shared-adapter.mjs";
10
- import { selectEngine } from "./runners.mjs";
11
- import { saveProjectMessage } from "../chat/project-messages.mjs";
12
-
13
- let _deps = {};
14
-
15
- export function initRtEnvelope(deps) {
16
- _deps = deps;
17
- }
18
-
19
- export async function handleRealtimeEnvelope(envelope, client, bridge) {
20
- const payload = envelope?.payload && typeof envelope.payload === "object" ? envelope.payload : {};
21
- const taskId = envelope?.taskId || "";
22
- const incomingType = envelope?.type || "event";
23
- const from = envelope?.from || "unknown";
24
- const to = envelope?.to || "broadcast";
25
- const correlationId = envelope?.id || undefined;
26
-
27
- const {
28
- CREWSWARM_RT_AGENT,
29
- CREWSWARM_RT_COMMAND_TYPES,
30
- pendingCmdApprovals,
31
- resolveSpawnTargets,
32
- spawnAgentDaemon,
33
- isAgentDaemonRunning,
34
- readPid,
35
- dispatchKeyForTask,
36
- shouldUseDispatchGuard,
37
- acquireTaskLease,
38
- renewTaskLease,
39
- releaseTaskLease,
40
- markTaskDone,
41
- telemetry,
42
- buildTaskPrompt,
43
- getOpencodeProjectDir,
44
- assertTaskPromptProtocol,
45
- selectEngine,
46
- runGenericEngineTask,
47
- loadGenericEngines,
48
- progress,
49
- getAgentOpenCodeConfig,
50
- buildMiniTaskForOpenCode,
51
- runOuroborosStyleLoop,
52
- runCursorCliTask,
53
- runClaudeCodeTask,
54
- runCodexTask,
55
- runDockerSandboxTask,
56
- runGeminiCliTask,
57
- runCrewCLITask,
58
- runOpenCodeTask,
59
- callLLMDirect,
60
- extractProjectDirFromTask,
61
- loadAgentPrompts,
62
- stripThink,
63
- executeToolCalls,
64
- validateCodingArtifacts,
65
- isCodingTask,
66
- shouldRetryTaskFailure,
67
- CREWSWARM_RT_DISPATCH_LEASE_MS,
68
- CREWSWARM_RT_DISPATCH_HEARTBEAT_MS,
69
- CREWSWARM_RT_DISPATCH_MAX_RETRIES,
70
- CREWSWARM_RT_DISPATCH_MAX_RETRIES_CODING,
71
- CREWSWARM_RT_DISPATCH_RETRY_BACKOFF_MS,
72
- CREWSWARM_OPENCODE_AGENT,
73
- CREWSWARM_OPENCODE_MODEL,
74
- OPENCODE_FREE_MODEL_CHAIN,
75
- RT_TO_GATEWAY_AGENT_MAP,
76
- SHARED_MEMORY_DIR,
77
- SWARM_DLQ_DIR,
78
- COORDINATOR_AGENT_IDS,
79
- } = _deps;
80
-
81
- // Per-agent routing: skip tasks not addressed to us (unless broadcast)
82
- if (to !== "broadcast" && to !== CREWSWARM_RT_AGENT) {
83
- client.ack({ messageId: envelope.id, status: "skipped", note: `not for us (to=${to}, we=${CREWSWARM_RT_AGENT})` });
84
- return;
85
- }
86
-
87
- if (!CREWSWARM_RT_COMMAND_TYPES.has(incomingType)) {
88
- client.ack({ messageId: envelope.id, status: "skipped", note: `unsupported type ${incomingType}` });
89
- return;
90
- }
91
-
92
- // ── cmd approval resolution (from crew-lead via RT bus) ───────────────────
93
- if (incomingType === "cmd.approved" || incomingType === "cmd.rejected") {
94
- const approvalId = payload?.approvalId;
95
- if (approvalId && pendingCmdApprovals.has(approvalId)) {
96
- const pending = pendingCmdApprovals.get(approvalId);
97
- clearTimeout(pending.timer);
98
- pendingCmdApprovals.delete(approvalId);
99
- pending.resolve(incomingType === "cmd.approved");
100
- console.log(`[${CREWSWARM_RT_AGENT}] cmd ${incomingType === "cmd.approved" ? "✅ approved" : "⛔ rejected"}: ${approvalId}`);
101
- }
102
- try { client.ack({ messageId: envelope.id, status: "done", note: `cmd ${incomingType}` }); } catch {}
103
- return;
104
- }
105
-
106
- const action = String(payload.action || payload.command || "run_task").trim().toLowerCase();
107
- if (incomingType === "command.spawn_agent") {
108
- const targets = resolveSpawnTargets(payload);
109
- const results = targets.map((agent) => spawnAgentDaemon(agent));
110
- client.publish({
111
- channel: "done",
112
- type: "task.done",
113
- to: from,
114
- taskId,
115
- correlationId,
116
- priority: "high",
117
- payload: {
118
- source: CREWSWARM_RT_AGENT,
119
- incomingType,
120
- action: "spawn_agent",
121
- results,
122
- },
123
- });
124
- client.ack({ messageId: envelope.id, status: "done", note: `spawned ${results.length} agent(s)` });
125
- return;
126
- }
127
-
128
- if (incomingType === "command.collect_status") {
129
- const targets = resolveSpawnTargets(payload);
130
- const status = targets.map((agent) => ({ agent, running: isAgentDaemonRunning(agent), pid: readPid(agent) || null }));
131
- client.publish({
132
- channel: "done",
133
- type: "task.done",
134
- to: from,
135
- taskId,
136
- correlationId,
137
- priority: "medium",
138
- payload: {
139
- source: CREWSWARM_RT_AGENT,
140
- incomingType,
141
- action: "collect_status",
142
- status,
143
- },
144
- });
145
- client.ack({ messageId: envelope.id, status: "done", note: `status for ${status.length} agent(s)` });
146
- return;
147
- }
148
-
149
- if (incomingType.startsWith("command.") && action !== "run_task" && action !== "collect_status") {
150
- client.publish({
151
- channel: "issues",
152
- type: "command.unsupported",
153
- to: from,
154
- taskId,
155
- correlationId,
156
- priority: "medium",
157
- payload: {
158
- source: CREWSWARM_RT_AGENT,
159
- action,
160
- note: "Legacy bridge supports run_task and collect_status command actions",
161
- },
162
- });
163
- client.ack({ messageId: envelope.id, status: "failed", note: `unsupported action ${action}` });
164
- return;
165
- }
166
-
167
- const prompt = payload.prompt || payload.message || payload.description || [payload.title, payload.description].filter(Boolean).join("\n\n");
168
- if (!prompt || typeof prompt !== "string") {
169
- client.ack({ messageId: envelope.id, status: "failed", note: "missing prompt/message" });
170
- return;
171
- }
172
-
173
- const dispatchAttempt = Number(payload?._dispatchAttempt || 0);
174
- const dispatchKey = dispatchKeyForTask({
175
- taskId,
176
- incomingType,
177
- prompt,
178
- idempotencyKey: payload?._dispatchIdempotencyKey || payload?.idempotencyKey,
179
- });
180
- const dispatchGuardEnabled = shouldUseDispatchGuard(incomingType);
181
- let dispatchClaim = null;
182
- let dispatchHeartbeat = null;
183
-
184
- if (dispatchGuardEnabled) {
185
- try {
186
- dispatchClaim = acquireTaskLease({
187
- key: dispatchKey,
188
- source: incomingType,
189
- incomingType,
190
- from,
191
- leaseMs: CREWSWARM_RT_DISPATCH_LEASE_MS,
192
- });
193
- } catch (err) {
194
- telemetry("dispatch_claim_error", {
195
- key: dispatchKey,
196
- taskId,
197
- incomingType,
198
- error: err?.message ?? String(err),
199
- });
200
- client.ack({ messageId: envelope.id, status: "failed", note: "dispatch claim error" });
201
- return;
202
- }
203
-
204
- if (!dispatchClaim?.acquired) {
205
- const reason = dispatchClaim?.reason || "claimed";
206
- telemetry("dispatch_claim_skipped", {
207
- key: dispatchKey,
208
- taskId,
209
- incomingType,
210
- reason,
211
- claimedBy: dispatchClaim?.claimedBy || null,
212
- });
213
- const note = reason === "already_done"
214
- ? "duplicate task already completed"
215
- : `task claimed by ${dispatchClaim?.claimedBy || "another agent"}`;
216
- if (reason === "already_done" && dispatchClaim?.doneRecord?.reply) {
217
- client.publish({
218
- channel: "done",
219
- type: "task.done",
220
- to: from,
221
- taskId,
222
- correlationId,
223
- priority: "medium",
224
- payload: {
225
- source: CREWSWARM_RT_AGENT,
226
- incomingType,
227
- reply: dispatchClaim.doneRecord.reply,
228
- duplicate: true,
229
- idempotencyKey: dispatchKey,
230
- completedBy: dispatchClaim.doneRecord.agent || null,
231
- completedAt: dispatchClaim.doneRecord.doneAt || null,
232
- },
233
- });
234
- }
235
- client.ack({ messageId: envelope.id, status: "skipped", note });
236
- return;
237
- }
238
-
239
- dispatchHeartbeat = setInterval(() => {
240
- const renewed = renewTaskLease({
241
- key: dispatchKey,
242
- claimId: dispatchClaim.claimId,
243
- leaseMs: CREWSWARM_RT_DISPATCH_LEASE_MS,
244
- });
245
- if (!renewed) {
246
- telemetry("dispatch_lease_lost", {
247
- key: dispatchKey,
248
- taskId,
249
- incomingType,
250
- claimId: dispatchClaim?.claimId,
251
- });
252
- }
253
- }, CREWSWARM_RT_DISPATCH_HEARTBEAT_MS);
254
-
255
- telemetry("dispatch_claim_acquired", {
256
- key: dispatchKey,
257
- taskId,
258
- incomingType,
259
- claimId: dispatchClaim.claimId,
260
- attempt: dispatchAttempt,
261
- });
262
- }
263
-
264
- client.ack({ messageId: envelope.id, status: "received", note: `crewswarm accepted ${incomingType}` });
265
- client.publish({
266
- channel: "status",
267
- type: "task.in_progress",
268
- to: from,
269
- taskId,
270
- correlationId,
271
- priority: "high",
272
- payload: {
273
- source: CREWSWARM_RT_AGENT,
274
- note: `Processing ${incomingType}`,
275
- action,
276
- idempotencyKey: dispatchKey,
277
- attempt: dispatchAttempt,
278
- },
279
- });
280
-
281
- try {
282
- const taskProjectDir = payload?.projectDir || getOpencodeProjectDir() || null;
283
- const { finalPrompt, sharedMemory } = buildTaskPrompt(prompt, `Realtime task from ${from} (${incomingType})`, CREWSWARM_RT_AGENT, { projectDir: taskProjectDir });
284
- if (sharedMemory.loadFailed || finalPrompt === "MEMORY_LOAD_FAILED") {
285
- throw new Error("MEMORY_LOAD_FAILED");
286
- }
287
- assertTaskPromptProtocol(finalPrompt, "realtime");
288
-
289
- // ── Dynamic Engine Selection (registry-based) ──────────────────────────────
290
- const selectedEngine = selectEngine(payload, incomingType);
291
-
292
- if (selectedEngine) {
293
- progress(`Routing to ${selectedEngine.label || selectedEngine.id} (priority ${selectedEngine.priority})...`);
294
- telemetry(`realtime_route_${selectedEngine.id}`, { taskId, incomingType, from, agent: CREWSWARM_RT_AGENT });
295
- } else {
296
- // Fall back to OpenCode or direct LLM if no engine matches
297
- progress(`No engine matched — falling back to OpenCode or direct LLM`);
298
- telemetry("realtime_route_fallback", { taskId, incomingType, from, agent: CREWSWARM_RT_AGENT });
299
- }
300
-
301
- // Emit working indicator for ALL tasks
302
- client?.publish({ channel: "events", type: "agent_working", to: "broadcast", payload: { agent: CREWSWARM_RT_AGENT, ts: Date.now() } });
303
-
304
- let reply;
305
- let ocAgentId = null;
306
- let agentSysPrompt = null;
307
- let projectDir = taskProjectDir || null;
308
- let engineUsed = selectedEngine?.id || null;
309
- let modelUsed = null;
310
-
311
- // Token budget tracking (progressive disclosure pattern)
312
- const estimateTokens = (text) => Math.ceil((text || '').length / 4);
313
- const contextTokens = estimateTokens(finalPrompt);
314
- const maxContextTokens = 100000; // Conservative default, can be model-specific
315
- const tokenBudgetWarning = contextTokens > (maxContextTokens * 0.7)
316
- ? `\n\n⏰ **Context Budget:** ${contextTokens.toLocaleString()} / ${maxContextTokens.toLocaleString()} tokens used (${Math.round(contextTokens/maxContextTokens*100)}%). Prioritize completing current work before adding more context.`
317
- : '';
318
-
319
- // Inject warning into prompt if near limit
320
- let finalPromptWithBudget = finalPrompt;
321
- if (tokenBudgetWarning) {
322
- finalPromptWithBudget = finalPrompt + tokenBudgetWarning;
323
- telemetry("token_budget_warning", { taskId, contextTokens, maxContextTokens, utilization: contextTokens/maxContextTokens });
324
- }
325
-
326
- // Adaptive reasoning budget (LangChain pattern: xhigh-high-xhigh sandwich)
327
- const isPlanning = /plan|design|architect|scope|roadmap|strategy/i.test(prompt);
328
- const isVerification = /verify|test|check|validate|review|audit/i.test(prompt);
329
- const reasoningBudget = isPlanning ? 'xhigh' : isVerification ? 'xhigh' : 'high';
330
-
331
- const adaptivePayload = {
332
- ...payload,
333
- reasoningBudget,
334
- contextTokens,
335
- tokenBudgetWarning: !!tokenBudgetWarning
336
- };
337
-
338
- telemetry("task_start", {
339
- taskId,
340
- incomingType,
341
- contextTokens,
342
- reasoningBudget,
343
- isPlanning,
344
- isVerification
345
- });
346
-
347
- // ── Execute via selected engine ─────────────────────────────────────────────
348
- if (selectedEngine && selectedEngine.run) {
349
- projectDir = payload?.projectDir || getOpencodeProjectDir() || process.cwd();
350
- projectDir = String(projectDir).replace(/[.,;!?]+$/, "");
351
- const enginePrompt = await buildMiniTaskForOpenCode(prompt, CREWSWARM_RT_AGENT, projectDir);
352
- const agentCfg = getAgentOpenCodeConfig(CREWSWARM_RT_AGENT);
353
- modelUsed = payload?.model || agentCfg.model || 'unknown';
354
-
355
- try {
356
- // Check for ouroboros-style loop mode
357
- if (agentCfg.loop && (selectedEngine.id === 'cursor' || selectedEngine.id === 'claude-code')) {
358
- progress(`${selectedEngine.label} loop mode: LLM ↔ Engine until DONE…`);
359
- try {
360
- reply = await runOuroborosStyleLoop(prompt, CREWSWARM_RT_AGENT, projectDir, payload, progress, selectedEngine.id);
361
- } catch (e) {
362
- progress(`${selectedEngine.label} loop failed: ${e?.message?.slice(0, 80)} — falling back to single shot`);
363
- reply = await selectedEngine.run(enginePrompt, { ...payload, agentId: CREWSWARM_RT_AGENT, projectDir });
364
- }
365
- } else {
366
- reply = await selectedEngine.run(enginePrompt, { ...payload, agentId: CREWSWARM_RT_AGENT, projectDir });
367
- }
368
- } catch (e) {
369
- const msg = e?.message ?? String(e);
370
- const isUsageLimit = /usage.*limit|hit.*limit|quota.*exceeded|limit.*reset/i.test(msg);
371
- if (isUsageLimit) {
372
- progress(`${selectedEngine.label} usage limit hit: ${msg.slice(0, 120)}`);
373
- telemetry(`${selectedEngine.id}_usage_limit`, { taskId, error: msg });
374
- reply = `❌ ${selectedEngine.label} usage limit reached:\n\n${msg}\n\n(Fallback disabled to show you the error)`;
375
- } else {
376
- progress(`${selectedEngine.label} failed: ${msg.slice(0, 120)} — falling back to direct LLM`);
377
- telemetry(`${selectedEngine.id}_fallback`, { taskId, error: msg });
378
- reply = await callLLMDirect(finalPrompt, CREWSWARM_RT_AGENT, null);
379
- }
380
- }
381
- } else {
382
- // No engine matched — fall back to OpenCode or direct LLM
383
- engineUsed = "opencode";
384
- projectDir = payload?.projectDir || getOpencodeProjectDir() || null;
385
- if (!projectDir || projectDir === process.cwd()) {
386
- const fromTask = extractProjectDirFromTask(prompt);
387
- if (fromTask) projectDir = fromTask;
388
- }
389
- projectDir = projectDir || process.cwd();
390
- const ocAgentCfg = getAgentOpenCodeConfig(CREWSWARM_RT_AGENT);
391
- modelUsed = payload?.model || ocAgentCfg.model || CREWSWARM_OPENCODE_MODEL;
392
- let opencodeErr;
393
-
394
- if (ocAgentCfg.loop) {
395
- // Ouroboros-style: LLM decomposes → OpenCode executes each step → repeat until DONE
396
- progress("OpenCode loop mode: LLM ↔ OpenCode until DONE...");
397
- try {
398
- reply = await runOuroborosStyleLoop(prompt, CREWSWARM_RT_AGENT, projectDir, payload, progress, "opencode");
399
- } catch (e) {
400
- opencodeErr = e;
401
- progress(`OpenCode loop failed: ${e?.message?.slice(0, 80)} — falling back to single shot`);
402
- const ocPrompt = await buildMiniTaskForOpenCode(prompt, CREWSWARM_RT_AGENT, projectDir);
403
- reply = await runOpenCodeTask(ocPrompt, payload);
404
- }
405
- } else {
406
- // Single-shot: mini task only (now includes shared memory context)
407
- const ocPrompt = await buildMiniTaskForOpenCode(prompt, CREWSWARM_RT_AGENT, projectDir);
408
- try {
409
- reply = await runOpenCodeTask(ocPrompt, payload);
410
- } catch (e) {
411
- opencodeErr = e;
412
- const msg = e?.message ?? String(e);
413
- const isRateLimit = /429|rate\s*limit|usage.*limit|quota.*exceeded|too\s*many\s*requests|banner-only/i.test(msg);
414
- const isTimeout = /timeout|timed\s*out|stall/i.test(msg);
415
- if (isRateLimit || isTimeout) {
416
- // Build rotation chain: free models first, then configured fallback, deduplicated
417
- // Track ALL tried models (primary + each fallback attempt) to avoid re-trying failed ones
418
- const primaryModel = String(payload?.model || CREWSWARM_OPENCODE_MODEL);
419
- const configFallback = getAgentOpenCodeConfig(CREWSWARM_RT_AGENT).fallbackModel;
420
- const triedModels = new Set([primaryModel]);
421
- // Per-agent opencodeFallbackModel goes FIRST, then global free chain as safety net
422
- const chain = [...(configFallback ? [configFallback] : []), ...OPENCODE_FREE_MODEL_CHAIN]
423
- .filter((m, i, arr) => m !== primaryModel && arr.indexOf(m) === i);
424
- for (const fbModel of chain) {
425
- if (triedModels.has(fbModel)) continue; // skip already-tried models
426
- triedModels.add(fbModel);
427
- const reason = isTimeout ? "timed out" : "rate limited";
428
- progress(`OpenCode ${primaryModel} ${reason} — rotating to ${fbModel}`);
429
- telemetry("realtime_opencode_fallback", { taskId, incomingType, error: msg, fallbackModel: fbModel });
430
- try {
431
- reply = await runOpenCodeTask(ocPrompt, { ...payload, model: fbModel });
432
- if (reply) break;
433
- } catch (fbErr) {
434
- opencodeErr = fbErr;
435
- const fbMsg = fbErr?.message ?? String(fbErr);
436
- const fbRateLimit = /429|rate\s*limit|usage.*limit|quota.*exceeded|banner-only|stall/i.test(fbMsg);
437
- if (!fbRateLimit) break; // non-rate-limit/stall error — stop rotating
438
- // rate-limited/stalled on this fallback too — continue to next in chain
439
- }
440
- }
441
- }
442
- if (!reply && bridge?.kind === "gateway") {
443
- telemetry("realtime_opencode_fallback", { taskId, incomingType, error: opencodeErr?.message || msg });
444
- progress(`OpenCode failed, falling back to legacy gateway: ${(opencodeErr?.message || msg).slice(0, 120)}`);
445
- const gatewayAgentId = RT_TO_GATEWAY_AGENT_MAP[CREWSWARM_RT_AGENT] || "main";
446
- reply = await bridge.chat(finalPrompt, gatewayAgentId, { idempotencyKey: dispatchKey });
447
- } else if (!reply) {
448
- // Try direct LLM call (uses agent's configured model/provider from crewswarm.json)
449
- engineUsed = "direct-llm";
450
- const ocAgentId = RT_TO_GATEWAY_AGENT_MAP[CREWSWARM_RT_AGENT] || "main";
451
- const agentSysPrompt = loadAgentPrompts()[ocAgentId] || null;
452
- const agentCfg = getAgentOpenCodeConfig(CREWSWARM_RT_AGENT);
453
- modelUsed = agentCfg?.model || 'unknown';
454
- progress(`Trying direct LLM for ${CREWSWARM_RT_AGENT} (mapped: ${ocAgentId})...`);
455
- reply = await callLLMDirect(finalPrompt, ocAgentId, agentSysPrompt);
456
-
457
- if (!reply) {
458
- // Fall through to legacy gateway (uses its default model)
459
- progress(`No direct LLM config for ${ocAgentId}, falling back to legacy gateway...`);
460
- telemetry("realtime_direct_llm_fallback", { taskId, ocAgentId, incomingType });
461
- assertTaskPromptProtocol(finalPrompt, "realtime-gateway-chat");
462
- reply = await bridge.chat(finalPrompt, ocAgentId, { idempotencyKey: dispatchKey });
463
- }
464
- }
465
- }
466
- }
467
- }
468
- if (!reply || reply === "(timeout - no reply)") {
469
- throw new Error("Chat timeout while processing realtime task");
470
- }
471
- reply = stripThink(reply);
472
-
473
- // Execute any tool calls — suppress @@WRITE_FILE if searches are pending in the same reply
474
- const toolResults = await executeToolCalls(reply, CREWSWARM_RT_AGENT, { suppressWriteIfSearchPending: true });
475
- if (toolResults.length > 0) {
476
- reply = reply + "\n\n---\n**Tool execution results:**\n" + toolResults.join("\n");
477
- telemetry("agent_tools_executed", { taskId, agent: CREWSWARM_RT_AGENT, count: toolResults.length });
478
-
479
- // Do a follow-up LLM call whenever:
480
- // (a) searches ran (agent needs to see results before writing), OR
481
- // (b) write was suppressed (agent tried to write before searching)
482
- const hasSearchResults = toolResults.some(r => r.includes("[tool:web_search]") || r.includes("[tool:web_fetch]") || r.includes("[tool:read_file]"));
483
- const writeSuppressed = toolResults.some(r => r.includes("⏸ Write suppressed"));
484
- const didWriteFile = toolResults.some(r => r.includes("[tool:write_file] ✅"));
485
-
486
- if (hasSearchResults && (!didWriteFile || writeSuppressed)) {
487
- try {
488
- const followUpPrompt = `${agentSysPrompt || ""}\n\n[Original task]:\n${finalPrompt}\n\n[Tool results from your searches]:\n${toolResults.join("\n")}\n\nUsing ONLY the search results above (not your training data), write the complete output now using @@WRITE_FILE. Do not search again — just synthesize and write.`;
489
- let followUpReply = ocAgentId
490
- ? await callLLMDirect(followUpPrompt, ocAgentId, agentSysPrompt)
491
- : null;
492
- if (!followUpReply) followUpReply = await bridge.chat(followUpPrompt, ocAgentId || "main", { idempotencyKey: dispatchKey + "-followup" });
493
- followUpReply = stripThink(followUpReply);
494
- const followUpTools = await executeToolCalls(followUpReply, CREWSWARM_RT_AGENT);
495
- reply = reply + "\n\n" + followUpReply;
496
- if (followUpTools.length > 0) {
497
- reply = reply + "\n\n---\n**Follow-up tool results:**\n" + followUpTools.join("\n");
498
- }
499
- } catch (err) {
500
- console.warn(`[bridge] Follow-up synthesis call failed: ${err.message}`);
501
- }
502
- }
503
- }
504
-
505
- // Append original task spec for self-verification (LangChain pattern)
506
- // Helps agent confirm implementation matches ALL requirements
507
- if (reply && prompt && !reply.includes('[ORIGINAL TASK]')) {
508
- const taskSpecReminder = `\n\n---\n**[ORIGINAL TASK]:**\n${prompt.slice(0, 500)}${prompt.length > 500 ? '...' : ''}\n\nDoes your implementation address ALL requirements above?`;
509
- reply = reply + taskSpecReminder;
510
- telemetry("task_spec_injected", { taskId, promptLength: prompt.length });
511
- }
512
-
513
- // Validate coding artifacts for coding tasks
514
- const validation = validateCodingArtifacts(reply, incomingType, prompt, payload);
515
- if (!validation.valid) {
516
- telemetry("coding_artifact_validation_failed", {
517
- taskId,
518
- incomingType,
519
- reason: validation.reason,
520
- replyLength: reply.length,
521
- });
522
-
523
- // Send feedback to agent before retrying
524
- client.publish({
525
- channel: "issues",
526
- type: "task.artifact_missing",
527
- to: CREWSWARM_RT_AGENT, // Send feedback to self for learning
528
- taskId,
529
- correlationId,
530
- priority: "high",
531
- payload: {
532
- source: "gateway",
533
- error: `CODING_ARTIFACT_MISSING: ${validation.reason}`,
534
- feedback: "Your reply must include: (1) Files changed with paths, (2) What changed in each file, (3) Command outputs (build/test/lint), (4) Verification steps. Do not reply with only suggestions or 'Done' without evidence.",
535
- originalPrompt: String(prompt).slice(0, 500),
536
- replyPreview: String(reply).slice(0, 500),
537
- },
538
- });
539
-
540
- throw new Error(`CODING_ARTIFACT_MISSING: ${validation.reason}`);
541
- }
542
-
543
- if (dispatchGuardEnabled && dispatchClaim?.acquired) {
544
- markTaskDone({
545
- key: dispatchKey,
546
- claimId: dispatchClaim.claimId,
547
- taskId,
548
- incomingType,
549
- from,
550
- attempt: dispatchAttempt,
551
- idempotencyKey: dispatchKey,
552
- reply,
553
- });
554
- telemetry("dispatch_task_done", {
555
- key: dispatchKey,
556
- taskId,
557
- incomingType,
558
- claimId: dispatchClaim.claimId,
559
- });
560
- }
561
-
562
- // Parse @@LESSON: tags — write to project brain (if projectDir) or global lessons.md
563
- // This is how agents contribute durable knowledge without polluting system prompts
564
- const lessonMatches = [...reply.matchAll(/@@LESSON:\s*([^\n]+)/g)];
565
- if (lessonMatches.length > 0) {
566
- const date = new Date().toISOString().slice(0, 10);
567
- for (const m of lessonMatches) {
568
- const entry = m[1].trim();
569
- if (!entry) continue;
570
- try {
571
- if (projectDir) {
572
- const projectMemDir = path.join(projectDir, ".crewswarm");
573
- fs.mkdirSync(projectMemDir, { recursive: true });
574
- const projectBrainPath = path.join(projectMemDir, "brain.md");
575
- if (!fs.existsSync(projectBrainPath)) {
576
- fs.writeFileSync(projectBrainPath, "# Project Brain\n\nAccumulated knowledge for this project.\n", "utf8");
577
- }
578
- fs.appendFileSync(projectBrainPath, `\n## [${date}] ${CREWSWARM_RT_AGENT}: ${entry}\n`, "utf8");
579
- } else {
580
- const lessonsPath = path.join(SHARED_MEMORY_DIR, "lessons.md");
581
- fs.appendFileSync(lessonsPath, `\n## [${date}] ${CREWSWARM_RT_AGENT}: ${entry}\n`, "utf8");
582
- }
583
- console.log(`[bridge:${CREWSWARM_RT_AGENT}] @@LESSON → ${projectDir ? path.basename(projectDir) + "/.crewswarm/brain.md" : "lessons.md"}: ${entry.slice(0, 80)}`);
584
- } catch (e) {
585
- console.warn(`[bridge:${CREWSWARM_RT_AGENT}] @@LESSON write failed: ${e.message}`);
586
- }
587
- }
588
- }
589
-
590
- // Parse and execute @@DISPATCH commands from coordinator agents only.
591
- // Canonical format: @@DISPATCH {"agent":"crew-coder","task":"..."}
592
- // Legacy format also supported: @@DISPATCH:agent-id|task description
593
- // Non-coordinator agents are blocked from dispatching to prevent loops.
594
- const COORDINATOR_AGENTS = new Set(COORDINATOR_AGENT_IDS);
595
- const rawDispatches = COORDINATOR_AGENTS.has(CREWSWARM_RT_AGENT)
596
- ? (() => {
597
- const results = [];
598
- // Canonical JSON format
599
- for (const m of reply.matchAll(/@@DISPATCH\s+(\{[^}]+\})/g)) {
600
- try {
601
- const d = JSON.parse(m[1]);
602
- if (d.agent && d.task) results.push({ targetAgent: d.agent.trim(), taskText: d.task.trim() });
603
- } catch {}
604
- }
605
- // Legacy pipe format (still supported, normalized here)
606
- for (const m of reply.matchAll(/@@DISPATCH:([a-z0-9_-]+)\|([^\n@@]+)/g)) {
607
- results.push({ targetAgent: m[1].trim(), taskText: m[2].trim() });
608
- }
609
- return results;
610
- })()
611
- : [];
612
- if (rawDispatches.length > 0) {
613
- for (const { targetAgent, taskText } of rawDispatches) {
614
- // Block self-dispatch and empty targets
615
- if (!targetAgent || !taskText || targetAgent === CREWSWARM_RT_AGENT) continue;
616
- try {
617
- // For audit/QA tasks, inject file contents so the agent can actually read them
618
- let enrichedTask = taskText;
619
- const filePaths = [...taskText.matchAll(/([~/\w.-]+\.(?:html|css|js|mjs|ts|md|json))/g)].map(m => m[1]);
620
- if (filePaths.length > 0) {
621
- const fileSnippets = [];
622
- for (const fp of filePaths.slice(0, 3)) {
623
- try {
624
- const absPath = fp.startsWith("~") ? fp.replace("~", os.homedir()) : fp;
625
- const content = fs.readFileSync(absPath, "utf8");
626
- const lines = content.split("\n");
627
- // Include full file for small files, truncated for large ones
628
- const snippet = lines.length <= 600
629
- ? content
630
- : lines.slice(0, 300).join("\n") + `\n\n... (${lines.length - 300} more lines truncated) ...\n` + lines.slice(-100).join("\n");
631
- fileSnippets.push(`\n\n--- FILE: ${absPath} (${lines.length} lines) ---\n${snippet}\n--- END FILE ---`);
632
- } catch { /* file not readable, skip */ }
633
- }
634
- if (fileSnippets.length > 0) {
635
- enrichedTask = taskText + "\n\nFile contents for your audit:" + fileSnippets.join("");
636
- }
637
- }
638
- const dispatchTaskId = "dispatch-" + Date.now() + "-" + Math.random().toString(36).slice(2, 6);
639
- client.publish({
640
- channel: "command",
641
- type: "command.run_task",
642
- to: targetAgent,
643
- taskId: dispatchTaskId,
644
- priority: "high",
645
- payload: {
646
- action: "run_task",
647
- prompt: enrichedTask,
648
- dispatchedBy: CREWSWARM_RT_AGENT,
649
- parentTaskId: taskId,
650
- },
651
- });
652
- telemetry("crew_dispatch_forwarded", { from: CREWSWARM_RT_AGENT, to: targetAgent, taskId: dispatchTaskId });
653
- progress(`Dispatched task to ${targetAgent}: ${taskText.slice(0, 60)}`);
654
- } catch (dispErr) {
655
- console.error(`[bridge] CREW_DISPATCH to ${targetAgent} failed:`, dispErr?.message);
656
- }
657
- }
658
- }
659
-
660
- client.publish({
661
- channel: "done",
662
- type: "task.done",
663
- to: from,
664
- taskId,
665
- correlationId,
666
- priority: "high",
667
- payload: {
668
- source: CREWSWARM_RT_AGENT,
669
- reply,
670
- incomingType,
671
- idempotencyKey: dispatchKey,
672
- engineUsed, // Track which coding engine handled the task (claude, codex, cursor, opencode, etc.)
673
- },
674
- });
675
-
676
- // Save to unified project messages store
677
- if (payload?.projectId && payload.projectId !== 'general') {
678
- try {
679
- saveProjectMessage(payload.projectId, {
680
- source: 'agent',
681
- role: 'assistant',
682
- content: reply,
683
- agent: CREWSWARM_RT_AGENT,
684
- metadata: {
685
- taskId,
686
- engineUsed,
687
- incomingType,
688
- model: modelUsed || 'unknown'
689
- }
690
- });
691
- } catch (e) {
692
- console.warn(`[rt-envelope] Failed to save agent reply to project store: ${e.message}`);
693
- }
694
- }
695
-
696
- // Record to shared memory (AgentKeeper) for future recall
697
- if (isSharedMemoryAvailable()) {
698
- const projectDir = extractProjectDirFromTask(prompt) || getOpencodeProjectDir() || process.cwd();
699
- recordTaskMemory(projectDir, {
700
- runId: taskId,
701
- tier: 'worker',
702
- task: prompt,
703
- result: reply,
704
- agent: CREWSWARM_RT_AGENT,
705
- model: modelUsed || 'unknown',
706
- metadata: {
707
- engineUsed,
708
- incomingType,
709
- success: true,
710
- timestamp: new Date().toISOString()
711
- }
712
- }).catch(err => {
713
- console.warn(`[${CREWSWARM_RT_AGENT}] Failed to record task memory: ${err.message}`);
714
- });
715
- }
716
-
717
- client?.publish({ channel: "events", type: "agent_idle", to: "broadcast", payload: { agent: CREWSWARM_RT_AGENT, ts: Date.now() } });
718
- client.ack({ messageId: envelope.id, status: "done", note: "task completed" });
719
- } catch (err) {
720
- const message = err?.message ?? String(err);
721
- const isCoding = isCodingTask(incomingType, prompt, payload);
722
- const maxRetries = isCoding ? CREWSWARM_RT_DISPATCH_MAX_RETRIES_CODING : CREWSWARM_RT_DISPATCH_MAX_RETRIES;
723
- const shouldRetry = dispatchGuardEnabled
724
- && dispatchClaim?.acquired
725
- && shouldRetryTaskFailure(err)
726
- && dispatchAttempt < maxRetries;
727
-
728
- if (shouldRetry) {
729
- const retryAttempt = dispatchAttempt + 1;
730
- const retryAfterMs = CREWSWARM_RT_DISPATCH_RETRY_BACKOFF_MS * (2 ** dispatchAttempt);
731
- telemetry("dispatch_retry_scheduled", {
732
- key: dispatchKey,
733
- taskId,
734
- incomingType,
735
- attempt: retryAttempt,
736
- retryAfterMs,
737
- error: message,
738
- });
739
- client.publish({
740
- channel: "status",
741
- type: "task.retrying",
742
- to: from,
743
- taskId,
744
- correlationId,
745
- priority: "high",
746
- payload: {
747
- source: CREWSWARM_RT_AGENT,
748
- incomingType,
749
- attempt: retryAttempt,
750
- retryAfterMs,
751
- error: message,
752
- idempotencyKey: dispatchKey,
753
- },
754
- });
755
-
756
- setTimeout(() => {
757
- try {
758
- client.publish({
759
- channel: "command",
760
- type: incomingType,
761
- to: CREWSWARM_RT_AGENT, // Retry to SELF, not broadcast (prevents 7x amplification)
762
- taskId,
763
- priority: "high",
764
- payload: {
765
- ...payload,
766
- _dispatchAttempt: retryAttempt,
767
- _dispatchIdempotencyKey: dispatchKey,
768
- _dispatchRetryOf: dispatchAttempt,
769
- _dispatchLastError: message,
770
- },
771
- });
772
- } catch (publishErr) {
773
- telemetry("dispatch_retry_publish_error", {
774
- key: dispatchKey,
775
- taskId,
776
- incomingType,
777
- attempt: retryAttempt,
778
- error: publishErr?.message ?? String(publishErr),
779
- });
780
- }
781
- }, retryAfterMs);
782
-
783
- return;
784
- }
785
-
786
- // Write to DLQ if all retries exhausted
787
- if (dispatchGuardEnabled && dispatchClaim?.acquired && dispatchAttempt >= maxRetries) {
788
- const dlqPath = path.join(SWARM_DLQ_DIR, `${dispatchKey}.json`);
789
- const dlqEntry = {
790
- key: dispatchKey,
791
- taskId,
792
- incomingType,
793
- from,
794
- agent: CREWSWARM_RT_AGENT,
795
- attempt: dispatchAttempt,
796
- error: message,
797
- prompt: String(prompt).slice(0, 2000),
798
- payload,
799
- failedAt: new Date().toISOString(),
800
- envelope,
801
- };
802
- try {
803
- fs.writeFileSync(dlqPath, JSON.stringify(dlqEntry, null, 2), "utf8");
804
- telemetry("dlq_write", { key: dispatchKey, taskId, incomingType });
805
- } catch (dlqErr) {
806
- telemetry("dlq_write_error", { key: dispatchKey, error: dlqErr?.message });
807
- }
808
-
809
- // ── Auto-escalate to crew-fixer when coding agents exhaust retries ─────
810
- const ESCALATABLE_AGENTS = new Set([
811
- "crew-coder", "crew-coder-front", "crew-coder-back", "crew-frontend", "crew-copywriter",
812
- ]);
813
- const isSelf = CREWSWARM_RT_AGENT === "crew-fixer"; // prevent fixer→fixer loop
814
- if (ESCALATABLE_AGENTS.has(CREWSWARM_RT_AGENT) && !isSelf) {
815
- const fixerTaskId = `fixer-escalation-${Date.now()}`;
816
- const fixerPrompt =
817
- `⚠️ Auto-escalation from ${CREWSWARM_RT_AGENT} (failed after ${dispatchAttempt + 1} attempts).\n\n` +
818
- `**Original task:**\n${String(prompt).slice(0, 1500)}\n\n` +
819
- `**Error:**\n${message.slice(0, 500)}\n\n` +
820
- `Use @@READ_FILE to inspect any relevant files, identify the root cause, and fix it.`;
821
- try {
822
- client.publish({
823
- channel: "command",
824
- type: "command.run_task",
825
- to: "crew-fixer",
826
- taskId: fixerTaskId,
827
- priority: "high",
828
- payload: { action: "run_task", prompt: fixerPrompt, escalatedFrom: CREWSWARM_RT_AGENT, parentTaskId: taskId },
829
- });
830
- telemetry("task_escalated_to_fixer", { fromAgent: CREWSWARM_RT_AGENT, taskId, fixerTaskId });
831
- console.log(`[${CREWSWARM_RT_AGENT}] ⬆️ Escalated failed task to crew-fixer (${fixerTaskId})`);
832
- } catch (escErr) {
833
- console.error(`[${CREWSWARM_RT_AGENT}] Escalation to crew-fixer failed:`, escErr?.message);
834
- }
835
- }
836
- // ─────────────────────────────────────────────────────────────────────────
837
- }
838
-
839
- client.publish({
840
- channel: "issues",
841
- type: "task.failed",
842
- to: from,
843
- taskId,
844
- correlationId,
845
- priority: "high",
846
- payload: {
847
- source: CREWSWARM_RT_AGENT,
848
- error: message,
849
- idempotencyKey: dispatchKey,
850
- attempt: dispatchAttempt,
851
- },
852
- });
853
- client.ack({ messageId: envelope.id, status: "failed", note: message.slice(0, 240) });
854
- } finally {
855
- if (dispatchHeartbeat) {
856
- clearInterval(dispatchHeartbeat);
857
- dispatchHeartbeat = null;
858
- }
859
- if (dispatchGuardEnabled && dispatchClaim?.acquired) {
860
- const released = releaseTaskLease({ key: dispatchKey, claimId: dispatchClaim.claimId });
861
- telemetry("dispatch_claim_released", {
862
- key: dispatchKey,
863
- taskId,
864
- incomingType,
865
- claimId: dispatchClaim.claimId,
866
- released,
867
- });
868
- }
869
- }
870
- }