crewswarm 0.9.2 → 0.9.3

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 (207) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
  3. package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
  4. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  5. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  6. package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
  7. package/apps/dashboard/dist/index.html +82 -11
  8. package/apps/vibe/README.md +2 -2
  9. package/apps/vibe/package.json +1 -1
  10. package/apps/vibe/server.mjs +3 -3
  11. package/crew-lead.mjs +34 -4
  12. package/lib/bridges/gateway-ws.mjs +4 -0
  13. package/lib/crew-lead/chat-handler.mjs +34 -0
  14. package/lib/crew-lead/http-server.mjs +55 -14
  15. package/lib/crew-lead/llm-caller.mjs +24 -8
  16. package/lib/crew-lead/prompts.mjs +7 -0
  17. package/lib/crew-lead/wave-dispatcher.mjs +15 -3
  18. package/lib/crew-lead/ws-router.mjs +219 -27
  19. package/lib/engines/engine-registry.mjs +9 -0
  20. package/lib/engines/rt-envelope.mjs +1 -0
  21. package/lib/engines/runners.mjs +5 -2
  22. package/lib/runtime/paths.mjs +12 -8
  23. package/package.json +35 -15
  24. package/scripts/capture-build-flow.mjs +118 -0
  25. package/scripts/coverage-report.mjs +209 -0
  26. package/scripts/coverage-summary.mjs +47 -0
  27. package/scripts/dashboard-validation.mjs +74 -0
  28. package/scripts/dashboard.mjs +560 -70
  29. package/scripts/live-bridge-matrix.mjs +79 -0
  30. package/scripts/live-cli-matrix.mjs +166 -0
  31. package/scripts/live-crewchat-check.mjs +42 -0
  32. package/scripts/live-engine-matrix.mjs +50 -0
  33. package/scripts/live-provider-failover-matrix.mjs +107 -0
  34. package/scripts/live-provider-matrix.mjs +228 -0
  35. package/scripts/restart-all-from-repo.sh +4 -4
  36. package/scripts/smoke-dispatch.mjs +4 -1
  37. package/scripts/test-blast-radius.mjs +204 -0
  38. package/scripts/test-report-summary.mjs +88 -0
  39. package/scripts/test-reporter.mjs +651 -0
  40. package/scripts/test-rerun.mjs +136 -0
  41. package/scripts/tmux-bridge +130 -0
  42. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  43. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  44. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  45. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  46. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  47. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  48. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  49. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  50. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  52. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  54. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  56. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  57. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  58. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  59. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  60. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  61. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  62. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  63. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  64. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  65. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  66. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  67. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  68. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  69. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  70. package/apps/dashboard/dist/index.html.br +0 -0
  71. package/apps/dashboard/dist/index.html.gz +0 -0
  72. package/apps/dashboard/index.html +0 -6529
  73. package/apps/dashboard/package.json +0 -15
  74. package/apps/dashboard/src/app.js +0 -2828
  75. package/apps/dashboard/src/app.js.br +0 -0
  76. package/apps/dashboard/src/app.js.gz +0 -0
  77. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  78. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  79. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  80. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  81. package/apps/dashboard/src/cli-process.js +0 -208
  82. package/apps/dashboard/src/cli-process.js.br +0 -0
  83. package/apps/dashboard/src/cli-process.js.gz +0 -0
  84. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  85. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  86. package/apps/dashboard/src/core/api.js +0 -18
  87. package/apps/dashboard/src/core/api.js.br +0 -0
  88. package/apps/dashboard/src/core/dom.js +0 -228
  89. package/apps/dashboard/src/core/dom.js.br +0 -0
  90. package/apps/dashboard/src/core/state.js +0 -91
  91. package/apps/dashboard/src/core/state.js.br +0 -0
  92. package/apps/dashboard/src/core/task-manager.js +0 -134
  93. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  94. package/apps/dashboard/src/orchestration-status.js +0 -127
  95. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  96. package/apps/dashboard/src/setup-wizard.js +0 -562
  97. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  98. package/apps/dashboard/src/styles.css +0 -2085
  99. package/apps/dashboard/src/styles.css.br +0 -0
  100. package/apps/dashboard/src/styles.css.gz +0 -0
  101. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  102. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  104. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  106. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  108. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  110. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  111. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  112. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  113. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  114. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  115. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  116. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  117. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  118. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  119. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  120. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  121. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  123. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  125. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  127. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  129. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  131. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  133. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  135. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  137. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  139. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  140. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  141. package/apps/vibe/.crew/cost.json +0 -17
  142. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  143. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  158. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  159. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  160. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  161. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  162. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  163. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  164. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  172. package/apps/vibe/.crew/sandbox.json +0 -7
  173. package/apps/vibe/.crew/session.json +0 -330
  174. package/apps/vibe/.crew/training-data.jsonl +0 -0
  175. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  176. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  177. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  178. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  179. package/apps/vibe/ARCHITECTURE.md +0 -3393
  180. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  181. package/apps/vibe/ROADMAP.md +0 -41
  182. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  183. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  184. package/apps/vibe/capture-demo.mjs +0 -160
  185. package/apps/vibe/capture-full-demo.mjs +0 -255
  186. package/apps/vibe/capture-quickstart.mjs +0 -256
  187. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  188. package/apps/vibe/capture-vibe-video.mjs +0 -260
  189. package/apps/vibe/check-buttons.js +0 -41
  190. package/apps/vibe/diagnose.html +0 -106
  191. package/apps/vibe/fix-buttons.js +0 -103
  192. package/apps/vibe/index.html +0 -3404
  193. package/apps/vibe/package-lock.json +0 -920
  194. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  195. package/apps/vibe/src/main.js +0 -2940
  196. package/apps/vibe/src/register-all-languages.js +0 -98
  197. package/apps/vibe/start-studio.sh +0 -11
  198. package/apps/vibe/test/accessibility-tests.js +0 -77
  199. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  200. package/apps/vibe/test/performance-tests.js +0 -120
  201. package/apps/vibe/test/security-tests.js +0 -213
  202. package/apps/vibe/tests/e2e.local.mjs +0 -54
  203. package/apps/vibe/tests/server.smoke.mjs +0 -106
  204. package/apps/vibe/update_website.mjs +0 -74
  205. package/apps/vibe/vite.config.js +0 -19
  206. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  207. 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
- }