@amsterdamdatalabs/enact-factory 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (644) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +566 -0
  3. package/dist/adapters/agenticLoop.d.ts +90 -0
  4. package/dist/adapters/agenticLoop.d.ts.map +1 -0
  5. package/dist/adapters/agenticLoop.js +219 -0
  6. package/dist/adapters/agenticLoop.js.map +1 -0
  7. package/dist/adapters/base.d.ts +16 -0
  8. package/dist/adapters/base.d.ts.map +1 -0
  9. package/dist/adapters/base.js +135 -0
  10. package/dist/adapters/base.js.map +1 -0
  11. package/dist/adapters/claude.d.ts +13 -0
  12. package/dist/adapters/claude.d.ts.map +1 -0
  13. package/dist/adapters/claude.js +318 -0
  14. package/dist/adapters/claude.js.map +1 -0
  15. package/dist/adapters/codex.d.ts +14 -0
  16. package/dist/adapters/codex.d.ts.map +1 -0
  17. package/dist/adapters/codex.js +366 -0
  18. package/dist/adapters/codex.js.map +1 -0
  19. package/dist/adapters/cryptoQuantAdapter.d.ts +85 -0
  20. package/dist/adapters/cryptoQuantAdapter.d.ts.map +1 -0
  21. package/dist/adapters/cryptoQuantAdapter.js +238 -0
  22. package/dist/adapters/cryptoQuantAdapter.js.map +1 -0
  23. package/dist/adapters/cursor.d.ts +13 -0
  24. package/dist/adapters/cursor.d.ts.map +1 -0
  25. package/dist/adapters/cursor.js +300 -0
  26. package/dist/adapters/cursor.js.map +1 -0
  27. package/dist/adapters/envPath.d.ts +20 -0
  28. package/dist/adapters/envPath.d.ts.map +1 -0
  29. package/dist/adapters/envPath.js +49 -0
  30. package/dist/adapters/envPath.js.map +1 -0
  31. package/dist/adapters/hermes.d.ts +13 -0
  32. package/dist/adapters/hermes.d.ts.map +1 -0
  33. package/dist/adapters/hermes.js +283 -0
  34. package/dist/adapters/hermes.js.map +1 -0
  35. package/dist/adapters/index.d.ts +18 -0
  36. package/dist/adapters/index.d.ts.map +1 -0
  37. package/dist/adapters/index.js +56 -0
  38. package/dist/adapters/index.js.map +1 -0
  39. package/dist/adapters/opencode.d.ts +13 -0
  40. package/dist/adapters/opencode.d.ts.map +1 -0
  41. package/dist/adapters/opencode.js +282 -0
  42. package/dist/adapters/opencode.js.map +1 -0
  43. package/dist/adapters/processRegistry.d.ts +38 -0
  44. package/dist/adapters/processRegistry.d.ts.map +1 -0
  45. package/dist/adapters/processRegistry.js +147 -0
  46. package/dist/adapters/processRegistry.js.map +1 -0
  47. package/dist/adapters/responses.d.ts +16 -0
  48. package/dist/adapters/responses.d.ts.map +1 -0
  49. package/dist/adapters/responses.js +244 -0
  50. package/dist/adapters/responses.js.map +1 -0
  51. package/dist/adapters/streamBuffer.d.ts +59 -0
  52. package/dist/adapters/streamBuffer.d.ts.map +1 -0
  53. package/dist/adapters/streamBuffer.js +123 -0
  54. package/dist/adapters/streamBuffer.js.map +1 -0
  55. package/dist/adapters/tools.d.ts +30 -0
  56. package/dist/adapters/tools.d.ts.map +1 -0
  57. package/dist/adapters/tools.js +219 -0
  58. package/dist/adapters/tools.js.map +1 -0
  59. package/dist/adapters/types.d.ts +82 -0
  60. package/dist/adapters/types.d.ts.map +1 -0
  61. package/dist/adapters/types.js +6 -0
  62. package/dist/adapters/types.js.map +1 -0
  63. package/dist/agents/agentBus.d.ts +160 -0
  64. package/dist/agents/agentBus.d.ts.map +1 -0
  65. package/dist/agents/agentBus.js +350 -0
  66. package/dist/agents/agentBus.js.map +1 -0
  67. package/dist/agents/agentPair.d.ts +215 -0
  68. package/dist/agents/agentPair.d.ts.map +1 -0
  69. package/dist/agents/agentPair.js +456 -0
  70. package/dist/agents/agentPair.js.map +1 -0
  71. package/dist/agents/auditor.d.ts +27 -0
  72. package/dist/agents/auditor.d.ts.map +1 -0
  73. package/dist/agents/auditor.js +238 -0
  74. package/dist/agents/auditor.js.map +1 -0
  75. package/dist/agents/cliStreamParser.d.ts +18 -0
  76. package/dist/agents/cliStreamParser.d.ts.map +1 -0
  77. package/dist/agents/cliStreamParser.js +156 -0
  78. package/dist/agents/cliStreamParser.js.map +1 -0
  79. package/dist/agents/documenter.d.ts +31 -0
  80. package/dist/agents/documenter.d.ts.map +1 -0
  81. package/dist/agents/documenter.js +286 -0
  82. package/dist/agents/documenter.js.map +1 -0
  83. package/dist/agents/draftAnalyzer.d.ts +50 -0
  84. package/dist/agents/draftAnalyzer.d.ts.map +1 -0
  85. package/dist/agents/draftAnalyzer.js +289 -0
  86. package/dist/agents/draftAnalyzer.js.map +1 -0
  87. package/dist/agents/evaluator.d.ts +61 -0
  88. package/dist/agents/evaluator.d.ts.map +1 -0
  89. package/dist/agents/evaluator.js +338 -0
  90. package/dist/agents/evaluator.js.map +1 -0
  91. package/dist/agents/executor.d.ts +33 -0
  92. package/dist/agents/executor.d.ts.map +1 -0
  93. package/dist/agents/executor.js +130 -0
  94. package/dist/agents/executor.js.map +1 -0
  95. package/dist/agents/index.d.ts +10 -0
  96. package/dist/agents/index.d.ts.map +1 -0
  97. package/dist/agents/index.js +10 -0
  98. package/dist/agents/index.js.map +1 -0
  99. package/dist/agents/pairMetrics.d.ts +63 -0
  100. package/dist/agents/pairMetrics.d.ts.map +1 -0
  101. package/dist/agents/pairMetrics.js +232 -0
  102. package/dist/agents/pairMetrics.js.map +1 -0
  103. package/dist/agents/pairPipeline.d.ts +184 -0
  104. package/dist/agents/pairPipeline.d.ts.map +1 -0
  105. package/dist/agents/pairPipeline.js +934 -0
  106. package/dist/agents/pairPipeline.js.map +1 -0
  107. package/dist/agents/pairWebhook.d.ts +59 -0
  108. package/dist/agents/pairWebhook.d.ts.map +1 -0
  109. package/dist/agents/pairWebhook.js +242 -0
  110. package/dist/agents/pairWebhook.js.map +1 -0
  111. package/dist/agents/pipelineFormat.d.ts +8 -0
  112. package/dist/agents/pipelineFormat.d.ts.map +1 -0
  113. package/dist/agents/pipelineFormat.js +65 -0
  114. package/dist/agents/pipelineFormat.js.map +1 -0
  115. package/dist/agents/pipelineGuards.d.ts +23 -0
  116. package/dist/agents/pipelineGuards.d.ts.map +1 -0
  117. package/dist/agents/pipelineGuards.js +257 -0
  118. package/dist/agents/pipelineGuards.js.map +1 -0
  119. package/dist/agents/reviewer.d.ts +37 -0
  120. package/dist/agents/reviewer.d.ts.map +1 -0
  121. package/dist/agents/reviewer.js +214 -0
  122. package/dist/agents/reviewer.js.map +1 -0
  123. package/dist/agents/skillDocumenter.d.ts +23 -0
  124. package/dist/agents/skillDocumenter.d.ts.map +1 -0
  125. package/dist/agents/skillDocumenter.js +219 -0
  126. package/dist/agents/skillDocumenter.js.map +1 -0
  127. package/dist/agents/tester.d.ts +37 -0
  128. package/dist/agents/tester.d.ts.map +1 -0
  129. package/dist/agents/tester.js +309 -0
  130. package/dist/agents/tester.js.map +1 -0
  131. package/dist/automation/autonomousRunner.d.ts +145 -0
  132. package/dist/automation/autonomousRunner.d.ts.map +1 -0
  133. package/dist/automation/autonomousRunner.js +1272 -0
  134. package/dist/automation/autonomousRunner.js.map +1 -0
  135. package/dist/automation/dailyReporter.d.ts +26 -0
  136. package/dist/automation/dailyReporter.d.ts.map +1 -0
  137. package/dist/automation/dailyReporter.js +130 -0
  138. package/dist/automation/dailyReporter.js.map +1 -0
  139. package/dist/automation/index.d.ts +5 -0
  140. package/dist/automation/index.d.ts.map +1 -0
  141. package/dist/automation/index.js +5 -0
  142. package/dist/automation/index.js.map +1 -0
  143. package/dist/automation/longRunningMonitor.d.ts +26 -0
  144. package/dist/automation/longRunningMonitor.d.ts.map +1 -0
  145. package/dist/automation/longRunningMonitor.js +356 -0
  146. package/dist/automation/longRunningMonitor.js.map +1 -0
  147. package/dist/automation/prOwnership.d.ts +18 -0
  148. package/dist/automation/prOwnership.d.ts.map +1 -0
  149. package/dist/automation/prOwnership.js +61 -0
  150. package/dist/automation/prOwnership.js.map +1 -0
  151. package/dist/automation/runnerExecution.d.ts +57 -0
  152. package/dist/automation/runnerExecution.d.ts.map +1 -0
  153. package/dist/automation/runnerExecution.js +701 -0
  154. package/dist/automation/runnerExecution.js.map +1 -0
  155. package/dist/automation/runnerState.d.ts +170 -0
  156. package/dist/automation/runnerState.d.ts.map +1 -0
  157. package/dist/automation/runnerState.js +496 -0
  158. package/dist/automation/runnerState.js.map +1 -0
  159. package/dist/automation/runnerTypes.d.ts +57 -0
  160. package/dist/automation/runnerTypes.d.ts.map +1 -0
  161. package/dist/automation/runnerTypes.js +5 -0
  162. package/dist/automation/runnerTypes.js.map +1 -0
  163. package/dist/automation/scheduler.d.ts +75 -0
  164. package/dist/automation/scheduler.d.ts.map +1 -0
  165. package/dist/automation/scheduler.js +402 -0
  166. package/dist/automation/scheduler.js.map +1 -0
  167. package/dist/azdo/azdo.d.ts +70 -0
  168. package/dist/azdo/azdo.d.ts.map +1 -0
  169. package/dist/azdo/azdo.js +328 -0
  170. package/dist/azdo/azdo.js.map +1 -0
  171. package/dist/azdo/index.d.ts +3 -0
  172. package/dist/azdo/index.d.ts.map +1 -0
  173. package/dist/azdo/index.js +3 -0
  174. package/dist/azdo/index.js.map +1 -0
  175. package/dist/azdo/projectUpdater.d.ts +13 -0
  176. package/dist/azdo/projectUpdater.d.ts.map +1 -0
  177. package/dist/azdo/projectUpdater.js +155 -0
  178. package/dist/azdo/projectUpdater.js.map +1 -0
  179. package/dist/azureDevOps/client.d.ts +75 -0
  180. package/dist/azureDevOps/client.d.ts.map +1 -0
  181. package/dist/azureDevOps/client.js +150 -0
  182. package/dist/azureDevOps/client.js.map +1 -0
  183. package/dist/azureDevOps/hierarchy.d.ts +119 -0
  184. package/dist/azureDevOps/hierarchy.d.ts.map +1 -0
  185. package/dist/azureDevOps/hierarchy.js +470 -0
  186. package/dist/azureDevOps/hierarchy.js.map +1 -0
  187. package/dist/azureDevOps/mapper.d.ts +101 -0
  188. package/dist/azureDevOps/mapper.d.ts.map +1 -0
  189. package/dist/azureDevOps/mapper.js +438 -0
  190. package/dist/azureDevOps/mapper.js.map +1 -0
  191. package/dist/azureDevOps/stateMapping.d.ts +15 -0
  192. package/dist/azureDevOps/stateMapping.d.ts.map +1 -0
  193. package/dist/azureDevOps/stateMapping.js +141 -0
  194. package/dist/azureDevOps/stateMapping.js.map +1 -0
  195. package/dist/cli/authHandler.d.ts +13 -0
  196. package/dist/cli/authHandler.d.ts.map +1 -0
  197. package/dist/cli/authHandler.js +70 -0
  198. package/dist/cli/authHandler.js.map +1 -0
  199. package/dist/cli/checkHandler.d.ts +27 -0
  200. package/dist/cli/checkHandler.d.ts.map +1 -0
  201. package/dist/cli/checkHandler.js +560 -0
  202. package/dist/cli/checkHandler.js.map +1 -0
  203. package/dist/cli/daemon.d.ts +30 -0
  204. package/dist/cli/daemon.d.ts.map +1 -0
  205. package/dist/cli/daemon.js +141 -0
  206. package/dist/cli/daemon.js.map +1 -0
  207. package/dist/cli/factoryCommands.d.ts +3 -0
  208. package/dist/cli/factoryCommands.d.ts.map +1 -0
  209. package/dist/cli/factoryCommands.js +165 -0
  210. package/dist/cli/factoryCommands.js.map +1 -0
  211. package/dist/cli/promptHandler.d.ts +13 -0
  212. package/dist/cli/promptHandler.d.ts.map +1 -0
  213. package/dist/cli/promptHandler.js +193 -0
  214. package/dist/cli/promptHandler.js.map +1 -0
  215. package/dist/cli.d.ts +3 -0
  216. package/dist/cli.d.ts.map +1 -0
  217. package/dist/cli.js +320 -0
  218. package/dist/cli.js.map +1 -0
  219. package/dist/core/agentLifecycle.d.ts +322 -0
  220. package/dist/core/agentLifecycle.d.ts.map +1 -0
  221. package/dist/core/agentLifecycle.js +230 -0
  222. package/dist/core/agentLifecycle.js.map +1 -0
  223. package/dist/core/areaMapping.d.ts +9 -0
  224. package/dist/core/areaMapping.d.ts.map +1 -0
  225. package/dist/core/areaMapping.js +37 -0
  226. package/dist/core/areaMapping.js.map +1 -0
  227. package/dist/core/config.d.ts +469 -0
  228. package/dist/core/config.d.ts.map +1 -0
  229. package/dist/core/config.js +780 -0
  230. package/dist/core/config.js.map +1 -0
  231. package/dist/core/dashboardContract.d.ts +204 -0
  232. package/dist/core/dashboardContract.d.ts.map +1 -0
  233. package/dist/core/dashboardContract.js +205 -0
  234. package/dist/core/dashboardContract.js.map +1 -0
  235. package/dist/core/devopsModel.d.ts +138 -0
  236. package/dist/core/devopsModel.d.ts.map +1 -0
  237. package/dist/core/devopsModel.js +137 -0
  238. package/dist/core/devopsModel.js.map +1 -0
  239. package/dist/core/envFile.d.ts +11 -0
  240. package/dist/core/envFile.d.ts.map +1 -0
  241. package/dist/core/envFile.js +104 -0
  242. package/dist/core/envFile.js.map +1 -0
  243. package/dist/core/eventHub.d.ts +220 -0
  244. package/dist/core/eventHub.d.ts.map +1 -0
  245. package/dist/core/eventHub.js +136 -0
  246. package/dist/core/eventHub.js.map +1 -0
  247. package/dist/core/index.d.ts +8 -0
  248. package/dist/core/index.d.ts.map +1 -0
  249. package/dist/core/index.js +7 -0
  250. package/dist/core/index.js.map +1 -0
  251. package/dist/core/laneExecutionState.d.ts +29 -0
  252. package/dist/core/laneExecutionState.d.ts.map +1 -0
  253. package/dist/core/laneExecutionState.js +18 -0
  254. package/dist/core/laneExecutionState.js.map +1 -0
  255. package/dist/core/laneStatus.d.ts +49 -0
  256. package/dist/core/laneStatus.d.ts.map +1 -0
  257. package/dist/core/laneStatus.js +153 -0
  258. package/dist/core/laneStatus.js.map +1 -0
  259. package/dist/core/prSidecar.d.ts +96 -0
  260. package/dist/core/prSidecar.d.ts.map +1 -0
  261. package/dist/core/prSidecar.js +33 -0
  262. package/dist/core/prSidecar.js.map +1 -0
  263. package/dist/core/runtimeConfig.d.ts +6 -0
  264. package/dist/core/runtimeConfig.d.ts.map +1 -0
  265. package/dist/core/runtimeConfig.js +24 -0
  266. package/dist/core/runtimeConfig.js.map +1 -0
  267. package/dist/core/scmProvider.d.ts +19 -0
  268. package/dist/core/scmProvider.d.ts.map +1 -0
  269. package/dist/core/scmProvider.js +38 -0
  270. package/dist/core/scmProvider.js.map +1 -0
  271. package/dist/core/service.d.ts +10 -0
  272. package/dist/core/service.d.ts.map +1 -0
  273. package/dist/core/service.js +297 -0
  274. package/dist/core/service.js.map +1 -0
  275. package/dist/core/traceCollector.d.ts +105 -0
  276. package/dist/core/traceCollector.d.ts.map +1 -0
  277. package/dist/core/traceCollector.js +141 -0
  278. package/dist/core/traceCollector.js.map +1 -0
  279. package/dist/core/types.d.ts +432 -0
  280. package/dist/core/types.d.ts.map +1 -0
  281. package/dist/core/types.js +2 -0
  282. package/dist/core/types.js.map +1 -0
  283. package/dist/core/workItemMapper.d.ts +39 -0
  284. package/dist/core/workItemMapper.d.ts.map +1 -0
  285. package/dist/core/workItemMapper.js +427 -0
  286. package/dist/core/workItemMapper.js.map +1 -0
  287. package/dist/core/workItemModel.d.ts +120 -0
  288. package/dist/core/workItemModel.d.ts.map +1 -0
  289. package/dist/core/workItemModel.js +104 -0
  290. package/dist/core/workItemModel.js.map +1 -0
  291. package/dist/core/workItemPayload.d.ts +195 -0
  292. package/dist/core/workItemPayload.d.ts.map +1 -0
  293. package/dist/core/workItemPayload.js +24 -0
  294. package/dist/core/workItemPayload.js.map +1 -0
  295. package/dist/core/workspaceConfig.d.ts +57 -0
  296. package/dist/core/workspaceConfig.d.ts.map +1 -0
  297. package/dist/core/workspaceConfig.js +184 -0
  298. package/dist/core/workspaceConfig.js.map +1 -0
  299. package/dist/doctor.d.ts +18 -0
  300. package/dist/doctor.d.ts.map +1 -0
  301. package/dist/doctor.js +34 -0
  302. package/dist/doctor.js.map +1 -0
  303. package/dist/factory/activeSkill.d.ts +11 -0
  304. package/dist/factory/activeSkill.d.ts.map +1 -0
  305. package/dist/factory/activeSkill.js +44 -0
  306. package/dist/factory/activeSkill.js.map +1 -0
  307. package/dist/factory/assignment.d.ts +54 -0
  308. package/dist/factory/assignment.d.ts.map +1 -0
  309. package/dist/factory/assignment.js +94 -0
  310. package/dist/factory/assignment.js.map +1 -0
  311. package/dist/factory/auditLog.d.ts +10 -0
  312. package/dist/factory/auditLog.d.ts.map +1 -0
  313. package/dist/factory/auditLog.js +38 -0
  314. package/dist/factory/auditLog.js.map +1 -0
  315. package/dist/factory/closureRequirements.d.ts +12 -0
  316. package/dist/factory/closureRequirements.d.ts.map +1 -0
  317. package/dist/factory/closureRequirements.js +30 -0
  318. package/dist/factory/closureRequirements.js.map +1 -0
  319. package/dist/factory/delegationPrompt.d.ts +3 -0
  320. package/dist/factory/delegationPrompt.d.ts.map +1 -0
  321. package/dist/factory/delegationPrompt.js +16 -0
  322. package/dist/factory/delegationPrompt.js.map +1 -0
  323. package/dist/factory/http.d.ts +3 -0
  324. package/dist/factory/http.d.ts.map +1 -0
  325. package/dist/factory/http.js +555 -0
  326. package/dist/factory/http.js.map +1 -0
  327. package/dist/factory/lifecyclePushMap.d.ts +4 -0
  328. package/dist/factory/lifecyclePushMap.d.ts.map +1 -0
  329. package/dist/factory/lifecyclePushMap.js +7 -0
  330. package/dist/factory/lifecyclePushMap.js.map +1 -0
  331. package/dist/factory/missions.d.ts +125 -0
  332. package/dist/factory/missions.d.ts.map +1 -0
  333. package/dist/factory/missions.js +304 -0
  334. package/dist/factory/missions.js.map +1 -0
  335. package/dist/factory/mode.d.ts +9 -0
  336. package/dist/factory/mode.d.ts.map +1 -0
  337. package/dist/factory/mode.js +30 -0
  338. package/dist/factory/mode.js.map +1 -0
  339. package/dist/factory/operatorActiveSkill.d.ts +15 -0
  340. package/dist/factory/operatorActiveSkill.d.ts.map +1 -0
  341. package/dist/factory/operatorActiveSkill.js +95 -0
  342. package/dist/factory/operatorActiveSkill.js.map +1 -0
  343. package/dist/factory/paseoDispatcher.d.ts +52 -0
  344. package/dist/factory/paseoDispatcher.d.ts.map +1 -0
  345. package/dist/factory/paseoDispatcher.js +122 -0
  346. package/dist/factory/paseoDispatcher.js.map +1 -0
  347. package/dist/factory/paseoLifecycle.d.ts +32 -0
  348. package/dist/factory/paseoLifecycle.d.ts.map +1 -0
  349. package/dist/factory/paseoLifecycle.js +260 -0
  350. package/dist/factory/paseoLifecycle.js.map +1 -0
  351. package/dist/factory/paths.d.ts +31 -0
  352. package/dist/factory/paths.d.ts.map +1 -0
  353. package/dist/factory/paths.js +139 -0
  354. package/dist/factory/paths.js.map +1 -0
  355. package/dist/factory/progressWatchdog.d.ts +58 -0
  356. package/dist/factory/progressWatchdog.d.ts.map +1 -0
  357. package/dist/factory/progressWatchdog.js +160 -0
  358. package/dist/factory/progressWatchdog.js.map +1 -0
  359. package/dist/factory/roster.d.ts +59 -0
  360. package/dist/factory/roster.d.ts.map +1 -0
  361. package/dist/factory/roster.js +116 -0
  362. package/dist/factory/roster.js.map +1 -0
  363. package/dist/factory/runtime.d.ts +44 -0
  364. package/dist/factory/runtime.d.ts.map +1 -0
  365. package/dist/factory/runtime.js +238 -0
  366. package/dist/factory/runtime.js.map +1 -0
  367. package/dist/factory/sync.d.ts +29 -0
  368. package/dist/factory/sync.d.ts.map +1 -0
  369. package/dist/factory/sync.js +77 -0
  370. package/dist/factory/sync.js.map +1 -0
  371. package/dist/factory/workitemQueues.d.ts +37 -0
  372. package/dist/factory/workitemQueues.d.ts.map +1 -0
  373. package/dist/factory/workitemQueues.js +99 -0
  374. package/dist/factory/workitemQueues.js.map +1 -0
  375. package/dist/factory/workitemTriage.d.ts +9 -0
  376. package/dist/factory/workitemTriage.d.ts.map +1 -0
  377. package/dist/factory/workitemTriage.js +81 -0
  378. package/dist/factory/workitemTriage.js.map +1 -0
  379. package/dist/hooks.d.ts +18 -0
  380. package/dist/hooks.d.ts.map +1 -0
  381. package/dist/hooks.js +96 -0
  382. package/dist/hooks.js.map +1 -0
  383. package/dist/index.d.ts +3 -0
  384. package/dist/index.d.ts.map +1 -0
  385. package/dist/index.js +90 -0
  386. package/dist/index.js.map +1 -0
  387. package/dist/install/agentCatalog.d.ts +7 -0
  388. package/dist/install/agentCatalog.d.ts.map +1 -0
  389. package/dist/install/agentCatalog.js +28 -0
  390. package/dist/install/agentCatalog.js.map +1 -0
  391. package/dist/install/bundlePaths.d.ts +10 -0
  392. package/dist/install/bundlePaths.d.ts.map +1 -0
  393. package/dist/install/bundlePaths.js +30 -0
  394. package/dist/install/bundlePaths.js.map +1 -0
  395. package/dist/install/codex.d.ts +43 -0
  396. package/dist/install/codex.d.ts.map +1 -0
  397. package/dist/install/codex.js +207 -0
  398. package/dist/install/codex.js.map +1 -0
  399. package/dist/install/enactHome.d.ts +37 -0
  400. package/dist/install/enactHome.d.ts.map +1 -0
  401. package/dist/install/enactHome.js +152 -0
  402. package/dist/install/enactHome.js.map +1 -0
  403. package/dist/install/plugins.d.ts +115 -0
  404. package/dist/install/plugins.d.ts.map +1 -0
  405. package/dist/install/plugins.js +259 -0
  406. package/dist/install/plugins.js.map +1 -0
  407. package/dist/install/setup.d.ts +33 -0
  408. package/dist/install/setup.d.ts.map +1 -0
  409. package/dist/install/setup.js +167 -0
  410. package/dist/install/setup.js.map +1 -0
  411. package/dist/locale/en.d.ts +3 -0
  412. package/dist/locale/en.d.ts.map +1 -0
  413. package/dist/locale/en.js +435 -0
  414. package/dist/locale/en.js.map +1 -0
  415. package/dist/locale/index.d.ts +28 -0
  416. package/dist/locale/index.d.ts.map +1 -0
  417. package/dist/locale/index.js +84 -0
  418. package/dist/locale/index.js.map +1 -0
  419. package/dist/locale/prompts/en.d.ts +3 -0
  420. package/dist/locale/prompts/en.d.ts.map +1 -0
  421. package/dist/locale/prompts/en.js +254 -0
  422. package/dist/locale/prompts/en.js.map +1 -0
  423. package/dist/locale/types.d.ts +433 -0
  424. package/dist/locale/types.d.ts.map +1 -0
  425. package/dist/locale/types.js +5 -0
  426. package/dist/locale/types.js.map +1 -0
  427. package/dist/mcp/server.d.ts +489 -0
  428. package/dist/mcp/server.d.ts.map +1 -0
  429. package/dist/mcp/server.js +597 -0
  430. package/dist/mcp/server.js.map +1 -0
  431. package/dist/orchestration/decisionEngine.d.ts +175 -0
  432. package/dist/orchestration/decisionEngine.d.ts.map +1 -0
  433. package/dist/orchestration/decisionEngine.js +471 -0
  434. package/dist/orchestration/decisionEngine.js.map +1 -0
  435. package/dist/orchestration/index.d.ts +5 -0
  436. package/dist/orchestration/index.d.ts.map +1 -0
  437. package/dist/orchestration/index.js +5 -0
  438. package/dist/orchestration/index.js.map +1 -0
  439. package/dist/orchestration/workItemParser.d.ts +67 -0
  440. package/dist/orchestration/workItemParser.d.ts.map +1 -0
  441. package/dist/orchestration/workItemParser.js +560 -0
  442. package/dist/orchestration/workItemParser.js.map +1 -0
  443. package/dist/orchestration/workItemScheduler.d.ts +141 -0
  444. package/dist/orchestration/workItemScheduler.d.ts.map +1 -0
  445. package/dist/orchestration/workItemScheduler.js +317 -0
  446. package/dist/orchestration/workItemScheduler.js.map +1 -0
  447. package/dist/orchestration/workflow.d.ts +145 -0
  448. package/dist/orchestration/workflow.d.ts.map +1 -0
  449. package/dist/orchestration/workflow.js +301 -0
  450. package/dist/orchestration/workflow.js.map +1 -0
  451. package/dist/providers/codexSessions.d.ts +93 -0
  452. package/dist/providers/codexSessions.d.ts.map +1 -0
  453. package/dist/providers/codexSessions.js +366 -0
  454. package/dist/providers/codexSessions.js.map +1 -0
  455. package/dist/registry/bsDetector.d.ts +24 -0
  456. package/dist/registry/bsDetector.d.ts.map +1 -0
  457. package/dist/registry/bsDetector.js +276 -0
  458. package/dist/registry/bsDetector.js.map +1 -0
  459. package/dist/registry/entityScanner.d.ts +36 -0
  460. package/dist/registry/entityScanner.d.ts.map +1 -0
  461. package/dist/registry/entityScanner.js +693 -0
  462. package/dist/registry/entityScanner.js.map +1 -0
  463. package/dist/registry/index.d.ts +9 -0
  464. package/dist/registry/index.d.ts.map +1 -0
  465. package/dist/registry/index.js +13 -0
  466. package/dist/registry/index.js.map +1 -0
  467. package/dist/registry/schema.d.ts +307 -0
  468. package/dist/registry/schema.d.ts.map +1 -0
  469. package/dist/registry/schema.js +139 -0
  470. package/dist/registry/schema.js.map +1 -0
  471. package/dist/registry/sqliteStore.d.ts +101 -0
  472. package/dist/registry/sqliteStore.d.ts.map +1 -0
  473. package/dist/registry/sqliteStore.js +688 -0
  474. package/dist/registry/sqliteStore.js.map +1 -0
  475. package/dist/registry/workItemBridge.d.ts +8 -0
  476. package/dist/registry/workItemBridge.d.ts.map +1 -0
  477. package/dist/registry/workItemBridge.js +30 -0
  478. package/dist/registry/workItemBridge.js.map +1 -0
  479. package/dist/runners/cliRunner.d.ts +11 -0
  480. package/dist/runners/cliRunner.d.ts.map +1 -0
  481. package/dist/runners/cliRunner.js +193 -0
  482. package/dist/runners/cliRunner.js.map +1 -0
  483. package/dist/support/apiCache.d.ts +85 -0
  484. package/dist/support/apiCache.d.ts.map +1 -0
  485. package/dist/support/apiCache.js +163 -0
  486. package/dist/support/apiCache.js.map +1 -0
  487. package/dist/support/chat.d.ts +3 -0
  488. package/dist/support/chat.d.ts.map +1 -0
  489. package/dist/support/chat.js +305 -0
  490. package/dist/support/chat.js.map +1 -0
  491. package/dist/support/chatBackend.d.ts +25 -0
  492. package/dist/support/chatBackend.d.ts.map +1 -0
  493. package/dist/support/chatBackend.js +289 -0
  494. package/dist/support/chatBackend.js.map +1 -0
  495. package/dist/support/chatTui.d.ts +3 -0
  496. package/dist/support/chatTui.d.ts.map +1 -0
  497. package/dist/support/chatTui.js +1082 -0
  498. package/dist/support/chatTui.js.map +1 -0
  499. package/dist/support/costTracker.d.ts +29 -0
  500. package/dist/support/costTracker.d.ts.map +1 -0
  501. package/dist/support/costTracker.js +113 -0
  502. package/dist/support/costTracker.js.map +1 -0
  503. package/dist/support/dashboardHtml.d.ts +5 -0
  504. package/dist/support/dashboardHtml.d.ts.map +1 -0
  505. package/dist/support/dashboardHtml.js +2629 -0
  506. package/dist/support/dashboardHtml.js.map +1 -0
  507. package/dist/support/dev.d.ts +55 -0
  508. package/dist/support/dev.d.ts.map +1 -0
  509. package/dist/support/dev.js +298 -0
  510. package/dist/support/dev.js.map +1 -0
  511. package/dist/support/editParser.d.ts +37 -0
  512. package/dist/support/editParser.d.ts.map +1 -0
  513. package/dist/support/editParser.js +365 -0
  514. package/dist/support/editParser.js.map +1 -0
  515. package/dist/support/ghosttyThemeCatalog.generated.d.ts +2 -0
  516. package/dist/support/ghosttyThemeCatalog.generated.d.ts.map +1 -0
  517. package/dist/support/ghosttyThemeCatalog.generated.js +11116 -0
  518. package/dist/support/ghosttyThemeCatalog.generated.js.map +1 -0
  519. package/dist/support/gitStatus.d.ts +21 -0
  520. package/dist/support/gitStatus.d.ts.map +1 -0
  521. package/dist/support/gitStatus.js +108 -0
  522. package/dist/support/gitStatus.js.map +1 -0
  523. package/dist/support/gitTracker.d.ts +30 -0
  524. package/dist/support/gitTracker.d.ts.map +1 -0
  525. package/dist/support/gitTracker.js +143 -0
  526. package/dist/support/gitTracker.js.map +1 -0
  527. package/dist/support/index.d.ts +12 -0
  528. package/dist/support/index.d.ts.map +1 -0
  529. package/dist/support/index.js +12 -0
  530. package/dist/support/index.js.map +1 -0
  531. package/dist/support/planner.d.ts +64 -0
  532. package/dist/support/planner.d.ts.map +1 -0
  533. package/dist/support/planner.js +396 -0
  534. package/dist/support/planner.js.map +1 -0
  535. package/dist/support/projectMapper.d.ts +46 -0
  536. package/dist/support/projectMapper.d.ts.map +1 -0
  537. package/dist/support/projectMapper.js +273 -0
  538. package/dist/support/projectMapper.js.map +1 -0
  539. package/dist/support/pty-helper.py +117 -0
  540. package/dist/support/quotaTracker.d.ts +29 -0
  541. package/dist/support/quotaTracker.d.ts.map +1 -0
  542. package/dist/support/quotaTracker.js +89 -0
  543. package/dist/support/quotaTracker.js.map +1 -0
  544. package/dist/support/rateLimiter.d.ts +101 -0
  545. package/dist/support/rateLimiter.d.ts.map +1 -0
  546. package/dist/support/rateLimiter.js +219 -0
  547. package/dist/support/rateLimiter.js.map +1 -0
  548. package/dist/support/rollback.d.ts +61 -0
  549. package/dist/support/rollback.d.ts.map +1 -0
  550. package/dist/support/rollback.js +329 -0
  551. package/dist/support/rollback.js.map +1 -0
  552. package/dist/support/sharedShell.d.ts +17 -0
  553. package/dist/support/sharedShell.d.ts.map +1 -0
  554. package/dist/support/sharedShell.js +439 -0
  555. package/dist/support/sharedShell.js.map +1 -0
  556. package/dist/support/stuckDetector.d.ts +68 -0
  557. package/dist/support/stuckDetector.d.ts.map +1 -0
  558. package/dist/support/stuckDetector.js +174 -0
  559. package/dist/support/stuckDetector.js.map +1 -0
  560. package/dist/support/terminalBridge.d.ts +18 -0
  561. package/dist/support/terminalBridge.d.ts.map +1 -0
  562. package/dist/support/terminalBridge.js +553 -0
  563. package/dist/support/terminalBridge.js.map +1 -0
  564. package/dist/support/timeWindow.d.ts +60 -0
  565. package/dist/support/timeWindow.d.ts.map +1 -0
  566. package/dist/support/timeWindow.js +236 -0
  567. package/dist/support/timeWindow.js.map +1 -0
  568. package/dist/support/uiThemes.d.ts +44 -0
  569. package/dist/support/uiThemes.d.ts.map +1 -0
  570. package/dist/support/uiThemes.js +290 -0
  571. package/dist/support/uiThemes.js.map +1 -0
  572. package/dist/support/web.d.ts +29 -0
  573. package/dist/support/web.d.ts.map +1 -0
  574. package/dist/support/web.js +1097 -0
  575. package/dist/support/web.js.map +1 -0
  576. package/dist/support/worktreeManager.d.ts +20 -0
  577. package/dist/support/worktreeManager.d.ts.map +1 -0
  578. package/dist/support/worktreeManager.js +140 -0
  579. package/dist/support/worktreeManager.js.map +1 -0
  580. package/dist/task_state_model.py +55 -0
  581. package/dist/workItemState/store.d.ts +122 -0
  582. package/dist/workItemState/store.d.ts.map +1 -0
  583. package/dist/workItemState/store.js +438 -0
  584. package/dist/workItemState/store.js.map +1 -0
  585. package/dist/workItems/azdoBridge.d.ts +42 -0
  586. package/dist/workItems/azdoBridge.d.ts.map +1 -0
  587. package/dist/workItems/azdoBridge.js +143 -0
  588. package/dist/workItems/azdoBridge.js.map +1 -0
  589. package/dist/workItems/azdoSyncRuntime.d.ts +28 -0
  590. package/dist/workItems/azdoSyncRuntime.d.ts.map +1 -0
  591. package/dist/workItems/azdoSyncRuntime.js +158 -0
  592. package/dist/workItems/azdoSyncRuntime.js.map +1 -0
  593. package/dist/workItems/azureDevOpsSync.d.ts +128 -0
  594. package/dist/workItems/azureDevOpsSync.d.ts.map +1 -0
  595. package/dist/workItems/azureDevOpsSync.js +748 -0
  596. package/dist/workItems/azureDevOpsSync.js.map +1 -0
  597. package/dist/workItems/helpers.d.ts +11 -0
  598. package/dist/workItems/helpers.d.ts.map +1 -0
  599. package/dist/workItems/helpers.js +17 -0
  600. package/dist/workItems/helpers.js.map +1 -0
  601. package/dist/workItems/index.d.ts +21 -0
  602. package/dist/workItems/index.d.ts.map +1 -0
  603. package/dist/workItems/index.js +89 -0
  604. package/dist/workItems/index.js.map +1 -0
  605. package/dist/workItems/localWorkItemFetcher.d.ts +55 -0
  606. package/dist/workItems/localWorkItemFetcher.d.ts.map +1 -0
  607. package/dist/workItems/localWorkItemFetcher.js +209 -0
  608. package/dist/workItems/localWorkItemFetcher.js.map +1 -0
  609. package/dist/workItems/migrations/001_rename_workItem_to_work_item.sql +10 -0
  610. package/dist/workItems/postgresStore.d.ts +78 -0
  611. package/dist/workItems/postgresStore.d.ts.map +1 -0
  612. package/dist/workItems/postgresStore.js +937 -0
  613. package/dist/workItems/postgresStore.js.map +1 -0
  614. package/dist/workItems/schema.d.ts +257 -0
  615. package/dist/workItems/schema.d.ts.map +1 -0
  616. package/dist/workItems/schema.js +176 -0
  617. package/dist/workItems/schema.js.map +1 -0
  618. package/dist/workItems/sqliteStore.d.ts +124 -0
  619. package/dist/workItems/sqliteStore.d.ts.map +1 -0
  620. package/dist/workItems/sqliteStore.js +713 -0
  621. package/dist/workItems/sqliteStore.js.map +1 -0
  622. package/dist/workItems/workItemBoardHtml.d.ts +5 -0
  623. package/dist/workItems/workItemBoardHtml.d.ts.map +1 -0
  624. package/dist/workItems/workItemBoardHtml.js +2192 -0
  625. package/dist/workItems/workItemBoardHtml.js.map +1 -0
  626. package/package.json +99 -0
  627. package/templates/AGENTS.md +432 -0
  628. package/templates/BOOT.md +25 -0
  629. package/templates/BOOTSTRAP.md +50 -0
  630. package/templates/CHANGELOG_AUDIT.md +74 -0
  631. package/templates/HEARTBEAT.md +86 -0
  632. package/templates/IDENTITY.md +27 -0
  633. package/templates/PR_LAND.md +75 -0
  634. package/templates/PR_REVIEW.md +97 -0
  635. package/templates/SOUL.dev.md +52 -0
  636. package/templates/SOUL.md +81 -0
  637. package/templates/TOOLS.md +52 -0
  638. package/templates/USER.md +22 -0
  639. package/templates/WORKITEM_ANALYSIS.md +31 -0
  640. package/templates/agents/executor.md +26 -0
  641. package/templates/agents/plan.md +22 -0
  642. package/templates/agents/ralph.md +37 -0
  643. package/templates/agents/review.md +22 -0
  644. package/templates/agents/team.md +39 -0
