issue-flow 0.16.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/dist/agent-HMZTXCRT.js +30 -0
  2. package/dist/{analyze-556KCJKZ.js → analyze-Q4ONIWRZ.js} +22 -22
  3. package/dist/{apply-WVV7G7WN.js → apply-EEFPY2W4.js} +14 -14
  4. package/dist/{bench-UBFGKUHR.js → bench-WWWXLTFH.js} +23 -23
  5. package/dist/{chunk-GV2DABNZ.js → chunk-34OCSKOB.js} +12 -12
  6. package/dist/chunk-3R27MJ5R.js +25 -0
  7. package/dist/chunk-3R27MJ5R.js.map +1 -0
  8. package/dist/{chunk-PGN2VX4Y.js → chunk-45WZ6GMI.js} +32 -48
  9. package/dist/chunk-45WZ6GMI.js.map +1 -0
  10. package/dist/{chunk-H3EYBJZU.js → chunk-62GVVZQO.js} +10 -10
  11. package/dist/{chunk-GPLP7EPL.js → chunk-6K2LCFG6.js} +10 -10
  12. package/dist/{chunk-GPLP7EPL.js.map → chunk-6K2LCFG6.js.map} +1 -1
  13. package/dist/{chunk-OGVWJO4S.js → chunk-7F7LGMVO.js} +3 -3
  14. package/dist/{chunk-X6GY3CM2.js → chunk-7UMMFOL2.js} +104 -104
  15. package/dist/chunk-7UMMFOL2.js.map +1 -0
  16. package/dist/{chunk-OLUOZLU5.js → chunk-CWSQVHVV.js} +1691 -1075
  17. package/dist/chunk-CWSQVHVV.js.map +1 -0
  18. package/dist/{chunk-MXCOSPLJ.js → chunk-DGRTJD5R.js} +3 -3
  19. package/dist/{chunk-ROIIEVZH.js → chunk-DUS5ZNKL.js} +474 -361
  20. package/dist/chunk-DUS5ZNKL.js.map +1 -0
  21. package/dist/{chunk-BSBJXPLC.js → chunk-E4I6MMHD.js} +10 -10
  22. package/dist/{chunk-QZOBR6YF.js → chunk-ECU7YO7S.js} +4 -4
  23. package/dist/chunk-ECU7YO7S.js.map +1 -0
  24. package/dist/{chunk-EPFC4LHT.js → chunk-EG2MGDP5.js} +9 -9
  25. package/dist/chunk-HOAHMERN.js +229 -0
  26. package/dist/chunk-HOAHMERN.js.map +1 -0
  27. package/dist/{chunk-QVLIIGBU.js → chunk-ICM463EO.js} +60 -13
  28. package/dist/chunk-ICM463EO.js.map +1 -0
  29. package/dist/{chunk-4FUWWBTP.js → chunk-KMCS76ZW.js} +8 -8
  30. package/dist/{chunk-SW2MF47D.js → chunk-KRAGAUKD.js} +19 -19
  31. package/dist/{chunk-OOX3RMD3.js → chunk-LGHND47K.js} +4 -4
  32. package/dist/{chunk-VTF4RWS5.js → chunk-LI35PIIS.js} +7 -7
  33. package/dist/{chunk-Q2TZA6AC.js → chunk-LTH6XYZ3.js} +14 -14
  34. package/dist/{chunk-YA6FWFYW.js → chunk-NEWNXZ65.js} +62 -11
  35. package/dist/chunk-NEWNXZ65.js.map +1 -0
  36. package/dist/{chunk-2H43OVTU.js → chunk-OODXFCQV.js} +11 -11
  37. package/dist/{chunk-KV2K7MK2.js → chunk-PMLE7KAQ.js} +20 -12
  38. package/dist/chunk-PMLE7KAQ.js.map +1 -0
  39. package/dist/{chunk-R42YHLDV.js → chunk-PPZ3K4YM.js} +2 -2
  40. package/dist/{chunk-J2LJB2OH.js → chunk-QF7VEL3M.js} +739 -579
  41. package/dist/chunk-QF7VEL3M.js.map +1 -0
  42. package/dist/{chunk-VK65RNFA.js → chunk-QJ44VG5G.js} +3 -3
  43. package/dist/{chunk-FKWYD44M.js → chunk-QPGEFLMY.js} +2 -2
  44. package/dist/{chunk-NQCR3HSU.js → chunk-RNFUVKC3.js} +3 -3
  45. package/dist/{chunk-G5LACAXO.js → chunk-RYOUZ6EK.js} +2 -2
  46. package/dist/{chunk-MCLPSL6B.js → chunk-SA6HXL4Y.js} +3 -3
  47. package/dist/{chunk-GVOZ6NDO.js → chunk-SOM4QA6C.js} +3 -3
  48. package/dist/{chunk-73TS2H42.js → chunk-TZYINKCC.js} +154 -34
  49. package/dist/chunk-TZYINKCC.js.map +1 -0
  50. package/dist/{chunk-DBEMD34J.js → chunk-V7QOIQYC.js} +8 -8
  51. package/dist/{chunk-IAMIBZRF.js → chunk-WCCZJL7W.js} +2 -2
  52. package/dist/{chunk-OGN2NX57.js → chunk-XUEMATVY.js} +9 -9
  53. package/dist/{chunk-SVDEWOUD.js → chunk-XZUEFTUP.js} +6 -6
  54. package/dist/{chunk-WLJVL25O.js → chunk-YUNBOUQH.js} +6 -6
  55. package/dist/{chunk-WNROH2YE.js → chunk-YVPZUN6X.js} +2 -2
  56. package/dist/chunk-ZVZMWNCG.js +415 -0
  57. package/dist/chunk-ZVZMWNCG.js.map +1 -0
  58. package/dist/cli.js +57 -49
  59. package/dist/cli.js.map +1 -1
  60. package/dist/{config-UE7IP36J.js → config-LEBZZCJP.js} +13 -13
  61. package/dist/{conventions-Z3QC2NSG.js → conventions-XOT6YW5L.js} +15 -15
  62. package/dist/{diagnostics-2Z5HFQOO.js → diagnostics-HTCWF7F4.js} +7 -7
  63. package/dist/execute-LELOSR5T.js +37 -0
  64. package/dist/{generate-PB4L7V5N.js → generate-JGW5JSKB.js} +22 -22
  65. package/dist/{git-7MUOGVRG.js → git-A2IP32PQ.js} +2 -2
  66. package/dist/init-TO6MUXO4.js +26 -0
  67. package/dist/{operations-QDJAFZUN.js → operations-O4FORFIH.js} +15 -15
  68. package/dist/{permissions-XVXXENVP.js → permissions-G4XCAESF.js} +4 -4
  69. package/dist/plan-OXAT344Q.js +34 -0
  70. package/dist/{policy-6WQZB6B2.js → policy-U5NBFAO5.js} +14 -14
  71. package/dist/pr-UZHNKKZC.js +39 -0
  72. package/dist/pr-review-Q4ACAWDM.js +32 -0
  73. package/dist/prd-6CYEMLDY.js +33 -0
  74. package/dist/{ps-CX7756QG.js → ps-76L6ZAZL.js} +12 -12
  75. package/dist/{recorder-RK6XZYF3.js → recorder-2BAKPBRA.js} +7 -7
  76. package/dist/{registry-POSB35Q4.js → registry-XLHHYLTR.js} +5 -5
  77. package/dist/{resume-OQJZV3JH.js → resume-URCU6CIJ.js} +39 -38
  78. package/dist/{resume-OQJZV3JH.js.map → resume-URCU6CIJ.js.map} +1 -1
  79. package/dist/review-LVZT2TMW.js +35 -0
  80. package/dist/routing-EK6RBZM4.js +33 -0
  81. package/dist/run-2RXJCJKT.js +56 -0
  82. package/dist/{session-publisher-VVXOCJ2S.js → session-publisher-KAJQRHNZ.js} +3 -2
  83. package/dist/{state-manager-TIC73GQY.js → state-manager-UPHO2EA2.js} +4 -4
  84. package/dist/state-manager-UPHO2EA2.js.map +1 -0
  85. package/dist/{usage-WDDNSIHP.js → usage-KTMYXAPF.js} +12 -12
  86. package/dist/{web-IPGY52AK.js → web-B5UPHHR5.js} +20 -18
  87. package/dist/{web-IPGY52AK.js.map → web-B5UPHHR5.js.map} +1 -1
  88. package/package.json +6 -5
  89. package/web/AGENTS.md +125 -18
  90. package/web/public/app.css +420 -234
  91. package/web/public/app.js +371 -80
  92. package/web/public/index.html +106 -78
  93. package/dist/agent-PMWCDWOW.js +0 -30
  94. package/dist/chunk-73TS2H42.js.map +0 -1
  95. package/dist/chunk-7Z4Y3VKM.js +0 -26
  96. package/dist/chunk-7Z4Y3VKM.js.map +0 -1
  97. package/dist/chunk-AOAQU7PD.js +0 -180
  98. package/dist/chunk-AOAQU7PD.js.map +0 -1
  99. package/dist/chunk-J2LJB2OH.js.map +0 -1
  100. package/dist/chunk-KV2K7MK2.js.map +0 -1
  101. package/dist/chunk-OLUOZLU5.js.map +0 -1
  102. package/dist/chunk-PGN2VX4Y.js.map +0 -1
  103. package/dist/chunk-QVLIIGBU.js.map +0 -1
  104. package/dist/chunk-QZOBR6YF.js.map +0 -1
  105. package/dist/chunk-ROIIEVZH.js.map +0 -1
  106. package/dist/chunk-X6GY3CM2.js.map +0 -1
  107. package/dist/chunk-YA6FWFYW.js.map +0 -1
  108. package/dist/execute-QYLJ5L4T.js +0 -36
  109. package/dist/init-XBERAZ4O.js +0 -24
  110. package/dist/plan-RXOW32KY.js +0 -34
  111. package/dist/pr-3WFYAVA7.js +0 -39
  112. package/dist/pr-review-RK63ZEMM.js +0 -32
  113. package/dist/prd-LRV6IW3E.js +0 -33
  114. package/dist/review-AEFISJZD.js +0 -35
  115. package/dist/routing-EA5NJGNU.js +0 -94
  116. package/dist/routing-EA5NJGNU.js.map +0 -1
  117. package/dist/run-HSC7R33Y.js +0 -55
  118. /package/dist/{agent-PMWCDWOW.js.map → agent-HMZTXCRT.js.map} +0 -0
  119. /package/dist/{analyze-556KCJKZ.js.map → analyze-Q4ONIWRZ.js.map} +0 -0
  120. /package/dist/{apply-WVV7G7WN.js.map → apply-EEFPY2W4.js.map} +0 -0
  121. /package/dist/{bench-UBFGKUHR.js.map → bench-WWWXLTFH.js.map} +0 -0
  122. /package/dist/{chunk-GV2DABNZ.js.map → chunk-34OCSKOB.js.map} +0 -0
  123. /package/dist/{chunk-H3EYBJZU.js.map → chunk-62GVVZQO.js.map} +0 -0
  124. /package/dist/{chunk-OGVWJO4S.js.map → chunk-7F7LGMVO.js.map} +0 -0
  125. /package/dist/{chunk-MXCOSPLJ.js.map → chunk-DGRTJD5R.js.map} +0 -0
  126. /package/dist/{chunk-BSBJXPLC.js.map → chunk-E4I6MMHD.js.map} +0 -0
  127. /package/dist/{chunk-EPFC4LHT.js.map → chunk-EG2MGDP5.js.map} +0 -0
  128. /package/dist/{chunk-4FUWWBTP.js.map → chunk-KMCS76ZW.js.map} +0 -0
  129. /package/dist/{chunk-SW2MF47D.js.map → chunk-KRAGAUKD.js.map} +0 -0
  130. /package/dist/{chunk-OOX3RMD3.js.map → chunk-LGHND47K.js.map} +0 -0
  131. /package/dist/{chunk-VTF4RWS5.js.map → chunk-LI35PIIS.js.map} +0 -0
  132. /package/dist/{chunk-Q2TZA6AC.js.map → chunk-LTH6XYZ3.js.map} +0 -0
  133. /package/dist/{chunk-2H43OVTU.js.map → chunk-OODXFCQV.js.map} +0 -0
  134. /package/dist/{chunk-R42YHLDV.js.map → chunk-PPZ3K4YM.js.map} +0 -0
  135. /package/dist/{chunk-VK65RNFA.js.map → chunk-QJ44VG5G.js.map} +0 -0
  136. /package/dist/{chunk-FKWYD44M.js.map → chunk-QPGEFLMY.js.map} +0 -0
  137. /package/dist/{chunk-NQCR3HSU.js.map → chunk-RNFUVKC3.js.map} +0 -0
  138. /package/dist/{chunk-G5LACAXO.js.map → chunk-RYOUZ6EK.js.map} +0 -0
  139. /package/dist/{chunk-MCLPSL6B.js.map → chunk-SA6HXL4Y.js.map} +0 -0
  140. /package/dist/{chunk-GVOZ6NDO.js.map → chunk-SOM4QA6C.js.map} +0 -0
  141. /package/dist/{chunk-DBEMD34J.js.map → chunk-V7QOIQYC.js.map} +0 -0
  142. /package/dist/{chunk-IAMIBZRF.js.map → chunk-WCCZJL7W.js.map} +0 -0
  143. /package/dist/{chunk-OGN2NX57.js.map → chunk-XUEMATVY.js.map} +0 -0
  144. /package/dist/{chunk-SVDEWOUD.js.map → chunk-XZUEFTUP.js.map} +0 -0
  145. /package/dist/{chunk-WLJVL25O.js.map → chunk-YUNBOUQH.js.map} +0 -0
  146. /package/dist/{chunk-WNROH2YE.js.map → chunk-YVPZUN6X.js.map} +0 -0
  147. /package/dist/{config-UE7IP36J.js.map → config-LEBZZCJP.js.map} +0 -0
  148. /package/dist/{conventions-Z3QC2NSG.js.map → conventions-XOT6YW5L.js.map} +0 -0
  149. /package/dist/{diagnostics-2Z5HFQOO.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
  150. /package/dist/{execute-QYLJ5L4T.js.map → execute-LELOSR5T.js.map} +0 -0
  151. /package/dist/{generate-PB4L7V5N.js.map → generate-JGW5JSKB.js.map} +0 -0
  152. /package/dist/{git-7MUOGVRG.js.map → git-A2IP32PQ.js.map} +0 -0
  153. /package/dist/{init-XBERAZ4O.js.map → init-TO6MUXO4.js.map} +0 -0
  154. /package/dist/{operations-QDJAFZUN.js.map → operations-O4FORFIH.js.map} +0 -0
  155. /package/dist/{permissions-XVXXENVP.js.map → permissions-G4XCAESF.js.map} +0 -0
  156. /package/dist/{plan-RXOW32KY.js.map → plan-OXAT344Q.js.map} +0 -0
  157. /package/dist/{policy-6WQZB6B2.js.map → policy-U5NBFAO5.js.map} +0 -0
  158. /package/dist/{pr-3WFYAVA7.js.map → pr-UZHNKKZC.js.map} +0 -0
  159. /package/dist/{pr-review-RK63ZEMM.js.map → pr-review-Q4ACAWDM.js.map} +0 -0
  160. /package/dist/{prd-LRV6IW3E.js.map → prd-6CYEMLDY.js.map} +0 -0
  161. /package/dist/{ps-CX7756QG.js.map → ps-76L6ZAZL.js.map} +0 -0
  162. /package/dist/{recorder-RK6XZYF3.js.map → recorder-2BAKPBRA.js.map} +0 -0
  163. /package/dist/{registry-POSB35Q4.js.map → registry-XLHHYLTR.js.map} +0 -0
  164. /package/dist/{review-AEFISJZD.js.map → review-LVZT2TMW.js.map} +0 -0
  165. /package/dist/{run-HSC7R33Y.js.map → routing-EK6RBZM4.js.map} +0 -0
  166. /package/dist/{session-publisher-VVXOCJ2S.js.map → run-2RXJCJKT.js.map} +0 -0
  167. /package/dist/{state-manager-TIC73GQY.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
  168. /package/dist/{usage-WDDNSIHP.js.map → usage-KTMYXAPF.js.map} +0 -0
@@ -2,14 +2,224 @@
2
2
  import {
3
3
  setTelemetrySessionId
4
4
  } from "./chunk-P3LRUNC6.js";
5
+ import {
6
+ writeFileAtomic
7
+ } from "./chunk-3R27MJ5R.js";
5
8
 
6
- // src/core/session-state.ts
7
- import { mkdir, rename, utimes, writeFile } from "fs/promises";
8
- import { dirname } from "path";
9
- import { stripVTControlCharacters } from "util";
9
+ // src/core/session/publishers.ts
10
+ import { utimes } from "fs/promises";
11
+
12
+ // src/core/session/events.ts
10
13
  var DEFAULT_LOG_LIMIT = 200;
11
14
  var DEFAULT_THROTTLE_MS = 1e3;
12
15
  var DEFAULT_SESSION_HEARTBEAT_MS = 1e4;
16
+
17
+ // src/core/session/derive.ts
18
+ function accumulate(current, delta) {
19
+ return delta === void 0 ? current : (current ?? 0) + delta;
20
+ }
21
+ function accumulateUsage(target, event) {
22
+ return {
23
+ ...target,
24
+ inputTokens: accumulate(target.inputTokens, event.inputTokens),
25
+ outputTokens: accumulate(target.outputTokens, event.outputTokens),
26
+ cacheReadTokens: accumulate(target.cacheReadTokens, event.cacheReadTokens),
27
+ cacheCreationTokens: accumulate(target.cacheCreationTokens, event.cacheCreationTokens),
28
+ costUsd: accumulate(target.costUsd, event.costUsd)
29
+ };
30
+ }
31
+ function reported(value, previous) {
32
+ return value === void 0 ? previous : value;
33
+ }
34
+ function secondsBetween(from, to) {
35
+ if (from === null) return null;
36
+ const fromMs = Date.parse(from);
37
+ const toMs = Date.parse(to);
38
+ if (Number.isNaN(fromMs) || Number.isNaN(toMs)) return null;
39
+ return Math.max(0, Math.round((toMs - fromMs) / 1e3));
40
+ }
41
+ function computePercent(phasesCompleted, phasesTotal) {
42
+ if (phasesTotal === 0) return 0;
43
+ return Math.round(phasesCompleted / phasesTotal * 100);
44
+ }
45
+ function estimateRemainingSeconds(snapshot) {
46
+ const completions = snapshot.stories.map((story) => story.completedAt === null ? Number.NaN : Date.parse(story.completedAt)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
47
+ if (completions.length < 2) return null;
48
+ const startMs = snapshot.startedAt === null ? Number.NaN : Date.parse(snapshot.startedAt);
49
+ const boundaries = Number.isNaN(startMs) ? completions : [startMs, ...completions];
50
+ const durations = [];
51
+ for (let i = 1; i < boundaries.length; i++) {
52
+ durations.push(Math.max(0, boundaries[i] - boundaries[i - 1]));
53
+ }
54
+ if (durations.length === 0) return null;
55
+ const averageMs = durations.reduce((sum, d) => sum + d, 0) / durations.length;
56
+ const pending = snapshot.stories.filter((story) => !story.passes).length;
57
+ return Math.round(averageMs * pending / 1e3);
58
+ }
59
+ function deriveNextSteps(snapshot) {
60
+ if (snapshot.status === "completed") return [];
61
+ return snapshot.phases.filter((phase) => phase.status === "pending").map((phase) => phase.name);
62
+ }
63
+ function deriveStoryStatus(story, activeStory) {
64
+ if (story.passes) return "done";
65
+ if (story.status === "in_review") return "in_review";
66
+ if (activeStory !== null && activeStory === story.id) return "in_progress";
67
+ return "backlog";
68
+ }
69
+ function deriveStoryStatuses(snapshot) {
70
+ const activeStory = snapshot.currentActivity?.story ?? null;
71
+ return snapshot.stories.map((story) => {
72
+ const status = deriveStoryStatus(story, activeStory);
73
+ return status === story.status ? story : { ...story, status };
74
+ });
75
+ }
76
+
77
+ // src/core/session/reducer-git.ts
78
+ function applyGitEvent(snapshot, event) {
79
+ switch (event.type) {
80
+ case "git:update":
81
+ return {
82
+ ...snapshot,
83
+ git: {
84
+ branch: event.branch ?? snapshot.git.branch,
85
+ baseBranch: event.baseBranch ?? snapshot.git.baseBranch,
86
+ branchCreated: reported(event.branchCreated, snapshot.git.branchCreated),
87
+ startCommit: snapshot.git.startCommit,
88
+ commits: event.commits ?? snapshot.git.commits
89
+ },
90
+ repository: {
91
+ // `undefined` is "not collected", so the previous value stands; an
92
+ // explicit `null` is "collected and unavailable" and overwrites it.
93
+ name: reported(event.repositoryName, snapshot.repository.name),
94
+ remoteUrl: reported(event.remoteUrl, snapshot.repository.remoteUrl),
95
+ branch: event.branch ?? snapshot.repository.branch,
96
+ headCommit: reported(event.headCommit, snapshot.repository.headCommit),
97
+ root: reported(event.repositoryRoot, snapshot.repository.root)
98
+ },
99
+ pullRequests: event.pullRequests ?? snapshot.pullRequests
100
+ };
101
+ }
102
+ }
103
+
104
+ // src/core/session/reducer-log.ts
105
+ import { stripVTControlCharacters } from "util";
106
+ function applyLogEvent(snapshot, event, options) {
107
+ switch (event.type) {
108
+ case "log": {
109
+ const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
110
+ const entry = {
111
+ at: event.at,
112
+ level: event.level,
113
+ message: stripVTControlCharacters(event.message)
114
+ };
115
+ const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));
116
+ return { ...snapshot, logs };
117
+ }
118
+ case "process:output": {
119
+ const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
120
+ const entry = {
121
+ at: event.at,
122
+ phase: event.phase,
123
+ executionId: event.executionId,
124
+ provider: event.provider,
125
+ stream: event.stream,
126
+ message: stripVTControlCharacters(event.message)
127
+ };
128
+ return {
129
+ ...snapshot,
130
+ processLogs: [...snapshot.processLogs, entry].slice(-Math.max(1, limit))
131
+ };
132
+ }
133
+ case "execution:update": {
134
+ const index = snapshot.executions.findIndex((entry) => entry.id === event.execution.id);
135
+ const executions = [...snapshot.executions];
136
+ if (index === -1) executions.push(event.execution);
137
+ else executions[index] = event.execution;
138
+ return { ...snapshot, executions };
139
+ }
140
+ default: {
141
+ const _exhaustive = event;
142
+ return _exhaustive;
143
+ }
144
+ }
145
+ }
146
+
147
+ // src/core/session/reducer-metrics.ts
148
+ function applyMetricsEvent(snapshot, event) {
149
+ switch (event.type) {
150
+ case "metrics:update": {
151
+ if (event.scope === "story") {
152
+ if (event.storyId === void 0) return snapshot;
153
+ if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;
154
+ return {
155
+ ...snapshot,
156
+ stories: snapshot.stories.map(
157
+ (story) => story.id === event.storyId ? {
158
+ ...accumulateUsage(story, event),
159
+ durationSeconds: accumulate(story.durationSeconds, event.durationSeconds)
160
+ } : story
161
+ )
162
+ };
163
+ }
164
+ if (event.phase === void 0) return snapshot;
165
+ if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;
166
+ return {
167
+ ...snapshot,
168
+ // durationSeconds is deliberately untouched here: phase:start and
169
+ // phase:end remain the single source of a phase's wall-clock time.
170
+ phases: snapshot.phases.map(
171
+ (phase) => phase.name === event.phase ? accumulateUsage(phase, event) : phase
172
+ ),
173
+ metrics: {
174
+ totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),
175
+ totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),
176
+ totalCacheReadTokens: accumulate(
177
+ snapshot.metrics.totalCacheReadTokens,
178
+ event.cacheReadTokens
179
+ ),
180
+ totalCacheCreationTokens: accumulate(
181
+ snapshot.metrics.totalCacheCreationTokens,
182
+ event.cacheCreationTokens
183
+ ),
184
+ totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd)
185
+ }
186
+ };
187
+ }
188
+ }
189
+ }
190
+
191
+ // src/core/session/reducer-phase.ts
192
+ import { stripVTControlCharacters as stripVTControlCharacters2 } from "util";
193
+
194
+ // src/core/session/reducer-stage.ts
195
+ function isTerminalStage(stage) {
196
+ return stage === "done" || stage === "failed";
197
+ }
198
+ function transitionStory(story, stage, at, detail) {
199
+ if (story.stage === stage && story.stageDetail === detail) return story;
200
+ return {
201
+ ...story,
202
+ stage,
203
+ stageSince: at,
204
+ stageDetail: detail,
205
+ history: [...story.history, { at, stage, detail }]
206
+ };
207
+ }
208
+ function deriveStageOnStoriesUpdate(story, before, at) {
209
+ if (!story.passes) {
210
+ return {
211
+ stage: before?.stage ?? "pending",
212
+ stageSince: before?.stageSince ?? at,
213
+ stageDetail: before?.stageDetail ?? null
214
+ };
215
+ }
216
+ if (before?.passes) {
217
+ return { stage: before.stage, stageSince: before.stageSince, stageDetail: before.stageDetail };
218
+ }
219
+ return { stage: "awaiting_review", stageSince: at, stageDetail: null };
220
+ }
221
+
222
+ // src/core/session/snapshot.ts
13
223
  function emptyUsage() {
14
224
  return {
15
225
  inputTokens: null,
@@ -93,106 +303,192 @@ function createInitialSnapshot() {
93
303
  verification: null
94
304
  };
95
305
  }
96
- function accumulate(current, delta) {
97
- return delta === void 0 ? current : (current ?? 0) + delta;
306
+
307
+ // src/core/session/reducer-phase.ts
308
+ function applyPhaseEvent(snapshot, event) {
309
+ switch (event.type) {
310
+ case "phase:start":
311
+ return applyPhaseStart(snapshot, event);
312
+ case "phase:end":
313
+ return applyPhaseEnd(snapshot, event);
314
+ case "iteration:start":
315
+ return applyIterationStart(snapshot, event);
316
+ case "iteration:end":
317
+ return { ...snapshot, currentActivity: null };
318
+ case "correction:cycle":
319
+ return applyCorrectionCycle(snapshot, event);
320
+ default: {
321
+ const _exhaustive = event;
322
+ return _exhaustive;
323
+ }
324
+ }
98
325
  }
99
- function accumulateUsage(target, event) {
326
+ function applyPhaseStart(snapshot, event) {
327
+ const known = snapshot.phases.some((p) => p.name === event.phase);
328
+ const phases = known ? snapshot.phases.map(
329
+ (p) => p.name === event.phase ? {
330
+ ...p,
331
+ status: "running",
332
+ startedAt: event.at,
333
+ endedAt: null,
334
+ error: null
335
+ } : p
336
+ ) : [
337
+ ...snapshot.phases,
338
+ {
339
+ name: event.phase,
340
+ status: "running",
341
+ startedAt: event.at,
342
+ endedAt: null,
343
+ durationSeconds: null,
344
+ error: null,
345
+ ...emptyPhaseTiming(),
346
+ ...emptyUsage()
347
+ }
348
+ ];
349
+ const stories = event.phase === "review" ? snapshot.stories.map(
350
+ (story) => story.passes ? transitionStory(story, "in_review", event.at, null) : story
351
+ ) : snapshot.stories;
100
352
  return {
101
- ...target,
102
- inputTokens: accumulate(target.inputTokens, event.inputTokens),
103
- outputTokens: accumulate(target.outputTokens, event.outputTokens),
104
- cacheReadTokens: accumulate(target.cacheReadTokens, event.cacheReadTokens),
105
- cacheCreationTokens: accumulate(target.cacheCreationTokens, event.cacheCreationTokens),
106
- costUsd: accumulate(target.costUsd, event.costUsd)
353
+ ...snapshot,
354
+ currentPhase: event.phase,
355
+ phases,
356
+ stories,
357
+ progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length }
107
358
  };
108
359
  }
109
- function reported(value, previous) {
110
- return value === void 0 ? previous : value;
111
- }
112
- function secondsBetween(from, to) {
113
- if (from === null) return null;
114
- const fromMs = Date.parse(from);
115
- const toMs = Date.parse(to);
116
- if (Number.isNaN(fromMs) || Number.isNaN(toMs)) return null;
117
- return Math.max(0, Math.round((toMs - fromMs) / 1e3));
118
- }
119
- function computePercent(phasesCompleted, phasesTotal) {
120
- if (phasesTotal === 0) return 0;
121
- return Math.round(phasesCompleted / phasesTotal * 100);
122
- }
123
- function estimateRemainingSeconds(snapshot) {
124
- const completions = snapshot.stories.map((story) => story.completedAt === null ? Number.NaN : Date.parse(story.completedAt)).filter((ms) => !Number.isNaN(ms)).sort((a, b) => a - b);
125
- if (completions.length < 2) return null;
126
- const startMs = snapshot.startedAt === null ? Number.NaN : Date.parse(snapshot.startedAt);
127
- const boundaries = Number.isNaN(startMs) ? completions : [startMs, ...completions];
128
- const durations = [];
129
- for (let i = 1; i < boundaries.length; i++) {
130
- durations.push(Math.max(0, boundaries[i] - boundaries[i - 1]));
131
- }
132
- if (durations.length === 0) return null;
133
- const averageMs = durations.reduce((sum, d) => sum + d, 0) / durations.length;
134
- const pending = snapshot.stories.filter((story) => !story.passes).length;
135
- return Math.round(averageMs * pending / 1e3);
136
- }
137
- function deriveNextSteps(snapshot) {
138
- if (snapshot.status === "completed") return [];
139
- return snapshot.phases.filter((phase) => phase.status === "pending").map((phase) => phase.name);
140
- }
141
- function deriveStoryStatus(story, activeStory) {
142
- if (story.passes) return "done";
143
- if (story.status === "in_review") return "in_review";
144
- if (activeStory !== null && activeStory === story.id) return "in_progress";
145
- return "backlog";
360
+ function applyPhaseEnd(snapshot, event) {
361
+ const phases = snapshot.phases.map(
362
+ (p) => p.name === event.phase ? {
363
+ ...p,
364
+ status: event.success ? "completed" : "failed",
365
+ endedAt: event.at,
366
+ durationSeconds: secondsBetween(p.startedAt, event.at),
367
+ error: event.error ? stripVTControlCharacters2(event.error) : null,
368
+ harnessExecutionMs: event.harnessExecutionMs ?? p.harnessExecutionMs,
369
+ orchestrationOverheadMs: event.orchestrationOverheadMs ?? p.orchestrationOverheadMs,
370
+ harnessStartupMs: event.harnessStartupMs ?? p.harnessStartupMs,
371
+ ttftMs: event.ttftMs ?? p.ttftMs,
372
+ attemptCount: event.attemptCount ?? p.attemptCount,
373
+ retryDurationMs: event.retryDurationMs ?? p.retryDurationMs
374
+ } : p
375
+ );
376
+ const phasesCompleted = phases.filter((p) => p.status === "completed").length;
377
+ const stories = !event.success ? snapshot.stories.map(
378
+ (story) => isTerminalStage(story.stage) ? story : transitionStory(story, "failed", event.at, null)
379
+ ) : event.phase === "review" ? snapshot.stories.map(
380
+ (story) => story.passes ? transitionStory(story, "done", event.at, null) : story
381
+ ) : snapshot.stories;
382
+ return {
383
+ ...snapshot,
384
+ currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,
385
+ currentActivity: null,
386
+ phases,
387
+ stories,
388
+ progress: {
389
+ ...snapshot.progress,
390
+ phasesCompleted,
391
+ percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal)
392
+ }
393
+ };
146
394
  }
147
- function deriveStoryStatuses(snapshot) {
148
- const activeStory = snapshot.currentActivity?.story ?? null;
149
- return snapshot.stories.map((story) => {
150
- const status = deriveStoryStatus(story, activeStory);
151
- return status === story.status ? story : { ...story, status };
395
+ function applyIterationStart(snapshot, event) {
396
+ const storyId = event.storyId;
397
+ const stories = snapshot.stories.map((story) => {
398
+ if (story.passes) return story;
399
+ if (story.id === storyId) {
400
+ return transitionStory(story, "executing", event.at, null);
401
+ }
402
+ return transitionStory(story, "pending", event.at, null);
152
403
  });
153
- }
154
- function reduceSessionEvent(snapshot, event, options) {
155
- const next = applyEvent(snapshot, event, options);
156
- if (next === snapshot) return snapshot;
157
- const elapsedSeconds = secondsBetween(next.startedAt, event.at) ?? next.elapsedSeconds;
158
404
  return {
159
- ...next,
160
- updatedAt: event.at,
161
- elapsedSeconds,
162
- stories: deriveStoryStatuses(next),
163
- estimatedRemainingSeconds: estimateRemainingSeconds(next),
164
- errors: next.logs.filter((entry) => entry.level === "error"),
165
- warnings: next.logs.filter((entry) => entry.level === "warn"),
166
- nextSteps: deriveNextSteps(next)
405
+ ...snapshot,
406
+ execution: { ...snapshot.execution, iteration: event.iteration },
407
+ stories,
408
+ // Finally populates the `story` field of the activity payload during
409
+ // execute — today this phase never publishes an `activity` event at
410
+ // all (no streaming), so `currentActivity` stays whatever an earlier
411
+ // phase left it at. Left untouched when storyId is absent (every
412
+ // story already passes, or the caller could not determine one).
413
+ currentActivity: storyId !== void 0 ? { story: storyId, tool: null, detail: null, since: event.at } : snapshot.currentActivity
167
414
  };
168
415
  }
169
- function isTerminalStage(stage) {
170
- return stage === "done" || stage === "failed";
171
- }
172
- function transitionStory(story, stage, at, detail) {
173
- if (story.stage === stage && story.stageDetail === detail) return story;
416
+ function applyCorrectionCycle(snapshot, event) {
417
+ const stageDetail = `Cycle ${event.cycle}/${event.maxCycles}`;
418
+ const stories = snapshot.stories.map(
419
+ (story) => story.passes ? transitionStory(story, "in_correction", event.at, stageDetail) : story
420
+ );
174
421
  return {
175
- ...story,
176
- stage,
177
- stageSince: at,
178
- stageDetail: detail,
179
- history: [...story.history, { at, stage, detail }]
422
+ ...snapshot,
423
+ execution: {
424
+ ...snapshot.execution,
425
+ correctionCycle: event.cycle,
426
+ maxCorrectionCycles: event.maxCycles
427
+ },
428
+ stories
180
429
  };
181
430
  }
182
- function deriveStageOnStoriesUpdate(story, before, at) {
183
- if (!story.passes) {
184
- return {
185
- stage: before?.stage ?? "pending",
186
- stageSince: before?.stageSince ?? at,
187
- stageDetail: before?.stageDetail ?? null
188
- };
189
- }
190
- if (before?.passes) {
191
- return { stage: before.stage, stageSince: before.stageSince, stageDetail: before.stageDetail };
431
+
432
+ // src/core/session/reducer-resilience.ts
433
+ function applyResilienceEvent(snapshot, event) {
434
+ switch (event.type) {
435
+ case "retry":
436
+ return {
437
+ ...snapshot,
438
+ execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 }
439
+ };
440
+ case "agent:attempt":
441
+ return {
442
+ ...snapshot,
443
+ resilience: {
444
+ ...snapshot.resilience,
445
+ attempt: event.attempt,
446
+ provider: event.provider,
447
+ model: event.model ?? null,
448
+ lastActivityAt: event.at
449
+ }
450
+ };
451
+ case "failover":
452
+ return {
453
+ ...snapshot,
454
+ resilience: {
455
+ ...snapshot.resilience,
456
+ provider: event.to,
457
+ lastFailureKind: event.reason,
458
+ cooldownUntil: event.cooldownUntil ?? null,
459
+ lastActivityAt: event.at
460
+ }
461
+ };
462
+ case "agent:result":
463
+ return {
464
+ ...snapshot,
465
+ resilience: {
466
+ ...snapshot.resilience,
467
+ provider: event.provider,
468
+ lastFailureKind: event.failureKind ?? snapshot.resilience.lastFailureKind,
469
+ cooldownUntil: event.cooldownUntil ?? null,
470
+ lastActivityAt: event.at
471
+ }
472
+ };
473
+ case "agent:activity":
474
+ return {
475
+ ...snapshot,
476
+ resilience: {
477
+ ...snapshot.resilience,
478
+ provider: event.provider,
479
+ lastActivityAt: event.at
480
+ }
481
+ };
482
+ default: {
483
+ const _exhaustive = event;
484
+ return _exhaustive;
485
+ }
192
486
  }
193
- return { stage: "awaiting_review", stageSince: at, stageDetail: null };
194
487
  }
195
- function applyEvent(snapshot, event, options) {
488
+
489
+ // src/core/session/reducer-session.ts
490
+ import { stripVTControlCharacters as stripVTControlCharacters3 } from "util";
491
+ function applySessionEvent(snapshot, event) {
196
492
  switch (event.type) {
197
493
  case "session:start": {
198
494
  setTelemetrySessionId(event.sessionId);
@@ -255,145 +551,43 @@ function applyEvent(snapshot, event, options) {
255
551
  state: event.state
256
552
  }
257
553
  };
258
- case "phase:start": {
259
- const known = snapshot.phases.some((p) => p.name === event.phase);
260
- const phases = known ? snapshot.phases.map(
261
- (p) => p.name === event.phase ? {
262
- ...p,
263
- status: "running",
264
- startedAt: event.at,
265
- endedAt: null,
266
- error: null
267
- } : p
268
- ) : [
269
- ...snapshot.phases,
270
- {
271
- name: event.phase,
272
- status: "running",
273
- startedAt: event.at,
274
- endedAt: null,
275
- durationSeconds: null,
276
- error: null,
277
- ...emptyPhaseTiming(),
278
- ...emptyUsage()
279
- }
280
- ];
281
- const stories = event.phase === "review" ? snapshot.stories.map(
282
- (story) => story.passes ? transitionStory(story, "in_review", event.at, null) : story
283
- ) : snapshot.stories;
284
- return {
285
- ...snapshot,
286
- currentPhase: event.phase,
287
- phases,
288
- stories,
289
- progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length }
290
- };
291
- }
292
- case "phase:end": {
293
- const phases = snapshot.phases.map(
294
- (p) => p.name === event.phase ? {
295
- ...p,
296
- status: event.success ? "completed" : "failed",
297
- endedAt: event.at,
298
- durationSeconds: secondsBetween(p.startedAt, event.at),
299
- error: event.error ? stripVTControlCharacters(event.error) : null,
300
- harnessExecutionMs: event.harnessExecutionMs ?? p.harnessExecutionMs,
301
- orchestrationOverheadMs: event.orchestrationOverheadMs ?? p.orchestrationOverheadMs,
302
- harnessStartupMs: event.harnessStartupMs ?? p.harnessStartupMs,
303
- ttftMs: event.ttftMs ?? p.ttftMs,
304
- attemptCount: event.attemptCount ?? p.attemptCount,
305
- retryDurationMs: event.retryDurationMs ?? p.retryDurationMs
306
- } : p
307
- );
308
- const phasesCompleted = phases.filter((p) => p.status === "completed").length;
309
- const stories = !event.success ? snapshot.stories.map(
310
- (story) => isTerminalStage(story.stage) ? story : transitionStory(story, "failed", event.at, null)
311
- ) : event.phase === "review" ? snapshot.stories.map(
312
- (story) => story.passes ? transitionStory(story, "done", event.at, null) : story
313
- ) : snapshot.stories;
554
+ case "verify:end":
314
555
  return {
315
556
  ...snapshot,
316
- currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,
317
- currentActivity: null,
318
- phases,
319
- stories,
320
- progress: {
321
- ...snapshot.progress,
322
- phasesCompleted,
323
- percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal)
557
+ verification: {
558
+ verdict: event.verdict,
559
+ level: event.level,
560
+ independence: event.independence
324
561
  }
325
562
  };
326
- }
327
- case "iteration:start": {
328
- const storyId = event.storyId;
329
- const stories = snapshot.stories.map((story) => {
330
- if (story.passes) return story;
331
- if (story.id === storyId) {
332
- return transitionStory(story, "executing", event.at, null);
333
- }
334
- return transitionStory(story, "pending", event.at, null);
335
- });
563
+ case "session:end":
336
564
  return {
337
565
  ...snapshot,
338
- execution: { ...snapshot.execution, iteration: event.iteration },
339
- stories,
340
- // Finally populates the `story` field of the activity payload during
341
- // execute — today this phase never publishes an `activity` event at
342
- // all (no streaming), so `currentActivity` stays whatever an earlier
343
- // phase left it at. Left untouched when storyId is absent (every
344
- // story already passes, or the caller could not determine one).
345
- currentActivity: storyId !== void 0 ? { story: storyId, tool: null, detail: null, since: event.at } : snapshot.currentActivity
566
+ status: event.status,
567
+ endedAt: event.at,
568
+ currentPhase: null,
569
+ currentActivity: null,
570
+ // Close every non-terminal stage: the run is over, so nothing can be
571
+ // 'executing'/'in_review'/'in_correction' any more. A story that never
572
+ // reached `passes` on a run that did not complete is exactly what the
573
+ // 'failed' stage means; anything else settles as 'done'.
574
+ stories: snapshot.stories.map((story) => {
575
+ if (isTerminalStage(story.stage)) return story;
576
+ const stage = event.status === "completed" && story.passes ? "done" : "failed";
577
+ return transitionStory(story, stage, event.at, null);
578
+ }),
579
+ lastError: event.error ? { message: stripVTControlCharacters3(event.error), at: event.at } : snapshot.lastError
346
580
  };
581
+ default: {
582
+ const _exhaustive = event;
583
+ return _exhaustive;
347
584
  }
348
- case "iteration:end":
349
- return { ...snapshot, currentActivity: null };
350
- case "retry":
351
- return {
352
- ...snapshot,
353
- execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 }
354
- };
355
- case "agent:attempt":
356
- return {
357
- ...snapshot,
358
- resilience: {
359
- ...snapshot.resilience,
360
- attempt: event.attempt,
361
- provider: event.provider,
362
- model: event.model ?? null,
363
- lastActivityAt: event.at
364
- }
365
- };
366
- case "failover":
367
- return {
368
- ...snapshot,
369
- resilience: {
370
- ...snapshot.resilience,
371
- provider: event.to,
372
- lastFailureKind: event.reason,
373
- cooldownUntil: event.cooldownUntil ?? null,
374
- lastActivityAt: event.at
375
- }
376
- };
377
- case "agent:result":
378
- return {
379
- ...snapshot,
380
- resilience: {
381
- ...snapshot.resilience,
382
- provider: event.provider,
383
- lastFailureKind: event.failureKind ?? snapshot.resilience.lastFailureKind,
384
- cooldownUntil: event.cooldownUntil ?? null,
385
- lastActivityAt: event.at
386
- }
387
- };
388
- case "agent:activity":
389
- return {
390
- ...snapshot,
391
- resilience: {
392
- ...snapshot.resilience,
393
- provider: event.provider,
394
- lastActivityAt: event.at
395
- }
396
- };
585
+ }
586
+ }
587
+
588
+ // src/core/session/reducer-story.ts
589
+ function applyStoryEvent(snapshot, event) {
590
+ switch (event.type) {
397
591
  case "stories:update": {
398
592
  const previous = new Map(snapshot.stories.map((story) => [story.id, story]));
399
593
  const stories = event.stories.map((story) => {
@@ -450,143 +644,68 @@ function applyEvent(snapshot, event, options) {
450
644
  resilience: { ...snapshot.resilience, lastActivityAt: event.at }
451
645
  };
452
646
  }
453
- case "log": {
454
- const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
455
- const entry = {
456
- at: event.at,
457
- level: event.level,
458
- message: stripVTControlCharacters(event.message)
459
- };
460
- const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));
461
- return { ...snapshot, logs };
462
- }
463
- case "process:output": {
464
- const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
465
- const entry = {
466
- at: event.at,
467
- phase: event.phase,
468
- executionId: event.executionId,
469
- provider: event.provider,
470
- stream: event.stream,
471
- message: stripVTControlCharacters(event.message)
472
- };
473
- return {
474
- ...snapshot,
475
- processLogs: [...snapshot.processLogs, entry].slice(-Math.max(1, limit))
476
- };
477
- }
478
- case "execution:update": {
479
- const index = snapshot.executions.findIndex((entry) => entry.id === event.execution.id);
480
- const executions = [...snapshot.executions];
481
- if (index === -1) executions.push(event.execution);
482
- else executions[index] = event.execution;
483
- return { ...snapshot, executions };
484
- }
485
- case "git:update":
486
- return {
487
- ...snapshot,
488
- git: {
489
- branch: event.branch ?? snapshot.git.branch,
490
- baseBranch: event.baseBranch ?? snapshot.git.baseBranch,
491
- branchCreated: reported(event.branchCreated, snapshot.git.branchCreated),
492
- startCommit: snapshot.git.startCommit,
493
- commits: event.commits ?? snapshot.git.commits
494
- },
495
- repository: {
496
- // `undefined` is "not collected", so the previous value stands; an
497
- // explicit `null` is "collected and unavailable" and overwrites it.
498
- name: reported(event.repositoryName, snapshot.repository.name),
499
- remoteUrl: reported(event.remoteUrl, snapshot.repository.remoteUrl),
500
- branch: event.branch ?? snapshot.repository.branch,
501
- headCommit: reported(event.headCommit, snapshot.repository.headCommit),
502
- root: reported(event.repositoryRoot, snapshot.repository.root)
503
- },
504
- pullRequests: event.pullRequests ?? snapshot.pullRequests
505
- };
506
- case "correction:cycle": {
507
- const stageDetail = `Cycle ${event.cycle}/${event.maxCycles}`;
508
- const stories = snapshot.stories.map(
509
- (story) => story.passes ? transitionStory(story, "in_correction", event.at, stageDetail) : story
510
- );
511
- return {
512
- ...snapshot,
513
- execution: {
514
- ...snapshot.execution,
515
- correctionCycle: event.cycle,
516
- maxCorrectionCycles: event.maxCycles
517
- },
518
- stories
519
- };
520
- }
521
- case "metrics:update": {
522
- if (event.scope === "story") {
523
- if (event.storyId === void 0) return snapshot;
524
- if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;
525
- return {
526
- ...snapshot,
527
- stories: snapshot.stories.map(
528
- (story) => story.id === event.storyId ? {
529
- ...accumulateUsage(story, event),
530
- durationSeconds: accumulate(story.durationSeconds, event.durationSeconds)
531
- } : story
532
- )
533
- };
534
- }
535
- if (event.phase === void 0) return snapshot;
536
- if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;
537
- return {
538
- ...snapshot,
539
- // durationSeconds is deliberately untouched here: phase:start and
540
- // phase:end remain the single source of a phase's wall-clock time.
541
- phases: snapshot.phases.map(
542
- (phase) => phase.name === event.phase ? accumulateUsage(phase, event) : phase
543
- ),
544
- metrics: {
545
- totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),
546
- totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),
547
- totalCacheReadTokens: accumulate(
548
- snapshot.metrics.totalCacheReadTokens,
549
- event.cacheReadTokens
550
- ),
551
- totalCacheCreationTokens: accumulate(
552
- snapshot.metrics.totalCacheCreationTokens,
553
- event.cacheCreationTokens
554
- ),
555
- totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd)
556
- }
557
- };
647
+ default: {
648
+ const _exhaustive = event;
649
+ return _exhaustive;
558
650
  }
559
- case "verify:end":
560
- return {
561
- ...snapshot,
562
- verification: {
563
- verdict: event.verdict,
564
- level: event.level,
565
- independence: event.independence
566
- }
567
- };
651
+ }
652
+ }
653
+
654
+ // src/core/session/reducer.ts
655
+ function reduceSessionEvent(snapshot, event, options) {
656
+ const next = applyEvent(snapshot, event, options);
657
+ if (next === snapshot) return snapshot;
658
+ const elapsedSeconds = secondsBetween(next.startedAt, event.at) ?? next.elapsedSeconds;
659
+ return {
660
+ ...next,
661
+ updatedAt: event.at,
662
+ elapsedSeconds,
663
+ stories: deriveStoryStatuses(next),
664
+ estimatedRemainingSeconds: estimateRemainingSeconds(next),
665
+ errors: next.logs.filter((entry) => entry.level === "error"),
666
+ warnings: next.logs.filter((entry) => entry.level === "warn"),
667
+ nextSteps: deriveNextSteps(next)
668
+ };
669
+ }
670
+ function applyEvent(snapshot, event, options) {
671
+ switch (event.type) {
672
+ case "session:start":
568
673
  case "session:end":
569
- return {
570
- ...snapshot,
571
- status: event.status,
572
- endedAt: event.at,
573
- currentPhase: null,
574
- currentActivity: null,
575
- // Close every non-terminal stage: the run is over, so nothing can be
576
- // 'executing'/'in_review'/'in_correction' any more. A story that never
577
- // reached `passes` on a run that did not complete is exactly what the
578
- // 'failed' stage means; anything else settles as 'done'.
579
- stories: snapshot.stories.map((story) => {
580
- if (isTerminalStage(story.stage)) return story;
581
- const stage = event.status === "completed" && story.passes ? "done" : "failed";
582
- return transitionStory(story, stage, event.at, null);
583
- }),
584
- lastError: event.error ? { message: stripVTControlCharacters(event.error), at: event.at } : snapshot.lastError
585
- };
586
- default:
674
+ case "issue:update":
675
+ case "verify:end":
676
+ return applySessionEvent(snapshot, event);
677
+ case "phase:start":
678
+ case "phase:end":
679
+ case "iteration:start":
680
+ case "iteration:end":
681
+ case "correction:cycle":
682
+ return applyPhaseEvent(snapshot, event);
683
+ case "stories:update":
684
+ case "activity":
685
+ return applyStoryEvent(snapshot, event);
686
+ case "metrics:update":
687
+ return applyMetricsEvent(snapshot, event);
688
+ case "git:update":
689
+ return applyGitEvent(snapshot, event);
690
+ case "log":
691
+ case "process:output":
692
+ case "execution:update":
693
+ return applyLogEvent(snapshot, event, options);
694
+ case "retry":
695
+ case "agent:attempt":
696
+ case "failover":
697
+ case "agent:result":
698
+ case "agent:activity":
699
+ return applyResilienceEvent(snapshot, event);
700
+ default: {
701
+ const _exhaustive = event;
702
+ void _exhaustive;
587
703
  return snapshot;
704
+ }
588
705
  }
589
706
  }
707
+
708
+ // src/core/session/publishers.ts
590
709
  var NullPublisher = class {
591
710
  empty = createInitialSnapshot();
592
711
  publish(_event) {
@@ -703,7 +822,7 @@ var FilePublisher = class extends MemoryPublisher {
703
822
  const payload = `${JSON.stringify(this.state, null, 2)}
704
823
  `;
705
824
  try {
706
- await atomicWriteFile(this.filePath, payload);
825
+ await writeFileAtomic(this.filePath, payload);
707
826
  this.lastWrittenVersion = version;
708
827
  } catch (err) {
709
828
  this.warnOnce(err);
@@ -747,12 +866,6 @@ var FilePublisher = class extends MemoryPublisher {
747
866
  await this.flush();
748
867
  }
749
868
  };
750
- async function atomicWriteFile(path, content) {
751
- await mkdir(dirname(path), { recursive: true });
752
- const tmpFile = `${path}.tmp`;
753
- await writeFile(tmpFile, content, "utf-8");
754
- await rename(tmpFile, path);
755
- }
756
869
 
757
870
  // src/core/session-publisher.ts
758
871
  var NULL_PUBLISHER = new NullPublisher();
@@ -773,4 +886,4 @@ export {
773
886
  setSessionPublisher,
774
887
  getSessionPublisher
775
888
  };
776
- //# sourceMappingURL=chunk-ROIIEVZH.js.map
889
+ //# sourceMappingURL=chunk-DUS5ZNKL.js.map