issue-flow 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-AT64RXTA.js → agent-PMWCDWOW.js} +12 -12
- package/dist/{analyze-O2PYDIFK.js → analyze-556KCJKZ.js} +20 -20
- package/dist/{apply-SSU6QCD4.js → apply-WVV7G7WN.js} +12 -12
- package/dist/{bench-ODFPAGW6.js → bench-UBFGKUHR.js} +18 -18
- package/dist/{chunk-KAHQ6F5C.js → chunk-2H43OVTU.js} +11 -11
- package/dist/{chunk-PCN7ZQND.js → chunk-4FUWWBTP.js} +6 -6
- package/dist/{chunk-5ZKBIXET.js → chunk-73TS2H42.js} +14 -14
- package/dist/{chunk-XLVGPXNL.js → chunk-AOAQU7PD.js} +2 -2
- package/dist/{chunk-6E2PEBFT.js → chunk-BSBJXPLC.js} +8 -8
- package/dist/chunk-C5TL7T4V.js +27 -0
- package/dist/chunk-C5TL7T4V.js.map +1 -0
- package/dist/{chunk-QRCGW2ZO.js → chunk-DBEMD34J.js} +6 -6
- package/dist/{chunk-FK4IDY6P.js → chunk-EPFC4LHT.js} +9 -9
- package/dist/{chunk-AYYMW33T.js → chunk-FKWYD44M.js} +2 -2
- package/dist/{chunk-RWEXK55D.js → chunk-G5LACAXO.js} +2 -2
- package/dist/{chunk-TBDP6QPJ.js → chunk-GPLP7EPL.js} +6 -6
- package/dist/{chunk-ARWNYXFL.js → chunk-GV2DABNZ.js} +12 -12
- package/dist/{chunk-XQHFC62U.js → chunk-GVOZ6NDO.js} +2 -2
- package/dist/{chunk-F4OK7KEP.js → chunk-H3EYBJZU.js} +7 -7
- package/dist/{chunk-3BWZO4YI.js → chunk-IAMIBZRF.js} +2 -2
- package/dist/{chunk-WM3Y7Q4A.js → chunk-J2LJB2OH.js} +10 -10
- package/dist/{chunk-MEFY4B7T.js → chunk-KV2K7MK2.js} +9 -9
- package/dist/{chunk-XG2NYDRA.js → chunk-MCLPSL6B.js} +3 -3
- package/dist/{chunk-ZXPASFHG.js → chunk-MXCOSPLJ.js} +2 -2
- package/dist/{chunk-MROS3SKL.js → chunk-NQCR3HSU.js} +3 -3
- package/dist/{chunk-2ZAM45PZ.js → chunk-OGN2NX57.js} +9 -9
- package/dist/{chunk-A35HE4V7.js → chunk-OGVWJO4S.js} +3 -3
- package/dist/{chunk-HGSYD57B.js → chunk-OLUOZLU5.js} +32 -28
- package/dist/chunk-OLUOZLU5.js.map +1 -0
- package/dist/{chunk-6SRE4KD7.js → chunk-OOX3RMD3.js} +4 -4
- package/dist/{chunk-6FXOU2KR.js → chunk-PGN2VX4Y.js} +6 -6
- package/dist/{chunk-XRLRKTQA.js → chunk-Q2TZA6AC.js} +11 -11
- package/dist/{chunk-XDKGA6UX.js → chunk-QVLIIGBU.js} +5 -2
- package/dist/chunk-QVLIIGBU.js.map +1 -0
- package/dist/{chunk-MGCOQU6N.js → chunk-QZOBR6YF.js} +4 -4
- package/dist/{chunk-UNQYMR3H.js → chunk-R42YHLDV.js} +2 -2
- package/dist/{chunk-6QYMSNVA.js → chunk-ROIIEVZH.js} +3 -2
- package/dist/chunk-ROIIEVZH.js.map +1 -0
- package/dist/{chunk-NVZMBXTE.js → chunk-SVDEWOUD.js} +4 -4
- package/dist/{chunk-ZFIXEYUJ.js → chunk-SW2MF47D.js} +26 -14
- package/dist/chunk-SW2MF47D.js.map +1 -0
- package/dist/{chunk-ZPIVCM7Q.js → chunk-VK65RNFA.js} +3 -3
- package/dist/{chunk-VR34TXHF.js → chunk-VTF4RWS5.js} +6 -6
- package/dist/{chunk-4RXYFWP7.js → chunk-WLJVL25O.js} +4 -4
- package/dist/{chunk-GSZCQUCY.js → chunk-WNROH2YE.js} +2 -2
- package/dist/{chunk-RPXDBX6R.js → chunk-X6GY3CM2.js} +3 -3
- package/dist/{chunk-BMHGRYL6.js → chunk-YA6FWFYW.js} +22 -21
- package/dist/chunk-YA6FWFYW.js.map +1 -0
- package/dist/cli.js +46 -45
- package/dist/cli.js.map +1 -1
- package/dist/{config-UMP6DBJB.js → config-UE7IP36J.js} +11 -11
- package/dist/{conventions-DKH7AE4T.js → conventions-Z3QC2NSG.js} +13 -13
- package/dist/{diagnostics-GOGCLTRS.js → diagnostics-2Z5HFQOO.js} +6 -6
- package/dist/execute-QYLJ5L4T.js +36 -0
- package/dist/{generate-AGBT7EHX.js → generate-PB4L7V5N.js} +20 -20
- package/dist/{git-AC542GU2.js → git-7MUOGVRG.js} +2 -2
- package/dist/init-XBERAZ4O.js +24 -0
- package/dist/{operations-JQYK2EMT.js → operations-QDJAFZUN.js} +13 -13
- package/dist/{permissions-6HL52K54.js → permissions-XVXXENVP.js} +3 -3
- package/dist/plan-RXOW32KY.js +34 -0
- package/dist/{policy-IMRXGG7X.js → policy-6WQZB6B2.js} +12 -12
- package/dist/pr-3WFYAVA7.js +39 -0
- package/dist/pr-review-RK63ZEMM.js +32 -0
- package/dist/prd-LRV6IW3E.js +33 -0
- package/dist/{ps-KUUZ6TUL.js → ps-CX7756QG.js} +10 -10
- package/dist/{recorder-BJ2KIAWT.js → recorder-RK6XZYF3.js} +5 -5
- package/dist/{registry-SB7XTMGE.js → registry-POSB35Q4.js} +5 -5
- package/dist/{resume-QNLQSCR5.js → resume-OQJZV3JH.js} +37 -36
- package/dist/{resume-QNLQSCR5.js.map → resume-OQJZV3JH.js.map} +1 -1
- package/dist/review-AEFISJZD.js +35 -0
- package/dist/{routing-TN7GLMGU.js → routing-EA5NJGNU.js} +12 -12
- package/dist/run-HSC7R33Y.js +55 -0
- package/dist/{session-publisher-T7EI3CIP.js → session-publisher-VVXOCJ2S.js} +2 -2
- package/dist/{state-manager-ZEPWTCA6.js → state-manager-TIC73GQY.js} +3 -3
- package/dist/{usage-QL563I7F.js → usage-WDDNSIHP.js} +10 -10
- package/dist/{web-PPSMNTQ6.js → web-IPGY52AK.js} +17 -16
- package/dist/{web-PPSMNTQ6.js.map → web-IPGY52AK.js.map} +1 -1
- package/package.json +1 -1
- package/web/AGENTS.md +5 -0
- package/web/public/app.css +13 -0
- package/web/public/app.js +48 -0
- package/web/public/index.html +6 -2
- package/dist/chunk-6QYMSNVA.js.map +0 -1
- package/dist/chunk-BMHGRYL6.js.map +0 -1
- package/dist/chunk-HGSYD57B.js.map +0 -1
- package/dist/chunk-XDKGA6UX.js.map +0 -1
- package/dist/chunk-ZFIXEYUJ.js.map +0 -1
- package/dist/execute-E3BRMDAQ.js +0 -35
- package/dist/init-LLBVJNPE.js +0 -24
- package/dist/plan-HSLWDJEZ.js +0 -34
- package/dist/pr-6TCZXG6K.js +0 -39
- package/dist/pr-review-6MWJO2NP.js +0 -32
- package/dist/prd-D6YDYAFX.js +0 -33
- package/dist/review-3KJ4BH2V.js +0 -35
- package/dist/run-5EHIFF2V.js +0 -54
- /package/dist/{agent-AT64RXTA.js.map → agent-PMWCDWOW.js.map} +0 -0
- /package/dist/{analyze-O2PYDIFK.js.map → analyze-556KCJKZ.js.map} +0 -0
- /package/dist/{apply-SSU6QCD4.js.map → apply-WVV7G7WN.js.map} +0 -0
- /package/dist/{bench-ODFPAGW6.js.map → bench-UBFGKUHR.js.map} +0 -0
- /package/dist/{chunk-KAHQ6F5C.js.map → chunk-2H43OVTU.js.map} +0 -0
- /package/dist/{chunk-PCN7ZQND.js.map → chunk-4FUWWBTP.js.map} +0 -0
- /package/dist/{chunk-5ZKBIXET.js.map → chunk-73TS2H42.js.map} +0 -0
- /package/dist/{chunk-XLVGPXNL.js.map → chunk-AOAQU7PD.js.map} +0 -0
- /package/dist/{chunk-6E2PEBFT.js.map → chunk-BSBJXPLC.js.map} +0 -0
- /package/dist/{chunk-QRCGW2ZO.js.map → chunk-DBEMD34J.js.map} +0 -0
- /package/dist/{chunk-FK4IDY6P.js.map → chunk-EPFC4LHT.js.map} +0 -0
- /package/dist/{chunk-AYYMW33T.js.map → chunk-FKWYD44M.js.map} +0 -0
- /package/dist/{chunk-RWEXK55D.js.map → chunk-G5LACAXO.js.map} +0 -0
- /package/dist/{chunk-TBDP6QPJ.js.map → chunk-GPLP7EPL.js.map} +0 -0
- /package/dist/{chunk-ARWNYXFL.js.map → chunk-GV2DABNZ.js.map} +0 -0
- /package/dist/{chunk-XQHFC62U.js.map → chunk-GVOZ6NDO.js.map} +0 -0
- /package/dist/{chunk-F4OK7KEP.js.map → chunk-H3EYBJZU.js.map} +0 -0
- /package/dist/{chunk-3BWZO4YI.js.map → chunk-IAMIBZRF.js.map} +0 -0
- /package/dist/{chunk-WM3Y7Q4A.js.map → chunk-J2LJB2OH.js.map} +0 -0
- /package/dist/{chunk-MEFY4B7T.js.map → chunk-KV2K7MK2.js.map} +0 -0
- /package/dist/{chunk-XG2NYDRA.js.map → chunk-MCLPSL6B.js.map} +0 -0
- /package/dist/{chunk-ZXPASFHG.js.map → chunk-MXCOSPLJ.js.map} +0 -0
- /package/dist/{chunk-MROS3SKL.js.map → chunk-NQCR3HSU.js.map} +0 -0
- /package/dist/{chunk-2ZAM45PZ.js.map → chunk-OGN2NX57.js.map} +0 -0
- /package/dist/{chunk-A35HE4V7.js.map → chunk-OGVWJO4S.js.map} +0 -0
- /package/dist/{chunk-6SRE4KD7.js.map → chunk-OOX3RMD3.js.map} +0 -0
- /package/dist/{chunk-6FXOU2KR.js.map → chunk-PGN2VX4Y.js.map} +0 -0
- /package/dist/{chunk-XRLRKTQA.js.map → chunk-Q2TZA6AC.js.map} +0 -0
- /package/dist/{chunk-MGCOQU6N.js.map → chunk-QZOBR6YF.js.map} +0 -0
- /package/dist/{chunk-UNQYMR3H.js.map → chunk-R42YHLDV.js.map} +0 -0
- /package/dist/{chunk-NVZMBXTE.js.map → chunk-SVDEWOUD.js.map} +0 -0
- /package/dist/{chunk-ZPIVCM7Q.js.map → chunk-VK65RNFA.js.map} +0 -0
- /package/dist/{chunk-VR34TXHF.js.map → chunk-VTF4RWS5.js.map} +0 -0
- /package/dist/{chunk-4RXYFWP7.js.map → chunk-WLJVL25O.js.map} +0 -0
- /package/dist/{chunk-GSZCQUCY.js.map → chunk-WNROH2YE.js.map} +0 -0
- /package/dist/{chunk-RPXDBX6R.js.map → chunk-X6GY3CM2.js.map} +0 -0
- /package/dist/{config-UMP6DBJB.js.map → config-UE7IP36J.js.map} +0 -0
- /package/dist/{conventions-DKH7AE4T.js.map → conventions-Z3QC2NSG.js.map} +0 -0
- /package/dist/{diagnostics-GOGCLTRS.js.map → diagnostics-2Z5HFQOO.js.map} +0 -0
- /package/dist/{execute-E3BRMDAQ.js.map → execute-QYLJ5L4T.js.map} +0 -0
- /package/dist/{generate-AGBT7EHX.js.map → generate-PB4L7V5N.js.map} +0 -0
- /package/dist/{git-AC542GU2.js.map → git-7MUOGVRG.js.map} +0 -0
- /package/dist/{init-LLBVJNPE.js.map → init-XBERAZ4O.js.map} +0 -0
- /package/dist/{operations-JQYK2EMT.js.map → operations-QDJAFZUN.js.map} +0 -0
- /package/dist/{permissions-6HL52K54.js.map → permissions-XVXXENVP.js.map} +0 -0
- /package/dist/{plan-HSLWDJEZ.js.map → plan-RXOW32KY.js.map} +0 -0
- /package/dist/{policy-IMRXGG7X.js.map → policy-6WQZB6B2.js.map} +0 -0
- /package/dist/{pr-6TCZXG6K.js.map → pr-3WFYAVA7.js.map} +0 -0
- /package/dist/{pr-review-6MWJO2NP.js.map → pr-review-RK63ZEMM.js.map} +0 -0
- /package/dist/{prd-D6YDYAFX.js.map → prd-LRV6IW3E.js.map} +0 -0
- /package/dist/{ps-KUUZ6TUL.js.map → ps-CX7756QG.js.map} +0 -0
- /package/dist/{recorder-BJ2KIAWT.js.map → recorder-RK6XZYF3.js.map} +0 -0
- /package/dist/{registry-SB7XTMGE.js.map → registry-POSB35Q4.js.map} +0 -0
- /package/dist/{review-3KJ4BH2V.js.map → review-AEFISJZD.js.map} +0 -0
- /package/dist/{routing-TN7GLMGU.js.map → routing-EA5NJGNU.js.map} +0 -0
- /package/dist/{run-5EHIFF2V.js.map → run-HSC7R33Y.js.map} +0 -0
- /package/dist/{session-publisher-T7EI3CIP.js.map → session-publisher-VVXOCJ2S.js.map} +0 -0
- /package/dist/{state-manager-ZEPWTCA6.js.map → state-manager-TIC73GQY.js.map} +0 -0
- /package/dist/{usage-QL563I7F.js.map → usage-WDDNSIHP.js.map} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
publishIssueDetails,
|
|
4
|
+
publishStorySeed,
|
|
5
|
+
runPipeline
|
|
6
|
+
} from "./chunk-OLUOZLU5.js";
|
|
7
|
+
import "./chunk-VK65RNFA.js";
|
|
8
|
+
import "./chunk-Q2TZA6AC.js";
|
|
9
|
+
import "./chunk-OGN2NX57.js";
|
|
10
|
+
import "./chunk-EPFC4LHT.js";
|
|
11
|
+
import "./chunk-X6GY3CM2.js";
|
|
12
|
+
import "./chunk-KV2K7MK2.js";
|
|
13
|
+
import "./chunk-SW2MF47D.js";
|
|
14
|
+
import "./chunk-YA6FWFYW.js";
|
|
15
|
+
import "./chunk-QZOBR6YF.js";
|
|
16
|
+
import "./chunk-BSBJXPLC.js";
|
|
17
|
+
import "./chunk-CRGIQFUQ.js";
|
|
18
|
+
import "./chunk-L6HSKTQE.js";
|
|
19
|
+
import "./chunk-PGN2VX4Y.js";
|
|
20
|
+
import "./chunk-GV2DABNZ.js";
|
|
21
|
+
import "./chunk-2H43OVTU.js";
|
|
22
|
+
import "./chunk-WLJVL25O.js";
|
|
23
|
+
import "./chunk-DBEMD34J.js";
|
|
24
|
+
import "./chunk-MCLPSL6B.js";
|
|
25
|
+
import "./chunk-H3EYBJZU.js";
|
|
26
|
+
import "./chunk-KMMASOA6.js";
|
|
27
|
+
import "./chunk-4FUWWBTP.js";
|
|
28
|
+
import "./chunk-OGVWJO4S.js";
|
|
29
|
+
import "./chunk-73TS2H42.js";
|
|
30
|
+
import "./chunk-AOAQU7PD.js";
|
|
31
|
+
import "./chunk-IAMIBZRF.js";
|
|
32
|
+
import "./chunk-MXCOSPLJ.js";
|
|
33
|
+
import "./chunk-C5TL7T4V.js";
|
|
34
|
+
import "./chunk-3D4A4B46.js";
|
|
35
|
+
import "./chunk-OOX3RMD3.js";
|
|
36
|
+
import "./chunk-J2LJB2OH.js";
|
|
37
|
+
import "./chunk-VTF4RWS5.js";
|
|
38
|
+
import "./chunk-SVDEWOUD.js";
|
|
39
|
+
import "./chunk-ROIIEVZH.js";
|
|
40
|
+
import "./chunk-FKWYD44M.js";
|
|
41
|
+
import "./chunk-HAKDLR6N.js";
|
|
42
|
+
import "./chunk-WNROH2YE.js";
|
|
43
|
+
import "./chunk-GPLP7EPL.js";
|
|
44
|
+
import "./chunk-P3LRUNC6.js";
|
|
45
|
+
import "./chunk-R42YHLDV.js";
|
|
46
|
+
import "./chunk-GVOZ6NDO.js";
|
|
47
|
+
import "./chunk-QVLIIGBU.js";
|
|
48
|
+
import "./chunk-7Z4Y3VKM.js";
|
|
49
|
+
import "./chunk-XEYH6ZZI.js";
|
|
50
|
+
export {
|
|
51
|
+
publishIssueDetails,
|
|
52
|
+
publishStorySeed,
|
|
53
|
+
runPipeline
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=run-HSC7R33Y.js.map
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
getSessionPublisher,
|
|
4
4
|
setSessionPublisher
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-ROIIEVZH.js";
|
|
6
6
|
import "./chunk-P3LRUNC6.js";
|
|
7
7
|
export {
|
|
8
8
|
getSessionPublisher,
|
|
9
9
|
setSessionPublisher
|
|
10
10
|
};
|
|
11
|
-
//# sourceMappingURL=session-publisher-
|
|
11
|
+
//# sourceMappingURL=session-publisher-VVXOCJ2S.js.map
|
|
@@ -14,8 +14,8 @@ import {
|
|
|
14
14
|
selectActiveStory,
|
|
15
15
|
setLastError,
|
|
16
16
|
trimErrorMessage
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-GVOZ6NDO.js";
|
|
18
|
+
import "./chunk-QVLIIGBU.js";
|
|
19
19
|
import "./chunk-7Z4Y3VKM.js";
|
|
20
20
|
export {
|
|
21
21
|
allStoriesPass,
|
|
@@ -33,4 +33,4 @@ export {
|
|
|
33
33
|
setLastError,
|
|
34
34
|
trimErrorMessage
|
|
35
35
|
};
|
|
36
|
-
//# sourceMappingURL=state-manager-
|
|
36
|
+
//# sourceMappingURL=state-manager-TIC73GQY.js.map
|
|
@@ -5,26 +5,26 @@ import {
|
|
|
5
5
|
} from "./chunk-CRGIQFUQ.js";
|
|
6
6
|
import {
|
|
7
7
|
resolveProjectPaths
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VTF4RWS5.js";
|
|
9
9
|
import {
|
|
10
10
|
printError,
|
|
11
11
|
printInfo
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-SVDEWOUD.js";
|
|
13
|
+
import "./chunk-ROIIEVZH.js";
|
|
14
|
+
import "./chunk-FKWYD44M.js";
|
|
15
15
|
import {
|
|
16
16
|
getIssuePaths
|
|
17
17
|
} from "./chunk-HAKDLR6N.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-WNROH2YE.js";
|
|
19
19
|
import {
|
|
20
20
|
discardedExecutionCount
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-GPLP7EPL.js";
|
|
22
22
|
import "./chunk-P3LRUNC6.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-R42YHLDV.js";
|
|
24
24
|
import {
|
|
25
25
|
loadTaskPlan
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-
|
|
26
|
+
} from "./chunk-GVOZ6NDO.js";
|
|
27
|
+
import "./chunk-QVLIIGBU.js";
|
|
28
28
|
import "./chunk-7Z4Y3VKM.js";
|
|
29
29
|
import "./chunk-XEYH6ZZI.js";
|
|
30
30
|
|
|
@@ -174,4 +174,4 @@ export {
|
|
|
174
174
|
formatUsageReport,
|
|
175
175
|
runUsage
|
|
176
176
|
};
|
|
177
|
-
//# sourceMappingURL=usage-
|
|
177
|
+
//# sourceMappingURL=usage-WDDNSIHP.js.map
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
readSessionFile
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G5LACAXO.js";
|
|
5
5
|
import {
|
|
6
6
|
ensureSingleWebServer,
|
|
7
7
|
stopWebMonitor
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-YA6FWFYW.js";
|
|
9
|
+
import "./chunk-PGN2VX4Y.js";
|
|
10
|
+
import "./chunk-OGVWJO4S.js";
|
|
11
11
|
import {
|
|
12
12
|
parseJournal
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-IAMIBZRF.js";
|
|
14
|
+
import "./chunk-C5TL7T4V.js";
|
|
14
15
|
import {
|
|
15
16
|
loadWebConfig
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
18
|
-
import "./chunk-
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
17
|
+
} from "./chunk-J2LJB2OH.js";
|
|
18
|
+
import "./chunk-VTF4RWS5.js";
|
|
19
|
+
import "./chunk-SVDEWOUD.js";
|
|
20
|
+
import "./chunk-ROIIEVZH.js";
|
|
21
|
+
import "./chunk-FKWYD44M.js";
|
|
21
22
|
import {
|
|
22
23
|
EVENTS_FILENAME,
|
|
23
24
|
ISSUES_DIR_NAME,
|
|
@@ -26,12 +27,12 @@ import {
|
|
|
26
27
|
SESSION_FILENAME,
|
|
27
28
|
getGlobalRoot
|
|
28
29
|
} from "./chunk-HAKDLR6N.js";
|
|
29
|
-
import "./chunk-
|
|
30
|
-
import "./chunk-
|
|
30
|
+
import "./chunk-WNROH2YE.js";
|
|
31
|
+
import "./chunk-GPLP7EPL.js";
|
|
31
32
|
import "./chunk-P3LRUNC6.js";
|
|
32
|
-
import "./chunk-
|
|
33
|
-
import "./chunk-
|
|
34
|
-
import "./chunk-
|
|
33
|
+
import "./chunk-R42YHLDV.js";
|
|
34
|
+
import "./chunk-GVOZ6NDO.js";
|
|
35
|
+
import "./chunk-QVLIIGBU.js";
|
|
35
36
|
import "./chunk-7Z4Y3VKM.js";
|
|
36
37
|
import "./chunk-XEYH6ZZI.js";
|
|
37
38
|
|
|
@@ -166,4 +167,4 @@ export {
|
|
|
166
167
|
runWebServe,
|
|
167
168
|
runWebStop
|
|
168
169
|
};
|
|
169
|
-
//# sourceMappingURL=web-
|
|
170
|
+
//# sourceMappingURL=web-IPGY52AK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web/session-directory.ts","../src/commands/web.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type JournalEntry, parseJournal } from '../core/journal.js';\nimport type { ValidatedSessionSnapshot } from '../schemas.js';\nimport {\n EVENTS_FILENAME,\n type GetGlobalRootOptions,\n getGlobalRoot,\n ISSUES_DIR_NAME,\n PROJECTS_DIR_NAME,\n ROTATED_EVENTS_FILENAME,\n SESSION_FILENAME,\n} from '../storage/paths.js';\nimport { readSessionFile } from '../storage/session-file.js';\n\n/**\n * Multi-session discovery for the web monitoring server (US-003).\n *\n * The single-instance server (`web/lock.ts`) runs decoupled from any one\n * pipeline invocation, so it cannot hold a `SessionPublisher` in memory for\n * \"the\" run being monitored — there may be zero, one or several running at\n * once, each in its own process. Instead this module polls every\n * `~/.issue-flow/projects/<project>/issues/<n>/session.json` on disk (the\n * same file `FilePublisher` already writes) and keeps an in-memory map of the\n * ones that are both well-formed and recently updated.\n *\n * Polling rather than `fs.watch` is a deliberate choice, not a placeholder:\n * `fs.watch`'s `recursive` option is only reliable on macOS and Windows (Linux\n * needs a manual per-directory watch tree, and the whole thing still degrades\n * to polling on network filesystems), while a `~/.issue-flow` tree is small\n * and local — a cheap poll of it is simpler and behaves identically on every\n * platform, which is what the US-003 acceptance criteria ask to validate.\n */\n\n/** How often the directory tree is rescanned, unless overridden. */\nexport const DEFAULT_POLL_INTERVAL_MS = 3000;\n\n/**\n * A session stops being reported once its file has gone this long without an\n * update. `FilePublisher` touches a live session every 10s without rewriting\n * its content. Three missed heartbeats plus ample scheduling/filesystem slack\n * avoid flapping while still removing an abruptly killed run promptly.\n */\nexport const DEFAULT_STALE_AFTER_MS = 90_000;\n\nexport interface ActiveSession {\n /** Directory the session.json was read from, one level up from the file. */\n issueDir: string;\n filePath: string;\n snapshot: ValidatedSessionSnapshot;\n /** mtime of the file at the last successful read, in epoch ms. */\n updatedAtMs: number;\n}\n\nexport interface SessionDirectoryOptions extends GetGlobalRootOptions {\n /** Poll interval, in ms. Default {@link DEFAULT_POLL_INTERVAL_MS}. */\n pollIntervalMs?: number;\n /** Staleness threshold, in ms. Default {@link DEFAULT_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Invoked (at most once) when a poll fails unexpectedly. Best-effort logging only. */\n onWarn?: (message: string) => void;\n}\n\nexport interface SessionDirectoryHandle {\n /** Snapshot of every session currently considered active. */\n sessions(): ActiveSession[];\n /** A single active session by id, or undefined. */\n getSession(sessionId: string): ActiveSession | undefined;\n /** Journal entries for one active session, oldest generation first. */\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n /** Force an immediate rescan instead of waiting for the next tick. Never throws. */\n refresh(): Promise<void>;\n /** Stop polling. Idempotent. */\n close(): void;\n}\n\nasync function listSubdirectories(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n // Symlinks are skipped on purpose, same discipline as the storage\n // migration walk: following one could pull session data from outside the\n // global tree into the listing.\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);\n } catch {\n // Missing/unreadable projects (or issues) directory reads as \"none yet\" —\n // this is routinely the case before the first --web run ever happens.\n return [];\n }\n}\n\n/** Every `session.json` path currently on disk, one per project/issue pair. */\nasync function discoverSessionFiles(root: string): Promise<string[]> {\n const projectsDir = join(root, PROJECTS_DIR_NAME);\n const projectIds = await listSubdirectories(projectsDir);\n\n const perProject = await Promise.all(\n projectIds.map(async (projectId) => {\n const issuesDir = join(projectsDir, projectId, ISSUES_DIR_NAME);\n const issueIds = await listSubdirectories(issuesDir);\n return issueIds.map((issueId) => join(issuesDir, issueId, SESSION_FILENAME));\n }),\n );\n\n return perProject.flat();\n}\n\nasync function readJournalFile(filePath: string): Promise<JournalEntry[]> {\n try {\n return parseJournal(await readFile(filePath, 'utf-8'));\n } catch {\n return [];\n }\n}\n\n/**\n * Start polling the global storage tree for active sessions.\n *\n * Returns a handle immediately; the first scan happens synchronously in the\n * background and `sessions()` answers `[]` until it completes, same as \"no\n * sessions found yet\" — there is nothing to await here that would not also\n * delay the server's very first bind.\n */\nexport function watchSessionDirectory(\n options: SessionDirectoryOptions = {},\n): SessionDirectoryHandle {\n const root = getGlobalRoot(options);\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n const warn = options.onWarn;\n\n // Keyed by sessionId, not by file path: a session's identity is the id\n // inside its content, and that is what /api/status?session=<id> looks up.\n let sessions = new Map<string, ActiveSession>();\n let warned = false;\n\n async function scan(): Promise<void> {\n let files: string[];\n try {\n files = await discoverSessionFiles(root);\n } catch (err) {\n if (!warned) {\n warned = true;\n warn?.(\n `issue-flow: web monitor could not scan the session directory (will keep retrying silently): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n return;\n }\n\n const now = Date.now();\n const next = new Map<string, ActiveSession>();\n for (const filePath of files) {\n const result = await readSessionFile(filePath);\n if (result === null) continue;\n if (now - result.updatedAtMs > staleAfterMs) continue; // stopped updating: not active\n const sessionId = result.snapshot.sessionId as string;\n next.set(sessionId, {\n issueDir: join(filePath, '..'),\n filePath,\n snapshot: result.snapshot,\n updatedAtMs: result.updatedAtMs,\n });\n }\n sessions = next;\n }\n\n // The first scan runs immediately (not just after the first interval tick)\n // so a server started right after a session begins does not report \"no\n // sessions\" for a whole poll interval.\n void scan();\n const timer = setInterval(() => void scan(), pollIntervalMs);\n timer.unref();\n\n return {\n sessions: () => [...sessions.values()],\n getSession: (sessionId: string) => sessions.get(sessionId),\n events: async (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const [rotated, current] = await Promise.all([\n readJournalFile(join(session.issueDir, ROTATED_EVENTS_FILENAME)),\n readJournalFile(join(session.issueDir, EVENTS_FILENAME)),\n ]);\n return [...rotated, ...current];\n },\n refresh: scan,\n close: () => clearInterval(timer),\n };\n}\n","import { loadWebConfig } from '../config.js';\nimport type { WebConfig } from '../schemas.js';\nimport { ensureSingleWebServer, stopWebMonitor } from '../web/lock.js';\nimport { watchSessionDirectory } from '../web/session-directory.js';\n\n/**\n * `issue-flow web serve` / `issue-flow web stop` (US-002).\n *\n * `serve` is the process `ensureWebMonitor()` (`web/lock.ts`) spawns detached\n * — it is not meant to be run interactively, though nothing stops a user from\n * doing so to watch every session on the machine without going through a\n * pipeline command first. It never exits on its own: the server stays bound\n * (see `unref: false` in `web/server.ts`) until `stop` sends it `SIGTERM`, at\n * which point `startWebServer`'s own signal handler closes it (removing the\n * lock) and re-raises the signal for the default termination behavior.\n */\n\nexport interface RunWebServeOptions {\n port?: number;\n host?: string;\n refresh?: number;\n}\n\n/**\n * Bind (or defer to) the single web monitor instance and keep the process\n * alive for as long as it stays bound. Logging is intentionally silent on the\n * happy path: this process is spawned with `stdio: 'ignore'`, so anything\n * printed here goes nowhere — the caller (`ensureWebMonitor`) is the one that\n * tells the user the server is up.\n */\nexport async function runWebServe(options: RunWebServeOptions): Promise<number> {\n // Built conditionally, not `{ port: options.port, ... }`: loadWebConfig()\n // spreads this object directly over the lower-precedence layers, and an\n // explicit `undefined` value would overwrite an env/.issue-flow.json\n // setting instead of falling through to it (only an *absent* key does).\n const cli: Partial<WebConfig> = {};\n if (options.port !== undefined) cli.port = options.port;\n if (options.host !== undefined) cli.host = options.host;\n if (options.refresh !== undefined) cli.refreshSeconds = options.refresh;\n const webConfig = await loadWebConfig({ cli });\n\n const sessions = watchSessionDirectory();\n\n const noop = (): void => {};\n const handle = await ensureSingleWebServer({\n sessions,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n unref: false,\n info: noop,\n warn: noop,\n });\n\n if (handle === null) {\n sessions.close();\n return 1;\n }\n\n if (handle.server === undefined) {\n // Another instance already won the race (or was already running by the\n // time this process got here): nothing to serve, so it exits right away\n // instead of idling as a redundant detached process.\n sessions.close();\n return 0;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n sessions.close();\n };\n\n return 0;\n}\n\n/** Stop the single running web monitor instance, if any. */\nexport async function runWebStop(): Promise<number> {\n const webConfig = await loadWebConfig();\n const result = await stopWebMonitor({ port: webConfig.port, host: webConfig.host });\n return result === 'failed' || result === 'unowned' ? 1 : 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/web/session-directory.ts","../src/commands/web.ts"],"sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { type JournalEntry, parseJournal } from '../core/journal.js';\nimport type { ValidatedSessionSnapshot } from '../schemas.js';\nimport {\n EVENTS_FILENAME,\n type GetGlobalRootOptions,\n getGlobalRoot,\n ISSUES_DIR_NAME,\n PROJECTS_DIR_NAME,\n ROTATED_EVENTS_FILENAME,\n SESSION_FILENAME,\n} from '../storage/paths.js';\nimport { readSessionFile } from '../storage/session-file.js';\n\n/**\n * Multi-session discovery for the web monitoring server (US-003).\n *\n * The single-instance server (`web/lock.ts`) runs decoupled from any one\n * pipeline invocation, so it cannot hold a `SessionPublisher` in memory for\n * \"the\" run being monitored — there may be zero, one or several running at\n * once, each in its own process. Instead this module polls every\n * `~/.issue-flow/projects/<project>/issues/<n>/session.json` on disk (the\n * same file `FilePublisher` already writes) and keeps an in-memory map of the\n * ones that are both well-formed and recently updated.\n *\n * Polling rather than `fs.watch` is a deliberate choice, not a placeholder:\n * `fs.watch`'s `recursive` option is only reliable on macOS and Windows (Linux\n * needs a manual per-directory watch tree, and the whole thing still degrades\n * to polling on network filesystems), while a `~/.issue-flow` tree is small\n * and local — a cheap poll of it is simpler and behaves identically on every\n * platform, which is what the US-003 acceptance criteria ask to validate.\n */\n\n/** How often the directory tree is rescanned, unless overridden. */\nexport const DEFAULT_POLL_INTERVAL_MS = 3000;\n\n/**\n * A session stops being reported once its file has gone this long without an\n * update. `FilePublisher` touches a live session every 10s without rewriting\n * its content. Three missed heartbeats plus ample scheduling/filesystem slack\n * avoid flapping while still removing an abruptly killed run promptly.\n */\nexport const DEFAULT_STALE_AFTER_MS = 90_000;\n\nexport interface ActiveSession {\n /** Directory the session.json was read from, one level up from the file. */\n issueDir: string;\n filePath: string;\n snapshot: ValidatedSessionSnapshot;\n /** mtime of the file at the last successful read, in epoch ms. */\n updatedAtMs: number;\n}\n\nexport interface SessionDirectoryOptions extends GetGlobalRootOptions {\n /** Poll interval, in ms. Default {@link DEFAULT_POLL_INTERVAL_MS}. */\n pollIntervalMs?: number;\n /** Staleness threshold, in ms. Default {@link DEFAULT_STALE_AFTER_MS}. */\n staleAfterMs?: number;\n /** Invoked (at most once) when a poll fails unexpectedly. Best-effort logging only. */\n onWarn?: (message: string) => void;\n}\n\nexport interface SessionDirectoryHandle {\n /** Snapshot of every session currently considered active. */\n sessions(): ActiveSession[];\n /** A single active session by id, or undefined. */\n getSession(sessionId: string): ActiveSession | undefined;\n /** Journal entries for one active session, oldest generation first. */\n events(sessionId: string): Promise<JournalEntry[] | undefined>;\n /** Force an immediate rescan instead of waiting for the next tick. Never throws. */\n refresh(): Promise<void>;\n /** Stop polling. Idempotent. */\n close(): void;\n}\n\nasync function listSubdirectories(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n // Symlinks are skipped on purpose, same discipline as the storage\n // migration walk: following one could pull session data from outside the\n // global tree into the listing.\n return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);\n } catch {\n // Missing/unreadable projects (or issues) directory reads as \"none yet\" —\n // this is routinely the case before the first --web run ever happens.\n return [];\n }\n}\n\n/** Every `session.json` path currently on disk, one per project/issue pair. */\nasync function discoverSessionFiles(root: string): Promise<string[]> {\n const projectsDir = join(root, PROJECTS_DIR_NAME);\n const projectIds = await listSubdirectories(projectsDir);\n\n const perProject = await Promise.all(\n projectIds.map(async (projectId) => {\n const issuesDir = join(projectsDir, projectId, ISSUES_DIR_NAME);\n const issueIds = await listSubdirectories(issuesDir);\n return issueIds.map((issueId) => join(issuesDir, issueId, SESSION_FILENAME));\n }),\n );\n\n return perProject.flat();\n}\n\nasync function readJournalFile(filePath: string): Promise<JournalEntry[]> {\n try {\n return parseJournal(await readFile(filePath, 'utf-8'));\n } catch {\n return [];\n }\n}\n\n/**\n * Start polling the global storage tree for active sessions.\n *\n * Returns a handle immediately; the first scan happens synchronously in the\n * background and `sessions()` answers `[]` until it completes, same as \"no\n * sessions found yet\" — there is nothing to await here that would not also\n * delay the server's very first bind.\n */\nexport function watchSessionDirectory(\n options: SessionDirectoryOptions = {},\n): SessionDirectoryHandle {\n const root = getGlobalRoot(options);\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const staleAfterMs = options.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;\n const warn = options.onWarn;\n\n // Keyed by sessionId, not by file path: a session's identity is the id\n // inside its content, and that is what /api/status?session=<id> looks up.\n let sessions = new Map<string, ActiveSession>();\n let warned = false;\n\n async function scan(): Promise<void> {\n let files: string[];\n try {\n files = await discoverSessionFiles(root);\n } catch (err) {\n if (!warned) {\n warned = true;\n warn?.(\n `issue-flow: web monitor could not scan the session directory (will keep retrying silently): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n return;\n }\n\n const now = Date.now();\n const next = new Map<string, ActiveSession>();\n for (const filePath of files) {\n const result = await readSessionFile(filePath);\n if (result === null) continue;\n if (now - result.updatedAtMs > staleAfterMs) continue; // stopped updating: not active\n const sessionId = result.snapshot.sessionId as string;\n next.set(sessionId, {\n issueDir: join(filePath, '..'),\n filePath,\n snapshot: result.snapshot,\n updatedAtMs: result.updatedAtMs,\n });\n }\n sessions = next;\n }\n\n // The first scan runs immediately (not just after the first interval tick)\n // so a server started right after a session begins does not report \"no\n // sessions\" for a whole poll interval.\n void scan();\n const timer = setInterval(() => void scan(), pollIntervalMs);\n timer.unref();\n\n return {\n sessions: () => [...sessions.values()],\n getSession: (sessionId: string) => sessions.get(sessionId),\n events: async (sessionId: string) => {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const [rotated, current] = await Promise.all([\n readJournalFile(join(session.issueDir, ROTATED_EVENTS_FILENAME)),\n readJournalFile(join(session.issueDir, EVENTS_FILENAME)),\n ]);\n return [...rotated, ...current];\n },\n refresh: scan,\n close: () => clearInterval(timer),\n };\n}\n","import { loadWebConfig } from '../config.js';\nimport type { WebConfig } from '../schemas.js';\nimport { ensureSingleWebServer, stopWebMonitor } from '../web/lock.js';\nimport { watchSessionDirectory } from '../web/session-directory.js';\n\n/**\n * `issue-flow web serve` / `issue-flow web stop` (US-002).\n *\n * `serve` is the process `ensureWebMonitor()` (`web/lock.ts`) spawns detached\n * — it is not meant to be run interactively, though nothing stops a user from\n * doing so to watch every session on the machine without going through a\n * pipeline command first. It never exits on its own: the server stays bound\n * (see `unref: false` in `web/server.ts`) until `stop` sends it `SIGTERM`, at\n * which point `startWebServer`'s own signal handler closes it (removing the\n * lock) and re-raises the signal for the default termination behavior.\n */\n\nexport interface RunWebServeOptions {\n port?: number;\n host?: string;\n refresh?: number;\n}\n\n/**\n * Bind (or defer to) the single web monitor instance and keep the process\n * alive for as long as it stays bound. Logging is intentionally silent on the\n * happy path: this process is spawned with `stdio: 'ignore'`, so anything\n * printed here goes nowhere — the caller (`ensureWebMonitor`) is the one that\n * tells the user the server is up.\n */\nexport async function runWebServe(options: RunWebServeOptions): Promise<number> {\n // Built conditionally, not `{ port: options.port, ... }`: loadWebConfig()\n // spreads this object directly over the lower-precedence layers, and an\n // explicit `undefined` value would overwrite an env/.issue-flow.json\n // setting instead of falling through to it (only an *absent* key does).\n const cli: Partial<WebConfig> = {};\n if (options.port !== undefined) cli.port = options.port;\n if (options.host !== undefined) cli.host = options.host;\n if (options.refresh !== undefined) cli.refreshSeconds = options.refresh;\n const webConfig = await loadWebConfig({ cli });\n\n const sessions = watchSessionDirectory();\n\n const noop = (): void => {};\n const handle = await ensureSingleWebServer({\n sessions,\n port: webConfig.port,\n host: webConfig.host,\n refreshSeconds: webConfig.refreshSeconds,\n unref: false,\n info: noop,\n warn: noop,\n });\n\n if (handle === null) {\n sessions.close();\n return 1;\n }\n\n if (handle.server === undefined) {\n // Another instance already won the race (or was already running by the\n // time this process got here): nothing to serve, so it exits right away\n // instead of idling as a redundant detached process.\n sessions.close();\n return 0;\n }\n\n const originalClose = handle.close;\n handle.close = async () => {\n await originalClose();\n sessions.close();\n };\n\n return 0;\n}\n\n/** Stop the single running web monitor instance, if any. */\nexport async function runWebStop(): Promise<number> {\n const webConfig = await loadWebConfig();\n const result = await stopWebMonitor({ port: webConfig.port, host: webConfig.host });\n return result === 'failed' || result === 'unowned' ? 1 : 0;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
package/web/AGENTS.md
CHANGED
|
@@ -18,6 +18,11 @@ informado). Os dois significam "não informado" e nunca podem virar `0`,
|
|
|
18
18
|
qualquer coisa que não seja número finito para `null`. Prefira
|
|
19
19
|
`x !== null && x !== undefined` a `!x`: zero é um valor legítimo.
|
|
20
20
|
|
|
21
|
+
O chip de versão no header é a versão do **monitor** (`/api/health`), não a da
|
|
22
|
+
CLI que executa o pipeline: os assets desta página vêm da memória daquele
|
|
23
|
+
processo, então é ela que explica o que está na tela. As duas aparecem juntas no
|
|
24
|
+
card de configuração, e a divergência entre elas vira um aviso ali.
|
|
25
|
+
|
|
21
26
|
Toda resposta nova carrega `X-Issue-Flow-Instance`. O client guarda a primeira
|
|
22
27
|
identidade observada e chama `window.location.reload()` quando ela muda: isso é
|
|
23
28
|
o handoff de assets depois de `--restart-web`, não um estado de sessão.
|
package/web/public/app.css
CHANGED
|
@@ -266,6 +266,19 @@ a:hover {
|
|
|
266
266
|
font-weight: 700;
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/* Versão do processo que serve o painel. Par medido: --text-muted sobre
|
|
270
|
+
--surface-sunken (6,10 claro / 5,33 escuro). */
|
|
271
|
+
.app-version {
|
|
272
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
273
|
+
font-size: 0.72rem;
|
|
274
|
+
font-weight: 500;
|
|
275
|
+
padding: 1px 7px;
|
|
276
|
+
border-radius: 999px;
|
|
277
|
+
background: var(--surface-sunken);
|
|
278
|
+
color: var(--text-muted);
|
|
279
|
+
vertical-align: middle;
|
|
280
|
+
}
|
|
281
|
+
|
|
269
282
|
.issue-link {
|
|
270
283
|
font-weight: 600;
|
|
271
284
|
}
|
package/web/public/app.js
CHANGED
|
@@ -89,6 +89,8 @@
|
|
|
89
89
|
now: document.getElementById('now'),
|
|
90
90
|
resilience: document.getElementById('resilience'),
|
|
91
91
|
configuration: document.getElementById('configuration'),
|
|
92
|
+
appVersion: document.getElementById('app-version'),
|
|
93
|
+
appVersionDashboard: document.getElementById('app-version-dashboard'),
|
|
92
94
|
phases: document.getElementById('phases'),
|
|
93
95
|
nextSteps: document.getElementById('next-steps'),
|
|
94
96
|
stories: document.getElementById('stories'),
|
|
@@ -130,6 +132,8 @@
|
|
|
130
132
|
// Identifica o processo que serviu os assets atuais. Uma troca significa
|
|
131
133
|
// que --restart-web colocou código novo no mesmo origin e exige reload.
|
|
132
134
|
serverInstanceId: null,
|
|
135
|
+
// Versão do monitor, vinda de /api/health. Null enquanto não respondeu.
|
|
136
|
+
monitorVersion: null,
|
|
133
137
|
// Multi-sessão (#35): lista de /api/sessions e seleção explícita do usuário.
|
|
134
138
|
// selectedSessionId null = modo automático (1 sessão → detalhe; 2+ → dashboard).
|
|
135
139
|
sessions: [],
|
|
@@ -531,6 +535,21 @@
|
|
|
531
535
|
state.eventsUrl = null;
|
|
532
536
|
}
|
|
533
537
|
|
|
538
|
+
// A versão exibida no header é a do processo que serviu esta página, nunca a
|
|
539
|
+
// da CLI que iniciou a execução: os assets vivem na memória desse processo,
|
|
540
|
+
// então é ela que explica o que está na tela. As duas aparecem juntas no card
|
|
541
|
+
// de configuração quando divergem.
|
|
542
|
+
function renderMonitorVersion(version) {
|
|
543
|
+
const label = typeof version === 'string' && version !== '' ? 'v' + version : null;
|
|
544
|
+
state.monitorVersion = label === null ? null : version;
|
|
545
|
+
for (const node of [els.appVersion, els.appVersionDashboard]) {
|
|
546
|
+
if (!node) continue;
|
|
547
|
+
node.textContent = label || '';
|
|
548
|
+
node.title = label === null ? '' : 'Versão do monitor que serve este painel';
|
|
549
|
+
node.hidden = label === null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
534
553
|
function serverInstanceChanged(response) {
|
|
535
554
|
const instanceId = response.headers.get('X-Issue-Flow-Instance');
|
|
536
555
|
if (!instanceId) return false; // servidor anterior à identidade de instância
|
|
@@ -1050,6 +1069,34 @@
|
|
|
1050
1069
|
|
|
1051
1070
|
function renderConfiguration(snapshot) {
|
|
1052
1071
|
clear(els.configuration);
|
|
1072
|
+
|
|
1073
|
+
// Quem executou e quem está mostrando. São processos diferentes e podem
|
|
1074
|
+
// estar em versões diferentes; este é o único lugar onde as duas aparecem
|
|
1075
|
+
// lado a lado, então a divergência é dita aqui.
|
|
1076
|
+
const env = snapshot.environment;
|
|
1077
|
+
const runVersion = env && env.cliVersion ? env.cliVersion : null;
|
|
1078
|
+
const runtime = el('dl', 'now-grid');
|
|
1079
|
+
const runtimeBits = [runVersion ? 'v' + runVersion : 'versão não registrada'];
|
|
1080
|
+
if (env && env.node) runtimeBits.push(env.node);
|
|
1081
|
+
if (env && env.platform) runtimeBits.push(env.platform);
|
|
1082
|
+
nowRow(runtime, 'Issue Flow (execução)', runtimeBits.join(' · '));
|
|
1083
|
+
nowRow(
|
|
1084
|
+
runtime,
|
|
1085
|
+
'Monitor (este painel)',
|
|
1086
|
+
state.monitorVersion ? 'v' + state.monitorVersion : '—',
|
|
1087
|
+
);
|
|
1088
|
+
els.configuration.appendChild(runtime);
|
|
1089
|
+
if (runVersion !== null && state.monitorVersion && runVersion !== state.monitorVersion) {
|
|
1090
|
+
els.configuration.appendChild(
|
|
1091
|
+
el(
|
|
1092
|
+
'p',
|
|
1093
|
+
'alert-entry level-warn',
|
|
1094
|
+
'Este painel é servido por uma versão diferente da que executa o pipeline. ' +
|
|
1095
|
+
'Reinicie o monitor com --restart-web para ver a interface desta versão.',
|
|
1096
|
+
),
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1053
1100
|
const config = snapshot.configuration;
|
|
1054
1101
|
if (!config) {
|
|
1055
1102
|
els.configuration.appendChild(el('p', 'empty', 'Configuração não capturada nesta execução.'));
|
|
@@ -1743,6 +1790,7 @@
|
|
|
1743
1790
|
const healthRes = await fetch('api/health', { cache: 'no-store' });
|
|
1744
1791
|
serverInstanceChanged(healthRes);
|
|
1745
1792
|
const health = await healthRes.json();
|
|
1793
|
+
renderMonitorVersion(health.version);
|
|
1746
1794
|
state.configWritable = list(health.capabilities).includes('config:agent:write');
|
|
1747
1795
|
const suggested = Number(health.refreshSeconds);
|
|
1748
1796
|
if (stored === null && Number.isFinite(suggested) && suggested > 0) {
|
package/web/public/index.html
CHANGED
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
<div id="view-dashboard" class="view" hidden>
|
|
38
38
|
<header class="card header">
|
|
39
39
|
<div class="header-main">
|
|
40
|
-
<h1
|
|
40
|
+
<h1>
|
|
41
|
+
<span class="brand">issue-flow</span>
|
|
42
|
+
<span id="app-version-dashboard" class="app-version" hidden></span>
|
|
43
|
+
</h1>
|
|
41
44
|
<p class="muted">Execuções ativas</p>
|
|
42
45
|
</div>
|
|
43
46
|
<div class="header-side">
|
|
@@ -69,6 +72,7 @@
|
|
|
69
72
|
</button>
|
|
70
73
|
<h1>
|
|
71
74
|
<span class="brand">issue-flow</span>
|
|
75
|
+
<span id="app-version" class="app-version" hidden></span>
|
|
72
76
|
<a id="issue-link" class="issue-link" target="_blank" rel="noopener" hidden>#—</a>
|
|
73
77
|
</h1>
|
|
74
78
|
<p id="branch-line" class="muted mono"></p>
|
|
@@ -142,7 +146,7 @@
|
|
|
142
146
|
<section class="card">
|
|
143
147
|
<div class="section-head">
|
|
144
148
|
<h2>Harnesses e configuração efetiva</h2>
|
|
145
|
-
<span class="muted">preferências futuras e overrides desta execução</span>
|
|
149
|
+
<span class="muted">versões, preferências futuras e overrides desta execução</span>
|
|
146
150
|
</div>
|
|
147
151
|
<div id="configuration"></div>
|
|
148
152
|
</section>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/session-state.ts","../src/core/session-publisher.ts"],"sourcesContent":["import { mkdir, rename, utimes, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { stripVTControlCharacters } from 'node:util';\nimport type { FailureKind } from '../resilience/errors.js';\nimport { setTelemetrySessionId } from '../telemetry/session-id.js';\nimport type { ExecutionRecord } from '../telemetry/types.js';\nimport type { StoryStage, UserStory, UserStoryStatus } from '../types.js';\n\n/**\n * Session state publishing layer for the optional web monitoring mode.\n *\n * Instrumentation points call publish() with SessionEvents; a pure reducer\n * folds each event into an in-memory SessionSnapshot that any surface\n * (file, HTTP endpoint, future webhooks) can consume in a single format.\n *\n * Monitoring must never affect the pipeline: publish() is synchronous and\n * never throws, and all publisher I/O failures are swallowed after a\n * single warning.\n */\n\nexport type SessionLogLevel = 'info' | 'warn' | 'error';\nexport type SessionStatus = 'idle' | 'running' | 'completed' | 'failed';\nexport type SessionPhaseStatus = 'pending' | 'running' | 'completed' | 'failed';\n\n/** Default max entries retained in the logs ring buffer. */\nexport const DEFAULT_LOG_LIMIT = 200;\n\n/** Default minimum interval between FilePublisher disk writes. */\nexport const DEFAULT_THROTTLE_MS = 1000;\n\n/** Default interval for touching a live session file without rewriting it. */\nexport const DEFAULT_SESSION_HEARTBEAT_MS = 10_000;\n\nexport type SessionEvent =\n | {\n type: 'session:start';\n at: string;\n sessionId: string;\n /** `null` for local identifiers that are not numbers. */\n issueNumber: number | null;\n issueUrl?: string;\n branch?: string;\n baseBranch?: string;\n phases: string[];\n environment?: SessionEnvironment;\n configuration?: SessionConfigurationSnapshot;\n branchCreated?: boolean | null;\n startCommit?: string | null;\n }\n | {\n /**\n * Structural data of the Issue being worked on, published once the\n * provider has resolved it. Merged over the `issue` section instead of\n * replacing it, so the number and the URL that came with `session:start`\n * survive an origin that reports neither.\n */\n type: 'issue:update';\n at: string;\n number: number | null;\n url?: string;\n title: string | null;\n /** Issue body, published whole — no truncation. */\n description: string | null;\n labels: string[];\n state: string | null;\n }\n | { type: 'phase:start'; at: string; phase: string }\n | {\n type: 'phase:end';\n at: string;\n phase: string;\n success: boolean;\n error?: string;\n harnessExecutionMs?: number | null;\n orchestrationOverheadMs?: number | null;\n harnessStartupMs?: number | null;\n ttftMs?: number | null;\n attemptCount?: number | null;\n retryDurationMs?: number | null;\n }\n | {\n type: 'iteration:start';\n at: string;\n iteration: number;\n /**\n * Id of the story `execute` is about to work on, computed by\n * `core/engine.ts` with the same \"highest priority, `passes: false`\"\n * rule `prompts/execute.md` gives the agent. Optional so a caller that\n * cannot determine it (or an older build) is still a valid event —\n * `applyEvent` simply skips the `executing`/`pending` transition then.\n */\n storyId?: string;\n }\n | { type: 'iteration:end'; at: string; iteration: number }\n | {\n type: 'retry';\n at: string;\n attempt: number;\n delaySeconds?: number;\n reason?: string;\n /**\n * What the resilience layer classified the failure as. Optional so an\n * event written by an older build — or by a caller with nothing but a\n * message — stays valid; the reducer only counts retries either way.\n */\n kind?: FailureKind;\n }\n | {\n type: 'agent:attempt';\n at: string;\n attempt: number;\n provider: string;\n model?: string | null;\n primaryProvider: string;\n }\n | {\n type: 'failover';\n at: string;\n from: string;\n to: string;\n reason: FailureKind | null;\n cooldownUntil?: string | null;\n }\n | {\n type: 'agent:result';\n at: string;\n provider: string;\n success: boolean;\n failureKind?: FailureKind;\n cooldownUntil?: string | null;\n }\n | { type: 'agent:activity'; at: string; provider: string }\n | { type: 'stories:update'; at: string; stories: UserStory[] }\n | { type: 'activity'; at: string; story?: string; tool?: string; detail?: string }\n | { type: 'log'; at: string; level: SessionLogLevel; message: string }\n | {\n type: 'process:output';\n at: string;\n phase: string;\n executionId: string | null;\n provider: string;\n stream: 'stdout' | 'stderr' | 'combined';\n message: string;\n }\n | { type: 'execution:update'; at: string; execution: ExecutionRecord }\n | { type: 'correction:cycle'; at: string; cycle: number; maxCycles: number }\n | {\n type: 'metrics:update';\n at: string;\n /**\n * Where the metrics land:\n * - `phase`: the named phase plus the issue-wide aggregate;\n * - `iteration`: one execute-loop pass — same targets as `phase`;\n * - `story`: the story alone, never the phase nor the aggregate (the\n * iteration event of the same cycle already counted those tokens).\n */\n scope: 'phase' | 'iteration' | 'story';\n phase?: string;\n storyId?: string;\n iteration?: number;\n inputTokens?: number;\n outputTokens?: number;\n cacheReadTokens?: number;\n cacheCreationTokens?: number;\n costUsd?: number;\n durationSeconds?: number;\n }\n | {\n /**\n * One publication feeds both the `git` section (branch, base, commits)\n * and the `repository` section (identity and location). They come from\n * the same collection pass, so extending this event keeps `branch`\n * consistent across the two instead of racing a second event.\n *\n * Every field is optional: `undefined` means \"not collected in this\n * publication\" and leaves the snapshot untouched, while an explicit\n * `null` means \"collected and unavailable\" and is written as-is.\n */\n type: 'git:update';\n at: string;\n branch?: string;\n baseBranch?: string;\n branchCreated?: boolean | null;\n commits?: SessionCommit[];\n pullRequests?: SessionPullRequest[];\n /** `owner/repo`, derived from the origin remote. */\n repositoryName?: string | null;\n remoteUrl?: string | null;\n headCommit?: string | null;\n repositoryRoot?: string | null;\n }\n | { type: 'session:end'; at: string; status: 'completed' | 'failed'; error?: string }\n | {\n type: 'verify:end';\n at: string;\n verdict: 'passed' | 'failed' | 'unverified';\n level: string;\n independence: string | null;\n executionId: string | null;\n };\n\nexport interface SessionEnvironment {\n node: string;\n platform: string;\n /** Winning agent provider for the run. `null` on snapshots from earlier releases. */\n agent: string | null;\n /** Winning model for the run. `null` when unset or on older snapshots. */\n model: string | null;\n}\n\nexport interface SessionLogEntry {\n at: string;\n level: SessionLogLevel;\n message: string;\n}\n\nexport interface SessionProcessLogEntry {\n at: string;\n phase: string;\n executionId: string | null;\n provider: string;\n stream: 'stdout' | 'stderr' | 'combined';\n message: string;\n}\n\nexport interface SessionConfigurationValue {\n value: string | null;\n source: 'default' | 'global' | 'project' | 'env' | 'cli' | 'fallback';\n}\n\nexport interface SessionPhaseConfiguration {\n phase: string;\n provider: SessionConfigurationValue;\n model: SessionConfigurationValue;\n}\n\nexport interface SessionConfigurationSnapshot {\n precedence: string[];\n defaultProvider: SessionConfigurationValue;\n defaultModel: SessionConfigurationValue;\n phases: SessionPhaseConfiguration[];\n fallbacks: string[];\n overrides: string[];\n}\n\nexport interface SessionStageHistoryEntry {\n at: string;\n stage: StoryStage;\n detail: string | null;\n}\n\n/**\n * Token/cost counters attached to a phase or a story.\n *\n * `null` means \"never reported\" — the `claude` CLI does not always return\n * usage data, and a metric that was never observed must stay distinguishable\n * from an observed zero.\n */\nexport interface SessionUsageSnapshot {\n inputTokens: number | null;\n outputTokens: number | null;\n cacheReadTokens: number | null;\n cacheCreationTokens: number | null;\n costUsd: number | null;\n}\n\n/** Issue-wide totals, accumulated from phase- and iteration-scoped metrics. */\nexport interface SessionMetricsSnapshot {\n totalInputTokens: number | null;\n totalOutputTokens: number | null;\n totalCacheReadTokens: number | null;\n totalCacheCreationTokens: number | null;\n totalCostUsd: number | null;\n}\n\nexport interface SessionPhaseSnapshot extends SessionUsageSnapshot {\n name: string;\n status: SessionPhaseStatus;\n startedAt: string | null;\n endedAt: string | null;\n durationSeconds: number | null;\n error: string | null;\n /** Sum of invocation walls for this phase. */\n harnessExecutionMs: number | null;\n /** Phase wall minus harnessExecutionMs, when both are known. */\n orchestrationOverheadMs: number | null;\n /** Wall clock − CLI `duration_ms`. */\n harnessStartupMs: number | null;\n /** Time to first output, when the harness reports it. */\n ttftMs: number | null;\n attemptCount: number | null;\n retryDurationMs: number | null;\n}\n\nexport interface SessionStorySnapshot extends SessionUsageSnapshot {\n id: string;\n title: string;\n priority: number;\n passes: boolean;\n /**\n * When the story flipped to passing during this session; null for stories\n * that were already passing at session start (their duration is unknown).\n */\n completedAt: string | null;\n /** Wall-clock seconds attributed to the story, or null when unknown. */\n durationSeconds: number | null;\n /**\n * Board-style status, recomputed on every reduction by\n * {@link deriveStoryStatus}. Observational: the pipeline keeps deciding what\n * to execute from `passes`.\n */\n status: UserStoryStatus;\n /** IDs of the stories this one depends on, as declared in the plan. */\n dependencies: string[];\n /** The plan's description; empty string when the plan carries none. */\n description: string;\n /** The plan's acceptance criteria; empty when the plan carries none. */\n acceptanceCriteria: string[];\n /**\n * Fine-grained execution stage, derived only from real pipeline events —\n * see {@link StoryStage} and the `applyEvent` cases for `iteration:start`,\n * `stories:update`, `phase:start`/`phase:end` (phase `'review'`) and\n * `correction:cycle`. Unlike `status`, this is not a post-hoc derivation\n * recomputed on every reduction: it is set directly, event by event, like\n * `completedAt`.\n */\n stage: StoryStage;\n /** ISO timestamp of the event that produced the current `stage`. */\n stageSince: string | null;\n /** Short human detail for the current stage (e.g. a correction cycle). */\n stageDetail: string | null;\n /** Append-only transition history retained in the snapshot for the drawer. */\n history: SessionStageHistoryEntry[];\n}\n\nexport interface SessionActivity {\n story: string | null;\n tool: string | null;\n detail: string | null;\n since: string;\n}\n\n/** Live resilience state for the current agent invocation. */\nexport interface SessionResilienceSnapshot {\n attempt: number;\n provider: string | null;\n model: string | null;\n lastFailureKind: FailureKind | null;\n cooldownUntil: string | null;\n lastActivityAt: string | null;\n}\n\nexport interface SessionCommit {\n hash: string;\n subject: string;\n committedAt?: string | null;\n storyId?: string | null;\n}\n\nexport interface SessionPullRequest {\n number: number;\n url: string;\n title: string;\n}\n\n/**\n * The Issue under execution, as far as the session knows it.\n *\n * `number`/`url` come with `session:start`; the remaining fields arrive with\n * `issue:update`, once the provider has resolved the Issue. Everything is\n * nullable because a run may start before (or without) that resolution —\n * `null` means \"not reported\", never \"empty\".\n */\nexport interface SessionIssueSnapshot {\n number: number | null;\n url: string | null;\n title: string | null;\n /** Issue body in full; the consumer decides how to fold it. */\n description: string | null;\n labels: string[];\n /** Provider lifecycle state ('open' / 'closed' for the built-ins). */\n state: string | null;\n}\n\n/**\n * Where the run is happening: which repository, which checkout, which commit.\n *\n * Fed by `git:update` (see `publishGitState`). Every field is nullable because\n * each source is independent and failure-tolerant — no remote configured, a\n * repository with no commits yet or a missing git binary all show up as\n * `null` instead of failing the publication.\n */\nexport interface SessionRepositorySnapshot {\n /** `owner/repo`, derived from the origin remote; null without one. */\n name: string | null;\n remoteUrl: string | null;\n branch: string | null;\n /** Abbreviated hash of HEAD. */\n headCommit: string | null;\n /** Absolute path of the working directory the pipeline runs from. */\n root: string | null;\n}\n\nexport interface SessionSnapshot {\n schemaVersion: 1;\n sessionId: string | null;\n readOnly: true;\n capabilities: string[];\n issue: SessionIssueSnapshot;\n status: SessionStatus;\n startedAt: string | null;\n updatedAt: string | null;\n endedAt: string | null;\n elapsedSeconds: number | null;\n estimatedRemainingSeconds: number | null;\n progress: {\n percent: number;\n phasesCompleted: number;\n phasesTotal: number;\n storiesCompleted: number;\n storiesTotal: number;\n };\n currentPhase: string | null;\n currentActivity: SessionActivity | null;\n phases: SessionPhaseSnapshot[];\n stories: SessionStorySnapshot[];\n metrics: SessionMetricsSnapshot;\n execution: {\n iteration: number;\n retries: number;\n correctionCycle: number;\n maxCorrectionCycles: number | null;\n };\n executions: ExecutionRecord[];\n processLogs: SessionProcessLogEntry[];\n configuration: SessionConfigurationSnapshot | null;\n resilience: SessionResilienceSnapshot;\n git: {\n branch: string | null;\n baseBranch: string | null;\n branchCreated: boolean | null;\n startCommit: string | null;\n commits: SessionCommit[];\n };\n repository: SessionRepositorySnapshot;\n pullRequests: SessionPullRequest[];\n logs: SessionLogEntry[];\n errors: SessionLogEntry[];\n warnings: SessionLogEntry[];\n lastError: { message: string; at: string } | null;\n nextSteps: string[];\n environment: SessionEnvironment | null;\n /** Acceptance-contract verdict. `null` until a contract has run. */\n verification: {\n verdict: 'passed' | 'failed' | 'unverified' | null;\n level: string | null;\n independence: string | null;\n } | null;\n}\n\nexport interface SessionReducerOptions {\n /** Max entries retained in the logs ring buffer. */\n logLimit?: number;\n}\n\n/** Fresh, all-null usage counters for a new phase or story entry. */\nfunction emptyUsage(): SessionUsageSnapshot {\n return {\n inputTokens: null,\n outputTokens: null,\n cacheReadTokens: null,\n cacheCreationTokens: null,\n costUsd: null,\n };\n}\n\nfunction emptyPhaseTiming(): Pick<\n SessionPhaseSnapshot,\n | 'harnessExecutionMs'\n | 'orchestrationOverheadMs'\n | 'harnessStartupMs'\n | 'ttftMs'\n | 'attemptCount'\n | 'retryDurationMs'\n> {\n return {\n harnessExecutionMs: null,\n orchestrationOverheadMs: null,\n harnessStartupMs: null,\n ttftMs: null,\n attemptCount: null,\n retryDurationMs: null,\n };\n}\n\nfunction emptyMetrics(): SessionMetricsSnapshot {\n return {\n totalInputTokens: null,\n totalOutputTokens: null,\n totalCacheReadTokens: null,\n totalCacheCreationTokens: null,\n totalCostUsd: null,\n };\n}\n\nexport function createInitialSnapshot(): SessionSnapshot {\n return {\n schemaVersion: 1,\n sessionId: null,\n readOnly: true,\n capabilities: [],\n issue: { number: null, url: null, title: null, description: null, labels: [], state: null },\n status: 'idle',\n startedAt: null,\n updatedAt: null,\n endedAt: null,\n elapsedSeconds: null,\n estimatedRemainingSeconds: null,\n progress: {\n percent: 0,\n phasesCompleted: 0,\n phasesTotal: 0,\n storiesCompleted: 0,\n storiesTotal: 0,\n },\n currentPhase: null,\n currentActivity: null,\n phases: [],\n stories: [],\n metrics: emptyMetrics(),\n execution: { iteration: 0, retries: 0, correctionCycle: 0, maxCorrectionCycles: null },\n executions: [],\n processLogs: [],\n configuration: null,\n resilience: {\n attempt: 0,\n provider: null,\n model: null,\n lastFailureKind: null,\n cooldownUntil: null,\n lastActivityAt: null,\n },\n git: {\n branch: null,\n baseBranch: null,\n branchCreated: null,\n startCommit: null,\n commits: [],\n },\n repository: { name: null, remoteUrl: null, branch: null, headCommit: null, root: null },\n pullRequests: [],\n logs: [],\n errors: [],\n warnings: [],\n lastError: null,\n nextSteps: [],\n environment: null,\n verification: null,\n };\n}\n\ntype MetricsUpdateEvent = Extract<SessionEvent, { type: 'metrics:update' }>;\n\n/**\n * Add a reported delta to an accumulator. `undefined` means \"not reported\":\n * it leaves the accumulator untouched, so a metric the CLI never returned\n * stays null instead of collapsing to zero.\n */\nfunction accumulate(current: number | null, delta: number | undefined): number | null {\n return delta === undefined ? current : (current ?? 0) + delta;\n}\n\n/** Fold the event's token/cost fields into a phase or story entry. */\nfunction accumulateUsage<T extends SessionUsageSnapshot>(target: T, event: MetricsUpdateEvent): T {\n return {\n ...target,\n inputTokens: accumulate(target.inputTokens, event.inputTokens),\n outputTokens: accumulate(target.outputTokens, event.outputTokens),\n cacheReadTokens: accumulate(target.cacheReadTokens, event.cacheReadTokens),\n cacheCreationTokens: accumulate(target.cacheCreationTokens, event.cacheCreationTokens),\n costUsd: accumulate(target.costUsd, event.costUsd),\n };\n}\n\n/**\n * Pick between a reported value and the one already in the snapshot.\n * Unlike `??`, an explicitly reported `null` wins: only `undefined` — the\n * absence of the field on the event — keeps the previous value.\n */\nfunction reported<T>(value: T | undefined, previous: T): T {\n return value === undefined ? previous : value;\n}\n\n/**\n * Seconds between two ISO timestamps, or null when either is unparsable.\n */\nfunction secondsBetween(from: string | null, to: string): number | null {\n if (from === null) return null;\n const fromMs = Date.parse(from);\n const toMs = Date.parse(to);\n if (Number.isNaN(fromMs) || Number.isNaN(toMs)) return null;\n return Math.max(0, Math.round((toMs - fromMs) / 1000));\n}\n\nfunction computePercent(phasesCompleted: number, phasesTotal: number): number {\n if (phasesTotal === 0) return 0;\n return Math.round((phasesCompleted / phasesTotal) * 100);\n}\n\n/**\n * Estimated seconds until all stories pass: average duration of the stories\n * completed during this session × pending stories. Durations are the gaps\n * between consecutive completions (the first is measured from startedAt).\n * Published as null with fewer than two samples.\n */\nfunction estimateRemainingSeconds(snapshot: SessionSnapshot): number | null {\n const completions = snapshot.stories\n .map((story) => (story.completedAt === null ? Number.NaN : Date.parse(story.completedAt)))\n .filter((ms) => !Number.isNaN(ms))\n .sort((a, b) => a - b);\n if (completions.length < 2) return null;\n\n const startMs = snapshot.startedAt === null ? Number.NaN : Date.parse(snapshot.startedAt);\n const boundaries = Number.isNaN(startMs) ? completions : [startMs, ...completions];\n const durations: number[] = [];\n for (let i = 1; i < boundaries.length; i++) {\n durations.push(Math.max(0, boundaries[i] - boundaries[i - 1]));\n }\n if (durations.length === 0) return null;\n\n const averageMs = durations.reduce((sum, d) => sum + d, 0) / durations.length;\n const pending = snapshot.stories.filter((story) => !story.passes).length;\n return Math.round((averageMs * pending) / 1000);\n}\n\n/**\n * Remaining pipeline phases, in order. The phase list originates from the\n * session:start event, whose caller passes PIPELINE_PHASES (or\n * PIPELINE_PHASES_NO_BRANCH in --no-branch mode) from src/core/pipeline.ts.\n */\nfunction deriveNextSteps(snapshot: SessionSnapshot): string[] {\n if (snapshot.status === 'completed') return [];\n return snapshot.phases.filter((phase) => phase.status === 'pending').map((phase) => phase.name);\n}\n\n/**\n * Board status of a single story, in a fixed order that makes the derivation\n * idempotent — recomputing it over its own result yields the same value:\n *\n * 1. `passes: true` → `done` (execution is the only authority on completion);\n * 2. already `in_review` → kept, since nothing here can produce or clear it;\n * 3. the story owns the current activity → `in_progress`;\n * 4. otherwise → `backlog`.\n *\n * `in_review` is therefore never derived: it only enters through an explicit\n * `status` in tasks.json, and leaves when the story starts passing.\n */\nfunction deriveStoryStatus(\n story: SessionStorySnapshot,\n activeStory: string | null,\n): UserStoryStatus {\n if (story.passes) return 'done';\n if (story.status === 'in_review') return 'in_review';\n if (activeStory !== null && activeStory === story.id) return 'in_progress';\n return 'backlog';\n}\n\n/** Recompute every story's status; entries that do not change keep identity. */\nfunction deriveStoryStatuses(snapshot: SessionSnapshot): SessionStorySnapshot[] {\n const activeStory = snapshot.currentActivity?.story ?? null;\n return snapshot.stories.map((story) => {\n const status = deriveStoryStatus(story, activeStory);\n return status === story.status ? story : { ...story, status };\n });\n}\n\n/**\n * Fold a SessionEvent into a SessionSnapshot. Pure: never mutates the input\n * and performs no I/O. Unknown event types return the snapshot unchanged.\n *\n * errors/warnings are derived slices of the logs ring buffer, recomputed on\n * each reduction — they are never accumulated separately. The same applies\n * to estimatedRemainingSeconds, nextSteps and each story's status.\n */\nexport function reduceSessionEvent(\n snapshot: SessionSnapshot,\n event: SessionEvent,\n options?: SessionReducerOptions,\n): SessionSnapshot {\n const next = applyEvent(snapshot, event, options);\n if (next === snapshot) return snapshot;\n\n const elapsedSeconds = secondsBetween(next.startedAt, event.at) ?? next.elapsedSeconds;\n return {\n ...next,\n updatedAt: event.at,\n elapsedSeconds,\n stories: deriveStoryStatuses(next),\n estimatedRemainingSeconds: estimateRemainingSeconds(next),\n errors: next.logs.filter((entry) => entry.level === 'error'),\n warnings: next.logs.filter((entry) => entry.level === 'warn'),\n nextSteps: deriveNextSteps(next),\n };\n}\n\n/**\n * Stages that describe a finished story. A run that ends — successfully or\n * not — must never leave a story on any other stage, or the panel keeps\n * claiming it is executing long after the process is gone.\n */\nfunction isTerminalStage(stage: StoryStage): boolean {\n return stage === 'done' || stage === 'failed';\n}\n\nfunction transitionStory(\n story: SessionStorySnapshot,\n stage: StoryStage,\n at: string,\n detail: string | null,\n): SessionStorySnapshot {\n if (story.stage === stage && story.stageDetail === detail) return story;\n return {\n ...story,\n stage,\n stageSince: at,\n stageDetail: detail,\n history: [...story.history, { at, stage, detail }],\n };\n}\n\n/**\n * `stage`/`stageSince`/`stageDetail` for one story, on one `stories:update`.\n *\n * This event owns exactly one transition — `awaiting_review` the moment a\n * story flips to `passes: true` (or is first seen already passing, since its\n * real completion moment is unknown either way). Everything else is a\n * carry-over: a still-`passes: false` story keeps whatever `iteration:start`\n * last gave it (defaulting to `'pending'` the very first time it is ever\n * seen), and a story already passing before this event is left exactly as it\n * was — `phase:start`/`phase:end` (review) and `correction:cycle` own every\n * transition past `awaiting_review`.\n */\nfunction deriveStageOnStoriesUpdate(\n story: UserStory,\n before: SessionStorySnapshot | undefined,\n at: string,\n): Pick<SessionStorySnapshot, 'stage' | 'stageSince' | 'stageDetail'> {\n if (!story.passes) {\n return {\n stage: before?.stage ?? 'pending',\n stageSince: before?.stageSince ?? at,\n stageDetail: before?.stageDetail ?? null,\n };\n }\n if (before?.passes) {\n return { stage: before.stage, stageSince: before.stageSince, stageDetail: before.stageDetail };\n }\n return { stage: 'awaiting_review', stageSince: at, stageDetail: null };\n}\n\nfunction applyEvent(\n snapshot: SessionSnapshot,\n event: SessionEvent,\n options?: SessionReducerOptions,\n): SessionSnapshot {\n switch (event.type) {\n case 'session:start': {\n setTelemetrySessionId(event.sessionId);\n const initial = createInitialSnapshot();\n return {\n ...initial,\n sessionId: event.sessionId,\n issue: { ...initial.issue, number: event.issueNumber, url: event.issueUrl ?? null },\n status: 'running',\n startedAt: event.at,\n progress: {\n ...initial.progress,\n phasesTotal: event.phases.length,\n },\n phases: event.phases.map((name) => ({\n name,\n status: 'pending' as const,\n startedAt: null,\n endedAt: null,\n durationSeconds: null,\n error: null,\n ...emptyPhaseTiming(),\n ...emptyUsage(),\n })),\n git: {\n branch: event.branch ?? null,\n baseBranch: event.baseBranch ?? null,\n branchCreated: event.branchCreated ?? null,\n startCommit: event.startCommit ?? null,\n commits: [],\n },\n // The branch is the one piece of repository identity the session\n // already knows here; the rest waits for publishGitState. Seeding it\n // keeps git.branch and repository.branch consistent for a poll that\n // lands before the first git:update.\n repository: { ...initial.repository, branch: event.branch ?? null },\n configuration: event.configuration ?? null,\n environment: event.environment\n ? {\n node: event.environment.node,\n platform: event.environment.platform,\n agent: event.environment.agent ?? null,\n model: event.environment.model ?? null,\n }\n : null,\n };\n }\n\n case 'issue:update':\n return {\n ...snapshot,\n issue: {\n ...snapshot.issue,\n // Merge, not replacement: the run may know a number and a URL that\n // the provider does not report (a local Issue mirroring a remote\n // one), and enriching the section must never erase them.\n number: event.number ?? snapshot.issue.number,\n url: event.url ?? snapshot.issue.url,\n title: event.title,\n description: event.description,\n labels: event.labels,\n state: event.state,\n },\n };\n\n case 'phase:start': {\n const known = snapshot.phases.some((p) => p.name === event.phase);\n const phases = known\n ? snapshot.phases.map((p) =>\n p.name === event.phase\n ? {\n ...p,\n status: 'running' as const,\n startedAt: event.at,\n endedAt: null,\n error: null,\n }\n : p,\n )\n : [\n ...snapshot.phases,\n {\n name: event.phase,\n status: 'running' as const,\n startedAt: event.at,\n endedAt: null,\n durationSeconds: null,\n error: null,\n ...emptyPhaseTiming(),\n ...emptyUsage(),\n },\n ];\n // The `execute` phase only completes (and `review` only starts) once\n // every story already passes — a pipeline invariant — so entering\n // `review` safely moves every passing story to 'in_review' in one go;\n // there is never a not-yet-passing story to skip over here.\n const stories =\n event.phase === 'review'\n ? snapshot.stories.map((story) =>\n story.passes ? transitionStory(story, 'in_review', event.at, null) : story,\n )\n : snapshot.stories;\n return {\n ...snapshot,\n currentPhase: event.phase,\n phases,\n stories,\n progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length },\n };\n }\n\n case 'phase:end': {\n const phases = snapshot.phases.map((p) =>\n p.name === event.phase\n ? {\n ...p,\n status: event.success ? ('completed' as const) : ('failed' as const),\n endedAt: event.at,\n durationSeconds: secondsBetween(p.startedAt, event.at),\n error: event.error ? stripVTControlCharacters(event.error) : null,\n harnessExecutionMs: event.harnessExecutionMs ?? p.harnessExecutionMs,\n orchestrationOverheadMs: event.orchestrationOverheadMs ?? p.orchestrationOverheadMs,\n harnessStartupMs: event.harnessStartupMs ?? p.harnessStartupMs,\n ttftMs: event.ttftMs ?? p.ttftMs,\n attemptCount: event.attemptCount ?? p.attemptCount,\n retryDurationMs: event.retryDurationMs ?? p.retryDurationMs,\n }\n : p,\n );\n const phasesCompleted = phases.filter((p) => p.status === 'completed').length;\n // Success moves every passing story to 'done'. Failure (the correction\n // loop gave up after maxCorrectionCycles) closes every story that is not\n // already 'done' as 'failed' — including the ones that never reached\n // `passes`, which are precisely the stories the issue calls failed. A\n // phase that fails outside `review` closes the same non-terminal stages,\n // so nothing is left frozen on 'executing' after the run stops.\n const stories = !event.success\n ? snapshot.stories.map((story) =>\n isTerminalStage(story.stage) ? story : transitionStory(story, 'failed', event.at, null),\n )\n : event.phase === 'review'\n ? snapshot.stories.map((story) =>\n story.passes ? transitionStory(story, 'done', event.at, null) : story,\n )\n : snapshot.stories;\n return {\n ...snapshot,\n currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,\n currentActivity: null,\n phases,\n stories,\n progress: {\n ...snapshot.progress,\n phasesCompleted,\n percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal),\n },\n };\n }\n\n case 'iteration:start': {\n // The story matching storyId becomes 'executing'; every other\n // not-yet-passing story becomes 'pending' (a story that was already\n // 'executing' in a previous iteration but lost the turn reverts here).\n // Passing stories are untouched — this event owns only the\n // execute-loop's own pending/executing transition, never review or\n // correction. No-op branches (`return story`) avoid needless object\n // churn when the stage is already correct.\n const storyId = event.storyId;\n const stories = snapshot.stories.map((story) => {\n if (story.passes) return story;\n if (story.id === storyId) {\n return transitionStory(story, 'executing', event.at, null);\n }\n return transitionStory(story, 'pending', event.at, null);\n });\n return {\n ...snapshot,\n execution: { ...snapshot.execution, iteration: event.iteration },\n stories,\n // Finally populates the `story` field of the activity payload during\n // execute — today this phase never publishes an `activity` event at\n // all (no streaming), so `currentActivity` stays whatever an earlier\n // phase left it at. Left untouched when storyId is absent (every\n // story already passes, or the caller could not determine one).\n currentActivity:\n storyId !== undefined\n ? { story: storyId, tool: null, detail: null, since: event.at }\n : snapshot.currentActivity,\n };\n }\n\n case 'iteration:end':\n return { ...snapshot, currentActivity: null };\n\n case 'retry':\n return {\n ...snapshot,\n execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 },\n };\n\n case 'agent:attempt':\n return {\n ...snapshot,\n resilience: {\n ...snapshot.resilience,\n attempt: event.attempt,\n provider: event.provider,\n model: event.model ?? null,\n lastActivityAt: event.at,\n },\n };\n\n case 'failover':\n return {\n ...snapshot,\n resilience: {\n ...snapshot.resilience,\n provider: event.to,\n lastFailureKind: event.reason,\n cooldownUntil: event.cooldownUntil ?? null,\n lastActivityAt: event.at,\n },\n };\n\n case 'agent:result':\n return {\n ...snapshot,\n resilience: {\n ...snapshot.resilience,\n provider: event.provider,\n lastFailureKind: event.failureKind ?? snapshot.resilience.lastFailureKind,\n cooldownUntil: event.cooldownUntil ?? null,\n lastActivityAt: event.at,\n },\n };\n\n case 'agent:activity':\n return {\n ...snapshot,\n resilience: {\n ...snapshot.resilience,\n provider: event.provider,\n lastActivityAt: event.at,\n },\n };\n\n case 'stories:update': {\n const previous = new Map(snapshot.stories.map((story) => [story.id, story]));\n const stories = event.stories.map((story) => {\n const before = previous.get(story.id);\n // Stamp the flip to passing; stories already passing when first seen\n // keep null (completed before this session, duration unknown).\n const completedAt = !story.passes\n ? null\n : before && !before.passes\n ? event.at\n : (before?.completedAt ?? null);\n // stories:update rebuilds the array from the plan on every publish, so\n // metrics already attributed to a story must be carried over here or\n // the next update would wipe them.\n const stage = deriveStageOnStoriesUpdate(story, before, event.at);\n const history = before?.history ?? [];\n const stageChanged = before !== undefined && before.stage !== stage.stage;\n return {\n id: story.id,\n title: story.title,\n priority: story.priority,\n passes: story.passes,\n completedAt,\n // Seed only: deriveStoryStatus() recomputes it right after, so an\n // explicit 'done' in the plan on a story with passes: false is not\n // honoured — `passes` remains the single source of truth. The plan's\n // value survives just for 'in_review', which no derivation produces.\n status: story.status ?? before?.status ?? 'backlog',\n dependencies: story.dependencies ?? [],\n // Required on UserStory, so the plan is always the source — no\n // carry-over from `before` the way the accumulated fields need.\n description: story.description,\n acceptanceCriteria: story.acceptanceCriteria,\n durationSeconds: before?.durationSeconds ?? null,\n inputTokens: before?.inputTokens ?? null,\n outputTokens: before?.outputTokens ?? null,\n cacheReadTokens: before?.cacheReadTokens ?? null,\n cacheCreationTokens: before?.cacheCreationTokens ?? null,\n costUsd: before?.costUsd ?? null,\n ...stage,\n history: stageChanged\n ? [...history, { at: event.at, stage: stage.stage, detail: stage.stageDetail }]\n : history,\n };\n });\n return {\n ...snapshot,\n stories,\n progress: {\n ...snapshot.progress,\n storiesCompleted: stories.filter((s) => s.passes).length,\n storiesTotal: stories.length,\n },\n };\n }\n\n case 'activity': {\n const story = event.story ?? null;\n const tool = event.tool ?? null;\n const detail = event.detail ?? null;\n const current = snapshot.currentActivity;\n // Same activity repeated: keep `since` so the UI can show \"for how long\".\n const since =\n current && current.story === story && current.tool === tool && current.detail === detail\n ? current.since\n : event.at;\n return {\n ...snapshot,\n currentActivity: { story, tool, detail, since },\n resilience: { ...snapshot.resilience, lastActivityAt: event.at },\n };\n }\n\n case 'log': {\n const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;\n const entry: SessionLogEntry = {\n at: event.at,\n level: event.level,\n message: stripVTControlCharacters(event.message),\n };\n const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));\n return { ...snapshot, logs };\n }\n\n case 'process:output': {\n const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;\n const entry: SessionProcessLogEntry = {\n at: event.at,\n phase: event.phase,\n executionId: event.executionId,\n provider: event.provider,\n stream: event.stream,\n message: stripVTControlCharacters(event.message),\n };\n return {\n ...snapshot,\n processLogs: [...snapshot.processLogs, entry].slice(-Math.max(1, limit)),\n };\n }\n\n case 'execution:update': {\n const index = snapshot.executions.findIndex((entry) => entry.id === event.execution.id);\n const executions = [...snapshot.executions];\n if (index === -1) executions.push(event.execution);\n else executions[index] = event.execution;\n return { ...snapshot, executions };\n }\n\n case 'git:update':\n return {\n ...snapshot,\n git: {\n branch: event.branch ?? snapshot.git.branch,\n baseBranch: event.baseBranch ?? snapshot.git.baseBranch,\n branchCreated: reported(event.branchCreated, snapshot.git.branchCreated),\n startCommit: snapshot.git.startCommit,\n commits: event.commits ?? snapshot.git.commits,\n },\n repository: {\n // `undefined` is \"not collected\", so the previous value stands; an\n // explicit `null` is \"collected and unavailable\" and overwrites it.\n name: reported(event.repositoryName, snapshot.repository.name),\n remoteUrl: reported(event.remoteUrl, snapshot.repository.remoteUrl),\n branch: event.branch ?? snapshot.repository.branch,\n headCommit: reported(event.headCommit, snapshot.repository.headCommit),\n root: reported(event.repositoryRoot, snapshot.repository.root),\n },\n pullRequests: event.pullRequests ?? snapshot.pullRequests,\n };\n\n case 'correction:cycle': {\n // Correction is pipeline-wide, not per-story: commands/run.ts re-runs\n // the whole execute+review cycle on a review failure, with no notion\n // of which story a finding belongs to — so every passing story moves\n // to 'in_correction' together, carrying the cycle count as a readable\n // stageDetail.\n const stageDetail = `Cycle ${event.cycle}/${event.maxCycles}`;\n const stories = snapshot.stories.map((story) =>\n story.passes ? transitionStory(story, 'in_correction', event.at, stageDetail) : story,\n );\n return {\n ...snapshot,\n execution: {\n ...snapshot.execution,\n correctionCycle: event.cycle,\n maxCorrectionCycles: event.maxCycles,\n },\n stories,\n };\n }\n\n case 'metrics:update': {\n if (event.scope === 'story') {\n // Story metrics are a rateio of the iteration that completed them;\n // the iteration-scoped event already fed the phase and the aggregate,\n // so counting them again here would double the totals.\n if (event.storyId === undefined) return snapshot;\n if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;\n return {\n ...snapshot,\n stories: snapshot.stories.map((story) =>\n story.id === event.storyId\n ? {\n ...accumulateUsage(story, event),\n durationSeconds: accumulate(story.durationSeconds, event.durationSeconds),\n }\n : story,\n ),\n };\n }\n\n // phase and iteration scopes both land on the named phase. An event for\n // a phase the snapshot never saw is ignored rather than appended: it\n // would show up as a phantom entry in the UI.\n if (event.phase === undefined) return snapshot;\n if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;\n return {\n ...snapshot,\n // durationSeconds is deliberately untouched here: phase:start and\n // phase:end remain the single source of a phase's wall-clock time.\n phases: snapshot.phases.map((phase) =>\n phase.name === event.phase ? accumulateUsage(phase, event) : phase,\n ),\n metrics: {\n totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),\n totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),\n totalCacheReadTokens: accumulate(\n snapshot.metrics.totalCacheReadTokens,\n event.cacheReadTokens,\n ),\n totalCacheCreationTokens: accumulate(\n snapshot.metrics.totalCacheCreationTokens,\n event.cacheCreationTokens,\n ),\n totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd),\n },\n };\n }\n\n case 'verify:end':\n return {\n ...snapshot,\n verification: {\n verdict: event.verdict,\n level: event.level,\n independence: event.independence,\n },\n };\n\n case 'session:end':\n return {\n ...snapshot,\n status: event.status,\n endedAt: event.at,\n currentPhase: null,\n currentActivity: null,\n // Close every non-terminal stage: the run is over, so nothing can be\n // 'executing'/'in_review'/'in_correction' any more. A story that never\n // reached `passes` on a run that did not complete is exactly what the\n // 'failed' stage means; anything else settles as 'done'.\n stories: snapshot.stories.map((story) => {\n if (isTerminalStage(story.stage)) return story;\n const stage =\n event.status === 'completed' && story.passes ? ('done' as const) : ('failed' as const);\n return transitionStory(story, stage, event.at, null);\n }),\n lastError: event.error\n ? { message: stripVTControlCharacters(event.error), at: event.at }\n : snapshot.lastError,\n };\n\n default:\n return snapshot;\n }\n}\n\nexport interface SessionPublisher {\n /**\n * Publish an event. Synchronous, never throws, never returns a promise —\n * safe to call from any instrumentation point without affecting execution.\n */\n publish(event: SessionEvent): void;\n /** Current in-memory snapshot. */\n snapshot(): SessionSnapshot;\n /** Monotonic counter, bumped on every applied event (basis for ETags). */\n version(): number;\n /** Force any pending output to be written. Never rejects. */\n flush(): Promise<void>;\n /** Flush and release resources. Never rejects. */\n close(): Promise<void>;\n}\n\n/**\n * Default publisher when monitoring is off: every call is a no-op, so each\n * instrumentation point costs a method call that returns immediately.\n */\nexport class NullPublisher implements SessionPublisher {\n private readonly empty = createInitialSnapshot();\n\n publish(_event: SessionEvent): void {}\n\n snapshot(): SessionSnapshot {\n return this.empty;\n }\n\n version(): number {\n return 0;\n }\n\n async flush(): Promise<void> {}\n\n async close(): Promise<void> {}\n}\n\nexport interface MemoryPublisherOptions extends SessionReducerOptions {\n /** Called at most once, on the first internal failure. */\n onWarn?: (message: string) => void;\n /**\n * When false, log events are dropped before reaching the snapshot, so no\n * log line is ever published (session.json or HTTP). Default true.\n */\n includeLogs?: boolean;\n}\n\n/**\n * In-memory publisher: reduces events over a snapshot and tracks a monotonic\n * version. Base class for publishers with an output surface (file, HTTP).\n */\nexport class MemoryPublisher implements SessionPublisher {\n protected state: SessionSnapshot = createInitialSnapshot();\n protected versionCounter = 0;\n private warned = false;\n private readonly onWarn: (message: string) => void;\n private readonly reducerOptions: SessionReducerOptions;\n private readonly includeLogs: boolean;\n\n constructor(options: MemoryPublisherOptions = {}) {\n this.onWarn = options.onWarn ?? ((message) => process.stderr.write(`${message}\\n`));\n this.reducerOptions = { logLimit: options.logLimit };\n this.includeLogs = options.includeLogs ?? true;\n }\n\n publish(event: SessionEvent): void {\n if (event.type === 'log' && !this.includeLogs) return;\n try {\n this.state = reduceSessionEvent(this.state, event, this.reducerOptions);\n this.versionCounter++;\n this.afterPublish(event);\n } catch (err) {\n this.warnOnce(err);\n }\n }\n\n /** Hook for subclasses; runs inside publish()'s try/catch. */\n protected afterPublish(_event: SessionEvent): void {}\n\n snapshot(): SessionSnapshot {\n return this.state;\n }\n\n version(): number {\n return this.versionCounter;\n }\n\n async flush(): Promise<void> {}\n\n async close(): Promise<void> {}\n\n protected warnOnce(err: unknown): void {\n if (this.warned) return;\n this.warned = true;\n const message = err instanceof Error ? err.message : String(err);\n try {\n this.onWarn(\n `issue-flow: web monitoring hit an error (will keep retrying silently): ${message}`,\n );\n } catch {\n // Even a failing warn callback must not propagate to the pipeline.\n }\n }\n}\n\nexport interface FilePublisherOptions extends MemoryPublisherOptions {\n /** Minimum interval between disk writes (ms). Default 1000. */\n throttleMs?: number;\n /** Interval between mtime-only heartbeats (ms). Zero disables it. Default 10000. */\n heartbeatMs?: number;\n}\n\n/**\n * Publisher that mirrors the snapshot to issues/N/session.json.\n *\n * Writes are atomic (write-to-temp + rename) and throttled; terminal events\n * (phase:end, session:end) force an immediate write. All I/O failures are\n * swallowed after a single warning.\n */\nexport class FilePublisher extends MemoryPublisher {\n private readonly filePath: string;\n private readonly throttleMs: number;\n private timer: NodeJS.Timeout | null = null;\n private heartbeatTimer: NodeJS.Timeout | null = null;\n private lastWriteStartedAt = 0;\n private lastWrittenVersion = 0;\n private writeChain: Promise<void> = Promise.resolve();\n private closed = false;\n\n constructor(filePath: string, options: FilePublisherOptions = {}) {\n super(options);\n this.filePath = filePath;\n this.throttleMs = options.throttleMs ?? DEFAULT_THROTTLE_MS;\n const heartbeatMs = options.heartbeatMs ?? DEFAULT_SESSION_HEARTBEAT_MS;\n if (heartbeatMs > 0) {\n this.heartbeatTimer = setInterval(() => this.enqueueHeartbeat(), heartbeatMs);\n this.heartbeatTimer.unref();\n }\n }\n\n protected override afterPublish(event: SessionEvent): void {\n if (this.closed) return;\n const terminal =\n event.type === 'phase:end' || event.type === 'session:end' || event.type === 'verify:end';\n this.scheduleWrite(terminal);\n }\n\n private scheduleWrite(force: boolean): void {\n if (force) {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n this.enqueueWrite();\n return;\n }\n if (this.timer !== null) return;\n const wait = Math.max(0, this.throttleMs - (Date.now() - this.lastWriteStartedAt));\n if (wait === 0) {\n this.enqueueWrite();\n return;\n }\n this.timer = setTimeout(() => {\n this.timer = null;\n this.enqueueWrite();\n }, wait);\n this.timer.unref();\n }\n\n private enqueueWrite(): void {\n this.lastWriteStartedAt = Date.now();\n this.writeChain = this.writeChain.then(async () => {\n const version = this.versionCounter;\n if (version === this.lastWrittenVersion) return;\n const payload = `${JSON.stringify(this.state, null, 2)}\\n`;\n try {\n await atomicWriteFile(this.filePath, payload);\n this.lastWrittenVersion = version;\n } catch (err) {\n this.warnOnce(err);\n }\n });\n }\n\n /**\n * Keep directory-based discovery alive without changing snapshot content or\n * its content-derived ETag. The write chain serializes the touch with atomic\n * snapshot replacement, and no heartbeat is attempted before the first file\n * has been written successfully.\n */\n private enqueueHeartbeat(): void {\n if (this.closed) return;\n this.writeChain = this.writeChain.then(async () => {\n if (this.closed || this.lastWrittenVersion === 0) return;\n const now = new Date();\n try {\n await utimes(this.filePath, now, now);\n } catch (err) {\n this.warnOnce(err);\n }\n });\n }\n\n override async flush(): Promise<void> {\n if (this.timer !== null) {\n clearTimeout(this.timer);\n this.timer = null;\n }\n if (this.versionCounter !== this.lastWrittenVersion) {\n this.enqueueWrite();\n }\n await this.writeChain;\n }\n\n override async close(): Promise<void> {\n if (this.heartbeatTimer !== null) {\n clearInterval(this.heartbeatTimer);\n this.heartbeatTimer = null;\n }\n this.closed = true;\n await this.flush();\n }\n}\n\n/**\n * Atomic write: write to a temp file next to the target, then rename. The\n * same-directory temp keeps the rename on a single filesystem (rename is\n * atomic; no EXDEV fallback needed, unlike an os.tmpdir() temp on Linux\n * tmpfs) and leaves nothing behind. The FilePublisher write chain is the\n * single writer, so the fixed .tmp name never races.\n *\n * The target directory (issues/N/) may not exist yet the first time a fresh\n * issue publishes — pipeline phases create it lazily, and this can be the\n * very first write. mkdir recursive is idempotent, so it's cheap to ensure\n * on every write rather than relying on call order elsewhere.\n */\nasync function atomicWriteFile(path: string, content: string): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmpFile = `${path}.tmp`;\n await writeFile(tmpFile, content, 'utf-8');\n await rename(tmpFile, path);\n}\n","import { NullPublisher, type SessionPublisher } from './session-state.js';\n\n/**\n * Global slot for the active SessionPublisher, following the same pattern as\n * setVerbose in verbose.ts. Instrumentation points (logger, engine, headless)\n * read the slot via getSessionPublisher(); run.ts installs a MemoryPublisher\n * for every run and a FilePublisher on top when web monitoring is enabled.\n * The default NullPublisher makes every instrumentation point a no-op\n * before a run (or a standalone command) installs a publisher.\n */\n\nconst NULL_PUBLISHER = new NullPublisher();\n\nlet _publisher: SessionPublisher = NULL_PUBLISHER;\n\n/**\n * Install the active session publisher. Pass undefined to reset to the\n * default NullPublisher.\n */\nexport function setSessionPublisher(publisher: SessionPublisher | undefined): void {\n _publisher = publisher ?? NULL_PUBLISHER;\n}\n\nexport function getSessionPublisher(): SessionPublisher {\n return _publisher;\n}\n"],"mappings":"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","names":[]}
|