@@ -0,0 +1,1097 @@
1
+ // ============================================
2
+ // EnactFactory - Web Interface
3
+ // ============================================
4
+ import { createServer } from 'node:http';
5
+ import { writeFileSync, readFileSync, existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import { addSSEClient, getActiveSSECount, broadcastEvent, getLogBuffer, getStageBuffer, getChatBuffer } from '../core/eventHub.js';
9
+ import { formatCost } from '../support/costTracker.js';
10
+ import { getRateLimiterMetrics } from '../support/rateLimiter.js';
11
+ import { scanLocalProjects, invalidateProjectCache } from '../support/projectMapper.js';
12
+ import { createDashboardHtml } from '../support/dashboardHtml.js';
13
+ import { getProjectGitInfo, startGitStatusPoller } from '../support/gitStatus.js';
14
+ import { getActiveMonitors, registerMonitor, unregisterMonitor } from '../automation/longRunningMonitor.js';
15
+ import { getAllProcesses, killProcess, startHealthChecker } from '../adapters/processRegistry.js';
16
+ import { setDefaultAdapter } from '../adapters/index.js';
17
+ import { fetchQuota } from '../support/quotaTracker.js';
18
+ import { PairPipeline } from '../agents/pairPipeline.js';
19
+ import { initLocale } from '../locale/index.js';
20
+ import { runChatCompletion, getDefaultChatModel } from '../support/chatBackend.js';
21
+ import { handleTerminalWebSocketUpgrade, shutdownTerminalBridge } from '../support/terminalBridge.js';
22
+ import { createWorkItemBoardHtml } from '../workItems/workItemBoardHtml.js';
23
+ import { getWorkItemStore, syncFromAzdo, isAzdoBridgeReady } from '../workItems/index.js';
24
+ import { getDaemonStatus, startDaemon, stopDaemon } from '../cli/daemon.js';
25
+ import { getWorkItemState } from '../workItemState/store.js';
26
+ import { handleFactoryApiRequest } from '../factory/http.js';
27
+ import { getLaneStatusSnapshot } from '../core/laneStatus.js';
28
+ import { buildDashboardSurfaceConfig } from '../core/dashboardContract.js';
29
+ import { requireWorkspaceUiPort } from '../core/workspaceConfig.js';
30
+ import { ensureAzdoSyncStarted, stopAzdoSync } from '../workItems/azdoSyncRuntime.js';
31
+ let server = null;
32
+ let runnerRef;
33
+ // In-process DB health state — updated by the sync heartbeat and /api/health checks.
34
+ // Exported so azdoSyncRuntime can mark it degraded on store errors.
35
+ let _dbHealthy = true;
36
+ export function setDbHealthy(ok) { _dbHealthy = ok; }
37
+ export function getDbHealthy() { return _dbHealthy; }
38
+ // The single UI port. Both `dash` and the daemon serve the dashboard here.
39
+ function defaultUiPort() {
40
+ return requireWorkspaceUiPort();
41
+ }
42
+ // CORS origin allowlist — hostname-strict match (no substring/prefix pitfalls)
43
+ function isAllowedOrigin(origin) {
44
+ let url;
45
+ try {
46
+ url = new URL(origin);
47
+ }
48
+ catch {
49
+ return false;
50
+ }
51
+ const { protocol, hostname } = url;
52
+ if (protocol !== 'http:' && protocol !== 'https:')
53
+ return false;
54
+ // Exact hostname matches
55
+ if (hostname === 'localhost' || hostname === '127.0.0.1')
56
+ return true;
57
+ if (hostname === 'tauri.localhost')
58
+ return true;
59
+ // Tailscale CGNAT range: 100.64.0.0/10 → first octet 100, second 64–127
60
+ const tailscaleMatch = hostname.match(/^100\.(\d{1,3})\.\d{1,3}\.\d{1,3}$/);
61
+ if (tailscaleMatch) {
62
+ const second = Number(tailscaleMatch[1]);
63
+ if (second >= 64 && second <= 127)
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ // Never leak raw Error objects (which include stack traces in many runtimes)
69
+ // or arbitrary thrown values to HTTP responses.
70
+ function safeErrorMessage(err) {
71
+ if (err instanceof Error && typeof err.message === 'string' && err.message) {
72
+ return err.message;
73
+ }
74
+ return 'Internal error';
75
+ }
76
+ const execWorkItems = new Map();
77
+ const CHAT_PROVIDERS = new Set(['claude', 'codex', 'responses', 'cursor', 'hermes', 'opencode']);
78
+ function cleanupExecWorkItem(workItemId) {
79
+ setTimeout(() => { execWorkItems.delete(workItemId); }, 3600000); // 1 hour
80
+ }
81
+ function isChatProvider(value) {
82
+ return typeof value === 'string' && CHAT_PROVIDERS.has(value);
83
+ }
84
+ function currentDashboardSurfaceConfig() {
85
+ return buildDashboardSurfaceConfig({
86
+ repoPaths: runnerRef?.getAllowedProjects() ?? [],
87
+ adapterSummary: runnerRef?.getAdapterSummary(),
88
+ });
89
+ }
90
+ // Pinned + enabled repos persistence
91
+ const REPOS_FILE = join(homedir(), '.claude', 'enact-factory-repos.json');
92
+ function loadReposConfig() {
93
+ try {
94
+ if (existsSync(REPOS_FILE)) {
95
+ return JSON.parse(readFileSync(REPOS_FILE, 'utf-8'));
96
+ }
97
+ }
98
+ catch (err) {
99
+ console.warn(`[Web] repos config 로드 실패:`, err instanceof Error ? err.message : err);
100
+ }
101
+ return { pinned: [], enabled: [], basePaths: [], removedConfigPaths: [] };
102
+ }
103
+ function saveReposConfig() {
104
+ try {
105
+ const cfg = {
106
+ pinned: Array.from(pinnedProjects),
107
+ enabled: runnerRef?.getEnabledProjects() ?? [],
108
+ basePaths: Array.from(customBasePaths),
109
+ removedConfigPaths: Array.from(removedConfigPaths),
110
+ };
111
+ writeFileSync(REPOS_FILE, JSON.stringify(cfg, null, 2));
112
+ }
113
+ catch (e) {
114
+ console.warn('[Web] Failed to save repos config:', e);
115
+ }
116
+ }
117
+ const _reposCfg = loadReposConfig();
118
+ const pinnedProjects = new Set(_reposCfg.pinned);
119
+ const customBasePaths = new Set(_reposCfg.basePaths ?? []);
120
+ const removedConfigPaths = new Set(_reposCfg.removedConfigPaths ?? []);
121
+ /** Expand a leading ~/ to the user's home directory (same logic as config.ts expandPath) */
122
+ function expandConfigPath(p) {
123
+ if (p.startsWith('~/'))
124
+ return join(homedir(), p.slice(2));
125
+ return p;
126
+ }
127
+ /**
128
+ * Seed config.projects[] paths into pinnedProjects so the dashboard REPOSITORIES
129
+ * panel shows them even when the user has no [autonomous] section (allowedProjects
130
+ * defaults to ~/dev) and hasn't manually pinned anything.
131
+ *
132
+ * Runner-independent: this only mutates pinnedProjects and, when a runner is given,
133
+ * the runner's path cache. De-dupe by expanded path makes it safe to call from both
134
+ * startWebServer (dash mode) and setWebRunner (daemon mode) — second call is a no-op
135
+ * for already-seeded paths. The runner.enableProject() side (config enabled/paused)
136
+ * stays in setWebRunner and is NOT done here.
137
+ */
138
+ export function seedConfigProjects(configProjects, runner) {
139
+ if (!configProjects || configProjects.length === 0)
140
+ return;
141
+ for (const project of configProjects) {
142
+ const resolvedPath = expandConfigPath(project.path);
143
+ // Skip paths the user explicitly removed from the dashboard
144
+ if (removedConfigPaths.has(resolvedPath))
145
+ continue;
146
+ // Pin if not already pinned (de-dupe by expanded path)
147
+ if (!pinnedProjects.has(resolvedPath)) {
148
+ pinnedProjects.add(resolvedPath);
149
+ }
150
+ // Register in path cache so Azdo project-name lookups resolve immediately
151
+ const name = resolvedPath.split('/').pop();
152
+ if (runner) {
153
+ runner.registerProjectPath(project.name, resolvedPath);
154
+ if (name && name !== project.name)
155
+ runner.registerProjectPath(name, resolvedPath);
156
+ }
157
+ }
158
+ }
159
+ /**
160
+ * Set runner reference (call after autonomous runner is initialized).
161
+ * @param configProjects - optional list from FactoryConfig.projects; when provided,
162
+ * their expanded paths are seeded into pinnedProjects so the dashboard REPOSITORIES
163
+ * panel shows them even before the user has manually pinned anything.
164
+ */
165
+ export function setWebRunner(runner, configProjects) {
166
+ runnerRef = runner;
167
+ // Restore persisted enabled state
168
+ for (const path of _reposCfg.enabled) {
169
+ runner.enableProject(path);
170
+ }
171
+ // 대시보드에서 제거된 config 경로를 runner의 allowedProjects에서도 제거
172
+ if (removedConfigPaths.size > 0) {
173
+ const current = runner.getAllowedProjects();
174
+ const filtered = current.filter(p => !removedConfigPaths.has(p));
175
+ if (filtered.length !== current.length) {
176
+ runner.updateAllowedProjects(filtered);
177
+ }
178
+ }
179
+ // Seed config.projects[] paths into pinnedProjects + path cache (de-duped).
180
+ if (configProjects && configProjects.length > 0) {
181
+ seedConfigProjects(configProjects, runner);
182
+ // Apply config enabled/paused state — runner-dependent, so kept here.
183
+ // Only when the user hasn't explicitly managed this path via the UI
184
+ // (i.e. not in the persisted enabled list).
185
+ const persistedEnabled = new Set(_reposCfg.enabled);
186
+ for (const project of configProjects) {
187
+ const resolvedPath = expandConfigPath(project.path);
188
+ if (removedConfigPaths.has(resolvedPath))
189
+ continue;
190
+ if (!persistedEnabled.has(resolvedPath) && project.enabled && !project.paused) {
191
+ runner.enableProject(resolvedPath);
192
+ }
193
+ }
194
+ }
195
+ // Pre-seed path cache from pinned + enabled projects so workItems without execution history are still matched
196
+ const allSeeded = new Set([...pinnedProjects, ..._reposCfg.enabled]);
197
+ for (const path of allSeeded) {
198
+ const name = path.split('/').pop();
199
+ if (name)
200
+ runner.registerProjectPath(name, path);
201
+ }
202
+ }
203
+ // Read POST body helper
204
+ function readBody(req) {
205
+ return new Promise((resolve) => {
206
+ let data = '';
207
+ req.on('data', (chunk) => { data += chunk.toString(); });
208
+ req.on('end', () => resolve(data));
209
+ });
210
+ }
211
+ // Start web server
212
+ export async function startWebServer(port = defaultUiPort()) {
213
+ if (server) {
214
+ console.log('Web interface already running, skipping...');
215
+ return;
216
+ }
217
+ // Seed config.projects[] so the dashboard REPOSITORIES panel is populated even in
218
+ // dash mode (where setWebRunner is never called and there is no autonomous runner).
219
+ // De-dupe by expanded path means this is harmless if setWebRunner later runs too.
220
+ try {
221
+ const { loadRuntimeConfig } = await import('../core/runtimeConfig.js');
222
+ const config = await loadRuntimeConfig();
223
+ seedConfigProjects(config.projects, runnerRef);
224
+ // Always-on, in-process AzDO sync: opening the dashboard via EITHER `dash`
225
+ // or the daemon keeps Postgres live and gives instant in-process SSE. The
226
+ // shared runtime guards against a second heartbeat when the daemon's
227
+ // service path also calls this (idempotent singleton).
228
+ try {
229
+ await ensureAzdoSyncStarted(config.azureDevOps);
230
+ }
231
+ catch (err) {
232
+ console.warn('[Web] Could not start AzDO sync:', err instanceof Error ? err.message : err);
233
+ }
234
+ }
235
+ catch (err) {
236
+ console.warn('[Web] Could not seed config projects:', err instanceof Error ? err.message : err);
237
+ }
238
+ return new Promise((resolve, reject) => {
239
+ server = createServer(async (req, res) => {
240
+ const url = req.url?.split('?')[0] || '/';
241
+ // CORS: allow localhost, Tauri webview, and Tailscale network
242
+ const origin = req.headers.origin;
243
+ if (origin && isAllowedOrigin(origin)) {
244
+ res.setHeader('Access-Control-Allow-Origin', origin);
245
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, DELETE');
246
+ }
247
+ if (await handleFactoryApiRequest(req, res)) {
248
+ return;
249
+ }
250
+ // ---- Work Item Board ----
251
+ if (url === '/workitems') {
252
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
253
+ res.end(createWorkItemBoardHtml(currentDashboardSurfaceConfig()));
254
+ // ---- Work Item Board Sync (Azdo -> local sqlite) ----
255
+ }
256
+ else if (url === '/api/workitems/sync' && req.method === 'POST') {
257
+ if (!isAzdoBridgeReady()) {
258
+ res.writeHead(202, { 'Content-Type': 'application/json' });
259
+ res.end(JSON.stringify({ ok: false, reason: 'workitem_bridge_not_ready' }));
260
+ }
261
+ else {
262
+ try {
263
+ const result = await syncFromAzdo(await getWorkItemStore(), 'azdo', { limit: 250 });
264
+ const syncedAt = Date.now();
265
+ res.writeHead(200, { 'Content-Type': 'application/json' });
266
+ res.end(JSON.stringify({ ok: true, pulled: result.pulled, created: result.created, updated: result.updated, pruned: result.pruned, syncedAt }));
267
+ broadcastEvent({ type: 'sync:complete', data: { syncedAt, pulled: result.pulled, pruned: result.pruned } });
268
+ }
269
+ catch (err) {
270
+ res.writeHead(500, { 'Content-Type': 'application/json' });
271
+ res.end(JSON.stringify({ ok: false, error: safeErrorMessage(err) }));
272
+ }
273
+ }
274
+ // ---- Dashboard ----
275
+ }
276
+ else if (url === '/' || url === '/index.html') {
277
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
278
+ res.end(createDashboardHtml(currentDashboardSurfaceConfig()));
279
+ // ---- SSE stream ----
280
+ }
281
+ else if (url === '/api/events') {
282
+ const skipReplay = req.url?.includes('skipReplay=1') ?? false;
283
+ res.writeHead(200, {
284
+ 'Content-Type': 'text/event-stream',
285
+ 'Cache-Control': 'no-cache',
286
+ 'Connection': 'keep-alive',
287
+ });
288
+ res.write(':connected\n\n');
289
+ addSSEClient(res, skipReplay);
290
+ // ---- Stats ----
291
+ }
292
+ else if (url === '/api/stats') {
293
+ const stats = runnerRef?.getStats();
294
+ const state = runnerRef?.getState();
295
+ const adapters = runnerRef?.getAdapterSummary();
296
+ res.writeHead(200, { 'Content-Type': 'application/json' });
297
+ res.end(JSON.stringify({
298
+ runningWorkItems: stats?.schedulerStats?.running ?? 0,
299
+ queuedWorkItems: stats?.schedulerStats?.queued ?? 0,
300
+ completedToday: stats?.schedulerStats?.completed ?? 0,
301
+ uptime: state?.startedAt ? Date.now() - state.startedAt : 0,
302
+ isRunning: stats?.isRunning ?? false,
303
+ sseClients: getActiveSSECount(),
304
+ adapters,
305
+ turboMode: stats?.turboMode ?? false,
306
+ turboExpiresAt: stats?.turboExpiresAt ?? null,
307
+ dailyPace: stats?.dailyPace ?? null,
308
+ executionPaused: runnerRef?.isExecutionPaused() ?? true,
309
+ }));
310
+ // ---- WorkItems ----
311
+ }
312
+ else if (url === '/api/workItems') {
313
+ const running = runnerRef?.getRunningWorkItems() ?? [];
314
+ const queued = runnerRef?.getQueuedWorkItems() ?? [];
315
+ res.writeHead(200, { 'Content-Type': 'application/json' });
316
+ res.end(JSON.stringify({ running, queued }));
317
+ // ---- Pipeline GET (detailed pipeline stages) ----
318
+ }
319
+ else if (url === '/api/pipeline') {
320
+ const stages = getStageBuffer();
321
+ res.writeHead(200, { 'Content-Type': 'application/json' });
322
+ res.end(JSON.stringify({ stages }));
323
+ // ---- Rate Limiter Metrics GET ----
324
+ }
325
+ else if (url === '/api/rate-limits') {
326
+ const metrics = getRateLimiterMetrics();
327
+ res.writeHead(200, { 'Content-Type': 'application/json' });
328
+ res.end(JSON.stringify(metrics));
329
+ // ---- Projects GET (pinned + active projects) ----
330
+ }
331
+ else if (url === '/api/projects' && req.method === 'GET') {
332
+ const enabledPaths = new Set(runnerRef?.getEnabledProjects() ?? []);
333
+ const workItemInfo = runnerRef?.getProjectsInfo() ?? [];
334
+ const byPath = new Map(workItemInfo.filter(p => p.path).map(p => [p.path, p]));
335
+ // Fallback: match by project name (for workItems not yet executed → path not cached)
336
+ const byName = new Map(workItemInfo.map(p => [p.name, p]));
337
+ // Start with pinned projects
338
+ const allPaths = new Set(pinnedProjects);
339
+ // Auto-include enabled projects and projects with active workItems
340
+ for (const path of enabledPaths)
341
+ allPaths.add(path);
342
+ for (const info of workItemInfo) {
343
+ if (info.path && (info.running.length > 0 || info.queued.length > 0)) {
344
+ allPaths.add(info.path);
345
+ }
346
+ }
347
+ const result = await Promise.all(Array.from(allPaths).map(async (p) => {
348
+ const dirName = p.split('/').pop() ?? p;
349
+ let info = byPath.get(p) ?? byName.get(dirName);
350
+ if (!info) {
351
+ // Fuzzy fallback: map enabled path basename (e.g. enact-runtime)
352
+ // to Azdo project names (e.g. "Enact Runtime — ...") when
353
+ // path cache isn't populated for that project yet.
354
+ const normDir = dirName.toLowerCase().replace(/[^a-z0-9]/g, '');
355
+ info = workItemInfo.find((candidate) => {
356
+ const normName = candidate.name.toLowerCase().replace(/[^a-z0-9]/g, '');
357
+ if (!normName || !normDir)
358
+ return false;
359
+ return normName.includes(normDir) || normDir.includes(normName);
360
+ });
361
+ }
362
+ const gitInfo = await getProjectGitInfo(p);
363
+ const pending = (info?.pending ?? []).map((workItem) => {
364
+ const retry = runnerRef?.getRetryBackoff(workItem.id);
365
+ const state = getWorkItemState(workItem.id);
366
+ const enriched = {
367
+ ...workItem,
368
+ executionStatus: state?.execution?.status,
369
+ blockedReason: state?.execution?.blockedReason,
370
+ };
371
+ if (!retry)
372
+ return enriched;
373
+ return {
374
+ ...enriched,
375
+ retryAt: retry.retryAt,
376
+ retryRemainingMs: retry.remainingMs,
377
+ retryFailedCount: retry.failedCount,
378
+ };
379
+ });
380
+ return {
381
+ path: p,
382
+ name: dirName,
383
+ enabled: enabledPaths.has(p),
384
+ pinned: pinnedProjects.has(p),
385
+ running: info?.running ?? [],
386
+ queued: info?.queued ?? [],
387
+ pending,
388
+ git: gitInfo.git,
389
+ prs: gitInfo.prs,
390
+ };
391
+ }));
392
+ result.sort((a, b) => {
393
+ const aActive = a.running.length + a.queued.length + a.pending.length;
394
+ const bActive = b.running.length + b.queued.length + b.pending.length;
395
+ if (aActive !== bActive)
396
+ return bActive - aActive;
397
+ return a.name.localeCompare(b.name);
398
+ });
399
+ res.writeHead(200, { 'Content-Type': 'application/json' });
400
+ res.end(JSON.stringify(result));
401
+ // ---- Local projects for picker ----
402
+ }
403
+ else if (url === '/api/local-projects' && req.method === 'GET') {
404
+ const configPaths = runnerRef?.getAllowedProjects() ?? [];
405
+ const allBasePaths = [...new Set([...configPaths, ...customBasePaths])];
406
+ try {
407
+ const locals = await scanLocalProjects(allBasePaths);
408
+ const SKIP = ['/node_modules/', '/.git/', '/dist/', '/build/', '/__pycache__/', '/venv/', '/.venv/'];
409
+ const filtered = locals.filter(l => !SKIP.some(s => l.path.includes(s)));
410
+ res.writeHead(200, { 'Content-Type': 'application/json' });
411
+ res.end(JSON.stringify(filtered.map(l => ({ path: l.path, name: l.name, pinned: pinnedProjects.has(l.path) }))));
412
+ }
413
+ catch (e) {
414
+ res.writeHead(500, { 'Content-Type': 'application/json' });
415
+ res.end(JSON.stringify({ error: safeErrorMessage(e) }));
416
+ }
417
+ // ---- Pin project ----
418
+ }
419
+ else if (url === '/api/projects/pin' && req.method === 'POST') {
420
+ const body = await readBody(req);
421
+ try {
422
+ const { projectPath } = JSON.parse(body);
423
+ if (typeof projectPath === 'string' && projectPath) {
424
+ pinnedProjects.add(projectPath);
425
+ saveReposConfig();
426
+ // Seed path cache so Azdo project name matches immediately
427
+ const name = projectPath.split('/').pop();
428
+ if (name && runnerRef)
429
+ runnerRef.registerProjectPath(name, projectPath);
430
+ }
431
+ res.writeHead(200, { 'Content-Type': 'application/json' });
432
+ res.end(JSON.stringify({ ok: true }));
433
+ }
434
+ catch {
435
+ res.writeHead(400).end(JSON.stringify({ error: 'Invalid JSON' }));
436
+ }
437
+ // ---- Unpin project ----
438
+ }
439
+ else if (url === '/api/projects/unpin' && req.method === 'POST') {
440
+ const body = await readBody(req);
441
+ try {
442
+ const { projectPath } = JSON.parse(body);
443
+ if (typeof projectPath === 'string') {
444
+ pinnedProjects.delete(projectPath);
445
+ // Also disable the project so it doesn't reappear via enabledPaths
446
+ runnerRef?.disableProject(projectPath);
447
+ saveReposConfig();
448
+ }
449
+ res.writeHead(200, { 'Content-Type': 'application/json' });
450
+ res.end(JSON.stringify({ ok: true }));
451
+ }
452
+ catch {
453
+ res.writeHead(400).end(JSON.stringify({ error: 'Invalid JSON' }));
454
+ }
455
+ // ---- Projects Toggle ----
456
+ }
457
+ else if (url === '/api/projects/toggle' && req.method === 'POST') {
458
+ const body = await readBody(req);
459
+ try {
460
+ const { projectPath, enabled } = JSON.parse(body);
461
+ if (typeof projectPath === 'string' && typeof enabled === 'boolean') {
462
+ if (enabled)
463
+ runnerRef?.enableProject(projectPath);
464
+ else
465
+ runnerRef?.disableProject(projectPath);
466
+ saveReposConfig();
467
+ broadcastEvent({ type: 'project:toggled', data: { projectPath, enabled } });
468
+ }
469
+ res.writeHead(200, { 'Content-Type': 'application/json' });
470
+ res.end(JSON.stringify({ ok: true }));
471
+ }
472
+ catch {
473
+ res.writeHead(400, { 'Content-Type': 'application/json' });
474
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
475
+ }
476
+ // ---- Move WorkItem to Todo ----
477
+ }
478
+ else if ((url === '/api/workitems/move-to-todo')
479
+ && req.method === 'POST') {
480
+ const body = await readBody(req);
481
+ try {
482
+ const { workItemId } = JSON.parse(body);
483
+ if (!workItemId) {
484
+ res.writeHead(400, { 'Content-Type': 'application/json' });
485
+ res.end(JSON.stringify({ error: 'Missing workItemId' }));
486
+ return;
487
+ }
488
+ // Import azdo dynamically to avoid circular deps
489
+ const azdoModule = await import('../azdo/index.js');
490
+ await azdoModule.updateWorkItemState(workItemId, 'Todo');
491
+ res.writeHead(200, { 'Content-Type': 'application/json' });
492
+ res.end(JSON.stringify({ ok: true }));
493
+ }
494
+ catch (error) {
495
+ console.error('[Web] Failed to move workitem to Todo:', error);
496
+ res.writeHead(500, { 'Content-Type': 'application/json' });
497
+ res.end(JSON.stringify({ error: safeErrorMessage(error) }));
498
+ }
499
+ // ---- Heartbeat (manual trigger) ----
500
+ }
501
+ else if (url === '/api/heartbeat' && req.method === 'POST') {
502
+ res.writeHead(202, { 'Content-Type': 'application/json' });
503
+ res.end(JSON.stringify({ ok: true }));
504
+ // Non-blocking heartbeat
505
+ runnerRef?.heartbeat().catch((e) => console.error('[Web] Heartbeat error:', e));
506
+ // ---- Provider toggle ----
507
+ }
508
+ else if (url === '/api/provider' && req.method === 'POST') {
509
+ const body = await readBody(req);
510
+ try {
511
+ const { provider } = JSON.parse(body);
512
+ const validProviders = ['claude', 'codex', 'cursor', 'hermes', 'opencode', 'responses'];
513
+ if (!validProviders.includes(provider)) {
514
+ res.writeHead(400, { 'Content-Type': 'application/json' });
515
+ res.end(JSON.stringify({ error: 'Invalid provider' }));
516
+ return;
517
+ }
518
+ setDefaultAdapter(provider);
519
+ runnerRef?.switchProvider(provider);
520
+ broadcastEvent({
521
+ type: 'log',
522
+ data: { workItemId: 'system', stage: 'provider', line: `Provider switched to ${provider}` },
523
+ });
524
+ res.writeHead(200, { 'Content-Type': 'application/json' });
525
+ res.end(JSON.stringify({ ok: true, provider }));
526
+ }
527
+ catch {
528
+ res.writeHead(400, { 'Content-Type': 'application/json' });
529
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
530
+ }
531
+ // ---- Turbo Mode Toggle ----
532
+ }
533
+ else if (url === '/api/turbo' && req.method === 'POST') {
534
+ const body = await readBody(req);
535
+ try {
536
+ const { enabled } = JSON.parse(body);
537
+ if (typeof enabled !== 'boolean') {
538
+ res.writeHead(400, { 'Content-Type': 'application/json' });
539
+ res.end(JSON.stringify({ error: 'enabled must be boolean' }));
540
+ return;
541
+ }
542
+ runnerRef?.setTurboMode(enabled);
543
+ res.writeHead(200, { 'Content-Type': 'application/json' });
544
+ res.end(JSON.stringify({ ok: true, turboMode: enabled }));
545
+ }
546
+ catch {
547
+ res.writeHead(400, { 'Content-Type': 'application/json' });
548
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
549
+ }
550
+ // ---- Execution Pause/Resume ----
551
+ }
552
+ else if (url === '/api/execution/pause' && req.method === 'POST') {
553
+ const body = await readBody(req);
554
+ let reason;
555
+ try {
556
+ const parsed = JSON.parse(body);
557
+ reason = parsed.reason;
558
+ }
559
+ catch { /* body is optional */ }
560
+ runnerRef?.pauseExecution(reason);
561
+ res.writeHead(200, { 'Content-Type': 'application/json' });
562
+ res.end(JSON.stringify({ ok: true, paused: runnerRef?.isExecutionPaused() ?? true, ...(runnerRef == null ? { note: 'no runner in this mode' } : {}) }));
563
+ }
564
+ else if (url === '/api/execution/resume' && req.method === 'POST') {
565
+ runnerRef?.resumeExecution();
566
+ res.writeHead(200, { 'Content-Type': 'application/json' });
567
+ res.end(JSON.stringify({ ok: true, paused: runnerRef?.isExecutionPaused() ?? true }));
568
+ }
569
+ else if (url === '/api/execution/status' && req.method === 'GET') {
570
+ const schedulerStats = runnerRef?.getStats()?.schedulerStats;
571
+ res.writeHead(200, { 'Content-Type': 'application/json' });
572
+ res.end(JSON.stringify({
573
+ paused: runnerRef?.isExecutionPaused() ?? true,
574
+ inFlightCount: schedulerStats?.running ?? 0,
575
+ }));
576
+ // ---- CI Executor Status (removed — AzDO-only) ----
577
+ }
578
+ else if (url === '/api/ci-executor-status' && req.method === 'GET') {
579
+ res.writeHead(200, { 'Content-Type': 'application/json' });
580
+ res.end(JSON.stringify({ running: false }));
581
+ // ---- Stuck/Failed Workitems ----
582
+ }
583
+ else if ((url === '/api/workitems/stuck')
584
+ && req.method === 'GET') {
585
+ try {
586
+ const azdoModule = await import('../azdo/index.js');
587
+ const result = await azdoModule.getStuckWorkItems();
588
+ // Augment with runtime-local blocked/retry state (not always reflected
589
+ // in Azdo labels), so dashboard STUCK/FAILED count stays truthful.
590
+ const localFailed = (runnerRef?.getProjectsInfo() ?? []).flatMap((project) => {
591
+ return project.pending.flatMap((workItem) => {
592
+ const retry = runnerRef?.getRetryBackoff(workItem.id);
593
+ const state = getWorkItemState(workItem.id);
594
+ const isBlocked = state?.execution?.status === 'blocked';
595
+ if (!retry && !isBlocked)
596
+ return [];
597
+ const reason = retry
598
+ ? `Retry backoff (${retry.failedCount}/4) — next attempt in ${Math.max(1, Math.ceil(retry.remainingMs / 60000))}m`
599
+ : (state?.execution?.blockedReason || 'Blocked');
600
+ return [{
601
+ id: workItem.id,
602
+ identifier: workItem.workItemIdentifier || workItem.id,
603
+ title: workItem.title,
604
+ description: undefined,
605
+ state: workItem.azdoState ?? 'Unknown',
606
+ priority: workItem.priority,
607
+ labels: [],
608
+ comments: [],
609
+ project: { id: 'local', name: project.name },
610
+ reason,
611
+ }];
612
+ });
613
+ });
614
+ const known = new Set(result.failedWorkItems.map((i) => i.id));
615
+ const mergedFailed = [
616
+ ...result.failedWorkItems,
617
+ ...localFailed.filter((i) => !known.has(i.id)),
618
+ ];
619
+ res.writeHead(200, { 'Content-Type': 'application/json' });
620
+ res.end(JSON.stringify({
621
+ stuckWorkItems: result.stuckWorkItems,
622
+ failedWorkItems: mergedFailed,
623
+ }));
624
+ }
625
+ catch (error) {
626
+ console.error('[Web] Failed to fetch stuck work items:', error);
627
+ res.writeHead(500, { 'Content-Type': 'application/json' });
628
+ res.end(JSON.stringify({ error: safeErrorMessage(error) }));
629
+ }
630
+ // ---- Chat history ----
631
+ }
632
+ else if (url === '/api/chat/history' && req.method === 'GET') {
633
+ const buf = getChatBuffer();
634
+ const history = buf
635
+ .filter((ev) => ev.type === 'chat:user' || ev.type === 'chat:agent')
636
+ .map(ev => ({
637
+ role: ev.type === 'chat:user' ? 'user' : 'agent',
638
+ text: ev.data.text,
639
+ ts: ev.data.ts,
640
+ }));
641
+ res.writeHead(200, { 'Content-Type': 'application/json' });
642
+ res.end(JSON.stringify(history.slice(-50)));
643
+ // ---- Log buffer snapshot ----
644
+ }
645
+ else if (url === '/api/logs' && req.method === 'GET') {
646
+ // 성능 최적화: 최근 200개 로그만 반환 (응답 시간 단축)
647
+ const allLogs = getLogBuffer();
648
+ const recentLogs = allLogs.slice(-200);
649
+ res.writeHead(200, { 'Content-Type': 'application/json' });
650
+ res.end(JSON.stringify(recentLogs));
651
+ // ---- Stage buffer snapshot ----
652
+ }
653
+ else if (url === '/api/stages' && req.method === 'GET') {
654
+ // 성능 최적화: 최근 100개 스테이지만 반환 (응답 시간 단축)
655
+ const allStages = getStageBuffer();
656
+ const recentStages = allStages.slice(-100);
657
+ res.writeHead(200, { 'Content-Type': 'application/json' });
658
+ res.end(JSON.stringify(recentStages));
659
+ // ---- Chat message ----
660
+ }
661
+ else if (url === '/api/chat' && req.method === 'POST') {
662
+ const body = await readBody(req);
663
+ let message;
664
+ let providerOverride;
665
+ let modelOverride;
666
+ try {
667
+ const payload = JSON.parse(body);
668
+ message = payload.message?.trim() ?? '';
669
+ providerOverride = isChatProvider(payload.provider) ? payload.provider : undefined;
670
+ modelOverride = typeof payload.model === 'string' && payload.model.trim().length > 0
671
+ ? payload.model.trim()
672
+ : undefined;
673
+ }
674
+ catch {
675
+ res.writeHead(400, { 'Content-Type': 'application/json' });
676
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
677
+ return;
678
+ }
679
+ if (!message) {
680
+ res.writeHead(400, { 'Content-Type': 'application/json' });
681
+ res.end(JSON.stringify({ error: 'Empty message' }));
682
+ return;
683
+ }
684
+ broadcastEvent({ type: 'chat:user', data: { text: message, ts: Date.now() } });
685
+ // Build context-aware prompt (including previous conversation)
686
+ const stats = runnerRef?.getStats();
687
+ const projects = runnerRef?.getProjectsInfo() ?? [];
688
+ const enabled = projects.filter(p => p.enabled).length;
689
+ const state = runnerRef?.getState();
690
+ const uptimeSec = state?.startedAt ? Math.floor((Date.now() - state.startedAt) / 1000) : 0;
691
+ // 1. Short-term memory: recent chat buffer
692
+ const chatBuf = getChatBuffer()
693
+ .filter((ev) => ev.type === 'chat:user' || ev.type === 'chat:agent');
694
+ const recentHistory = chatBuf.slice(-11, -1);
695
+ const historyBlock = recentHistory.length > 0
696
+ ? recentHistory.map(m => (m.type === 'chat:user' ? 'User' : 'EnactFactory') + ': ' + m.data.text).join('\n\n')
697
+ : '';
698
+ const adapterSummary = runnerRef?.getAdapterSummary();
699
+ const provider = providerOverride ?? adapterSummary?.defaultAdapter ?? 'claude';
700
+ const model = modelOverride ?? adapterSummary?.coder?.model ?? getDefaultChatModel(provider);
701
+ const contextPrompt = [
702
+ 'You are EnactFactory, an autonomous code development factory.',
703
+ 'You manage a fleet of coding agents that autonomously work on Work Items.',
704
+ `Current chat provider: ${provider}`,
705
+ `Current chat model: ${model}`,
706
+ '',
707
+ 'Current system status:',
708
+ '- Running workItems: ' + (stats?.schedulerStats?.running ?? 0),
709
+ '- Queued workItems: ' + (stats?.schedulerStats?.queued ?? 0),
710
+ '- Completed today: ' + (stats?.schedulerStats?.completed ?? 0),
711
+ '- Active repos: ' + enabled + '/' + projects.length,
712
+ '- Uptime: ' + uptimeSec + 's',
713
+ '',
714
+ ...(historyBlock ? [
715
+ 'Conversation history (most recent first):',
716
+ historyBlock,
717
+ '',
718
+ ] : []),
719
+ 'Answer the user concisely and helpfully in the same language they use. Use the status data above if relevant.',
720
+ '',
721
+ 'User: ' + message,
722
+ ].join('\n');
723
+ const result = await runChatCompletion({
724
+ prompt: contextPrompt,
725
+ provider,
726
+ model,
727
+ cwd: process.cwd(),
728
+ timeoutMs: 180000,
729
+ }).catch((error) => ({
730
+ response: `[Error: ${error.message}]`,
731
+ provider,
732
+ model,
733
+ cost: undefined,
734
+ tokens: undefined,
735
+ }));
736
+ const response = result.response;
737
+ if (result.cost !== undefined) {
738
+ console.log(`[Web Chat] Cost: ${formatCost({
739
+ costUsd: result.cost,
740
+ inputTokens: result.tokens ?? 0,
741
+ outputTokens: 0,
742
+ cacheReadTokens: 0,
743
+ cacheCreationTokens: 0,
744
+ durationMs: 0,
745
+ model: result.model,
746
+ })}`);
747
+ }
748
+ broadcastEvent({ type: 'chat:agent', data: { text: response, ts: Date.now() } });
749
+ res.writeHead(200, { 'Content-Type': 'application/json' });
750
+ res.end(JSON.stringify({ ok: true, response, provider: result.provider, model: result.model }));
751
+ // ---- App health check (lightweight DB ping) ----
752
+ }
753
+ else if (url === '/api/health' && req.method === 'GET') {
754
+ let dbOk = false;
755
+ try {
756
+ const store = await getWorkItemStore();
757
+ // Cheapest possible check: getSyncMeta with a non-existent key returns null, never throws for healthy DBs.
758
+ await store.getSyncMeta('__health_ping__');
759
+ dbOk = true;
760
+ setDbHealthy(true);
761
+ }
762
+ catch {
763
+ dbOk = false;
764
+ setDbHealthy(false);
765
+ }
766
+ const syncService = (await import('../workItems/azdoSyncRuntime.js')).getAzdoSyncService();
767
+ const syncStatus = syncService?.getStatus();
768
+ const syncOk = !syncStatus || (!syncStatus.isStale && !syncStatus.lastError);
769
+ res.writeHead(200, { 'Content-Type': 'application/json' });
770
+ res.end(JSON.stringify({
771
+ ok: dbOk,
772
+ db: dbOk,
773
+ sync: syncOk ? 'ok' : 'degraded',
774
+ }));
775
+ // ---- Service control: status ----
776
+ }
777
+ else if (url === '/api/service/status' && req.method === 'GET') {
778
+ const daemon = getDaemonStatus();
779
+ res.writeHead(200, { 'Content-Type': 'application/json' });
780
+ res.end(JSON.stringify({
781
+ status: daemon.running ? 'active' : 'inactive',
782
+ pid: daemon.pid,
783
+ uptimeSeconds: daemon.uptimeSeconds,
784
+ pidFile: daemon.pidFile,
785
+ logFile: daemon.logFile,
786
+ }));
787
+ // ---- Service control: stop ----
788
+ }
789
+ else if (url === '/api/service/stop' && req.method === 'POST') {
790
+ try {
791
+ const stopped = await stopDaemon();
792
+ res.writeHead(200, { 'Content-Type': 'application/json' });
793
+ res.end(JSON.stringify({ ok: true, stopped }));
794
+ }
795
+ catch (e) {
796
+ res.writeHead(500, { 'Content-Type': 'application/json' });
797
+ res.end(JSON.stringify({ error: safeErrorMessage(e) }));
798
+ }
799
+ // ---- Service control: restart ----
800
+ }
801
+ else if (url === '/api/service/restart' && req.method === 'POST') {
802
+ try {
803
+ await stopDaemon();
804
+ const started = startDaemon();
805
+ res.writeHead(200, { 'Content-Type': 'application/json' });
806
+ res.end(JSON.stringify({ ok: true, pid: started.pid }));
807
+ }
808
+ catch (e) {
809
+ res.writeHead(500, { 'Content-Type': 'application/json' });
810
+ res.end(JSON.stringify({ error: safeErrorMessage(e) }));
811
+ }
812
+ // ---- Pipeline history (time-ordered) ----
813
+ }
814
+ else if (url === '/api/pipeline/history' && req.method === 'GET') {
815
+ const limitParam = (req.url?.split('?')[1] || '').match(/limit=(\d+)/);
816
+ const limit = limitParam ? Math.min(Number(limitParam[1]), 100) : 50;
817
+ const history = runnerRef?.getPipelineHistory(limit) ?? [];
818
+ res.writeHead(200, { 'Content-Type': 'application/json' });
819
+ res.end(JSON.stringify(history));
820
+ // ---- Monitors: list ----
821
+ }
822
+ else if (url === '/api/monitors' && req.method === 'GET') {
823
+ res.writeHead(200, { 'Content-Type': 'application/json' });
824
+ res.end(JSON.stringify(getActiveMonitors()));
825
+ // ---- Monitors: register ----
826
+ }
827
+ else if (url === '/api/monitors' && req.method === 'POST') {
828
+ const body = await readBody(req);
829
+ try {
830
+ const config = JSON.parse(body);
831
+ if (!config.id ||
832
+ !config.name ||
833
+ !Array.isArray(config.checkCommand) ||
834
+ config.checkCommand.length === 0 ||
835
+ !config.completionCheck) {
836
+ res.writeHead(400, { 'Content-Type': 'application/json' });
837
+ res.end(JSON.stringify({
838
+ error: 'Missing or invalid required fields: id, name, checkCommand (string[]), completionCheck',
839
+ }));
840
+ return;
841
+ }
842
+ const monitor = registerMonitor(config);
843
+ res.writeHead(201, { 'Content-Type': 'application/json' });
844
+ res.end(JSON.stringify(monitor));
845
+ }
846
+ catch (e) {
847
+ res.writeHead(400, { 'Content-Type': 'application/json' });
848
+ res.end(JSON.stringify({ error: safeErrorMessage(e) }));
849
+ }
850
+ // ---- Monitors: delete ----
851
+ }
852
+ else if (url.startsWith('/api/monitors/') && req.method === 'DELETE') {
853
+ const monitorId = url.replace('/api/monitors/', '');
854
+ const deleted = unregisterMonitor(monitorId);
855
+ res.writeHead(deleted ? 200 : 404, { 'Content-Type': 'application/json' });
856
+ res.end(JSON.stringify({ ok: deleted }));
857
+ // ---- Processes: list ----
858
+ }
859
+ else if (url === '/api/processes' && req.method === 'GET') {
860
+ res.writeHead(200, { 'Content-Type': 'application/json' });
861
+ res.end(JSON.stringify(getAllProcesses()));
862
+ // ---- Lane status (workspace truth: lanes + assignments + terminal sessions) ----
863
+ }
864
+ else if ((url === '/api/lanes' || url === '/api/lane-status') && req.method === 'GET') {
865
+ try {
866
+ const payload = getLaneStatusSnapshot();
867
+ res.writeHead(200, { 'Content-Type': 'application/json' });
868
+ res.end(JSON.stringify(payload));
869
+ }
870
+ catch {
871
+ res.writeHead(500, { 'Content-Type': 'application/json' });
872
+ res.end(JSON.stringify({ error: 'Lane status unavailable' }));
873
+ }
874
+ // ---- Processes: kill ----
875
+ }
876
+ else if (url.startsWith('/api/processes/') && req.method === 'DELETE') {
877
+ const pidStr = url.replace('/api/processes/', '');
878
+ const pid = parseInt(pidStr, 10);
879
+ if (isNaN(pid)) {
880
+ res.writeHead(400, { 'Content-Type': 'application/json' });
881
+ res.end(JSON.stringify({ error: 'Invalid PID' }));
882
+ }
883
+ else {
884
+ const killed = await killProcess(pid);
885
+ res.writeHead(killed ? 200 : 404, { 'Content-Type': 'application/json' });
886
+ res.end(JSON.stringify({ ok: killed }));
887
+ }
888
+ // ---- Scan Paths: list ----
889
+ }
890
+ else if (url === '/api/scan-paths' && req.method === 'GET') {
891
+ // removedConfigPaths에 있는 경로는 UI에 표시하지 않음
892
+ const allConfigPaths = runnerRef?.getAllowedProjects() ?? [];
893
+ const configPaths = allConfigPaths.filter(p => !removedConfigPaths.has(p));
894
+ res.writeHead(200, { 'Content-Type': 'application/json' });
895
+ res.end(JSON.stringify({
896
+ configPaths,
897
+ customPaths: Array.from(customBasePaths),
898
+ }));
899
+ // ---- Scan Paths: add ----
900
+ }
901
+ else if (url === '/api/scan-paths' && req.method === 'POST') {
902
+ const body = await readBody(req);
903
+ try {
904
+ const { path: newPath } = JSON.parse(body);
905
+ if (typeof newPath !== 'string' || !newPath.trim()) {
906
+ res.writeHead(400, { 'Content-Type': 'application/json' });
907
+ res.end(JSON.stringify({ error: 'Missing path' }));
908
+ }
909
+ else {
910
+ customBasePaths.add(newPath.trim());
911
+ invalidateProjectCache();
912
+ // Update runner's allowedProjects with merged list
913
+ const configPaths = runnerRef?.getAllowedProjects() ?? [];
914
+ const merged = [...new Set([...configPaths, ...customBasePaths])];
915
+ runnerRef?.updateAllowedProjects(merged);
916
+ saveReposConfig();
917
+ res.writeHead(200, { 'Content-Type': 'application/json' });
918
+ res.end(JSON.stringify({ ok: true }));
919
+ }
920
+ }
921
+ catch {
922
+ res.writeHead(400, { 'Content-Type': 'application/json' });
923
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
924
+ }
925
+ // ---- Scan Paths: remove ----
926
+ }
927
+ else if (url.startsWith('/api/scan-paths/') && req.method === 'DELETE') {
928
+ const encodedPath = url.replace('/api/scan-paths/', '');
929
+ const decodedPath = decodeURIComponent(encodedPath);
930
+ // customPaths에서 제거
931
+ customBasePaths.delete(decodedPath);
932
+ // configPaths에서도 제거: removedConfigPaths에 기록하고 runner에서 즉시 반영
933
+ const allConfigPaths = runnerRef?.getAllowedProjects() ?? [];
934
+ if (allConfigPaths.includes(decodedPath)) {
935
+ removedConfigPaths.add(decodedPath);
936
+ runnerRef?.updateAllowedProjects(allConfigPaths.filter(p => p !== decodedPath));
937
+ }
938
+ invalidateProjectCache();
939
+ saveReposConfig();
940
+ res.writeHead(200, { 'Content-Type': 'application/json' });
941
+ res.end(JSON.stringify({ ok: true }));
942
+ // ---- Claude Code Quota ----
943
+ }
944
+ else if (url === '/api/quota' && req.method === 'GET') {
945
+ const quota = await fetchQuota();
946
+ res.writeHead(200, { 'Content-Type': 'application/json' });
947
+ res.end(JSON.stringify(quota ?? { error: 'unavailable' }));
948
+ // ---- Chat history (event hub buffer) ----
949
+ }
950
+ else if (url === '/api/history') {
951
+ const history = getChatBuffer();
952
+ res.writeHead(200, { 'Content-Type': 'application/json' });
953
+ res.end(JSON.stringify(history));
954
+ // ---- Exec: submit workItem ----
955
+ }
956
+ else if (url === '/api/exec' && req.method === 'POST') {
957
+ const body = await readBody(req);
958
+ try {
959
+ const { prompt, projectPath, pipeline, executorOnly, model } = JSON.parse(body);
960
+ if (!prompt?.trim()) {
961
+ res.writeHead(400, { 'Content-Type': 'application/json' });
962
+ res.end(JSON.stringify({ error: 'Missing prompt' }));
963
+ return;
964
+ }
965
+ const workItemId = `exec-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
966
+ const resolvedPath = projectPath ?? process.cwd();
967
+ const entry = {
968
+ workItemId,
969
+ status: 'queued',
970
+ createdAt: Date.now(),
971
+ };
972
+ execWorkItems.set(workItemId, entry);
973
+ res.writeHead(202, { 'Content-Type': 'application/json' });
974
+ res.end(JSON.stringify({ workItemId, status: 'queued' }));
975
+ // Run pipeline asynchronously
976
+ (async () => {
977
+ try {
978
+ initLocale('en');
979
+ entry.status = 'running';
980
+ // Determine stages
981
+ let stages;
982
+ if (executorOnly) {
983
+ stages = ['coder'];
984
+ }
985
+ else if (pipeline) {
986
+ stages = ['coder', 'critic', 'tester', 'documenter'];
987
+ }
988
+ else {
989
+ stages = ['coder', 'critic'];
990
+ }
991
+ // Build role config
992
+ const stageConfigs = {};
993
+ if (model) {
994
+ stageConfigs.coder = { enabled: true, model, timeoutMs: 0 };
995
+ }
996
+ const workItem = {
997
+ id: workItemId,
998
+ source: 'local',
999
+ title: prompt,
1000
+ description: prompt,
1001
+ priority: 3,
1002
+ projectPath: resolvedPath,
1003
+ createdAt: Date.now(),
1004
+ };
1005
+ const pipelineInstance = new PairPipeline({
1006
+ stages,
1007
+ maxIterations: 3,
1008
+ stageConfigs: Object.keys(stageConfigs).length > 0 ? stageConfigs : undefined,
1009
+ });
1010
+ pipelineInstance.on('stage:start', ({ stage }) => {
1011
+ entry.currentStage = stage;
1012
+ });
1013
+ const result = await pipelineInstance.run(workItem, resolvedPath);
1014
+ entry.status = 'completed';
1015
+ entry.result = {
1016
+ success: result.success,
1017
+ summary: result.executorResult?.summary,
1018
+ finalStatus: result.finalStatus,
1019
+ };
1020
+ }
1021
+ catch (err) {
1022
+ entry.status = 'failed';
1023
+ entry.error = err instanceof Error ? err.message : String(err);
1024
+ }
1025
+ finally {
1026
+ cleanupExecWorkItem(workItemId);
1027
+ }
1028
+ })();
1029
+ }
1030
+ catch {
1031
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1032
+ res.end(JSON.stringify({ error: 'Invalid JSON' }));
1033
+ }
1034
+ // ---- Exec: workItem status ----
1035
+ }
1036
+ else if (url.startsWith('/api/exec/') && req.method === 'GET') {
1037
+ const workItemId = url.replace('/api/exec/', '');
1038
+ const entry = execWorkItems.get(workItemId);
1039
+ if (!entry) {
1040
+ res.writeHead(404, { 'Content-Type': 'application/json' });
1041
+ res.end(JSON.stringify({ error: 'WorkItem not found' }));
1042
+ }
1043
+ else {
1044
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1045
+ res.end(JSON.stringify({
1046
+ workItemId: entry.workItemId,
1047
+ status: entry.status,
1048
+ currentStage: entry.currentStage,
1049
+ result: entry.result,
1050
+ error: entry.error,
1051
+ }));
1052
+ }
1053
+ }
1054
+ else {
1055
+ res.writeHead(404);
1056
+ res.end('Not Found');
1057
+ }
1058
+ });
1059
+ server.on('upgrade', (req, socket, head) => {
1060
+ if (!handleTerminalWebSocketUpgrade(req, socket, head)) {
1061
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
1062
+ socket.destroy();
1063
+ }
1064
+ });
1065
+ server.on('error', (err) => {
1066
+ if (err.code === 'EADDRINUSE') {
1067
+ console.warn(`Port ${port} is already in use, skipping web server...`);
1068
+ server = null;
1069
+ resolve();
1070
+ }
1071
+ else {
1072
+ reject(err);
1073
+ }
1074
+ });
1075
+ server.listen(port, '0.0.0.0', () => {
1076
+ const tailscaleIP = '100.95.200.28'; // Current Tailscale IP
1077
+ console.log(`Web interface running at:`);
1078
+ console.log(` - http://127.0.0.1:${port} (localhost)`);
1079
+ console.log(` - http://${tailscaleIP}:${port} (Tailscale)`);
1080
+ startGitStatusPoller(() => Array.from(pinnedProjects));
1081
+ startHealthChecker(30000);
1082
+ resolve();
1083
+ });
1084
+ });
1085
+ }
1086
+ /**
1087
+ * Stop the web server
1088
+ */
1089
+ export async function stopWebServer() {
1090
+ if (server) {
1091
+ server.close();
1092
+ shutdownTerminalBridge();
1093
+ server = null;
1094
+ }
1095
+ await stopAzdoSync();
1096
+ }
1097
+ //# sourceMappingURL=web.js.map