@intrect/openswarm 0.2.0

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 (437) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +544 -0
  3. package/config.example.yaml +107 -0
  4. package/dist/adapters/base.d.ts +8 -0
  5. package/dist/adapters/base.d.ts.map +1 -0
  6. package/dist/adapters/base.js +104 -0
  7. package/dist/adapters/base.js.map +1 -0
  8. package/dist/adapters/claude.d.ts +13 -0
  9. package/dist/adapters/claude.d.ts.map +1 -0
  10. package/dist/adapters/claude.js +318 -0
  11. package/dist/adapters/claude.js.map +1 -0
  12. package/dist/adapters/codex.d.ts +14 -0
  13. package/dist/adapters/codex.d.ts.map +1 -0
  14. package/dist/adapters/codex.js +366 -0
  15. package/dist/adapters/codex.js.map +1 -0
  16. package/dist/adapters/cryptoQuantAdapter.d.ts +85 -0
  17. package/dist/adapters/cryptoQuantAdapter.d.ts.map +1 -0
  18. package/dist/adapters/cryptoQuantAdapter.js +238 -0
  19. package/dist/adapters/cryptoQuantAdapter.js.map +1 -0
  20. package/dist/adapters/index.d.ts +17 -0
  21. package/dist/adapters/index.d.ts.map +1 -0
  22. package/dist/adapters/index.js +47 -0
  23. package/dist/adapters/index.js.map +1 -0
  24. package/dist/adapters/processRegistry.d.ts +38 -0
  25. package/dist/adapters/processRegistry.d.ts.map +1 -0
  26. package/dist/adapters/processRegistry.js +147 -0
  27. package/dist/adapters/processRegistry.js.map +1 -0
  28. package/dist/adapters/streamBuffer.d.ts +59 -0
  29. package/dist/adapters/streamBuffer.d.ts.map +1 -0
  30. package/dist/adapters/streamBuffer.js +123 -0
  31. package/dist/adapters/streamBuffer.js.map +1 -0
  32. package/dist/adapters/types.d.ts +65 -0
  33. package/dist/adapters/types.d.ts.map +1 -0
  34. package/dist/adapters/types.js +6 -0
  35. package/dist/adapters/types.js.map +1 -0
  36. package/dist/agents/agentBus.d.ts +160 -0
  37. package/dist/agents/agentBus.d.ts.map +1 -0
  38. package/dist/agents/agentBus.js +350 -0
  39. package/dist/agents/agentBus.js.map +1 -0
  40. package/dist/agents/agentPair.d.ts +210 -0
  41. package/dist/agents/agentPair.d.ts.map +1 -0
  42. package/dist/agents/agentPair.js +420 -0
  43. package/dist/agents/agentPair.js.map +1 -0
  44. package/dist/agents/auditor.d.ts +27 -0
  45. package/dist/agents/auditor.d.ts.map +1 -0
  46. package/dist/agents/auditor.js +237 -0
  47. package/dist/agents/auditor.js.map +1 -0
  48. package/dist/agents/cliStreamParser.d.ts +18 -0
  49. package/dist/agents/cliStreamParser.d.ts.map +1 -0
  50. package/dist/agents/cliStreamParser.js +156 -0
  51. package/dist/agents/cliStreamParser.js.map +1 -0
  52. package/dist/agents/documenter.d.ts +31 -0
  53. package/dist/agents/documenter.d.ts.map +1 -0
  54. package/dist/agents/documenter.js +285 -0
  55. package/dist/agents/documenter.js.map +1 -0
  56. package/dist/agents/index.d.ts +10 -0
  57. package/dist/agents/index.d.ts.map +1 -0
  58. package/dist/agents/index.js +10 -0
  59. package/dist/agents/index.js.map +1 -0
  60. package/dist/agents/pairMetrics.d.ts +63 -0
  61. package/dist/agents/pairMetrics.d.ts.map +1 -0
  62. package/dist/agents/pairMetrics.js +231 -0
  63. package/dist/agents/pairMetrics.js.map +1 -0
  64. package/dist/agents/pairPipeline.d.ts +155 -0
  65. package/dist/agents/pairPipeline.d.ts.map +1 -0
  66. package/dist/agents/pairPipeline.js +793 -0
  67. package/dist/agents/pairPipeline.js.map +1 -0
  68. package/dist/agents/pairWebhook.d.ts +59 -0
  69. package/dist/agents/pairWebhook.d.ts.map +1 -0
  70. package/dist/agents/pairWebhook.js +242 -0
  71. package/dist/agents/pairWebhook.js.map +1 -0
  72. package/dist/agents/pipelineFormat.d.ts +11 -0
  73. package/dist/agents/pipelineFormat.d.ts.map +1 -0
  74. package/dist/agents/pipelineFormat.js +164 -0
  75. package/dist/agents/pipelineFormat.js.map +1 -0
  76. package/dist/agents/pipelineGuards.d.ts +23 -0
  77. package/dist/agents/pipelineGuards.d.ts.map +1 -0
  78. package/dist/agents/pipelineGuards.js +175 -0
  79. package/dist/agents/pipelineGuards.js.map +1 -0
  80. package/dist/agents/reviewer.d.ts +37 -0
  81. package/dist/agents/reviewer.d.ts.map +1 -0
  82. package/dist/agents/reviewer.js +213 -0
  83. package/dist/agents/reviewer.js.map +1 -0
  84. package/dist/agents/skillDocumenter.d.ts +23 -0
  85. package/dist/agents/skillDocumenter.d.ts.map +1 -0
  86. package/dist/agents/skillDocumenter.js +218 -0
  87. package/dist/agents/skillDocumenter.js.map +1 -0
  88. package/dist/agents/tester.d.ts +37 -0
  89. package/dist/agents/tester.d.ts.map +1 -0
  90. package/dist/agents/tester.js +308 -0
  91. package/dist/agents/tester.js.map +1 -0
  92. package/dist/agents/worker.d.ts +30 -0
  93. package/dist/agents/worker.d.ts.map +1 -0
  94. package/dist/agents/worker.js +128 -0
  95. package/dist/agents/worker.js.map +1 -0
  96. package/dist/automation/autonomousRunner.d.ts +123 -0
  97. package/dist/automation/autonomousRunner.d.ts.map +1 -0
  98. package/dist/automation/autonomousRunner.js +1082 -0
  99. package/dist/automation/autonomousRunner.js.map +1 -0
  100. package/dist/automation/ciWorker.d.ts +51 -0
  101. package/dist/automation/ciWorker.d.ts.map +1 -0
  102. package/dist/automation/ciWorker.js +282 -0
  103. package/dist/automation/ciWorker.js.map +1 -0
  104. package/dist/automation/conflictResolver.d.ts +29 -0
  105. package/dist/automation/conflictResolver.d.ts.map +1 -0
  106. package/dist/automation/conflictResolver.js +261 -0
  107. package/dist/automation/conflictResolver.js.map +1 -0
  108. package/dist/automation/dailyReporter.d.ts +26 -0
  109. package/dist/automation/dailyReporter.d.ts.map +1 -0
  110. package/dist/automation/dailyReporter.js +132 -0
  111. package/dist/automation/dailyReporter.js.map +1 -0
  112. package/dist/automation/index.d.ts +7 -0
  113. package/dist/automation/index.d.ts.map +1 -0
  114. package/dist/automation/index.js +7 -0
  115. package/dist/automation/index.js.map +1 -0
  116. package/dist/automation/longRunningMonitor.d.ts +26 -0
  117. package/dist/automation/longRunningMonitor.d.ts.map +1 -0
  118. package/dist/automation/longRunningMonitor.js +231 -0
  119. package/dist/automation/longRunningMonitor.js.map +1 -0
  120. package/dist/automation/prOwnership.d.ts +18 -0
  121. package/dist/automation/prOwnership.d.ts.map +1 -0
  122. package/dist/automation/prOwnership.js +61 -0
  123. package/dist/automation/prOwnership.js.map +1 -0
  124. package/dist/automation/prProcessor.d.ts +62 -0
  125. package/dist/automation/prProcessor.d.ts.map +1 -0
  126. package/dist/automation/prProcessor.js +700 -0
  127. package/dist/automation/prProcessor.js.map +1 -0
  128. package/dist/automation/runnerExecution.d.ts +49 -0
  129. package/dist/automation/runnerExecution.d.ts.map +1 -0
  130. package/dist/automation/runnerExecution.js +663 -0
  131. package/dist/automation/runnerExecution.js.map +1 -0
  132. package/dist/automation/runnerState.d.ts +170 -0
  133. package/dist/automation/runnerState.d.ts.map +1 -0
  134. package/dist/automation/runnerState.js +495 -0
  135. package/dist/automation/runnerState.js.map +1 -0
  136. package/dist/automation/runnerTypes.d.ts +46 -0
  137. package/dist/automation/runnerTypes.d.ts.map +1 -0
  138. package/dist/automation/runnerTypes.js +5 -0
  139. package/dist/automation/runnerTypes.js.map +1 -0
  140. package/dist/automation/scheduler.d.ts +75 -0
  141. package/dist/automation/scheduler.d.ts.map +1 -0
  142. package/dist/automation/scheduler.js +394 -0
  143. package/dist/automation/scheduler.js.map +1 -0
  144. package/dist/cli/promptHandler.d.ts +13 -0
  145. package/dist/cli/promptHandler.d.ts.map +1 -0
  146. package/dist/cli/promptHandler.js +189 -0
  147. package/dist/cli/promptHandler.js.map +1 -0
  148. package/dist/cli.d.ts +3 -0
  149. package/dist/cli.d.ts.map +1 -0
  150. package/dist/cli.js +138 -0
  151. package/dist/cli.js.map +1 -0
  152. package/dist/core/config.d.ts +308 -0
  153. package/dist/core/config.d.ts.map +1 -0
  154. package/dist/core/config.js +529 -0
  155. package/dist/core/config.js.map +1 -0
  156. package/dist/core/eventHub.d.ts +194 -0
  157. package/dist/core/eventHub.d.ts.map +1 -0
  158. package/dist/core/eventHub.js +136 -0
  159. package/dist/core/eventHub.js.map +1 -0
  160. package/dist/core/index.d.ts +6 -0
  161. package/dist/core/index.d.ts.map +1 -0
  162. package/dist/core/index.js +6 -0
  163. package/dist/core/index.js.map +1 -0
  164. package/dist/core/service.d.ts +27 -0
  165. package/dist/core/service.d.ts.map +1 -0
  166. package/dist/core/service.js +438 -0
  167. package/dist/core/service.js.map +1 -0
  168. package/dist/core/traceCollector.d.ts +105 -0
  169. package/dist/core/traceCollector.d.ts.map +1 -0
  170. package/dist/core/traceCollector.js +141 -0
  171. package/dist/core/traceCollector.js.map +1 -0
  172. package/dist/core/types.d.ts +413 -0
  173. package/dist/core/types.d.ts.map +1 -0
  174. package/dist/core/types.js +5 -0
  175. package/dist/core/types.js.map +1 -0
  176. package/dist/discord/discordCore.d.ts +104 -0
  177. package/dist/discord/discordCore.d.ts.map +1 -0
  178. package/dist/discord/discordCore.js +698 -0
  179. package/dist/discord/discordCore.js.map +1 -0
  180. package/dist/discord/discordHandlers.d.ts +86 -0
  181. package/dist/discord/discordHandlers.d.ts.map +1 -0
  182. package/dist/discord/discordHandlers.js +849 -0
  183. package/dist/discord/discordHandlers.js.map +1 -0
  184. package/dist/discord/discordPair.d.ts +6 -0
  185. package/dist/discord/discordPair.d.ts.map +1 -0
  186. package/dist/discord/discordPair.js +567 -0
  187. package/dist/discord/discordPair.js.map +1 -0
  188. package/dist/discord/index.d.ts +4 -0
  189. package/dist/discord/index.d.ts.map +1 -0
  190. package/dist/discord/index.js +11 -0
  191. package/dist/discord/index.js.map +1 -0
  192. package/dist/github/github.d.ts +236 -0
  193. package/dist/github/github.d.ts.map +1 -0
  194. package/dist/github/github.js +535 -0
  195. package/dist/github/github.js.map +1 -0
  196. package/dist/github/index.d.ts +2 -0
  197. package/dist/github/index.d.ts.map +1 -0
  198. package/dist/github/index.js +2 -0
  199. package/dist/github/index.js.map +1 -0
  200. package/dist/index.d.ts +3 -0
  201. package/dist/index.d.ts.map +1 -0
  202. package/dist/index.js +60 -0
  203. package/dist/index.js.map +1 -0
  204. package/dist/knowledge/analyzer.d.ts +36 -0
  205. package/dist/knowledge/analyzer.d.ts.map +1 -0
  206. package/dist/knowledge/analyzer.js +170 -0
  207. package/dist/knowledge/analyzer.js.map +1 -0
  208. package/dist/knowledge/gitInfo.d.ts +10 -0
  209. package/dist/knowledge/gitInfo.d.ts.map +1 -0
  210. package/dist/knowledge/gitInfo.js +134 -0
  211. package/dist/knowledge/gitInfo.js.map +1 -0
  212. package/dist/knowledge/graph.d.ts +45 -0
  213. package/dist/knowledge/graph.d.ts.map +1 -0
  214. package/dist/knowledge/graph.js +262 -0
  215. package/dist/knowledge/graph.js.map +1 -0
  216. package/dist/knowledge/graphqlExporter.d.ts +64 -0
  217. package/dist/knowledge/graphqlExporter.d.ts.map +1 -0
  218. package/dist/knowledge/graphqlExporter.js +333 -0
  219. package/dist/knowledge/graphqlExporter.js.map +1 -0
  220. package/dist/knowledge/index.d.ts +58 -0
  221. package/dist/knowledge/index.d.ts.map +1 -0
  222. package/dist/knowledge/index.js +212 -0
  223. package/dist/knowledge/index.js.map +1 -0
  224. package/dist/knowledge/repository.d.ts +64 -0
  225. package/dist/knowledge/repository.d.ts.map +1 -0
  226. package/dist/knowledge/repository.js +250 -0
  227. package/dist/knowledge/repository.js.map +1 -0
  228. package/dist/knowledge/riskOnAnalyzer.d.ts +79 -0
  229. package/dist/knowledge/riskOnAnalyzer.d.ts.map +1 -0
  230. package/dist/knowledge/riskOnAnalyzer.js +243 -0
  231. package/dist/knowledge/riskOnAnalyzer.js.map +1 -0
  232. package/dist/knowledge/scanner.d.ts +14 -0
  233. package/dist/knowledge/scanner.d.ts.map +1 -0
  234. package/dist/knowledge/scanner.js +350 -0
  235. package/dist/knowledge/scanner.js.map +1 -0
  236. package/dist/knowledge/store.d.ts +23 -0
  237. package/dist/knowledge/store.d.ts.map +1 -0
  238. package/dist/knowledge/store.js +86 -0
  239. package/dist/knowledge/store.js.map +1 -0
  240. package/dist/knowledge/types.d.ts +288 -0
  241. package/dist/knowledge/types.d.ts.map +1 -0
  242. package/dist/knowledge/types.js +111 -0
  243. package/dist/knowledge/types.js.map +1 -0
  244. package/dist/linear/index.d.ts +3 -0
  245. package/dist/linear/index.d.ts.map +1 -0
  246. package/dist/linear/index.js +3 -0
  247. package/dist/linear/index.js.map +1 -0
  248. package/dist/linear/linear.d.ts +160 -0
  249. package/dist/linear/linear.d.ts.map +1 -0
  250. package/dist/linear/linear.js +983 -0
  251. package/dist/linear/linear.js.map +1 -0
  252. package/dist/linear/projectUpdater.d.ts +23 -0
  253. package/dist/linear/projectUpdater.d.ts.map +1 -0
  254. package/dist/linear/projectUpdater.js +347 -0
  255. package/dist/linear/projectUpdater.js.map +1 -0
  256. package/dist/locale/en.d.ts +3 -0
  257. package/dist/locale/en.d.ts.map +1 -0
  258. package/dist/locale/en.js +436 -0
  259. package/dist/locale/en.js.map +1 -0
  260. package/dist/locale/index.d.ts +28 -0
  261. package/dist/locale/index.d.ts.map +1 -0
  262. package/dist/locale/index.js +89 -0
  263. package/dist/locale/index.js.map +1 -0
  264. package/dist/locale/ko.d.ts +3 -0
  265. package/dist/locale/ko.d.ts.map +1 -0
  266. package/dist/locale/ko.js +436 -0
  267. package/dist/locale/ko.js.map +1 -0
  268. package/dist/locale/prompts/en.d.ts +3 -0
  269. package/dist/locale/prompts/en.d.ts.map +1 -0
  270. package/dist/locale/prompts/en.js +278 -0
  271. package/dist/locale/prompts/en.js.map +1 -0
  272. package/dist/locale/prompts/ko.d.ts +3 -0
  273. package/dist/locale/prompts/ko.d.ts.map +1 -0
  274. package/dist/locale/prompts/ko.js +279 -0
  275. package/dist/locale/prompts/ko.js.map +1 -0
  276. package/dist/locale/types.d.ts +407 -0
  277. package/dist/locale/types.d.ts.map +1 -0
  278. package/dist/locale/types.js +5 -0
  279. package/dist/locale/types.js.map +1 -0
  280. package/dist/memory/codex.d.ts +93 -0
  281. package/dist/memory/codex.d.ts.map +1 -0
  282. package/dist/memory/codex.js +366 -0
  283. package/dist/memory/codex.js.map +1 -0
  284. package/dist/memory/compaction.d.ts +21 -0
  285. package/dist/memory/compaction.d.ts.map +1 -0
  286. package/dist/memory/compaction.js +205 -0
  287. package/dist/memory/compaction.js.map +1 -0
  288. package/dist/memory/index.d.ts +13 -0
  289. package/dist/memory/index.d.ts.map +1 -0
  290. package/dist/memory/index.js +13 -0
  291. package/dist/memory/index.js.map +1 -0
  292. package/dist/memory/memoryCore.d.ts +178 -0
  293. package/dist/memory/memoryCore.d.ts.map +1 -0
  294. package/dist/memory/memoryCore.js +623 -0
  295. package/dist/memory/memoryCore.js.map +1 -0
  296. package/dist/memory/memoryOps.d.ts +90 -0
  297. package/dist/memory/memoryOps.d.ts.map +1 -0
  298. package/dist/memory/memoryOps.js +606 -0
  299. package/dist/memory/memoryOps.js.map +1 -0
  300. package/dist/orchestration/conflictDetector.d.ts +15 -0
  301. package/dist/orchestration/conflictDetector.d.ts.map +1 -0
  302. package/dist/orchestration/conflictDetector.js +130 -0
  303. package/dist/orchestration/conflictDetector.js.map +1 -0
  304. package/dist/orchestration/decisionEngine.d.ts +177 -0
  305. package/dist/orchestration/decisionEngine.d.ts.map +1 -0
  306. package/dist/orchestration/decisionEngine.js +496 -0
  307. package/dist/orchestration/decisionEngine.js.map +1 -0
  308. package/dist/orchestration/index.d.ts +5 -0
  309. package/dist/orchestration/index.d.ts.map +1 -0
  310. package/dist/orchestration/index.js +5 -0
  311. package/dist/orchestration/index.js.map +1 -0
  312. package/dist/orchestration/taskParser.d.ts +67 -0
  313. package/dist/orchestration/taskParser.d.ts.map +1 -0
  314. package/dist/orchestration/taskParser.js +542 -0
  315. package/dist/orchestration/taskParser.js.map +1 -0
  316. package/dist/orchestration/taskScheduler.d.ts +141 -0
  317. package/dist/orchestration/taskScheduler.d.ts.map +1 -0
  318. package/dist/orchestration/taskScheduler.js +317 -0
  319. package/dist/orchestration/taskScheduler.js.map +1 -0
  320. package/dist/orchestration/workflow.d.ts +145 -0
  321. package/dist/orchestration/workflow.d.ts.map +1 -0
  322. package/dist/orchestration/workflow.js +301 -0
  323. package/dist/orchestration/workflow.js.map +1 -0
  324. package/dist/runners/cliRunner.d.ts +11 -0
  325. package/dist/runners/cliRunner.d.ts.map +1 -0
  326. package/dist/runners/cliRunner.js +194 -0
  327. package/dist/runners/cliRunner.js.map +1 -0
  328. package/dist/support/apiCache.d.ts +85 -0
  329. package/dist/support/apiCache.d.ts.map +1 -0
  330. package/dist/support/apiCache.js +163 -0
  331. package/dist/support/apiCache.js.map +1 -0
  332. package/dist/support/chat.d.ts +3 -0
  333. package/dist/support/chat.d.ts.map +1 -0
  334. package/dist/support/chat.js +304 -0
  335. package/dist/support/chat.js.map +1 -0
  336. package/dist/support/chatBackend.d.ts +25 -0
  337. package/dist/support/chatBackend.d.ts.map +1 -0
  338. package/dist/support/chatBackend.js +235 -0
  339. package/dist/support/chatBackend.js.map +1 -0
  340. package/dist/support/chatMemory.d.ts +71 -0
  341. package/dist/support/chatMemory.d.ts.map +1 -0
  342. package/dist/support/chatMemory.js +119 -0
  343. package/dist/support/chatMemory.js.map +1 -0
  344. package/dist/support/chatTui.d.ts +3 -0
  345. package/dist/support/chatTui.d.ts.map +1 -0
  346. package/dist/support/chatTui.js +998 -0
  347. package/dist/support/chatTui.js.map +1 -0
  348. package/dist/support/costTracker.d.ts +29 -0
  349. package/dist/support/costTracker.d.ts.map +1 -0
  350. package/dist/support/costTracker.js +113 -0
  351. package/dist/support/costTracker.js.map +1 -0
  352. package/dist/support/dashboardHtml.d.ts +3 -0
  353. package/dist/support/dashboardHtml.d.ts.map +1 -0
  354. package/dist/support/dashboardHtml.js +2070 -0
  355. package/dist/support/dashboardHtml.js.map +1 -0
  356. package/dist/support/delete-beliefs.d.ts +2 -0
  357. package/dist/support/delete-beliefs.d.ts.map +1 -0
  358. package/dist/support/delete-beliefs.js +34 -0
  359. package/dist/support/delete-beliefs.js.map +1 -0
  360. package/dist/support/dev.d.ts +55 -0
  361. package/dist/support/dev.d.ts.map +1 -0
  362. package/dist/support/dev.js +298 -0
  363. package/dist/support/dev.js.map +1 -0
  364. package/dist/support/editParser.d.ts +37 -0
  365. package/dist/support/editParser.d.ts.map +1 -0
  366. package/dist/support/editParser.js +365 -0
  367. package/dist/support/editParser.js.map +1 -0
  368. package/dist/support/gitStatus.d.ts +21 -0
  369. package/dist/support/gitStatus.d.ts.map +1 -0
  370. package/dist/support/gitStatus.js +108 -0
  371. package/dist/support/gitStatus.js.map +1 -0
  372. package/dist/support/gitTracker.d.ts +30 -0
  373. package/dist/support/gitTracker.d.ts.map +1 -0
  374. package/dist/support/gitTracker.js +143 -0
  375. package/dist/support/gitTracker.js.map +1 -0
  376. package/dist/support/index.d.ts +13 -0
  377. package/dist/support/index.d.ts.map +1 -0
  378. package/dist/support/index.js +13 -0
  379. package/dist/support/index.js.map +1 -0
  380. package/dist/support/planner.d.ts +58 -0
  381. package/dist/support/planner.d.ts.map +1 -0
  382. package/dist/support/planner.js +395 -0
  383. package/dist/support/planner.js.map +1 -0
  384. package/dist/support/projectMapper.d.ts +46 -0
  385. package/dist/support/projectMapper.d.ts.map +1 -0
  386. package/dist/support/projectMapper.js +259 -0
  387. package/dist/support/projectMapper.js.map +1 -0
  388. package/dist/support/quotaTracker.d.ts +29 -0
  389. package/dist/support/quotaTracker.d.ts.map +1 -0
  390. package/dist/support/quotaTracker.js +89 -0
  391. package/dist/support/quotaTracker.js.map +1 -0
  392. package/dist/support/rateLimiter.d.ts +101 -0
  393. package/dist/support/rateLimiter.d.ts.map +1 -0
  394. package/dist/support/rateLimiter.js +219 -0
  395. package/dist/support/rateLimiter.js.map +1 -0
  396. package/dist/support/rollback.d.ts +61 -0
  397. package/dist/support/rollback.d.ts.map +1 -0
  398. package/dist/support/rollback.js +328 -0
  399. package/dist/support/rollback.js.map +1 -0
  400. package/dist/support/stuckDetector.d.ts +68 -0
  401. package/dist/support/stuckDetector.d.ts.map +1 -0
  402. package/dist/support/stuckDetector.js +174 -0
  403. package/dist/support/stuckDetector.js.map +1 -0
  404. package/dist/support/timeWindow.d.ts +60 -0
  405. package/dist/support/timeWindow.d.ts.map +1 -0
  406. package/dist/support/timeWindow.js +236 -0
  407. package/dist/support/timeWindow.js.map +1 -0
  408. package/dist/support/web.d.ts +11 -0
  409. package/dist/support/web.d.ts.map +1 -0
  410. package/dist/support/web.js +938 -0
  411. package/dist/support/web.js.map +1 -0
  412. package/dist/support/workflowLinear.d.ts +99 -0
  413. package/dist/support/workflowLinear.d.ts.map +1 -0
  414. package/dist/support/workflowLinear.js +257 -0
  415. package/dist/support/workflowLinear.js.map +1 -0
  416. package/dist/support/worktreeManager.d.ts +20 -0
  417. package/dist/support/worktreeManager.d.ts.map +1 -0
  418. package/dist/support/worktreeManager.js +144 -0
  419. package/dist/support/worktreeManager.js.map +1 -0
  420. package/dist/taskState/store.d.ts +101 -0
  421. package/dist/taskState/store.d.ts.map +1 -0
  422. package/dist/taskState/store.js +346 -0
  423. package/dist/taskState/store.js.map +1 -0
  424. package/package.json +70 -0
  425. package/templates/AGENTS.md +432 -0
  426. package/templates/BOOT.md +25 -0
  427. package/templates/BOOTSTRAP.md +50 -0
  428. package/templates/CHANGELOG_AUDIT.md +74 -0
  429. package/templates/HEARTBEAT.md +86 -0
  430. package/templates/IDENTITY.md +27 -0
  431. package/templates/ISSUE_ANALYSIS.md +31 -0
  432. package/templates/PR_LAND.md +75 -0
  433. package/templates/PR_REVIEW.md +97 -0
  434. package/templates/SOUL.dev.md +52 -0
  435. package/templates/SOUL.md +81 -0
  436. package/templates/TOOLS.md +52 -0
  437. package/templates/USER.md +22 -0
