issue-flow 0.17.0 → 0.19.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 (239) hide show
  1. package/README.md +34 -100
  2. package/dist/agent-EWKA7357.js +32 -0
  3. package/dist/{analyze-6M3KEOX7.js → analyze-Z7ODC27F.js} +42 -41
  4. package/dist/analyze-Z7ODC27F.js.map +1 -0
  5. package/dist/{apply-ADB3ZK7W.js → apply-VNSWQS6O.js} +22 -24
  6. package/dist/apply-VNSWQS6O.js.map +1 -0
  7. package/dist/artifacts-AMJQJFFV.js +82 -0
  8. package/dist/artifacts-AMJQJFFV.js.map +1 -0
  9. package/dist/{bench-FM7I7EIV.js → bench-ZJEIACUK.js} +42 -42
  10. package/dist/{chunk-RYOUZ6EK.js → chunk-23VHVVKW.js} +3 -3
  11. package/dist/chunk-2EYLOS5I.js +99 -0
  12. package/dist/chunk-2EYLOS5I.js.map +1 -0
  13. package/dist/{chunk-ANI2NTR4.js → chunk-2EZW4X57.js} +28 -25
  14. package/dist/chunk-2EZW4X57.js.map +1 -0
  15. package/dist/{chunk-E6KXCV3T.js → chunk-2Q6PG3MT.js} +15 -15
  16. package/dist/chunk-2Q6PG3MT.js.map +1 -0
  17. package/dist/{chunk-C5TL7T4V.js → chunk-2YM2VH6R.js} +2 -2
  18. package/dist/{chunk-LPWPFIIB.js → chunk-2YYP7AE6.js} +14 -16
  19. package/dist/{chunk-LPWPFIIB.js.map → chunk-2YYP7AE6.js.map} +1 -1
  20. package/dist/chunk-3JD25HHJ.js +278 -0
  21. package/dist/chunk-3JD25HHJ.js.map +1 -0
  22. package/dist/{chunk-ISZ54SVE.js → chunk-46S7APLQ.js} +41 -17
  23. package/dist/chunk-46S7APLQ.js.map +1 -0
  24. package/dist/chunk-6AOZCWN2.js +21 -0
  25. package/dist/chunk-6AOZCWN2.js.map +1 -0
  26. package/dist/{chunk-QPGEFLMY.js → chunk-7CMVL4X6.js} +7 -7
  27. package/dist/chunk-7MJGYPHF.js +1571 -0
  28. package/dist/chunk-7MJGYPHF.js.map +1 -0
  29. package/dist/chunk-7ZZUSMBJ.js +35 -0
  30. package/dist/chunk-7ZZUSMBJ.js.map +1 -0
  31. package/dist/{chunk-DUS5ZNKL.js → chunk-DIMDAYZS.js} +7 -6
  32. package/dist/{chunk-DGRTJD5R.js → chunk-DXH4TQRB.js} +7 -7
  33. package/dist/{chunk-CRGIQFUQ.js → chunk-E2H3JV2E.js} +3 -1
  34. package/dist/chunk-E2H3JV2E.js.map +1 -0
  35. package/dist/{chunk-YUNBOUQH.js → chunk-ERAG5IQB.js} +8 -8
  36. package/dist/{chunk-D6ZBGJ3L.js → chunk-F2NFJ27Z.js} +130 -36
  37. package/dist/chunk-F2NFJ27Z.js.map +1 -0
  38. package/dist/{chunk-SOM4QA6C.js → chunk-GD7JZMSJ.js} +93 -18
  39. package/dist/chunk-GD7JZMSJ.js.map +1 -0
  40. package/dist/{chunk-YII4U7ZD.js → chunk-HEHHQU4G.js} +70 -81
  41. package/dist/chunk-HEHHQU4G.js.map +1 -0
  42. package/dist/{chunk-KZ5EY5XX.js → chunk-HHXWAWU6.js} +159 -114
  43. package/dist/chunk-HHXWAWU6.js.map +1 -0
  44. package/dist/{chunk-SGTTR4XW.js → chunk-HWWB22HC.js} +152 -100
  45. package/dist/chunk-HWWB22HC.js.map +1 -0
  46. package/dist/chunk-IMB6FA2F.js +673 -0
  47. package/dist/chunk-IMB6FA2F.js.map +1 -0
  48. package/dist/{chunk-6YQT3SSM.js → chunk-IQYXXFRC.js} +22 -6
  49. package/dist/chunk-IQYXXFRC.js.map +1 -0
  50. package/dist/chunk-J6VJ5M2U.js +1201 -0
  51. package/dist/chunk-J6VJ5M2U.js.map +1 -0
  52. package/dist/{chunk-B3TSS7BT.js → chunk-KDQRNVBZ.js} +3 -3
  53. package/dist/{chunk-A4H7P7T7.js → chunk-LRWIP7QS.js} +41 -26
  54. package/dist/chunk-LRWIP7QS.js.map +1 -0
  55. package/dist/{chunk-DV7HUC4Z.js → chunk-LYC57OXF.js} +20 -117
  56. package/dist/chunk-LYC57OXF.js.map +1 -0
  57. package/dist/{chunk-HAKDLR6N.js → chunk-MAJ6QDRL.js} +65 -78
  58. package/dist/chunk-MAJ6QDRL.js.map +1 -0
  59. package/dist/{chunk-HJBF4WD2.js → chunk-MHA7Q3KU.js} +288 -108
  60. package/dist/chunk-MHA7Q3KU.js.map +1 -0
  61. package/dist/{chunk-UK4E2FUA.js → chunk-MQTVMB7W.js} +103 -76
  62. package/dist/chunk-MQTVMB7W.js.map +1 -0
  63. package/dist/chunk-NP47HRX2.js +136 -0
  64. package/dist/chunk-NP47HRX2.js.map +1 -0
  65. package/dist/{chunk-MI5TREN3.js → chunk-NYDAHE3Y.js} +52 -46
  66. package/dist/chunk-NYDAHE3Y.js.map +1 -0
  67. package/dist/{chunk-UWDCIFTP.js → chunk-P7ZYOC23.js} +41 -57
  68. package/dist/chunk-P7ZYOC23.js.map +1 -0
  69. package/dist/{chunk-3R27MJ5R.js → chunk-PFWHHYEO.js} +3 -3
  70. package/dist/{chunk-2SUFAE6X.js → chunk-PHPAK2L6.js} +44 -33
  71. package/dist/{chunk-2SUFAE6X.js.map → chunk-PHPAK2L6.js.map} +1 -1
  72. package/dist/{chunk-PPZ3K4YM.js → chunk-PRGPSNGA.js} +2 -2
  73. package/dist/{chunk-32JSYXEK.js → chunk-RGFMQFX7.js} +9 -23
  74. package/dist/{chunk-32JSYXEK.js.map → chunk-RGFMQFX7.js.map} +1 -1
  75. package/dist/{chunk-YVPZUN6X.js → chunk-TJVX42KY.js} +335 -38
  76. package/dist/chunk-TJVX42KY.js.map +1 -0
  77. package/dist/{chunk-RCZJQUTC.js → chunk-UDRAD7MF.js} +60 -27
  78. package/dist/chunk-UDRAD7MF.js.map +1 -0
  79. package/dist/{chunk-HY5HLFDU.js → chunk-UHDXXLYH.js} +148 -135
  80. package/dist/chunk-UHDXXLYH.js.map +1 -0
  81. package/dist/{chunk-RNFUVKC3.js → chunk-UPAAZU4W.js} +30 -13
  82. package/dist/chunk-UPAAZU4W.js.map +1 -0
  83. package/dist/{chunk-L6HSKTQE.js → chunk-W7LZLJAF.js} +4 -4
  84. package/dist/{chunk-HLAPOUEM.js → chunk-WB2FVIUS.js} +44 -12
  85. package/dist/chunk-WB2FVIUS.js.map +1 -0
  86. package/dist/{chunk-VE3IYNDN.js → chunk-WPHC6JBR.js} +9 -9
  87. package/dist/chunk-WPHC6JBR.js.map +1 -0
  88. package/dist/{chunk-KZXQWC7U.js → chunk-XHCP6ZNO.js} +201 -64
  89. package/dist/chunk-XHCP6ZNO.js.map +1 -0
  90. package/dist/{chunk-7F7LGMVO.js → chunk-XKR3VD2Z.js} +12 -12
  91. package/dist/{chunk-7F7LGMVO.js.map → chunk-XKR3VD2Z.js.map} +1 -1
  92. package/dist/{chunk-ICM463EO.js → chunk-XNK5UN2D.js} +5 -649
  93. package/dist/chunk-XNK5UN2D.js.map +1 -0
  94. package/dist/{chunk-Z5O2XBWA.js → chunk-XQLNZW7I.js} +40 -30
  95. package/dist/chunk-XQLNZW7I.js.map +1 -0
  96. package/dist/{chunk-QMIQR7EE.js → chunk-ZAYATXJD.js} +32 -316
  97. package/dist/chunk-ZAYATXJD.js.map +1 -0
  98. package/dist/cli.js +128 -60
  99. package/dist/cli.js.map +1 -1
  100. package/dist/compat-YPDZB34U.js +24 -0
  101. package/dist/{config-KGZUPAUP.js → config-56B4OHZE.js} +20 -17
  102. package/dist/{contract-JH2WBIO6.js → contract-HWLFFL3F.js} +2 -2
  103. package/dist/{conventions-RYN7OXJN.js → conventions-DW2CM5I3.js} +20 -16
  104. package/dist/{conventions-RYN7OXJN.js.map → conventions-DW2CM5I3.js.map} +1 -1
  105. package/dist/db-5NU77PJA.js +264 -0
  106. package/dist/db-5NU77PJA.js.map +1 -0
  107. package/dist/diagnostics-UCTS2VUF.js +25 -0
  108. package/dist/execute-R7AVOLNG.js +38 -0
  109. package/dist/{generate-CX6KNHZS.js → generate-O7GUXLN4.js} +46 -43
  110. package/dist/generate-O7GUXLN4.js.map +1 -0
  111. package/dist/{git-A2IP32PQ.js → git-LCG45YXD.js} +2 -3
  112. package/dist/history-6GSURU3H.js +101 -0
  113. package/dist/history-6GSURU3H.js.map +1 -0
  114. package/dist/init-DD6DBRC5.js +28 -0
  115. package/dist/{operations-CAKIT2BO.js → operations-HU5ZFEBA.js} +78 -52
  116. package/dist/operations-HU5ZFEBA.js.map +1 -0
  117. package/dist/{permissions-G4XCAESF.js → permissions-NNNSRC4P.js} +5 -6
  118. package/dist/plan-XR6B7EM3.js +38 -0
  119. package/dist/{policy-WX6O2JID.js → policy-RFJOJQL6.js} +17 -15
  120. package/dist/{policy-WX6O2JID.js.map → policy-RFJOJQL6.js.map} +1 -1
  121. package/dist/pr-OSCNJCOO.js +42 -0
  122. package/dist/pr-review-IKIYHFBO.js +33 -0
  123. package/dist/prd-HFDZJJQR.js +37 -0
  124. package/dist/{ps-76L6ZAZL.js → ps-AKRHGPSR.js} +26 -15
  125. package/dist/ps-AKRHGPSR.js.map +1 -0
  126. package/dist/{recorder-N7U66HCO.js → recorder-T7426CKV.js} +11 -7
  127. package/dist/registry-DWRKD65A.js +17 -0
  128. package/dist/repository-TABBRW2C.js +80 -0
  129. package/dist/resume-GDXKK5XP.js +278 -0
  130. package/dist/resume-GDXKK5XP.js.map +1 -0
  131. package/dist/review-JJHMQA2O.js +38 -0
  132. package/dist/routing-FPNUVE7T.js +35 -0
  133. package/dist/run-RLCURLLL.js +61 -0
  134. package/dist/{session-publisher-KAJQRHNZ.js → session-publisher-Q5TNEZPX.js} +3 -3
  135. package/dist/session-publisher-Q5TNEZPX.js.map +1 -0
  136. package/dist/{state-manager-UPHO2EA2.js → state-manager-IGNTD4AB.js} +9 -4
  137. package/dist/state-manager-IGNTD4AB.js.map +1 -0
  138. package/dist/{usage-U7QNGCPK.js → usage-XW27AL5K.js} +69 -63
  139. package/dist/usage-XW27AL5K.js.map +1 -0
  140. package/dist/web-NCDBVZ5Y.js +205 -0
  141. package/dist/web-NCDBVZ5Y.js.map +1 -0
  142. package/package.json +20 -7
  143. package/prompts/analyze.md +16 -19
  144. package/prompts/execute.md +73 -144
  145. package/prompts/generate.md +23 -10
  146. package/prompts/plan.md +32 -60
  147. package/prompts/pr-review.md +58 -40
  148. package/prompts/pr.md +50 -16
  149. package/prompts/prd.md +26 -19
  150. package/prompts/review.md +41 -18
  151. package/web/AGENTS.md +115 -7
  152. package/web/public/app.css +393 -245
  153. package/web/public/app.js +212 -143
  154. package/web/public/index.html +106 -78
  155. package/dist/agent-TB6W7PT7.js +0 -30
  156. package/dist/analyze-6M3KEOX7.js.map +0 -1
  157. package/dist/apply-ADB3ZK7W.js.map +0 -1
  158. package/dist/chunk-6YQT3SSM.js.map +0 -1
  159. package/dist/chunk-A4H7P7T7.js.map +0 -1
  160. package/dist/chunk-ANI2NTR4.js.map +0 -1
  161. package/dist/chunk-CRGIQFUQ.js.map +0 -1
  162. package/dist/chunk-D6ZBGJ3L.js.map +0 -1
  163. package/dist/chunk-DV7HUC4Z.js.map +0 -1
  164. package/dist/chunk-E6KXCV3T.js.map +0 -1
  165. package/dist/chunk-HAKDLR6N.js.map +0 -1
  166. package/dist/chunk-HJBF4WD2.js.map +0 -1
  167. package/dist/chunk-HLAPOUEM.js.map +0 -1
  168. package/dist/chunk-HY5HLFDU.js.map +0 -1
  169. package/dist/chunk-ICM463EO.js.map +0 -1
  170. package/dist/chunk-ISZ54SVE.js.map +0 -1
  171. package/dist/chunk-KZ5EY5XX.js.map +0 -1
  172. package/dist/chunk-KZXQWC7U.js.map +0 -1
  173. package/dist/chunk-MI5TREN3.js.map +0 -1
  174. package/dist/chunk-QMIQR7EE.js.map +0 -1
  175. package/dist/chunk-RCZJQUTC.js.map +0 -1
  176. package/dist/chunk-RNFUVKC3.js.map +0 -1
  177. package/dist/chunk-SGTTR4XW.js.map +0 -1
  178. package/dist/chunk-SOM4QA6C.js.map +0 -1
  179. package/dist/chunk-UK4E2FUA.js.map +0 -1
  180. package/dist/chunk-UWDCIFTP.js.map +0 -1
  181. package/dist/chunk-VE3IYNDN.js.map +0 -1
  182. package/dist/chunk-WCCZJL7W.js +0 -141
  183. package/dist/chunk-WCCZJL7W.js.map +0 -1
  184. package/dist/chunk-XEYH6ZZI.js +0 -289
  185. package/dist/chunk-XEYH6ZZI.js.map +0 -1
  186. package/dist/chunk-XZUEFTUP.js +0 -243
  187. package/dist/chunk-XZUEFTUP.js.map +0 -1
  188. package/dist/chunk-YII4U7ZD.js.map +0 -1
  189. package/dist/chunk-YVPZUN6X.js.map +0 -1
  190. package/dist/chunk-Z5O2XBWA.js.map +0 -1
  191. package/dist/diagnostics-HTCWF7F4.js +0 -23
  192. package/dist/execute-QL3ZQIAP.js +0 -37
  193. package/dist/generate-CX6KNHZS.js.map +0 -1
  194. package/dist/init-PDVQDRS6.js +0 -24
  195. package/dist/operations-CAKIT2BO.js.map +0 -1
  196. package/dist/plan-PRORGS3L.js +0 -34
  197. package/dist/pr-27RMQI5F.js +0 -39
  198. package/dist/pr-review-VNESMAMT.js +0 -32
  199. package/dist/prd-ZNC4T5BS.js +0 -33
  200. package/dist/ps-76L6ZAZL.js.map +0 -1
  201. package/dist/registry-XLHHYLTR.js +0 -15
  202. package/dist/resume-VLWIE264.js +0 -244
  203. package/dist/resume-VLWIE264.js.map +0 -1
  204. package/dist/review-7CREJDKB.js +0 -35
  205. package/dist/routing-VCDYHSXA.js +0 -33
  206. package/dist/run-3MPORLF5.js +0 -56
  207. package/dist/usage-U7QNGCPK.js.map +0 -1
  208. package/dist/web-HUNDHCJ4.js +0 -172
  209. package/dist/web-HUNDHCJ4.js.map +0 -1
  210. /package/dist/{agent-TB6W7PT7.js.map → agent-EWKA7357.js.map} +0 -0
  211. /package/dist/{bench-FM7I7EIV.js.map → bench-ZJEIACUK.js.map} +0 -0
  212. /package/dist/{chunk-RYOUZ6EK.js.map → chunk-23VHVVKW.js.map} +0 -0
  213. /package/dist/{chunk-C5TL7T4V.js.map → chunk-2YM2VH6R.js.map} +0 -0
  214. /package/dist/{chunk-QPGEFLMY.js.map → chunk-7CMVL4X6.js.map} +0 -0
  215. /package/dist/{chunk-DUS5ZNKL.js.map → chunk-DIMDAYZS.js.map} +0 -0
  216. /package/dist/{chunk-DGRTJD5R.js.map → chunk-DXH4TQRB.js.map} +0 -0
  217. /package/dist/{chunk-YUNBOUQH.js.map → chunk-ERAG5IQB.js.map} +0 -0
  218. /package/dist/{chunk-B3TSS7BT.js.map → chunk-KDQRNVBZ.js.map} +0 -0
  219. /package/dist/{chunk-3R27MJ5R.js.map → chunk-PFWHHYEO.js.map} +0 -0
  220. /package/dist/{chunk-PPZ3K4YM.js.map → chunk-PRGPSNGA.js.map} +0 -0
  221. /package/dist/{chunk-L6HSKTQE.js.map → chunk-W7LZLJAF.js.map} +0 -0
  222. /package/dist/{config-KGZUPAUP.js.map → compat-YPDZB34U.js.map} +0 -0
  223. /package/dist/{contract-JH2WBIO6.js.map → config-56B4OHZE.js.map} +0 -0
  224. /package/dist/{diagnostics-HTCWF7F4.js.map → contract-HWLFFL3F.js.map} +0 -0
  225. /package/dist/{execute-QL3ZQIAP.js.map → diagnostics-UCTS2VUF.js.map} +0 -0
  226. /package/dist/{git-A2IP32PQ.js.map → execute-R7AVOLNG.js.map} +0 -0
  227. /package/dist/{init-PDVQDRS6.js.map → git-LCG45YXD.js.map} +0 -0
  228. /package/dist/{permissions-G4XCAESF.js.map → init-DD6DBRC5.js.map} +0 -0
  229. /package/dist/{plan-PRORGS3L.js.map → permissions-NNNSRC4P.js.map} +0 -0
  230. /package/dist/{pr-27RMQI5F.js.map → plan-XR6B7EM3.js.map} +0 -0
  231. /package/dist/{pr-review-VNESMAMT.js.map → pr-OSCNJCOO.js.map} +0 -0
  232. /package/dist/{prd-ZNC4T5BS.js.map → pr-review-IKIYHFBO.js.map} +0 -0
  233. /package/dist/{recorder-N7U66HCO.js.map → prd-HFDZJJQR.js.map} +0 -0
  234. /package/dist/{registry-XLHHYLTR.js.map → recorder-T7426CKV.js.map} +0 -0
  235. /package/dist/{review-7CREJDKB.js.map → registry-DWRKD65A.js.map} +0 -0
  236. /package/dist/{routing-VCDYHSXA.js.map → repository-TABBRW2C.js.map} +0 -0
  237. /package/dist/{run-3MPORLF5.js.map → review-JJHMQA2O.js.map} +0 -0
  238. /package/dist/{session-publisher-KAJQRHNZ.js.map → routing-FPNUVE7T.js.map} +0 -0
  239. /package/dist/{state-manager-UPHO2EA2.js.map → run-RLCURLLL.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/web/lock.ts","../src/web/server.ts"],"sourcesContent":["import { type ChildProcess, spawn as nodeSpawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { isoNow } from '../core/state-manager.js';\n// The liveness probe is shared with `storage/lock.ts`, which generalised this\n// module's guard into run ownership: one definition of \"is that pid alive\",\n// with one place to get the EPERM case right.\nimport { isProcessAlive } from '../storage/lock.js';\nimport { type GetGlobalRootOptions, getGlobalRoot } from '../storage/paths.js';\nimport { type WebLock, webLockSchema } from '../storage/schemas.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { getPackageVersion } from '../version.js';\nimport { startWebServer, type WebServerHandle, type WebServerOptions } from './server.js';\n\n/**\n * Single-instance guard for the web monitoring server (`~/.issue-flow/web.lock`).\n *\n * A machine runs at most one monitoring server. Two entry points build on top\n * of the same lock:\n *\n * - `ensureSingleWebServer` binds (or reuses) a server **in the calling\n * process**. It is the low-level primitive, used by the `web serve` command\n * (the standalone process that ends up owning the lock) and by this\n * module's own tests. Nothing else should call it directly.\n * - `ensureWebMonitor` (US-002) is what `run`/`execute` call: it reuses an\n * active instance exactly like `ensureSingleWebServer`, but when none\n * exists it spawns `web serve` as a **detached** child instead of binding\n * locally, so the server outlives the pipeline process. Falls back to the\n * legacy in-process `startWebServer` (US-006) when the global storage tree\n * itself is unreachable.\n */\n\n/** Lock file name, sibling of `config.json` at the global storage root. */\nexport const WEB_LOCK_FILENAME = 'web.lock';\nexport const WEB_RESTART_LOCK_FILENAME = 'web.restart.lock';\n\n/** How long a health probe against a candidate instance waits before giving up. */\nconst HEALTH_PROBE_TIMEOUT_MS = 1000;\n\ninterface WebHealth {\n ok: true;\n pid?: number;\n instanceId?: string;\n startedAt?: string;\n uptime?: number;\n version?: string;\n}\n\ninterface ManagedWebInstance {\n lock: WebLock;\n health: WebHealth;\n source: 'lock' | 'port';\n}\n\nexport interface PortOwner {\n pid: number;\n command: string;\n}\n\n/**\n * A process that loses the atomic lock creation retries this many times,\n * waiting for the winner to finish starting up, before giving up.\n */\nconst LOCK_CLAIM_RETRIES = 5;\nconst LOCK_CLAIM_RETRY_DELAY_MS = 100;\n\nexport function getWebLockFile(options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), WEB_LOCK_FILENAME);\n}\n\nfunction getWebRestartLockFile(options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), WEB_RESTART_LOCK_FILENAME);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** 0.0.0.0 is not a connectable address; probe loopback instead, same mapping\n * `startWebServer` uses to build its own display URL. */\nfunction probeUrl(host: string, port: number): string {\n const probeHost = host === '0.0.0.0' ? '127.0.0.1' : host;\n return `http://${probeHost}:${port}/api/health`;\n}\n\nasync function probeHealth(host: string, port: number): Promise<WebHealth | null> {\n try {\n const res = await fetch(probeUrl(host, port), {\n signal: AbortSignal.timeout(HEALTH_PROBE_TIMEOUT_MS),\n });\n if (!res.ok) return null;\n const body = (await res.json()) as Record<string, unknown>;\n if (body.ok !== true) return null;\n return {\n ok: true,\n ...(typeof body.pid === 'number' && Number.isInteger(body.pid) && body.pid > 0\n ? { pid: body.pid }\n : {}),\n ...(typeof body.instanceId === 'string' && body.instanceId !== ''\n ? { instanceId: body.instanceId }\n : {}),\n ...(typeof body.startedAt === 'string' && body.startedAt !== ''\n ? { startedAt: body.startedAt }\n : {}),\n ...(typeof body.uptime === 'number' && Number.isFinite(body.uptime)\n ? { uptime: body.uptime }\n : {}),\n ...(typeof body.version === 'string' && body.version !== '' ? { version: body.version } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction sameLock(a: WebLock | null, b: WebLock): boolean {\n return (\n a !== null &&\n a.pid === b.pid &&\n a.port === b.port &&\n a.host === b.host &&\n a.startedAt === b.startedAt &&\n a.instanceId === b.instanceId\n );\n}\n\nasync function removeWebLockIfOwned(lockFile: string, expected: WebLock): Promise<void> {\n if (sameLock(await readWebLock(lockFile), expected)) {\n await removeWebLock(lockFile);\n }\n}\n\nasync function inspectProcessCommand(pid: number): Promise<string | null> {\n const invocation =\n process.platform === 'win32'\n ? {\n command: 'powershell.exe',\n args: [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\").CommandLine`,\n ],\n }\n : { command: 'ps', args: ['-p', String(pid), '-o', 'command='] };\n try {\n const result = await execa(invocation.command, invocation.args, { reject: false });\n const command = result.stdout.trim();\n return result.exitCode === 0 && command !== '' ? command : null;\n } catch {\n return null;\n }\n}\n\nasync function findListeningProcess(port: number): Promise<PortOwner | null> {\n try {\n let pids: number[];\n if (process.platform === 'win32') {\n const result = await execa(\n 'powershell.exe',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(Get-NetTCPConnection -State Listen -LocalPort ${port} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique) -join [Environment]::NewLine`,\n ],\n { reject: false },\n );\n pids = result.stdout\n .split(/\\s+/)\n .map(Number)\n .filter((pid) => Number.isInteger(pid) && pid > 0);\n } else {\n const result = await execa('lsof', ['-nP', '-t', `-iTCP:${port}`, '-sTCP:LISTEN'], {\n reject: false,\n });\n pids = result.stdout\n .split(/\\s+/)\n .map(Number)\n .filter((pid) => Number.isInteger(pid) && pid > 0);\n }\n const unique = [...new Set(pids)];\n if (unique.length !== 1) return null;\n const pid = unique[0] as number;\n const command = await inspectProcessCommand(pid);\n return command === null ? null : { pid, command };\n } catch {\n return null;\n }\n}\n\nfunction isIssueFlowWebServe(owner: PortOwner, port: number): boolean {\n const escapedPort = String(port).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return (\n /(?:^|\\s)web\\s+serve(?:\\s|$)/.test(owner.command) &&\n new RegExp(`(?:^|\\\\s)--port(?:=|\\\\s+)${escapedPort}(?:\\\\s|$)`).test(owner.command) &&\n /issue-flow|(?:^|[/\\\\])dist[/\\\\]cli\\.js/.test(owner.command)\n );\n}\n\n/** Malformed content (partial write, older/newer incompatible shape) reads as\n * \"no lock\" — the same degrade-to-absent behavior as `loadGlobalConfig()`. */\nexport async function readWebLock(lockFile: string): Promise<WebLock | null> {\n let raw: string;\n try {\n raw = await readFile(lockFile, 'utf-8');\n } catch {\n return null;\n }\n try {\n const result = webLockSchema.safeParse(JSON.parse(raw));\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nexport async function removeWebLock(lockFile: string): Promise<void> {\n try {\n await rm(lockFile, { force: true });\n } catch {\n /* best-effort cleanup */\n }\n}\n\n/**\n * A lock is only trustworthy when both signals agree: the owning process is\n * alive AND it answers `/api/health`. A dead pid is the obvious stale case;\n * a live pid that does not answer covers a process that still exists but\n * crashed before binding, was killed -9 leaving a zombie-adjacent state, or\n * whose port was reclaimed by something else entirely. Either way the lock is\n * removed so the next attempt starts clean instead of looping on it forever.\n */\nexport async function detectActiveInstance(lockFile: string): Promise<WebLock | null> {\n const lock = await readWebLock(lockFile);\n if (lock === null) return null;\n\n const health = isProcessAlive(lock.pid) ? await probeHealth(lock.host, lock.port) : null;\n if (health !== null && (lock.instanceId === undefined || health.instanceId === lock.instanceId)) {\n return lock;\n }\n\n await removeWebLockIfOwned(lockFile, lock);\n return null;\n}\n\n/**\n * Atomic exclusive create: `wx` fails with EEXIST when another process won\n * the race, which is what lets two concurrent invocations agree on a single\n * winner without a separate lock-of-the-lock.\n */\nasync function claimWebLock(lockFile: string, lock: WebLock): Promise<boolean> {\n try {\n await writeFile(lockFile, JSON.stringify(lock, null, 2), { encoding: 'utf-8', flag: 'wx' });\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') return false;\n throw err;\n }\n}\n\ninterface RestartLock {\n pid: number;\n operationId: string;\n startedAt: string;\n}\n\nasync function readRestartLock(file: string): Promise<RestartLock | null> {\n try {\n const value = JSON.parse(await readFile(file, 'utf-8')) as Record<string, unknown>;\n return typeof value.pid === 'number' &&\n Number.isInteger(value.pid) &&\n value.pid > 0 &&\n typeof value.operationId === 'string' &&\n value.operationId !== '' &&\n typeof value.startedAt === 'string' &&\n value.startedAt !== ''\n ? { pid: value.pid, operationId: value.operationId, startedAt: value.startedAt }\n : null;\n } catch {\n return null;\n }\n}\n\nasync function removeRestartLockIfOwned(file: string, expected: RestartLock): Promise<void> {\n const current = await readRestartLock(file);\n if (current?.operationId === expected.operationId) {\n await rm(file, { force: true }).catch(() => {});\n }\n}\n\nasync function waitForRestart(file: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const lock = await readRestartLock(file);\n if (lock === null) {\n // Missing or malformed means there is no owner we can safely wait for.\n // Do not unlink here: a restart may have claimed the path between the\n // read and this branch. The explicit acquirer cleans malformed files.\n return true;\n }\n if (!isProcessAlive(lock.pid)) {\n await removeRestartLockIfOwned(file, lock);\n return true;\n }\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n }\n return false;\n}\n\nasync function acquireRestartLock(\n file: string,\n timeoutMs: number,\n): Promise<{ lock: RestartLock; waited: boolean; release: () => Promise<void> } | null> {\n const deadline = Date.now() + timeoutMs;\n let waited = false;\n while (Date.now() < deadline) {\n const lock: RestartLock = {\n pid: process.pid,\n operationId: randomUUID(),\n startedAt: isoNow(),\n };\n try {\n await writeFile(file, JSON.stringify(lock, null, 2), { encoding: 'utf-8', flag: 'wx' });\n return {\n lock,\n waited,\n release: () => removeRestartLockIfOwned(file, lock),\n };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') return null;\n }\n waited = true;\n const existing = await readRestartLock(file);\n if (existing === null || !isProcessAlive(existing.pid)) {\n if (existing === null) await rm(file, { force: true }).catch(() => {});\n else await removeRestartLockIfOwned(file, existing);\n continue;\n }\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n }\n return null;\n}\n\n/** Handle returned when reusing an already-running instance instead of\n * binding a new one — there is no local `Server` to hold, and closing it\n * must not tear down a server another process owns. */\nfunction reusedHandle(lock: WebLock): WebServerHandle {\n const displayHost = lock.host === '0.0.0.0' ? 'localhost' : lock.host;\n return {\n host: lock.host,\n port: lock.port,\n url: `http://${displayHost}:${lock.port}`,\n instanceId: lock.instanceId ?? `legacy-${lock.pid}-${lock.startedAt}`,\n close: async () => {},\n };\n}\n\n/**\n * Start the web monitoring server, first checking whether a live instance\n * already owns `web.lock` and reusing it instead of binding a second one.\n *\n * The lock is claimed only *after* a successful bind, never before: claiming\n * it with the port the server is about to (but hasn't yet) listen on would\n * make that lock briefly answer no health probe at all, and a concurrent\n * invocation would misread that gap as \"stale\" and delete a perfectly good\n * lock out from under its owner. Binding first also means two invocations\n * racing for the same fixed port never both reach the claim step — the OS\n * itself lets only one `listen()` succeed. The `wx` claim then exists for the\n * remaining race: two invocations that *both* manage to bind (only possible\n * with an ephemeral port) still agree on exactly one winner. The loser closes\n * the server it just opened and defers to whichever lock exists.\n *\n * Returns null exactly when `startWebServer` would: nothing here may affect\n * the pipeline, so a lock we cannot resolve after every retry degrades to \"no\n * server\" rather than throwing.\n */\nexport async function ensureSingleWebServer(\n options: WebServerOptions,\n lockOptions: GetGlobalRootOptions = {},\n): Promise<WebServerHandle | null> {\n const warn = options.warn ?? printWarning;\n const info = options.info ?? printInfo;\n const lockFile = getWebLockFile(lockOptions);\n\n for (let attempt = 0; attempt <= LOCK_CLAIM_RETRIES; attempt++) {\n const active = await detectActiveInstance(lockFile);\n if (active !== null) {\n info(`Reusing existing web monitor at ${reusedHandle(active).url}`);\n return reusedHandle(active);\n }\n\n const handle = await startWebServer(options);\n if (handle === null) {\n // Could not bind. Most likely another instance's listen() won a race\n // on the same fixed port between our detection above and this call —\n // its lock should be visible shortly. Anything else (port taken by an\n // unrelated process, invalid host, ...) fails the same way on the next\n // retry too, and the loop's own limit stops it from spinning forever.\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n continue;\n }\n\n let claimed: boolean;\n try {\n claimed = await claimWebLock(lockFile, {\n pid: process.pid,\n port: handle.port,\n host: handle.host,\n startedAt: isoNow(),\n instanceId: handle.instanceId,\n });\n } catch (err) {\n await handle.close();\n throw err;\n }\n\n if (!claimed) {\n // We bound successfully (an ephemeral port let both of us through),\n // but another invocation's lock got written first. We are not the\n // canonical instance: close what we just opened and defer to it.\n await handle.close();\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n continue;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n await removeWebLock(lockFile);\n };\n return handle;\n }\n\n warn('Could not determine the web monitor lock ownership. Continuing without the web server.');\n return null;\n}\n\n/** Bounded wait for a freshly spawned `web serve` child to claim the lock. */\nconst DETACHED_CLAIM_TIMEOUT_MS = 5000;\nconst DETACHED_CLAIM_POLL_MS = 150;\n\nexport interface EnsureWebMonitorOptions extends GetGlobalRootOptions {\n /** Injectable for tests. Defaults to `node:child_process`'s `spawn`. */\n spawn?: (\n command: string,\n args: string[],\n options: { detached: true; stdio: 'ignore' },\n ) => ChildProcess;\n /** Node executable to spawn. Defaults to `process.execPath`. */\n execPath?: string;\n /** CLI entry script re-invoked as `<entry> web serve ...`. Defaults to `process.argv[1]`. */\n entryScript?: string;\n /** How long to wait for the spawned instance's lock to become healthy. */\n claimTimeoutMs?: number;\n claimPollIntervalMs?: number;\n /** Explicitly replace any managed monitor before spawning this entry point. */\n restart?: boolean;\n /** Injectable process operations used by restart and orphan recovery tests. */\n kill?: typeof process.kill;\n isAlive?: (pid: number) => boolean;\n findPortOwner?: (port: number) => Promise<PortOwner | null>;\n inspectProcess?: (pid: number) => Promise<string | null>;\n shutdownTimeoutMs?: number;\n restartLockTimeoutMs?: number;\n}\n\ninterface DiscoveryResult {\n instance: ManagedWebInstance | null;\n /** A monitor-like health endpoint answered, but ownership could not be proven. */\n unownedMonitor: boolean;\n}\n\nfunction inferredStartedAt(health: WebHealth): string {\n if (health.startedAt !== undefined) return health.startedAt;\n if (health.uptime !== undefined) {\n return new Date(Date.now() - Math.max(0, health.uptime) * 1000).toISOString();\n }\n return isoNow();\n}\n\nasync function discoverManagedInstance(\n lockFile: string,\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions,\n): Promise<DiscoveryResult> {\n const existingLock = await readWebLock(lockFile);\n if (existingLock !== null) {\n const health = isProcessAlive(existingLock.pid)\n ? await probeHealth(existingLock.host, existingLock.port)\n : null;\n if (\n health !== null &&\n (existingLock.instanceId === undefined || health.instanceId === existingLock.instanceId)\n ) {\n return { instance: { lock: existingLock, health, source: 'lock' }, unownedMonitor: false };\n }\n await removeWebLockIfOwned(lockFile, existingLock);\n }\n\n // The global storage may have been deleted while the detached process kept\n // running. In that state there is no PID file to consult, so prove both\n // halves independently: an Issue Flow health endpoint and the command line\n // of the sole process listening on the configured port.\n const health = await probeHealth(options.host, options.port);\n if (health === null) return { instance: null, unownedMonitor: false };\n\n const inspect = monitorOptions.inspectProcess ?? inspectProcessCommand;\n let owner: PortOwner | null = null;\n if (health.pid !== undefined) {\n const command = await inspect(health.pid);\n if (command !== null) owner = { pid: health.pid, command };\n }\n owner ??= await (monitorOptions.findPortOwner ?? findListeningProcess)(options.port);\n if (owner === null || !isIssueFlowWebServe(owner, options.port)) {\n return { instance: null, unownedMonitor: true };\n }\n\n const recovered: WebLock = {\n pid: owner.pid,\n port: options.port,\n host: options.host,\n startedAt: inferredStartedAt(health),\n ...(health.instanceId === undefined ? {} : { instanceId: health.instanceId }),\n };\n const claimed = await claimWebLock(lockFile, recovered).catch(() => false);\n if (!claimed) {\n const winner = await readWebLock(lockFile);\n if (winner !== null) {\n const winnerHealth = await probeHealth(winner.host, winner.port);\n if (winnerHealth !== null) {\n return {\n instance: { lock: winner, health: winnerHealth, source: 'lock' },\n unownedMonitor: false,\n };\n }\n }\n }\n return { instance: { lock: recovered, health, source: 'port' }, unownedMonitor: false };\n}\n\nasync function stopManagedInstance(\n instance: ManagedWebInstance,\n lockFile: string,\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions,\n): Promise<boolean> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n const version =\n instance.health.version === undefined ? '' : `, version ${instance.health.version}`;\n info(`Web monitor: stopping previous instance (pid ${instance.lock.pid}${version}).`);\n\n try {\n (monitorOptions.kill ?? process.kill)(instance.lock.pid, 'SIGTERM');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ESRCH') {\n await removeWebLockIfOwned(lockFile, instance.lock);\n info(`Web monitor: previous pid ${instance.lock.pid} was already gone; stale lock removed.`);\n return true;\n }\n warn(\n `Web monitor: failed to stop pid ${instance.lock.pid} (${err instanceof Error ? err.message : String(err)}). Continuing the pipeline without restarting it.`,\n );\n return false;\n }\n\n const isAlive = monitorOptions.isAlive ?? isProcessAlive;\n const deadline = Date.now() + (monitorOptions.shutdownTimeoutMs ?? 5000);\n const pollMs = monitorOptions.claimPollIntervalMs ?? DETACHED_CLAIM_POLL_MS;\n while (Date.now() < deadline) {\n const alive = isAlive(instance.lock.pid);\n const health = alive ? await probeHealth(instance.lock.host, instance.lock.port) : null;\n if (!alive || health === null) {\n await removeWebLockIfOwned(lockFile, instance.lock);\n info(`Web monitor: previous instance stopped (pid ${instance.lock.pid}).`);\n info(\n 'Web monitor: process-local static asset and ETag caches invalidated; no on-disk web build cache exists.',\n );\n return true;\n }\n await sleep(pollMs);\n }\n\n warn(\n `Web monitor: pid ${instance.lock.pid} did not confirm shutdown in time. Continuing the pipeline without starting a competing instance.`,\n );\n return false;\n}\n\nexport type StopWebMonitorResult = 'stopped' | 'not-running' | 'unowned' | 'failed';\n\n/** Stop the managed monitor without starting a replacement. Shared by\n * `web stop` and the restart path so both use identical ownership checks. */\nexport async function stopWebMonitor(\n options: Pick<WebServerOptions, 'port' | 'host' | 'info' | 'warn'>,\n monitorOptions: EnsureWebMonitorOptions = {},\n): Promise<StopWebMonitorResult> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n let lockFile: string;\n let restartLockFile: string;\n try {\n await mkdir(getGlobalRoot(monitorOptions), { recursive: true });\n lockFile = getWebLockFile(monitorOptions);\n restartLockFile = getWebRestartLockFile(monitorOptions);\n } catch (err) {\n warn(\n `Web monitor: global storage unavailable (${err instanceof Error ? err.message : String(err)}).`,\n );\n return 'failed';\n }\n\n const lease = await acquireRestartLock(\n restartLockFile,\n monitorOptions.restartLockTimeoutMs ?? 5000,\n );\n if (lease === null) {\n warn('Web monitor: could not acquire the maintenance lock; no process was stopped.');\n return 'failed';\n }\n try {\n const stale = await readWebLock(lockFile);\n const discovered = await discoverManagedInstance(lockFile, options, monitorOptions);\n if (discovered.instance === null) {\n if (stale !== null && !isProcessAlive(stale.pid)) {\n info(`Web monitor: stale lock for dead pid ${stale.pid} removed.`);\n }\n if (discovered.unownedMonitor) {\n warn(\n `Web monitor: port ${options.port} looks like an Issue Flow monitor, but its process ownership could not be verified; it was left untouched.`,\n );\n return 'unowned';\n }\n info('No web monitor is currently running.');\n return 'not-running';\n }\n return (await stopManagedInstance(discovered.instance, lockFile, options, monitorOptions))\n ? 'stopped'\n : 'failed';\n } finally {\n await lease.release();\n }\n}\n\n/**\n * Entry point for `run`/`execute`: get a web monitor without binding one in\n * this process (US-002).\n *\n * 1. Reuse a live, healthy instance exactly like `ensureSingleWebServer`.\n * 2. Otherwise, spawn `<node> <cli> web serve --port … --host … [--refresh …]`\n * detached (`{ detached: true, stdio: 'ignore' }`) and `unref()`ed, so it\n * survives this process exiting and never pipes its own stdio back here.\n * 3. Poll the lock file (bounded) until the spawned instance claims it, then\n * return a *reused* handle for it — this process never owns that `Server`,\n * the same shape `ensureSingleWebServer`'s reuse path already returns.\n *\n * Two failure modes degrade gracefully rather than affecting the pipeline:\n * the global storage tree being unreachable (no resolvable home directory)\n * falls back to `startWebServer` bound right here, exactly the pre-US-001\n * behavior (US-006); a spawn or claim failure with the storage tree otherwise\n * healthy just returns null, same contract `startWebServer` itself has.\n */\nexport async function ensureWebMonitor(\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions = {},\n): Promise<WebServerHandle | null> {\n const warn = options.warn ?? printWarning;\n const info = options.info ?? printInfo;\n\n let lockFile: string;\n let restartLockFile: string;\n try {\n await mkdir(getGlobalRoot(monitorOptions), { recursive: true });\n lockFile = getWebLockFile(monitorOptions);\n restartLockFile = getWebRestartLockFile(monitorOptions);\n } catch (err) {\n warn(\n `Web monitor: global storage unavailable (${err instanceof Error ? err.message : String(err)}). Falling back to legacy in-process mode.`,\n );\n return startWebServer(options);\n }\n\n const coordinationTimeout = monitorOptions.restartLockTimeoutMs ?? 5000;\n const restartRequestedAt = Date.now();\n const restartLease = monitorOptions.restart\n ? await acquireRestartLock(restartLockFile, coordinationTimeout)\n : null;\n if (monitorOptions.restart && restartLease === null) {\n warn(\n 'Web monitor: could not acquire the restart lock. Continuing the pipeline without changing the server.',\n );\n return null;\n }\n if (!monitorOptions.restart && !(await waitForRestart(restartLockFile, coordinationTimeout))) {\n warn(\n 'Web monitor: another restart is still in progress. Continuing the pipeline without starting a competing instance.',\n );\n return null;\n }\n\n try {\n const discovered = await discoverManagedInstance(lockFile, options, monitorOptions);\n const active = discovered.instance;\n if (\n monitorOptions.restart &&\n restartLease?.waited === true &&\n active !== null &&\n Date.parse(active.lock.startedAt) >= restartRequestedAt\n ) {\n info(\n `Web monitor: a concurrent restart already started pid ${active.lock.pid}; reusing ${reusedHandle(active.lock).url}`,\n );\n return reusedHandle(active.lock);\n }\n\n if (active !== null && !monitorOptions.restart) {\n if (active.source === 'port') {\n info(\n `Recovered orphaned web monitor pid ${active.lock.pid} on port ${active.lock.port} and restored web.lock.`,\n );\n }\n const running = active.health.version;\n info(\n `Reusing existing web monitor${running === undefined ? '' : ` v${running}`} at ${chalk.bold.cyan(reusedHandle(active.lock).url)}`,\n );\n // The dashboard is served from the monitor's own memory, so an older\n // process keeps showing an older UI no matter which CLI starts the run.\n // Saying so here is the only moment the user can act on it.\n const current = getPackageVersion();\n if (running !== undefined && running !== current) {\n warn(\n `Web monitor: it runs v${running} and this CLI is v${current}, so the dashboard is the older one. Replace it with --restart-web.`,\n );\n }\n return reusedHandle(active.lock);\n }\n\n if (active !== null && monitorOptions.restart) {\n if (!(await stopManagedInstance(active, lockFile, options, monitorOptions))) return null;\n } else if (discovered.unownedMonitor) {\n warn(\n `Web monitor: port ${options.port} answers an Issue Flow health check, but its owning process could not be verified. It was left untouched and no competing server was started.`,\n );\n return null;\n } else if (monitorOptions.restart) {\n info('Web monitor: no previous managed instance was found; starting a fresh one.');\n info('Web monitor: no on-disk web build cache exists; no files were removed.');\n }\n\n const execPath = monitorOptions.execPath ?? process.execPath;\n const entryScript = monitorOptions.entryScript ?? process.argv[1];\n if (entryScript === undefined) {\n warn('Web monitor: could not determine the CLI entry point to spawn a detached server.');\n return null;\n }\n\n const args = [\n entryScript,\n 'web',\n 'serve',\n '--port',\n String(options.port),\n '--host',\n options.host,\n ];\n if (options.refreshSeconds !== undefined) {\n args.push('--refresh', String(options.refreshSeconds));\n }\n\n const spawnFn = monitorOptions.spawn ?? nodeSpawn;\n try {\n const child = spawnFn(execPath, args, { detached: true, stdio: 'ignore' });\n child.unref();\n } catch (err) {\n warn(\n `Web monitor: failed to start the detached server (${err instanceof Error ? err.message : String(err)}). Continuing without the web server.`,\n );\n return null;\n }\n\n const timeoutMs = monitorOptions.claimTimeoutMs ?? DETACHED_CLAIM_TIMEOUT_MS;\n const pollMs = monitorOptions.claimPollIntervalMs ?? DETACHED_CLAIM_POLL_MS;\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const claimed = await detectActiveInstance(lockFile);\n if (claimed !== null) {\n const handle = reusedHandle(claimed);\n const health = await probeHealth(claimed.host, claimed.port);\n if (monitorOptions.restart) {\n const version = health?.version === undefined ? '' : `, version ${health.version}`;\n info(\n `Web monitor: new instance started (pid ${claimed.pid}${version}) at ${chalk.bold.cyan(handle.url)}`,\n );\n } else {\n info(`Web monitor running at ${chalk.bold.cyan(handle.url)}`);\n }\n return handle;\n }\n await sleep(pollMs);\n }\n\n warn(\n 'Web monitor: the detached server did not start in time. Continuing without the web server.',\n );\n return null;\n } finally {\n await restartLease?.release();\n }\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport { type AgentAvailability, probeAgent } from '../agents/availability.js';\nimport { isAgentPhase, isAgentProviderId } from '../agents/types.js';\nimport { writeAgentPreference } from '../commands/agent.js';\nimport { writeRoutingPreference } from '../commands/routing.js';\nimport { loadRoutingConfig } from '../config.js';\nimport type { JournalEntry } from '../core/journal.js';\nimport { resolvePackageDir } from '../core/prompt-resolver.js';\nimport {\n NullPublisher,\n type SessionPublisher,\n type SessionSnapshot,\n} from '../core/session-state.js';\nimport { MODEL_CATALOG } from '../routing/models.js';\nimport { routingConfigInputSchema } from '../schemas.js';\nimport { readDiagnostics } from '../storage/diagnostics.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { getPackageVersion } from '../version.js';\nimport type { SessionDirectoryHandle } from './session-directory.js';\n\n/**\n * HTTP server for the web monitoring mode. Plain node:http — no new runtime\n * dependencies. Serves the publisher's in-memory snapshot (it never re-reads\n * issues/N/session.json) and the static UI assets.\n *\n * Resilience contract: nothing here may ever affect the pipeline. Listen\n * failures (EADDRINUSE included) log a warning and the execution continues\n * without a server; the server is unref()'d so it cannot keep the process\n * alive; request handling is wrapped so a handler error answers 500 instead\n * of crashing the process.\n */\n\n/** Max length of `issueDescription` on GET /api/sessions (dashboard preview). */\nexport const SESSION_LIST_DESCRIPTION_MAX = 280;\n\n/** Collapse whitespace and truncate for the sessions list payload. */\nexport function truncateSessionDescription(\n text: string | null | undefined,\n max = SESSION_LIST_DESCRIPTION_MAX,\n): string | null {\n if (text === null || text === undefined) return null;\n const normalized = text.replace(/\\s+/g, ' ').trim();\n if (normalized.length === 0) return normalized;\n if (normalized.length <= max) return normalized;\n return `${normalized.slice(0, max - 1).trimEnd()}…`;\n}\n\nexport interface WebServerOptions {\n /**\n * Legacy single-session mode (pre-US-003, still used by the US-006\n * fallback): serves this publisher's in-memory snapshot directly, with no\n * directory scan and no lock file involved. Mutually exclusive with\n * `sessions` — when both are given, `sessions` wins.\n */\n publisher?: SessionPublisher;\n /**\n * Multi-session mode: sessions discovered by polling the global storage\n * tree (`web/session-directory.ts`). This is what every current entry\n * point (`web serve`) passes; `publisher` only exists for the legacy path.\n */\n sessions?: SessionDirectoryHandle;\n port: number;\n host: string;\n /** Suggested UI polling interval, exposed via /api/health. */\n refreshSeconds?: number;\n /** Package version reported by /api/health. Default: read from package.json. */\n version?: string;\n /** Stable identity for this server process. Default: a fresh UUID at startup. */\n instanceId?: string;\n /** Directory holding index.html/app.css/app.js. Default: auto-resolved. */\n publicDir?: string;\n /** Info logger. Default: printInfo. */\n info?: (message: string) => void;\n /** Warning logger. Default: printWarning. */\n warn?: (message: string) => void;\n /** Test seam for the installed-harness catalog returned by /api/config. */\n probeAvailability?: (provider: Parameters<typeof probeAgent>[0]) => Promise<AgentAvailability>;\n /**\n * Whether to `unref()` the underlying socket so it never keeps the process\n * alive on its own. Default `true`, matching the historical contract for a\n * server bound inline in the pipeline process. The standalone `web serve`\n * process (the detached server itself) passes `false`: it has nothing else\n * to do, so staying alive for as long as the server is bound *is* the job.\n */\n unref?: boolean;\n}\n\nexport interface WebServerHandle {\n /**\n * Absent when this handle represents an existing instance reused through\n * `ensureSingleWebServer` (`web/lock.ts`) instead of one bound locally.\n */\n server?: Server;\n /** Host the server is bound to. */\n host: string;\n /** Actual bound port (relevant when options.port is 0). */\n port: number;\n /** Human-facing access URL. */\n url: string;\n /** Identity exposed by health checks and written into web.lock. */\n instanceId: string;\n /** Close the server and release signal handlers. Idempotent, never rejects. */\n close(): Promise<void>;\n}\n\ninterface StaticAsset {\n body: string;\n contentType: string;\n}\n\nconst JSON_TYPE = 'application/json; charset=utf-8';\n\n/**\n * Uniform view over \"whatever sessions this server knows about\", so the route\n * handlers below never care whether they are backed by a single in-memory\n * publisher (legacy mode) or by the global directory scan (US-003/US-004).\n */\ninterface SessionSource {\n /** Every session currently considered active, in no particular order. */\n list(): SessionSnapshot[];\n /** A specific session by id, or undefined when it is not currently active. */\n get(sessionId: string): SessionSnapshot | undefined;\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n}\n\nfunction publisherSessionSource(publisher: SessionPublisher): SessionSource {\n return {\n list: () => {\n const snapshot = publisher.snapshot();\n return snapshot.sessionId === null ? [] : [snapshot];\n },\n get: (sessionId) => {\n const snapshot = publisher.snapshot();\n return snapshot.sessionId === sessionId ? snapshot : undefined;\n },\n events: async (sessionId) => (publisher.snapshot().sessionId === sessionId ? [] : undefined),\n };\n}\n\nfunction directorySessionSource(handle: SessionDirectoryHandle): SessionSource {\n return {\n list: () => handle.sessions().map((entry) => entry.snapshot),\n get: (sessionId) => handle.getSession(sessionId)?.snapshot,\n events: (sessionId) => handle.events(sessionId),\n };\n}\n\nfunction resolveSessionSource(options: WebServerOptions): SessionSource {\n if (options.sessions) return directorySessionSource(options.sessions);\n return publisherSessionSource(options.publisher ?? new NullPublisher());\n}\n\nconst STATIC_ROUTES: Record<string, { file: string; contentType: string }> = {\n '/': { file: 'index.html', contentType: 'text/html; charset=utf-8' },\n '/app.css': { file: 'app.css', contentType: 'text/css; charset=utf-8' },\n '/app.js': { file: 'app.js', contentType: 'text/javascript; charset=utf-8' },\n};\n\n/** Assets are read once at startup; missing files simply 404. */\nasync function loadStaticAssets(publicDir: string | null): Promise<Map<string, StaticAsset>> {\n const assets = new Map<string, StaticAsset>();\n if (publicDir === null) return assets;\n for (const [route, { file, contentType }] of Object.entries(STATIC_ROUTES)) {\n try {\n const body = await readFile(join(publicDir, file), 'utf-8');\n assets.set(route, { body, contentType });\n } catch {\n // Asset not present (e.g. UI not built yet) — route answers 404.\n }\n }\n return assets;\n}\n\n/** Headers applied to every response, including 304s and errors. */\nfunction baseHeaders(res: ServerResponse, instanceId: string): void {\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('X-Issue-Flow-Instance', instanceId);\n res.setHeader('X-Content-Type-Options', 'nosniff');\n res.setHeader('Referrer-Policy', 'no-referrer');\n res.setHeader('X-Robots-Tag', 'noindex, nofollow');\n}\n\nfunction respond(res: ServerResponse, status: number, contentType: string, body: string): void {\n res.statusCode = status;\n res.setHeader('Content-Type', contentType);\n res.end(body);\n}\n\nfunction respondJson(res: ServerResponse, status: number, payload: unknown): void {\n respond(res, status, JSON_TYPE, JSON.stringify(payload));\n}\n\nfunction isLoopbackHost(host: string): boolean {\n return host === '127.0.0.1' || host === '::1' || host === 'localhost';\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += buffer.length;\n if (size > 64 * 1024) throw new Error('Request body too large.');\n chunks.push(buffer);\n }\n return JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');\n}\n\n/**\n * Start the monitoring HTTP server. Returns null when the server could not\n * listen (port in use, invalid host, ...) — the pipeline continues without\n * monitoring, it is never brought down by the server.\n */\nexport async function startWebServer(options: WebServerOptions): Promise<WebServerHandle | null> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n const version = options.version ?? getPackageVersion();\n const instanceId = options.instanceId ?? randomUUID();\n const startedAtMs = Date.now();\n\n // The UI ships at the package root as web/public/ (sibling of prompts/),\n // resolved the same way from src/ and from the published dist/ layout.\n const assets = await loadStaticAssets(\n options.publicDir ?? resolvePackageDir(join('web', 'public')),\n );\n\n const source = resolveSessionSource(options);\n\n // JSON serialization memoized per session id: an unchanged poll answers 304\n // with an empty body. Content-hashed rather than counter-based, unlike the\n // pre-multi-session version — a directory-backed session has no in-process\n // publisher to hand out a monotonic version(), and a hash works uniformly\n // for both sources.\n const statusCache = new Map<string, { body: string; etag: string }>();\n const statusPayload = (snapshot: SessionSnapshot): { body: string; etag: string } => {\n const key = snapshot.sessionId ?? '';\n const body = JSON.stringify(snapshot);\n const cached = statusCache.get(key);\n if (cached !== undefined && cached.body === body) return cached;\n const etag = `\"${createHash('sha1').update(body).digest('hex')}\"`;\n const entry = { body, etag };\n statusCache.set(key, entry);\n return entry;\n };\n\n const handleRequest = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n baseHeaders(res, instanceId);\n\n const requestUrl = new URL(req.url ?? '/', 'http://localhost');\n const path = requestUrl.pathname;\n\n if (req.method === 'POST' && path === '/api/config/agent') {\n if (!isLoopbackHost(options.host)) {\n respondJson(res, 403, {\n error: 'Configuration writes are disabled when the monitor is not bound to loopback.',\n });\n return;\n }\n const body = await readJsonBody(req);\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n respondJson(res, 400, { error: 'Expected a JSON object.' });\n return;\n }\n const input = body as { provider?: unknown; model?: unknown; phase?: unknown };\n if (typeof input.provider !== 'string' || !isAgentProviderId(input.provider)) {\n respondJson(res, 400, { error: 'Invalid provider.' });\n return;\n }\n if (\n input.phase !== undefined &&\n (typeof input.phase !== 'string' || !isAgentPhase(input.phase))\n ) {\n respondJson(res, 400, { error: 'Invalid phase.' });\n return;\n }\n if (input.model !== undefined && typeof input.model !== 'string') {\n respondJson(res, 400, { error: 'Invalid model.' });\n return;\n }\n const file = await writeAgentPreference({\n target: 'global',\n provider: input.provider,\n ...(input.model ? { model: input.model } : {}),\n ...(input.phase ? { phase: input.phase } : {}),\n });\n respondJson(res, 200, { ok: true, file, appliesTo: 'future executions' });\n return;\n }\n\n if (req.method === 'POST' && path === '/api/config/routing') {\n if (!isLoopbackHost(options.host)) {\n respondJson(res, 403, {\n error: 'Configuration writes are disabled when the monitor is not bound to loopback.',\n });\n return;\n }\n const body = await readJsonBody(req);\n const parsed = routingConfigInputSchema.safeParse(body);\n if (!parsed.success || Object.keys(parsed.data).length === 0) {\n respondJson(res, 400, {\n error: parsed.success\n ? 'At least one routing setting is required.'\n : (parsed.error.issues[0]?.message ?? 'Invalid routing configuration.'),\n });\n return;\n }\n const file = await writeRoutingPreference({ target: 'global', values: parsed.data });\n respondJson(res, 200, { ok: true, file, appliesTo: 'future executions' });\n return;\n }\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n respondJson(res, 404, { error: 'Not found' });\n return;\n }\n\n if (path === '/api/status' || path === '/status.json') {\n const sessionId = requestUrl.searchParams.get('session');\n let snapshot: SessionSnapshot | undefined;\n if (sessionId !== null) {\n snapshot = source.get(sessionId);\n if (snapshot === undefined) {\n respondJson(res, 404, { error: `No active session with id '${sessionId}'.` });\n return;\n }\n } else {\n // Back-compat shorthand (US-005): with exactly one active session,\n // GET /api/status with no query string still answers it directly, the\n // same behavior a single-session run always had. Zero or several\n // active sessions is genuinely ambiguous without an id and answers an\n // explicit error instead of guessing.\n const active = source.list();\n if (active.length === 0) {\n respondJson(res, 404, {\n error: 'No active session. Pass ?session=<id> — see GET /api/sessions.',\n });\n return;\n }\n if (active.length > 1) {\n respondJson(res, 409, {\n error: 'Multiple active sessions; specify ?session=<id>.',\n sessions: active.map((entry) => entry.sessionId),\n });\n return;\n }\n snapshot = active[0];\n }\n\n const { body, etag } = statusPayload(snapshot);\n res.setHeader('ETag', etag);\n if (req.headers['if-none-match'] === etag) {\n res.statusCode = 304;\n res.end();\n return;\n }\n respond(res, 200, JSON_TYPE, body);\n return;\n }\n\n if (path === '/api/sessions') {\n // Summary fields for the multi-session dashboard (issue #35): the full\n // SessionSnapshot is already in memory, so the client can render cards\n // from this single list without N× /api/status fetches per poll.\n // issueDescription is truncated — cards only need a short preview.\n respondJson(\n res,\n 200,\n source.list().map((snapshot) => ({\n sessionId: snapshot.sessionId,\n issueNumber: snapshot.issue.number,\n issueTitle: snapshot.issue.title,\n issueDescription: truncateSessionDescription(snapshot.issue.description),\n repositoryName: snapshot.repository.name,\n currentPhase: snapshot.currentPhase,\n progressPercent: snapshot.progress.percent,\n elapsedSeconds: snapshot.elapsedSeconds,\n status: snapshot.status,\n startedAt: snapshot.startedAt,\n updatedAt: snapshot.updatedAt,\n // Resilience fields, for a card that has to answer \"is this still\n // moving\" during a six-hour run. `updatedAt` is already the last\n // activity; these two say how hard the run has had to work for it.\n retries: snapshot.execution.retries,\n correctionCycle: snapshot.execution.correctionCycle,\n attempt: snapshot.resilience.attempt,\n provider: snapshot.resilience.provider,\n lastFailureKind: snapshot.resilience.lastFailureKind,\n cooldownUntil: snapshot.resilience.cooldownUntil,\n lastActivityAt: snapshot.resilience.lastActivityAt,\n statusUrl: `/api/status?session=${encodeURIComponent(snapshot.sessionId ?? '')}`,\n eventsUrl: `/api/events?session=${encodeURIComponent(snapshot.sessionId ?? '')}`,\n })),\n );\n return;\n }\n\n if (path === '/api/events') {\n const sessionId = requestUrl.searchParams.get('session');\n if (sessionId === null) {\n respondJson(res, 400, { error: 'Pass ?session=<id>.' });\n return;\n }\n const entries = await source.events(sessionId);\n if (entries === undefined) {\n respondJson(res, 404, { error: `No active session with id '${sessionId}'.` });\n return;\n }\n respondJson(res, 200, entries);\n return;\n }\n\n if (path === '/api/diagnostics') {\n const sessionId = requestUrl.searchParams.get('session') ?? undefined;\n respondJson(res, 200, await readDiagnostics({ sessionId, limit: 500 }));\n return;\n }\n\n if (path === '/api/config') {\n const sessionId = requestUrl.searchParams.get('session');\n const snapshot = sessionId === null ? undefined : source.get(sessionId);\n const providers = ['claude', 'codex', 'cursor', 'antigravity'] as const;\n const harnesses = ['claude-code', 'codex-cli', 'cursor-cli', 'antigravity-cli'] as const;\n const probe = options.probeAvailability ?? probeAgent;\n const availability = await Promise.all(providers.map((provider) => probe(provider)));\n respondJson(res, 200, {\n effective: snapshot?.configuration ?? null,\n capturedForSession: snapshot?.sessionId ?? null,\n routing: await loadRoutingConfig(),\n catalog: harnesses.map((harness, index) => ({\n harness,\n provider: providers[index],\n installed: availability[index]?.installed ?? false,\n authenticated: availability[index]?.authenticated ?? false,\n models: MODEL_CATALOG[harness] ?? [],\n })),\n writable: isLoopbackHost(options.host),\n writeScope: 'global preferences for future executions',\n });\n return;\n }\n\n if (path === '/api/health') {\n respondJson(res, 200, {\n ok: true,\n pid: process.pid,\n instanceId,\n startedAt: new Date(startedAtMs).toISOString(),\n uptime: Math.round((Date.now() - startedAtMs) / 1000),\n version,\n refreshSeconds: options.refreshSeconds ?? 5,\n capabilities: isLoopbackHost(options.host)\n ? ['config:agent:write', 'config:routing:write']\n : [],\n });\n return;\n }\n\n const asset = assets.get(path);\n if (asset) {\n respond(res, 200, asset.contentType, asset.body);\n return;\n }\n\n respondJson(res, 404, { error: 'Not found' });\n };\n\n const server = createServer((req, res) => {\n void handleRequest(req, res).catch((err) => {\n try {\n respondJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n } catch {\n // Response already destroyed — nothing to do, never crash the process.\n }\n });\n });\n\n const listening = await new Promise<boolean>((resolve) => {\n const onError = (err: NodeJS.ErrnoException): void => {\n if (err.code === 'EADDRINUSE') {\n warn(\n `Web monitor port ${options.port} is already in use. Continuing without the web server.`,\n );\n } else {\n warn(`Web monitor failed to start (${err.message}). Continuing without the web server.`);\n }\n resolve(false);\n };\n server.once('error', onError);\n server.listen(options.port, options.host, () => {\n server.removeListener('error', onError);\n resolve(true);\n });\n });\n\n if (!listening) return null;\n\n // Never keep the process alive on its own, unless explicitly told not to\n // unref: the pipeline ending must end the process. The one exception is the\n // detached `web serve` process (options.unref === false) — it has no other\n // work, so staying alive for as long as the server is bound is the point.\n if (options.unref !== false) {\n server.unref();\n }\n // Post-listen errors must never become uncaught exceptions.\n server.on('error', (err) => {\n warn(`Web monitor server error: ${err.message}`);\n });\n\n const address = server.address() as AddressInfo;\n const port = address.port;\n const displayHost = options.host === '0.0.0.0' ? 'localhost' : options.host;\n const url = `http://${displayHost}:${port}`;\n\n if (options.host === '0.0.0.0') {\n warn('Web monitor bound to 0.0.0.0: anyone on your local network can view the session state.');\n }\n info(`Web monitor running at ${chalk.bold.cyan(url)}`);\n\n let closed = false;\n let closePromise: Promise<void> | null = null;\n\n const doClose = (): Promise<void> =>\n new Promise<void>((resolve) => {\n process.removeListener('SIGINT', onSignal);\n process.removeListener('SIGTERM', onSignal);\n server.close(() => resolve());\n // Drop idle keep-alive connections so close() never hangs.\n server.closeAllConnections();\n });\n\n const close = (): Promise<void> => {\n if (closed) return closePromise ?? Promise.resolve();\n closed = true;\n closePromise = doClose();\n return closePromise;\n };\n\n // `handle.close` is built here, but ensureSingleWebServer/ensureWebMonitor\n // and the `web serve` command each wrap it afterwards (to also remove the\n // lock file, stop the session-directory poller, ...). onSignal below calls\n // `handle.close()` — the *current* value of the property — rather than the\n // `close` closed over here, so a `kill -TERM <pid>` (including `issue-flow\n // web stop`) always runs the fully wrapped shutdown, not just this raw\n // socket close. Capturing `close` directly here was a real bug: the wrapped\n // behavior only ever ran for an *explicit* `handle.close()` call, leaving\n // the lock file (and the poller) behind on every signal-driven shutdown.\n const handle: WebServerHandle = { server, host: options.host, port, url, instanceId, close };\n\n // Explicit close on SIGINT/SIGTERM, then re-raise so the default\n // termination behavior still applies.\n const onSignal = (signal: NodeJS.Signals): void => {\n void handle.close().finally(() => {\n process.kill(process.pid, signal);\n });\n };\n process.once('SIGINT', onSignal);\n process.once('SIGTERM', onSignal);\n\n return handle;\n}\n"],"mappings":"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","names":["randomUUID","readFile","join","chalk","join","pid","readFile","randomUUID","health","chalk"]}
1
+ {"version":3,"sources":["../src/web/lock.ts","../src/web/server.ts"],"sourcesContent":["import { type ChildProcess, spawn as nodeSpawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport { execa } from 'execa';\nimport { isoNow } from '../core/state-manager.js';\n// The liveness probe is shared with `storage/lock.ts`, which generalised this\n// module's guard into run ownership: one definition of \"is that pid alive\",\n// with one place to get the EPERM case right.\nimport { isProcessAlive } from '../storage/lock.js';\nimport { type GetGlobalRootOptions, getGlobalRoot } from '../storage/paths.js';\nimport { type WebLock, webLockSchema } from '../storage/schemas.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { getPackageVersion } from '../version.js';\nimport { startWebServer, type WebServerHandle, type WebServerOptions } from './server.js';\n\n/**\n * Single-instance guard for the web monitoring server (`~/.issue-flow/web.lock`).\n *\n * A machine runs at most one monitoring server. Two entry points build on top\n * of the same lock:\n *\n * - `ensureSingleWebServer` binds (or reuses) a server **in the calling\n * process**. It is the low-level primitive, used by the `web serve` command\n * (the standalone process that ends up owning the lock) and by this\n * module's own tests. Nothing else should call it directly.\n * - `ensureWebMonitor` (US-002) is what `run`/`execute` call: it reuses an\n * active instance exactly like `ensureSingleWebServer`, but when none\n * exists it spawns `web serve` as a **detached** child instead of binding\n * locally, so the server outlives the pipeline process. Falls back to the\n * legacy in-process `startWebServer` (US-006) when the global storage tree\n * itself is unreachable.\n */\n\n/** Lock file name, sibling of `config.json` at the global storage root. */\nexport const WEB_LOCK_FILENAME = 'web.lock';\nexport const WEB_RESTART_LOCK_FILENAME = 'web.restart.lock';\n\n/** How long a health probe against a candidate instance waits before giving up. */\nconst HEALTH_PROBE_TIMEOUT_MS = 1000;\n\ninterface WebHealth {\n ok: true;\n pid?: number;\n instanceId?: string;\n startedAt?: string;\n uptime?: number;\n version?: string;\n}\n\ninterface ManagedWebInstance {\n lock: WebLock;\n health: WebHealth;\n source: 'lock' | 'port';\n}\n\nexport interface PortOwner {\n pid: number;\n command: string;\n}\n\n/**\n * A process that loses the atomic lock creation retries this many times,\n * waiting for the winner to finish starting up, before giving up.\n */\nconst LOCK_CLAIM_RETRIES = 5;\nconst LOCK_CLAIM_RETRY_DELAY_MS = 100;\n\nexport function getWebLockFile(options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), WEB_LOCK_FILENAME);\n}\n\nfunction getWebRestartLockFile(options: GetGlobalRootOptions = {}): string {\n return join(getGlobalRoot(options), WEB_RESTART_LOCK_FILENAME);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** 0.0.0.0 is not a connectable address; probe loopback instead, same mapping\n * `startWebServer` uses to build its own display URL. */\nfunction probeUrl(host: string, port: number): string {\n const probeHost = host === '0.0.0.0' ? '127.0.0.1' : host;\n return `http://${probeHost}:${port}/api/health`;\n}\n\nasync function probeHealth(host: string, port: number): Promise<WebHealth | null> {\n try {\n const res = await fetch(probeUrl(host, port), {\n signal: AbortSignal.timeout(HEALTH_PROBE_TIMEOUT_MS),\n });\n if (!res.ok) return null;\n const body = (await res.json()) as Record<string, unknown>;\n if (body.ok !== true) return null;\n return {\n ok: true,\n ...(typeof body.pid === 'number' && Number.isInteger(body.pid) && body.pid > 0\n ? { pid: body.pid }\n : {}),\n ...(typeof body.instanceId === 'string' && body.instanceId !== ''\n ? { instanceId: body.instanceId }\n : {}),\n ...(typeof body.startedAt === 'string' && body.startedAt !== ''\n ? { startedAt: body.startedAt }\n : {}),\n ...(typeof body.uptime === 'number' && Number.isFinite(body.uptime)\n ? { uptime: body.uptime }\n : {}),\n ...(typeof body.version === 'string' && body.version !== '' ? { version: body.version } : {}),\n };\n } catch {\n return null;\n }\n}\n\nfunction sameLock(a: WebLock | null, b: WebLock): boolean {\n return (\n a !== null &&\n a.pid === b.pid &&\n a.port === b.port &&\n a.host === b.host &&\n a.startedAt === b.startedAt &&\n a.instanceId === b.instanceId\n );\n}\n\nasync function removeWebLockIfOwned(lockFile: string, expected: WebLock): Promise<void> {\n if (sameLock(await readWebLock(lockFile), expected)) {\n await removeWebLock(lockFile);\n }\n}\n\nasync function inspectProcessCommand(pid: number): Promise<string | null> {\n const invocation =\n process.platform === 'win32'\n ? {\n command: 'powershell.exe',\n args: [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(Get-CimInstance Win32_Process -Filter \"ProcessId = ${pid}\").CommandLine`,\n ],\n }\n : { command: 'ps', args: ['-p', String(pid), '-o', 'command='] };\n try {\n const result = await execa(invocation.command, invocation.args, { reject: false });\n const command = result.stdout.trim();\n return result.exitCode === 0 && command !== '' ? command : null;\n } catch {\n return null;\n }\n}\n\nasync function findListeningProcess(port: number): Promise<PortOwner | null> {\n try {\n let pids: number[];\n if (process.platform === 'win32') {\n const result = await execa(\n 'powershell.exe',\n [\n '-NoProfile',\n '-NonInteractive',\n '-Command',\n `(Get-NetTCPConnection -State Listen -LocalPort ${port} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty OwningProcess -Unique) -join [Environment]::NewLine`,\n ],\n { reject: false },\n );\n pids = result.stdout\n .split(/\\s+/)\n .map(Number)\n .filter((pid) => Number.isInteger(pid) && pid > 0);\n } else {\n const result = await execa('lsof', ['-nP', '-t', `-iTCP:${port}`, '-sTCP:LISTEN'], {\n reject: false,\n });\n pids = result.stdout\n .split(/\\s+/)\n .map(Number)\n .filter((pid) => Number.isInteger(pid) && pid > 0);\n }\n const unique = [...new Set(pids)];\n if (unique.length !== 1) return null;\n const pid = unique[0] as number;\n const command = await inspectProcessCommand(pid);\n return command === null ? null : { pid, command };\n } catch {\n return null;\n }\n}\n\nfunction isIssueFlowWebServe(owner: PortOwner, port: number): boolean {\n const escapedPort = String(port).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return (\n /(?:^|\\s)web\\s+serve(?:\\s|$)/.test(owner.command) &&\n new RegExp(`(?:^|\\\\s)--port(?:=|\\\\s+)${escapedPort}(?:\\\\s|$)`).test(owner.command) &&\n /issue-flow|(?:^|[/\\\\])dist[/\\\\]cli\\.js/.test(owner.command)\n );\n}\n\n/** Malformed content (partial write, older/newer incompatible shape) reads as\n * \"no lock\" — the same degrade-to-absent behavior as `loadGlobalConfig()`. */\nexport async function readWebLock(lockFile: string): Promise<WebLock | null> {\n let raw: string;\n try {\n raw = await readFile(lockFile, 'utf-8');\n } catch {\n return null;\n }\n try {\n const result = webLockSchema.safeParse(JSON.parse(raw));\n return result.success ? result.data : null;\n } catch {\n return null;\n }\n}\n\nexport async function removeWebLock(lockFile: string): Promise<void> {\n try {\n await rm(lockFile, { force: true });\n } catch {\n /* best-effort cleanup */\n }\n}\n\n/**\n * A lock is only trustworthy when both signals agree: the owning process is\n * alive AND it answers `/api/health`. A dead pid is the obvious stale case;\n * a live pid that does not answer covers a process that still exists but\n * crashed before binding, was killed -9 leaving a zombie-adjacent state, or\n * whose port was reclaimed by something else entirely. Either way the lock is\n * removed so the next attempt starts clean instead of looping on it forever.\n */\nexport async function detectActiveInstance(lockFile: string): Promise<WebLock | null> {\n const lock = await readWebLock(lockFile);\n if (lock === null) return null;\n\n const health = isProcessAlive(lock.pid) ? await probeHealth(lock.host, lock.port) : null;\n if (health !== null && (lock.instanceId === undefined || health.instanceId === lock.instanceId)) {\n return lock;\n }\n\n await removeWebLockIfOwned(lockFile, lock);\n return null;\n}\n\n/**\n * Atomic exclusive create: `wx` fails with EEXIST when another process won\n * the race, which is what lets two concurrent invocations agree on a single\n * winner without a separate lock-of-the-lock.\n */\nasync function claimWebLock(lockFile: string, lock: WebLock): Promise<boolean> {\n try {\n await writeFile(lockFile, JSON.stringify(lock, null, 2), { encoding: 'utf-8', flag: 'wx' });\n return true;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'EEXIST') return false;\n throw err;\n }\n}\n\ninterface RestartLock {\n pid: number;\n operationId: string;\n startedAt: string;\n}\n\nasync function readRestartLock(file: string): Promise<RestartLock | null> {\n try {\n const value = JSON.parse(await readFile(file, 'utf-8')) as Record<string, unknown>;\n return typeof value.pid === 'number' &&\n Number.isInteger(value.pid) &&\n value.pid > 0 &&\n typeof value.operationId === 'string' &&\n value.operationId !== '' &&\n typeof value.startedAt === 'string' &&\n value.startedAt !== ''\n ? { pid: value.pid, operationId: value.operationId, startedAt: value.startedAt }\n : null;\n } catch {\n return null;\n }\n}\n\nasync function removeRestartLockIfOwned(file: string, expected: RestartLock): Promise<void> {\n const current = await readRestartLock(file);\n if (current?.operationId === expected.operationId) {\n await rm(file, { force: true }).catch(() => {});\n }\n}\n\nasync function waitForRestart(file: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const lock = await readRestartLock(file);\n if (lock === null) {\n // Missing or malformed means there is no owner we can safely wait for.\n // Do not unlink here: a restart may have claimed the path between the\n // read and this branch. The explicit acquirer cleans malformed files.\n return true;\n }\n if (!isProcessAlive(lock.pid)) {\n await removeRestartLockIfOwned(file, lock);\n return true;\n }\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n }\n return false;\n}\n\nasync function acquireRestartLock(\n file: string,\n timeoutMs: number,\n): Promise<{ lock: RestartLock; waited: boolean; release: () => Promise<void> } | null> {\n const deadline = Date.now() + timeoutMs;\n let waited = false;\n while (Date.now() < deadline) {\n const lock: RestartLock = {\n pid: process.pid,\n operationId: randomUUID(),\n startedAt: isoNow(),\n };\n try {\n await writeFile(file, JSON.stringify(lock, null, 2), { encoding: 'utf-8', flag: 'wx' });\n return {\n lock,\n waited,\n release: () => removeRestartLockIfOwned(file, lock),\n };\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') return null;\n }\n waited = true;\n const existing = await readRestartLock(file);\n if (existing === null || !isProcessAlive(existing.pid)) {\n if (existing === null) await rm(file, { force: true }).catch(() => {});\n else await removeRestartLockIfOwned(file, existing);\n continue;\n }\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n }\n return null;\n}\n\n/** Handle returned when reusing an already-running instance instead of\n * binding a new one — there is no local `Server` to hold, and closing it\n * must not tear down a server another process owns. */\nfunction reusedHandle(lock: WebLock): WebServerHandle {\n const displayHost = lock.host === '0.0.0.0' ? 'localhost' : lock.host;\n return {\n host: lock.host,\n port: lock.port,\n url: `http://${displayHost}:${lock.port}`,\n instanceId: lock.instanceId ?? `legacy-${lock.pid}-${lock.startedAt}`,\n close: async () => {},\n };\n}\n\n/**\n * Start the web monitoring server, first checking whether a live instance\n * already owns `web.lock` and reusing it instead of binding a second one.\n *\n * The lock is claimed only *after* a successful bind, never before: claiming\n * it with the port the server is about to (but hasn't yet) listen on would\n * make that lock briefly answer no health probe at all, and a concurrent\n * invocation would misread that gap as \"stale\" and delete a perfectly good\n * lock out from under its owner. Binding first also means two invocations\n * racing for the same fixed port never both reach the claim step — the OS\n * itself lets only one `listen()` succeed. The `wx` claim then exists for the\n * remaining race: two invocations that *both* manage to bind (only possible\n * with an ephemeral port) still agree on exactly one winner. The loser closes\n * the server it just opened and defers to whichever lock exists.\n *\n * Returns null exactly when `startWebServer` would: nothing here may affect\n * the pipeline, so a lock we cannot resolve after every retry degrades to \"no\n * server\" rather than throwing.\n */\nexport async function ensureSingleWebServer(\n options: WebServerOptions,\n lockOptions: GetGlobalRootOptions = {},\n): Promise<WebServerHandle | null> {\n const warn = options.warn ?? printWarning;\n const info = options.info ?? printInfo;\n const lockFile = getWebLockFile(lockOptions);\n\n for (let attempt = 0; attempt <= LOCK_CLAIM_RETRIES; attempt++) {\n const active = await detectActiveInstance(lockFile);\n if (active !== null) {\n info(`Reusing existing web monitor at ${reusedHandle(active).url}`);\n return reusedHandle(active);\n }\n\n const handle = await startWebServer(options);\n if (handle === null) {\n // Could not bind. Most likely another instance's listen() won a race\n // on the same fixed port between our detection above and this call —\n // its lock should be visible shortly. Anything else (port taken by an\n // unrelated process, invalid host, ...) fails the same way on the next\n // retry too, and the loop's own limit stops it from spinning forever.\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n continue;\n }\n\n let claimed: boolean;\n try {\n claimed = await claimWebLock(lockFile, {\n pid: process.pid,\n port: handle.port,\n host: handle.host,\n startedAt: isoNow(),\n instanceId: handle.instanceId,\n });\n } catch (err) {\n await handle.close();\n throw err;\n }\n\n if (!claimed) {\n // We bound successfully (an ephemeral port let both of us through),\n // but another invocation's lock got written first. We are not the\n // canonical instance: close what we just opened and defer to it.\n await handle.close();\n await sleep(LOCK_CLAIM_RETRY_DELAY_MS);\n continue;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n await removeWebLock(lockFile);\n };\n return handle;\n }\n\n warn('Could not determine the web monitor lock ownership. Continuing without the web server.');\n return null;\n}\n\n/** Bounded wait for a freshly spawned `web serve` child to claim the lock. */\nconst DETACHED_CLAIM_TIMEOUT_MS = 5000;\nconst DETACHED_CLAIM_POLL_MS = 150;\n\nexport interface EnsureWebMonitorOptions extends GetGlobalRootOptions {\n /** Injectable for tests. Defaults to `node:child_process`'s `spawn`. */\n spawn?: (\n command: string,\n args: string[],\n options: { detached: true; stdio: 'ignore' },\n ) => ChildProcess;\n /** Node executable to spawn. Defaults to `process.execPath`. */\n execPath?: string;\n /** CLI entry script re-invoked as `<entry> web serve ...`. Defaults to `process.argv[1]`. */\n entryScript?: string;\n /** How long to wait for the spawned instance's lock to become healthy. */\n claimTimeoutMs?: number;\n claimPollIntervalMs?: number;\n /** Explicitly replace any managed monitor before spawning this entry point. */\n restart?: boolean;\n /** Injectable process operations used by restart and orphan recovery tests. */\n kill?: typeof process.kill;\n isAlive?: (pid: number) => boolean;\n findPortOwner?: (port: number) => Promise<PortOwner | null>;\n inspectProcess?: (pid: number) => Promise<string | null>;\n shutdownTimeoutMs?: number;\n restartLockTimeoutMs?: number;\n}\n\ninterface DiscoveryResult {\n instance: ManagedWebInstance | null;\n /** A monitor-like health endpoint answered, but ownership could not be proven. */\n unownedMonitor: boolean;\n}\n\nfunction inferredStartedAt(health: WebHealth): string {\n if (health.startedAt !== undefined) return health.startedAt;\n if (health.uptime !== undefined) {\n return new Date(Date.now() - Math.max(0, health.uptime) * 1000).toISOString();\n }\n return isoNow();\n}\n\nasync function discoverManagedInstance(\n lockFile: string,\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions,\n): Promise<DiscoveryResult> {\n const existingLock = await readWebLock(lockFile);\n if (existingLock !== null) {\n const health = isProcessAlive(existingLock.pid)\n ? await probeHealth(existingLock.host, existingLock.port)\n : null;\n if (\n health !== null &&\n (existingLock.instanceId === undefined || health.instanceId === existingLock.instanceId)\n ) {\n return { instance: { lock: existingLock, health, source: 'lock' }, unownedMonitor: false };\n }\n await removeWebLockIfOwned(lockFile, existingLock);\n }\n\n // The global storage may have been deleted while the detached process kept\n // running. In that state there is no PID file to consult, so prove both\n // halves independently: an Issue Flow health endpoint and the command line\n // of the sole process listening on the configured port.\n const health = await probeHealth(options.host, options.port);\n if (health === null) return { instance: null, unownedMonitor: false };\n\n const inspect = monitorOptions.inspectProcess ?? inspectProcessCommand;\n let owner: PortOwner | null = null;\n if (health.pid !== undefined) {\n const command = await inspect(health.pid);\n if (command !== null) owner = { pid: health.pid, command };\n }\n owner ??= await (monitorOptions.findPortOwner ?? findListeningProcess)(options.port);\n if (owner === null || !isIssueFlowWebServe(owner, options.port)) {\n return { instance: null, unownedMonitor: true };\n }\n\n const recovered: WebLock = {\n pid: owner.pid,\n port: options.port,\n host: options.host,\n startedAt: inferredStartedAt(health),\n ...(health.instanceId === undefined ? {} : { instanceId: health.instanceId }),\n };\n const claimed = await claimWebLock(lockFile, recovered).catch(() => false);\n if (!claimed) {\n const winner = await readWebLock(lockFile);\n if (winner !== null) {\n const winnerHealth = await probeHealth(winner.host, winner.port);\n if (winnerHealth !== null) {\n return {\n instance: { lock: winner, health: winnerHealth, source: 'lock' },\n unownedMonitor: false,\n };\n }\n }\n }\n return { instance: { lock: recovered, health, source: 'port' }, unownedMonitor: false };\n}\n\nasync function stopManagedInstance(\n instance: ManagedWebInstance,\n lockFile: string,\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions,\n): Promise<boolean> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n const version =\n instance.health.version === undefined ? '' : `, version ${instance.health.version}`;\n info(`Web monitor: stopping previous instance (pid ${instance.lock.pid}${version}).`);\n\n try {\n (monitorOptions.kill ?? process.kill)(instance.lock.pid, 'SIGTERM');\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ESRCH') {\n await removeWebLockIfOwned(lockFile, instance.lock);\n info(`Web monitor: previous pid ${instance.lock.pid} was already gone; stale lock removed.`);\n return true;\n }\n warn(\n `Web monitor: failed to stop pid ${instance.lock.pid} (${err instanceof Error ? err.message : String(err)}). Continuing the pipeline without restarting it.`,\n );\n return false;\n }\n\n const isAlive = monitorOptions.isAlive ?? isProcessAlive;\n const deadline = Date.now() + (monitorOptions.shutdownTimeoutMs ?? 5000);\n const pollMs = monitorOptions.claimPollIntervalMs ?? DETACHED_CLAIM_POLL_MS;\n while (Date.now() < deadline) {\n const alive = isAlive(instance.lock.pid);\n const health = alive ? await probeHealth(instance.lock.host, instance.lock.port) : null;\n if (!alive || health === null) {\n await removeWebLockIfOwned(lockFile, instance.lock);\n info(`Web monitor: previous instance stopped (pid ${instance.lock.pid}).`);\n info(\n 'Web monitor: process-local static asset and ETag caches invalidated; no on-disk web build cache exists.',\n );\n return true;\n }\n await sleep(pollMs);\n }\n\n warn(\n `Web monitor: pid ${instance.lock.pid} did not confirm shutdown in time. Continuing the pipeline without starting a competing instance.`,\n );\n return false;\n}\n\nexport type StopWebMonitorResult = 'stopped' | 'not-running' | 'unowned' | 'failed';\n\n/** Stop the managed monitor without starting a replacement. Shared by\n * `web stop` and the restart path so both use identical ownership checks. */\nexport async function stopWebMonitor(\n options: Pick<WebServerOptions, 'port' | 'host' | 'info' | 'warn'>,\n monitorOptions: EnsureWebMonitorOptions = {},\n): Promise<StopWebMonitorResult> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n let lockFile: string;\n let restartLockFile: string;\n try {\n await mkdir(getGlobalRoot(monitorOptions), { recursive: true });\n lockFile = getWebLockFile(monitorOptions);\n restartLockFile = getWebRestartLockFile(monitorOptions);\n } catch (err) {\n warn(\n `Web monitor: global storage unavailable (${err instanceof Error ? err.message : String(err)}).`,\n );\n return 'failed';\n }\n\n const lease = await acquireRestartLock(\n restartLockFile,\n monitorOptions.restartLockTimeoutMs ?? 5000,\n );\n if (lease === null) {\n warn('Web monitor: could not acquire the maintenance lock; no process was stopped.');\n return 'failed';\n }\n try {\n const stale = await readWebLock(lockFile);\n const discovered = await discoverManagedInstance(lockFile, options, monitorOptions);\n if (discovered.instance === null) {\n if (stale !== null && !isProcessAlive(stale.pid)) {\n info(`Web monitor: stale lock for dead pid ${stale.pid} removed.`);\n }\n if (discovered.unownedMonitor) {\n warn(\n `Web monitor: port ${options.port} looks like an Issue Flow monitor, but its process ownership could not be verified; it was left untouched.`,\n );\n return 'unowned';\n }\n info('No web monitor is currently running.');\n return 'not-running';\n }\n return (await stopManagedInstance(discovered.instance, lockFile, options, monitorOptions))\n ? 'stopped'\n : 'failed';\n } finally {\n await lease.release();\n }\n}\n\n/**\n * Entry point for `run`/`execute`: get a web monitor without binding one in\n * this process (US-002).\n *\n * 1. Reuse a live, healthy instance exactly like `ensureSingleWebServer`.\n * 2. Otherwise, spawn `<node> <cli> web serve --port … --host … [--refresh …]`\n * detached (`{ detached: true, stdio: 'ignore' }`) and `unref()`ed, so it\n * survives this process exiting and never pipes its own stdio back here.\n * 3. Poll the lock file (bounded) until the spawned instance claims it, then\n * return a *reused* handle for it — this process never owns that `Server`,\n * the same shape `ensureSingleWebServer`'s reuse path already returns.\n *\n * Two failure modes degrade gracefully rather than affecting the pipeline:\n * the global storage tree being unreachable (no resolvable home directory)\n * falls back to `startWebServer` bound right here, exactly the pre-US-001\n * behavior (US-006); a spawn or claim failure with the storage tree otherwise\n * healthy just returns null, same contract `startWebServer` itself has.\n */\nexport async function ensureWebMonitor(\n options: WebServerOptions,\n monitorOptions: EnsureWebMonitorOptions = {},\n): Promise<WebServerHandle | null> {\n const warn = options.warn ?? printWarning;\n const info = options.info ?? printInfo;\n\n let lockFile: string;\n let restartLockFile: string;\n try {\n await mkdir(getGlobalRoot(monitorOptions), { recursive: true });\n lockFile = getWebLockFile(monitorOptions);\n restartLockFile = getWebRestartLockFile(monitorOptions);\n } catch (err) {\n warn(\n `Web monitor: global storage unavailable (${err instanceof Error ? err.message : String(err)}). Falling back to legacy in-process mode.`,\n );\n return startWebServer(options);\n }\n\n const coordinationTimeout = monitorOptions.restartLockTimeoutMs ?? 5000;\n const restartRequestedAt = Date.now();\n const restartLease = monitorOptions.restart\n ? await acquireRestartLock(restartLockFile, coordinationTimeout)\n : null;\n if (monitorOptions.restart && restartLease === null) {\n warn(\n 'Web monitor: could not acquire the restart lock. Continuing the pipeline without changing the server.',\n );\n return null;\n }\n if (!monitorOptions.restart && !(await waitForRestart(restartLockFile, coordinationTimeout))) {\n warn(\n 'Web monitor: another restart is still in progress. Continuing the pipeline without starting a competing instance.',\n );\n return null;\n }\n\n try {\n const discovered = await discoverManagedInstance(lockFile, options, monitorOptions);\n const active = discovered.instance;\n if (\n monitorOptions.restart &&\n restartLease?.waited === true &&\n active !== null &&\n Date.parse(active.lock.startedAt) >= restartRequestedAt\n ) {\n info(\n `Web monitor: a concurrent restart already started pid ${active.lock.pid}; reusing ${reusedHandle(active.lock).url}`,\n );\n return reusedHandle(active.lock);\n }\n\n if (active !== null && !monitorOptions.restart) {\n if (active.source === 'port') {\n info(\n `Recovered orphaned web monitor pid ${active.lock.pid} on port ${active.lock.port} and restored web.lock.`,\n );\n }\n const running = active.health.version;\n info(\n `Reusing existing web monitor${running === undefined ? '' : ` v${running}`} at ${chalk.bold.cyan(reusedHandle(active.lock).url)}`,\n );\n // The dashboard is served from the monitor's own memory, so an older\n // process keeps showing an older UI no matter which CLI starts the run.\n // Saying so here is the only moment the user can act on it.\n const current = getPackageVersion();\n if (running !== undefined && running !== current) {\n warn(\n `Web monitor: it runs v${running} and this CLI is v${current}, so the dashboard is the older one. Replace it with --restart-web.`,\n );\n }\n return reusedHandle(active.lock);\n }\n\n if (active !== null && monitorOptions.restart) {\n if (!(await stopManagedInstance(active, lockFile, options, monitorOptions))) return null;\n } else if (discovered.unownedMonitor) {\n warn(\n `Web monitor: port ${options.port} answers an Issue Flow health check, but its owning process could not be verified. It was left untouched and no competing server was started.`,\n );\n return null;\n } else if (monitorOptions.restart) {\n info('Web monitor: no previous managed instance was found; starting a fresh one.');\n info('Web monitor: no on-disk web build cache exists; no files were removed.');\n }\n\n const execPath = monitorOptions.execPath ?? process.execPath;\n const entryScript = monitorOptions.entryScript ?? process.argv[1];\n if (entryScript === undefined) {\n warn('Web monitor: could not determine the CLI entry point to spawn a detached server.');\n return null;\n }\n\n const args = [\n entryScript,\n 'web',\n 'serve',\n '--port',\n String(options.port),\n '--host',\n options.host,\n ];\n if (options.refreshSeconds !== undefined) {\n args.push('--refresh', String(options.refreshSeconds));\n }\n\n const spawnFn = monitorOptions.spawn ?? nodeSpawn;\n try {\n const child = spawnFn(execPath, args, { detached: true, stdio: 'ignore' });\n child.unref();\n } catch (err) {\n warn(\n `Web monitor: failed to start the detached server (${err instanceof Error ? err.message : String(err)}). Continuing without the web server.`,\n );\n return null;\n }\n\n const timeoutMs = monitorOptions.claimTimeoutMs ?? DETACHED_CLAIM_TIMEOUT_MS;\n const pollMs = monitorOptions.claimPollIntervalMs ?? DETACHED_CLAIM_POLL_MS;\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n const claimed = await detectActiveInstance(lockFile);\n if (claimed !== null) {\n const handle = reusedHandle(claimed);\n const health = await probeHealth(claimed.host, claimed.port);\n if (monitorOptions.restart) {\n const version = health?.version === undefined ? '' : `, version ${health.version}`;\n info(\n `Web monitor: new instance started (pid ${claimed.pid}${version}) at ${chalk.bold.cyan(handle.url)}`,\n );\n } else {\n info(`Web monitor running at ${chalk.bold.cyan(handle.url)}`);\n }\n return handle;\n }\n await sleep(pollMs);\n }\n\n warn(\n 'Web monitor: the detached server did not start in time. Continuing without the web server.',\n );\n return null;\n } finally {\n await restartLease?.release();\n }\n}\n","import { createHash, randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport { join } from 'node:path';\nimport chalk from 'chalk';\nimport { type AgentAvailability, probeAgent } from '../agents/availability.js';\nimport { isAgentPhase, isAgentProviderId } from '../agents/types.js';\nimport { writeAgentPreference } from '../commands/agent.js';\nimport { writeRoutingPreference } from '../commands/routing.js';\nimport { loadRoutingConfig } from '../config.js';\nimport type { JournalEntry } from '../core/journal.js';\nimport { resolvePackageDir } from '../core/prompt-resolver.js';\nimport {\n NullPublisher,\n type SessionPublisher,\n type SessionSnapshot,\n} from '../core/session-state.js';\nimport { MODEL_CATALOG } from '../routing/models.js';\nimport { routingConfigInputSchema } from '../schemas.js';\nimport { readDiagnostics } from '../storage/diagnostics.js';\nimport { printInfo, printWarning } from '../ui/logger.js';\nimport { getPackageVersion } from '../version.js';\nimport type { SessionDirectoryHandle } from './session-directory.js';\n\n/**\n * HTTP server for the web monitoring mode. Plain node:http — no new runtime\n * dependencies. Serves the publisher's in-memory snapshot (it never re-reads\n * issues/N/session.json) and the static UI assets.\n *\n * Resilience contract: nothing here may ever affect the pipeline. Listen\n * failures (EADDRINUSE included) log a warning and the execution continues\n * without a server; the server is unref()'d so it cannot keep the process\n * alive; request handling is wrapped so a handler error answers 500 instead\n * of crashing the process.\n */\n\n/** Max length of `issueDescription` on GET /api/sessions (dashboard preview). */\nexport const SESSION_LIST_DESCRIPTION_MAX = 280;\n\n/** Collapse whitespace and truncate for the sessions list payload. */\nexport function truncateSessionDescription(\n text: string | null | undefined,\n max = SESSION_LIST_DESCRIPTION_MAX,\n): string | null {\n if (text === null || text === undefined) return null;\n const normalized = text.replace(/\\s+/g, ' ').trim();\n if (normalized.length === 0) return normalized;\n if (normalized.length <= max) return normalized;\n return `${normalized.slice(0, max - 1).trimEnd()}…`;\n}\n\nexport interface WebServerOptions {\n /**\n * Legacy single-session mode (pre-US-003, still used by the US-006\n * fallback): serves this publisher's in-memory snapshot directly, with no\n * directory scan and no lock file involved. Mutually exclusive with\n * `sessions` — when both are given, `sessions` wins.\n */\n publisher?: SessionPublisher;\n /**\n * Multi-session mode: sessions discovered by polling the global storage\n * tree (`web/session-directory.ts`). This is what every current entry\n * point (`web serve`) passes; `publisher` only exists for the legacy path.\n */\n sessions?: SessionDirectoryHandle;\n port: number;\n host: string;\n /** Suggested UI polling interval, exposed via /api/health. */\n refreshSeconds?: number;\n /** Package version reported by /api/health. Default: read from package.json. */\n version?: string;\n /** Stable identity for this server process. Default: a fresh UUID at startup. */\n instanceId?: string;\n /** Directory holding index.html/app.css/app.js. Default: auto-resolved. */\n publicDir?: string;\n /** Info logger. Default: printInfo. */\n info?: (message: string) => void;\n /** Warning logger. Default: printWarning. */\n warn?: (message: string) => void;\n /** Test seam for the installed-harness catalog returned by /api/config. */\n probeAvailability?: (provider: Parameters<typeof probeAgent>[0]) => Promise<AgentAvailability>;\n /**\n * Whether to `unref()` the underlying socket so it never keeps the process\n * alive on its own. Default `true`, matching the historical contract for a\n * server bound inline in the pipeline process. The standalone `web serve`\n * process (the detached server itself) passes `false`: it has nothing else\n * to do, so staying alive for as long as the server is bound *is* the job.\n */\n unref?: boolean;\n}\n\nexport interface WebServerHandle {\n /**\n * Absent when this handle represents an existing instance reused through\n * `ensureSingleWebServer` (`web/lock.ts`) instead of one bound locally.\n */\n server?: Server;\n /** Host the server is bound to. */\n host: string;\n /** Actual bound port (relevant when options.port is 0). */\n port: number;\n /** Human-facing access URL. */\n url: string;\n /** Identity exposed by health checks and written into web.lock. */\n instanceId: string;\n /** Close the server and release signal handlers. Idempotent, never rejects. */\n close(): Promise<void>;\n}\n\ninterface StaticAsset {\n body: string;\n contentType: string;\n}\n\nconst JSON_TYPE = 'application/json; charset=utf-8';\n\n/**\n * Uniform view over \"whatever sessions this server knows about\", so the route\n * handlers below never care whether they are backed by a single in-memory\n * publisher (legacy mode) or by the global directory scan (US-003/US-004).\n */\ninterface SessionSource {\n /** Every session currently considered active, in no particular order. */\n list(): SessionSnapshot[];\n /** A specific session by id, or undefined when it is not currently active. */\n get(sessionId: string): SessionSnapshot | undefined;\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n}\n\nfunction publisherSessionSource(publisher: SessionPublisher): SessionSource {\n return {\n list: () => {\n const snapshot = publisher.snapshot();\n return snapshot.sessionId === null ? [] : [snapshot];\n },\n get: (sessionId) => {\n const snapshot = publisher.snapshot();\n return snapshot.sessionId === sessionId ? snapshot : undefined;\n },\n events: async (sessionId) => (publisher.snapshot().sessionId === sessionId ? [] : undefined),\n };\n}\n\nfunction directorySessionSource(handle: SessionDirectoryHandle): SessionSource {\n return {\n list: () => handle.sessions().map((entry) => entry.snapshot),\n get: (sessionId) => handle.getSession(sessionId)?.snapshot,\n events: (sessionId) => handle.events(sessionId),\n };\n}\n\nfunction resolveSessionSource(options: WebServerOptions): SessionSource {\n if (options.sessions) return directorySessionSource(options.sessions);\n return publisherSessionSource(options.publisher ?? new NullPublisher());\n}\n\nconst STATIC_ROUTES: Record<string, { file: string; contentType: string }> = {\n '/': { file: 'index.html', contentType: 'text/html; charset=utf-8' },\n '/app.css': { file: 'app.css', contentType: 'text/css; charset=utf-8' },\n '/app.js': { file: 'app.js', contentType: 'text/javascript; charset=utf-8' },\n};\n\n/** Assets are read once at startup; missing files simply 404. */\nasync function loadStaticAssets(publicDir: string | null): Promise<Map<string, StaticAsset>> {\n const assets = new Map<string, StaticAsset>();\n if (publicDir === null) return assets;\n for (const [route, { file, contentType }] of Object.entries(STATIC_ROUTES)) {\n try {\n const body = await readFile(join(publicDir, file), 'utf-8');\n assets.set(route, { body, contentType });\n } catch {\n // Asset not present (e.g. UI not built yet) — route answers 404.\n }\n }\n return assets;\n}\n\n/** Headers applied to every response, including 304s and errors. */\nfunction baseHeaders(res: ServerResponse, instanceId: string): void {\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('X-Issue-Flow-Instance', instanceId);\n res.setHeader('X-Content-Type-Options', 'nosniff');\n res.setHeader('Referrer-Policy', 'no-referrer');\n res.setHeader('X-Robots-Tag', 'noindex, nofollow');\n}\n\nfunction respond(res: ServerResponse, status: number, contentType: string, body: string): void {\n res.statusCode = status;\n res.setHeader('Content-Type', contentType);\n res.end(body);\n}\n\nfunction respondJson(res: ServerResponse, status: number, payload: unknown): void {\n respond(res, status, JSON_TYPE, JSON.stringify(payload));\n}\n\nfunction isLoopbackHost(host: string): boolean {\n return host === '127.0.0.1' || host === '::1' || host === 'localhost';\n}\n\nasync function readJsonBody(req: IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const chunk of req) {\n const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);\n size += buffer.length;\n if (size > 64 * 1024) throw new Error('Request body too large.');\n chunks.push(buffer);\n }\n return JSON.parse(Buffer.concat(chunks).toString('utf-8') || '{}');\n}\n\n/**\n * Start the monitoring HTTP server. Returns null when the server could not\n * listen (port in use, invalid host, ...) — the pipeline continues without\n * monitoring, it is never brought down by the server.\n */\nexport async function startWebServer(options: WebServerOptions): Promise<WebServerHandle | null> {\n const info = options.info ?? printInfo;\n const warn = options.warn ?? printWarning;\n const version = options.version ?? getPackageVersion();\n const instanceId = options.instanceId ?? randomUUID();\n const startedAtMs = Date.now();\n\n // The UI ships at the package root as web/public/ (sibling of prompts/),\n // resolved the same way from src/ and from the published dist/ layout.\n const assets = await loadStaticAssets(\n options.publicDir ?? resolvePackageDir(join('web', 'public')),\n );\n\n const source = resolveSessionSource(options);\n\n // JSON serialization memoized per session id: an unchanged poll answers 304\n // with an empty body. Content-hashed rather than counter-based, unlike the\n // pre-multi-session version — a directory-backed session has no in-process\n // publisher to hand out a monotonic version(), and a hash works uniformly\n // for both sources.\n const statusCache = new Map<string, { body: string; etag: string }>();\n const statusPayload = (snapshot: SessionSnapshot): { body: string; etag: string } => {\n const key = snapshot.sessionId ?? '';\n const body = JSON.stringify(snapshot);\n const cached = statusCache.get(key);\n if (cached !== undefined && cached.body === body) return cached;\n const etag = `\"${createHash('sha1').update(body).digest('hex')}\"`;\n const entry = { body, etag };\n statusCache.set(key, entry);\n return entry;\n };\n\n const handleRequest = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n baseHeaders(res, instanceId);\n\n const requestUrl = new URL(req.url ?? '/', 'http://localhost');\n const path = requestUrl.pathname;\n\n if (req.method === 'POST' && path === '/api/config/agent') {\n if (!isLoopbackHost(options.host)) {\n respondJson(res, 403, {\n error: 'Configuration writes are disabled when the monitor is not bound to loopback.',\n });\n return;\n }\n const body = await readJsonBody(req);\n if (body === null || typeof body !== 'object' || Array.isArray(body)) {\n respondJson(res, 400, { error: 'Expected a JSON object.' });\n return;\n }\n const input = body as { provider?: unknown; model?: unknown; phase?: unknown };\n if (typeof input.provider !== 'string' || !isAgentProviderId(input.provider)) {\n respondJson(res, 400, { error: 'Invalid provider.' });\n return;\n }\n if (\n input.phase !== undefined &&\n (typeof input.phase !== 'string' || !isAgentPhase(input.phase))\n ) {\n respondJson(res, 400, { error: 'Invalid phase.' });\n return;\n }\n if (input.model !== undefined && typeof input.model !== 'string') {\n respondJson(res, 400, { error: 'Invalid model.' });\n return;\n }\n const file = await writeAgentPreference({\n target: 'global',\n provider: input.provider,\n ...(input.model ? { model: input.model } : {}),\n ...(input.phase ? { phase: input.phase } : {}),\n });\n respondJson(res, 200, { ok: true, file, appliesTo: 'future executions' });\n return;\n }\n\n if (req.method === 'POST' && path === '/api/config/routing') {\n if (!isLoopbackHost(options.host)) {\n respondJson(res, 403, {\n error: 'Configuration writes are disabled when the monitor is not bound to loopback.',\n });\n return;\n }\n const body = await readJsonBody(req);\n const parsed = routingConfigInputSchema.safeParse(body);\n if (!parsed.success || Object.keys(parsed.data).length === 0) {\n respondJson(res, 400, {\n error: parsed.success\n ? 'At least one routing setting is required.'\n : (parsed.error.issues[0]?.message ?? 'Invalid routing configuration.'),\n });\n return;\n }\n const file = await writeRoutingPreference({ target: 'global', values: parsed.data });\n respondJson(res, 200, { ok: true, file, appliesTo: 'future executions' });\n return;\n }\n\n if (req.method !== 'GET' && req.method !== 'HEAD') {\n respondJson(res, 404, { error: 'Not found' });\n return;\n }\n\n if (path === '/api/status' || path === '/status.json') {\n const sessionId = requestUrl.searchParams.get('session');\n let snapshot: SessionSnapshot | undefined;\n if (sessionId !== null) {\n snapshot = source.get(sessionId);\n if (snapshot === undefined) {\n respondJson(res, 404, { error: `No active session with id '${sessionId}'.` });\n return;\n }\n } else {\n // Back-compat shorthand (US-005): with exactly one active session,\n // GET /api/status with no query string still answers it directly, the\n // same behavior a single-session run always had. Zero or several\n // active sessions is genuinely ambiguous without an id and answers an\n // explicit error instead of guessing.\n const active = source.list();\n if (active.length === 0) {\n respondJson(res, 404, {\n error: 'No active session. Pass ?session=<id> — see GET /api/sessions.',\n });\n return;\n }\n if (active.length > 1) {\n respondJson(res, 409, {\n error: 'Multiple active sessions; specify ?session=<id>.',\n sessions: active.map((entry) => entry.sessionId),\n });\n return;\n }\n snapshot = active[0];\n }\n\n const { body, etag } = statusPayload(snapshot);\n res.setHeader('ETag', etag);\n if (req.headers['if-none-match'] === etag) {\n res.statusCode = 304;\n res.end();\n return;\n }\n respond(res, 200, JSON_TYPE, body);\n return;\n }\n\n if (path === '/api/sessions') {\n // Summary fields for the multi-session dashboard (issue #35): the full\n // SessionSnapshot is already in memory, so the client can render cards\n // from this single list without N× /api/status fetches per poll.\n // issueDescription is truncated — cards only need a short preview.\n respondJson(\n res,\n 200,\n source.list().map((snapshot) => ({\n sessionId: snapshot.sessionId,\n issueNumber: snapshot.issue.number,\n issueTitle: snapshot.issue.title,\n issueDescription: truncateSessionDescription(snapshot.issue.description),\n repositoryName: snapshot.repository.name,\n currentPhase: snapshot.currentPhase,\n progressPercent: snapshot.progress.percent,\n elapsedSeconds: snapshot.elapsedSeconds,\n status: snapshot.status,\n startedAt: snapshot.startedAt,\n updatedAt: snapshot.updatedAt,\n // Resilience fields, for a card that has to answer \"is this still\n // moving\" during a six-hour run. `updatedAt` is already the last\n // activity; these two say how hard the run has had to work for it.\n retries: snapshot.execution.retries,\n correctionCycle: snapshot.execution.correctionCycle,\n attempt: snapshot.resilience.attempt,\n provider: snapshot.resilience.provider,\n lastFailureKind: snapshot.resilience.lastFailureKind,\n cooldownUntil: snapshot.resilience.cooldownUntil,\n lastActivityAt: snapshot.resilience.lastActivityAt,\n statusUrl: `/api/status?session=${encodeURIComponent(snapshot.sessionId ?? '')}`,\n eventsUrl: `/api/events?session=${encodeURIComponent(snapshot.sessionId ?? '')}`,\n })),\n );\n return;\n }\n\n if (path === '/api/events') {\n const sessionId = requestUrl.searchParams.get('session');\n if (sessionId === null) {\n respondJson(res, 400, { error: 'Pass ?session=<id>.' });\n return;\n }\n const entries = await source.events(sessionId);\n if (entries === undefined) {\n respondJson(res, 404, { error: `No active session with id '${sessionId}'.` });\n return;\n }\n respondJson(res, 200, entries);\n return;\n }\n\n if (path === '/api/diagnostics') {\n const sessionId = requestUrl.searchParams.get('session') ?? undefined;\n respondJson(res, 200, await readDiagnostics({ sessionId, limit: 500 }));\n return;\n }\n\n if (path === '/api/config') {\n const sessionId = requestUrl.searchParams.get('session');\n const snapshot = sessionId === null ? undefined : source.get(sessionId);\n const providers = ['claude', 'codex', 'cursor', 'antigravity'] as const;\n const harnesses = ['claude-code', 'codex-cli', 'cursor-cli', 'antigravity-cli'] as const;\n const probe = options.probeAvailability ?? probeAgent;\n const availability = await Promise.all(providers.map((provider) => probe(provider)));\n respondJson(res, 200, {\n effective: snapshot?.configuration ?? null,\n capturedForSession: snapshot?.sessionId ?? null,\n routing: await loadRoutingConfig(),\n catalog: harnesses.map((harness, index) => {\n const entry = availability[index];\n return {\n harness,\n provider: providers[index],\n installed: entry?.installed ?? false,\n authenticated: entry?.authenticated ?? false,\n authentication: entry?.authentication ?? 'failed',\n state: entry?.state ?? 'unavailable',\n source: entry?.source ?? 'probe',\n observedAt: entry?.observedAt ?? null,\n expiresAt: entry?.expiresAt ?? null,\n detail: entry?.detail ?? null,\n models: MODEL_CATALOG[harness] ?? [],\n };\n }),\n writable: isLoopbackHost(options.host),\n writeScope: 'global preferences for future executions',\n });\n return;\n }\n\n if (path === '/api/health') {\n respondJson(res, 200, {\n ok: true,\n pid: process.pid,\n instanceId,\n startedAt: new Date(startedAtMs).toISOString(),\n uptime: Math.round((Date.now() - startedAtMs) / 1000),\n version,\n refreshSeconds: options.refreshSeconds ?? 5,\n capabilities: isLoopbackHost(options.host)\n ? ['config:agent:write', 'config:routing:write']\n : [],\n });\n return;\n }\n\n const asset = assets.get(path);\n if (asset) {\n respond(res, 200, asset.contentType, asset.body);\n return;\n }\n\n respondJson(res, 404, { error: 'Not found' });\n };\n\n const server = createServer((req, res) => {\n void handleRequest(req, res).catch((err) => {\n try {\n respondJson(res, 500, { error: err instanceof Error ? err.message : String(err) });\n } catch {\n // Response already destroyed — nothing to do, never crash the process.\n }\n });\n });\n\n const listening = await new Promise<boolean>((resolve) => {\n const onError = (err: NodeJS.ErrnoException): void => {\n if (err.code === 'EADDRINUSE') {\n warn(\n `Web monitor port ${options.port} is already in use. Continuing without the web server.`,\n );\n } else {\n warn(`Web monitor failed to start (${err.message}). Continuing without the web server.`);\n }\n resolve(false);\n };\n server.once('error', onError);\n server.listen(options.port, options.host, () => {\n server.removeListener('error', onError);\n resolve(true);\n });\n });\n\n if (!listening) return null;\n\n // Never keep the process alive on its own, unless explicitly told not to\n // unref: the pipeline ending must end the process. The one exception is the\n // detached `web serve` process (options.unref === false) — it has no other\n // work, so staying alive for as long as the server is bound is the point.\n if (options.unref !== false) {\n server.unref();\n }\n // Post-listen errors must never become uncaught exceptions.\n server.on('error', (err) => {\n warn(`Web monitor server error: ${err.message}`);\n });\n\n const address = server.address() as AddressInfo;\n const port = address.port;\n const displayHost = options.host === '0.0.0.0' ? 'localhost' : options.host;\n const url = `http://${displayHost}:${port}`;\n\n if (options.host === '0.0.0.0') {\n warn('Web monitor bound to 0.0.0.0: anyone on your local network can view the session state.');\n }\n info(`Web monitor running at ${chalk.bold.cyan(url)}`);\n\n let closed = false;\n let closePromise: Promise<void> | null = null;\n\n const doClose = (): Promise<void> =>\n new Promise<void>((resolve) => {\n process.removeListener('SIGINT', onSignal);\n process.removeListener('SIGTERM', onSignal);\n server.close(() => resolve());\n // Drop idle keep-alive connections so close() never hangs.\n server.closeAllConnections();\n });\n\n const close = (): Promise<void> => {\n if (closed) return closePromise ?? Promise.resolve();\n closed = true;\n closePromise = doClose();\n return closePromise;\n };\n\n // `handle.close` is built here, but ensureSingleWebServer/ensureWebMonitor\n // and the `web serve` command each wrap it afterwards (to also remove the\n // lock file, stop the session-directory poller, ...). onSignal below calls\n // `handle.close()` — the *current* value of the property — rather than the\n // `close` closed over here, so a `kill -TERM <pid>` (including `issue-flow\n // web stop`) always runs the fully wrapped shutdown, not just this raw\n // socket close. Capturing `close` directly here was a real bug: the wrapped\n // behavior only ever ran for an *explicit* `handle.close()` call, leaving\n // the lock file (and the poller) behind on every signal-driven shutdown.\n const handle: WebServerHandle = { server, host: options.host, port, url, instanceId, close };\n\n // Explicit close on SIGINT/SIGTERM, then re-raise so the default\n // termination behavior still applies.\n const onSignal = (signal: NodeJS.Signals): void => {\n void handle.close().finally(() => {\n process.kill(process.pid, signal);\n });\n };\n process.once('SIGINT', onSignal);\n process.once('SIGTERM', onSignal);\n\n return handle;\n}\n"],"mappings":"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","names":["randomUUID","readFile","join","chalk","join","pid","readFile","randomUUID","health","chalk"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  trimErrorMessage
4
- } from "./chunk-SOM4QA6C.js";
4
+ } from "./chunk-GD7JZMSJ.js";
5
5
 
6
6
  // src/telemetry/redact.ts
7
7
  var SECRET = /(?:sk-ant-|sk-|ghp_|gho_|github_pat_|xox[baprs]-)[A-Za-z0-9_-]{8,}/g;
@@ -22,4 +22,4 @@ export {
22
22
  redactSecrets,
23
23
  redactFailureMessage
24
24
  };
25
- //# sourceMappingURL=chunk-PPZ3K4YM.js.map
25
+ //# sourceMappingURL=chunk-PRGPSNGA.js.map
@@ -1,26 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getShutdownSignal
4
- } from "./chunk-HY5HLFDU.js";
4
+ } from "./chunk-UHDXXLYH.js";
5
5
  import {
6
6
  printRetry
7
- } from "./chunk-XZUEFTUP.js";
8
- import {
9
- fixedBackoffPolicy,
10
- withRetry
11
- } from "./chunk-YVPZUN6X.js";
7
+ } from "./chunk-J6VJ5M2U.js";
12
8
  import {
13
9
  getSessionPublisher
14
- } from "./chunk-DUS5ZNKL.js";
10
+ } from "./chunk-DIMDAYZS.js";
15
11
  import {
16
12
  isoNow
17
- } from "./chunk-SOM4QA6C.js";
13
+ } from "./chunk-GD7JZMSJ.js";
18
14
  import {
19
- classify
20
- } from "./chunk-XEYH6ZZI.js";
15
+ classify,
16
+ fixedBackoffPolicy,
17
+ withRetry
18
+ } from "./chunk-TJVX42KY.js";
21
19
 
