issue-flow 0.15.0 → 0.17.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 (169) hide show
  1. package/dist/agent-TB6W7PT7.js +30 -0
  2. package/dist/{analyze-O2PYDIFK.js → analyze-6M3KEOX7.js} +22 -22
  3. package/dist/{apply-SSU6QCD4.js → apply-ADB3ZK7W.js} +14 -14
  4. package/dist/{bench-ODFPAGW6.js → bench-FM7I7EIV.js} +23 -23
  5. package/dist/{chunk-BMHGRYL6.js → chunk-2SUFAE6X.js} +67 -24
  6. package/dist/chunk-2SUFAE6X.js.map +1 -0
  7. package/dist/{chunk-QRCGW2ZO.js → chunk-32JSYXEK.js} +8 -8
  8. package/dist/chunk-3R27MJ5R.js +25 -0
  9. package/dist/chunk-3R27MJ5R.js.map +1 -0
  10. package/dist/{chunk-MGCOQU6N.js → chunk-6YQT3SSM.js} +4 -4
  11. package/dist/chunk-6YQT3SSM.js.map +1 -0
  12. package/dist/{chunk-A35HE4V7.js → chunk-7F7LGMVO.js} +3 -3
  13. package/dist/{chunk-RPXDBX6R.js → chunk-A4H7P7T7.js} +102 -102
  14. package/dist/chunk-A4H7P7T7.js.map +1 -0
  15. package/dist/{chunk-2ZAM45PZ.js → chunk-ANI2NTR4.js} +9 -9
  16. package/dist/{chunk-ZPIVCM7Q.js → chunk-B3TSS7BT.js} +3 -3
  17. package/dist/chunk-C5TL7T4V.js +27 -0
  18. package/dist/chunk-C5TL7T4V.js.map +1 -0
  19. package/dist/chunk-D6ZBGJ3L.js +321 -0
  20. package/dist/chunk-D6ZBGJ3L.js.map +1 -0
  21. package/dist/{chunk-ZXPASFHG.js → chunk-DGRTJD5R.js} +3 -3
  22. package/dist/{chunk-6QYMSNVA.js → chunk-DUS5ZNKL.js} +476 -362
  23. package/dist/chunk-DUS5ZNKL.js.map +1 -0
  24. package/dist/{chunk-VR34TXHF.js → chunk-DV7HUC4Z.js} +7 -7
  25. package/dist/{chunk-6SRE4KD7.js → chunk-E6KXCV3T.js} +4 -4
  26. package/dist/{chunk-HGSYD57B.js → chunk-HJBF4WD2.js} +1656 -1070
  27. package/dist/chunk-HJBF4WD2.js.map +1 -0
  28. package/dist/{chunk-PCN7ZQND.js → chunk-HLAPOUEM.js} +8 -8
  29. package/dist/{chunk-WM3Y7Q4A.js → chunk-HY5HLFDU.js} +630 -571
  30. package/dist/chunk-HY5HLFDU.js.map +1 -0
  31. package/dist/{chunk-XDKGA6UX.js → chunk-ICM463EO.js} +64 -14
  32. package/dist/chunk-ICM463EO.js.map +1 -0
  33. package/dist/{chunk-MEFY4B7T.js → chunk-ISZ54SVE.js} +9 -9
  34. package/dist/{chunk-ARWNYXFL.js → chunk-KZ5EY5XX.js} +12 -12
  35. package/dist/{chunk-ZFIXEYUJ.js → chunk-KZXQWC7U.js} +32 -20
  36. package/dist/chunk-KZXQWC7U.js.map +1 -0
  37. package/dist/{chunk-KAHQ6F5C.js → chunk-LPWPFIIB.js} +11 -11
  38. package/dist/{chunk-FK4IDY6P.js → chunk-MI5TREN3.js} +9 -9
  39. package/dist/{chunk-UNQYMR3H.js → chunk-PPZ3K4YM.js} +2 -2
  40. package/dist/{chunk-F4OK7KEP.js → chunk-QMIQR7EE.js} +10 -10
  41. package/dist/{chunk-AYYMW33T.js → chunk-QPGEFLMY.js} +2 -2
  42. package/dist/chunk-RCZJQUTC.js +196 -0
  43. package/dist/chunk-RCZJQUTC.js.map +1 -0
  44. package/dist/{chunk-MROS3SKL.js → chunk-RNFUVKC3.js} +3 -3
  45. package/dist/{chunk-RWEXK55D.js → chunk-RYOUZ6EK.js} +2 -2
  46. package/dist/{chunk-6E2PEBFT.js → chunk-SGTTR4XW.js} +10 -10
  47. package/dist/{chunk-XQHFC62U.js → chunk-SOM4QA6C.js} +3 -3
  48. package/dist/{chunk-5ZKBIXET.js → chunk-UK4E2FUA.js} +132 -34
  49. package/dist/chunk-UK4E2FUA.js.map +1 -0
  50. package/dist/{chunk-6FXOU2KR.js → chunk-UWDCIFTP.js} +7 -7
  51. package/dist/{chunk-XG2NYDRA.js → chunk-VE3IYNDN.js} +3 -3
  52. package/dist/{chunk-3BWZO4YI.js → chunk-WCCZJL7W.js} +2 -2
  53. package/dist/{chunk-NVZMBXTE.js → chunk-XZUEFTUP.js} +6 -6
  54. package/dist/{chunk-TBDP6QPJ.js → chunk-YII4U7ZD.js} +10 -10
  55. package/dist/{chunk-TBDP6QPJ.js.map → chunk-YII4U7ZD.js.map} +1 -1
  56. package/dist/{chunk-4RXYFWP7.js → chunk-YUNBOUQH.js} +6 -6
  57. package/dist/{chunk-GSZCQUCY.js → chunk-YVPZUN6X.js} +2 -2
  58. package/dist/{chunk-XRLRKTQA.js → chunk-Z5O2XBWA.js} +14 -14
  59. package/dist/cli.js +61 -52
  60. package/dist/cli.js.map +1 -1
  61. package/dist/{config-UMP6DBJB.js → config-KGZUPAUP.js} +13 -13
  62. package/dist/{conventions-DKH7AE4T.js → conventions-RYN7OXJN.js} +15 -15
  63. package/dist/{diagnostics-GOGCLTRS.js → diagnostics-HTCWF7F4.js} +7 -7
  64. package/dist/execute-QL3ZQIAP.js +37 -0
  65. package/dist/{generate-AGBT7EHX.js → generate-CX6KNHZS.js} +22 -22
  66. package/dist/{git-AC542GU2.js → git-A2IP32PQ.js} +2 -2
  67. package/dist/init-PDVQDRS6.js +24 -0
  68. package/dist/{operations-JQYK2EMT.js → operations-CAKIT2BO.js} +15 -15
  69. package/dist/{permissions-6HL52K54.js → permissions-G4XCAESF.js} +4 -4
  70. package/dist/plan-PRORGS3L.js +34 -0
  71. package/dist/{policy-IMRXGG7X.js → policy-WX6O2JID.js} +14 -14
  72. package/dist/pr-27RMQI5F.js +39 -0
  73. package/dist/pr-review-VNESMAMT.js +32 -0
  74. package/dist/prd-ZNC4T5BS.js +33 -0
  75. package/dist/{ps-KUUZ6TUL.js → ps-76L6ZAZL.js} +12 -12
  76. package/dist/{recorder-BJ2KIAWT.js → recorder-N7U66HCO.js} +7 -7
  77. package/dist/{registry-SB7XTMGE.js → registry-XLHHYLTR.js} +5 -5
  78. package/dist/{resume-QNLQSCR5.js → resume-VLWIE264.js} +40 -38
  79. package/dist/{resume-QNLQSCR5.js.map → resume-VLWIE264.js.map} +1 -1
  80. package/dist/review-7CREJDKB.js +35 -0
  81. package/dist/routing-VCDYHSXA.js +33 -0
  82. package/dist/run-3MPORLF5.js +56 -0
  83. package/dist/{session-publisher-T7EI3CIP.js → session-publisher-KAJQRHNZ.js} +3 -2
  84. package/dist/{state-manager-ZEPWTCA6.js → state-manager-UPHO2EA2.js} +4 -4
  85. package/dist/state-manager-UPHO2EA2.js.map +1 -0
  86. package/dist/{usage-QL563I7F.js → usage-U7QNGCPK.js} +12 -12
  87. package/dist/{web-PPSMNTQ6.js → web-HUNDHCJ4.js} +21 -18
  88. package/dist/{web-PPSMNTQ6.js.map → web-HUNDHCJ4.js.map} +1 -1
  89. package/package.json +6 -5
  90. package/web/AGENTS.md +15 -11
  91. package/web/public/app.css +51 -0
  92. package/web/public/app.js +325 -56
  93. package/web/public/index.html +6 -2
  94. package/dist/agent-AT64RXTA.js +0 -30
  95. package/dist/chunk-5ZKBIXET.js.map +0 -1
  96. package/dist/chunk-6QYMSNVA.js.map +0 -1
  97. package/dist/chunk-7Z4Y3VKM.js +0 -26
  98. package/dist/chunk-7Z4Y3VKM.js.map +0 -1
  99. package/dist/chunk-BMHGRYL6.js.map +0 -1
  100. package/dist/chunk-HGSYD57B.js.map +0 -1
  101. package/dist/chunk-MGCOQU6N.js.map +0 -1
  102. package/dist/chunk-RPXDBX6R.js.map +0 -1
  103. package/dist/chunk-WM3Y7Q4A.js.map +0 -1
  104. package/dist/chunk-XDKGA6UX.js.map +0 -1
  105. package/dist/chunk-XLVGPXNL.js +0 -180
  106. package/dist/chunk-XLVGPXNL.js.map +0 -1
  107. package/dist/chunk-ZFIXEYUJ.js.map +0 -1
  108. package/dist/execute-E3BRMDAQ.js +0 -35
  109. package/dist/init-LLBVJNPE.js +0 -24
  110. package/dist/plan-HSLWDJEZ.js +0 -34
  111. package/dist/pr-6TCZXG6K.js +0 -39
  112. package/dist/pr-review-6MWJO2NP.js +0 -32
  113. package/dist/prd-D6YDYAFX.js +0 -33
  114. package/dist/review-3KJ4BH2V.js +0 -35
  115. package/dist/routing-TN7GLMGU.js +0 -94
  116. package/dist/routing-TN7GLMGU.js.map +0 -1
  117. package/dist/run-5EHIFF2V.js +0 -54
  118. /package/dist/{agent-AT64RXTA.js.map → agent-TB6W7PT7.js.map} +0 -0
  119. /package/dist/{analyze-O2PYDIFK.js.map → analyze-6M3KEOX7.js.map} +0 -0
  120. /package/dist/{apply-SSU6QCD4.js.map → apply-ADB3ZK7W.js.map} +0 -0
  121. /package/dist/{bench-ODFPAGW6.js.map → bench-FM7I7EIV.js.map} +0 -0
  122. /package/dist/{chunk-QRCGW2ZO.js.map → chunk-32JSYXEK.js.map} +0 -0
  123. /package/dist/{chunk-A35HE4V7.js.map → chunk-7F7LGMVO.js.map} +0 -0
  124. /package/dist/{chunk-2ZAM45PZ.js.map → chunk-ANI2NTR4.js.map} +0 -0
  125. /package/dist/{chunk-ZPIVCM7Q.js.map → chunk-B3TSS7BT.js.map} +0 -0
  126. /package/dist/{chunk-ZXPASFHG.js.map → chunk-DGRTJD5R.js.map} +0 -0
  127. /package/dist/{chunk-VR34TXHF.js.map → chunk-DV7HUC4Z.js.map} +0 -0
  128. /package/dist/{chunk-6SRE4KD7.js.map → chunk-E6KXCV3T.js.map} +0 -0
  129. /package/dist/{chunk-PCN7ZQND.js.map → chunk-HLAPOUEM.js.map} +0 -0
  130. /package/dist/{chunk-MEFY4B7T.js.map → chunk-ISZ54SVE.js.map} +0 -0
  131. /package/dist/{chunk-ARWNYXFL.js.map → chunk-KZ5EY5XX.js.map} +0 -0
  132. /package/dist/{chunk-KAHQ6F5C.js.map → chunk-LPWPFIIB.js.map} +0 -0
  133. /package/dist/{chunk-FK4IDY6P.js.map → chunk-MI5TREN3.js.map} +0 -0
  134. /package/dist/{chunk-UNQYMR3H.js.map → chunk-PPZ3K4YM.js.map} +0 -0
  135. /package/dist/{chunk-F4OK7KEP.js.map → chunk-QMIQR7EE.js.map} +0 -0
  136. /package/dist/{chunk-AYYMW33T.js.map → chunk-QPGEFLMY.js.map} +0 -0
  137. /package/dist/{chunk-MROS3SKL.js.map → chunk-RNFUVKC3.js.map} +0 -0
  138. /package/dist/{chunk-RWEXK55D.js.map → chunk-RYOUZ6EK.js.map} +0 -0
  139. /package/dist/{chunk-6E2PEBFT.js.map → chunk-SGTTR4XW.js.map} +0 -0
  140. /package/dist/{chunk-XQHFC62U.js.map → chunk-SOM4QA6C.js.map} +0 -0
  141. /package/dist/{chunk-6FXOU2KR.js.map → chunk-UWDCIFTP.js.map} +0 -0
  142. /package/dist/{chunk-XG2NYDRA.js.map → chunk-VE3IYNDN.js.map} +0 -0
  143. /package/dist/{chunk-3BWZO4YI.js.map → chunk-WCCZJL7W.js.map} +0 -0
  144. /package/dist/{chunk-NVZMBXTE.js.map → chunk-XZUEFTUP.js.map} +0 -0
  145. /package/dist/{chunk-4RXYFWP7.js.map → chunk-YUNBOUQH.js.map} +0 -0
  146. /package/dist/{chunk-GSZCQUCY.js.map → chunk-YVPZUN6X.js.map} +0 -0
  147. /package/dist/{chunk-XRLRKTQA.js.map → chunk-Z5O2XBWA.js.map} +0 -0
  148. /package/dist/{config-UMP6DBJB.js.map → config-KGZUPAUP.js.map} +0 -0
  149. /package/dist/{conventions-DKH7AE4T.js.map → conventions-RYN7OXJN.js.map} +0 -0
  150. /package/dist/{diagnostics-GOGCLTRS.js.map → diagnostics-HTCWF7F4.js.map} +0 -0
  151. /package/dist/{execute-E3BRMDAQ.js.map → execute-QL3ZQIAP.js.map} +0 -0
  152. /package/dist/{generate-AGBT7EHX.js.map → generate-CX6KNHZS.js.map} +0 -0
  153. /package/dist/{git-AC542GU2.js.map → git-A2IP32PQ.js.map} +0 -0
  154. /package/dist/{init-LLBVJNPE.js.map → init-PDVQDRS6.js.map} +0 -0
  155. /package/dist/{operations-JQYK2EMT.js.map → operations-CAKIT2BO.js.map} +0 -0
  156. /package/dist/{permissions-6HL52K54.js.map → permissions-G4XCAESF.js.map} +0 -0
  157. /package/dist/{plan-HSLWDJEZ.js.map → plan-PRORGS3L.js.map} +0 -0
  158. /package/dist/{policy-IMRXGG7X.js.map → policy-WX6O2JID.js.map} +0 -0
  159. /package/dist/{pr-6TCZXG6K.js.map → pr-27RMQI5F.js.map} +0 -0
  160. /package/dist/{pr-review-6MWJO2NP.js.map → pr-review-VNESMAMT.js.map} +0 -0
  161. /package/dist/{prd-D6YDYAFX.js.map → prd-ZNC4T5BS.js.map} +0 -0
  162. /package/dist/{ps-KUUZ6TUL.js.map → ps-76L6ZAZL.js.map} +0 -0
  163. /package/dist/{recorder-BJ2KIAWT.js.map → recorder-N7U66HCO.js.map} +0 -0
  164. /package/dist/{registry-SB7XTMGE.js.map → registry-XLHHYLTR.js.map} +0 -0
  165. /package/dist/{review-3KJ4BH2V.js.map → review-7CREJDKB.js.map} +0 -0
  166. /package/dist/{run-5EHIFF2V.js.map → routing-VCDYHSXA.js.map} +0 -0
  167. /package/dist/{session-publisher-T7EI3CIP.js.map → run-3MPORLF5.js.map} +0 -0
  168. /package/dist/{state-manager-ZEPWTCA6.js.map → session-publisher-KAJQRHNZ.js.map} +0 -0
  169. /package/dist/{usage-QL563I7F.js.map → usage-U7QNGCPK.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);
