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,663 +0,0 @@
1
- /**
2
- * Projects + Build tab — extracted from app.js
3
- * Deps: getJSON, postJSON (core/api), escHtml, showNotification (core/dom), state (core/state)
4
- * Uses showChat, showBuild (app.js) via injected helpers or window globals
5
- */
6
-
7
- import { getJSON, postJSON } from '../core/api.js';
8
- import { escHtml, showNotification } from '../core/dom.js';
9
- import { state } from '../core/state.js';
10
-
11
- // ── Nav ───────────────────────────────────────────────────────────────────────
12
-
13
- export function showBuild(helpers) {
14
- helpers.hideAllViews();
15
- document.getElementById('buildView').classList.add('active');
16
- helpers.setNavActive('navBuild');
17
- loadPhasedProgress();
18
- }
19
-
20
- export function showProjects(helpers) {
21
- helpers.hideAllViews();
22
- document.getElementById('projectsView').classList.add('active');
23
- helpers.setNavActive('navProjects');
24
- loadProjects();
25
- }
26
-
27
- // ── Project list ──────────────────────────────────────────────────────────────
28
-
29
- export async function loadProjects() {
30
- const list = document.getElementById('projectsList');
31
- list.innerHTML = '<div class="meta" style="padding:20px;">Loading projects...</div>';
32
- try {
33
- const data = await getJSON('/api/projects');
34
- const projects = data.projects || [];
35
- state.projectsData = {};
36
- projects.forEach(p => { state.projectsData[p.id] = p; });
37
- populateChatProjectDropdown(projects);
38
- if (!projects.length) {
39
- list.innerHTML = '<div class="meta" style="padding:20px;">No projects yet. Click &quot;+ New Project&quot; to create one.</div>';
40
- return;
41
- }
42
- list.innerHTML = projects.map(p => {
43
- const id = escHtml(p.id);
44
- const pct = p.roadmap.total ? Math.round((p.roadmap.done / p.roadmap.total) * 100) : 0;
45
- const barColor = pct === 100 ? 'var(--green)' : pct > 50 ? 'var(--accent)' : 'var(--yellow)';
46
- const statusBg = p.status === 'active' ? 'rgba(52,211,153,0.1)' : 'var(--bg-card2)';
47
- const statusColor= p.status === 'active' ? 'var(--green)' : 'var(--text-3)';
48
- const retryBtn = p.roadmap.failed
49
- ? '<button data-action="retry-failed" data-id="' + id + '" style="background:rgba(248,113,113,0.15);color:var(--red);border:1px solid rgba(248,113,113,0.3);border-radius:6px;padding:6px 12px;cursor:pointer;font-size:13px;font-weight:600;">↩ Retry ' + p.roadmap.failed + ' failed</button>'
50
- : '';
51
- return '<div class="card" id="proj-card-' + id + '" data-proj-id="' + id + '">'
52
- + '<div id="proj-view-' + id + '">'
53
- + '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">'
54
- + '<div>'
55
- + '<strong style="font-size:15px;">' + escHtml(p.name) + '</strong>'
56
- + '<span style="margin-left:10px;font-size:11px;padding:2px 8px;border-radius:999px;background:' + statusBg + ';color:' + statusColor + ';border:1px solid ' + statusColor + '40;">' + escHtml(p.status) + '</span>'
57
- + (p.running ? '<span style="margin-left:8px;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(99,102,241,0.15);color:var(--purple);border:1px solid rgba(99,102,241,0.3);">▶ running</span>' : '')
58
- + (p.description ? '<div class="meta" style="margin-top:4px;">' + escHtml(p.description) + '</div>' : '')
59
- + '</div>'
60
- + '<div class="meta">' + new Date(p.created).toLocaleDateString() + '</div>'
61
- + '</div>'
62
- + '<div style="margin-bottom:12px;">'
63
- + '<div style="display:flex;justify-content:space-between;margin-bottom:6px;">'
64
- + '<span class="meta">Roadmap</span>'
65
- + '<span class="meta">' + p.roadmap.done + '/' + p.roadmap.total + ' done' + (p.roadmap.failed ? ' · ' + p.roadmap.failed + ' failed' : '') + ' · ' + p.roadmap.pending + ' pending</span>'
66
- + '</div>'
67
- + '<div class="prog-bar"><div class="prog-fill" style="width:' + pct + '%;background:' + barColor + ';"></div></div>'
68
- + '</div>'
69
- + '<div style="font-size:11px;color:var(--text-3);margin-bottom:12px;font-family:monospace;">' + escHtml(p.outputDir) + '</div>'
70
- + '<div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">'
71
- + '<button data-action="pm-toggle" data-id="' + id + '" class="' + (p.running ? 'btn-red' : 'btn-green') + '" style="font-size:13px;">' + (p.running ? '⏹ Stop PM Loop' : '▶ Start PM Loop') + '</button>'
72
- + '<button data-action="open-build" data-id="' + id + '" class="btn-ghost" style="font-size:13px;">🔧 Build tab</button>'
73
- + '<button data-action="edit-roadmap" data-id="' + id + '" class="btn-ghost" style="font-size:13px;" id="roadmap-btn-' + id + '">📋 Roadmap</button>'
74
- + '<button data-action="chat-project" data-id="' + id + '" data-name="' + escHtml(p.name) + '" class="btn-ghost" style="font-size:13px;">🧠 Chat</button>'
75
- + retryBtn
76
- + '<label style="margin-left:auto;display:flex;align-items:center;gap:6px;cursor:pointer;font-size:12px;color:var(--text-3);user-select:none;" title="When enabled, crew-lead automatically starts the next ROADMAP phase when the current pipeline completes">'
77
- + '<input type="checkbox" data-action="toggle-auto-advance" data-id="' + id + '" ' + (p.autoAdvance ? 'checked' : '') + ' style="accent-color:var(--green);width:14px;height:14px;cursor:pointer;">'
78
- + '⚡ Auto-advance'
79
- + '</label>'
80
- + '<button data-action="edit" data-id="' + id + '" style="background:transparent;color:var(--text-3);border:1px solid var(--border);border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;" title="Edit project">✎ Edit</button>'
81
- + '<button data-action="delete" data-id="' + id + '" style="background:transparent;color:var(--text-3);border:1px solid var(--border);border-radius:6px;padding:4px 10px;cursor:pointer;font-size:12px;" title="Remove from dashboard (files stay on disk)">🗑 Delete</button>'
82
- + '</div>'
83
- + '</div>'
84
- + '<div id="proj-edit-' + id + '" style="display:none;padding:12px;border-top:1px solid var(--border);margin-top:12px;">'
85
- + '<div style="margin-bottom:8px;"><label style="font-size:11px;color:var(--text-3);">Name</label><input id="proj-name-' + id + '" type="text" value="' + escHtml(p.name) + '" style="margin-top:4px;" /></div>'
86
- + '<div style="margin-bottom:8px;"><label style="font-size:11px;color:var(--text-3);">Description</label><input id="proj-desc-' + id + '" type="text" value="' + escHtml(p.description || '') + '" style="margin-top:4px;" placeholder="Optional" /></div>'
87
- + '<div style="margin-bottom:8px;"><label style="font-size:11px;color:var(--text-3);">Output directory</label><input id="proj-dir-' + id + '" type="text" value="' + escHtml(p.outputDir || '') + '" style="margin-top:4px;" /></div>'
88
- + '<div style="display:flex;gap:8px;"><button data-action="save-project-edit" data-id="' + id + '" class="btn-green" style="font-size:12px;">Save</button><button data-action="cancel-project-edit" data-id="' + id + '" class="btn-ghost" style="font-size:12px;">Cancel</button></div>'
89
- + '</div>'
90
- + '<div id="proj-pm-status-' + id + '" style="display:none;margin-top:10px;font-size:12px;padding:8px 12px;background:rgba(99,102,241,0.08);border-radius:6px;border:1px solid rgba(99,102,241,0.2);color:#a5b4fc;"></div>'
91
- + '<div id="rm-editor-' + id + '" style="display:none;margin-top:14px;">'
92
- + '<div style="display:flex;gap:8px;align-items:center;margin-bottom:8px;flex-wrap:wrap;">'
93
- + '<span class="field-label" style="margin:0;">ROADMAP</span>'
94
- + '<span class="meta" style="font-family:monospace;">' + escHtml(p.roadmapFile) + '</span>'
95
- + '<div style="margin-left:auto;display:flex;gap:6px;">'
96
- + '<button data-action="add-item" data-id="' + id + '" style="font-size:11px;padding:3px 10px;background:var(--green);color:#000;">+ Add item</button>'
97
- + '<button data-action="skip-next" data-id="' + id + '" style="font-size:11px;padding:3px 10px;background:var(--yellow);color:#000;">⏭ Skip next</button>'
98
- + '<button data-action="reset-failed" data-id="' + id + '" style="font-size:11px;padding:3px 10px;" class="btn-ghost">↩ Reset failed</button>'
99
- + '<button data-action="save-roadmap" data-id="' + id + '" style="font-size:11px;padding:3px 10px;background:var(--accent);color:#000;">💾 Save</button>'
100
- + '<button data-action="close-editor" data-id="' + id + '" style="font-size:11px;padding:3px 10px;" class="btn-ghost">✕</button>'
101
- + '</div>'
102
- + '</div>'
103
- + '<div style="display:flex;gap:8px;margin-bottom:8px;">'
104
- + '<input id="rm-add-' + id + '" type="text" placeholder="New item text… (Enter to add)" style="flex:1;font-size:13px;" data-rm-add-id="' + id + '" />'
105
- + '</div>'
106
- + '<textarea id="rm-ta-' + id + '" rows="16" class="rm-textarea" spellcheck="false"></textarea>'
107
- + '<div id="rm-status-' + id + '" class="meta" style="margin-top:6px;min-height:16px;"></div>'
108
- + '</div>'
109
- + '</div>';
110
- }).join('');
111
-
112
- list.querySelectorAll('[data-rm-add-id]').forEach(inp => {
113
- inp.addEventListener('keydown', e => { if (e.key === 'Enter') addRoadmapItem(inp.dataset.rmAddId); });
114
- });
115
-
116
- } catch(e) { list.innerHTML = '<div class="meta" style="padding:20px;color:var(--red-hi);">Failed to load projects: ' + escHtml(e.message) + '</div>'; }
117
- }
118
-
119
- export function toggleProjectEdit(projectId) {
120
- const viewEl = document.getElementById('proj-view-' + projectId);
121
- const editEl = document.getElementById('proj-edit-' + projectId);
122
- if (!viewEl || !editEl) return;
123
- const isEditing = editEl.style.display !== 'none';
124
- viewEl.style.display = isEditing ? '' : 'none';
125
- editEl.style.display = isEditing ? 'none' : 'block';
126
- }
127
-
128
- export async function saveProjectEdit(projectId) {
129
- const name = document.getElementById('proj-name-' + projectId)?.value?.trim();
130
- const description = document.getElementById('proj-desc-' + projectId)?.value?.trim();
131
- const outputDir = document.getElementById('proj-dir-' + projectId)?.value?.trim();
132
- if (!name) { showNotification('Project name is required', true); return; }
133
- try {
134
- await postJSON('/api/projects/update', { projectId, name, description, outputDir });
135
- showNotification('Project saved');
136
- toggleProjectEdit(projectId);
137
- loadProjects();
138
- } catch(e) { showNotification('Failed: ' + e.message, true); }
139
- }
140
-
141
- export function initProjectsList(deps) {
142
- const el = document.getElementById('projectsList');
143
- if (!el) return;
144
- el.addEventListener('click', e => {
145
- const btn = e.target.closest('[data-action]');
146
- if (!btn) return;
147
- const id = btn.dataset.id;
148
- const proj = state.projectsData[id];
149
- switch (btn.dataset.action) {
150
- case 'pm-toggle': proj && proj.running ? stopProjectPMLoop(id) : startProjectPMLoop(id); break;
151
- case 'open-build': openProjectInBuild(id, deps); break;
152
- case 'edit-roadmap': proj && openRoadmapEditor(id, proj.roadmapFile); break;
153
- case 'retry-failed': proj && retryFailed(proj.roadmapFile); break;
154
- case 'delete': deleteProject(id); break;
155
- case 'chat-project': {
156
- deps.showChat();
157
- autoSelectChatProject(id);
158
- document.getElementById('chatInput')?.focus();
159
- break;
160
- }
161
- case 'toggle-auto-advance': {
162
- const checked = btn.checked;
163
- postJSON('/api/projects/update', { projectId: id, autoAdvance: checked })
164
- .then(() => {
165
- if (state.projectsData[id]) state.projectsData[id].autoAdvance = checked;
166
- showNotification('Auto-advance ' + (checked ? 'enabled' : 'disabled') + ' for ' + (proj?.name || id));
167
- })
168
- .catch(e => { showNotification('Failed: ' + e.message, true); btn.checked = !checked; });
169
- return;
170
- }
171
- case 'edit': toggleProjectEdit(id); break;
172
- case 'save-project-edit': saveProjectEdit(id); break;
173
- case 'cancel-project-edit': toggleProjectEdit(id); break;
174
- case 'add-item': addRoadmapItem(id); break;
175
- case 'skip-next': skipNextItem(id); break;
176
- case 'reset-failed': resetAllFailed(id); break;
177
- case 'save-roadmap': saveRoadmap(id); break;
178
- case 'close-editor': closeRoadmapEditor(id); break;
179
- }
180
- });
181
- }
182
-
183
- // ── Chat project dropdown ─────────────────────────────────────────────────────
184
-
185
- const CHAT_ACTIVE_PROJECT_KEY = 'crewswarm_chat_active_project_id';
186
-
187
- export function getStoredChatProjectId() {
188
- try { return localStorage.getItem(CHAT_ACTIVE_PROJECT_KEY) || ''; } catch { return ''; }
189
- }
190
- export function setStoredChatProjectId(id) {
191
- try { if (id) localStorage.setItem(CHAT_ACTIVE_PROJECT_KEY, id); else localStorage.removeItem(CHAT_ACTIVE_PROJECT_KEY); } catch {}
192
- }
193
-
194
- function persistSharedActiveProjectId(id) {
195
- const normalizedId =
196
- id && String(id).trim() && id !== "undefined" ? String(id).trim() : "general";
197
- return fetch("/api/ui/active-project", {
198
- method: "POST",
199
- headers: { "content-type": "application/json" },
200
- body: JSON.stringify({ projectId: normalizedId }),
201
- }).catch(() => {});
202
- }
203
-
204
- export function populateChatProjectDropdown(projects) {
205
- const tabsContainer = document.getElementById('chatProjectTabs');
206
- if (!tabsContainer) return;
207
-
208
- const prev = getStoredChatProjectId() || state.chatActiveProjectId || 'general';
209
-
210
- // Clear existing tabs except General (first child)
211
- while (tabsContainer.children.length > 1) {
212
- tabsContainer.removeChild(tabsContainer.lastChild);
213
- }
214
-
215
- // Deduplicate by ID; ensure every project has a stable id (backend should send it; fallback from name)
216
- const seen = new Set();
217
- const uniqueProjects = (projects || []).filter(p => {
218
- const id = p.id || (p.name && p.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''));
219
- if (!id || seen.has(id)) return false;
220
- seen.add(id);
221
- return true;
222
- }).map(p => ({ ...p, id: p.id || (p.name && p.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')) }));
223
-
224
- // Add project tabs (only projects with a valid id)
225
- uniqueProjects.forEach(p => {
226
- if (!p.id) return;
227
- const tab = document.createElement('button');
228
- tab.className = 'project-tab';
229
- tab.dataset.projectId = p.id;
230
- tab.textContent = `📁 ${p.name || p.id}`;
231
- tab.onclick = () => window.selectProjectTab(p.id);
232
- tabsContainer.appendChild(tab);
233
- });
234
-
235
- console.log('[Projects] Populated tabs:', uniqueProjects.length, 'projects');
236
-
237
- // Restore the active tab styling without forcing navigation back to Chat.
238
- const availableIds = new Set(['general', ...uniqueProjects.map((p) => p.id).filter(Boolean)]);
239
- const activeProjectId = availableIds.has(prev) ? prev : 'general';
240
- state.chatActiveProjectId = activeProjectId;
241
- setStoredChatProjectId(activeProjectId);
242
- persistSharedActiveProjectId(activeProjectId);
243
- Array.from(tabsContainer.children).forEach((tab) => {
244
- tab.classList.toggle('active', tab.dataset.projectId === activeProjectId);
245
- });
246
- }
247
-
248
-
249
- export function onChatProjectChange() {
250
- // Legacy: now handled by tab clicks
251
- const sel = document.getElementById('chatProjectSelect');
252
- if (sel) {
253
- state.chatActiveProjectId = sel.value;
254
- setStoredChatProjectId(state.chatActiveProjectId);
255
- persistSharedActiveProjectId(state.chatActiveProjectId);
256
- updateChatProjectHint();
257
- }
258
- }
259
-
260
- export function updateChatProjectHint() {
261
- const hint = document.getElementById('chatProjectHint');
262
- if (!hint) return;
263
- if (state.chatActiveProjectId && state.projectsData[state.chatActiveProjectId]) {
264
- const p = state.projectsData[state.chatActiveProjectId];
265
- hint.textContent = p.outputDir || '';
266
- hint.style.display = p.outputDir ? 'block' : 'none';
267
- } else {
268
- hint.style.display = 'none';
269
- }
270
- }
271
-
272
- export function autoSelectChatProject(projectId) {
273
- state.chatActiveProjectId = projectId;
274
- setStoredChatProjectId(projectId);
275
- persistSharedActiveProjectId(projectId);
276
- const sel = document.getElementById('chatProjectSelect');
277
- if (sel && sel.querySelector('option[value="' + projectId + '"]')) {
278
- sel.value = projectId;
279
- updateChatProjectHint();
280
- }
281
- }
282
-
283
- // ── PM loop controls ──────────────────────────────────────────────────────────
284
-
285
- export async function resumeProject(projectId) {
286
- try {
287
- const resp = await fetch('/api/pm-loop/start', { method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({ projectId }) });
288
- const r = await resp.json();
289
- if (r.alreadyRunning) { showNotification('PM Loop already running (pid ' + r.pid + ')', true); return; }
290
- showNotification('PM Loop started for project ' + projectId + ' (pid ' + r.pid + ')');
291
- setTimeout(loadProjects, 3000);
292
- } catch(e) { showNotification('Failed: ' + e.message, true); }
293
- }
294
-
295
- export async function stopProjectPMLoop(projectId) {
296
- try {
297
- await postJSON('/api/pm-loop/stop', { projectId });
298
- showNotification('Stop signal sent — PM will finish current task then halt.');
299
- const statusEl = document.getElementById('proj-pm-status-' + projectId);
300
- if (statusEl) { statusEl.style.display = 'block'; statusEl.textContent = '⛔ Stopping after current task…'; }
301
- setTimeout(loadProjects, 3000);
302
- } catch(e) { showNotification('Stop failed: ' + e.message, true); }
303
- }
304
-
305
- export async function startProjectPMLoop(projectId) {
306
- const statusEl = document.getElementById('proj-pm-status-' + projectId);
307
- try {
308
- if (statusEl) { statusEl.style.display = 'block'; statusEl.textContent = '⚙ Starting PM Loop…'; }
309
- const r = await postJSON('/api/pm-loop/start', { projectId });
310
- if (r.alreadyRunning) {
311
- showNotification('PM Loop already running (pid ' + r.pid + ')', true);
312
- if (statusEl) statusEl.textContent = '▶ Already running (pid ' + r.pid + ')';
313
- return;
314
- }
315
- showNotification('PM Loop started (pid ' + r.pid + ')');
316
- if (statusEl) statusEl.textContent = '▶ Running (pid ' + r.pid + ') — check Build tab for live log';
317
- setTimeout(loadProjects, 3000);
318
- } catch(e) {
319
- showNotification('Start failed: ' + e.message, true);
320
- if (statusEl) statusEl.style.display = 'none';
321
- }
322
- }
323
-
324
- export async function deleteProject(projectId) {
325
- const proj = state.projectsData[projectId];
326
- const name = proj ? proj.name : projectId;
327
- if (!confirm('Remove "' + name + '" from the dashboard registry?\n\nFiles on disk are NOT deleted.')) return;
328
- try {
329
- await postJSON('/api/projects/delete', { projectId });
330
- showNotification('Project "' + name + '" removed from dashboard.');
331
- loadProjects();
332
- } catch(e) { showNotification('Delete failed: ' + e.message, true); }
333
- }
334
-
335
- export function openProjectInBuild(projectId, deps) {
336
- deps.showBuild();
337
- loadBuildProjectPicker().then(() => {
338
- const sel = document.getElementById('buildProjectPicker');
339
- if (sel) { sel.value = projectId; onBuildProjectChange(); }
340
- });
341
- }
342
-
343
- // ── Build tab project picker ──────────────────────────────────────────────────
344
-
345
- let _buildProjects = {};
346
-
347
- export function getBuildProjectById(projectId) {
348
- return _buildProjects[projectId] || null;
349
- }
350
-
351
- export async function loadBuildProjectPicker() {
352
- try {
353
- const data = await getJSON('/api/projects');
354
- _buildProjects = {};
355
- const sel = document.getElementById('buildProjectPicker');
356
- const cur = sel ? sel.value : '';
357
- if (!sel) return;
358
- sel.innerHTML = '<option value="">— No project (use defaults) —</option>';
359
- (data.projects || []).forEach(p => {
360
- _buildProjects[p.id] = p;
361
- const opt = document.createElement('option');
362
- opt.value = p.id;
363
- opt.textContent = p.name + (p.running ? ' ▶' : '') + ' (' + p.roadmap.pending + ' pending)';
364
- if (p.id === cur) opt.selected = true;
365
- sel.appendChild(opt);
366
- });
367
- onBuildProjectChange();
368
- } catch(e) { /* ignore */ }
369
- }
370
-
371
- export function onBuildProjectChange() {
372
- const sel = document.getElementById('buildProjectPicker');
373
- const info = document.getElementById('buildProjectInfo');
374
- const label = document.getElementById('pmLoopProjectLabel');
375
- const phasedLabel = document.getElementById('phasedProgressLabel');
376
- const proj = _buildProjects[sel ? sel.value : ''];
377
- if (proj) {
378
- info.style.display = 'block';
379
- info.innerHTML =
380
- '<b>' + proj.name + '</b><br>' +
381
- 'Output: ' + proj.outputDir + '<br>' +
382
- 'Roadmap: ' + proj.roadmapFile + '<br>' +
383
- 'Tasks: ' + proj.roadmap.done + ' done · ' + proj.roadmap.pending + ' pending · ' + proj.roadmap.failed + ' failed' +
384
- (proj.running ? '<br><span style="color:var(--purple);">▶ PM Loop is running</span>' : '');
385
- if (label) label.innerHTML =
386
- '<b style="color:var(--accent);">▶ ' + proj.name + '</b>' +
387
- ' &nbsp;·&nbsp; ' + proj.roadmap.done + ' done · ' + proj.roadmap.pending + ' pending' +
388
- (proj.running ? ' &nbsp;<span style="color:var(--green-hi); font-weight:600;">● running</span>' : '');
389
- if (phasedLabel) phasedLabel.textContent = '▶ ' + proj.name;
390
- } else {
391
- info.style.display = 'none';
392
- if (label) label.innerHTML = '← Select a project above';
393
- if (phasedLabel) phasedLabel.textContent = 'All projects (no project selected)';
394
- }
395
- // Reload phased progress with new project filter
396
- loadPhasedProgress();
397
- }
398
-
399
- // ── Stop build ────────────────────────────────────────────────────────────────
400
-
401
- export async function stopBuild() {
402
- try {
403
- await postJSON('/api/build/stop', {});
404
- showNotification('Build stop signal sent');
405
- document.getElementById('stopBuildBtn').style.display = 'none';
406
- document.getElementById('runBuildBtn').style.display = '';
407
- document.getElementById('buildStatus').textContent = '';
408
- } catch(e) { showNotification('Stop failed: ' + e.message, true); }
409
- }
410
-
411
- export async function stopContinuousBuild() {
412
- try {
413
- await postJSON('/api/continuous-build/stop', {});
414
- showNotification('Continuous build stop signal sent');
415
- document.getElementById('stopContinuousBtn').style.display = 'none';
416
- document.getElementById('continuousBuildBtn').style.display = '';
417
- } catch(e) { showNotification('Stop failed: ' + e.message, true); }
418
- }
419
-
420
- export async function retryFailed(roadmapFile) {
421
- if (!confirm('Reset all [!] failed items back to [ ] pending so the PM Loop retries them?')) return;
422
- try {
423
- const r = await postJSON('/api/roadmap/retry-failed', { roadmapFile });
424
- if (r.count === 0) { showNotification('No failed items found in roadmap', true); return; }
425
- showNotification('↩ ' + r.count + ' failed item' + (r.count !== 1 ? 's' : '') + ' reset — click Resume to retry');
426
- await loadProjects();
427
- } catch(e) { showNotification('Retry failed: ' + e.message, true); }
428
- }
429
-
430
- // ── Roadmap editor ────────────────────────────────────────────────────────────
431
-
432
- const _roadmapFiles = {};
433
-
434
- export async function openRoadmapEditor(projectId, roadmapFile) {
435
- _roadmapFiles[projectId] = roadmapFile;
436
- const panel = document.getElementById('rm-editor-' + projectId);
437
- const ta = document.getElementById('rm-ta-' + projectId);
438
- const btn = document.getElementById('roadmap-btn-' + projectId);
439
- if (!panel || !ta) return;
440
- if (panel.style.display !== 'none') { closeRoadmapEditor(projectId); return; }
441
- panel.style.display = 'block';
442
- if (btn) btn.textContent = '📋 Editing…';
443
- ta.value = 'Loading…';
444
- try {
445
- const r = await postJSON('/api/roadmap/read', { roadmapFile });
446
- ta.value = r.content || '';
447
- setRmStatus(projectId, 'Loaded · ' + (r.content || '').split('\n').length + ' lines');
448
- } catch(e) { ta.value = ''; setRmStatus(projectId, 'Error: ' + e.message, true); }
449
- }
450
-
451
- export function closeRoadmapEditor(projectId) {
452
- const panel = document.getElementById('rm-editor-' + projectId);
453
- const btn = document.getElementById('roadmap-btn-' + projectId);
454
- if (panel) panel.style.display = 'none';
455
- if (btn) btn.textContent = '📋 Edit Roadmap';
456
- }
457
-
458
- function setRmStatus(projectId, msg, isErr) {
459
- const el = document.getElementById('rm-status-' + projectId);
460
- if (!el) return;
461
- el.textContent = msg;
462
- el.style.color = isErr ? 'var(--red)' : 'var(--text-2)';
463
- }
464
-
465
- export async function saveRoadmap(projectId) {
466
- const ta = document.getElementById('rm-ta-' + projectId);
467
- const roadmapFile = _roadmapFiles[projectId];
468
- if (!ta || !roadmapFile) return;
469
- try {
470
- await postJSON('/api/roadmap/write', { roadmapFile, content: ta.value });
471
- setRmStatus(projectId, '✓ Saved — ' + new Date().toLocaleTimeString());
472
- showNotification('Roadmap saved');
473
- setTimeout(loadProjects, 800);
474
- } catch(e) { setRmStatus(projectId, 'Save failed: ' + e.message, true); }
475
- }
476
-
477
- export function addRoadmapItem(projectId) {
478
- const ta = document.getElementById('rm-ta-' + projectId);
479
- const input = document.getElementById('rm-add-' + projectId);
480
- if (!ta) return;
481
- const text = (input ? input.value.trim() : '') || 'New task';
482
- if (!text) return;
483
- const line = '- [ ] ' + text;
484
- ta.value = ta.value.trimEnd() + '\n' + line + '\n';
485
- ta.scrollTop = ta.scrollHeight;
486
- if (input) input.value = '';
487
- setRmStatus(projectId, 'Item added — click 💾 Save to persist');
488
- }
489
-
490
- export function skipNextItem(projectId) {
491
- const ta = document.getElementById('rm-ta-' + projectId);
492
- if (!ta) return;
493
- const lines = ta.value.split('\n');
494
- let skipped = false;
495
- for (let i = 0; i < lines.length; i++) {
496
- if (/^- \[ \]/.test(lines[i])) {
497
- lines[i] = lines[i].replace('- [ ]', '- [x]') + ' ✓ skipped';
498
- skipped = true;
499
- break;
500
- }
501
- }
502
- if (skipped) {
503
- ta.value = lines.join('\n');
504
- setRmStatus(projectId, 'Next pending item skipped — click 💾 Save to persist');
505
- } else {
506
- setRmStatus(projectId, 'No pending items to skip');
507
- }
508
- }
509
-
510
- export async function resetAllFailed(projectId) {
511
- const ta = document.getElementById('rm-ta-' + projectId);
512
- if (!ta) return;
513
- const before = (ta.value.match(/\[!\]/g) || []).length;
514
- if (!before) { setRmStatus(projectId, 'No failed items to reset'); return; }
515
- ta.value = ta.value
516
- .split('\n')
517
- .map(l => l.replace(/\[!\]/, '[ ]').replace(/\s+✗\s+\d+:\d+:\d+/g, ''))
518
- .join('\n');
519
- setRmStatus(projectId, before + ' failed item(s) reset — click 💾 Save to persist');
520
- }
521
-
522
- // ── Build tab ─────────────────────────────────────────────────────────────────
523
-
524
- export async function loadPhasedProgress() {
525
- const box = document.getElementById('phasedProgress');
526
- if (!box) return;
527
- const projectId = document.getElementById('buildProjectPicker')?.value || '';
528
- const label = document.getElementById('phasedProgressLabel');
529
- try {
530
- const url = '/api/phased-progress' + (projectId ? '?projectId=' + encodeURIComponent(projectId) : '');
531
- const data = await getJSON(url);
532
- const scopeText = projectId ? 'This project' : 'All projects (no project selected)';
533
- if (label) label.textContent = scopeText;
534
- if (!data.length) {
535
- if (projectId) {
536
- box.innerHTML = '<div style="padding:20px;color:var(--text-3);text-align:center;">No phased builds yet for this project.<br><br>💡 Tip: Click <b>▶ Run Build</b> or <b>🔁 Build Until Done</b> above to start.</div>';
537
- } else {
538
- box.textContent = 'No phased runs yet.';
539
- }
540
- return;
541
- }
542
- box.innerHTML = data.map(e => {
543
- const phase = e.phase || '?';
544
- const agent = e.agent || '?';
545
- const task = (e.task || '').slice(0, 50) + ((e.task || '').length > 50 ? '...' : '');
546
- const status = e.status === 'completed' ? '✅' : '❌';
547
- const dur = e.duration_s != null ? e.duration_s + 's' : '';
548
-
549
- // Add timestamp in human-readable format
550
- let timeStr = '';
551
- if (e.timestamp) {
552
- const d = new Date(e.timestamp);
553
- const hours = d.getHours();
554
- const mins = d.getMinutes().toString().padStart(2, '0');
555
- const ampm = hours >= 12 ? 'PM' : 'AM';
556
- const hrs12 = hours % 12 || 12;
557
- timeStr = `${hrs12}:${mins} ${ampm}`;
558
- }
559
-
560
- // Add project name if showing all projects
561
- const projName = !projectId && e.projectId && _buildProjects[e.projectId] ?
562
- `<span style="color:var(--text-3);font-size:10px;"> · ${_buildProjects[e.projectId].name}</span>` : '';
563
-
564
- return `<div style="margin-bottom:4px;">${status} [${phase}] ${agent}: ${task} ${dur}${timeStr ? ` <span style="color:var(--text-3);font-size:10px;">· ${timeStr}</span>` : ''}${projName}</div>`;
565
- }).join('');
566
- box.scrollTop = box.scrollHeight;
567
- } catch (e) { box.textContent = 'Could not load progress.'; }
568
- }
569
-
570
- export async function runBuild() {
571
- const req = document.getElementById('buildRequirement').value.trim();
572
- if (!req) { showNotification('Enter a requirement', true); return; }
573
- const status = document.getElementById('buildStatus');
574
- const btn = document.getElementById('runBuildBtn');
575
- const stopBtn = document.getElementById('stopBuildBtn');
576
- const projectId = document.getElementById('buildProjectPicker')?.value || '';
577
- try {
578
- status.textContent = 'Starting...';
579
- btn.disabled = true;
580
- const r = await postJSON('/api/build', { requirement: req, projectId });
581
- showNotification('Build started (pid ' + r.pid + '). Watch RT Messages or Phased Progress.');
582
- status.textContent = 'Running (pid ' + r.pid + ')';
583
- btn.style.display = 'none';
584
- if (stopBtn) stopBtn.style.display = '';
585
- setTimeout(() => {
586
- status.textContent = '';
587
- btn.disabled = false;
588
- btn.style.display = '';
589
- if (stopBtn) stopBtn.style.display = 'none';
590
- }, 120000);
591
- } catch (e) { showNotification('Build failed: ' + e.message, true); status.textContent = ''; btn.disabled = false; }
592
- }
593
-
594
- export async function enhancePrompt() {
595
- const ta = document.getElementById('buildRequirement');
596
- const raw = ta.value.trim();
597
- const btn = document.getElementById('enhancePromptBtn');
598
- if (!raw) { showNotification('Type an idea first', true); return; }
599
- try {
600
- btn.disabled = true;
601
- document.getElementById('buildStatus').textContent = 'Enhancing...';
602
- const r = await postJSON('/api/enhance-prompt', { text: raw });
603
- if (r.enhanced) { ta.value = r.enhanced; showNotification('Prompt updated'); }
604
- else { showNotification(r.error || 'No result', true); }
605
- } catch (e) { showNotification('Enhance failed: ' + e.message, true); }
606
- finally { btn.disabled = false; document.getElementById('buildStatus').textContent = ''; }
607
- }
608
-
609
- export async function continuousBuildRun() {
610
- const req = document.getElementById('buildRequirement').value.trim();
611
- if (!req) { showNotification('Enter a requirement first', true); return; }
612
- const status = document.getElementById('buildStatus');
613
- const btn = document.getElementById('continuousBuildBtn');
614
- const stopBtn = document.getElementById('stopContinuousBtn');
615
- const logBox = document.getElementById('buildLiveLog');
616
- const projectId = document.getElementById('buildProjectPicker')?.value || '';
617
- try {
618
- status.textContent = 'Running continuously...';
619
- btn.disabled = true;
620
- btn.style.display = 'none';
621
- if (stopBtn) stopBtn.style.display = '';
622
- logBox.style.display = 'block';
623
- logBox.textContent = '⚙ Starting continuous build...\n';
624
- const r = await postJSON('/api/continuous-build', { requirement: req, projectId });
625
- logBox.textContent += '✅ Spawned (pid ' + r.pid + '). Checking progress below and in RT Messages tab.\n';
626
- showNotification('Continuous build started — will keep going until all sections are done.');
627
- status.textContent = 'Running (continuous)';
628
- const poller = setInterval(async () => {
629
- try {
630
- const lg = await fetch('/api/continuous-build/log').then(r2 => r2.json());
631
- if (lg.lines && lg.lines.length) {
632
- logBox.textContent = lg.lines.map(l => {
633
- const icon = l.status === 'completed' ? '✅' : l.status === 'failed' ? '❌' : l.status === 'done' ? '🏁' : '·';
634
- return `${icon} [rd${l.round||'?'}] ${l.agent ? l.agent+': ' : ''}${l.task || l.status || JSON.stringify(l)}`;
635
- }).join('\n');
636
- logBox.scrollTop = logBox.scrollHeight;
637
- const last = lg.lines[lg.lines.length - 1];
638
- if (last && last.status === 'done') {
639
- clearInterval(poller);
640
- btn.disabled = false;
641
- btn.style.display = '';
642
- if (stopBtn) stopBtn.style.display = 'none';
643
- status.textContent = '🏁 Done!';
644
- showNotification('🏁 Continuous build complete!');
645
- }
646
- }
647
- } catch(_){}
648
- }, 4000);
649
- setTimeout(() => {
650
- clearInterval(poller);
651
- btn.disabled = false;
652
- btn.style.display = '';
653
- if (stopBtn) stopBtn.style.display = 'none';
654
- if (status.textContent.includes('continuous')) status.textContent = '';
655
- }, 30 * 60 * 1000);
656
- } catch (e) {
657
- showNotification('Continuous build failed: ' + e.message, true);
658
- status.textContent = '';
659
- btn.disabled = false;
660
- btn.style.display = '';
661
- if (stopBtn) stopBtn.style.display = 'none';
662
- }
663
- }