@songsid/agend 0.0.1

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 (232) hide show
  1. package/README.md +210 -0
  2. package/README.zh-TW.md +134 -0
  3. package/dist/access-path.d.ts +10 -0
  4. package/dist/access-path.js +32 -0
  5. package/dist/access-path.js.map +1 -0
  6. package/dist/adapter-world.d.ts +25 -0
  7. package/dist/adapter-world.js +41 -0
  8. package/dist/adapter-world.js.map +1 -0
  9. package/dist/agent-cli-instructions.md +50 -0
  10. package/dist/agent-cli.d.ts +2 -0
  11. package/dist/agent-cli.js +200 -0
  12. package/dist/agent-cli.js.map +1 -0
  13. package/dist/agent-endpoint.d.ts +25 -0
  14. package/dist/agent-endpoint.js +162 -0
  15. package/dist/agent-endpoint.js.map +1 -0
  16. package/dist/backend/antigravity.d.ts +17 -0
  17. package/dist/backend/antigravity.js +98 -0
  18. package/dist/backend/antigravity.js.map +1 -0
  19. package/dist/backend/claude-code.d.ts +23 -0
  20. package/dist/backend/claude-code.js +171 -0
  21. package/dist/backend/claude-code.js.map +1 -0
  22. package/dist/backend/codex.d.ts +18 -0
  23. package/dist/backend/codex.js +160 -0
  24. package/dist/backend/codex.js.map +1 -0
  25. package/dist/backend/factory.d.ts +2 -0
  26. package/dist/backend/factory.js +28 -0
  27. package/dist/backend/factory.js.map +1 -0
  28. package/dist/backend/gemini-cli.d.ts +17 -0
  29. package/dist/backend/gemini-cli.js +163 -0
  30. package/dist/backend/gemini-cli.js.map +1 -0
  31. package/dist/backend/index.d.ts +7 -0
  32. package/dist/backend/index.js +7 -0
  33. package/dist/backend/index.js.map +1 -0
  34. package/dist/backend/kiro.d.ts +17 -0
  35. package/dist/backend/kiro.js +147 -0
  36. package/dist/backend/kiro.js.map +1 -0
  37. package/dist/backend/marker-utils.d.ts +13 -0
  38. package/dist/backend/marker-utils.js +64 -0
  39. package/dist/backend/marker-utils.js.map +1 -0
  40. package/dist/backend/mock.d.ts +25 -0
  41. package/dist/backend/mock.js +85 -0
  42. package/dist/backend/mock.js.map +1 -0
  43. package/dist/backend/opencode.d.ts +16 -0
  44. package/dist/backend/opencode.js +136 -0
  45. package/dist/backend/opencode.js.map +1 -0
  46. package/dist/backend/types.d.ts +86 -0
  47. package/dist/backend/types.js +33 -0
  48. package/dist/backend/types.js.map +1 -0
  49. package/dist/channel/access-manager.d.ts +18 -0
  50. package/dist/channel/access-manager.js +153 -0
  51. package/dist/channel/access-manager.js.map +1 -0
  52. package/dist/channel/adapters/telegram.d.ts +63 -0
  53. package/dist/channel/adapters/telegram.js +646 -0
  54. package/dist/channel/adapters/telegram.js.map +1 -0
  55. package/dist/channel/attachment-handler.d.ts +15 -0
  56. package/dist/channel/attachment-handler.js +88 -0
  57. package/dist/channel/attachment-handler.js.map +1 -0
  58. package/dist/channel/factory.d.ts +12 -0
  59. package/dist/channel/factory.js +67 -0
  60. package/dist/channel/factory.js.map +1 -0
  61. package/dist/channel/ipc-bridge.d.ts +26 -0
  62. package/dist/channel/ipc-bridge.js +220 -0
  63. package/dist/channel/ipc-bridge.js.map +1 -0
  64. package/dist/channel/mcp-server.d.ts +10 -0
  65. package/dist/channel/mcp-server.js +288 -0
  66. package/dist/channel/mcp-server.js.map +1 -0
  67. package/dist/channel/mcp-tools.d.ts +17 -0
  68. package/dist/channel/mcp-tools.js +110 -0
  69. package/dist/channel/mcp-tools.js.map +1 -0
  70. package/dist/channel/message-bus.d.ts +17 -0
  71. package/dist/channel/message-bus.js +86 -0
  72. package/dist/channel/message-bus.js.map +1 -0
  73. package/dist/channel/message-queue.d.ts +39 -0
  74. package/dist/channel/message-queue.js +253 -0
  75. package/dist/channel/message-queue.js.map +1 -0
  76. package/dist/channel/tool-router.d.ts +6 -0
  77. package/dist/channel/tool-router.js +75 -0
  78. package/dist/channel/tool-router.js.map +1 -0
  79. package/dist/channel/tool-tracker.d.ts +13 -0
  80. package/dist/channel/tool-tracker.js +58 -0
  81. package/dist/channel/tool-tracker.js.map +1 -0
  82. package/dist/channel/types.d.ts +118 -0
  83. package/dist/channel/types.js +2 -0
  84. package/dist/channel/types.js.map +1 -0
  85. package/dist/chat-export.d.ts +4 -0
  86. package/dist/chat-export.js +91 -0
  87. package/dist/chat-export.js.map +1 -0
  88. package/dist/classic-channel-manager.d.ts +59 -0
  89. package/dist/classic-channel-manager.js +193 -0
  90. package/dist/classic-channel-manager.js.map +1 -0
  91. package/dist/cli.d.ts +2 -0
  92. package/dist/cli.js +1833 -0
  93. package/dist/cli.js.map +1 -0
  94. package/dist/config.d.ts +9 -0
  95. package/dist/config.js +118 -0
  96. package/dist/config.js.map +1 -0
  97. package/dist/context-guardian.d.ts +26 -0
  98. package/dist/context-guardian.js +73 -0
  99. package/dist/context-guardian.js.map +1 -0
  100. package/dist/cost-guard.d.ts +36 -0
  101. package/dist/cost-guard.js +147 -0
  102. package/dist/cost-guard.js.map +1 -0
  103. package/dist/daemon-entry.d.ts +1 -0
  104. package/dist/daemon-entry.js +29 -0
  105. package/dist/daemon-entry.js.map +1 -0
  106. package/dist/daemon.d.ts +152 -0
  107. package/dist/daemon.js +1714 -0
  108. package/dist/daemon.js.map +1 -0
  109. package/dist/daily-summary.d.ts +13 -0
  110. package/dist/daily-summary.js +55 -0
  111. package/dist/daily-summary.js.map +1 -0
  112. package/dist/event-log.d.ts +36 -0
  113. package/dist/event-log.js +100 -0
  114. package/dist/event-log.js.map +1 -0
  115. package/dist/export-import.d.ts +2 -0
  116. package/dist/export-import.js +162 -0
  117. package/dist/export-import.js.map +1 -0
  118. package/dist/fleet-context.d.ts +61 -0
  119. package/dist/fleet-context.js +4 -0
  120. package/dist/fleet-context.js.map +1 -0
  121. package/dist/fleet-dashboard-html.d.ts +6 -0
  122. package/dist/fleet-dashboard-html.js +443 -0
  123. package/dist/fleet-dashboard-html.js.map +1 -0
  124. package/dist/fleet-health-server.d.ts +35 -0
  125. package/dist/fleet-health-server.js +290 -0
  126. package/dist/fleet-health-server.js.map +1 -0
  127. package/dist/fleet-instructions.d.ts +5 -0
  128. package/dist/fleet-instructions.js +161 -0
  129. package/dist/fleet-instructions.js.map +1 -0
  130. package/dist/fleet-manager.d.ts +212 -0
  131. package/dist/fleet-manager.js +3655 -0
  132. package/dist/fleet-manager.js.map +1 -0
  133. package/dist/fleet-rpc-handlers.d.ts +42 -0
  134. package/dist/fleet-rpc-handlers.js +356 -0
  135. package/dist/fleet-rpc-handlers.js.map +1 -0
  136. package/dist/fleet-system-prompt.d.ts +11 -0
  137. package/dist/fleet-system-prompt.js +61 -0
  138. package/dist/fleet-system-prompt.js.map +1 -0
  139. package/dist/general-knowledge/skills.md +177 -0
  140. package/dist/hang-detector.d.ts +16 -0
  141. package/dist/hang-detector.js +53 -0
  142. package/dist/hang-detector.js.map +1 -0
  143. package/dist/index.d.ts +8 -0
  144. package/dist/index.js +6 -0
  145. package/dist/index.js.map +1 -0
  146. package/dist/instance-lifecycle.d.ts +90 -0
  147. package/dist/instance-lifecycle.js +592 -0
  148. package/dist/instance-lifecycle.js.map +1 -0
  149. package/dist/instructions.d.ts +15 -0
  150. package/dist/instructions.js +90 -0
  151. package/dist/instructions.js.map +1 -0
  152. package/dist/logger.d.ts +7 -0
  153. package/dist/logger.js +84 -0
  154. package/dist/logger.js.map +1 -0
  155. package/dist/outbound-handlers.d.ts +51 -0
  156. package/dist/outbound-handlers.js +739 -0
  157. package/dist/outbound-handlers.js.map +1 -0
  158. package/dist/outbound-schemas.d.ts +238 -0
  159. package/dist/outbound-schemas.js +248 -0
  160. package/dist/outbound-schemas.js.map +1 -0
  161. package/dist/paths.d.ts +10 -0
  162. package/dist/paths.js +42 -0
  163. package/dist/paths.js.map +1 -0
  164. package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
  165. package/dist/quickstart.d.ts +1 -0
  166. package/dist/quickstart.js +595 -0
  167. package/dist/quickstart.js.map +1 -0
  168. package/dist/routing-engine.d.ts +22 -0
  169. package/dist/routing-engine.js +44 -0
  170. package/dist/routing-engine.js.map +1 -0
  171. package/dist/safe-async.d.ts +6 -0
  172. package/dist/safe-async.js +20 -0
  173. package/dist/safe-async.js.map +1 -0
  174. package/dist/scheduler/db.d.ts +37 -0
  175. package/dist/scheduler/db.js +360 -0
  176. package/dist/scheduler/db.js.map +1 -0
  177. package/dist/scheduler/db.test.d.ts +1 -0
  178. package/dist/scheduler/db.test.js +92 -0
  179. package/dist/scheduler/db.test.js.map +1 -0
  180. package/dist/scheduler/index.d.ts +4 -0
  181. package/dist/scheduler/index.js +4 -0
  182. package/dist/scheduler/index.js.map +1 -0
  183. package/dist/scheduler/scheduler.d.ts +44 -0
  184. package/dist/scheduler/scheduler.js +197 -0
  185. package/dist/scheduler/scheduler.js.map +1 -0
  186. package/dist/scheduler/scheduler.test.d.ts +1 -0
  187. package/dist/scheduler/scheduler.test.js +119 -0
  188. package/dist/scheduler/scheduler.test.js.map +1 -0
  189. package/dist/scheduler/types.d.ts +107 -0
  190. package/dist/scheduler/types.js +7 -0
  191. package/dist/scheduler/types.js.map +1 -0
  192. package/dist/service-installer.d.ts +17 -0
  193. package/dist/service-installer.js +182 -0
  194. package/dist/service-installer.js.map +1 -0
  195. package/dist/setup-wizard.d.ts +48 -0
  196. package/dist/setup-wizard.js +701 -0
  197. package/dist/setup-wizard.js.map +1 -0
  198. package/dist/statusline-watcher.d.ts +34 -0
  199. package/dist/statusline-watcher.js +73 -0
  200. package/dist/statusline-watcher.js.map +1 -0
  201. package/dist/stt.d.ts +10 -0
  202. package/dist/stt.js +33 -0
  203. package/dist/stt.js.map +1 -0
  204. package/dist/tmux-control.d.ts +52 -0
  205. package/dist/tmux-control.js +207 -0
  206. package/dist/tmux-control.js.map +1 -0
  207. package/dist/tmux-manager.d.ts +44 -0
  208. package/dist/tmux-manager.js +218 -0
  209. package/dist/tmux-manager.js.map +1 -0
  210. package/dist/topic-archiver.d.ts +40 -0
  211. package/dist/topic-archiver.js +103 -0
  212. package/dist/topic-archiver.js.map +1 -0
  213. package/dist/topic-commands.d.ts +28 -0
  214. package/dist/topic-commands.js +359 -0
  215. package/dist/topic-commands.js.map +1 -0
  216. package/dist/transcript-monitor.d.ts +23 -0
  217. package/dist/transcript-monitor.js +164 -0
  218. package/dist/transcript-monitor.js.map +1 -0
  219. package/dist/types.d.ts +211 -0
  220. package/dist/types.js +2 -0
  221. package/dist/types.js.map +1 -0
  222. package/dist/ui/dashboard.html +719 -0
  223. package/dist/web-api.d.ts +101 -0
  224. package/dist/web-api.js +648 -0
  225. package/dist/web-api.js.map +1 -0
  226. package/dist/webhook-emitter.d.ts +15 -0
  227. package/dist/webhook-emitter.js +41 -0
  228. package/dist/webhook-emitter.js.map +1 -0
  229. package/dist/workflow-templates/default.md +35 -0
  230. package/package.json +76 -0
  231. package/templates/launchd.plist.ejs +31 -0
  232. package/templates/systemd.service.ejs +16 -0
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Activity / fleet dashboard HTML served by the daemon's health server.
3
+ * Pure constant — extracted from fleet-manager.ts to keep that module under
4
+ * a manageable size (P4.1).
5
+ */
6
+ export declare const ACTIVITY_VIEWER_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>AgEnD Activity Viewer</title>\n<script src=\"https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js\"></script>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { background: #0d1117; color: #c9d1d9; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace; }\n .header { padding: 16px 24px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }\n .header h1 { font-size: 18px; color: #58a6ff; font-weight: 600; }\n .controls { display: flex; gap: 8px; align-items: center; }\n .controls select, .controls button { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 4px 10px; font-size: 13px; cursor: pointer; }\n .controls button.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }\n .controls button:hover { border-color: #58a6ff; }\n .speed-group { display: flex; gap: 2px; }\n .speed-group button { border-radius: 0; }\n .speed-group button:first-child { border-radius: 6px 0 0 6px; }\n .speed-group button:last-child { border-radius: 0 6px 6px 0; }\n .status { font-size: 12px; color: #8b949e; margin-left: auto; }\n #diagram { padding: 24px; overflow-x: auto; }\n #diagram .mermaid { background: transparent; }\n #diagram svg { max-width: 100%; }\n .feed { padding: 12px 24px; max-height: 300px; overflow-y: auto; border-top: 1px solid #21262d; font-size: 13px; line-height: 1.8; }\n .feed-line { opacity: 0.6; }\n .feed-line.visible { opacity: 1; }\n .feed-line .time { color: #8b949e; }\n .feed-line .msg { color: #58a6ff; }\n .feed-line .tool { color: #d29922; }\n .feed-line .task { color: #3fb950; }\n /* Agent Board */\n .board { padding: 16px 24px; display: flex; gap: 12px; flex-wrap: wrap; border-bottom: 1px solid #21262d; }\n .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px 14px; min-width: 200px; flex: 1; max-width: 280px; transition: border-color 0.3s; }\n .card.flash { border-color: #58a6ff; }\n .card-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }\n .card-header .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }\n .card-header .dot.running { background: #3fb950; }\n .card-header .dot.stopped { background: #8b949e; }\n .card-header .dot.crashed { background: #f85149; }\n .card-header .name { font-weight: 600; font-size: 14px; }\n .card-row { font-size: 12px; color: #8b949e; line-height: 1.6; }\n .card-row span { color: #c9d1d9; }\n .card-task { font-size: 12px; color: #d29922; margin-top: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .board-empty { font-size: 13px; color: #8b949e; padding: 8px 0; }\n .section-label { font-size: 11px; color: #484f58; text-transform: uppercase; letter-spacing: 1px; padding: 10px 24px 0; }\n .tabs { display: flex; gap: 0; padding: 0 24px; border-bottom: 1px solid #21262d; }\n .tab { padding: 8px 16px; font-size: 13px; color: #8b949e; cursor: pointer; border: none; border-bottom: 2px solid transparent; background: none; }\n .tab.active { color: #58a6ff; border-bottom-color: #58a6ff; }\n .tab:hover { color: #c9d1d9; }\n .view { display: none; }\n .view.active { display: block; }\n #graphCanvas { width: 100%; background: #0d1117; display: block; }\n</style>\n</head>\n<body>\n<div class=\"header\">\n <h1>AgEnD Activity</h1>\n <div class=\"controls\">\n <select id=\"range\">\n <option value=\"1h\">1h</option>\n <option value=\"2h\" selected>2h</option>\n <option value=\"4h\">4h</option>\n <option value=\"8h\">8h</option>\n <option value=\"24h\">24h</option>\n </select>\n <button id=\"btnLoad\">Load</button>\n <button id=\"btnPlay\">\u25B6 Play</button>\n <button id=\"btnPause\" style=\"display:none\">\u23F8 Pause</button>\n <div class=\"speed-group\">\n <button class=\"speed\" data-speed=\"1\">1x</button>\n <button class=\"speed active\" data-speed=\"2\">2x</button>\n <button class=\"speed\" data-speed=\"5\">5x</button>\n <button class=\"speed\" data-speed=\"10\">10x</button>\n </div>\n </div>\n <div class=\"status\" id=\"status\">Ready</div>\n</div>\n<div class=\"section-label\">Agents</div>\n<div class=\"board\" id=\"board\"><div class=\"board-empty\">Loading...</div></div>\n<div class=\"tabs\">\n <button class=\"tab active\" data-view=\"graph\">Network Graph</button>\n <button class=\"tab\" data-view=\"seq\">Sequence Diagram</button>\n</div>\n<div id=\"viewGraph\" class=\"view active\"><canvas id=\"graphCanvas\" height=\"400\"></canvas></div>\n<div id=\"viewSeq\" class=\"view\"><div id=\"diagram\"><div class=\"mermaid\" id=\"mermaidEl\"></div></div></div>\n<div class=\"feed\" id=\"feed\"></div>\n\n<script>\nmermaid.initialize({ startOnLoad: false, theme: 'dark', sequence: { mirrorActors: false, messageAlign: 'left' } });\n\nlet rows = [];\nlet speed = 2;\nlet playing = false;\nlet playTimeout = null;\nlet visibleCount = 0;\n\ndocument.querySelectorAll('.speed').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.speed').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n speed = parseInt(btn.dataset.speed);\n });\n});\n\ndocument.getElementById('btnLoad').addEventListener('click', load);\ndocument.getElementById('btnPlay').addEventListener('click', startReplay);\ndocument.getElementById('btnPause').addEventListener('click', pauseReplay);\n\nasync function load() {\n const range = document.getElementById('range').value;\n document.getElementById('status').textContent = 'Loading...';\n try {\n const resp = await fetch('/api/activity?since=' + range + '&limit=500');\n rows = await resp.json();\n document.getElementById('status').textContent = rows.length + ' events loaded';\n visibleCount = rows.length;\n renderFull();\n } catch (e) {\n document.getElementById('status').textContent = 'Error: ' + e.message;\n }\n}\n\nfunction buildMermaid(entries) {\n const participants = new Set();\n entries.forEach(r => { participants.add(r.sender); if (r.receiver) participants.add(r.receiver); });\n const aliases = new Map();\n let idx = 0;\n participants.forEach(p => {\n const a = p.length > 12 ? String.fromCharCode(65 + idx++) : p;\n aliases.set(p, a);\n });\n\n let lines = ['sequenceDiagram'];\n aliases.forEach((a, p) => lines.push(' participant ' + a + ' as ' + p));\n\n entries.forEach(r => {\n const s = aliases.get(r.sender) || r.sender;\n const summary = (r.summary || '').replace(/\"/g, \"'\").slice(0, 80);\n if (r.event === 'tool_call') {\n lines.push(' Note over ' + s + ': \uD83D\uDD27 ' + summary);\n } else if (r.receiver) {\n const recv = aliases.get(r.receiver) || r.receiver;\n lines.push(' ' + s + '->>' + recv + ': ' + summary);\n } else {\n lines.push(' Note over ' + s + ': ' + summary);\n }\n });\n return lines.join('\\n');\n}\n\nasync function renderDiagram(entries) {\n const code = buildMermaid(entries);\n const el = document.getElementById('mermaidEl');\n el.removeAttribute('data-processed');\n el.innerHTML = code;\n try { await mermaid.run({ nodes: [el] }); } catch {}\n}\n\nfunction renderFeed(count) {\n const feed = document.getElementById('feed');\n feed.innerHTML = '';\n rows.forEach((r, i) => {\n const vis = i < count;\n const time = (r.timestamp || '').replace('T', ' ').slice(11, 19);\n const icon = r.event === 'message' ? '\uD83D\uDCAC' : r.event === 'tool_call' ? '\uD83D\uDD27' : '\uD83D\uDCCB';\n const cls = r.event === 'tool_call' ? 'tool' : r.event === 'task_update' ? 'task' : 'msg';\n const arrow = r.receiver ? r.sender + ' \u2192 ' + r.receiver : r.sender;\n const line = document.createElement('div');\n line.className = 'feed-line' + (vis ? ' visible' : '');\n line.innerHTML = '<span class=\"time\">' + time + '</span> ' + icon + ' <span class=\"' + cls + '\">' + arrow + ': ' + (r.summary || '') + '</span>';\n feed.appendChild(line);\n });\n if (count > 0) feed.lastElementChild?.scrollIntoView({ behavior: 'smooth' });\n}\n\nfunction renderFull() {\n visibleCount = rows.length;\n renderDiagram(rows);\n renderFeed(rows.length);\n}\n\nfunction startReplay() {\n playing = true;\n visibleCount = 0;\n document.getElementById('btnPlay').style.display = 'none';\n document.getElementById('btnPause').style.display = '';\n stepReplay();\n}\n\nfunction pauseReplay() {\n playing = false;\n if (playTimeout) clearTimeout(playTimeout);\n document.getElementById('btnPlay').style.display = '';\n document.getElementById('btnPause').style.display = 'none';\n}\n\nfunction stepReplay() {\n if (!playing || visibleCount >= rows.length) {\n pauseReplay();\n document.getElementById('status').textContent = 'Replay complete';\n return;\n }\n visibleCount++;\n const visible = rows.slice(0, visibleCount);\n renderDiagram(visible);\n renderFeed(visibleCount);\n document.getElementById('status').textContent = visibleCount + '/' + rows.length;\n\n // Calculate delay from real timestamps\n let delayMs = 500;\n if (visibleCount < rows.length) {\n const curr = new Date(rows[visibleCount - 1].timestamp).getTime();\n const next = new Date(rows[visibleCount].timestamp).getTime();\n delayMs = Math.max(100, Math.min(3000, (next - curr) / speed));\n }\n playTimeout = setTimeout(stepReplay, delayMs);\n}\n\n// \u2500\u2500 Tab switching \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.tab').forEach(tab => {\n tab.addEventListener('click', () => {\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));\n tab.classList.add('active');\n document.getElementById('view' + (tab.dataset.view === 'graph' ? 'Graph' : 'Seq')).classList.add('active');\n if (tab.dataset.view === 'graph') resizeCanvas();\n });\n});\n\n// \u2500\u2500 Network Graph \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst canvas = document.getElementById('graphCanvas');\nconst ctx2d = canvas.getContext('2d');\nlet graphNodes = []; // {name, x, y, color, isGeneral}\nlet graphEdges = new Map(); // \"a->b\" \u2192 {from, to}\nlet pulses = []; // {fromX, fromY, toX, toY, progress, color}\n\nfunction resizeCanvas() {\n canvas.width = canvas.parentElement.offsetWidth;\n canvas.height = 400;\n layoutNodes();\n}\n\nfunction layoutNodes() {\n if (graphNodes.length === 0) return;\n const cx = canvas.width / 2;\n const cy = canvas.height / 2;\n const radius = Math.min(cx, cy) - 60;\n // Find general (center)\n const general = graphNodes.find(n => n.isGeneral);\n const others = graphNodes.filter(n => !n.isGeneral);\n if (general) { general.x = cx; general.y = cy; }\n others.forEach((n, i) => {\n const angle = (2 * Math.PI * i / others.length) - Math.PI / 2;\n n.x = cx + radius * Math.cos(angle);\n n.y = cy + radius * Math.sin(angle);\n });\n}\n\nfunction updateGraphFromFleet(data) {\n const names = new Set();\n data.instances.forEach(inst => names.add(inst.name));\n // Add user node if activity mentions it\n rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });\n // Rebuild nodes (preserve positions if same set)\n const oldMap = new Map(graphNodes.map(n => [n.name, n]));\n graphNodes = [...names].map(name => {\n const old = oldMap.get(name);\n const inst = data.instances.find(i => i.name === name);\n const color = !inst ? '#8b949e' : inst.status === 'running' ? '#3fb950' : inst.status === 'crashed' ? '#f85149' : '#484f58';\n return { name, x: old?.x ?? 0, y: old?.y ?? 0, color, isGeneral: inst?.general_topic ?? false };\n });\n layoutNodes();\n // Build edges from activity\n graphEdges.clear();\n rows.forEach(r => {\n if (r.receiver && r.event === 'message') {\n const key = r.sender + '->' + r.receiver;\n graphEdges.set(key, { from: r.sender, to: r.receiver });\n }\n });\n}\n\nfunction spawnPulse(sender, receiver, event) {\n const from = graphNodes.find(n => n.name === sender);\n const to = graphNodes.find(n => n.name === (receiver || sender));\n if (!from || !to) return;\n const colors = { message: '#58a6ff', tool_call: '#d29922', task_update: '#3fb950' };\n pulses.push({ fromX: from.x, fromY: from.y, toX: to.x, toY: to.y, progress: 0, color: colors[event] || '#58a6ff' });\n}\n\nfunction drawGraph() {\n if (!ctx2d) return;\n ctx2d.clearRect(0, 0, canvas.width, canvas.height);\n // Draw edges\n ctx2d.strokeStyle = '#21262d';\n ctx2d.lineWidth = 1;\n graphEdges.forEach(e => {\n const from = graphNodes.find(n => n.name === e.from);\n const to = graphNodes.find(n => n.name === e.to);\n if (from && to) {\n ctx2d.beginPath();\n ctx2d.moveTo(from.x, from.y);\n ctx2d.lineTo(to.x, to.y);\n ctx2d.stroke();\n }\n });\n // Draw pulses\n pulses = pulses.filter(p => p.progress <= 1);\n pulses.forEach(p => {\n p.progress += 0.02;\n const x = p.fromX + (p.toX - p.fromX) * p.progress;\n const y = p.fromY + (p.toY - p.fromY) * p.progress;\n ctx2d.beginPath();\n ctx2d.arc(x, y, 5, 0, Math.PI * 2);\n ctx2d.fillStyle = p.color;\n ctx2d.shadowColor = p.color;\n ctx2d.shadowBlur = 12;\n ctx2d.fill();\n ctx2d.shadowBlur = 0;\n });\n // Draw nodes\n graphNodes.forEach(n => {\n // Glow\n ctx2d.beginPath();\n ctx2d.arc(n.x, n.y, n.isGeneral ? 28 : 22, 0, Math.PI * 2);\n ctx2d.fillStyle = n.color + '22';\n ctx2d.fill();\n // Circle\n ctx2d.beginPath();\n ctx2d.arc(n.x, n.y, n.isGeneral ? 24 : 18, 0, Math.PI * 2);\n ctx2d.fillStyle = '#161b22';\n ctx2d.strokeStyle = n.color;\n ctx2d.lineWidth = 2;\n ctx2d.fill();\n ctx2d.stroke();\n // Label\n ctx2d.fillStyle = '#c9d1d9';\n ctx2d.font = (n.isGeneral ? '12' : '11') + 'px -apple-system, monospace';\n ctx2d.textAlign = 'center';\n ctx2d.fillText(n.name.length > 14 ? n.name.slice(0, 12) + '..' : n.name, n.x, n.y + (n.isGeneral ? 38 : 32));\n });\n requestAnimationFrame(drawGraph);\n}\n\n// Hook into replay: spawn pulses when stepping\nconst origStep = stepReplay;\nstepReplay = function() {\n const prevCount = visibleCount;\n origStep();\n if (visibleCount > prevCount && visibleCount <= rows.length) {\n const r = rows[visibleCount - 1];\n spawnPulse(r.sender, r.receiver, r.event);\n }\n};\n\n// Hook into full load: spawn pulses for all visible events on load\nconst origRenderFull = renderFull;\nrenderFull = function() {\n origRenderFull();\n // Update graph nodes from fleet data (if available)\n fetch('/api/fleet').then(r => r.json()).then(data => {\n updateGraphFromFleet(data);\n }).catch(() => {\n // Fallback: build nodes from activity only\n const names = new Set();\n rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });\n graphNodes = [...names].map(n => ({ name: n, x: 0, y: 0, color: '#8b949e', isGeneral: n === 'general' }));\n layoutNodes();\n });\n};\n\nresizeCanvas();\nwindow.addEventListener('resize', resizeCanvas);\nrequestAnimationFrame(drawGraph);\n\n// \u2500\u2500 Agent Board \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nlet prevBoard = '';\n\nasync function loadBoard() {\n try {\n const resp = await fetch('/api/fleet');\n const data = await resp.json();\n renderBoard(data);\n } catch {}\n}\n\nfunction renderBoard(data) {\n const board = document.getElementById('board');\n const cards = data.instances.map(inst => {\n const statusDot = inst.status === 'running' ? 'running' : inst.status === 'crashed' ? 'crashed' : 'stopped';\n const icon = inst.status === 'running' ? '\uD83D\uDFE2' : inst.status === 'crashed' ? '\uD83D\uDD34' : '\u26AA';\n const role = inst.general_topic ? 'coordinator' : inst.description || 'worker';\n const costStr = '$' + (inst.costCents / 100).toFixed(2);\n const lastMs = inst.lastActivity;\n let lastStr = '\u2014';\n if (lastMs) {\n const ago = Math.floor((Date.now() - lastMs) / 1000);\n lastStr = ago < 60 ? ago + 's ago' : ago < 3600 ? Math.floor(ago/60) + 'm ago' : Math.floor(ago/3600) + 'h ago';\n }\n const ipc = inst.ipc ? '\u2713' : '\u2717';\n const rl = inst.rateLimits ? ' \u00B7 5h:' + inst.rateLimits.five_hour_pct + '%' : '';\n const taskLine = inst.currentTask\n ? '<div class=\"card-task\">\uD83D\uDCCC ' + inst.currentTask + '</div>'\n : '<div class=\"card-task\" style=\"color:#484f58\">(idle)</div>';\n return '<div class=\"card\" data-name=\"' + inst.name + '\">' +\n '<div class=\"card-header\"><div class=\"dot ' + statusDot + '\"></div><div class=\"name\">' + inst.name + '</div></div>' +\n '<div class=\"card-row\">' + role.slice(0, 30) + '</div>' +\n '<div class=\"card-row\">Backend: <span>' + inst.backend + '</span> \u00B7 Tools: <span>' + inst.tool_set + '</span></div>' +\n '<div class=\"card-row\">IPC: <span>' + ipc + '</span> \u00B7 Cost: <span>' + costStr + '</span>' + rl + '</div>' +\n '<div class=\"card-row\">Last: <span>' + lastStr + '</span></div>' +\n taskLine +\n '</div>';\n });\n\n const newHtml = cards.join('');\n if (newHtml !== prevBoard) {\n board.innerHTML = newHtml;\n // Flash changed cards\n board.querySelectorAll('.card').forEach(c => {\n c.classList.add('flash');\n setTimeout(() => c.classList.remove('flash'), 1000);\n });\n prevBoard = newHtml;\n }\n}\n\n// Auto-refresh board every 10s\nsetInterval(loadBoard, 10000);\n\n// Auto-load on page open\nloadBoard();\nload();\n</script>\n</body>\n</html>";
@@ -0,0 +1,443 @@
1
+ /**
2
+ * Activity / fleet dashboard HTML served by the daemon's health server.
3
+ * Pure constant — extracted from fleet-manager.ts to keep that module under
4
+ * a manageable size (P4.1).
5
+ */
6
+ export const ACTIVITY_VIEWER_HTML = `<!DOCTYPE html>
7
+ <html lang="en">
8
+ <head>
9
+ <meta charset="utf-8">
10
+ <meta name="viewport" content="width=device-width, initial-scale=1">
11
+ <title>AgEnD Activity Viewer</title>
12
+ <script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
13
+ <style>
14
+ * { margin: 0; padding: 0; box-sizing: border-box; }
15
+ body { background: #0d1117; color: #c9d1d9; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', monospace; }
16
+ .header { padding: 16px 24px; border-bottom: 1px solid #21262d; display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
17
+ .header h1 { font-size: 18px; color: #58a6ff; font-weight: 600; }
18
+ .controls { display: flex; gap: 8px; align-items: center; }
19
+ .controls select, .controls button { background: #21262d; color: #c9d1d9; border: 1px solid #30363d; border-radius: 6px; padding: 4px 10px; font-size: 13px; cursor: pointer; }
20
+ .controls button.active { background: #1f6feb; border-color: #1f6feb; color: #fff; }
21
+ .controls button:hover { border-color: #58a6ff; }
22
+ .speed-group { display: flex; gap: 2px; }
23
+ .speed-group button { border-radius: 0; }
24
+ .speed-group button:first-child { border-radius: 6px 0 0 6px; }
25
+ .speed-group button:last-child { border-radius: 0 6px 6px 0; }
26
+ .status { font-size: 12px; color: #8b949e; margin-left: auto; }
27
+ #diagram { padding: 24px; overflow-x: auto; }
28
+ #diagram .mermaid { background: transparent; }
29
+ #diagram svg { max-width: 100%; }
30
+ .feed { padding: 12px 24px; max-height: 300px; overflow-y: auto; border-top: 1px solid #21262d; font-size: 13px; line-height: 1.8; }
31
+ .feed-line { opacity: 0.6; }
32
+ .feed-line.visible { opacity: 1; }
33
+ .feed-line .time { color: #8b949e; }
34
+ .feed-line .msg { color: #58a6ff; }
35
+ .feed-line .tool { color: #d29922; }
36
+ .feed-line .task { color: #3fb950; }
37
+ /* Agent Board */
38
+ .board { padding: 16px 24px; display: flex; gap: 12px; flex-wrap: wrap; border-bottom: 1px solid #21262d; }
39
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px 14px; min-width: 200px; flex: 1; max-width: 280px; transition: border-color 0.3s; }
40
+ .card.flash { border-color: #58a6ff; }
41
+ .card-header { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
42
+ .card-header .dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
43
+ .card-header .dot.running { background: #3fb950; }
44
+ .card-header .dot.stopped { background: #8b949e; }
45
+ .card-header .dot.crashed { background: #f85149; }
46
+ .card-header .name { font-weight: 600; font-size: 14px; }
47
+ .card-row { font-size: 12px; color: #8b949e; line-height: 1.6; }
48
+ .card-row span { color: #c9d1d9; }
49
+ .card-task { font-size: 12px; color: #d29922; margin-top: 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
50
+ .board-empty { font-size: 13px; color: #8b949e; padding: 8px 0; }
51
+ .section-label { font-size: 11px; color: #484f58; text-transform: uppercase; letter-spacing: 1px; padding: 10px 24px 0; }
52
+ .tabs { display: flex; gap: 0; padding: 0 24px; border-bottom: 1px solid #21262d; }
53
+ .tab { padding: 8px 16px; font-size: 13px; color: #8b949e; cursor: pointer; border: none; border-bottom: 2px solid transparent; background: none; }
54
+ .tab.active { color: #58a6ff; border-bottom-color: #58a6ff; }
55
+ .tab:hover { color: #c9d1d9; }
56
+ .view { display: none; }
57
+ .view.active { display: block; }
58
+ #graphCanvas { width: 100%; background: #0d1117; display: block; }
59
+ </style>
60
+ </head>
61
+ <body>
62
+ <div class="header">
63
+ <h1>AgEnD Activity</h1>
64
+ <div class="controls">
65
+ <select id="range">
66
+ <option value="1h">1h</option>
67
+ <option value="2h" selected>2h</option>
68
+ <option value="4h">4h</option>
69
+ <option value="8h">8h</option>
70
+ <option value="24h">24h</option>
71
+ </select>
72
+ <button id="btnLoad">Load</button>
73
+ <button id="btnPlay">▶ Play</button>
74
+ <button id="btnPause" style="display:none">⏸ Pause</button>
75
+ <div class="speed-group">
76
+ <button class="speed" data-speed="1">1x</button>
77
+ <button class="speed active" data-speed="2">2x</button>
78
+ <button class="speed" data-speed="5">5x</button>
79
+ <button class="speed" data-speed="10">10x</button>
80
+ </div>
81
+ </div>
82
+ <div class="status" id="status">Ready</div>
83
+ </div>
84
+ <div class="section-label">Agents</div>
85
+ <div class="board" id="board"><div class="board-empty">Loading...</div></div>
86
+ <div class="tabs">
87
+ <button class="tab active" data-view="graph">Network Graph</button>
88
+ <button class="tab" data-view="seq">Sequence Diagram</button>
89
+ </div>
90
+ <div id="viewGraph" class="view active"><canvas id="graphCanvas" height="400"></canvas></div>
91
+ <div id="viewSeq" class="view"><div id="diagram"><div class="mermaid" id="mermaidEl"></div></div></div>
92
+ <div class="feed" id="feed"></div>
93
+
94
+ <script>
95
+ mermaid.initialize({ startOnLoad: false, theme: 'dark', sequence: { mirrorActors: false, messageAlign: 'left' } });
96
+
97
+ let rows = [];
98
+ let speed = 2;
99
+ let playing = false;
100
+ let playTimeout = null;
101
+ let visibleCount = 0;
102
+
103
+ document.querySelectorAll('.speed').forEach(btn => {
104
+ btn.addEventListener('click', () => {
105
+ document.querySelectorAll('.speed').forEach(b => b.classList.remove('active'));
106
+ btn.classList.add('active');
107
+ speed = parseInt(btn.dataset.speed);
108
+ });
109
+ });
110
+
111
+ document.getElementById('btnLoad').addEventListener('click', load);
112
+ document.getElementById('btnPlay').addEventListener('click', startReplay);
113
+ document.getElementById('btnPause').addEventListener('click', pauseReplay);
114
+
115
+ async function load() {
116
+ const range = document.getElementById('range').value;
117
+ document.getElementById('status').textContent = 'Loading...';
118
+ try {
119
+ const resp = await fetch('/api/activity?since=' + range + '&limit=500');
120
+ rows = await resp.json();
121
+ document.getElementById('status').textContent = rows.length + ' events loaded';
122
+ visibleCount = rows.length;
123
+ renderFull();
124
+ } catch (e) {
125
+ document.getElementById('status').textContent = 'Error: ' + e.message;
126
+ }
127
+ }
128
+
129
+ function buildMermaid(entries) {
130
+ const participants = new Set();
131
+ entries.forEach(r => { participants.add(r.sender); if (r.receiver) participants.add(r.receiver); });
132
+ const aliases = new Map();
133
+ let idx = 0;
134
+ participants.forEach(p => {
135
+ const a = p.length > 12 ? String.fromCharCode(65 + idx++) : p;
136
+ aliases.set(p, a);
137
+ });
138
+
139
+ let lines = ['sequenceDiagram'];
140
+ aliases.forEach((a, p) => lines.push(' participant ' + a + ' as ' + p));
141
+
142
+ entries.forEach(r => {
143
+ const s = aliases.get(r.sender) || r.sender;
144
+ const summary = (r.summary || '').replace(/"/g, "'").slice(0, 80);
145
+ if (r.event === 'tool_call') {
146
+ lines.push(' Note over ' + s + ': 🔧 ' + summary);
147
+ } else if (r.receiver) {
148
+ const recv = aliases.get(r.receiver) || r.receiver;
149
+ lines.push(' ' + s + '->>' + recv + ': ' + summary);
150
+ } else {
151
+ lines.push(' Note over ' + s + ': ' + summary);
152
+ }
153
+ });
154
+ return lines.join('\\n');
155
+ }
156
+
157
+ async function renderDiagram(entries) {
158
+ const code = buildMermaid(entries);
159
+ const el = document.getElementById('mermaidEl');
160
+ el.removeAttribute('data-processed');
161
+ el.innerHTML = code;
162
+ try { await mermaid.run({ nodes: [el] }); } catch {}
163
+ }
164
+
165
+ function renderFeed(count) {
166
+ const feed = document.getElementById('feed');
167
+ feed.innerHTML = '';
168
+ rows.forEach((r, i) => {
169
+ const vis = i < count;
170
+ const time = (r.timestamp || '').replace('T', ' ').slice(11, 19);
171
+ const icon = r.event === 'message' ? '💬' : r.event === 'tool_call' ? '🔧' : '📋';
172
+ const cls = r.event === 'tool_call' ? 'tool' : r.event === 'task_update' ? 'task' : 'msg';
173
+ const arrow = r.receiver ? r.sender + ' → ' + r.receiver : r.sender;
174
+ const line = document.createElement('div');
175
+ line.className = 'feed-line' + (vis ? ' visible' : '');
176
+ line.innerHTML = '<span class="time">' + time + '</span> ' + icon + ' <span class="' + cls + '">' + arrow + ': ' + (r.summary || '') + '</span>';
177
+ feed.appendChild(line);
178
+ });
179
+ if (count > 0) feed.lastElementChild?.scrollIntoView({ behavior: 'smooth' });
180
+ }
181
+
182
+ function renderFull() {
183
+ visibleCount = rows.length;
184
+ renderDiagram(rows);
185
+ renderFeed(rows.length);
186
+ }
187
+
188
+ function startReplay() {
189
+ playing = true;
190
+ visibleCount = 0;
191
+ document.getElementById('btnPlay').style.display = 'none';
192
+ document.getElementById('btnPause').style.display = '';
193
+ stepReplay();
194
+ }
195
+
196
+ function pauseReplay() {
197
+ playing = false;
198
+ if (playTimeout) clearTimeout(playTimeout);
199
+ document.getElementById('btnPlay').style.display = '';
200
+ document.getElementById('btnPause').style.display = 'none';
201
+ }
202
+
203
+ function stepReplay() {
204
+ if (!playing || visibleCount >= rows.length) {
205
+ pauseReplay();
206
+ document.getElementById('status').textContent = 'Replay complete';
207
+ return;
208
+ }
209
+ visibleCount++;
210
+ const visible = rows.slice(0, visibleCount);
211
+ renderDiagram(visible);
212
+ renderFeed(visibleCount);
213
+ document.getElementById('status').textContent = visibleCount + '/' + rows.length;
214
+
215
+ // Calculate delay from real timestamps
216
+ let delayMs = 500;
217
+ if (visibleCount < rows.length) {
218
+ const curr = new Date(rows[visibleCount - 1].timestamp).getTime();
219
+ const next = new Date(rows[visibleCount].timestamp).getTime();
220
+ delayMs = Math.max(100, Math.min(3000, (next - curr) / speed));
221
+ }
222
+ playTimeout = setTimeout(stepReplay, delayMs);
223
+ }
224
+
225
+ // ── Tab switching ────────────────────────────────
226
+ document.querySelectorAll('.tab').forEach(tab => {
227
+ tab.addEventListener('click', () => {
228
+ document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
229
+ document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
230
+ tab.classList.add('active');
231
+ document.getElementById('view' + (tab.dataset.view === 'graph' ? 'Graph' : 'Seq')).classList.add('active');
232
+ if (tab.dataset.view === 'graph') resizeCanvas();
233
+ });
234
+ });
235
+
236
+ // ── Network Graph ────────────────────────────────
237
+ const canvas = document.getElementById('graphCanvas');
238
+ const ctx2d = canvas.getContext('2d');
239
+ let graphNodes = []; // {name, x, y, color, isGeneral}
240
+ let graphEdges = new Map(); // "a->b" → {from, to}
241
+ let pulses = []; // {fromX, fromY, toX, toY, progress, color}
242
+
243
+ function resizeCanvas() {
244
+ canvas.width = canvas.parentElement.offsetWidth;
245
+ canvas.height = 400;
246
+ layoutNodes();
247
+ }
248
+
249
+ function layoutNodes() {
250
+ if (graphNodes.length === 0) return;
251
+ const cx = canvas.width / 2;
252
+ const cy = canvas.height / 2;
253
+ const radius = Math.min(cx, cy) - 60;
254
+ // Find general (center)
255
+ const general = graphNodes.find(n => n.isGeneral);
256
+ const others = graphNodes.filter(n => !n.isGeneral);
257
+ if (general) { general.x = cx; general.y = cy; }
258
+ others.forEach((n, i) => {
259
+ const angle = (2 * Math.PI * i / others.length) - Math.PI / 2;
260
+ n.x = cx + radius * Math.cos(angle);
261
+ n.y = cy + radius * Math.sin(angle);
262
+ });
263
+ }
264
+
265
+ function updateGraphFromFleet(data) {
266
+ const names = new Set();
267
+ data.instances.forEach(inst => names.add(inst.name));
268
+ // Add user node if activity mentions it
269
+ rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });
270
+ // Rebuild nodes (preserve positions if same set)
271
+ const oldMap = new Map(graphNodes.map(n => [n.name, n]));
272
+ graphNodes = [...names].map(name => {
273
+ const old = oldMap.get(name);
274
+ const inst = data.instances.find(i => i.name === name);
275
+ const color = !inst ? '#8b949e' : inst.status === 'running' ? '#3fb950' : inst.status === 'crashed' ? '#f85149' : '#484f58';
276
+ return { name, x: old?.x ?? 0, y: old?.y ?? 0, color, isGeneral: inst?.general_topic ?? false };
277
+ });
278
+ layoutNodes();
279
+ // Build edges from activity
280
+ graphEdges.clear();
281
+ rows.forEach(r => {
282
+ if (r.receiver && r.event === 'message') {
283
+ const key = r.sender + '->' + r.receiver;
284
+ graphEdges.set(key, { from: r.sender, to: r.receiver });
285
+ }
286
+ });
287
+ }
288
+
289
+ function spawnPulse(sender, receiver, event) {
290
+ const from = graphNodes.find(n => n.name === sender);
291
+ const to = graphNodes.find(n => n.name === (receiver || sender));
292
+ if (!from || !to) return;
293
+ const colors = { message: '#58a6ff', tool_call: '#d29922', task_update: '#3fb950' };
294
+ pulses.push({ fromX: from.x, fromY: from.y, toX: to.x, toY: to.y, progress: 0, color: colors[event] || '#58a6ff' });
295
+ }
296
+
297
+ function drawGraph() {
298
+ if (!ctx2d) return;
299
+ ctx2d.clearRect(0, 0, canvas.width, canvas.height);
300
+ // Draw edges
301
+ ctx2d.strokeStyle = '#21262d';
302
+ ctx2d.lineWidth = 1;
303
+ graphEdges.forEach(e => {
304
+ const from = graphNodes.find(n => n.name === e.from);
305
+ const to = graphNodes.find(n => n.name === e.to);
306
+ if (from && to) {
307
+ ctx2d.beginPath();
308
+ ctx2d.moveTo(from.x, from.y);
309
+ ctx2d.lineTo(to.x, to.y);
310
+ ctx2d.stroke();
311
+ }
312
+ });
313
+ // Draw pulses
314
+ pulses = pulses.filter(p => p.progress <= 1);
315
+ pulses.forEach(p => {
316
+ p.progress += 0.02;
317
+ const x = p.fromX + (p.toX - p.fromX) * p.progress;
318
+ const y = p.fromY + (p.toY - p.fromY) * p.progress;
319
+ ctx2d.beginPath();
320
+ ctx2d.arc(x, y, 5, 0, Math.PI * 2);
321
+ ctx2d.fillStyle = p.color;
322
+ ctx2d.shadowColor = p.color;
323
+ ctx2d.shadowBlur = 12;
324
+ ctx2d.fill();
325
+ ctx2d.shadowBlur = 0;
326
+ });
327
+ // Draw nodes
328
+ graphNodes.forEach(n => {
329
+ // Glow
330
+ ctx2d.beginPath();
331
+ ctx2d.arc(n.x, n.y, n.isGeneral ? 28 : 22, 0, Math.PI * 2);
332
+ ctx2d.fillStyle = n.color + '22';
333
+ ctx2d.fill();
334
+ // Circle
335
+ ctx2d.beginPath();
336
+ ctx2d.arc(n.x, n.y, n.isGeneral ? 24 : 18, 0, Math.PI * 2);
337
+ ctx2d.fillStyle = '#161b22';
338
+ ctx2d.strokeStyle = n.color;
339
+ ctx2d.lineWidth = 2;
340
+ ctx2d.fill();
341
+ ctx2d.stroke();
342
+ // Label
343
+ ctx2d.fillStyle = '#c9d1d9';
344
+ ctx2d.font = (n.isGeneral ? '12' : '11') + 'px -apple-system, monospace';
345
+ ctx2d.textAlign = 'center';
346
+ ctx2d.fillText(n.name.length > 14 ? n.name.slice(0, 12) + '..' : n.name, n.x, n.y + (n.isGeneral ? 38 : 32));
347
+ });
348
+ requestAnimationFrame(drawGraph);
349
+ }
350
+
351
+ // Hook into replay: spawn pulses when stepping
352
+ const origStep = stepReplay;
353
+ stepReplay = function() {
354
+ const prevCount = visibleCount;
355
+ origStep();
356
+ if (visibleCount > prevCount && visibleCount <= rows.length) {
357
+ const r = rows[visibleCount - 1];
358
+ spawnPulse(r.sender, r.receiver, r.event);
359
+ }
360
+ };
361
+
362
+ // Hook into full load: spawn pulses for all visible events on load
363
+ const origRenderFull = renderFull;
364
+ renderFull = function() {
365
+ origRenderFull();
366
+ // Update graph nodes from fleet data (if available)
367
+ fetch('/api/fleet').then(r => r.json()).then(data => {
368
+ updateGraphFromFleet(data);
369
+ }).catch(() => {
370
+ // Fallback: build nodes from activity only
371
+ const names = new Set();
372
+ rows.forEach(r => { names.add(r.sender); if (r.receiver) names.add(r.receiver); });
373
+ graphNodes = [...names].map(n => ({ name: n, x: 0, y: 0, color: '#8b949e', isGeneral: n === 'general' }));
374
+ layoutNodes();
375
+ });
376
+ };
377
+
378
+ resizeCanvas();
379
+ window.addEventListener('resize', resizeCanvas);
380
+ requestAnimationFrame(drawGraph);
381
+
382
+ // ── Agent Board ──────────────────────────────────
383
+
384
+ let prevBoard = '';
385
+
386
+ async function loadBoard() {
387
+ try {
388
+ const resp = await fetch('/api/fleet');
389
+ const data = await resp.json();
390
+ renderBoard(data);
391
+ } catch {}
392
+ }
393
+
394
+ function renderBoard(data) {
395
+ const board = document.getElementById('board');
396
+ const cards = data.instances.map(inst => {
397
+ const statusDot = inst.status === 'running' ? 'running' : inst.status === 'crashed' ? 'crashed' : 'stopped';
398
+ const icon = inst.status === 'running' ? '🟢' : inst.status === 'crashed' ? '🔴' : '⚪';
399
+ const role = inst.general_topic ? 'coordinator' : inst.description || 'worker';
400
+ const costStr = '$' + (inst.costCents / 100).toFixed(2);
401
+ const lastMs = inst.lastActivity;
402
+ let lastStr = '—';
403
+ if (lastMs) {
404
+ const ago = Math.floor((Date.now() - lastMs) / 1000);
405
+ lastStr = ago < 60 ? ago + 's ago' : ago < 3600 ? Math.floor(ago/60) + 'm ago' : Math.floor(ago/3600) + 'h ago';
406
+ }
407
+ const ipc = inst.ipc ? '✓' : '✗';
408
+ const rl = inst.rateLimits ? ' · 5h:' + inst.rateLimits.five_hour_pct + '%' : '';
409
+ const taskLine = inst.currentTask
410
+ ? '<div class="card-task">📌 ' + inst.currentTask + '</div>'
411
+ : '<div class="card-task" style="color:#484f58">(idle)</div>';
412
+ return '<div class="card" data-name="' + inst.name + '">' +
413
+ '<div class="card-header"><div class="dot ' + statusDot + '"></div><div class="name">' + inst.name + '</div></div>' +
414
+ '<div class="card-row">' + role.slice(0, 30) + '</div>' +
415
+ '<div class="card-row">Backend: <span>' + inst.backend + '</span> · Tools: <span>' + inst.tool_set + '</span></div>' +
416
+ '<div class="card-row">IPC: <span>' + ipc + '</span> · Cost: <span>' + costStr + '</span>' + rl + '</div>' +
417
+ '<div class="card-row">Last: <span>' + lastStr + '</span></div>' +
418
+ taskLine +
419
+ '</div>';
420
+ });
421
+
422
+ const newHtml = cards.join('');
423
+ if (newHtml !== prevBoard) {
424
+ board.innerHTML = newHtml;
425
+ // Flash changed cards
426
+ board.querySelectorAll('.card').forEach(c => {
427
+ c.classList.add('flash');
428
+ setTimeout(() => c.classList.remove('flash'), 1000);
429
+ });
430
+ prevBoard = newHtml;
431
+ }
432
+ }
433
+
434
+ // Auto-refresh board every 10s
435
+ setInterval(loadBoard, 10000);
436
+
437
+ // Auto-load on page open
438
+ loadBoard();
439
+ load();
440
+ </script>
441
+ </body>
442
+ </html>`;
443
+ //# sourceMappingURL=fleet-dashboard-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fleet-dashboard-html.js","sourceRoot":"","sources":["../src/fleet-dashboard-html.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAob5B,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type Server } from "node:http";
2
+ import type { Logger } from "./logger.js";
3
+ import type { FleetConfig, InstanceConfig } from "./types.js";
4
+ import type { Scheduler } from "./scheduler/index.js";
5
+ import type { EventLog } from "./event-log.js";
6
+ import type { SysInfo } from "./fleet-context.js";
7
+ /**
8
+ * Extract a web token from a request, accepting (in order):
9
+ * 1. `?token=` query string
10
+ * 2. `Authorization: Bearer <token>` header (standard)
11
+ * 3. `X-Agend-Token: <token>` header (legacy compatibility)
12
+ */
13
+ export declare function extractWebToken(parsedUrl: URL, headers: Record<string, string | string[] | undefined>): string | null;
14
+ export interface UiStatusContext {
15
+ readonly fleetConfig: FleetConfig | null;
16
+ readonly logger: Logger;
17
+ getInstanceDir(name: string): string;
18
+ getInstanceStatus(name: string): "running" | "stopped" | "crashed";
19
+ }
20
+ export interface HealthServerContext extends UiStatusContext {
21
+ readonly dataDir: string;
22
+ readonly scheduler: Scheduler | null;
23
+ readonly eventLog: EventLog | null;
24
+ getSysInfo(): SysInfo;
25
+ lastActivityMs(name: string): number;
26
+ startInstance(name: string, config: InstanceConfig, topicMode: boolean): Promise<void>;
27
+ restartSingleInstance(name: string): Promise<void>;
28
+ emitSseEvent(event: string, data: unknown): void;
29
+ }
30
+ export declare function startHealthServer(ctx: HealthServerContext, port: number): {
31
+ server: Server;
32
+ webToken: string;
33
+ startedAt: number;
34
+ };
35
+ export declare function getUiStatus(ctx: UiStatusContext, startedAt: number): unknown;