@caupulican/pi-adaptative 0.81.27 → 0.81.30

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 (191) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/bundled-resources/skills/harness-self-adaptation/SKILL.md +327 -0
  3. package/dist/bundled-resources/skills/harness-self-adaptation/references/adaptation-layers.md +89 -0
  4. package/dist/core/agent-session.d.ts +20 -3
  5. package/dist/core/agent-session.d.ts.map +1 -1
  6. package/dist/core/agent-session.js +73 -17
  7. package/dist/core/agent-session.js.map +1 -1
  8. package/dist/core/auth-storage.d.ts +6 -0
  9. package/dist/core/auth-storage.d.ts.map +1 -1
  10. package/dist/core/auth-storage.js +35 -0
  11. package/dist/core/auth-storage.js.map +1 -1
  12. package/dist/core/autonomy/approval-gate.d.ts +1 -1
  13. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  14. package/dist/core/autonomy/approval-gate.js +5 -1
  15. package/dist/core/autonomy/approval-gate.js.map +1 -1
  16. package/dist/core/autonomy/contracts.d.ts +2 -0
  17. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  18. package/dist/core/autonomy/contracts.js.map +1 -1
  19. package/dist/core/autonomy/lane-tool-surface.d.ts +4 -0
  20. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  21. package/dist/core/autonomy/lane-tool-surface.js +31 -4
  22. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  23. package/dist/core/autonomy/lane-tracker.d.ts +6 -0
  24. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  25. package/dist/core/autonomy/lane-tracker.js +25 -3
  26. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  27. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  28. package/dist/core/autonomy/risk-assessment.js +1 -1
  29. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  30. package/dist/core/background-lane-controller.d.ts +34 -1
  31. package/dist/core/background-lane-controller.d.ts.map +1 -1
  32. package/dist/core/background-lane-controller.js +204 -9
  33. package/dist/core/background-lane-controller.js.map +1 -1
  34. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  35. package/dist/core/context/context-artifacts.js +7 -6
  36. package/dist/core/context/context-artifacts.js.map +1 -1
  37. package/dist/core/context/context-store-retention.d.ts +12 -0
  38. package/dist/core/context/context-store-retention.d.ts.map +1 -0
  39. package/dist/core/context/context-store-retention.js +161 -0
  40. package/dist/core/context/context-store-retention.js.map +1 -0
  41. package/dist/core/context-gc.d.ts.map +1 -1
  42. package/dist/core/context-gc.js +20 -4
  43. package/dist/core/context-gc.js.map +1 -1
  44. package/dist/core/context-pipeline.d.ts +18 -15
  45. package/dist/core/context-pipeline.d.ts.map +1 -1
  46. package/dist/core/context-pipeline.js +143 -38
  47. package/dist/core/context-pipeline.js.map +1 -1
  48. package/dist/core/cost/cost-summary.d.ts +7 -1
  49. package/dist/core/cost/cost-summary.d.ts.map +1 -1
  50. package/dist/core/cost/cost-summary.js +28 -17
  51. package/dist/core/cost/cost-summary.js.map +1 -1
  52. package/dist/core/cost/daily-usage.d.ts +7 -3
  53. package/dist/core/cost/daily-usage.d.ts.map +1 -1
  54. package/dist/core/cost/daily-usage.js +10 -5
  55. package/dist/core/cost/daily-usage.js.map +1 -1
  56. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  57. package/dist/core/delegation/worker-result.js +3 -0
  58. package/dist/core/delegation/worker-result.js.map +1 -1
  59. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  60. package/dist/core/delegation/worker-runner.js +70 -5
  61. package/dist/core/delegation/worker-runner.js.map +1 -1
  62. package/dist/core/export-html/template.js +2 -1
  63. package/dist/core/extensions/loader.d.ts.map +1 -1
  64. package/dist/core/extensions/loader.js +52 -2
  65. package/dist/core/extensions/loader.js.map +1 -1
  66. package/dist/core/extensions/runner.d.ts.map +1 -1
  67. package/dist/core/extensions/runner.js +21 -19
  68. package/dist/core/extensions/runner.js.map +1 -1
  69. package/dist/core/extensions/types.d.ts +5 -0
  70. package/dist/core/extensions/types.d.ts.map +1 -1
  71. package/dist/core/extensions/types.js.map +1 -1
  72. package/dist/core/failure-corpus.d.ts.map +1 -1
  73. package/dist/core/failure-corpus.js +36 -13
  74. package/dist/core/failure-corpus.js.map +1 -1
  75. package/dist/core/local-runtime-controller.d.ts +16 -0
  76. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  77. package/dist/core/local-runtime-controller.js +127 -30
  78. package/dist/core/local-runtime-controller.js.map +1 -1
  79. package/dist/core/memory-controller.d.ts +2 -0
  80. package/dist/core/memory-controller.d.ts.map +1 -1
  81. package/dist/core/memory-controller.js +18 -0
  82. package/dist/core/memory-controller.js.map +1 -1
  83. package/dist/core/model-registry.d.ts +2 -0
  84. package/dist/core/model-registry.d.ts.map +1 -1
  85. package/dist/core/model-registry.js +4 -0
  86. package/dist/core/model-registry.js.map +1 -1
  87. package/dist/core/models/local-runtime.d.ts +2 -0
  88. package/dist/core/models/local-runtime.d.ts.map +1 -1
  89. package/dist/core/models/local-runtime.js +25 -14
  90. package/dist/core/models/local-runtime.js.map +1 -1
  91. package/dist/core/models/runtime-arbiter.d.ts +11 -0
  92. package/dist/core/models/runtime-arbiter.d.ts.map +1 -1
  93. package/dist/core/models/runtime-arbiter.js +35 -13
  94. package/dist/core/models/runtime-arbiter.js.map +1 -1
  95. package/dist/core/reflection-controller.d.ts +5 -1
  96. package/dist/core/reflection-controller.d.ts.map +1 -1
  97. package/dist/core/reflection-controller.js +9 -3
  98. package/dist/core/reflection-controller.js.map +1 -1
  99. package/dist/core/runtime-builder.d.ts +19 -2
  100. package/dist/core/runtime-builder.d.ts.map +1 -1
  101. package/dist/core/runtime-builder.js +13 -5
  102. package/dist/core/runtime-builder.js.map +1 -1
  103. package/dist/core/sdk.d.ts.map +1 -1
  104. package/dist/core/sdk.js +3 -0
  105. package/dist/core/sdk.js.map +1 -1
  106. package/dist/core/session-analytics.d.ts +13 -7
  107. package/dist/core/session-analytics.d.ts.map +1 -1
  108. package/dist/core/session-analytics.js +155 -112
  109. package/dist/core/session-analytics.js.map +1 -1
  110. package/dist/core/tool-gate-controller.d.ts +3 -0
  111. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  112. package/dist/core/tool-gate-controller.js +20 -15
  113. package/dist/core/tool-gate-controller.js.map +1 -1
  114. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  115. package/dist/core/tool-recovery-log-records.js +54 -9
  116. package/dist/core/tool-recovery-log-records.js.map +1 -1
  117. package/dist/core/tool-recovery-logger.d.ts +2 -0
  118. package/dist/core/tool-recovery-logger.d.ts.map +1 -1
  119. package/dist/core/tool-recovery-logger.js +32 -13
  120. package/dist/core/tool-recovery-logger.js.map +1 -1
  121. package/dist/core/tool-recovery-stats.d.ts +15 -0
  122. package/dist/core/tool-recovery-stats.d.ts.map +1 -0
  123. package/dist/core/tool-recovery-stats.js +206 -0
  124. package/dist/core/tool-recovery-stats.js.map +1 -0
  125. package/dist/core/tool-selection/expected-utility.d.ts +51 -0
  126. package/dist/core/tool-selection/expected-utility.d.ts.map +1 -0
  127. package/dist/core/tool-selection/expected-utility.js +85 -0
  128. package/dist/core/tool-selection/expected-utility.js.map +1 -0
  129. package/dist/core/tool-selection/tool-performance-store.d.ts +82 -0
  130. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -0
  131. package/dist/core/tool-selection/tool-performance-store.js +249 -0
  132. package/dist/core/tool-selection/tool-performance-store.js.map +1 -0
  133. package/dist/core/tool-selection/tool-selection-controller.d.ts +41 -0
  134. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -0
  135. package/dist/core/tool-selection/tool-selection-controller.js +199 -0
  136. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -0
  137. package/dist/core/tools/delegate-status.d.ts +9 -0
  138. package/dist/core/tools/delegate-status.d.ts.map +1 -0
  139. package/dist/core/tools/delegate-status.js +61 -0
  140. package/dist/core/tools/delegate-status.js.map +1 -0
  141. package/dist/core/tools/delegate.d.ts +13 -0
  142. package/dist/core/tools/delegate.d.ts.map +1 -1
  143. package/dist/core/tools/delegate.js +28 -4
  144. package/dist/core/tools/delegate.js.map +1 -1
  145. package/dist/core/tools/run-toolkit-script.d.ts +3 -0
  146. package/dist/core/tools/run-toolkit-script.d.ts.map +1 -1
  147. package/dist/core/tools/run-toolkit-script.js +17 -7
  148. package/dist/core/tools/run-toolkit-script.js.map +1 -1
  149. package/dist/main.d.ts.map +1 -1
  150. package/dist/main.js +1 -1
  151. package/dist/main.js.map +1 -1
  152. package/dist/modes/interactive/components/bash-execution.d.ts +2 -1
  153. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  154. package/dist/modes/interactive/components/bash-execution.js +12 -1
  155. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  156. package/dist/modes/interactive/components/footer.d.ts +1 -0
  157. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  158. package/dist/modes/interactive/components/footer.js +19 -9
  159. package/dist/modes/interactive/components/footer.js.map +1 -1
  160. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  161. package/dist/modes/interactive/components/tool-execution.js +12 -2
  162. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  163. package/dist/modes/interactive/components/tool-panel-registry.d.ts +1 -0
  164. package/dist/modes/interactive/components/tool-panel-registry.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/tool-panel-registry.js +18 -2
  166. package/dist/modes/interactive/components/tool-panel-registry.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  168. package/dist/modes/interactive/interactive-mode.js +18 -0
  169. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  170. package/dist/modes/rpc/jsonl.d.ts.map +1 -1
  171. package/dist/modes/rpc/jsonl.js +9 -28
  172. package/dist/modes/rpc/jsonl.js.map +1 -1
  173. package/dist/utils/streaming-lines.d.ts +20 -0
  174. package/dist/utils/streaming-lines.d.ts.map +1 -0
  175. package/dist/utils/streaming-lines.js +79 -0
  176. package/dist/utils/streaming-lines.js.map +1 -0
  177. package/docs/extensions.md +2 -0
  178. package/docs/index.md +1 -1
  179. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +365 -0
  180. package/docs/integration-sweep-resume-2026-07-09.md +407 -0
  181. package/docs/self-adaptation.md +7 -0
  182. package/docs/settings.md +7 -1
  183. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  184. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  185. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  186. package/examples/extensions/sandbox/package-lock.json +2 -2
  187. package/examples/extensions/sandbox/package.json +1 -1
  188. package/examples/extensions/with-deps/package-lock.json +2 -2
  189. package/examples/extensions/with-deps/package.json +1 -1
  190. package/npm-shrinkwrap.json +12 -12
  191. package/package.json +4 -4