@@ -0,0 +1,2070 @@
1
+ // Auto-generated: Dashboard HTML template for OpenSwarm Supervisor
2
+ const DASHBOARD_HTML = `<!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>OpenSwarm :: Supervisor</title>
8
+ <style>
9
+ :root {
10
+ --bg: #0a0c0a;
11
+ --bg2: #0d100d;
12
+ --bg3: #111411;
13
+ --green: #00ff41;
14
+ --green-dim: #003a00;
15
+ --green-mid: #00aa00;
16
+ --green-lo: #005500;
17
+ --cyan: #00ccdd;
18
+ --cyan-dim: #003344;
19
+ --amber: #ffaa00;
20
+ --red: #ff3333;
21
+ --white: #ccddcc;
22
+ --dim: #445544;
23
+ --border: #1a2a1a;
24
+ --border2: #0d1a0d;
25
+ }
26
+ * { box-sizing: border-box; margin: 0; padding: 0; }
27
+ html, body { height: 100%; overflow: hidden; }
28
+ body {
29
+ font-family: 'Cascadia Code', 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
30
+ background: var(--bg);
31
+ color: var(--white);
32
+ font-size: 13px;
33
+ line-height: 1.4;
34
+ }
35
+
36
+ /* ===== SCROLLBAR ===== */
37
+ ::-webkit-scrollbar { width: 4px; }
38
+ ::-webkit-scrollbar-track { background: var(--bg); }
39
+ ::-webkit-scrollbar-thumb { background: var(--green-lo); border-radius: 2px; }
40
+
41
+ /* ===== HEADER ===== */
42
+ header {
43
+ height: 38px;
44
+ background: var(--bg2);
45
+ border-bottom: 1px solid var(--border);
46
+ display: flex;
47
+ align-items: center;
48
+ padding: 0 1rem;
49
+ gap: 0.75rem;
50
+ flex-shrink: 0;
51
+ }
52
+ .hdr-logo {
53
+ color: var(--green);
54
+ font-weight: bold;
55
+ font-size: 14px;
56
+ letter-spacing: 0.15em;
57
+ }
58
+ .hdr-fullname { color: var(--dim); font-size: 11px; letter-spacing: 0.05em; margin-left: 0.25rem; }
59
+ .hdr-sep { color: var(--dim); margin-left: 0.5rem; }
60
+ .hdr-sub { color: var(--dim); font-size: 11px; letter-spacing: 0.1em; }
61
+ .hdr-right { margin-left: auto; display: flex; align-items: center; gap: 0.5rem; }
62
+ #sse-status {
63
+ font-size: 10px;
64
+ padding: 1px 6px;
65
+ border: 1px solid var(--dim);
66
+ color: var(--dim);
67
+ letter-spacing: 0.1em;
68
+ }
69
+ #sse-status.connected { border-color: var(--green); color: var(--green); }
70
+ #sse-status.disconnected { border-color: var(--red); color: var(--red); }
71
+ .btn {
72
+ font-family: inherit;
73
+ font-size: 10px;
74
+ padding: 2px 10px;
75
+ background: transparent;
76
+ border: 1px solid var(--green-lo);
77
+ color: var(--green-mid);
78
+ cursor: pointer;
79
+ letter-spacing: 0.1em;
80
+ transition: all 0.15s;
81
+ }
82
+ .btn:hover:not(:disabled) { border-color: var(--green); color: var(--green); background: var(--green-dim); }
83
+ .btn:disabled { opacity: 0.4; cursor: default; }
84
+ .btn-active { border-color: var(--amber); color: var(--amber); }
85
+ .btn-active:hover:not(:disabled) { background: #332200; border-color: var(--amber); }
86
+ .btn-danger { border-color: #551111; color: var(--red); }
87
+ .btn-danger:hover:not(:disabled) { background: #220000; border-color: var(--red); }
88
+ #turbo-btn { border-color: #553300; color: #ff8800; transition: all 0.3s; }
89
+ #turbo-btn:hover:not(:disabled) { background: #221100; border-color: #ff8800; }
90
+ #turbo-btn.turbo-active { background: #331800; border-color: #ff8800; color: #ffaa00; box-shadow: 0 0 8px rgba(255,136,0,0.3); animation: turbo-pulse 2s infinite; }
91
+ @keyframes turbo-pulse { 0%,100% { box-shadow: 0 0 4px rgba(255,136,0,0.2); } 50% { box-shadow: 0 0 12px rgba(255,136,0,0.5); } }
92
+ .move-to-todo-btn {
93
+ font-family: inherit;
94
+ font-size: 9px;
95
+ padding: 1px 6px;
96
+ background: transparent;
97
+ border: 1px solid var(--cyan-dim);
98
+ color: var(--cyan);
99
+ cursor: pointer;
100
+ margin-left: auto;
101
+ flex-shrink: 0;
102
+ transition: all 0.15s;
103
+ }
104
+ .move-to-todo-btn:hover:not(:disabled) { border-color: var(--cyan); background: var(--cyan-dim); }
105
+ .move-to-todo-btn:disabled { opacity: 0.4; cursor: default; }
106
+ .svc-group { display: flex; align-items: center; gap: 4px; margin-right: 8px; }
107
+ .svc-status {
108
+ font-size: 9px; padding: 1px 6px;
109
+ border: 1px solid var(--dim); color: var(--dim);
110
+ letter-spacing: 0.1em; text-transform: uppercase;
111
+ }
112
+ .svc-status.active { border-color: var(--green); color: var(--green); }
113
+ .svc-status.inactive { border-color: var(--red); color: var(--red); }
114
+ .svc-sep { color: var(--border); margin: 0 2px; }
115
+
116
+ /* ===== STATS BAR ===== */
117
+ .stats-bar {
118
+ height: 36px;
119
+ background: var(--bg2);
120
+ border-bottom: 1px solid var(--border2);
121
+ display: flex;
122
+ align-items: center;
123
+ padding: 0 1rem;
124
+ gap: 1.5rem;
125
+ flex-shrink: 0;
126
+ }
127
+ .stat {
128
+ display: flex;
129
+ align-items: baseline;
130
+ gap: 0.4rem;
131
+ }
132
+ .stat-label { font-size: 10px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.1em; }
133
+ .stat-val { font-size: 13px; font-weight: 500; color: var(--green); }
134
+ .stat-val.amber { color: var(--amber); }
135
+ .stat-val.cyan { color: var(--cyan); }
136
+ .stat-val.red { color: #ff5555; }
137
+ #stat-adapter, #stat-pair-adapters {
138
+ font-size: 10px;
139
+ font-weight: 400;
140
+ letter-spacing: 0.02em;
141
+ }
142
+ .provider-toggle {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ gap: 4px;
146
+ padding: 2px;
147
+ border: 1px solid var(--border);
148
+ background: var(--bg3);
149
+ }
150
+ .provider-btn {
151
+ font-family: inherit;
152
+ font-size: 9px;
153
+ line-height: 1;
154
+ padding: 4px 8px;
155
+ background: transparent;
156
+ border: 1px solid transparent;
157
+ color: var(--dim);
158
+ cursor: pointer;
159
+ letter-spacing: 0.08em;
160
+ text-transform: uppercase;
161
+ }
162
+ .provider-btn:hover:not(:disabled) {
163
+ color: var(--white);
164
+ border-color: var(--border);
165
+ }
166
+ .provider-btn.active {
167
+ color: var(--green);
168
+ border-color: var(--green-lo);
169
+ background: var(--green-dim);
170
+ }
171
+ .stat-divider { color: var(--border); }
172
+
173
+ /* ===== MAIN GRID ===== */
174
+ .main-grid {
175
+ display: grid;
176
+ grid-template-columns: 290px 1fr 340px;
177
+ height: calc(100vh - 74px);
178
+ overflow: hidden;
179
+ }
180
+ .col {
181
+ display: flex;
182
+ flex-direction: column;
183
+ border-right: 1px solid var(--border);
184
+ overflow: hidden;
185
+ }
186
+ .col:last-child { border-right: none; }
187
+
188
+ /* ===== PANEL ===== */
189
+ .panel { display: flex; flex-direction: column; overflow: hidden; flex: 1; }
190
+ .panel + .panel { border-top: 1px solid var(--border); }
191
+ .panel-hdr {
192
+ height: 28px;
193
+ padding: 0 0.75rem;
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 0.5rem;
197
+ background: var(--bg3);
198
+ border-bottom: 1px solid var(--border2);
199
+ flex-shrink: 0;
200
+ font-size: 10px;
201
+ text-transform: uppercase;
202
+ letter-spacing: 0.12em;
203
+ color: var(--dim);
204
+ }
205
+ .panel-hdr-title { color: var(--green-mid); }
206
+ .panel-hdr-badge {
207
+ margin-left: auto;
208
+ font-size: 9px;
209
+ color: var(--dim);
210
+ }
211
+ .panel-body {
212
+ flex: 1;
213
+ overflow-y: auto;
214
+ padding: 0.5rem;
215
+ }
216
+ .empty { color: var(--dim); font-size: 11px; text-align: center; padding: 1.5rem 0.5rem; }
217
+
218
+ /* ===== PROJECTS ===== */
219
+ .proj-card {
220
+ border: 1px solid var(--border);
221
+ margin-bottom: 4px;
222
+ background: var(--bg2);
223
+ }
224
+ .proj-card.disabled { opacity: 0.45; }
225
+ .proj-hdr {
226
+ display: flex;
227
+ align-items: center;
228
+ padding: 5px 7px;
229
+ gap: 6px;
230
+ cursor: pointer;
231
+ user-select: none;
232
+ }
233
+ .proj-hdr:hover { background: var(--green-dim); }
234
+ .proj-arrow { color: var(--dim); font-size: 9px; width: 10px; flex-shrink: 0; }
235
+ .proj-card.expanded .proj-arrow::before { content: "▼"; }
236
+ .proj-card:not(.expanded) .proj-arrow::before { content: "▶"; }
237
+ .proj-info { flex: 1; min-width: 0; }
238
+ .proj-name { color: var(--green); font-size: 12px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
239
+ .proj-path { color: var(--dim); font-size: 9px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
240
+ .proj-counts { display: flex; gap: 3px; }
241
+ .cnt { font-size: 9px; padding: 1px 4px; font-weight: bold; }
242
+ .cnt-run { color: var(--green); border: 1px solid var(--green-lo); }
243
+ .cnt-que { color: var(--amber); border: 1px solid #332200; }
244
+ .cnt-pnd { color: var(--cyan); border: 1px solid var(--cyan-dim); }
245
+ .proj-toggle { flex-shrink: 0; }
246
+ .toggle { position: relative; display: inline-block; width: 30px; height: 16px; }
247
+ .toggle input { opacity: 0; width: 0; height: 0; }
248
+ .slider {
249
+ position: absolute; cursor: pointer;
250
+ top: 0; left: 0; right: 0; bottom: 0;
251
+ background: #111; border: 1px solid var(--dim);
252
+ border-radius: 16px; transition: 0.2s;
253
+ }
254
+ .slider:before {
255
+ position: absolute; content: "";
256
+ height: 10px; width: 10px;
257
+ left: 2px; bottom: 2px;
258
+ background: var(--dim); border-radius: 50%; transition: 0.2s;
259
+ }
260
+ input:checked + .slider { background: var(--green-dim); border-color: var(--green-lo); }
261
+ input:checked + .slider:before { background: var(--green); transform: translateX(14px); }
262
+ .proj-issues { border-top: 1px solid var(--border2); padding: 4px 7px; }
263
+ .issue-sec-label {
264
+ font-size: 9px; color: var(--dim); text-transform: uppercase;
265
+ letter-spacing: 0.1em; margin: 4px 0 2px;
266
+ }
267
+ .issue-row {
268
+ display: flex; align-items: center; gap: 4px;
269
+ padding: 2px 0; font-size: 11px;
270
+ border-bottom: 1px solid var(--border2);
271
+ }
272
+ .issue-row:last-child { border-bottom: none; }
273
+ .git-info { color: var(--dim); font-size: 9px; display: flex; gap: 6px; align-items: center; }
274
+ .git-branch-name { color: var(--cyan); }
275
+ .git-dirty { color: var(--amber); }
276
+ .git-sync { color: var(--dim); }
277
+ .pr-row { display: flex; align-items: center; gap: 4px; padding: 2px 0; font-size: 11px; border-bottom: 1px solid var(--border2); }
278
+ .pr-row:last-child { border-bottom: none; }
279
+ .pr-num { color: var(--cyan); font-size: 9px; min-width: 32px; }
280
+ .pr-branch { color: var(--green-lo); font-size: 9px; max-width: 80px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
281
+ .pr-title { flex: 1; color: var(--white); font-size: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
282
+ .pr-age { color: var(--dim); font-size: 9px; flex-shrink: 0; }
283
+ .idot { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; }
284
+ .idot-run { background: var(--green); }
285
+ .idot-que { background: var(--amber); }
286
+ .idot-pnd { background: var(--dim); }
287
+ .prio { width: 5px; height: 5px; border-radius: 50%; flex-shrink: 0; }
288
+ .prio-1 { background: var(--red); }
289
+ .prio-2 { background: var(--amber); }
290
+ .prio-3 { background: var(--green-mid); }
291
+ .prio-4 { background: var(--dim); }
292
+ .issue-id { color: var(--cyan); font-size: 9px; min-width: 50px; }
293
+ .issue-title { flex: 1; color: var(--white); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
294
+ .issue-row.issue-backlog { opacity: 0.45; }
295
+
296
+ /* ===== PROCESS ROW ===== */
297
+ .proc-row {
298
+ display: flex; align-items: center; gap: 6px;
299
+ padding: 4px 6px; border-bottom: 1px solid var(--border2);
300
+ font-size: 11px;
301
+ }
302
+ .proc-pid { color: var(--cyan); font-size: 10px; min-width: 42px; font-variant-numeric: tabular-nums; }
303
+ .proc-stage { color: var(--green); min-width: 56px; font-weight: bold; text-transform: uppercase; font-size: 10px; }
304
+ .proc-model { color: var(--dim); font-size: 9px; min-width: 56px; }
305
+ .proc-dur { color: var(--amber); font-size: 9px; min-width: 42px; text-align: right; font-variant-numeric: tabular-nums; }
306
+ .proc-activity { font-size: 10px; min-width: 16px; text-align: center; }
307
+ .proc-kill {
308
+ font-family: inherit; font-size: 9px; padding: 1px 5px;
309
+ background: transparent; border: 1px solid #551111; color: var(--red);
310
+ cursor: pointer; margin-left: auto;
311
+ }
312
+ .proc-kill:hover { background: #220000; border-color: var(--red); }
313
+
314
+ /* ===== PIPELINE ===== */
315
+ .stage-row {
316
+ display: flex; align-items: center; gap: 6px;
317
+ padding: 3px 0; border-bottom: 1px solid var(--border2);
318
+ font-size: 11px;
319
+ }
320
+ .sdot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; background: var(--dim); }
321
+ .sdot.start { background: var(--amber); }
322
+ .sdot.complete { background: var(--green); }
323
+ .sdot.fail { background: var(--red); }
324
+ .sname { color: var(--white); min-width: 70px; }
325
+ .srepo { color: var(--green-lo); font-size: 9px; min-width: 50px; max-width: 90px; flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
326
+ .stask { color: var(--cyan); font-size: 10px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
327
+ .selapsed { color: var(--amber); font-size: 9px; flex-shrink: 0; min-width: 36px; text-align: right; }
328
+ .smodel { color: var(--dim); font-size: 9px; flex-shrink: 0; min-width: 56px; text-align: right; }
329
+ .stokens { color: var(--amber); font-size: 9px; flex-shrink: 0; min-width: 80px; text-align: right; white-space: nowrap; }
330
+ .sstatus { margin-left: auto; font-size: 9px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.06em; flex-shrink: 0; }
331
+
332
+ /* ===== LOG TAB BAR ===== */
333
+ .log-tab-bar {
334
+ display: flex; gap: 0; border-bottom: 1px solid #1a2a1a;
335
+ padding: 0 4px; overflow-x: auto; flex-shrink: 0;
336
+ }
337
+ .log-tab {
338
+ background: transparent; border: none; border-bottom: 2px solid transparent;
339
+ color: var(--dim); font-family: inherit; font-size: 10px;
340
+ padding: 4px 8px; cursor: pointer; white-space: nowrap;
341
+ text-transform: uppercase; letter-spacing: .05em;
342
+ }
343
+ .log-tab:hover { color: var(--green-mid); }
344
+ .log-tab.active { color: var(--green); border-bottom-color: var(--green); }
345
+
346
+ /* ===== LOG ===== */
347
+ .log-area { font-size: 11px; line-height: 1.5; padding: 4px 0; }
348
+ .log-line { padding: 3px 8px; display: flex; gap: 6px; align-items: flex-start; border-radius: 2px; margin: 1px 0; }
349
+ .log-line:hover { background: rgba(255,255,255,0.03); }
350
+ .log-line.log-success .ltext { color: var(--green); }
351
+ .log-line.log-fail .ltext { color: var(--red); }
352
+ .log-line.log-warn .ltext { color: var(--amber); }
353
+ .log-line.log-system { opacity: 0.6; }
354
+ .log-line.log-heading { border-top: 1px solid var(--border2); margin-top: 6px; padding-top: 8px; }
355
+ .ltime { color: var(--dim); font-size: 9px; flex-shrink: 0; min-width: 36px; opacity: 0.7; padding-top: 2px; font-variant-numeric: tabular-nums; }
356
+ .licon { flex-shrink: 0; min-width: 14px; text-align: center; font-size: 11px; padding-top: 1px; }
357
+ .ltag { color: var(--green-lo); min-width: 52px; flex-shrink: 0; padding-top: 1px; font-size: 10px; font-weight: 500; }
358
+ .lstage { color: var(--cyan); min-width: 60px; flex-shrink: 0; font-size: 10px; padding-top: 1px; text-transform: uppercase; letter-spacing: 0.03em; opacity: 0.8; }
359
+ .ltext { color: #99aa99; word-break: break-word; white-space: pre-wrap; flex: 1; min-width: 0; }
360
+ .ltext .lhighlight { color: var(--white); font-weight: 500; }
361
+ .ltext .lcost { color: var(--amber); font-size: 10px; }
362
+ .ltext .lfiles { color: var(--cyan); font-size: 10px; }
363
+ .log-line.log-spacer { height: 6px; padding: 0; margin: 0; min-height: 6px; }
364
+ .log-line.log-separator { opacity: 0.2; padding: 0 8px; margin: 4px 0; }
365
+ .log-line.log-separator .ltext { color: var(--dim); }
366
+ .log-line.log-code .ltext { font-family: 'JetBrains Mono', 'Fira Code', monospace; color: var(--cyan); opacity: 0.8; font-size: 10px; }
367
+ .log-line.log-heading2 .ltext { color: var(--white); font-weight: 600; font-size: 12px; }
368
+ .log-line.log-tool .ltext { color: var(--dim); font-style: italic; font-size: 10px; }
369
+
370
+ /* ===== CHAT ===== */
371
+ .chat-col { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
372
+ .chat-messages {
373
+ flex: 1;
374
+ overflow-y: auto;
375
+ padding: 0.5rem;
376
+ display: flex;
377
+ flex-direction: column;
378
+ gap: 6px;
379
+ }
380
+ .chat-line { display: flex; gap: 6px; font-size: 12px; line-height: 1.5; }
381
+ .chat-prefix {
382
+ flex-shrink: 0;
383
+ font-weight: bold;
384
+ }
385
+ .chat-user .chat-prefix { color: var(--amber); }
386
+ .chat-agent .chat-prefix { color: var(--cyan); }
387
+ .chat-text { color: var(--white); white-space: pre-wrap; word-break: break-word; flex: 1; }
388
+ .chat-agent .chat-text { color: var(--white); }
389
+ .chat-user .chat-text { color: var(--amber); }
390
+ .chat-ts { color: var(--dim); font-size: 9px; flex-shrink: 0; align-self: flex-start; padding-top: 2px; }
391
+ .chat-thinking { animation: blink 1s infinite; color: var(--cyan); }
392
+ @keyframes blink { 0%,100% { opacity:1; } 50% { opacity:0.3; } }
393
+
394
+ .chat-input-area {
395
+ border-top: 1px solid var(--border);
396
+ padding: 6px 8px;
397
+ display: flex;
398
+ align-items: center;
399
+ gap: 6px;
400
+ background: var(--bg3);
401
+ flex-shrink: 0;
402
+ }
403
+ .chat-prompt { color: var(--green); font-size: 12px; flex-shrink: 0; }
404
+ .chat-input {
405
+ flex: 1;
406
+ background: transparent;
407
+ border: none;
408
+ outline: none;
409
+ font-family: inherit;
410
+ font-size: 12px;
411
+ color: var(--green);
412
+ caret-color: var(--green);
413
+ }
414
+ .chat-input::placeholder { color: var(--dim); }
415
+ .chat-send {
416
+ font-family: inherit;
417
+ font-size: 10px;
418
+ padding: 2px 8px;
419
+ background: transparent;
420
+ border: 1px solid var(--green-lo);
421
+ color: var(--green-mid);
422
+ cursor: pointer;
423
+ }
424
+ .chat-send:hover { border-color: var(--green); color: var(--green); }
425
+ .chat-send:disabled { opacity: 0.3; cursor: default; }
426
+
427
+ /* ===== REPO PICKER ===== */
428
+ .repo-item {
429
+ display: flex; align-items: center; gap: 8px;
430
+ padding: 5px 12px; cursor: pointer; font-size: 11px;
431
+ }
432
+ .repo-item:hover { background: var(--green-dim); }
433
+ .repo-item-name { color: var(--green); font-weight: bold; }
434
+ .repo-item-path { color: var(--dim); font-size: 10px; flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
435
+ .repo-item-badge { font-size: 9px; padding: 1px 5px; border: 1px solid var(--green-lo); color: var(--green-mid); flex-shrink: 0; }
436
+
437
+ .scan-path-row { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 11px; }
438
+ .scan-path-row .path { color: var(--dim); flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
439
+ .scan-path-badge { font-size: 9px; padding: 1px 5px; border: 1px solid #333; color: #556655; flex-shrink: 0; }
440
+ .scan-path-remove { background: transparent; border: none; color: #553333; cursor: pointer; font-size: 12px; padding: 0 2px; flex-shrink: 0; }
441
+ .scan-path-remove:hover { color: var(--red); }
442
+
443
+ /* ===== TAB BAR (hidden on desktop) ===== */
444
+ .tab-bar { display: none; }
445
+
446
+ /* ===== MOBILE RESPONSIVE ===== */
447
+ @media (max-width: 768px) {
448
+ html, body { overflow: auto; }
449
+
450
+ /* Header */
451
+ header {
452
+ height: auto;
453
+ min-height: 38px;
454
+ flex-wrap: wrap;
455
+ padding: 6px 0.75rem;
456
+ gap: 4px;
457
+ }
458
+ .hdr-fullname { display: none; }
459
+ .hdr-right { width: 100%; justify-content: flex-end; }
460
+ .svc-group .btn { font-size: 0; padding: 4px 8px; min-height: 32px; }
461
+ .svc-group #svc-stop-btn::after { content: "\\23F8"; font-size: 12px; }
462
+ .svc-group #svc-restart-btn::after { content: "\\21BB"; font-size: 12px; }
463
+ #hb-btn { min-height: 32px; }
464
+
465
+ /* Stats bar */
466
+ .stats-bar {
467
+ height: auto;
468
+ min-height: 32px;
469
+ flex-wrap: wrap;
470
+ padding: 4px 0.75rem;
471
+ gap: 0.5rem;
472
+ font-size: 11px;
473
+ }
474
+ .stat-divider { display: none; }
475
+
476
+ /* Tab bar */
477
+ .tab-bar {
478
+ display: flex;
479
+ background: var(--bg2);
480
+ border-bottom: 1px solid var(--border);
481
+ }
482
+ .tab {
483
+ flex: 1;
484
+ font-family: inherit;
485
+ font-size: 11px;
486
+ letter-spacing: 0.1em;
487
+ padding: 10px 0;
488
+ min-height: 44px;
489
+ background: transparent;
490
+ border: none;
491
+ border-bottom: 2px solid transparent;
492
+ color: var(--dim);
493
+ cursor: pointer;
494
+ text-align: center;
495
+ transition: all 0.15s;
496
+ }
497
+ .tab.active {
498
+ color: var(--green);
499
+ border-bottom-color: var(--green);
500
+ }
501
+
502
+ /* Main grid → single column */
503
+ .main-grid {
504
+ display: flex;
505
+ flex-direction: column;
506
+ height: auto;
507
+ min-height: calc(100vh - 160px);
508
+ overflow: visible;
509
+ }
510
+ .col {
511
+ display: none;
512
+ border-right: none;
513
+ overflow: visible;
514
+ min-height: calc(100vh - 200px);
515
+ }
516
+ .col.mob-active {
517
+ display: flex;
518
+ flex: 1;
519
+ }
520
+
521
+ /* Repo picker → fullscreen */
522
+ #repo-picker > div {
523
+ width: 100% !important;
524
+ max-height: 90vh !important;
525
+ margin: 5vh 0 0;
526
+ }
527
+ .repo-item { min-height: 44px; }
528
+
529
+ /* Chat input → sticky bottom */
530
+ .chat-input-area {
531
+ position: sticky;
532
+ bottom: 0;
533
+ z-index: 10;
534
+ min-height: 44px;
535
+ }
536
+ .chat-input { min-height: 32px; font-size: 14px; }
537
+ .chat-send { min-height: 36px; padding: 4px 12px; }
538
+
539
+ /* Touch targets */
540
+ .btn { min-height: 36px; padding: 4px 10px; }
541
+ .proj-hdr { min-height: 44px; padding: 8px 7px; }
542
+ .toggle { width: 40px; height: 22px; }
543
+ .slider:before { height: 14px; width: 14px; left: 3px; bottom: 3px; }
544
+ input:checked + .slider:before { transform: translateX(18px); }
545
+ }
546
+ </style>
547
+ </head>
548
+ <body>
549
+ <!-- HEADER -->
550
+ <header>
551
+ <span class="hdr-logo">OpenSwarm</span>
552
+ <span class="hdr-fullname">: Vector-Encoded General Agent</span>
553
+ <span class="hdr-sep">::</span>
554
+ <span class="hdr-sub">SUPERVISOR</span>
555
+ <div class="hdr-right">
556
+ <div class="svc-group">
557
+ <span class="svc-status" id="svc-status">...</span>
558
+ <span class="svc-sep">│</span>
559
+ <div class="provider-toggle">
560
+ <button class="provider-btn" id="provider-claude" onclick="switchProvider('claude')">Claude</button>
561
+ <button class="provider-btn" id="provider-codex" onclick="switchProvider('codex')">Codex</button>
562
+ </div>
563
+ <span class="svc-sep">│</span>
564
+ <button class="btn" id="turbo-btn" onclick="toggleTurbo()" title="Turbo: 5min heartbeat, 20 daily cap, 4h auto-expire">TURBO</button>
565
+ <span class="svc-sep">│</span>
566
+ <button class="btn btn-danger" id="svc-stop-btn" onclick="svcAction('stop')">⏸ STOP</button>
567
+ <button class="btn" id="svc-restart-btn" onclick="svcAction('restart')">↻ RESTART</button>
568
+ </div>
569
+ <span id="sse-status">CONNECTING</span>
570
+ <button class="btn btn-active" id="hb-btn" onclick="triggerHeartbeat()">▶ HEARTBEAT</button>
571
+ <button class="btn" id="pr-proc-btn" onclick="triggerPRProcessor()">↻ PR REVIEW</button>
572
+ </div>
573
+ </header>
574
+
575
+ <!-- STATS BAR -->
576
+ <div class="stats-bar">
577
+ <div class="stat"><span class="stat-label">RUN</span><span class="stat-val" id="stat-running">0</span></div>
578
+ <span class="stat-divider">│</span>
579
+ <div class="stat"><span class="stat-label">QUEUE</span><span class="stat-val amber" id="stat-queued">0</span></div>
580
+ <span class="stat-divider">│</span>
581
+ <div class="stat"><span class="stat-label">DONE</span><span class="stat-val" id="stat-completed">0</span></div>
582
+ <span class="stat-divider">│</span>
583
+ <div class="stat"><span class="stat-label">PACE</span><span class="stat-val" id="stat-pace">-</span></div>
584
+ <span class="stat-divider">│</span>
585
+ <div class="stat"><span class="stat-label">SSE</span><span class="stat-val cyan" id="stat-sse">-</span></div>
586
+ <span class="stat-divider">│</span>
587
+ <div class="stat"><span class="stat-label">CLI</span><span class="stat-val cyan" id="stat-adapter">-</span></div>
588
+ <span class="stat-divider">│</span>
589
+ <div class="stat"><span class="stat-label">PAIR</span><span class="stat-val cyan" id="stat-pair-adapters">-</span></div>
590
+ <span class="stat-divider">│</span>
591
+ <div class="stat"><span class="stat-label">UPTIME</span><span class="stat-val" id="stat-uptime">-</span></div>
592
+ <span class="stat-divider">│</span>
593
+ <div class="stat"><span class="stat-label">COST</span><span class="stat-val cyan" id="stat-cost">$0.00</span></div>
594
+ <span class="stat-divider">│</span>
595
+ <div class="stat"><span class="stat-label">QUOTA 5h</span><span class="stat-val" id="stat-quota-5h">-</span></div>
596
+ <span class="stat-divider">│</span>
597
+ <div class="stat"><span class="stat-label">QUOTA 7d</span><span class="stat-val" id="stat-quota-7d">-</span></div>
598
+ </div>
599
+
600
+ <!-- TAB BAR (mobile only) -->
601
+ <div class="tab-bar">
602
+ <button class="tab active" data-tab="0">REPOS</button>
603
+ <button class="tab" data-tab="1">PIPELINE</button>
604
+ <button class="tab" data-tab="2">CHAT</button>
605
+ </div>
606
+
607
+ <!-- MAIN GRID -->
608
+ <div class="main-grid">
609
+
610
+ <!-- LEFT: REPOSITORIES -->
611
+ <div class="col">
612
+ <div class="panel">
613
+ <div class="panel-hdr">
614
+ <span class="panel-hdr-title">REPOSITORIES</span>
615
+ <span class="panel-hdr-badge" id="proj-summary"></span>
616
+ <button class="btn" style="margin-left:auto;font-size:9px;padding:1px 6px" onclick="openRepoPicker()">+ ADD</button>
617
+ </div>
618
+ <div class="panel-body" id="project-list">
619
+ <div class="empty">loading...</div>
620
+ </div>
621
+ </div>
622
+ <div class="panel" id="monitor-panel">
623
+ <div class="panel-hdr">
624
+ <span class="panel-hdr-title">MONITORS & PROCESSES</span>
625
+ <span class="panel-hdr-badge" id="monitor-count"></span>
626
+ </div>
627
+ <div class="panel-body" id="monitor-list">
628
+ <div class="empty">no monitors or processes</div>
629
+ </div>
630
+ </div>
631
+ </div>
632
+
633
+ <!-- REPO PICKER OVERLAY -->
634
+ <div id="repo-picker" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:100;align-items:center;justify-content:center">
635
+ <div style="background:#0d100d;border:1px solid #1a2a1a;width:500px;max-height:70vh;display:flex;flex-direction:column">
636
+ <div style="padding:8px 12px;border-bottom:1px solid #1a2a1a;display:flex;align-items:center;gap:8px">
637
+ <span style="color:#00aa00;font-size:11px;text-transform:uppercase;letter-spacing:.1em">ADD REPOSITORY</span>
638
+ <button onclick="closeRepoPicker()" style="margin-left:auto;background:transparent;border:none;color:#445544;cursor:pointer;font-size:14px">✕</button>
639
+ </div>
640
+ <div style="padding:6px 12px;border-bottom:1px solid #1a2a1a">
641
+ <input id="repo-search" type="text" placeholder="filter repositories..."
642
+ style="width:100%;background:transparent;border:none;outline:none;font-family:inherit;font-size:12px;color:#00ff41;caret-color:#00ff41"
643
+ oninput="filterRepos(this.value)" onkeydown="if(event.key==='Escape')closeRepoPicker()">
644
+ </div>
645
+ <div id="repo-picker-list" style="overflow-y:auto;flex:1;padding:4px 0"></div>
646
+ <div id="scan-paths-section" style="border-top:1px solid #1a2a1a;padding:8px 12px">
647
+ <div style="color:#00aa00;font-size:10px;text-transform:uppercase;letter-spacing:.1em;margin-bottom:6px">SCAN PATHS</div>
648
+ <div id="scan-paths-list"></div>
649
+ <div style="display:flex;gap:4px;margin-top:6px">
650
+ <input id="scan-path-input" type="text" placeholder="/path/to/scan"
651
+ style="flex:1;background:transparent;border:1px solid #1a2a1a;outline:none;font-family:inherit;font-size:11px;color:#00ff41;padding:3px 6px;caret-color:#00ff41"
652
+ onkeydown="if(event.key==='Enter')addScanPath()">
653
+ <button class="btn" style="font-size:9px;padding:1px 6px" onclick="addScanPath()">+ ADD</button>
654
+ </div>
655
+ </div>
656
+ </div>
657
+ </div>
658
+
659
+ <!-- MIDDLE: PIPELINE + LOG -->
660
+ <div class="col">
661
+ <div class="panel" style="flex: 0 0 38%">
662
+ <div class="panel-hdr">
663
+ <span class="panel-hdr-title">PIPELINE</span>
664
+ <span class="panel-hdr-badge" id="stage-count"></span>
665
+ </div>
666
+ <div class="panel-body" id="stage-list">
667
+ <div class="empty">no pipeline events</div>
668
+ </div>
669
+ </div>
670
+ <div class="panel">
671
+ <div class="panel-hdr">
672
+ <span class="panel-hdr-title">LIVE LOG</span>
673
+ <span class="panel-hdr-badge" id="log-count"></span>
674
+ </div>
675
+ <div class="log-tab-bar" id="log-tab-bar">
676
+ <button class="log-tab active" data-task="all" onclick="selectLogTab(null)">ALL</button>
677
+ </div>
678
+ <div class="panel-body log-area" id="log-list">
679
+ <div class="empty">no log output</div>
680
+ </div>
681
+ </div>
682
+ </div>
683
+
684
+ <!-- RIGHT: CHAT -->
685
+ <div class="col">
686
+ <!-- PR PROCESSOR -->
687
+ <div class="panel" style="flex: 0 0 auto;">
688
+ <div class="panel-hdr">
689
+ <span class="panel-hdr-title">PR PROCESSOR</span>
690
+ <span class="panel-hdr-badge" id="pr-proc-badge"></span>
691
+ </div>
692
+ <div class="panel-body" style="font-size: 11px; line-height: 1.5;">
693
+ <div id="pr-proc-body" style="color: var(--dim);">Loading...</div>
694
+ </div>
695
+ </div>
696
+
697
+ <!-- STUCK/FAILED ISSUES -->
698
+ <div class="panel" style="flex: 0 0 auto; max-height: 200px;">
699
+ <div class="panel-hdr">
700
+ <span class="panel-hdr-title">⚠ STUCK/FAILED</span>
701
+ <span class="panel-hdr-badge" id="stuck-badge">0</span>
702
+ <button class="btn" style="margin-left: 0.5rem; font-size: 9px; padding: 1px 6px;" onclick="restartStuckIssues()" id="restart-stuck-btn">↻ RESTART ALL</button>
703
+ </div>
704
+ <div class="panel-body" style="font-size: 10px; line-height: 1.4; overflow-y: auto;">
705
+ <div id="stuck-list" style="color: var(--dim);">Loading...</div>
706
+ </div>
707
+ </div>
708
+
709
+ <!-- AGENT CHAT -->
710
+ <div class="panel-hdr">
711
+ <span class="panel-hdr-title">AGENT CHAT</span>
712
+ <span class="panel-hdr-badge" id="chat-status"></span>
713
+ </div>
714
+ <div class="chat-col">
715
+ <div class="chat-messages" id="chat-messages"></div>
716
+ <div class="chat-input-area">
717
+ <span class="chat-prompt">&gt;</span>
718
+ <input
719
+ class="chat-input" id="chat-input"
720
+ type="text" placeholder="message OpenSwarm..."
721
+ onkeydown="if(event.key==='Enter')sendChat()"
722
+ >
723
+ <button class="chat-send" id="chat-send" onclick="sendChat()">SEND</button>
724
+ </div>
725
+ </div>
726
+ </div>
727
+
728
+ </div>
729
+
730
+ <script>
731
+ const MAX_LOG = 200;
732
+ const MAX_STAGE = 100;
733
+
734
+ let projects = [];
735
+ let expandedProjects = new Set();
736
+ let knowledgeCache = {};
737
+ let logLines = [];
738
+ let selectedLogTaskId = null; // null = ALL, string = specific taskId
739
+ let stageRows = [];
740
+ let chatBusy = false;
741
+ let totalCostUsd = 0;
742
+ const taskProjectMap = new Map();
743
+ // taskId → { title, issueIdentifier } for pipeline display
744
+ const taskTitleMap = new Map();
745
+ // taskId → start timestamp for elapsed time
746
+ const taskStartMap = new Map();
747
+
748
+ // ---- SSE ----
749
+ function connectSSE(skipReplay) {
750
+ const url = skipReplay ? "/api/events?skipReplay=1" : "/api/events";
751
+ const es = new EventSource(url);
752
+ const statusEl = document.getElementById("sse-status");
753
+ es.onopen = () => { statusEl.textContent = "LIVE"; statusEl.className = "connected"; };
754
+ es.onmessage = e => {
755
+ let ev; try { ev = JSON.parse(e.data); } catch { return; }
756
+ handleEvent(ev);
757
+ };
758
+ es.onerror = () => {
759
+ statusEl.textContent = "RECONNECTING"; statusEl.className = "disconnected";
760
+ es.close(); setTimeout(function() { connectSSE(false); }, 3000);
761
+ };
762
+ }
763
+
764
+ function handleEvent(ev) {
765
+ switch (ev.type) {
766
+ case "stats": updateStats(ev.data); break;
767
+ case "task:queued":
768
+ taskProjectMap.set(ev.data.taskId, ev.data.projectPath);
769
+ taskTitleMap.set(ev.data.taskId, { title: ev.data.title, issueIdentifier: ev.data.issueIdentifier });
770
+ updateProjectTask(ev.data.projectPath, ev.data.taskId, ev.data.title, ev.data.priority, "queued");
771
+ break;
772
+ case "task:started": {
773
+ const p = taskProjectMap.get(ev.data.taskId);
774
+ if (ev.data.title) taskTitleMap.set(ev.data.taskId, { title: ev.data.title, issueIdentifier: ev.data.issueIdentifier });
775
+ taskStartMap.set(ev.data.taskId, Date.now());
776
+ if (p) updateProjectTask(p, ev.data.taskId, ev.data.title, null, "running");
777
+ break;
778
+ }
779
+ case "task:completed": {
780
+ const p = taskProjectMap.get(ev.data.taskId);
781
+ if (p) removeProjectTask(p, ev.data.taskId);
782
+ break;
783
+ }
784
+ case "pipeline:stage": addStageRow(ev.data); break;
785
+ case "pipeline:iteration":
786
+ addStageRow({ taskId: ev.data.taskId, stage: "iter #" + ev.data.iteration, status: "start" });
787
+ break;
788
+ case "log": addLogLine(ev.data); break;
789
+ case "project:toggled": {
790
+ const p = projects.find(x => x.path === ev.data.projectPath);
791
+ if (p) { p.enabled = ev.data.enabled; renderProjects(); }
792
+ break;
793
+ }
794
+ case "task:cost": {
795
+ totalCostUsd += ev.data.cost?.costUsd ?? 0;
796
+ document.getElementById("stat-cost").textContent = "$" + totalCostUsd.toFixed(2);
797
+ break;
798
+ }
799
+ case "chat:agent": appendChatMsg("agent", ev.data.text, null, ev.data.ts); break;
800
+ case "monitor:checked":
801
+ case "monitor:stateChange":
802
+ fetchMonitors();
803
+ break;
804
+ case "process:spawn":
805
+ fetchProcesses();
806
+ addLogLine({ taskId: ev.data.taskId || "system", stage: ev.data.stage || "spawn", line: "Process spawned PID=" + ev.data.pid + " stage=" + ev.data.stage + (ev.data.model ? " model=" + ev.data.model : "") });
807
+ break;
808
+ case "process:exit":
809
+ fetchProcesses();
810
+ addLogLine({ taskId: "system", stage: "exit", line: "Process exited PID=" + ev.data.pid + " code=" + ev.data.exitCode + " duration=" + (ev.data.durationMs / 1000).toFixed(1) + "s" });
811
+ break;
812
+ case "heartbeat": {
813
+ const btn = document.getElementById("hb-btn");
814
+ btn.disabled = false; btn.textContent = "▶ HEARTBEAT";
815
+ break;
816
+ }
817
+ case "pr_processor_start":
818
+ case "pr_processor_end":
819
+ case "pr_processor_pr":
820
+ fetchPRProcessorStatus();
821
+ break;
822
+ }
823
+ }
824
+
825
+ // ---- Stats ----
826
+ function updateStats(data) {
827
+ function shortModel(model) {
828
+ if (!model) return "-";
829
+ return model.length > 18 ? model.slice(0, 15) + "..." : model;
830
+ }
831
+
832
+ document.getElementById("stat-running").textContent = data.runningTasks ?? 0;
833
+ document.getElementById("stat-queued").textContent = data.queuedTasks ?? 0;
834
+ document.getElementById("stat-completed").textContent = data.completedToday ?? 0;
835
+ const defaultAdapter = data.adapters?.defaultAdapter ?? "-";
836
+ const workerAdapter = data.adapters?.worker?.adapter ?? "-";
837
+ const workerModel = shortModel(data.adapters?.worker?.model);
838
+ const reviewerAdapter = data.adapters?.reviewer?.adapter ?? "-";
839
+ const reviewerModel = shortModel(data.adapters?.reviewer?.model);
840
+ const chatModel = workerModel || "-";
841
+ document.getElementById("stat-adapter").textContent = defaultAdapter;
842
+ document.getElementById("stat-pair-adapters").textContent =
843
+ "W " + workerAdapter + ":" + workerModel + " / R " + reviewerAdapter + ":" + reviewerModel;
844
+ document.getElementById("chat-status").textContent = defaultAdapter + ":" + chatModel;
845
+ document.getElementById("provider-claude").classList.toggle("active", defaultAdapter === "claude");
846
+ document.getElementById("provider-codex").classList.toggle("active", defaultAdapter === "codex");
847
+ if (data.uptime != null) {
848
+ const s = Math.floor(data.uptime / 1000);
849
+ const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60), ss = s % 60;
850
+ document.getElementById("stat-uptime").textContent =
851
+ (h ? h + "h " : "") + (m ? m + "m " : "") + ss + "s";
852
+ }
853
+ // Turbo mode
854
+ const turboBtn = document.getElementById("turbo-btn");
855
+ if (turboBtn) {
856
+ turboBtn.classList.toggle("turbo-active", !!data.turboMode);
857
+ if (data.turboMode && data.turboExpiresAt) {
858
+ const remainMin = Math.max(0, Math.round((data.turboExpiresAt - Date.now()) / 60000));
859
+ turboBtn.textContent = "TURBO " + remainMin + "m";
860
+ } else {
861
+ turboBtn.textContent = "TURBO";
862
+ }
863
+ }
864
+ // Daily pace
865
+ const paceEl = document.getElementById("stat-pace");
866
+ if (paceEl && data.dailyPace) {
867
+ const cap = data.turboMode ? 20 : 6;
868
+ paceEl.textContent = data.dailyPace.completedToday + "/" + cap;
869
+ paceEl.className = "stat-val" + (data.turboMode ? " amber" : "");
870
+ }
871
+ }
872
+
873
+ // ---- Service control ----
874
+ async function fetchSvcStatus() {
875
+ try {
876
+ const res = await fetch("/api/service/status");
877
+ const data = await res.json();
878
+ const el = document.getElementById("svc-status");
879
+ const status = data.status || "unknown";
880
+ el.textContent = status;
881
+ el.className = "svc-status " + (status === "active" ? "active" : "inactive");
882
+ } catch {
883
+ const el = document.getElementById("svc-status");
884
+ el.textContent = "unknown";
885
+ el.className = "svc-status inactive";
886
+ }
887
+ }
888
+
889
+ // ---- Stuck/Failed Issues ----
890
+ async function fetchStuckIssues() {
891
+ try {
892
+ const res = await fetch("/api/stuck-issues");
893
+ const data = await res.json();
894
+ const list = document.getElementById("stuck-list");
895
+ const badge = document.getElementById("stuck-badge");
896
+
897
+ const totalStuck = data.stuckIssues?.length ?? 0;
898
+ const totalFailed = data.failedIssues?.length ?? 0;
899
+ const total = totalStuck + totalFailed;
900
+
901
+ badge.textContent = total;
902
+ badge.style.color = total > 0 ? "var(--red)" : "var(--dim)";
903
+
904
+ if (total === 0) {
905
+ list.innerHTML = '<div style="color: var(--green-mid); padding: 4px;">✓ All issues healthy</div>';
906
+ return;
907
+ }
908
+
909
+ let html = '';
910
+
911
+ // Stuck issues (In Progress for >7 days)
912
+ if (totalStuck > 0) {
913
+ html += '<div style="color: var(--amber); font-weight: bold; margin-bottom: 4px; font-size: 9px; text-transform: uppercase;">⏱ Stuck (' + totalStuck + ')</div>';
914
+ data.stuckIssues.forEach(issue => {
915
+ const priorityColor = issue.priority === 1 ? 'var(--red)' : issue.priority === 2 ? 'var(--amber)' : 'var(--dim)';
916
+ html += '<div style="margin-bottom: 6px; padding: 4px; border-left: 2px solid ' + priorityColor + '; background: rgba(255, 170, 0, 0.05);">';
917
+ html += '<div style="color: var(--white); font-size: 10px; margin-bottom: 2px;">' + issue.identifier + ': ' + issue.title.substring(0, 40) + (issue.title.length > 40 ? '...' : '') + '</div>';
918
+ html += '<div style="color: var(--amber); font-size: 9px;">' + issue.reason + '</div>';
919
+ if (issue.project?.name) {
920
+ html += '<div style="color: var(--dim); font-size: 9px; margin-top: 2px;">📁 ' + issue.project.name + '</div>';
921
+ }
922
+ html += '</div>';
923
+ });
924
+ }
925
+
926
+ // Failed issues (retry, failed, blocked labels)
927
+ if (totalFailed > 0) {
928
+ if (totalStuck > 0) html += '<div style="height: 8px;"></div>';
929
+ html += '<div style="color: var(--red); font-weight: bold; margin-bottom: 4px; font-size: 9px; text-transform: uppercase;">✖ Failed (' + totalFailed + ')</div>';
930
+ data.failedIssues.forEach(issue => {
931
+ const priorityColor = issue.priority === 1 ? 'var(--red)' : issue.priority === 2 ? 'var(--amber)' : 'var(--dim)';
932
+ html += '<div style="margin-bottom: 6px; padding: 4px; border-left: 2px solid ' + priorityColor + '; background: rgba(255, 51, 51, 0.05);">';
933
+ html += '<div style="color: var(--white); font-size: 10px; margin-bottom: 2px;">' + issue.identifier + ': ' + issue.title.substring(0, 40) + (issue.title.length > 40 ? '...' : '') + '</div>';
934
+ html += '<div style="color: var(--red); font-size: 9px;">' + issue.reason + '</div>';
935
+ if (issue.project?.name) {
936
+ html += '<div style="color: var(--dim); font-size: 9px; margin-top: 2px;">📁 ' + issue.project.name + '</div>';
937
+ }
938
+ html += '</div>';
939
+ });
940
+ }
941
+
942
+ list.innerHTML = html;
943
+ } catch (err) {
944
+ console.error("Failed to fetch stuck issues:", err);
945
+ document.getElementById("stuck-list").innerHTML = '<div style="color: var(--red);">Error loading</div>';
946
+ }
947
+ }
948
+
949
+ // ---- PR Processor Status ----
950
+ async function fetchPRProcessorStatus() {
951
+ try {
952
+ const res = await fetch("/api/pr-processor-status");
953
+ const data = await res.json();
954
+ const body = document.getElementById("pr-proc-body");
955
+ const badge = document.getElementById("pr-proc-badge");
956
+
957
+ if (!data) {
958
+ body.innerHTML = '<div style="color: var(--dim);">Not configured</div>';
959
+ badge.textContent = "OFF";
960
+ badge.style.color = "var(--dim)";
961
+ return;
962
+ }
963
+
964
+ const status = data.processing ? "RUNNING" : "IDLE";
965
+ badge.textContent = status;
966
+ badge.style.color = data.processing ? "var(--green)" : "var(--cyan)";
967
+
968
+ const formatTime = (ts) => {
969
+ if (!ts) return "N/A";
970
+ const d = new Date(ts);
971
+ return d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
972
+ };
973
+
974
+ let html = '<div style="display: flex; flex-direction: column; gap: 6px;">';
975
+ html += '<div><span style="color: var(--dim);">Schedule:</span> <span style="color: var(--text);">' + (data.schedule || "N/A") + '</span></div>';
976
+ html += '<div><span style="color: var(--dim);">Repos:</span> <span style="color: var(--text);">' + (data.repos?.length || 0) + '</span></div>';
977
+
978
+ if (data.currentPR) {
979
+ html += '<div><span style="color: var(--amber);">Processing:</span> <span style="color: var(--text); font-family: monospace; font-size: 10px;">' + data.currentPR + '</span></div>';
980
+ }
981
+
982
+ html += '<div><span style="color: var(--dim);">Last run:</span> <span style="color: var(--text);">' + formatTime(data.lastRun) + '</span></div>';
983
+ html += '<div><span style="color: var(--dim);">Next run:</span> <span style="color: var(--text);">' + formatTime(data.nextRun) + '</span></div>';
984
+
985
+ if (data.conflictResolverEnabled) {
986
+ html += '<div style="color: var(--green); font-size: 10px; margin-top: 4px;">✓ Conflict Resolver: ON</div>';
987
+ }
988
+
989
+ html += '</div>';
990
+ body.innerHTML = html;
991
+ } catch (e) {
992
+ const body = document.getElementById("pr-proc-body");
993
+ body.innerHTML = '<div style="color: var(--red);">Error: ' + e.message + '</div>';
994
+ }
995
+ }
996
+
997
+ async function svcAction(action) {
998
+ const label = action === "stop" ? "STOP" : "RESTART";
999
+ if (!confirm("Are you sure you want to " + label + " the service?")) return;
1000
+ const btnId = action === "stop" ? "svc-stop-btn" : "svc-restart-btn";
1001
+ const btn = document.getElementById(btnId);
1002
+ btn.disabled = true;
1003
+ try {
1004
+ await fetch("/api/service/" + action, { method: "POST" });
1005
+ addLogLine({ taskId: "system", stage: "service", line: "Service " + action + " requested" });
1006
+ } catch(e) {
1007
+ addLogLine({ taskId: "system", stage: "error", line: "Service " + action + " failed: " + e.message });
1008
+ }
1009
+ btn.disabled = false;
1010
+ setTimeout(fetchSvcStatus, 2000);
1011
+ }
1012
+
1013
+ // ---- Heartbeat trigger ----
1014
+ async function triggerHeartbeat() {
1015
+ const btn = document.getElementById("hb-btn");
1016
+ btn.disabled = true; btn.textContent = "⟳ RUNNING";
1017
+ addLogLine({ taskId: "system", stage: "manual", line: "Heartbeat triggered by user" });
1018
+ try {
1019
+ await fetch("/api/heartbeat", { method: "POST" });
1020
+ } catch(e) {
1021
+ addLogLine({ taskId: "system", stage: "error", line: "Heartbeat failed: " + e.message });
1022
+ btn.disabled = false; btn.textContent = "▶ HEARTBEAT";
1023
+ }
1024
+ }
1025
+
1026
+ async function toggleTurbo() {
1027
+ const btn = document.getElementById("turbo-btn");
1028
+ const isActive = btn.classList.contains("turbo-active");
1029
+ const newState = !isActive;
1030
+ if (newState && !confirm("Enable TURBO mode? (5min heartbeat, 20 daily cap, auto-expires in 4h)")) return;
1031
+ btn.disabled = true;
1032
+ try {
1033
+ const res = await fetch("/api/turbo", {
1034
+ method: "POST",
1035
+ headers: { "Content-Type": "application/json" },
1036
+ body: JSON.stringify({ enabled: newState })
1037
+ });
1038
+ if (!res.ok) throw new Error("Failed");
1039
+ addLogLine({ taskId: "system", stage: "turbo", line: newState ? "TURBO MODE ON" : "TURBO MODE OFF" });
1040
+ const stats = await fetch("/api/stats").then(r => r.json());
1041
+ updateStats(stats);
1042
+ } catch (e) {
1043
+ addLogLine({ taskId: "system", stage: "error", line: "Turbo toggle failed: " + e.message });
1044
+ }
1045
+ btn.disabled = false;
1046
+ }
1047
+
1048
+ async function switchProvider(provider) {
1049
+ try {
1050
+ const res = await fetch("/api/provider", {
1051
+ method: "POST",
1052
+ headers: { "Content-Type": "application/json" },
1053
+ body: JSON.stringify({ provider })
1054
+ });
1055
+ if (!res.ok) {
1056
+ const data = await res.json().catch(() => ({}));
1057
+ throw new Error(data.error || "Failed to switch provider");
1058
+ }
1059
+ addLogLine({ taskId: "system", stage: "provider", line: "Provider switched to " + provider });
1060
+ const stats = await fetch("/api/stats").then(r => r.json());
1061
+ updateStats(stats);
1062
+ } catch (e) {
1063
+ addLogLine({ taskId: "system", stage: "error", line: "Provider switch failed: " + e.message });
1064
+ }
1065
+ }
1066
+
1067
+ // ---- PR Processor trigger ----
1068
+ async function triggerPRProcessor() {
1069
+ const btn = document.getElementById("pr-proc-btn");
1070
+ btn.disabled = true; btn.textContent = "⟳ PROCESSING";
1071
+ addLogLine({ taskId: "system", stage: "manual", line: "PR Processor triggered by user" });
1072
+ try {
1073
+ const res = await fetch("/api/trigger-pr-processor", { method: "POST" });
1074
+ if (!res.ok) {
1075
+ const data = await res.json();
1076
+ throw new Error(data.error || "Failed to trigger PR processor");
1077
+ }
1078
+ addLogLine({ taskId: "system", stage: "manual", line: "PR Processor started successfully" });
1079
+ setTimeout(() => {
1080
+ btn.disabled = false;
1081
+ btn.textContent = "↻ PR REVIEW";
1082
+ }, 3000);
1083
+ } catch(e) {
1084
+ addLogLine({ taskId: "system", stage: "error", line: "PR Processor failed: " + e.message });
1085
+ btn.disabled = false; btn.textContent = "↻ PR REVIEW";
1086
+ }
1087
+ }
1088
+
1089
+ // ---- Restart stuck issues ----
1090
+ async function restartStuckIssues() {
1091
+ if (!confirm("Move all stuck/failed issues to Todo?")) return;
1092
+ const btn = document.getElementById("restart-stuck-btn");
1093
+ btn.disabled = true;
1094
+ btn.textContent = "⟳ PROCESSING...";
1095
+
1096
+ try {
1097
+ const res = await fetch("/api/stuck-issues");
1098
+ const data = await res.json();
1099
+ const allIssues = [...data.stuckIssues, ...data.failedIssues];
1100
+
1101
+ let success = 0;
1102
+ let failed = 0;
1103
+
1104
+ for (const issue of allIssues) {
1105
+ try {
1106
+ const moveRes = await fetch("/api/issue/move-to-todo", {
1107
+ method: "POST",
1108
+ headers: { "Content-Type": "application/json" },
1109
+ body: JSON.stringify({ issueId: issue.id })
1110
+ });
1111
+
1112
+ if (moveRes.ok) {
1113
+ success++;
1114
+ addLogLine({ taskId: "system", stage: "stuck", line: "Moved " + issue.identifier + " to Todo" });
1115
+ } else {
1116
+ failed++;
1117
+ }
1118
+ } catch (e) {
1119
+ failed++;
1120
+ }
1121
+ }
1122
+
1123
+ addLogLine({ taskId: "system", stage: "stuck", line: "Restart complete: " + success + " moved, " + failed + " failed" });
1124
+ setTimeout(fetchStuckIssues, 1000);
1125
+ } catch (e) {
1126
+ addLogLine({ taskId: "system", stage: "error", line: "Failed to restart stuck issues: " + e.message });
1127
+ }
1128
+
1129
+ btn.disabled = false;
1130
+ btn.textContent = "↻ RESTART ALL";
1131
+ }
1132
+
1133
+ // ---- Project task updates ----
1134
+ function updateProjectTask(projectPath, taskId, title, priority, status) {
1135
+ const p = projects.find(x => x.path === projectPath);
1136
+ if (!p) return;
1137
+
1138
+ // Get issueIdentifier from taskTitleMap
1139
+ const taskInfo = taskTitleMap.get(taskId);
1140
+ const issueIdentifier = taskInfo?.issueIdentifier;
1141
+
1142
+ if (status === "running") {
1143
+ p.queued = p.queued.filter(t => t.id !== taskId);
1144
+ if (!p.running.find(t => t.id === taskId)) {
1145
+ p.running.push({ id: taskId, title, priority, issueIdentifier });
1146
+ }
1147
+ } else {
1148
+ if (!p.queued.find(t => t.id === taskId)) {
1149
+ p.queued.push({ id: taskId, title, priority, issueIdentifier });
1150
+ }
1151
+ }
1152
+ renderProjects();
1153
+ }
1154
+ function removeProjectTask(projectPath, taskId) {
1155
+ const p = projects.find(x => x.path === projectPath);
1156
+ if (!p) return;
1157
+ p.running = p.running.filter(t => t.id !== taskId);
1158
+ p.queued = p.queued.filter(t => t.id !== taskId);
1159
+ renderProjects();
1160
+ }
1161
+
1162
+ // ---- Toggle project ----
1163
+ async function toggleProject(projectPath, enabled) {
1164
+ const p = projects.find(x => x.path === projectPath);
1165
+ if (p) p.enabled = enabled;
1166
+ renderProjects();
1167
+ try {
1168
+ await fetch("/api/projects/toggle", {
1169
+ method: "POST",
1170
+ headers: { "Content-Type": "application/json" },
1171
+ body: JSON.stringify({ projectPath, enabled }),
1172
+ });
1173
+ } catch(e) {
1174
+ if (p) p.enabled = !enabled;
1175
+ renderProjects();
1176
+ }
1177
+ }
1178
+
1179
+ // ---- Render Projects ----
1180
+ function renderProjects() {
1181
+ const el = document.getElementById("project-list");
1182
+ const sumEl = document.getElementById("proj-summary");
1183
+ if (!projects.length) { el.innerHTML = "<div class=\\"empty\\">no repositories</div>"; return; }
1184
+ const on = projects.filter(p => p.enabled).length;
1185
+ if (sumEl) sumEl.textContent = on + "/" + projects.length;
1186
+
1187
+ el.innerHTML = projects.map(p => {
1188
+ // Use path as key, fall back to __n:name for unmapped projects
1189
+ const key = p.path || ("__n:" + p.name);
1190
+ const expanded = expandedProjects.has(key);
1191
+ const checked = p.enabled ? "checked" : "";
1192
+ const dCls = p.enabled ? "" : " disabled";
1193
+ const eCls = expanded ? " expanded" : "";
1194
+ // PRs always visible (not gated by expand)
1195
+ let prsHtml = "";
1196
+ if (p.prs && p.prs.length) {
1197
+ prsHtml = "<div class=\\"proj-issues\\">" +
1198
+ "<div class=\\"issue-sec-label\\">open PRs (" + p.prs.length + ")</div>" +
1199
+ p.prs.map(function(pr) {
1200
+ return "<div class=\\"pr-row\\">" +
1201
+ "<span class=\\"pr-num\\">#" + pr.number + "</span>" +
1202
+ "<span class=\\"pr-branch\\" title=\\"" + escapeAttr(pr.branch) + "\\">" + escapeHtml(pr.branch) + "</span>" +
1203
+ "<span class=\\"pr-title\\" title=\\"" + escapeAttr(pr.title) + "\\">" + escapeHtml(pr.title) + "</span>" +
1204
+ "<span class=\\"pr-age\\">" + fmtAge(pr.updatedAt) + "</span>" +
1205
+ "</div>";
1206
+ }).join("") +
1207
+ "</div>";
1208
+ }
1209
+ let issuesHtml = "";
1210
+ if (expanded) {
1211
+ const secs = [];
1212
+ if (p.running.length) secs.push(
1213
+ "<div class=\\"issue-sec-label\\">running</div>" +
1214
+ p.running.map(t => issueRow(t, "idot-run")).join("")
1215
+ );
1216
+ if (p.queued.length) secs.push(
1217
+ "<div class=\\"issue-sec-label\\">queued</div>" +
1218
+ p.queued.map(t => issueRow(t, "idot-que")).join("")
1219
+ );
1220
+ if (p.pending.length) {
1221
+ var stateOrder = ["In Review", "In Progress", "Todo", "Backlog"];
1222
+ var byState = {};
1223
+ for (var ti = 0; ti < p.pending.length; ti++) {
1224
+ var st = p.pending[ti].linearState || "Todo";
1225
+ if (!byState[st]) byState[st] = [];
1226
+ byState[st].push(p.pending[ti]);
1227
+ }
1228
+ for (var si = 0; si < stateOrder.length; si++) {
1229
+ var sn = stateOrder[si];
1230
+ if (!byState[sn] || !byState[sn].length) continue;
1231
+ secs.push(
1232
+ "<div class=\\"issue-sec-label\\">" + sn.toLowerCase() + " (" + byState[sn].length + ")</div>" +
1233
+ byState[sn].map(t => issueRow(t, "idot-pnd")).join("")
1234
+ );
1235
+ }
1236
+ var otherKeys = Object.keys(byState);
1237
+ for (var oi = 0; oi < otherKeys.length; oi++) {
1238
+ if (stateOrder.indexOf(otherKeys[oi]) === -1) {
1239
+ secs.push(
1240
+ "<div class=\\"issue-sec-label\\">" + otherKeys[oi].toLowerCase() + " (" + byState[otherKeys[oi]].length + ")</div>" +
1241
+ byState[otherKeys[oi]].map(t => issueRow(t, "idot-pnd")).join("")
1242
+ );
1243
+ }
1244
+ }
1245
+ }
1246
+ if (!secs.length) secs.push("<div class=\\"empty\\" style=\\"padding:4px\\">no issues</div>");
1247
+ // Knowledge graph health info (if cached)
1248
+ var kgData = knowledgeCache[p.name] || knowledgeCache[p.path];
1249
+ if (kgData && kgData.summary) {
1250
+ var s = kgData.summary;
1251
+ secs.push(
1252
+ "<div class=\\"issue-sec-label\\">code health</div>" +
1253
+ "<div style=\\"padding:2px 8px;font-size:10px;color:#88aa88\\">" +
1254
+ "modules:" + s.totalModules + " tests:" + s.totalTestFiles +
1255
+ " untested:" + s.untestedModules.length +
1256
+ " churn:" + (s.avgChurnScore || 0).toFixed(2) +
1257
+ (s.hotModules.length ? " hot:" + s.hotModules.slice(0,3).map(function(m){return m.split("/").pop()}).join(",") : "") +
1258
+ "</div>"
1259
+ );
1260
+ }
1261
+ issuesHtml = "<div class=\\"proj-issues\\">" + secs.join("") + "</div>";
1262
+ }
1263
+
1264
+ return (
1265
+ "<div class=\\"proj-card" + dCls + eCls + "\\" data-key=\\"" + escapeAttr(key) + "\\">" +
1266
+ "<div class=\\"proj-hdr\\" data-key=\\"" + escapeAttr(key) + "\\" onclick=\\"handleToggleExpand(this)\\">" +
1267
+ "<span class=\\"proj-arrow\\"></span>" +
1268
+ "<div class=\\"proj-info\\">" +
1269
+ "<div class=\\"proj-name\\">" + escapeHtml(p.name) + "</div>" +
1270
+ "<div class=\\"proj-path\\">" + escapeHtml(p.path) + "</div>" +
1271
+ (p.git ? "<div class=\\"git-info\\">" +
1272
+ "\\u2387 <span class=\\"git-branch-name\\">" + escapeHtml(p.git.branch) + "</span>" +
1273
+ (p.git.hasChanges ? " <span class=\\"git-dirty\\">\\u25CF " + p.git.uncommittedFiles + "</span>" : "") +
1274
+ ((p.git.ahead || p.git.behind) ? " <span class=\\"git-sync\\">" +
1275
+ (p.git.ahead ? "\\u2191" + p.git.ahead : "") +
1276
+ (p.git.behind ? " \\u2193" + p.git.behind : "") +
1277
+ "</span>" : "") +
1278
+ "</div>" : "") +
1279
+ "</div>" +
1280
+ "<div class=\\"proj-counts\\">" +
1281
+ (p.running.length ? "<span class=\\"cnt cnt-run\\">" + p.running.length + "r</span>" : "") +
1282
+ (p.queued.length ? "<span class=\\"cnt cnt-que\\">" + p.queued.length + "q</span>" : "") +
1283
+ (p.pending.length ? "<span class=\\"cnt cnt-pnd\\">" + p.pending.length + "p</span>" : "") +
1284
+ "</div>" +
1285
+ "<div class=\\"proj-toggle\\" onclick=\\"event.stopPropagation()\\" style=\\"display:flex;align-items:center;gap:4px\\">" +
1286
+ "<button class=\\"btn\\" style=\\"font-size:8px;padding:1px 4px;opacity:.5\\" data-path=\\"" + escapeAttr(p.path) + "\\" onclick=\\"handleUnpin(this)\\">✕</button>" +
1287
+ "<label class=\\"toggle\\">" +
1288
+ "<input type=\\"checkbox\\" " + checked + " data-path=\\"" + escapeAttr(p.path) + "\\" onchange=\\"handleToggleProject(this)\\">" +
1289
+ "<span class=\\"slider\\"></span>" +
1290
+ "</label>" +
1291
+ "</div>" +
1292
+ "</div>" +
1293
+ prsHtml +
1294
+ issuesHtml +
1295
+ "</div>"
1296
+ );
1297
+ }).join("");
1298
+ }
1299
+
1300
+ function issueRow(t, dotClass) {
1301
+ const prio = t.priority || 3;
1302
+ const extraCls = t.linearState === "Backlog" ? " issue-backlog" : "";
1303
+ const moveBtn = t.linearState === "Backlog" && t.linearId
1304
+ ? "<button class=\\"move-to-todo-btn\\" data-issue-id=\\"" + escapeAttr(t.linearId) + "\\" onclick=\\"handleMoveToTodo(this)\\">→ Todo</button>"
1305
+ : "";
1306
+ return (
1307
+ "<div class=\\"issue-row" + extraCls + "\\">" +
1308
+ "<span class=\\"idot " + dotClass + "\\"></span>" +
1309
+ "<span class=\\"prio prio-" + Math.min(4, prio) + "\\"></span>" +
1310
+ (t.issueIdentifier ? "<span class=\\"issue-id\\">" + escapeHtml(t.issueIdentifier) + "</span>" : "") +
1311
+ "<span class=\\"issue-title\\" title=\\"" + escapeAttr(t.title) + "\\">" + escapeHtml(t.title) + "</span>" +
1312
+ moveBtn +
1313
+ "</div>"
1314
+ );
1315
+ }
1316
+
1317
+ function toggleExpand(key) {
1318
+ if (expandedProjects.has(key)) expandedProjects.delete(key);
1319
+ else expandedProjects.add(key);
1320
+ renderProjects();
1321
+ }
1322
+ function handleToggleExpand(el) {
1323
+ const key = el.getAttribute("data-key");
1324
+ if (key) toggleExpand(key);
1325
+ }
1326
+ function handleToggleProject(el) {
1327
+ const path = el.getAttribute("data-path");
1328
+ if (path) toggleProject(path, el.checked);
1329
+ }
1330
+ async function handleUnpin(el) {
1331
+ const path = el.getAttribute("data-path");
1332
+ if (!path) return;
1333
+ el.disabled = true;
1334
+ try {
1335
+ await fetch("/api/projects/unpin", {
1336
+ method: "POST",
1337
+ headers: { "Content-Type": "application/json" },
1338
+ body: JSON.stringify({ projectPath: path }),
1339
+ });
1340
+ const res = await fetch("/api/projects");
1341
+ projects = await res.json();
1342
+ renderProjects();
1343
+ // Update picker state so re-adding works correctly
1344
+ const item = allLocalProjects.find(function(p) { return p.path === path; });
1345
+ if (item) item.pinned = false;
1346
+ } catch(e) { el.disabled = false; }
1347
+ }
1348
+ async function handleMoveToTodo(el) {
1349
+ const issueId = el.getAttribute("data-issue-id");
1350
+ if (!issueId) return;
1351
+
1352
+ const originalText = el.textContent;
1353
+ el.disabled = true;
1354
+ el.textContent = "Moving...";
1355
+
1356
+ try {
1357
+ const response = await fetch("/api/issue/move-to-todo", {
1358
+ method: "POST",
1359
+ headers: { "Content-Type": "application/json" },
1360
+ body: JSON.stringify({ issueId }),
1361
+ });
1362
+
1363
+ if (!response.ok) {
1364
+ throw new Error("Failed to move issue");
1365
+ }
1366
+
1367
+ // Refresh projects to show updated state
1368
+ const res = await fetch("/api/projects");
1369
+ projects = await res.json();
1370
+ renderProjects();
1371
+ } catch(e) {
1372
+ el.disabled = false;
1373
+ el.textContent = originalText;
1374
+ alert("Failed to move issue to Todo: " + e.message);
1375
+ }
1376
+ }
1377
+
1378
+ // ---- Pipeline Stages ----
1379
+ function addStageRow(data) {
1380
+ stageRows.push(data);
1381
+ if (stageRows.length > MAX_STAGE) stageRows = stageRows.slice(-MAX_STAGE);
1382
+ renderStages();
1383
+ }
1384
+ function shortModel(name) {
1385
+ if (!name) return "";
1386
+ if (name.includes("sonnet-4-5")) return "sonnet-4.5";
1387
+ if (name.includes("haiku-4-5")) return "haiku-4.5";
1388
+ if (name.includes("opus-4-6")) return "opus-4.6";
1389
+ if (name.includes("opus-4")) return "opus-4";
1390
+ if (name.includes("sonnet-4")) return "sonnet-4";
1391
+ var parts = name.split("-");
1392
+ return parts[parts.length - 1];
1393
+ }
1394
+ function fmtTokens(n) {
1395
+ if (n == null) return "";
1396
+ if (n >= 1000000) return (n / 1000000).toFixed(1) + "M";
1397
+ if (n >= 1000) return (n / 1000).toFixed(1) + "k";
1398
+ return String(n);
1399
+ }
1400
+ function renderStages() {
1401
+ const el = document.getElementById("stage-list");
1402
+ const cnt = document.getElementById("stage-count");
1403
+ if (!stageRows.length) { el.innerHTML = "<div class=\\"empty\\">no pipeline events</div>"; return; }
1404
+ if (cnt) cnt.textContent = stageRows.length + "/" + MAX_STAGE;
1405
+ el.innerHTML = stageRows.slice().reverse().map(r => {
1406
+ const info = r.taskId ? taskTitleMap.get(r.taskId) : null;
1407
+ let taskLabel = "";
1408
+ if (info) {
1409
+ taskLabel = info.issueIdentifier
1410
+ ? info.issueIdentifier + (info.title ? " " + info.title.slice(0, 22) : "")
1411
+ : (info.title ? info.title.slice(0, 30) : "");
1412
+ } else if (r.taskId) {
1413
+ taskLabel = r.taskId.slice(0, 8);
1414
+ }
1415
+ // repo name from projectPath
1416
+ const projPath = r.taskId ? taskProjectMap.get(r.taskId) : null;
1417
+ const repoName = projPath ? projPath.split("/").pop() : "";
1418
+ // elapsed time
1419
+ const startTs = r.taskId ? taskStartMap.get(r.taskId) : null;
1420
+ let elapsed = "";
1421
+ if (startTs) {
1422
+ const sec = Math.floor((Date.now() - startTs) / 1000);
1423
+ if (sec < 60) elapsed = sec + "s";
1424
+ else if (sec < 3600) elapsed = Math.floor(sec / 60) + "m" + (sec % 60) + "s";
1425
+ else elapsed = Math.floor(sec / 3600) + "h" + Math.floor((sec % 3600) / 60) + "m";
1426
+ }
1427
+ // model/token info (only on complete)
1428
+ var modelStr = r.model ? shortModel(r.model) : "";
1429
+ var tokenStr = "";
1430
+ if (r.inputTokens || r.outputTokens) {
1431
+ tokenStr = fmtTokens(r.inputTokens) + "/" + fmtTokens(r.outputTokens);
1432
+ if (r.costUsd != null) tokenStr += " $" + r.costUsd.toFixed(2);
1433
+ }
1434
+ return (
1435
+ "<div class=\\"stage-row\\">" +
1436
+ "<div class=\\"sdot " + (r.status || "") + "\\"></div>" +
1437
+ "<div class=\\"srepo\\">" + escapeHtml(repoName) + "</div>" +
1438
+ "<div class=\\"sname\\">" + escapeHtml(r.stage) + "</div>" +
1439
+ "<div class=\\"stask\\" title=\\"" + escapeAttr(r.taskId || "") + "\\">" + escapeHtml(taskLabel) + "</div>" +
1440
+ "<div class=\\"smodel\\">" + escapeHtml(modelStr) + "</div>" +
1441
+ "<div class=\\"stokens\\">" + escapeHtml(tokenStr) + "</div>" +
1442
+ "<div class=\\"selapsed\\">" + elapsed + "</div>" +
1443
+ "<div class=\\"sstatus\\">" + (r.status || "") + "</div>" +
1444
+ "</div>"
1445
+ );
1446
+ }).join("");
1447
+ el.scrollTop = 0;
1448
+ }
1449
+
1450
+ // ---- Log Tab ----
1451
+ function selectLogTab(taskId) {
1452
+ selectedLogTaskId = taskId;
1453
+ document.querySelectorAll('.log-tab').forEach(t =>
1454
+ t.classList.toggle('active', t.dataset.task === (taskId ?? 'all'))
1455
+ );
1456
+ renderLog();
1457
+ }
1458
+
1459
+ function updateLogTabs() {
1460
+ const bar = document.getElementById('log-tab-bar');
1461
+ const taskIds = [...new Set(logLines.map(l => l.taskId).filter(id => id && id !== 'system'))];
1462
+ // Sort by most recent start time
1463
+ taskIds.sort((a, b) => (taskStartMap.get(b) || 0) - (taskStartMap.get(a) || 0));
1464
+
1465
+ let html = '<button class="log-tab' + (selectedLogTaskId === null ? ' active' : '')
1466
+ + '" data-task="all" onclick="selectLogTab(null)">ALL</button>';
1467
+
1468
+ for (const tid of taskIds) {
1469
+ const info = taskTitleMap.get(tid);
1470
+ const label = info?.issueIdentifier || tid.slice(0, 8);
1471
+ const isActive = selectedLogTaskId === tid;
1472
+ html += '<button class="log-tab' + (isActive ? ' active' : '')
1473
+ + '" data-task="' + tid + '" onclick="selectLogTab(\\'' + tid + '\\')">'
1474
+ + escapeHtml(label) + '</button>';
1475
+ }
1476
+ bar.innerHTML = html;
1477
+ }
1478
+
1479
+ // ---- Log ----
1480
+ function addLogLine(data) {
1481
+ data._ts = Date.now();
1482
+ logLines.push(data);
1483
+ if (logLines.length > MAX_LOG) logLines = logLines.slice(-MAX_LOG);
1484
+ updateLogTabs();
1485
+ renderLog();
1486
+ }
1487
+
1488
+ function classifyLog(line) {
1489
+ if (!line) return { cls: "log-spacer", icon: "" };
1490
+ if (line === "───") return { cls: "log-separator", icon: "" };
1491
+ if (/^■ /.test(line)) return { cls: "log-heading2", icon: "■" };
1492
+ if (/^[┌└│]/.test(line)) return { cls: "log-code", icon: "" };
1493
+ if (/^▸ /.test(line)) return { cls: "log-tool", icon: "▸" };
1494
+ if (/^▶|Heartbeat started|Stage started|Iteration [0-9]/.test(line)) return { cls: "log-heading", icon: "▶" };
1495
+ if (/^✓|success=true|approved|completed|Done|Created sub-issue/.test(line)) return { cls: "log-success", icon: "✓" };
1496
+ if (/^✗|success=false|failed|error|Error|rejected|exceeded/.test(line)) return { cls: "log-fail", icon: "✗" };
1497
+ if (/^⟳|Fetching|Decomposing|Running|Scheduling|Spawning/.test(line)) return { cls: "", icon: "⟳" };
1498
+ if (/^⛔|Blocked|Time window|blocked/.test(line)) return { cls: "log-warn", icon: "⛔" };
1499
+ if (/^—|No task|already completed|no log/.test(line)) return { cls: "log-system", icon: "—" };
1500
+ if (/Cost:|\\$[\\.0-9]/.test(line)) return { cls: "", icon: "💲" };
1501
+ if (/Git detected|files changed|filesChanged/.test(line)) return { cls: "", icon: "📁" };
1502
+ if (/Selected [0-9]+ tasks/.test(line)) return { cls: "", icon: "🎯" };
1503
+ if (/Enqueued|executePipeline/.test(line)) return { cls: "", icon: "📋" };
1504
+ if (/Direct path|Project|path found/.test(line)) return { cls: "log-system", icon: "📂" };
1505
+ return { cls: "", icon: "·" };
1506
+ }
1507
+
1508
+ function formatLogText(raw) {
1509
+ if (!raw) return "";
1510
+ // Detect and humanize raw JSON that slipped through
1511
+ const trimmed = raw.trim();
1512
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
1513
+ try {
1514
+ const obj = JSON.parse(trimmed);
1515
+ if (obj.needsDecomposition === false) {
1516
+ const r = obj.reason ? obj.reason.slice(0, 120) : "";
1517
+ raw = "\\u2713 No decomposition needed (" + (obj.totalEstimatedMinutes || "?") + "min) " + r;
1518
+ } else if (obj.needsDecomposition === true && obj.subTasks) {
1519
+ raw = "\\uD83D\\uDD00 Decomposed into " + obj.subTasks.length + " sub-tasks (total " + (obj.totalEstimatedMinutes || "?") + "min)";
1520
+ } else if (obj.success !== undefined) {
1521
+ raw = (obj.success ? "\\u2713 " : "\\u2717 ") + (obj.summary || obj.error || JSON.stringify(obj).slice(0, 120));
1522
+ }
1523
+ } catch { /* not valid JSON */ }
1524
+ }
1525
+ let t = escapeHtml(raw);
1526
+ // inline bold: **text** → highlighted
1527
+ t = t.replace(/\\*\\*([^*]+)\\*\\*/g, '<span class="lhighlight">$1</span>');
1528
+ // highlight cost figures
1529
+ t = t.replace(/(\\$[\\d.]+)/g, '<span class="lcost">$1</span>');
1530
+ // highlight file counts
1531
+ t = t.replace(/(\\d+ files? changed)/g, '<span class="lfiles">$1</span>');
1532
+ // highlight durations
1533
+ t = t.replace(/(\\d+\\.\\d+s|\\d+ms)/g, '<span class="lcost">$1</span>');
1534
+ // highlight task titles in quotes
1535
+ t = t.replace(/(&quot;[^&]+&quot;)/g, '<span class="lhighlight">$1</span>');
1536
+ // highlight issue identifiers
1537
+ t = t.replace(/(INT-\\d+)/g, '<span class="lhighlight">$1</span>');
1538
+ return t;
1539
+ }
1540
+
1541
+ function fmtLogTime(ts) {
1542
+ if (!ts) return "";
1543
+ const d = new Date(ts);
1544
+ return d.getHours().toString().padStart(2,"0") + ":" +
1545
+ d.getMinutes().toString().padStart(2,"0") + ":" +
1546
+ d.getSeconds().toString().padStart(2,"0");
1547
+ }
1548
+
1549
+ function renderLog() {
1550
+ const el = document.getElementById("log-list");
1551
+ const cnt = document.getElementById("log-count");
1552
+ const filtered = selectedLogTaskId === null
1553
+ ? logLines
1554
+ : logLines.filter(l => l.taskId === selectedLogTaskId);
1555
+ if (!filtered.length) {
1556
+ el.innerHTML = "<div class=\\"empty\\">" + (selectedLogTaskId ? "no logs for this task" : "no log output") + "</div>";
1557
+ if (cnt) cnt.textContent = selectedLogTaskId ? filtered.length + "/" + logLines.length : "";
1558
+ return;
1559
+ }
1560
+ if (cnt) cnt.textContent = (selectedLogTaskId ? filtered.length + "/" : "") + logLines.length + "/" + MAX_LOG;
1561
+ const atBottom = el.scrollHeight - el.scrollTop <= el.clientHeight + 50;
1562
+ el.innerHTML = filtered.map(l => {
1563
+ const info = l.taskId ? taskTitleMap.get(l.taskId) : null;
1564
+ const tag = info?.issueIdentifier
1565
+ ? info.issueIdentifier
1566
+ : (l.taskId === "system" ? "SYS" : (l.taskId || "").slice(0, 8));
1567
+ const { cls, icon } = classifyLog(l.line);
1568
+ // spacer/separator use minimal rendering
1569
+ if (cls === "log-spacer") return "<div class=\\"log-line log-spacer\\"></div>";
1570
+ if (cls === "log-separator") return "<div class=\\"log-line log-separator\\"><span class=\\"ltext\\">───────────────────</span></div>";
1571
+ const time = fmtLogTime(l._ts);
1572
+ const stage = l.stage && l.stage !== "heartbeat" ? l.stage : "";
1573
+ // heading2: strip ■ prefix (icon handles it)
1574
+ const displayLine = cls === "log-heading2" ? (l.line || "").replace(/^■ /, "") : l.line;
1575
+ // tool: strip ▸ prefix
1576
+ const displayLine2 = cls === "log-tool" ? (displayLine || "").replace(/^▸ /, "") : displayLine;
1577
+ return (
1578
+ "<div class=\\"log-line " + cls + "\\">" +
1579
+ "<span class=\\"ltime\\">" + time + "</span>" +
1580
+ "<span class=\\"licon\\">" + icon + "</span>" +
1581
+ "<span class=\\"ltag\\" title=\\"" + escapeAttr(l.taskId || "") + "\\">" + escapeHtml(tag) + "</span>" +
1582
+ (stage ? "<span class=\\"lstage\\">" + escapeHtml(stage) + "</span>" : "") +
1583
+ "<span class=\\"ltext\\">" + formatLogText(displayLine2) + "</span>" +
1584
+ "</div>"
1585
+ );
1586
+ }).join("");
1587
+ if (atBottom) el.scrollTop = el.scrollHeight;
1588
+ }
1589
+
1590
+ // ---- Chat ----
1591
+ function fmtTime(ts) {
1592
+ const d = new Date(ts);
1593
+ return d.getHours().toString().padStart(2,"0") + ":" +
1594
+ d.getMinutes().toString().padStart(2,"0");
1595
+ }
1596
+
1597
+ function appendChatMsg(role, text, id, ts) {
1598
+ const container = document.getElementById("chat-messages");
1599
+ const line = document.createElement("div");
1600
+ line.className = "chat-line chat-" + role;
1601
+ if (id) line.id = id;
1602
+ const prefix = role === "user"
1603
+ ? "<span class=\\"chat-prefix\\">YOU &gt;</span>"
1604
+ : "<span class=\\"chat-prefix\\">OpenSwarm&gt;</span>";
1605
+ const tsStr = ts ? "<span class=\\"chat-ts\\">" + fmtTime(ts) + "</span>" : "";
1606
+ line.innerHTML = prefix + " <span class=\\"chat-text\\">" + escapeHtml(text) + "</span>" + tsStr;
1607
+ container.appendChild(line);
1608
+ container.scrollTop = container.scrollHeight;
1609
+ }
1610
+
1611
+ async function sendChat() {
1612
+ if (chatBusy) return;
1613
+ const input = document.getElementById("chat-input");
1614
+ const sendBtn = document.getElementById("chat-send");
1615
+ const msg = input.value.trim();
1616
+ if (!msg) return;
1617
+ input.value = "";
1618
+ chatBusy = true;
1619
+ sendBtn.disabled = true;
1620
+
1621
+ appendChatMsg("user", msg, null, Date.now());
1622
+
1623
+ const thinkId = "think-" + Date.now();
1624
+ const thinkEl = document.createElement("div");
1625
+ thinkEl.id = thinkId;
1626
+ thinkEl.className = "chat-line chat-agent";
1627
+ thinkEl.innerHTML = "<span class=\\"chat-prefix\\">OpenSwarm&gt;</span> <span class=\\"chat-text chat-thinking\\">thinking...</span>";
1628
+ document.getElementById("chat-messages").appendChild(thinkEl);
1629
+ document.getElementById("chat-messages").scrollTop = 99999;
1630
+
1631
+ try {
1632
+ const res = await fetch("/api/chat", {
1633
+ method: "POST",
1634
+ headers: { "Content-Type": "application/json" },
1635
+ body: JSON.stringify({ message: msg }),
1636
+ });
1637
+ const data = await res.json();
1638
+ document.getElementById(thinkId)?.remove();
1639
+ appendChatMsg("agent", data.response || data.error || "(no response)", null, Date.now());
1640
+ } catch(e) {
1641
+ document.getElementById(thinkId)?.remove();
1642
+ appendChatMsg("agent", "[ERROR] " + e.message, null, Date.now());
1643
+ }
1644
+
1645
+ chatBusy = false;
1646
+ sendBtn.disabled = false;
1647
+ input.focus();
1648
+ }
1649
+
1650
+ // ---- Utils ----
1651
+ function fmtAge(isoDate) {
1652
+ if (!isoDate) return "";
1653
+ var diff = Math.max(0, Date.now() - new Date(isoDate).getTime());
1654
+ var sec = Math.floor(diff / 1000);
1655
+ if (sec < 60) return sec + "s";
1656
+ var min = Math.floor(sec / 60);
1657
+ if (min < 60) return min + "m";
1658
+ var hr = Math.floor(min / 60);
1659
+ if (hr < 24) return hr + "h";
1660
+ var day = Math.floor(hr / 24);
1661
+ if (day < 7) return day + "d";
1662
+ var wk = Math.floor(day / 7);
1663
+ return wk + "w";
1664
+ }
1665
+ function escapeHtml(text) {
1666
+ const d = document.createElement("div"); d.textContent = String(text || ""); return d.innerHTML;
1667
+ }
1668
+ function escapeAttr(text) {
1669
+ return String(text || "").replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1670
+ }
1671
+
1672
+ // ---- Repo Picker ----
1673
+ let allLocalProjects = [];
1674
+ let pickerOpen = false;
1675
+
1676
+ async function openRepoPicker() {
1677
+ if (pickerOpen) return;
1678
+ pickerOpen = true;
1679
+ const overlay = document.getElementById("repo-picker");
1680
+ overlay.style.display = "flex";
1681
+ document.getElementById("repo-search").value = "";
1682
+ document.getElementById("repo-picker-list").innerHTML =
1683
+ "<div class=\\"empty\\">loading...</div>";
1684
+ document.getElementById("repo-search").focus();
1685
+
1686
+ try {
1687
+ fetchScanPaths();
1688
+ const res = await fetch("/api/local-projects");
1689
+ allLocalProjects = await res.json();
1690
+ filterRepos("");
1691
+ } catch(e) {
1692
+ document.getElementById("repo-picker-list").innerHTML =
1693
+ "<div class=\\"empty\\">failed to load: " + escapeHtml(e.message) + "</div>";
1694
+ }
1695
+ }
1696
+
1697
+ function closeRepoPicker() {
1698
+ pickerOpen = false;
1699
+ document.getElementById("repo-picker").style.display = "none";
1700
+ }
1701
+
1702
+ function filterRepos(q) {
1703
+ const list = document.getElementById("repo-picker-list");
1704
+ const filtered = q
1705
+ ? allLocalProjects.filter(p =>
1706
+ p.name.toLowerCase().includes(q.toLowerCase()) ||
1707
+ p.path.toLowerCase().includes(q.toLowerCase()))
1708
+ : allLocalProjects;
1709
+
1710
+ if (!filtered.length) {
1711
+ list.innerHTML = "<div class=\\"empty\\">no results</div>";
1712
+ return;
1713
+ }
1714
+ list.innerHTML = filtered.slice(0, 80).map(p => {
1715
+ const badge = p.pinned ? "<span class=\\"repo-item-badge\\">pinned</span>" : "";
1716
+ return (
1717
+ "<div class=\\"repo-item\\" data-path=\\"" + escapeAttr(p.path) + "\\" onclick=\\"pickRepo(this)\\">" +
1718
+ "<div>" +
1719
+ "<div class=\\"repo-item-name\\">" + escapeHtml(p.name) + "</div>" +
1720
+ "<div class=\\"repo-item-path\\">" + escapeHtml(p.path) + "</div>" +
1721
+ "</div>" +
1722
+ badge +
1723
+ "</div>"
1724
+ );
1725
+ }).join("");
1726
+ }
1727
+
1728
+ async function pickRepo(el) {
1729
+ const path = el.getAttribute("data-path");
1730
+ if (!path) return;
1731
+ el.style.opacity = "0.4";
1732
+ try {
1733
+ await fetch("/api/projects/pin", {
1734
+ method: "POST",
1735
+ headers: { "Content-Type": "application/json" },
1736
+ body: JSON.stringify({ projectPath: path }),
1737
+ });
1738
+ // Refresh project list
1739
+ const res = await fetch("/api/projects");
1740
+ projects = await res.json();
1741
+ renderProjects();
1742
+ // Mark as pinned in local picker list
1743
+ const item = allLocalProjects.find(p => p.path === path);
1744
+ if (item) item.pinned = true;
1745
+ filterRepos(document.getElementById("repo-search").value);
1746
+ } catch(e) {
1747
+ console.error("Pin failed:", e);
1748
+ }
1749
+ el.style.opacity = "1";
1750
+ }
1751
+
1752
+ // ---- Scan Paths ----
1753
+ async function fetchScanPaths() {
1754
+ try {
1755
+ const res = await fetch("/api/scan-paths");
1756
+ if (res.ok) {
1757
+ const data = await res.json();
1758
+ renderScanPaths(data);
1759
+ }
1760
+ } catch(e) {
1761
+ console.error("fetchScanPaths error:", e);
1762
+ }
1763
+ }
1764
+
1765
+ function renderScanPaths(data) {
1766
+ const list = document.getElementById("scan-paths-list");
1767
+ if (!list) return;
1768
+ const rows = [];
1769
+ for (const p of (data.configPaths || [])) {
1770
+ rows.push(
1771
+ "<div class=\\"scan-path-row\\">" +
1772
+ "<span class=\\"path\\">" + escapeHtml(p) + "</span>" +
1773
+ "<button class=\\"scan-path-remove\\" title=\\"remove\\" onclick=\\"removeScanPath('" + escapeAttr(p) + "')\\">✕</button>" +
1774
+ "</div>"
1775
+ );
1776
+ }
1777
+ for (const p of (data.customPaths || [])) {
1778
+ rows.push(
1779
+ "<div class=\\"scan-path-row\\">" +
1780
+ "<span class=\\"path\\">" + escapeHtml(p) + "</span>" +
1781
+ "<button class=\\"scan-path-remove\\" onclick=\\"removeScanPath('" + escapeAttr(p) + "')\\">✕</button>" +
1782
+ "</div>"
1783
+ );
1784
+ }
1785
+ list.innerHTML = rows.length > 0 ? rows.join("") : "<div style=\\"color:#334433;font-size:10px\\">no scan paths configured</div>";
1786
+ }
1787
+
1788
+ async function addScanPath() {
1789
+ const input = document.getElementById("scan-path-input");
1790
+ const path = input.value.trim();
1791
+ if (!path) return;
1792
+ input.value = "";
1793
+ try {
1794
+ await fetch("/api/scan-paths", {
1795
+ method: "POST",
1796
+ headers: { "Content-Type": "application/json" },
1797
+ body: JSON.stringify({ path }),
1798
+ });
1799
+ await fetchScanPaths();
1800
+ // Refresh project list in picker
1801
+ const res = await fetch("/api/local-projects");
1802
+ allLocalProjects = await res.json();
1803
+ filterRepos(document.getElementById("repo-search").value);
1804
+ } catch(e) {
1805
+ console.error("addScanPath error:", e);
1806
+ }
1807
+ }
1808
+
1809
+ async function removeScanPath(path) {
1810
+ try {
1811
+ await fetch("/api/scan-paths/" + encodeURIComponent(path), {
1812
+ method: "DELETE",
1813
+ });
1814
+ await fetchScanPaths();
1815
+ // Refresh project list in picker
1816
+ const res = await fetch("/api/local-projects");
1817
+ allLocalProjects = await res.json();
1818
+ filterRepos(document.getElementById("repo-search").value);
1819
+ } catch(e) {
1820
+ console.error("removeScanPath error:", e);
1821
+ }
1822
+ }
1823
+
1824
+ // ---- Monitors ----
1825
+ var monitorsData = [];
1826
+ async function fetchMonitors() {
1827
+ try {
1828
+ const res = await fetch("/api/monitors");
1829
+ if (res.ok) { monitorsData = await res.json(); renderMonitors(); }
1830
+ } catch {}
1831
+ }
1832
+ function renderMonitors() {
1833
+ renderMonitorsAndProcesses();
1834
+ }
1835
+ function fmtDur(ms) {
1836
+ var s = Math.floor(ms / 1000);
1837
+ var h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60);
1838
+ if (h >= 24) return Math.floor(h / 24) + "d " + (h % 24) + "h";
1839
+ if (h > 0) return h + "h " + m + "m";
1840
+ return m + "m";
1841
+ }
1842
+
1843
+ // ---- Processes ----
1844
+ var processesData = [];
1845
+ async function fetchProcesses() {
1846
+ try {
1847
+ const res = await fetch("/api/processes");
1848
+ if (res.ok) { processesData = await res.json(); renderMonitorsAndProcesses(); }
1849
+ } catch {}
1850
+ }
1851
+ async function killProcess(pid) {
1852
+ if (!confirm("Kill process PID " + pid + "?")) return;
1853
+ try {
1854
+ await fetch("/api/processes/" + pid, { method: "DELETE" });
1855
+ processesData = processesData.filter(p => p.pid !== pid);
1856
+ renderMonitorsAndProcesses();
1857
+ } catch(e) {
1858
+ addLogLine({ taskId: "system", stage: "error", line: "Kill failed: " + e.message });
1859
+ }
1860
+ }
1861
+ function procActivityIcon(lastActivityAt) {
1862
+ var ago = (Date.now() - lastActivityAt) / 1000;
1863
+ if (ago < 10) return "\\u26A1";
1864
+ if (ago < 60) return "\\u23F8";
1865
+ return "\\u2757";
1866
+ }
1867
+ function renderMonitorsAndProcesses() {
1868
+ var panel = document.getElementById("monitor-panel");
1869
+ var el = document.getElementById("monitor-list");
1870
+ var countEl = document.getElementById("monitor-count");
1871
+ var hasMonitors = monitorsData.length > 0;
1872
+ var hasProcesses = processesData.length > 0;
1873
+ if (!hasMonitors && !hasProcesses) {
1874
+ el.innerHTML = "<div class=\\"empty\\">no monitors or processes</div>";
1875
+ var counts = [];
1876
+ if (countEl) countEl.textContent = "";
1877
+ return;
1878
+ }
1879
+ var parts = [];
1880
+ if (processesData.length) parts.push(processesData.length + "p");
1881
+ if (monitorsData.length) parts.push(monitorsData.length + "m");
1882
+ if (countEl) countEl.textContent = parts.join(" ");
1883
+ var html = "";
1884
+ // Processes section
1885
+ if (hasProcesses) {
1886
+ html += "<div class=\\"issue-sec-label\\">processes</div>";
1887
+ html += processesData.map(function(p) {
1888
+ var dur = fmtDur(Date.now() - p.spawnedAt);
1889
+ var act = procActivityIcon(p.lastActivityAt);
1890
+ var modelStr = p.model ? shortModel(p.model) : "";
1891
+ var projName = p.projectPath ? p.projectPath.split("/").pop() : "";
1892
+ return '<div class="proc-row">' +
1893
+ '<span class="proc-pid">' + p.pid + '</span>' +
1894
+ '<span class="proc-stage">' + escapeHtml(p.stage) + '</span>' +
1895
+ '<span class="proc-model">' + escapeHtml(modelStr) + '</span>' +
1896
+ '<span style="color:var(--dim);font-size:9px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeAttr(p.projectPath || "") + '">' + escapeHtml(projName) + '</span>' +
1897
+ '<span class="proc-activity">' + act + '</span>' +
1898
+ '<span class="proc-dur">' + dur + '</span>' +
1899
+ '<button class="proc-kill" onclick="killProcess(' + p.pid + ')">KILL</button>' +
1900
+ '</div>';
1901
+ }).join("");
1902
+ }
1903
+ // Monitors section
1904
+ if (hasMonitors) {
1905
+ html += "<div class=\\"issue-sec-label\\">monitors</div>";
1906
+ html += monitorsData.map(function(m) {
1907
+ var stateColor = m.state === "running" ? "var(--green)" : m.state === "completed" ? "var(--cyan)" : m.state === "failed" || m.state === "timeout" ? "var(--red)" : "var(--dim)";
1908
+ var elapsed = m.registeredAt ? fmtDur(Date.now() - m.registeredAt) : "-";
1909
+ var lastOut = m.lastOutput ? escapeHtml(m.lastOutput.slice(0, 80)) : "-";
1910
+ return '<div style="padding:4px 6px;border-bottom:1px solid var(--border);font-size:11px">' +
1911
+ '<div style="display:flex;align-items:center;gap:6px">' +
1912
+ '<span style="color:' + stateColor + ';font-weight:bold">[' + m.state.toUpperCase() + ']</span>' +
1913
+ '<span style="color:var(--green)">' + escapeHtml(m.name) + '</span>' +
1914
+ '<span style="margin-left:auto;color:var(--dim)">' + elapsed + '</span>' +
1915
+ '</div>' +
1916
+ '<div style="color:var(--dim);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + escapeAttr(m.lastOutput || "") + '">' + lastOut + '</div>' +
1917
+ (m.issueId ? '<div style="color:var(--cyan-dim);font-size:10px;margin-top:1px">' + escapeHtml(m.issueId) + ' | checks: ' + m.checkCount + '</div>' : '') +
1918
+ '</div>';
1919
+ }).join("");
1920
+ }
1921
+ el.innerHTML = html;
1922
+ }
1923
+
1924
+ // ---- Init ----
1925
+ async function loadInitial() {
1926
+ try {
1927
+ // 1단계: 필수 데이터 먼저 로드 (성능 개선)
1928
+ const [statsRes, projectsRes] = await Promise.all([
1929
+ fetch("/api/stats"),
1930
+ fetch("/api/projects"),
1931
+ ]);
1932
+ const stats = await statsRes.json();
1933
+ updateStats(stats);
1934
+ document.getElementById("stat-sse").textContent = stats.sseClients ?? "-";
1935
+
1936
+ projects = await projectsRes.json();
1937
+ renderProjects();
1938
+
1939
+ // 2단계: 추가 데이터는 비동기로 지연 로드 (브라우저 렌더링 블로킹 제거)
1940
+ loadSupplementalData();
1941
+ } catch(e) {
1942
+ console.error("Init failed:", e);
1943
+ }
1944
+ }
1945
+
1946
+ // 추가 데이터 지연 로드 (초기 렌더링을 방해하지 않음)
1947
+ async function loadSupplementalData() {
1948
+ try {
1949
+ const [chatRes, logsRes, stagesRes] = await Promise.all([
1950
+ fetch("/api/chat/history"),
1951
+ fetch("/api/logs"),
1952
+ fetch("/api/stages"),
1953
+ ]);
1954
+
1955
+ const history = await chatRes.json();
1956
+ for (const msg of history) appendChatMsg(msg.role, msg.text, null, msg.ts);
1957
+
1958
+ // Restore logs
1959
+ const logs = await logsRes.json();
1960
+ for (const ev of logs) addLogLine(ev.data);
1961
+
1962
+ // Restore pipeline/task events
1963
+ const stages = await stagesRes.json();
1964
+ for (const ev of stages) handleEvent(ev);
1965
+ } catch(e) {
1966
+ console.error("Supplemental data load failed:", e);
1967
+ }
1968
+ }
1969
+
1970
+ // 성능 최적화: stats + projects 폴링을 60초로 증가 (변화 빈도 낮음)
1971
+ setInterval(async () => {
1972
+ try {
1973
+ const [sRes, pRes] = await Promise.all([fetch("/api/stats"), fetch("/api/projects")]);
1974
+ const stats = await sRes.json();
1975
+ document.getElementById("stat-sse").textContent = stats.sseClients ?? "-";
1976
+ updateStats(stats);
1977
+ const fresh = await pRes.json();
1978
+ fresh.forEach(p => {
1979
+ const local = projects.find(l => l.path === p.path);
1980
+ if (local) p.enabled = local.enabled;
1981
+ });
1982
+ projects = fresh;
1983
+ renderProjects();
1984
+ } catch {}
1985
+ }, 60000);
1986
+
1987
+ // ---- Mobile Tab Navigation ----
1988
+ function switchTab(idx) {
1989
+ const cols = document.querySelectorAll(".main-grid > .col");
1990
+ const tabs = document.querySelectorAll(".tab-bar .tab");
1991
+ cols.forEach((c, i) => c.classList.toggle("mob-active", i === idx));
1992
+ tabs.forEach((t, i) => t.classList.toggle("active", i === idx));
1993
+ }
1994
+ document.querySelector(".tab-bar").addEventListener("click", e => {
1995
+ const tab = e.target.closest(".tab");
1996
+ if (!tab) return;
1997
+ switchTab(parseInt(tab.dataset.tab, 10));
1998
+ });
1999
+ // Activate first tab on load
2000
+ switchTab(0);
2001
+
2002
+ // Knowledge graph data fetcher
2003
+ async function fetchKnowledgeData() {
2004
+ try {
2005
+ const res = await fetch("/api/knowledge");
2006
+ if (res.ok) {
2007
+ const data = await res.json();
2008
+ for (const item of data) {
2009
+ knowledgeCache[item.slug] = item;
2010
+ // Also cache by last segment for name-based lookup
2011
+ const parts = item.slug.split("-");
2012
+ knowledgeCache[parts[parts.length - 1]] = item;
2013
+ }
2014
+ renderProjects();
2015
+ }
2016
+ } catch {}
2017
+ }
2018
+
2019
+ // ---- Quota tracker ----
2020
+ async function fetchQuota() {
2021
+ try {
2022
+ var res = await fetch("/api/quota");
2023
+ if (!res.ok) return;
2024
+ var q = await res.json();
2025
+ if (q.error) return;
2026
+ var el5h = document.getElementById("stat-quota-5h");
2027
+ var el7d = document.getElementById("stat-quota-7d");
2028
+ if (q.five_hour && el5h) {
2029
+ var u5 = Math.round(q.five_hour.utilization);
2030
+ el5h.textContent = u5 + "%";
2031
+ el5h.className = "stat-val " + (u5 >= 80 ? "red" : u5 >= 50 ? "amber" : "cyan");
2032
+ }
2033
+ if (q.seven_day && el7d) {
2034
+ var u7 = Math.round(q.seven_day.utilization);
2035
+ el7d.textContent = u7 + "%";
2036
+ el7d.className = "stat-val " + (u7 >= 80 ? "red" : u7 >= 50 ? "amber" : "cyan");
2037
+ }
2038
+ } catch {}
2039
+ }
2040
+
2041
+ // 성능 최적화: 초기 로드 후 2단계 페칭 (렌더링 블로킹 방지)
2042
+ loadInitial().then(function() { connectSSE(true); });
2043
+
2044
+ // 1단계: 초기화 후 즉시 필수 폴링만 시작
2045
+ setInterval(fetchSvcStatus, 15000);
2046
+ setInterval(fetchPRProcessorStatus, 60000); // 성능 최적화: 30초 → 60초 (변화 빈도 낮음)
2047
+ setInterval(fetchStuckIssues, 60000); // 성능 최적화: 30초 → 60초 (Linear API 부하 감소)
2048
+ setInterval(fetchKnowledgeData, 60000);
2049
+ setInterval(fetchMonitors, 60000);
2050
+ setInterval(fetchProcesses, 30000);
2051
+ setInterval(fetchQuota, 300000);
2052
+
2053
+ // 2단계: 렌더링 안정화 후 비필수 데이터 로드 (3초 지연)
2054
+ setTimeout(function() {
2055
+ fetchSvcStatus();
2056
+ fetchPRProcessorStatus();
2057
+ fetchStuckIssues();
2058
+ fetchKnowledgeData();
2059
+ fetchMonitors();
2060
+ fetchProcesses();
2061
+ fetchQuota();
2062
+ }, 3000);
2063
+
2064
+ // 렌더링 성능: 스테이지 업데이트 폴링 제거 (SSE 이벤트 활용)
2065
+ // setInterval(() => { if (stageRows.length) renderStages(); }, 10000);
2066
+ </script>
2067
+ </body>
2068
+ </html>`;
2069
+ export { DASHBOARD_HTML };
2070
+ //# sourceMappingURL=dashboardHtml.js.map