22
20
  // src/core/phase-runner.ts
23
- import { readFile } from "fs/promises";
24
21
  var DEFAULT_PHASE_RETRY_LIMIT = 3;
25
22
  var DEFAULT_BACKOFF_BASE_SECONDS = 15;
26
23
  var DEFAULT_BACKOFF_MAX_SECONDS = 120;
@@ -62,16 +59,6 @@ function phaseFailure(result) {
62
59
  retryable: result.transient === true
63
60
  };
64
61
  }
65
- async function readFileWithGrace(path, delaysMs = [300, 800]) {
66
- for (let i = 0; ; i++) {
67
- try {
68
- return await readFile(path, "utf-8");
69
- } catch (err) {
70
- if (i >= delaysMs.length) throw err;
71
- await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));
72
- }
73
- }
74
- }
75
62
 
76
63
  // src/utils/retry.ts
77
64
  function isTransientFailure(exitCode, output) {
@@ -80,7 +67,6 @@ function isTransientFailure(exitCode, output) {
80
67
 
81
68
  export {
82
69
  runPhaseWithRetry,
83
- readFileWithGrace,
84
70
  isTransientFailure
85
71
  };
86
- //# sourceMappingURL=chunk-32JSYXEK.js.map
72
+ //# sourceMappingURL=chunk-RGFMQFX7.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/phase-runner.ts","../src/utils/retry.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { type ClassifiedFailure, classify } from '../resilience/errors.js';\nimport { fixedBackoffPolicy, withRetry } from '../resilience/retry.js';\nimport { printRetry } from '../ui/logger.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { getShutdownSignal } from './shutdown.js';\nimport { isoNow } from './state-manager.js';\n\nexport interface PhaseAttemptResult {\n ok: boolean;\n /** Whether a failed attempt is worth retrying. Ignored when ok is true. */\n transient?: boolean;\n error?: string;\n}\n\nexport interface PhaseRetryOptions {\n phase: string;\n attempt: (attemptNumber: number) => Promise<PhaseAttemptResult>;\n retryLimit?: number;\n backoffBaseSeconds?: number;\n backoffMaxSeconds?: number;\n /** Cuts a pending backoff short, so a phase stops waiting on shutdown. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_PHASE_RETRY_LIMIT = 3;\nconst DEFAULT_BACKOFF_BASE_SECONDS = 15;\nconst DEFAULT_BACKOFF_MAX_SECONDS = 120;\n\n/**\n * Bounded retry wrapper for single-shot phases (prd, plan, pr) that invoke\n * the Claude CLI once and validate an artifact. Applied to phases that\n * previously aborted the whole pipeline on the first failure with no chance\n * to recover. Non-transient failures (or exhausting the retry budget) still\n * end the phase — this bounds recovery attempts, it never retries forever.\n *\n * The loop itself is `resilience/retry.ts:withRetry`, shared with the\n * `execute` loop of `core/engine.ts`. What stays here is what is specific to a\n * phase: the caller's `transient` verdict, the `retry` event and the printed\n * line. With no options supplied the effective numbers are the ones this\n * module has always used — 3 attempts, 15s doubling to 120s, no jitter.\n */\nexport async function runPhaseWithRetry(options: PhaseRetryOptions): Promise<PhaseAttemptResult> {\n const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;\n const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;\n const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;\n\n if (retryLimit < 1) return { ok: false, error: 'Phase never attempted' };\n\n const outcome = await withRetry<PhaseAttemptResult>((attempt) => options.attempt(attempt), {\n policy: fixedBackoffPolicy(retryLimit, backoffBaseSeconds, backoffMaxSeconds),\n // The caller's own signal when it has one, and the process-wide shutdown\n // otherwise: a backoff nobody can interrupt is a `Ctrl+C` that appears to\n // do nothing for two minutes.\n signal: options.signal ?? getShutdownSignal(),\n evaluate: (result) => (result.ok ? null : phaseFailure(result)),\n onAttempt: ({ attempt, failure, willRetry, delayMs }) => {\n if (failure === null || !willRetry) return;\n\n const delaySeconds = delayMs / 1000;\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt,\n delaySeconds,\n reason: failure.message,\n kind: failure.kind,\n });\n printRetry(\n `Phase ${options.phase} hit a recoverable failure (attempt ${attempt}/${retryLimit}): ${failure.message}. Retrying in ${delaySeconds}s.`,\n );\n },\n });\n\n return outcome.value;\n}\n\n/**\n * Turn a phase's own verdict into the classified failure `withRetry` decides\n * on.\n *\n * `transient` stays authoritative: the phases build it with\n * `isTransientFailure()` over the CLI's output, but a phase can also fail on\n * something it alone can see — a PRD file that never appeared — and that\n * judgement is not recoverable from the error text. `classify()` is consulted\n * only to name the *kind*, which is what the `retry` event now reports.\n */\nfunction phaseFailure(result: PhaseAttemptResult): ClassifiedFailure {\n const message = result.error ?? 'Phase failed';\n return {\n ...classify({ source: 'agent', stdout: message }),\n message,\n retryable: result.transient === true,\n };\n}\n\n/**\n * Re-read a file a few times with short delays before giving up. Absorbs a\n * brief filesystem-visibility lag between the Claude CLI's Write tool\n * completing and this process observing the result, without spending a full\n * phase retry (and a new Claude invocation) on what may just be a timing gap.\n */\nexport async function readFileWithGrace(\n path: string,\n delaysMs: number[] = [300, 800],\n): Promise<string> {\n for (let i = 0; ; i++) {\n try {\n return await readFile(path, 'utf-8');\n } catch (err) {\n if (i >= delaysMs.length) throw err;\n await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));\n }\n }\n}\n","import { classify } from '../resilience/errors.js';\n\n/**\n * Determine if an agent invocation failure is transient (retryable).\n *\n * This is a thin adapter over `resilience/errors.ts:classify()` — the taxonomy\n * is the single source of truth, and this signature survives only because\n * `core/engine.ts` and the phase commands are written against it. A caller\n * that has more evidence than an exit code and a blob of text (an `errno`, an\n * HTTP status, `timedOut`) should call `classify()` directly and keep it.\n */\nexport function isTransientFailure(exitCode: number, output: string): boolean {\n return classify({ source: 'agent', exitCode, stdout: output }).retryable;\n}\n\n/**\n * Calculate retry delay using exponential backoff.\n *\n * delay = baseSeconds * 2^(attempt-1), capped at maxSeconds\n *\n * @param attempt - The retry attempt number (1-based)\n * @param baseSeconds - Base delay in seconds (default: 30)\n * @param maxSeconds - Maximum delay in seconds (default: 900)\n */\nexport function retryDelaySeconds(\n attempt: number,\n baseSeconds: number = 30,\n maxSeconds: number = 900,\n): number {\n const delay = baseSeconds * 2 ** (attempt - 1);\n return Math.min(delay, maxSeconds);\n}\n\n/**\n * Sleep for a given number of seconds.\n */\nexport function sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AAyBzB,IAAM,4BAA4B;AAClC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AAepC,eAAsB,kBAAkB,SAAyD;AAC/F,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,aAAa,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,wBAAwB;AAEvE,QAAM,UAAU,MAAM,UAA8B,CAAC,YAAY,QAAQ,QAAQ,OAAO,GAAG;AAAA,IACzF,QAAQ,mBAAmB,YAAY,oBAAoB,iBAAiB;AAAA;AAAA;AAAA;AAAA,IAI5E,QAAQ,QAAQ,UAAU,kBAAkB;AAAA,IAC5C,UAAU,CAAC,WAAY,OAAO,KAAK,OAAO,aAAa,MAAM;AAAA,IAC7D,WAAW,CAAC,EAAE,SAAS,SAAS,WAAW,QAAQ,MAAM;AACvD,UAAI,YAAY,QAAQ,CAAC,UAAW;AAEpC,YAAM,eAAe,UAAU;AAC/B,0BAAoB,EAAE,QAAQ;AAAA,QAC5B,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,MAChB,CAAC;AACD;AAAA,QACE,SAAS,QAAQ,KAAK,uCAAuC,OAAO,IAAI,UAAU,MAAM,QAAQ,OAAO,iBAAiB,YAAY;AAAA,MACtI;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,QAAQ;AACjB;AAYA,SAAS,aAAa,QAA+C;AACnE,QAAM,UAAU,OAAO,SAAS;AAChC,SAAO;AAAA,IACL,GAAG,SAAS,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAChD;AAAA,IACA,WAAW,OAAO,cAAc;AAAA,EAClC;AACF;AAQA,eAAsB,kBACpB,MACA,WAAqB,CAAC,KAAK,GAAG,GACb;AACjB,WAAS,IAAI,KAAK,KAAK;AACrB,QAAI;AACF,aAAO,MAAM,SAAS,MAAM,OAAO;AAAA,IACrC,SAAS,KAAK;AACZ,UAAI,KAAK,SAAS,OAAQ,OAAM;AAChC,YAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,SAAS,CAAC,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACF;;;ACvGO,SAAS,mBAAmB,UAAkB,QAAyB;AAC5E,SAAO,SAAS,EAAE,QAAQ,SAAS,UAAU,QAAQ,OAAO,CAAC,EAAE;AACjE;","names":[]}
1
+ {"version":3,"sources":["../src/core/phase-runner.ts","../src/utils/retry.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { type ClassifiedFailure, classify } from '../resilience/errors.js';\nimport { fixedBackoffPolicy, withRetry } from '../resilience/retry.js';\nimport { printRetry } from '../ui/logger.js';\nimport { getSessionPublisher } from './session-publisher.js';\nimport { getShutdownSignal } from './shutdown.js';\nimport { isoNow } from './state-manager.js';\n\nexport interface PhaseAttemptResult {\n ok: boolean;\n /** Whether a failed attempt is worth retrying. Ignored when ok is true. */\n transient?: boolean;\n error?: string;\n}\n\nexport interface PhaseRetryOptions {\n phase: string;\n attempt: (attemptNumber: number) => Promise<PhaseAttemptResult>;\n retryLimit?: number;\n backoffBaseSeconds?: number;\n backoffMaxSeconds?: number;\n /** Cuts a pending backoff short, so a phase stops waiting on shutdown. */\n signal?: AbortSignal;\n}\n\nconst DEFAULT_PHASE_RETRY_LIMIT = 3;\nconst DEFAULT_BACKOFF_BASE_SECONDS = 15;\nconst DEFAULT_BACKOFF_MAX_SECONDS = 120;\n\n/**\n * Bounded retry wrapper for single-shot phases (prd, plan, pr) that invoke\n * the Claude CLI once and validate an artifact. Applied to phases that\n * previously aborted the whole pipeline on the first failure with no chance\n * to recover. Non-transient failures (or exhausting the retry budget) still\n * end the phase — this bounds recovery attempts, it never retries forever.\n *\n * The loop itself is `resilience/retry.ts:withRetry`, shared with the\n * `execute` loop of `core/engine.ts`. What stays here is what is specific to a\n * phase: the caller's `transient` verdict, the `retry` event and the printed\n * line. With no options supplied the effective numbers are the ones this\n * module has always used — 3 attempts, 15s doubling to 120s, no jitter.\n */\nexport async function runPhaseWithRetry(options: PhaseRetryOptions): Promise<PhaseAttemptResult> {\n const retryLimit = options.retryLimit ?? DEFAULT_PHASE_RETRY_LIMIT;\n const backoffBaseSeconds = options.backoffBaseSeconds ?? DEFAULT_BACKOFF_BASE_SECONDS;\n const backoffMaxSeconds = options.backoffMaxSeconds ?? DEFAULT_BACKOFF_MAX_SECONDS;\n\n if (retryLimit < 1) return { ok: false, error: 'Phase never attempted' };\n\n const outcome = await withRetry<PhaseAttemptResult>((attempt) => options.attempt(attempt), {\n policy: fixedBackoffPolicy(retryLimit, backoffBaseSeconds, backoffMaxSeconds),\n // The caller's own signal when it has one, and the process-wide shutdown\n // otherwise: a backoff nobody can interrupt is a `Ctrl+C` that appears to\n // do nothing for two minutes.\n signal: options.signal ?? getShutdownSignal(),\n evaluate: (result) => (result.ok ? null : phaseFailure(result)),\n onAttempt: ({ attempt, failure, willRetry, delayMs }) => {\n if (failure === null || !willRetry) return;\n\n const delaySeconds = delayMs / 1000;\n getSessionPublisher().publish({\n type: 'retry',\n at: isoNow(),\n attempt,\n delaySeconds,\n reason: failure.message,\n kind: failure.kind,\n });\n printRetry(\n `Phase ${options.phase} hit a recoverable failure (attempt ${attempt}/${retryLimit}): ${failure.message}. Retrying in ${delaySeconds}s.`,\n );\n },\n });\n\n return outcome.value;\n}\n\n/**\n * Turn a phase's own verdict into the classified failure `withRetry` decides\n * on.\n *\n * `transient` stays authoritative: the phases build it with\n * `isTransientFailure()` over the CLI's output, but a phase can also fail on\n * something it alone can see — a PRD file that never appeared — and that\n * judgement is not recoverable from the error text. `classify()` is consulted\n * only to name the *kind*, which is what the `retry` event now reports.\n */\nfunction phaseFailure(result: PhaseAttemptResult): ClassifiedFailure {\n const message = result.error ?? 'Phase failed';\n return {\n ...classify({ source: 'agent', stdout: message }),\n message,\n retryable: result.transient === true,\n };\n}\n\n/**\n * Re-read a file a few times with short delays before giving up. Absorbs a\n * brief filesystem-visibility lag between the Claude CLI's Write tool\n * completing and this process observing the result, without spending a full\n * phase retry (and a new Claude invocation) on what may just be a timing gap.\n */\nexport async function readFileWithGrace(\n path: string,\n delaysMs: number[] = [300, 800],\n): Promise<string> {\n for (let i = 0; ; i++) {\n try {\n return await readFile(path, 'utf-8');\n } catch (err) {\n if (i >= delaysMs.length) throw err;\n await new Promise((resolve) => setTimeout(resolve, delaysMs[i]));\n }\n }\n}\n","import { classify } from '../resilience/errors.js';\n\n/**\n * Determine if an agent invocation failure is transient (retryable).\n *\n * This is a thin adapter over `resilience/errors.ts:classify()` — the taxonomy\n * is the single source of truth, and this signature survives only because\n * `core/engine.ts` and the phase commands are written against it. A caller\n * that has more evidence than an exit code and a blob of text (an `errno`, an\n * HTTP status, `timedOut`) should call `classify()` directly and keep it.\n */\nexport function isTransientFailure(exitCode: number, output: string): boolean {\n return classify({ source: 'agent', exitCode, stdout: output }).retryable;\n}\n\n/**\n * Calculate retry delay using exponential backoff.\n *\n * delay = baseSeconds * 2^(attempt-1), capped at maxSeconds\n *\n * @param attempt - The retry attempt number (1-based)\n * @param baseSeconds - Base delay in seconds (default: 30)\n * @param maxSeconds - Maximum delay in seconds (default: 900)\n */\nexport function retryDelaySeconds(\n attempt: number,\n baseSeconds: number = 30,\n maxSeconds: number = 900,\n): number {\n const delay = baseSeconds * 2 ** (attempt - 1);\n return Math.min(delay, maxSeconds);\n}\n\n/**\n * Sleep for a given number of seconds.\n */\nexport function sleep(seconds: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, seconds * 1000));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,IAAM,4BAA4B;AAClC,IAAM,+BAA+B;AACrC,IAAM,8BAA8B;AAepC,eAAsB,kBAAkB,SAAyD;AAC/F,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,qBAAqB,QAAQ,sBAAsB;AACzD,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,MAAI,aAAa,EAAG,QAAO,EAAE,IAAI,OAAO,OAAO,wBAAwB;AAEvE,QAAM,UAAU,MAAM,UAA8B,CAAC,YAAY,QAAQ,QAAQ,OAAO,GAAG;AAAA,IACzF,QAAQ,mBAAmB,YAAY,oBAAoB,iBAAiB;AAAA;AAAA;AAAA;AAAA,IAI5E,QAAQ,QAAQ,UAAU,kBAAkB;AAAA,IAC5C,UAAU,CAAC,WAAY,OAAO,KAAK,OAAO,aAAa,MAAM;AAAA,IAC7D,WAAW,CAAC,EAAE,SAAS,SAAS,WAAW,QAAQ,MAAM;AACvD,UAAI,YAAY,QAAQ,CAAC,UAAW;AAEpC,YAAM,eAAe,UAAU;AAC/B,0BAAoB,EAAE,QAAQ;AAAA,QAC5B,MAAM;AAAA,QACN,IAAI,OAAO;AAAA,QACX;AAAA,QACA;AAAA,QACA,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,MAChB,CAAC;AACD;AAAA,QACE,SAAS,QAAQ,KAAK,uCAAuC,OAAO,IAAI,UAAU,MAAM,QAAQ,OAAO,iBAAiB,YAAY;AAAA,MACtI;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,QAAQ;AACjB;AAYA,SAAS,aAAa,QAA+C;AACnE,QAAM,UAAU,OAAO,SAAS;AAChC,SAAO;AAAA,IACL,GAAG,SAAS,EAAE,QAAQ,SAAS,QAAQ,QAAQ,CAAC;AAAA,IAChD;AAAA,IACA,WAAW,OAAO,cAAc;AAAA,EAClC;AACF;;;ACnFO,SAAS,mBAAmB,UAAkB,QAAyB;AAC5E,SAAO,SAAS,EAAE,QAAQ,SAAS,UAAU,QAAQ,OAAO,CAAC,EAAE;AACjE;","names":[]}