@@ -234,7 +530,8 @@ function applyEvent(snapshot, event, options) {
234
530
  node: event.environment.node,
235
531
  platform: event.environment.platform,
236
532
  agent: event.environment.agent ?? null,
237
- model: event.environment.model ?? null
533
+ model: event.environment.model ?? null,
534
+ cliVersion: event.environment.cliVersion ?? null
238
535
  } : null
239
536
  };
240
537
  }
@@ -254,145 +551,43 @@ function applyEvent(snapshot, event, options) {
254
551
  state: event.state
255
552
  }
256
553
  };
257
- case "phase:start": {
258
- const known = snapshot.phases.some((p) => p.name === event.phase);
259
- const phases = known ? snapshot.phases.map(
260
- (p) => p.name === event.phase ? {
261
- ...p,
262
- status: "running",
263
- startedAt: event.at,
264
- endedAt: null,
265
- error: null
266
- } : p
267
- ) : [
268
- ...snapshot.phases,
269
- {
270
- name: event.phase,
271
- status: "running",
272
- startedAt: event.at,
273
- endedAt: null,
274
- durationSeconds: null,
275
- error: null,
276
- ...emptyPhaseTiming(),
277
- ...emptyUsage()
278
- }
279
- ];
280
- const stories = event.phase === "review" ? snapshot.stories.map(
281
- (story) => story.passes ? transitionStory(story, "in_review", event.at, null) : story
282
- ) : snapshot.stories;
283
- return {
284
- ...snapshot,
285
- currentPhase: event.phase,
286
- phases,
287
- stories,
288
- progress: known ? snapshot.progress : { ...snapshot.progress, phasesTotal: phases.length }
289
- };
290
- }
291
- case "phase:end": {
292
- const phases = snapshot.phases.map(
293
- (p) => p.name === event.phase ? {
294
- ...p,
295
- status: event.success ? "completed" : "failed",
296
- endedAt: event.at,
297
- durationSeconds: secondsBetween(p.startedAt, event.at),
298
- error: event.error ? stripVTControlCharacters(event.error) : null,
299
- harnessExecutionMs: event.harnessExecutionMs ?? p.harnessExecutionMs,
300
- orchestrationOverheadMs: event.orchestrationOverheadMs ?? p.orchestrationOverheadMs,
301
- harnessStartupMs: event.harnessStartupMs ?? p.harnessStartupMs,
302
- ttftMs: event.ttftMs ?? p.ttftMs,
303
- attemptCount: event.attemptCount ?? p.attemptCount,
304
- retryDurationMs: event.retryDurationMs ?? p.retryDurationMs
305
- } : p
306
- );
307
- const phasesCompleted = phases.filter((p) => p.status === "completed").length;
308
- const stories = !event.success ? snapshot.stories.map(
309
- (story) => isTerminalStage(story.stage) ? story : transitionStory(story, "failed", event.at, null)
310
- ) : event.phase === "review" ? snapshot.stories.map(
311
- (story) => story.passes ? transitionStory(story, "done", event.at, null) : story
312
- ) : snapshot.stories;
554
+ case "verify:end":
313
555
  return {
314
556
  ...snapshot,
315
- currentPhase: snapshot.currentPhase === event.phase ? null : snapshot.currentPhase,
316
- currentActivity: null,
317
- phases,
318
- stories,
319
- progress: {
320
- ...snapshot.progress,
321
- phasesCompleted,
322
- percent: computePercent(phasesCompleted, snapshot.progress.phasesTotal)
557
+ verification: {
558
+ verdict: event.verdict,
559
+ level: event.level,
560
+ independence: event.independence
323
561
  }
324
562
  };
325
- }
326
- case "iteration:start": {
327
- const storyId = event.storyId;
328
- const stories = snapshot.stories.map((story) => {
329
- if (story.passes) return story;
330
- if (story.id === storyId) {
331
- return transitionStory(story, "executing", event.at, null);
332
- }
333
- return transitionStory(story, "pending", event.at, null);
334
- });
563
+ case "session:end":
335
564
  return {
336
565
  ...snapshot,
337
- execution: { ...snapshot.execution, iteration: event.iteration },
338
- stories,
339
- // Finally populates the `story` field of the activity payload during
340
- // execute — today this phase never publishes an `activity` event at
341
- // all (no streaming), so `currentActivity` stays whatever an earlier
342
- // phase left it at. Left untouched when storyId is absent (every
343
- // story already passes, or the caller could not determine one).
344
- 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
345
580
  };
581
+ default: {
582
+ const _exhaustive = event;
583
+ return _exhaustive;
346
584
  }
347
- case "iteration:end":
348
- return { ...snapshot, currentActivity: null };
349
- case "retry":
350
- return {
351
- ...snapshot,
352
- execution: { ...snapshot.execution, retries: snapshot.execution.retries + 1 }
353
- };
354
- case "agent:attempt":
355
- return {
356
- ...snapshot,
357
- resilience: {
358
- ...snapshot.resilience,
359
- attempt: event.attempt,
360
- provider: event.provider,
361
- model: event.model ?? null,
362
- lastActivityAt: event.at
363
- }
364
- };
365
- case "failover":
366
- return {
367
- ...snapshot,
368
- resilience: {
369
- ...snapshot.resilience,
370
- provider: event.to,
371
- lastFailureKind: event.reason,
372
- cooldownUntil: event.cooldownUntil ?? null,
373
- lastActivityAt: event.at
374
- }
375
- };
376
- case "agent:result":
377
- return {
378
- ...snapshot,
379
- resilience: {
380
- ...snapshot.resilience,
381
- provider: event.provider,
382
- lastFailureKind: event.failureKind ?? snapshot.resilience.lastFailureKind,
383
- cooldownUntil: event.cooldownUntil ?? null,
384
- lastActivityAt: event.at
385
- }
386
- };
387
- case "agent:activity":
388
- return {
389
- ...snapshot,
390
- resilience: {
391
- ...snapshot.resilience,
392
- provider: event.provider,
393
- lastActivityAt: event.at
394
- }
395
- };
585
+ }
586
+ }
587
+
588
+ // src/core/session/reducer-story.ts
589
+ function applyStoryEvent(snapshot, event) {
590
+ switch (event.type) {
396
591
  case "stories:update": {
397
592
  const previous = new Map(snapshot.stories.map((story) => [story.id, story]));
398
593
  const stories = event.stories.map((story) => {
@@ -449,143 +644,68 @@ function applyEvent(snapshot, event, options) {
449
644
  resilience: { ...snapshot.resilience, lastActivityAt: event.at }
450
645
  };
451
646
  }
452
- case "log": {
453
- const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
454
- const entry = {
455
- at: event.at,
456
- level: event.level,
457
- message: stripVTControlCharacters(event.message)
458
- };
459
- const logs = [...snapshot.logs, entry].slice(-Math.max(1, limit));
460
- return { ...snapshot, logs };
461
- }
462
- case "process:output": {
463
- const limit = options?.logLimit ?? DEFAULT_LOG_LIMIT;
464
- const entry = {
465
- at: event.at,
466
- phase: event.phase,
467
- executionId: event.executionId,
468
- provider: event.provider,
469
- stream: event.stream,
470
- message: stripVTControlCharacters(event.message)
471
- };
472
- return {
473
- ...snapshot,
474
- processLogs: [...snapshot.processLogs, entry].slice(-Math.max(1, limit))
475
- };
476
- }
477
- case "execution:update": {
478
- const index = snapshot.executions.findIndex((entry) => entry.id === event.execution.id);
479
- const executions = [...snapshot.executions];
480
- if (index === -1) executions.push(event.execution);
481
- else executions[index] = event.execution;
482
- return { ...snapshot, executions };
483
- }
484
- case "git:update":
485
- return {
486
- ...snapshot,
487
- git: {
488
- branch: event.branch ?? snapshot.git.branch,
489
- baseBranch: event.baseBranch ?? snapshot.git.baseBranch,
490
- branchCreated: reported(event.branchCreated, snapshot.git.branchCreated),
491
- startCommit: snapshot.git.startCommit,
492
- commits: event.commits ?? snapshot.git.commits
493
- },
494
- repository: {
495
- // `undefined` is "not collected", so the previous value stands; an
496
- // explicit `null` is "collected and unavailable" and overwrites it.
497
- name: reported(event.repositoryName, snapshot.repository.name),
498
- remoteUrl: reported(event.remoteUrl, snapshot.repository.remoteUrl),
499
- branch: event.branch ?? snapshot.repository.branch,
500
- headCommit: reported(event.headCommit, snapshot.repository.headCommit),
501
- root: reported(event.repositoryRoot, snapshot.repository.root)
502
- },
503
- pullRequests: event.pullRequests ?? snapshot.pullRequests
504
- };
505
- case "correction:cycle": {
506
- const stageDetail = `Cycle ${event.cycle}/${event.maxCycles}`;
507
- const stories = snapshot.stories.map(
508
- (story) => story.passes ? transitionStory(story, "in_correction", event.at, stageDetail) : story
509
- );
510
- return {
511
- ...snapshot,
512
- execution: {
513
- ...snapshot.execution,
514
- correctionCycle: event.cycle,
515
- maxCorrectionCycles: event.maxCycles
516
- },
517
- stories
518
- };
519
- }
520
- case "metrics:update": {
521
- if (event.scope === "story") {
522
- if (event.storyId === void 0) return snapshot;
523
- if (!snapshot.stories.some((s) => s.id === event.storyId)) return snapshot;
524
- return {
525
- ...snapshot,
526
- stories: snapshot.stories.map(
527
- (story) => story.id === event.storyId ? {
528
- ...accumulateUsage(story, event),
529
- durationSeconds: accumulate(story.durationSeconds, event.durationSeconds)
530
- } : story
531
- )
532
- };
533
- }
534
- if (event.phase === void 0) return snapshot;
535
- if (!snapshot.phases.some((p) => p.name === event.phase)) return snapshot;
536
- return {
537
- ...snapshot,
538
- // durationSeconds is deliberately untouched here: phase:start and
539
- // phase:end remain the single source of a phase's wall-clock time.
540
- phases: snapshot.phases.map(
541
- (phase) => phase.name === event.phase ? accumulateUsage(phase, event) : phase
542
- ),
543
- metrics: {
544
- totalInputTokens: accumulate(snapshot.metrics.totalInputTokens, event.inputTokens),
545
- totalOutputTokens: accumulate(snapshot.metrics.totalOutputTokens, event.outputTokens),
546
- totalCacheReadTokens: accumulate(
547
- snapshot.metrics.totalCacheReadTokens,
548
- event.cacheReadTokens
549
- ),
550
- totalCacheCreationTokens: accumulate(
551
- snapshot.metrics.totalCacheCreationTokens,
552
- event.cacheCreationTokens
553
- ),
554
- totalCostUsd: accumulate(snapshot.metrics.totalCostUsd, event.costUsd)
555
- }
556
- };
647
+ default: {
648
+ const _exhaustive = event;
649
+ return _exhaustive;
557
650
  }
558
- case "verify:end":
559
- return {
560
- ...snapshot,
561
- verification: {
562
- verdict: event.verdict,
563
- level: event.level,
564
- independence: event.independence
565
- }
566
- };
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":
567
673
  case "session:end":
568
- return {
569
- ...snapshot,
570
- status: event.status,
571
- endedAt: event.at,
572
- currentPhase: null,
573
- currentActivity: null,
574
- // Close every non-terminal stage: the run is over, so nothing can be
575
- // 'executing'/'in_review'/'in_correction' any more. A story that never
576
- // reached `passes` on a run that did not complete is exactly what the
577
- // 'failed' stage means; anything else settles as 'done'.
578
- stories: snapshot.stories.map((story) => {
579
- if (isTerminalStage(story.stage)) return story;
580
- const stage = event.status === "completed" && story.passes ? "done" : "failed";
581
- return transitionStory(story, stage, event.at, null);
582
- }),
583
- lastError: event.error ? { message: stripVTControlCharacters(event.error), at: event.at } : snapshot.lastError
584
- };
585
- 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;
586
703
  return snapshot;
704
+ }
587
705
  }
588
706
  }
707
+
708
+ // src/core/session/publishers.ts
589
709
  var NullPublisher = class {
590
710
  empty = createInitialSnapshot();
591
711
  publish(_event) {
@@ -702,7 +822,7 @@ var FilePublisher = class extends MemoryPublisher {
702
822
  const payload = `${JSON.stringify(this.state, null, 2)}
703
823
  `;
704
824
  try {
705
- await atomicWriteFile(this.filePath, payload);
825
+ await writeFileAtomic(this.filePath, payload);
706
826
  this.lastWrittenVersion = version;
707
827
  } catch (err) {
708
828
  this.warnOnce(err);
@@ -746,12 +866,6 @@ var FilePublisher = class extends MemoryPublisher {
746
866
  await this.flush();
747
867
  }
748
868
  };
749
- async function atomicWriteFile(path, content) {
750
- await mkdir(dirname(path), { recursive: true });
751
- const tmpFile = `${path}.tmp`;
752
- await writeFile(tmpFile, content, "utf-8");
753
- await rename(tmpFile, path);
754
- }
755
869
 
756
870
  // src/core/session-publisher.ts
757
871
  var NULL_PUBLISHER = new NullPublisher();
@@ -772,4 +886,4 @@ export {
772
886
  setSessionPublisher,
773
887
  getSessionPublisher
774
888
  };
775
- //# sourceMappingURL=chunk-6QYMSNVA.js.map
889
+ //# sourceMappingURL=chunk-DUS5ZNKL.js.map