@@ -14,19 +14,25 @@ import { getSessionsDir } from "../config.js";
14
14
  import { theme } from "../modes/interactive/theme/theme.js";
15
15
  import { resolvePath } from "../utils/paths.js";
16
16
  import { SPAWNED_USAGE_CUSTOM_TYPE, } from "./agent-session.js";
17
- import { aggregateCurrentSessionCostsFromEntries, createSessionCostSummary, } from "./cost/cost-summary.js";
17
+ import { accumulateCurrentSessionCostsFromEntries, createCurrentSessionCostAccumulator, createSessionCostSummary, } from "./cost/cost-summary.js";
18
18
  import { aggregateDailyUsageFromSessionFiles, aggregateDailyUsageFromSessionRoot, formatDailyUsageBreakdown, getLocalDayWindow, } from "./cost/daily-usage.js";
19
19
  import { exportSessionToHtml } from "./export-html/index.js";
20
20
  import { createToolHtmlRenderer } from "./export-html/tool-renderer.js";
21
21
  import { isToolArgumentValidationLogRecord, } from "./tool-recovery-log-records.js";
22
+ import { consumeToolArgumentValidationRecord, createEmptyToolArgumentValidationStats, getToolRecoveryRecordSequence, readPersistedToolRecoveryStats, } from "./tool-recovery-stats.js";
22
23
  export const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = "tool_argument_validation";
23
24
  export class SessionAnalytics {
24
- /** Memoized spawned-usage totals, keyed by session entry count (Bug #22: O(1) between turns). */
25
- _spawnedUsageCache;
25
+ /** Incremental aggregate over append-ordered session entries. */
26
+ _currentSessionCostCache;
26
27
  /** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */
27
28
  _dailyUsageCache;
28
- /** Live recovery telemetry records already snapshotted for the worker; keyed to dedupe sidecar reads. */
29
- _toolArgumentValidationRecords = new Map();
29
+ /** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so
30
+ * rescanning a long session log there turns redraw into O(entries) work per frame. */
31
+ _costSummaryCache;
32
+ /** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */
33
+ _toolArgumentValidationStats;
34
+ /** Incremental context-usage state keyed by the append-only branch leaf. */
35
+ _postCompactionUsageCache;
30
36
  deps;
31
37
  constructor(deps) {
32
38
  this.deps = deps;
@@ -98,7 +104,9 @@ export class SessionAnalytics {
98
104
  return { gateFailures, deterministicGapFills, compactionsWithGateFailures };
99
105
  }
100
106
  recordToolArgumentValidation(record) {
101
- this._toolArgumentValidationRecords.set(record.recordId, record);
107
+ const stats = this.getToolArgumentValidationStats();
108
+ consumeToolArgumentValidationRecord(stats, record);
109
+ this._toolArgumentValidationStats = stats;
102
110
  }
103
111
  readToolArgumentValidationSidecarRecords() {
104
112
  const filePath = this.deps.getToolRecoveryEventLogPath();
@@ -122,64 +130,18 @@ export class SessionAnalytics {
122
130
  return records;
123
131
  }
124
132
  getToolArgumentValidationStats() {
125
- const stats = {
126
- clean: 0,
127
- repaired: 0,
128
- bounced: 0,
129
- failureModes: {},
130
- repairsApplied: {},
131
- taught: { none: 0, note: 0, rule: 0 },
132
- executionOutcome: { not_run: 0, succeeded: 0, failed: 0 },
133
- teachEfficacy: {},
134
- };
135
- const getEfficacy = (record, mode) => {
136
- const key = `${record.provider ?? "unknown"}/${record.model ?? "unknown"}:${mode}`;
137
- stats.teachEfficacy[key] ??= {
138
- recurrenceBefore: 0,
139
- recurrenceAfter: 0,
140
- repairedThenSucceeded: 0,
141
- repairedThenFailed: 0,
142
- repairedThenNotRun: 0,
143
- };
144
- return stats.teachEfficacy[key];
145
- };
133
+ if (this._toolArgumentValidationStats)
134
+ return structuredClone(this._toolArgumentValidationStats);
135
+ const eventLogPath = this.deps.getToolRecoveryEventLogPath();
136
+ const sessionId = this.deps.getSessionManager().getSessionId();
137
+ const persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);
138
+ const stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();
146
139
  const seen = new Set();
147
140
  const consume = (record, key) => {
148
141
  if (seen.has(key))
149
142
  return;
150
143
  seen.add(key);
151
- stats[record.outcome] += 1;
152
- const taught = record.taught ?? "none";
153
- const executionOutcome = record.executionOutcome ?? "not_run";
154
- stats.taught[taught] += 1;
155
- stats.executionOutcome[executionOutcome] += 1;
156
- const modes = new Set([...record.failureModes, ...record.repairsApplied]);
157
- for (const mode of record.failureModes) {
158
- stats.failureModes[mode] = (stats.failureModes[mode] ?? 0) + 1;
159
- }
160
- for (const repair of record.repairsApplied) {
161
- stats.repairsApplied[repair] = (stats.repairsApplied[repair] ?? 0) + 1;
162
- }
163
- for (const mode of modes) {
164
- const efficacy = getEfficacy(record, mode);
165
- if (taught === "none") {
166
- efficacy.recurrenceBefore++;
167
- }
168
- else {
169
- efficacy.recurrenceAfter++;
170
- }
171
- }
172
- if (record.outcome === "repaired") {
173
- for (const repair of record.repairsApplied) {
174
- const efficacy = getEfficacy(record, repair);
175
- if (executionOutcome === "succeeded")
176
- efficacy.repairedThenSucceeded++;
177
- if (executionOutcome === "failed")
178
- efficacy.repairedThenFailed++;
179
- if (executionOutcome === "not_run")
180
- efficacy.repairedThenNotRun++;
181
- }
182
- }
144
+ consumeToolArgumentValidationRecord(stats, record);
183
145
  };
184
146
  for (const entry of this.deps.getSessionManager().getEntries()) {
185
147
  if (entry.type !== "custom" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE)
@@ -190,12 +152,15 @@ export class SessionAnalytics {
190
152
  consume(record, `legacy:${entry.id}`);
191
153
  }
192
154
  for (const record of this.readToolArgumentValidationSidecarRecords()) {
155
+ if (persisted &&
156
+ record.sessionId === sessionId &&
157
+ getToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence) {
158
+ continue;
159
+ }
193
160
  consume(record, record.recordId);
194
161
  }
195
- for (const record of this._toolArgumentValidationRecords.values()) {
196
- consume(record, record.recordId);
197
- }
198
- return stats;
162
+ this._toolArgumentValidationStats = stats;
163
+ return structuredClone(stats);
199
164
  }
200
165
  /**
201
166
  * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own
@@ -282,14 +247,8 @@ export class SessionAnalytics {
282
247
  */
283
248
  addSpawnedUsage(usage, opts) {
284
249
  const reportId = opts?.reportId;
285
- if (reportId) {
286
- for (const entry of this.deps.getSessionManager().getEntries()) {
287
- if (entry.type === "custom" &&
288
- entry.customType === SPAWNED_USAGE_CUSTOM_TYPE &&
289
- entry.data?.reportId === reportId) {
290
- return undefined;
291
- }
292
- }
250
+ if (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {
251
+ return undefined;
293
252
  }
294
253
  const report = {
295
254
  usage,
@@ -297,30 +256,62 @@ export class SessionAnalytics {
297
256
  sourceSessionId: opts?.sourceSessionId,
298
257
  reportId,
299
258
  };
300
- return this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);
259
+ const entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);
260
+ if (reportId)
261
+ this.getCurrentSessionCostTotals();
262
+ return entryId;
263
+ }
264
+ getCurrentSessionCostTotals() {
265
+ const sessionManager = this.deps.getSessionManager();
266
+ const entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;
267
+ let cache = this._currentSessionCostCache;
268
+ const getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);
269
+ if (!cache || entryCount < cache.entryCount || !getEntriesSince) {
270
+ cache = {
271
+ entryCount,
272
+ accumulator: accumulateCurrentSessionCostsFromEntries(createCurrentSessionCostAccumulator(), sessionManager.getEntries()),
273
+ };
274
+ this._currentSessionCostCache = cache;
275
+ }
276
+ else if (entryCount > cache.entryCount) {
277
+ accumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));
278
+ cache.entryCount = entryCount;
279
+ }
280
+ return cache.accumulator;
301
281
  }
302
282
  /**
303
- * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). Cached by the session
304
- * entry count so the interactive footer (which calls this every render frame) is O(1) between turns
305
- * instead of an O(N) scan on every keystroke (Bug #22). Recomputes only when entries change.
283
+ * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered
284
+ * accumulator processes only new entries, so repeated turns do not rescan the full session.
306
285
  */
307
286
  getSpawnedUsage() {
308
- const sessionManager = this.deps.getSessionManager();
309
- const entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;
310
- if (this._spawnedUsageCache?.entryCount === entryCount)
311
- return this._spawnedUsageCache.totals;
312
- const current = aggregateCurrentSessionCostsFromEntries(sessionManager.getEntries());
313
- const totals = { cost: current.subagentCost, reports: current.subagentReports };
314
- this._spawnedUsageCache = { entryCount, totals };
315
- return totals;
287
+ const current = this.getCurrentSessionCostTotals();
288
+ return { cost: current.subagentCost, reports: current.subagentReports };
316
289
  }
317
290
  getCostSummary(now = new Date()) {
291
+ const sessionManager = this.deps.getSessionManager();
292
+ const entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;
318
293
  const window = getLocalDayWindow(now);
319
- return createSessionCostSummary({
320
- entries: this.deps.getSessionManager().getEntries(),
321
- dailyTotals: this.getDailyUsageTotals(now),
294
+ const dailyTotals = this.getDailyUsageTotals(now);
295
+ const cached = this._costSummaryCache;
296
+ if (cached?.entryCount === entryCount &&
297
+ cached.dailyTotals === dailyTotals &&
298
+ cached.windowStartMs === window.startMs &&
299
+ cached.windowEndMs === window.endMs) {
300
+ return cached.summary;
301
+ }
302
+ const summary = createSessionCostSummary({
303
+ currentTotals: this.getCurrentSessionCostTotals(),
304
+ dailyTotals,
322
305
  todayWindow: window,
323
306
  });
307
+ this._costSummaryCache = {
308
+ entryCount,
309
+ dailyTotals,
310
+ windowStartMs: window.startMs,
311
+ windowEndMs: window.endMs,
312
+ summary,
313
+ };
314
+ return summary;
324
315
  }
325
316
  getDailyUsageTotals(now = new Date()) {
326
317
  const sessionManager = this.deps.getSessionManager();
@@ -334,9 +325,11 @@ export class SessionAnalytics {
334
325
  this._dailyUsageCache.expiresAt > nowMs) {
335
326
  return this._dailyUsageCache.totals;
336
327
  }
328
+ const sessionFile = sessionManager.getSessionFile();
329
+ const liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;
337
330
  const totals = sessionManager.usesDefaultSessionDir()
338
- ? aggregateDailyUsageFromSessionRoot(scope, window)
339
- : aggregateDailyUsageFromSessionFiles(sessionDir, window);
331
+ ? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)
332
+ : aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);
340
333
  this._dailyUsageCache = {
341
334
  sessionDir: scope,
342
335
  windowStartMs: window.startMs,
@@ -349,6 +342,75 @@ export class SessionAnalytics {
349
342
  getDailyUsageBreakdown(formatLabel, now = new Date()) {
350
343
  return formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);
351
344
  }
345
+ getPostCompactionUsageState() {
346
+ const sessionManager = this.deps.getSessionManager();
347
+ const indexedSessionManager = sessionManager;
348
+ const getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);
349
+ const getEntry = indexedSessionManager.getEntry?.bind(sessionManager);
350
+ const leafId = getLeafId?.();
351
+ if (getLeafId && getEntry && leafId !== undefined) {
352
+ if (this._postCompactionUsageCache?.leafId === leafId) {
353
+ return { ...this._postCompactionUsageCache };
354
+ }
355
+ const appended = [];
356
+ const cachedLeafId = this._postCompactionUsageCache?.leafId;
357
+ let currentId = leafId;
358
+ const seen = new Set();
359
+ while (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {
360
+ seen.add(currentId);
361
+ const entry = getEntry(currentId);
362
+ if (!entry)
363
+ break;
364
+ appended.push(entry);
365
+ currentId = entry.parentId;
366
+ }
367
+ const extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;
368
+ const state = extendsCachedBranch
369
+ ? { ...this._postCompactionUsageCache }
370
+ : { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };
371
+ if (!extendsCachedBranch && currentId !== null) {
372
+ while (currentId !== null && !seen.has(currentId)) {
373
+ seen.add(currentId);
374
+ const entry = getEntry(currentId);
375
+ if (!entry)
376
+ break;
377
+ appended.push(entry);
378
+ currentId = entry.parentId;
379
+ }
380
+ }
381
+ for (let index = appended.length - 1; index >= 0; index--) {
382
+ const entry = appended[index];
383
+ if (entry.type === "compaction") {
384
+ state.hasCompaction = true;
385
+ state.hasPostCompactionUsage = false;
386
+ }
387
+ else if (entry.type === "message" && entry.message.role === "assistant") {
388
+ const assistant = entry.message;
389
+ if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error" && state.hasCompaction) {
390
+ state.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;
391
+ }
392
+ }
393
+ }
394
+ state.leafId = leafId;
395
+ this._postCompactionUsageCache = state;
396
+ return { ...state };
397
+ }
398
+ const branchEntries = sessionManager.getBranch();
399
+ const latestCompaction = getLatestCompactionEntry(branchEntries);
400
+ if (!latestCompaction)
401
+ return { hasCompaction: false, hasPostCompactionUsage: false };
402
+ const compactionIndex = branchEntries.lastIndexOf(latestCompaction);
403
+ for (let index = branchEntries.length - 1; index > compactionIndex; index--) {
404
+ const entry = branchEntries[index];
405
+ if (entry.type !== "message" || entry.message.role !== "assistant")
406
+ continue;
407
+ const assistant = entry.message;
408
+ if (assistant.stopReason === "aborted" || assistant.stopReason === "error")
409
+ continue;
410
+ return { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };
411
+ }
412
+ return { hasCompaction: true, hasPostCompactionUsage: false };
413
+ }
352
414
  getContextUsage() {
353
415
  const model = this.deps.getModel();
354
416
  if (!model)
@@ -357,30 +419,11 @@ export class SessionAnalytics {
357
419
  if (contextWindow <= 0)
358
420
  return undefined;
359
421
  // After compaction, the last assistant usage reflects pre-compaction context size.
360
- // We can only trust usage from an assistant that responded after the latest compaction.
361
- // If no such assistant exists, context token count is unknown until the next LLM response.
362
- const branchEntries = this.deps.getSessionManager().getBranch();
363
- const latestCompaction = getLatestCompactionEntry(branchEntries);
364
- if (latestCompaction) {
365
- // Check if there's a valid assistant usage after the compaction boundary
366
- const compactionIndex = branchEntries.lastIndexOf(latestCompaction);
367
- let hasPostCompactionUsage = false;
368
- for (let i = branchEntries.length - 1; i > compactionIndex; i--) {
369
- const entry = branchEntries[i];
370
- if (entry.type === "message" && entry.message.role === "assistant") {
371
- const assistant = entry.message;
372
- if (assistant.stopReason !== "aborted" && assistant.stopReason !== "error") {
373
- const contextTokens = calculateContextTokens(assistant.usage);
374
- if (contextTokens > 0) {
375
- hasPostCompactionUsage = true;
376
- }
377
- break;
378
- }
379
- }
380
- }
381
- if (!hasPostCompactionUsage) {
382
- return { tokens: null, contextWindow, percent: null };
383
- }
422
+ // Walk backward only to the nearest compaction instead of rebuilding the whole branch on
423
+ // every footer invalidation; the distance is bounded by the live post-compaction context.
424
+ const compactionState = this.getPostCompactionUsageState();
425
+ if (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {
426
+ return { tokens: null, contextWindow, percent: null };
384
427
  }
385
428
  const estimate = estimateContextTokens(this.deps.getMessages());
386
429
  const percent = (estimate.tokens / contextWindow) * 100;
@@ -1 +1 @@
1
- {"version":3,"file":"session-analytics.js","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taggregateCurrentSessionCostsFromEntries,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Memoized spawned-usage totals, keyed by session entry count (Bug #22: O(1) between turns). */\n\tprivate _spawnedUsageCache?: { entryCount: number; totals: SpawnedUsageTotals };\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Live recovery telemetry records already snapshotted for the worker; keyed to dedupe sidecar reads. */\n\tprivate readonly _toolArgumentValidationRecords = new Map<string, ToolArgumentValidationLogRecord>();\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tthis._toolArgumentValidationRecords.set(record.recordId, record);\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tconst stats: ToolArgumentValidationStats = {\n\t\t\tclean: 0,\n\t\t\trepaired: 0,\n\t\t\tbounced: 0,\n\t\t\tfailureModes: {},\n\t\t\trepairsApplied: {},\n\t\t\ttaught: { none: 0, note: 0, rule: 0 },\n\t\t\texecutionOutcome: { not_run: 0, succeeded: 0, failed: 0 },\n\t\t\tteachEfficacy: {},\n\t\t};\n\n\t\tconst getEfficacy = (record: ToolArgumentValidationRecord, mode: string): ToolArgumentValidationTeachEfficacy => {\n\t\t\tconst key = `${record.provider ?? \"unknown\"}/${record.model ?? \"unknown\"}:${mode}`;\n\t\t\tstats.teachEfficacy[key] ??= {\n\t\t\t\trecurrenceBefore: 0,\n\t\t\t\trecurrenceAfter: 0,\n\t\t\t\trepairedThenSucceeded: 0,\n\t\t\t\trepairedThenFailed: 0,\n\t\t\t\trepairedThenNotRun: 0,\n\t\t\t};\n\t\t\treturn stats.teachEfficacy[key];\n\t\t};\n\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tstats[record.outcome] += 1;\n\t\t\tconst taught = record.taught ?? \"none\";\n\t\t\tconst executionOutcome = record.executionOutcome ?? \"not_run\";\n\t\t\tstats.taught[taught] += 1;\n\t\t\tstats.executionOutcome[executionOutcome] += 1;\n\t\t\tconst modes = new Set([...record.failureModes, ...record.repairsApplied]);\n\t\t\tfor (const mode of record.failureModes) {\n\t\t\t\tstats.failureModes[mode] = (stats.failureModes[mode] ?? 0) + 1;\n\t\t\t}\n\t\t\tfor (const repair of record.repairsApplied) {\n\t\t\t\tstats.repairsApplied[repair] = (stats.repairsApplied[repair] ?? 0) + 1;\n\t\t\t}\n\t\t\tfor (const mode of modes) {\n\t\t\t\tconst efficacy = getEfficacy(record, mode);\n\t\t\t\tif (taught === \"none\") {\n\t\t\t\t\tefficacy.recurrenceBefore++;\n\t\t\t\t} else {\n\t\t\t\t\tefficacy.recurrenceAfter++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (record.outcome === \"repaired\") {\n\t\t\t\tfor (const repair of record.repairsApplied) {\n\t\t\t\t\tconst efficacy = getEfficacy(record, repair);\n\t\t\t\t\tif (executionOutcome === \"succeeded\") efficacy.repairedThenSucceeded++;\n\t\t\t\t\tif (executionOutcome === \"failed\") efficacy.repairedThenFailed++;\n\t\t\t\t\tif (executionOutcome === \"not_run\") efficacy.repairedThenNotRun++;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\t\tfor (const record of this._toolArgumentValidationRecords.values()) {\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\treturn stats;\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId) {\n\t\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\t\tif (\n\t\t\t\t\tentry.type === \"custom\" &&\n\t\t\t\t\tentry.customType === SPAWNED_USAGE_CUSTOM_TYPE &&\n\t\t\t\t\t(entry.data as SpawnedUsageReport | undefined)?.reportId === reportId\n\t\t\t\t) {\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\treturn this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). Cached by the session\n\t * entry count so the interactive footer (which calls this every render frame) is O(1) between turns\n\t * instead of an O(N) scan on every keystroke (Bug #22). Recomputes only when entries change.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tif (this._spawnedUsageCache?.entryCount === entryCount) return this._spawnedUsageCache.totals;\n\t\tconst current = aggregateCurrentSessionCostsFromEntries(sessionManager.getEntries());\n\t\tconst totals: SpawnedUsageTotals = { cost: current.subagentCost, reports: current.subagentReports };\n\t\tthis._spawnedUsageCache = { entryCount, totals };\n\t\treturn totals;\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst window = getLocalDayWindow(now);\n\t\treturn createSessionCostSummary({\n\t\t\tentries: this.deps.getSessionManager().getEntries(),\n\t\t\tdailyTotals: this.getDailyUsageTotals(now),\n\t\t\ttodayWindow: window,\n\t\t});\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// We can only trust usage from an assistant that responded after the latest compaction.\n\t\t// If no such assistant exists, context token count is unknown until the next LLM response.\n\t\tconst branchEntries = this.deps.getSessionManager().getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\n\t\tif (latestCompaction) {\n\t\t\t// Check if there's a valid assistant usage after the compaction boundary\n\t\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\t\tlet hasPostCompactionUsage = false;\n\t\t\tfor (let i = branchEntries.length - 1; i > compactionIndex; i--) {\n\t\t\t\tconst entry = branchEntries[i];\n\t\t\t\tif (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\") {\n\t\t\t\t\t\tconst contextTokens = calculateContextTokens(assistant.usage);\n\t\t\t\t\t\tif (contextTokens > 0) {\n\t\t\t\t\t\t\thasPostCompactionUsage = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!hasPostCompactionUsage) {\n\t\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t\t}\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-analytics.js","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACN,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,wBAAwB,GAIxB,MAAM,gCAAgC,CAAC;AASxC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,qCAAqC,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAEN,yBAAyB,GAGzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACN,wCAAwC,EAExC,mCAAmC,EACnC,wBAAwB,GAExB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,mCAAmC,EACnC,kCAAkC,EAElC,yBAAyB,EACzB,iBAAiB,GACjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAyB,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAGxE,OAAO,EACN,iCAAiC,GAEjC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACN,mCAAmC,EACnC,sCAAsC,EACtC,6BAA6B,EAC7B,8BAA8B,GAC9B,MAAM,0BAA0B,CAAC;AAElC,MAAM,CAAC,MAAM,oCAAoC,GAAG,0BAA0B,CAAC;AA0C/E,MAAM,OAAO,gBAAgB;IAC5B,iEAAiE;IACzD,wBAAwB,CAG9B;IACF,0GAA0G;IAClG,gBAAgB,CAMtB;IACF;0FACsF;IAC9E,iBAAiB,CAMvB;IACF,iHAAiH;IACzG,4BAA4B,CAA0C;IAC9E,4EAA4E;IACpE,yBAAyB,CAI/B;IAEe,IAAI,CAAuB;IAE5C,YAAY,IAA0B,EAAE;QACvC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;IAED,eAAe,GAAiB;QAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;QAC5E,MAAM,iBAAiB,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;QACtF,MAAM,WAAW,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,MAAM,CAAC;QAEjF,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,eAAe,GAAG,CAAC,CAAC;QACxB,IAAI,SAAS,GAAG,CAAC,CAAC;QAElB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACtC,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBAClC,MAAM,YAAY,GAAG,OAA2B,CAAC;gBACjD,SAAS,IAAI,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC;gBAC9E,UAAU,IAAI,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC;gBACvC,WAAW,IAAI,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC;gBACzC,cAAc,IAAI,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC;gBAC/C,eAAe,IAAI,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC;gBACjD,SAAS,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC5C,CAAC;QACF,CAAC;QAED,MAAM,sBAAsB,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACrE,MAAM,eAAe,GAAG,IAAI,CAAC,sBAAsB,EAAE,CAAC;QAEtD,OAAO;YACN,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,cAAc,EAAE;YAC3D,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,YAAY,EAAE;YACvD,YAAY;YACZ,iBAAiB;YACjB,SAAS;YACT,WAAW;YACX,aAAa,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM;YACpC,MAAM,EAAE;gBACP,KAAK,EAAE,UAAU;gBACjB,MAAM,EAAE,WAAW;gBACnB,SAAS,EAAE,cAAc;gBACzB,UAAU,EAAE,eAAe;gBAC3B,KAAK,EAAE,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,eAAe;aAClE;YACD,IAAI,EAAE,SAAS;YACf,YAAY,EAAE,IAAI,CAAC,eAAe,EAAE;YACpC,sBAAsB;YACtB,eAAe;SACf,CAAC;IAAA,CACF;IAED,sBAAsB,GAAoC;QACzD,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,qBAAqB,GAAG,CAAC,CAAC;QAC9B,IAAI,2BAA2B,GAAG,CAAC,CAAC;QACpC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC;YAChE,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;gBAAE,SAAS;YAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;YAC9B,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAAE,SAAS;YACtD,MAAM,eAAe,GAAI,OAAkD,CAAC,wBAAwB,CAAC;YACrG,MAAM,WAAW,GAAI,OAA+C,CAAC,qBAAqB,CAAC;YAC3F,MAAM,iBAAiB,GACtB,OAAO,eAAe,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/F,MAAM,aAAa,GAAG,OAAO,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;YACxG,YAAY,IAAI,iBAAiB,CAAC;YAClC,qBAAqB,IAAI,aAAa,CAAC;YACvC,IAAI,iBAAiB,GAAG,CAAC;gBAAE,2BAA2B,EAAE,CAAC;QAC1D,CAAC;QACD,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,CAAC;IAAA,CAC5E;IAED,4BAA4B,CAAC,MAAuC,EAAQ;QAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACpD,mCAAmC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACnD,IAAI,CAAC,4BAA4B,GAAG,KAAK,CAAC;IAAA,CAC1C;IAEO,wCAAwC,GAAsC;QACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACzD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,EAAE,CAAC;QACrC,MAAM,OAAO,GAAsC,EAAE,CAAC;QACtD,IAAI,CAAC;YACJ,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,YAAY,EAAE,CAAC;YAC/D,KAAK,MAAM,IAAI,IAAI,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAS;gBACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;gBAC3C,IAAI,CAAC,iCAAiC,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;oBAAE,SAAS;gBAC3F,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACtB,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,EAAE,CAAC;QACX,CAAC;QACD,OAAO,OAAO,CAAC;IAAA,CACf;IAED,8BAA8B,GAAgC;QAC7D,IAAI,IAAI,CAAC,4BAA4B;YAAE,OAAO,eAAe,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QACjG,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,2BAA2B,EAAE,CAAC;QAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,YAAY,EAAE,CAAC;QAC/D,MAAM,SAAS,GAAG,8BAA8B,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC1E,MAAM,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,sCAAsC,EAAE,CAAC;QACtG,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,MAAM,OAAO,GAAG,CAAC,MAAoC,EAAE,GAAW,EAAQ,EAAE,CAAC;YAC5E,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,mCAAmC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAAA,CACnD,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC;YAChE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,oCAAoC;gBAAE,SAAS;YACnG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAgD,CAAC;YACtE,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;gBAAE,SAAS;YAC9C,OAAO,CAAC,MAAM,EAAE,UAAU,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACvC,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,wCAAwC,EAAE,EAAE,CAAC;YACtE,IACC,SAAS;gBACT,MAAM,CAAC,SAAS,KAAK,SAAS;gBAC9B,6BAA6B,CAAC,MAAM,CAAC,IAAI,SAAS,CAAC,kBAAkB,EACpE,CAAC;gBACF,SAAS;YACV,CAAC;YACD,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,CAAC;QAED,IAAI,CAAC,4BAA4B,GAAG,KAAK,CAAC;QAC1C,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;IAAA,CAC9B;IAED;;;;;;;;;OASG;IACH,kBAAkB,GAAU;QAC3B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,aAAa,GAAG,CAAC,CAAC;QACtB,IAAI,cAAc,GAAG,CAAC,CAAC;QACvB,IAAI,SAAS,GAAG,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,CAAC,KAAY,EAAE,EAAE,CAAC;YAC7B,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC;YACvB,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;YAC7B,UAAU,IAAI,KAAK,CAAC,UAAU,CAAC;YAC/B,WAAW,IAAI,KAAK,CAAC,WAAW,CAAC;YACjC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;YAC9B,UAAU,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAChC,aAAa,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC;YACtC,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;YACxC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;QAAA,CAC9B,CAAC;QACF,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC;YACrD,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAC3C,MAAM,KAAK,GAAI,OAA4B,CAAC,KAAK,CAAC;YAClD,IAAI,CAAC,KAAK;gBAAE,SAAS;YACrB,GAAG,CAAC,KAAK,CAAC,CAAC;QACZ,CAAC;QACD,kFAAkF;QAClF,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC;YAChE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,KAAK,yBAAyB;gBAAE,SAAS;YACxF,MAAM,IAAI,GAAG,KAAK,CAAC,IAAsC,CAAC;YAC1D,IAAI,CAAC,IAAI,EAAE,KAAK;gBAAE,SAAS;YAC3B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACnB,IAAI,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC;oBAAE,SAAS;gBACtD,oBAAoB,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YACzC,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;QACD,OAAO;YACN,KAAK;YACL,MAAM;YACN,SAAS;YACT,UAAU;YACV,WAAW;YACX,IAAI,EAAE;gBACL,KAAK,EAAE,SAAS;gBAChB,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE,aAAa;gBACxB,UAAU,EAAE,cAAc;gBAC1B,KAAK,EAAE,SAAS;aAChB;SACD,CAAC;IAAA,CACF;IAED;;;;;;;;;;;OAWG;IACH,eAAe,CACd,KAAY,EACZ,IAAsE,EACjD;QACrB,MAAM,QAAQ,GAAG,IAAI,EAAE,QAAQ,CAAC;QAChC,IAAI,QAAQ,IAAI,IAAI,CAAC,2BAA2B,EAAE,CAAC,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACxF,OAAO,SAAS,CAAC;QAClB,CAAC;QACD,MAAM,MAAM,GAAuB;YAClC,KAAK;YACL,KAAK,EAAE,IAAI,EAAE,KAAK;YAClB,eAAe,EAAE,IAAI,EAAE,eAAe;YACtC,QAAQ;SACR,CAAC;QACF,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,iBAAiB,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;QACnG,IAAI,QAAQ;YAAE,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACjD,OAAO,OAAO,CAAC;IAAA,CACf;IAEO,2BAA2B,GAAkC;QACpE,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,UAAU,GAAG,cAAc,CAAC,aAAa,EAAE,EAAE,IAAI,cAAc,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC;QAC1F,IAAI,KAAK,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC1C,MAAM,eAAe,GAAG,cAAc,CAAC,eAAe,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK,IAAI,UAAU,GAAG,KAAK,CAAC,UAAU,IAAI,CAAC,eAAe,EAAE,CAAC;YACjE,KAAK,GAAG;gBACP,UAAU;gBACV,WAAW,EAAE,wCAAwC,CACpD,mCAAmC,EAAE,EACrC,cAAc,CAAC,UAAU,EAAE,CAC3B;aACD,CAAC;YACF,IAAI,CAAC,wBAAwB,GAAG,KAAK,CAAC;QACvC,CAAC;aAAM,IAAI,UAAU,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC;YAC1C,wCAAwC,CAAC,KAAK,CAAC,WAAW,EAAE,eAAe,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;YAC/F,KAAK,CAAC,UAAU,GAAG,UAAU,CAAC;QAC/B,CAAC;QACD,OAAO,KAAK,CAAC,WAAW,CAAC;IAAA,CACzB;IAED;;;OAGG;IACH,eAAe,GAAuB;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,2BAA2B,EAAE,CAAC;QACnD,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,YAAY,EAAE,OAAO,EAAE,OAAO,CAAC,eAAe,EAAE,CAAC;IAAA,CACxE;IAED,cAAc,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,EAAsB;QACpD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,UAAU,GAAG,cAAc,CAAC,aAAa,EAAE,EAAE,IAAI,cAAc,CAAC,UAAU,EAAE,CAAC,MAAM,CAAC;QAC1F,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,WAAW,GAAG,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,CAAC;QACtC,IACC,MAAM,EAAE,UAAU,KAAK,UAAU;YACjC,MAAM,CAAC,WAAW,KAAK,WAAW;YAClC,MAAM,CAAC,aAAa,KAAK,MAAM,CAAC,OAAO;YACvC,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,KAAK,EAClC,CAAC;YACF,OAAO,MAAM,CAAC,OAAO,CAAC;QACvB,CAAC;QACD,MAAM,OAAO,GAAG,wBAAwB,CAAC;YACxC,aAAa,EAAE,IAAI,CAAC,2BAA2B,EAAE;YACjD,WAAW;YACX,WAAW,EAAE,MAAM;SACnB,CAAC,CAAC;QACH,IAAI,CAAC,iBAAiB,GAAG;YACxB,UAAU;YACV,WAAW;YACX,aAAa,EAAE,MAAM,CAAC,OAAO;YAC7B,WAAW,EAAE,MAAM,CAAC,KAAK;YACzB,OAAO;SACP,CAAC;QACF,OAAO,OAAO,CAAC;IAAA,CACf;IAED,mBAAmB,CAAC,GAAG,GAAG,IAAI,IAAI,EAAE,EAAoB;QACvD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,UAAU,GAAG,cAAc,CAAC,aAAa,EAAE,CAAC;QAClD,MAAM,KAAK,GAAG,cAAc,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;QACrF,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACtC,IACC,IAAI,CAAC,gBAAgB,EAAE,UAAU,KAAK,KAAK;YAC3C,IAAI,CAAC,gBAAgB,CAAC,aAAa,KAAK,MAAM,CAAC,OAAO;YACtD,IAAI,CAAC,gBAAgB,CAAC,WAAW,KAAK,MAAM,CAAC,KAAK;YAClD,IAAI,CAAC,gBAAgB,CAAC,SAAS,GAAG,KAAK,EACtC,CAAC;YACF,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACrC,CAAC;QACD,MAAM,WAAW,GAAG,cAAc,CAAC,cAAc,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC9G,MAAM,MAAM,GAAG,cAAc,CAAC,qBAAqB,EAAE;YACpD,CAAC,CAAC,kCAAkC,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC;YAChE,CAAC,CAAC,mCAAmC,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;QACxE,IAAI,CAAC,gBAAgB,GAAG;YACvB,UAAU,EAAE,KAAK;YACjB,aAAa,EAAE,MAAM,CAAC,OAAO;YAC7B,WAAW,EAAE,MAAM,CAAC,KAAK;YACzB,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC;YACjD,MAAM;SACN,CAAC;QACF,OAAO,MAAM,CAAC;IAAA,CACd;IAED,sBAAsB,CAAC,WAAuC,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE,EAAU;QACzF,OAAO,yBAAyB,CAAC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,WAAW,CAAC,CAAC;IAAA,CAC7E;IAEO,2BAA2B,GAAgE;QAClG,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,qBAAqB,GAAG,cAG7B,CAAC;QACF,MAAM,SAAS,GAAG,qBAAqB,CAAC,SAAS,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACxE,MAAM,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACtE,MAAM,MAAM,GAAG,SAAS,EAAE,EAAE,CAAC;QAC7B,IAAI,SAAS,IAAI,QAAQ,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,yBAAyB,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;gBACvD,OAAO,EAAE,GAAG,IAAI,CAAC,yBAAyB,EAAE,CAAC;YAC9C,CAAC;YACD,MAAM,QAAQ,GAAmB,EAAE,CAAC;YACpC,MAAM,YAAY,GAAG,IAAI,CAAC,yBAAyB,EAAE,MAAM,CAAC;YAC5D,IAAI,SAAS,GAAG,MAAM,CAAC;YACvB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;YAC/B,OAAO,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACjF,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAClC,IAAI,CAAC,KAAK;oBAAE,MAAM;gBAClB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACrB,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC;YAC5B,CAAC;YACD,MAAM,mBAAmB,GAAG,IAAI,CAAC,yBAAyB,KAAK,SAAS,IAAI,SAAS,KAAK,YAAY,CAAC;YACvG,MAAM,KAAK,GAAG,mBAAmB;gBAChC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,yBAA0B,EAAE;gBACxC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC;YACzE,IAAI,CAAC,mBAAmB,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBAChD,OAAO,SAAS,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;oBACnD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;oBACpB,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC;oBAClC,IAAI,CAAC,KAAK;wBAAE,MAAM;oBAClB,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBACrB,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC;gBAC5B,CAAC;YACF,CAAC;YACD,KAAK,IAAI,KAAK,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC9B,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACjC,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;oBAC3B,KAAK,CAAC,sBAAsB,GAAG,KAAK,CAAC;gBACtC,CAAC;qBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC3E,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;oBAChC,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,KAAK,OAAO,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;wBACnG,KAAK,CAAC,sBAAsB,GAAG,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;oBAC5E,CAAC;gBACF,CAAC;YACF,CAAC;YACD,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;YACtB,IAAI,CAAC,yBAAyB,GAAG,KAAK,CAAC;YACvC,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;QACrB,CAAC;QAED,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,EAAE,CAAC;QACjD,MAAM,gBAAgB,GAAG,wBAAwB,CAAC,aAAa,CAAC,CAAC;QACjE,IAAI,CAAC,gBAAgB;YAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC;QACtF,MAAM,eAAe,GAAG,aAAa,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;QACpE,KAAK,IAAI,KAAK,GAAG,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,eAAe,EAAE,KAAK,EAAE,EAAE,CAAC;YAC7E,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;YACnC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAC7E,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC;YAChC,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,IAAI,SAAS,CAAC,UAAU,KAAK,OAAO;gBAAE,SAAS;YACrF,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,sBAAsB,EAAE,sBAAsB,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACrG,CAAC;QACD,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,CAAC;IAAA,CAC9D;IAED,eAAe,GAA6B;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAE7B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC;QAC/C,IAAI,aAAa,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAEzC,mFAAmF;QACnF,yFAAyF;QACzF,0FAA0F;QAC1F,MAAM,eAAe,GAAG,IAAI,CAAC,2BAA2B,EAAE,CAAC;QAC3D,IAAI,eAAe,CAAC,aAAa,IAAI,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC;YAC9E,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACvD,CAAC;QAED,MAAM,QAAQ,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,aAAa,CAAC,GAAG,GAAG,CAAC;QAExD,OAAO;YACN,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,aAAa;YACb,OAAO;SACP,CAAC;IAAA,CACF;IAED;;;;OAIG;IACH,KAAK,CAAC,YAAY,CAAC,UAAmB,EAAmB;QACxD,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,QAAQ,EAAE,CAAC;QAE5D,uFAAuF;QACvF,MAAM,YAAY,GAAqB,sBAAsB,CAAC;YAC7D,iBAAiB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;YAC9D,KAAK;YACL,GAAG,EAAE,cAAc,CAAC,MAAM,EAAE;SAC5B,CAAC,CAAC;QAEH,OAAO,MAAM,mBAAmB,CAAC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE;YACtE,UAAU;YACV,SAAS;YACT,YAAY;SACZ,CAAC,CAAC;IAAA,CACH;IAED;;;;;OAKG;IACH,aAAa,CAAC,UAAmB,EAAU;QAC1C,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACrD,MAAM,QAAQ,GAAG,WAAW,CAC3B,UAAU,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,EAC/E,OAAO,CAAC,GAAG,EAAE,CACb,CAAC;QACF,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrC,CAAC;QAED,MAAM,MAAM,GAAkB;YAC7B,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,uBAAuB;YAChC,EAAE,EAAE,cAAc,CAAC,YAAY,EAAE;YACjC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,EAAE,cAAc,CAAC,MAAM,EAAE;SAC5B,CAAC;QAEF,MAAM,aAAa,GAAG,cAAc,CAAC,SAAS,EAAE,CAAC;QACjD,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAEvC,+CAA+C;QAC/C,IAAI,MAAM,GAAkB,IAAI,CAAC;QACjC,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,EAAE,GAAG,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;YAC9C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YACnC,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC;QACnB,CAAC;QAED,aAAa,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAAA,CAChB;IAED;;;;OAIG;IACH,oBAAoB,GAAuB;QAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI;aAC7B,WAAW,EAAE;aACb,KAAK,EAAE;aACP,OAAO,EAAE;aACT,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW;gBAAE,OAAO,KAAK,CAAC;YACzC,MAAM,GAAG,GAAG,CAAqB,CAAC;YAClC,wCAAwC;YACxC,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC3E,OAAO,IAAI,CAAC;QAAA,CACZ,CAAC,CAAC;QAEJ,IAAI,CAAC,aAAa;YAAE,OAAO,SAAS,CAAC;QAErC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,MAAM,OAAO,IAAK,aAAkC,CAAC,OAAO,EAAE,CAAC;YACnE,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC7B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YACtB,CAAC;QACF,CAAC;QAED,OAAO,IAAI,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IAAA,CAChC;CACD","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taccumulateCurrentSessionCostsFromEntries,\n\ttype CurrentSessionCostAccumulator,\n\tcreateCurrentSessionCostAccumulator,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\nimport {\n\tconsumeToolArgumentValidationRecord,\n\tcreateEmptyToolArgumentValidationStats,\n\tgetToolRecoveryRecordSequence,\n\treadPersistedToolRecoveryStats,\n} from \"./tool-recovery-stats.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Incremental aggregate over append-ordered session entries. */\n\tprivate _currentSessionCostCache?: {\n\t\tentryCount: number;\n\t\taccumulator: CurrentSessionCostAccumulator;\n\t};\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so\n\t * rescanning a long session log there turns redraw into O(entries) work per frame. */\n\tprivate _costSummaryCache?: {\n\t\tentryCount: number;\n\t\tdailyTotals: DailyUsageTotals;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\tsummary: SessionCostSummary;\n\t};\n\t/** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */\n\tprivate _toolArgumentValidationStats: ToolArgumentValidationStats | undefined;\n\t/** Incremental context-usage state keyed by the append-only branch leaf. */\n\tprivate _postCompactionUsageCache?: {\n\t\tleafId: string | null;\n\t\thasCompaction: boolean;\n\t\thasPostCompactionUsage: boolean;\n\t};\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tconst stats = this.getToolArgumentValidationStats();\n\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\tthis._toolArgumentValidationStats = stats;\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tif (this._toolArgumentValidationStats) return structuredClone(this._toolArgumentValidationStats);\n\t\tconst eventLogPath = this.deps.getToolRecoveryEventLogPath();\n\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\tconst persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);\n\t\tconst stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tif (\n\t\t\t\tpersisted &&\n\t\t\t\trecord.sessionId === sessionId &&\n\t\t\t\tgetToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\tthis._toolArgumentValidationStats = stats;\n\t\treturn structuredClone(stats);\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\tconst entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t\tif (reportId) this.getCurrentSessionCostTotals();\n\t\treturn entryId;\n\t}\n\n\tprivate getCurrentSessionCostTotals(): CurrentSessionCostAccumulator {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tlet cache = this._currentSessionCostCache;\n\t\tconst getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);\n\t\tif (!cache || entryCount < cache.entryCount || !getEntriesSince) {\n\t\t\tcache = {\n\t\t\t\tentryCount,\n\t\t\t\taccumulator: accumulateCurrentSessionCostsFromEntries(\n\t\t\t\t\tcreateCurrentSessionCostAccumulator(),\n\t\t\t\t\tsessionManager.getEntries(),\n\t\t\t\t),\n\t\t\t};\n\t\t\tthis._currentSessionCostCache = cache;\n\t\t} else if (entryCount > cache.entryCount) {\n\t\t\taccumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));\n\t\t\tcache.entryCount = entryCount;\n\t\t}\n\t\treturn cache.accumulator;\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered\n\t * accumulator processes only new entries, so repeated turns do not rescan the full session.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst current = this.getCurrentSessionCostTotals();\n\t\treturn { cost: current.subagentCost, reports: current.subagentReports };\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tconst window = getLocalDayWindow(now);\n\t\tconst dailyTotals = this.getDailyUsageTotals(now);\n\t\tconst cached = this._costSummaryCache;\n\t\tif (\n\t\t\tcached?.entryCount === entryCount &&\n\t\t\tcached.dailyTotals === dailyTotals &&\n\t\t\tcached.windowStartMs === window.startMs &&\n\t\t\tcached.windowEndMs === window.endMs\n\t\t) {\n\t\t\treturn cached.summary;\n\t\t}\n\t\tconst summary = createSessionCostSummary({\n\t\t\tcurrentTotals: this.getCurrentSessionCostTotals(),\n\t\t\tdailyTotals,\n\t\t\ttodayWindow: window,\n\t\t});\n\t\tthis._costSummaryCache = {\n\t\t\tentryCount,\n\t\t\tdailyTotals,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\tsummary,\n\t\t};\n\t\treturn summary;\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tprivate getPostCompactionUsageState(): { hasCompaction: boolean; hasPostCompactionUsage: boolean } {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst indexedSessionManager = sessionManager as unknown as {\n\t\t\tgetLeafId?: () => string | null;\n\t\t\tgetEntry?: (id: string) => SessionEntry | undefined;\n\t\t};\n\t\tconst getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);\n\t\tconst getEntry = indexedSessionManager.getEntry?.bind(sessionManager);\n\t\tconst leafId = getLeafId?.();\n\t\tif (getLeafId && getEntry && leafId !== undefined) {\n\t\t\tif (this._postCompactionUsageCache?.leafId === leafId) {\n\t\t\t\treturn { ...this._postCompactionUsageCache };\n\t\t\t}\n\t\t\tconst appended: SessionEntry[] = [];\n\t\t\tconst cachedLeafId = this._postCompactionUsageCache?.leafId;\n\t\t\tlet currentId = leafId;\n\t\t\tconst seen = new Set<string>();\n\t\t\twhile (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {\n\t\t\t\tseen.add(currentId);\n\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\tif (!entry) break;\n\t\t\t\tappended.push(entry);\n\t\t\t\tcurrentId = entry.parentId;\n\t\t\t}\n\t\t\tconst extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;\n\t\t\tconst state = extendsCachedBranch\n\t\t\t\t? { ...this._postCompactionUsageCache! }\n\t\t\t\t: { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };\n\t\t\tif (!extendsCachedBranch && currentId !== null) {\n\t\t\t\twhile (currentId !== null && !seen.has(currentId)) {\n\t\t\t\t\tseen.add(currentId);\n\t\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\t\tif (!entry) break;\n\t\t\t\t\tappended.push(entry);\n\t\t\t\t\tcurrentId = entry.parentId;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let index = appended.length - 1; index >= 0; index--) {\n\t\t\t\tconst entry = appended[index];\n\t\t\t\tif (entry.type === \"compaction\") {\n\t\t\t\t\tstate.hasCompaction = true;\n\t\t\t\t\tstate.hasPostCompactionUsage = false;\n\t\t\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\" && state.hasCompaction) {\n\t\t\t\t\t\tstate.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate.leafId = leafId;\n\t\t\tthis._postCompactionUsageCache = state;\n\t\t\treturn { ...state };\n\t\t}\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tif (!latestCompaction) return { hasCompaction: false, hasPostCompactionUsage: false };\n\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };\n\t\t}\n\t\treturn { hasCompaction: true, hasPostCompactionUsage: false };\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// Walk backward only to the nearest compaction instead of rebuilding the whole branch on\n\t\t// every footer invalidation; the distance is bounded by the live post-compaction context.\n\t\tconst compactionState = this.getPostCompactionUsageState();\n\t\tif (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
@@ -11,6 +11,7 @@
11
11
  import type { Agent } from "@caupulican/pi-agent-core";
12
12
  import type { CapabilityEnvelope, GateOutcome } from "./autonomy/contracts.ts";
13
13
  import type { ExtensionRunner } from "./extensions/index.ts";
14
+ import type { ToolSelectionController } from "./tool-selection/tool-selection-controller.ts";
14
15
  type BeforeToolCall = NonNullable<Agent["beforeToolCall"]>;
15
16
  type AfterToolCall = NonNullable<Agent["afterToolCall"]>;
16
17
  export interface ToolGateControllerDeps {
@@ -25,6 +26,8 @@ export interface ToolGateControllerDeps {
25
26
  /** Record an autonomy gate outcome (only when a capability envelope is active). */
26
27
  recordGateOutcome(outcome: GateOutcome): void;
27
28
  getExtensionRunner(): ExtensionRunner;
29
+ /** Observe an execution only after all pre-execution gates and extension hooks allow it. */
30
+ getToolSelectionController?(): ToolSelectionController | undefined;
28
31
  }
29
32
  export declare class ToolGateController {
30
33
  private readonly deps;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-gate-controller.d.ts","sourceRoot":"","sources":["../../src/core/tool-gate-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAG7D,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAC3D,KAAK,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACtC,oFAAoF;IACpF,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpG,MAAM,IAAI,MAAM,CAAC;IACjB,iGAA+F;IAC/F,qBAAqB,IAAI,kBAAkB,GAAG,SAAS,CAAC;IACxD,mFAAmF;IACnF,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,kBAAkB,IAAI,eAAe,CAAC;CACtC;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,YAAY,IAAI,EAAE,sBAAsB,EAEvC;IAED,QAAQ,CAAC,cAAc,EAAE,cAAc,CA4CrC;IAEF,QAAQ,CAAC,aAAa,EAAE,aAAa,CAsCnC;CACF","sourcesContent":["/**\n * Agent tool-call gate: model-router escalation, autonomy gating, extension tool hooks, and the\n * untrusted-content output boundary.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Provides the two agent-core\n * hooks (`beforeToolCall`/`afterToolCall`) as bound arrow-field members the session installs onto its\n * agent. `beforeToolCall` runs the router escalation, then the autonomy tool gate (recording the\n * outcome when a capability envelope is active), then extension `tool_call` handlers. `afterToolCall`\n * runs extension `tool_result` handlers and structurally fences output from untrusted-content sources.\n */\n\nimport type { Agent } from \"@caupulican/pi-agent-core\";\nimport type { CapabilityEnvelope, GateOutcome } from \"./autonomy/contracts.ts\";\nimport { evaluateToolGate } from \"./autonomy/gates.ts\";\nimport type { ExtensionRunner } from \"./extensions/index.ts\";\nimport { classifyToolTrust, wrapUntrustedText } from \"./security/untrusted-boundary.ts\";\n\ntype BeforeToolCall = NonNullable<Agent[\"beforeToolCall\"]>;\ntype AfterToolCall = NonNullable<Agent[\"afterToolCall\"]>;\n\nexport interface ToolGateControllerDeps {\n\t/** Router escalation: block a tool the active cheap route is not allowed to run. */\n\tmaybeEscalateToolCall(toolName: string, args: unknown): { block: true; reason: string } | undefined;\n\tgetCwd(): string;\n\t/** Active foreground capability envelope, if any — presence enables gate-outcome recording. */\n\tgetCapabilityEnvelope(): CapabilityEnvelope | undefined;\n\t/** Record an autonomy gate outcome (only when a capability envelope is active). */\n\trecordGateOutcome(outcome: GateOutcome): void;\n\tgetExtensionRunner(): ExtensionRunner;\n}\n\nexport class ToolGateController {\n\tprivate readonly deps: ToolGateControllerDeps;\n\n\tconstructor(deps: ToolGateControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\treadonly beforeToolCall: BeforeToolCall = async ({ toolCall, args }) => {\n\t\tconst escalation = this.deps.maybeEscalateToolCall(toolCall.name, args);\n\t\tif (escalation) {\n\t\t\treturn escalation;\n\t\t}\n\n\t\t// Autonomy tool gating\n\t\tconst envelope = this.deps.getCapabilityEnvelope();\n\t\tconst gateResult = evaluateToolGate({\n\t\t\ttoolName: toolCall.name,\n\t\t\targs,\n\t\t\tcwd: this.deps.getCwd(),\n\t\t\tenvelope,\n\t\t});\n\n\t\tif (envelope) {\n\t\t\tthis.deps.recordGateOutcome(gateResult);\n\t\t}\n\n\t\tif (gateResult.outcome === \"block\" || gateResult.outcome === \"ask-user\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: `Tool execution blocked by autonomy gate [${gateResult.gate}]: ${gateResult.message} (${gateResult.reasonCode})`,\n\t\t\t};\n\t\t}\n\n\t\tconst runner = this.deps.getExtensionRunner();\n\t\tif (!runner.hasHandlers(\"tool_call\")) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\ttry {\n\t\t\treturn await runner.emitToolCall({\n\t\t\t\ttype: \"tool_call\",\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t});\n\t\t} catch (err) {\n\t\t\tif (err instanceof Error) {\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(err)}`);\n\t\t}\n\t};\n\n\treadonly afterToolCall: AfterToolCall = async ({ toolCall, args, result, isError }) => {\n\t\tconst runner = this.deps.getExtensionRunner();\n\t\tlet content = result.content;\n\t\tlet details = result.details;\n\t\tlet resolvedIsError = isError;\n\n\t\tif (runner.hasHandlers(\"tool_result\")) {\n\t\t\tconst hookResult = await runner.emitToolResult({\n\t\t\t\ttype: \"tool_result\",\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\tcontent,\n\t\t\t\tdetails,\n\t\t\t\tisError,\n\t\t\t});\n\t\t\tif (hookResult) {\n\t\t\t\tcontent = hookResult.content ?? content;\n\t\t\t\tdetails = hookResult.details;\n\t\t\t\tresolvedIsError = hookResult.isError ?? isError;\n\t\t\t}\n\t\t}\n\n\t\t// Untrusted-content boundary: structurally fence output from attacker-controllable sources\n\t\t// (web/search, subagents, recall, third-party tools) so injection payloads are framed as data.\n\t\t// First-party tools (read/grep/find/ls/edit/write/bash) are trusted and pass through unchanged.\n\t\tif (classifyToolTrust(toolCall.name) === \"untrusted\") {\n\t\t\tconst source = `tool:${toolCall.name}`;\n\t\t\tconst wrapped = content.map((block) =>\n\t\t\t\tblock.type === \"text\" ? { ...block, text: wrapUntrustedText(block.text, source) } : block,\n\t\t\t);\n\t\t\tcontent = wrapped;\n\t\t}\n\n\t\tif (content === result.content && details === result.details && resolvedIsError === isError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { content, details, isError: resolvedIsError };\n\t};\n}\n"]}
1
+ {"version":3,"file":"tool-gate-controller.d.ts","sourceRoot":"","sources":["../../src/core/tool-gate-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAwB,MAAM,2BAA2B,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,+CAA+C,CAAC;AAE7F,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC;AAC3D,KAAK,aAAa,GAAG,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;AAEzD,MAAM,WAAW,sBAAsB;IACtC,oFAAoF;IACpF,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpG,MAAM,IAAI,MAAM,CAAC;IACjB,iGAA+F;IAC/F,qBAAqB,IAAI,kBAAkB,GAAG,SAAS,CAAC;IACxD,mFAAmF;IACnF,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9C,kBAAkB,IAAI,eAAe,CAAC;IACtC,4FAA4F;IAC5F,0BAA0B,CAAC,IAAI,uBAAuB,GAAG,SAAS,CAAC;CACnE;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAyB;IAE9C,YAAY,IAAI,EAAE,sBAAsB,EAEvC;IAED,QAAQ,CAAC,cAAc,EAAE,cAAc,CA8CrC;IAEF,QAAQ,CAAC,aAAa,EAAE,aAAa,CAuCnC;CACF","sourcesContent":["/**\n * Agent tool-call gate: model-router escalation, autonomy gating, extension tool hooks, and the\n * untrusted-content output boundary.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Provides the two agent-core\n * hooks (`beforeToolCall`/`afterToolCall`) as bound arrow-field members the session installs onto its\n * agent. `beforeToolCall` runs the router escalation, then the autonomy tool gate (recording the\n * outcome when a capability envelope is active), then extension `tool_call` handlers. `afterToolCall`\n * runs extension `tool_result` handlers and structurally fences output from untrusted-content sources.\n */\n\nimport type { Agent, BeforeToolCallResult } from \"@caupulican/pi-agent-core\";\nimport type { CapabilityEnvelope, GateOutcome } from \"./autonomy/contracts.ts\";\nimport { evaluateToolGate } from \"./autonomy/gates.ts\";\nimport type { ExtensionRunner } from \"./extensions/index.ts\";\nimport { classifyToolTrust, wrapUntrustedText } from \"./security/untrusted-boundary.ts\";\nimport type { ToolSelectionController } from \"./tool-selection/tool-selection-controller.ts\";\n\ntype BeforeToolCall = NonNullable<Agent[\"beforeToolCall\"]>;\ntype AfterToolCall = NonNullable<Agent[\"afterToolCall\"]>;\n\nexport interface ToolGateControllerDeps {\n\t/** Router escalation: block a tool the active cheap route is not allowed to run. */\n\tmaybeEscalateToolCall(toolName: string, args: unknown): { block: true; reason: string } | undefined;\n\tgetCwd(): string;\n\t/** Active foreground capability envelope, if any — presence enables gate-outcome recording. */\n\tgetCapabilityEnvelope(): CapabilityEnvelope | undefined;\n\t/** Record an autonomy gate outcome (only when a capability envelope is active). */\n\trecordGateOutcome(outcome: GateOutcome): void;\n\tgetExtensionRunner(): ExtensionRunner;\n\t/** Observe an execution only after all pre-execution gates and extension hooks allow it. */\n\tgetToolSelectionController?(): ToolSelectionController | undefined;\n}\n\nexport class ToolGateController {\n\tprivate readonly deps: ToolGateControllerDeps;\n\n\tconstructor(deps: ToolGateControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\treadonly beforeToolCall: BeforeToolCall = async ({ toolCall, args }) => {\n\t\tconst escalation = this.deps.maybeEscalateToolCall(toolCall.name, args);\n\t\tif (escalation) {\n\t\t\treturn escalation;\n\t\t}\n\n\t\t// Autonomy tool gating\n\t\tconst envelope = this.deps.getCapabilityEnvelope();\n\t\tconst gateResult = evaluateToolGate({\n\t\t\ttoolName: toolCall.name,\n\t\t\targs,\n\t\t\tcwd: this.deps.getCwd(),\n\t\t\tenvelope,\n\t\t});\n\n\t\tif (envelope) {\n\t\t\tthis.deps.recordGateOutcome(gateResult);\n\t\t}\n\n\t\tif (gateResult.outcome === \"block\" || gateResult.outcome === \"ask-user\") {\n\t\t\treturn {\n\t\t\t\tblock: true,\n\t\t\t\treason: `Tool execution blocked by autonomy gate [${gateResult.gate}]: ${gateResult.message} (${gateResult.reasonCode})`,\n\t\t\t};\n\t\t}\n\n\t\tconst runner = this.deps.getExtensionRunner();\n\t\tlet extensionResult: BeforeToolCallResult | undefined;\n\t\tif (runner.hasHandlers(\"tool_call\")) {\n\t\t\ttry {\n\t\t\t\textensionResult = await runner.emitToolCall({\n\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\t});\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t\tthrow new Error(`Extension failed, blocking execution: ${String(err)}`);\n\t\t\t}\n\t\t}\n\t\tif (extensionResult) return extensionResult;\n\t\tthis.deps.getToolSelectionController?.()?.begin(toolCall.id, toolCall.name, args);\n\t\treturn undefined;\n\t};\n\n\treadonly afterToolCall: AfterToolCall = async ({ toolCall, args, result, isError }) => {\n\t\tconst runner = this.deps.getExtensionRunner();\n\t\tlet content = result.content;\n\t\tlet details = result.details;\n\t\tlet resolvedIsError = isError;\n\n\t\tif (runner.hasHandlers(\"tool_result\")) {\n\t\t\tconst hookResult = await runner.emitToolResult({\n\t\t\t\ttype: \"tool_result\",\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\tinput: args as Record<string, unknown>,\n\t\t\t\tcontent,\n\t\t\t\tdetails,\n\t\t\t\tisError,\n\t\t\t});\n\t\t\tif (hookResult) {\n\t\t\t\tcontent = hookResult.content ?? content;\n\t\t\t\tdetails = hookResult.details;\n\t\t\t\tresolvedIsError = hookResult.isError ?? isError;\n\t\t\t}\n\t\t}\n\n\t\t// Untrusted-content boundary: structurally fence output from attacker-controllable sources\n\t\t// (web/search, subagents, recall, third-party tools) so injection payloads are framed as data.\n\t\t// First-party tools (read/grep/find/ls/edit/write/bash) are trusted and pass through unchanged.\n\t\tif (classifyToolTrust(toolCall.name) === \"untrusted\") {\n\t\t\tconst source = `tool:${toolCall.name}`;\n\t\t\tconst wrapped = content.map((block) =>\n\t\t\t\tblock.type === \"text\" ? { ...block, text: wrapUntrustedText(block.text, source) } : block,\n\t\t\t);\n\t\t\tcontent = wrapped;\n\t\t}\n\n\t\tthis.deps.getToolSelectionController?.()?.complete(toolCall.id, !resolvedIsError, content);\n\t\tif (content === result.content && details === result.details && resolvedIsError === isError) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn { content, details, isError: resolvedIsError };\n\t};\n}\n"]}