mixdog 0.9.152 → 0.9.154

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 (300) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +8 -10
  4. package/package.json +19 -3
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/src/agents/front-worker/AGENT.md +13 -0
  8. package/src/agents/front-worker/agent.json +6 -0
  9. package/src/agents/heavy-worker/AGENT.md +10 -11
  10. package/src/agents/heavy-worker/agent.json +1 -1
  11. package/src/agents/maintainer/AGENT.md +0 -2
  12. package/src/agents/reviewer/AGENT.md +9 -13
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/agents/security/AGENT.md +14 -0
  15. package/src/agents/security/agent.json +6 -0
  16. package/src/agents/worker/AGENT.md +8 -8
  17. package/src/agents/worker/agent.json +1 -1
  18. package/src/agents/writer/AGENT.md +14 -0
  19. package/src/agents/writer/agent.json +6 -0
  20. package/src/rules/agent/00-common.md +4 -7
  21. package/src/rules/lead/01-general.md +0 -3
  22. package/src/rules/shared/10-tool-workflow.md +10 -2
  23. package/src/rules/shared/60-verification.md +2 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  25. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  26. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  27. package/src/runtime/agent/orchestrator/config.mjs +7 -2
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  29. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -1
  30. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  31. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  32. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +25 -2
  33. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -10
  34. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  35. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  36. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +140 -86
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -13
  38. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +72 -4
  39. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  40. package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +0 -22
  41. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +11 -23
  43. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +28 -269
  44. package/src/runtime/agent/orchestrator/session/compact.mjs +0 -6
  45. package/src/runtime/agent/orchestrator/session/context-utils.mjs +86 -11
  46. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  47. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  48. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  49. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  50. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +80 -18
  51. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  52. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +3 -0
  53. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  54. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +80 -21
  55. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +33 -72
  56. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +21 -2
  57. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -51
  58. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  59. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  60. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +39 -15
  61. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +14 -4
  62. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +41 -8
  63. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  64. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +2 -2
  65. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  66. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  67. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  68. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  69. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  70. package/src/runtime/agent/orchestrator/session/store.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  72. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  73. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +4 -0
  74. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  75. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  76. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  77. package/src/runtime/browser-bridge/client.mjs +75 -26
  78. package/src/runtime/browser-bridge/tool-defs.mjs +132 -15
  79. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  80. package/src/runtime/computer-bridge/client.mjs +211 -28
  81. package/src/runtime/computer-bridge/tool-defs.mjs +25 -45
  82. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  83. package/src/runtime/office/assurance.mjs +833 -0
  84. package/src/runtime/office/benchmark.mjs +496 -0
  85. package/src/runtime/office/capabilities.mjs +551 -0
  86. package/src/runtime/office/com-adapter.mjs +283 -0
  87. package/src/runtime/office/content-model.mjs +183 -0
  88. package/src/runtime/office/contract-benchmark.mjs +105 -0
  89. package/src/runtime/office/design-library.mjs +1562 -0
  90. package/src/runtime/office/design-system.mjs +1966 -0
  91. package/src/runtime/office/index.mjs +2413 -0
  92. package/src/runtime/office/journal.mjs +82 -0
  93. package/src/runtime/office/office-com-host.ps1 +3781 -0
  94. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  95. package/src/runtime/office/ooxml-validator.mjs +271 -0
  96. package/src/runtime/office/pagination.mjs +132 -0
  97. package/src/runtime/office/pdf-adapter.mjs +591 -0
  98. package/src/runtime/office/pdf-analysis.mjs +372 -0
  99. package/src/runtime/office/pdf-render.mjs +167 -0
  100. package/src/runtime/office/pdf-security.mjs +63 -0
  101. package/src/runtime/office/polish-benchmark.mjs +190 -0
  102. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  103. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  104. package/src/runtime/office/quality-pipeline.mjs +136 -0
  105. package/src/runtime/office/tabular.mjs +302 -0
  106. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  107. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  108. package/src/runtime/office/tool-defs.mjs +113 -0
  109. package/src/runtime/office/visual-diff.mjs +89 -0
  110. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  111. package/src/runtime/office/xlsx-contract.mjs +147 -0
  112. package/src/runtime/shared/agent-route-config.mjs +9 -0
  113. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  114. package/src/runtime/shared/tool-surface.mjs +9 -0
  115. package/src/session-runtime/context-status.mjs +9 -3
  116. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  117. package/src/session-runtime/goal-runtime.mjs +617 -197
  118. package/src/session-runtime/lifecycle-api.mjs +50 -8
  119. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  120. package/src/session-runtime/resource-api.mjs +31 -94
  121. package/src/session-runtime/runtime-core.mjs +78 -26
  122. package/src/session-runtime/session-lifecycle.mjs +34 -12
  123. package/src/session-runtime/session-title.mjs +2 -2
  124. package/src/session-runtime/session-turn-api.mjs +15 -14
  125. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  126. package/src/session-runtime/tool-catalog.mjs +38 -99
  127. package/src/session-runtime/tool-defs.mjs +6 -1
  128. package/src/session-runtime/tool-surface.mjs +18 -8
  129. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  130. package/src/session-runtime/workflow.mjs +8 -6
  131. package/src/standalone/agent-tool/helpers.mjs +2 -2
  132. package/src/standalone/agent-tool/job-views.mjs +3 -1
  133. package/src/standalone/agent-tool/notify.mjs +5 -1
  134. package/src/standalone/agent-tool/render.mjs +3 -0
  135. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  136. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  137. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  138. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  139. package/src/standalone/agent-tool.mjs +57 -10
  140. package/src/standalone/daemon.mjs +105 -6
  141. package/src/standalone/session-runtime-host.mjs +61 -48
  142. package/src/standalone/session-runtime-inline-host.mjs +34 -127
  143. package/src/standalone/session-runtime-worker.mjs +20 -198
  144. package/src/standalone/session-service.mjs +632 -7
  145. package/src/tui/dist/index.mjs +24 -1
  146. package/src/tui/lib/voice-setup.mjs +4 -1
  147. package/src/tui/session/context-state.mjs +2 -13
  148. package/src/tui/session/goal-continuation.mjs +38 -7
  149. package/src/tui/session/session-api-ext.mjs +9 -0
  150. package/src/tui/session/session-api.mjs +15 -1
  151. package/src/tui/session/session-flow.mjs +25 -1
  152. package/src/tui/session/turn.mjs +88 -16
  153. package/src/tui/session-local.mjs +25 -3
  154. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  155. package/scripts/generate-voice-runtime-manifest.test.mjs +0 -135
  156. package/scripts/native-binary-arch.test.mjs +0 -111
  157. package/scripts/patch-replay.test.mjs +0 -94
  158. package/src/headless-exec.test.mjs +0 -539
  159. package/src/runtime/agent/orchestrator/agent-trace-timing.test.mjs +0 -57
  160. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +0 -153
  161. package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +0 -139
  162. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +0 -66
  163. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +0 -80
  164. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +0 -90
  165. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +0 -215
  166. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.test.mjs +0 -165
  167. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +0 -142
  168. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -153
  169. package/src/runtime/agent/orchestrator/providers/openai-codex-model.test.mjs +0 -24
  170. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +0 -190
  171. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +0 -235
  172. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +0 -719
  173. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.test.mjs +0 -40
  174. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +0 -176
  175. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +0 -161
  176. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +0 -115
  177. package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +0 -228
  178. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +0 -245
  179. package/src/runtime/agent/orchestrator/session/lossless-tool-output.test.mjs +0 -125
  180. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +0 -325
  181. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +0 -70
  182. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +0 -457
  183. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.test.mjs +0 -117
  184. package/src/runtime/agent/orchestrator/session/reduction-metrics.test.mjs +0 -87
  185. package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +0 -60
  186. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +0 -583
  187. package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +0 -78
  188. package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +0 -43
  189. package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +0 -63
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +0 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +0 -224
  192. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +0 -224
  193. package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +0 -56
  194. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +0 -103
  195. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.test.mjs +0 -30
  196. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +0 -390
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.test.mjs +0 -83
  198. package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +0 -31
  199. package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +0 -106
  200. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.test.mjs +0 -102
  201. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +0 -152
  202. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +0 -24
  203. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +0 -24
  204. package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +0 -69
  205. package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +0 -71
  206. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +0 -56
  207. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +0 -16
  208. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-top-k.test.mjs +0 -39
  209. package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +0 -153
  210. package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +0 -55
  211. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +0 -88
  212. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +0 -72
  213. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +0 -54
  214. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.test.mjs +0 -20
  215. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +0 -55
  216. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +0 -268
  217. package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +0 -57
  218. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.test.mjs +0 -19
  219. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +0 -104
  220. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +0 -661
  221. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +0 -552
  222. package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +0 -141
  223. package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +0 -57
  224. package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +0 -81
  225. package/src/runtime/agent/orchestrator/tools/spawn-binary-fetcher.test.mjs +0 -52
  226. package/src/runtime/attachments/store.test.mjs +0 -209
  227. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +0 -17
  228. package/src/runtime/channels/lib/webhook/deliveries.test.mjs +0 -18
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.test.mjs +0 -68
  230. package/src/runtime/media/adapters/codex-image.test.mjs +0 -22
  231. package/src/runtime/media/download.test.mjs +0 -52
  232. package/src/runtime/media/renditions.test.mjs +0 -99
  233. package/src/runtime/media/store.test.mjs +0 -159
  234. package/src/runtime/memory/lib/archive-security.test.mjs +0 -128
  235. package/src/runtime/memory/lib/embedding-model-config.test.mjs +0 -33
  236. package/src/runtime/memory/lib/http-wire.test.mjs +0 -15
  237. package/src/runtime/memory/lib/memory-cycle-packets.test.mjs +0 -49
  238. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +0 -78
  239. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +0 -50
  240. package/src/runtime/memory/lib/pg/process.test.mjs +0 -36
  241. package/src/runtime/memory/lib/trace-store.test.mjs +0 -57
  242. package/src/runtime/memory/lib/transcript-ingest.test.mjs +0 -52
  243. package/src/runtime/shared/background-tasks.test.mjs +0 -78
  244. package/src/runtime/shared/bounded-download.test.mjs +0 -41
  245. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -25
  246. package/src/runtime/shared/child-spawn-remote.test.mjs +0 -124
  247. package/src/runtime/shared/remote-intent.test.mjs +0 -40
  248. package/src/runtime/shared/resource-admission.test.mjs +0 -43
  249. package/src/runtime/shared/runtime-root.test.mjs +0 -34
  250. package/src/runtime/shared/schedule-time.test.mjs +0 -29
  251. package/src/runtime/shared/session-runtime-health.test.mjs +0 -83
  252. package/src/runtime/shared/skill-document.test.mjs +0 -81
  253. package/src/runtime/shared/tool-surface.test.mjs +0 -87
  254. package/src/runtime/shared/update-checker.test.mjs +0 -33
  255. package/src/runtime/shared/user-cwd.test.mjs +0 -43
  256. package/src/runtime/shared/webhook-session-run.test.mjs +0 -14
  257. package/src/session-runtime/agent-disable.test.mjs +0 -123
  258. package/src/session-runtime/cwd-plugins.test.mjs +0 -75
  259. package/src/session-runtime/cwd-tool-routing.test.mjs +0 -61
  260. package/src/session-runtime/global-extensions.test.mjs +0 -28
  261. package/src/session-runtime/goal-runtime.test.mjs +0 -108
  262. package/src/session-runtime/lifecycle-api.test.mjs +0 -21
  263. package/src/session-runtime/model-settings-persist.test.mjs +0 -105
  264. package/src/session-runtime/notification-bus.test.mjs +0 -148
  265. package/src/session-runtime/plugin-mcp-standard.test.mjs +0 -177
  266. package/src/session-runtime/prewarm.test.mjs +0 -52
  267. package/src/session-runtime/provider-request-snapshot.test.mjs +0 -37
  268. package/src/session-runtime/resource-api-global.test.mjs +0 -69
  269. package/src/session-runtime/session-lifecycle.test.mjs +0 -125
  270. package/src/session-runtime/session-title.test.mjs +0 -36
  271. package/src/session-runtime/skills-api.test.mjs +0 -99
  272. package/src/session-runtime/tool-policy-surface.test.mjs +0 -331
  273. package/src/standalone/agent-tool/helpers.test.mjs +0 -62
  274. package/src/standalone/agent-tool/notify.test.mjs +0 -37
  275. package/src/standalone/agent-tool/spawn-flow.test.mjs +0 -63
  276. package/src/standalone/channel-restart.test.mjs +0 -145
  277. package/src/standalone/channel-session-router.test.mjs +0 -37
  278. package/src/standalone/channel-worker-heartbeat.test.mjs +0 -30
  279. package/src/standalone/hook-bus/handlers.test.mjs +0 -88
  280. package/src/standalone/plugin-admin.test.mjs +0 -173
  281. package/src/standalone/session-runtime-agent-control-client.test.mjs +0 -71
  282. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +0 -139
  283. package/src/standalone/session-runtime-host-factory.test.mjs +0 -31
  284. package/src/standalone/session-runtime-host-health.test.mjs +0 -160
  285. package/src/standalone/session-runtime-inline-host.test.mjs +0 -83
  286. package/src/standalone/session-runtime-provider-cooldown.test.mjs +0 -80
  287. package/src/standalone/session-runtime-shard-host.test.mjs +0 -569
  288. package/src/standalone/session-runtime-shard-router.test.mjs +0 -101
  289. package/src/tui/app/live-spinner-visibility.test.mjs +0 -44
  290. package/src/tui/app/panel-epoch.test.mjs +0 -1009
  291. package/src/tui/app/panel-handoff.test.mjs +0 -311
  292. package/src/tui/app/prompt-submit.test.mjs +0 -206
  293. package/src/tui/session/completion-card-restore.test.mjs +0 -111
  294. package/src/tui/session/goal-continuation.test.mjs +0 -84
  295. package/src/tui/session/oauth-flows.test.mjs +0 -134
  296. package/src/tui/session/queue-helpers.test.mjs +0 -46
  297. package/src/tui/session/session-action-surface.test.mjs +0 -108
  298. package/src/tui/session/task-wait-submit.test.mjs +0 -80
  299. package/src/tui/session/tool-result-status.test.mjs +0 -131
  300. package/src/tui/session/tui-steering-persist.test.mjs +0 -104
@@ -1,13 +1,11 @@
1
- // Session tool schema resolution + permission narrowing, extracted verbatim
2
- // from manager.mjs. Behavior-preserving: same toolset ids, same ordering,
3
- // same dedup, same fail-closed permission intersection.
1
+ // Session tool schema resolution. Lead and Agent share one full-capability
2
+ // catalog; Agent removes only the recursive `agent` control tool.
4
3
  import { getMcpTools } from '../../mcp/client.mjs';
5
4
  import { getInternalTools } from '../../internal-tools.mjs';
6
5
  import { BUILTIN_TOOLS } from '../../tools/builtin/builtin-tools.mjs';
7
6
  import { PATCH_TOOL_DEFS } from '../../tools/patch-tool-defs.mjs';
8
7
  import { CODE_GRAPH_TOOL_DEFS } from '../../tools/code-graph-tool-defs.mjs';
9
8
  import { buildSkillToolDefs } from '../../context/collect.mjs';
10
- import { getHiddenAgent } from '../../internal-agents.mjs';
11
9
  import { filterModelEditTools } from '../../../../shared/edit-tool-dialect.mjs';
12
10
 
13
11
  // Merge externally-connected MCP tools with the plugin's in-process tools
@@ -81,101 +79,6 @@ const READONLY_TOOL_NAMES = new Set([
81
79
  'read',
82
80
  ]);
83
81
 
84
- const AGENT_STRING_PERMISSION_READ_ALLOW = Object.freeze([
85
- 'code_graph',
86
- 'find',
87
- 'glob',
88
- 'list',
89
- 'grep',
90
- 'read',
91
- // shell/task: verifying read-role agents (reviewer) must run their own
92
- // verification (tests/repro). task is required because shell's
93
- // auto-background transition settles with a bare jobId — agent sessions
94
- // get no completion notification, so `task wait/read` is the only way to
95
- // collect a long-running result. apply_patch stays excluded: read roles
96
- // keep the no-edit contract.
97
- 'shell',
98
- 'task',
99
- 'web_search',
100
- 'web_fetch',
101
- 'Skill',
102
- ]);
103
-
104
- // Retrieval roles never self-verify, so they keep the historical no-shell
105
- // read surface. Covers hidden retrieval agents and invokedBy wrapper roles,
106
- // the same set
107
- // pre-dispatch-deny treats as recursive wrappers. Only verifying read roles
108
- // (reviewer) get shell/task.
109
- const AGENT_STRING_PERMISSION_READ_RETRIEVAL_ALLOW = Object.freeze(
110
- AGENT_STRING_PERMISSION_READ_ALLOW.filter((n) => n !== 'shell' && n !== 'task'),
111
- );
112
-
113
- function isRetrievalToolRole(agent) {
114
- if (!agent) return false;
115
- return getHiddenAgent(agent)?.kind === 'retrieval';
116
- }
117
-
118
- function stringToolPermissionAllowList(toolPermission, { retrieval = false } = {}) {
119
- if (toolPermission === 'read') {
120
- return retrieval ? AGENT_STRING_PERMISSION_READ_RETRIEVAL_ALLOW : AGENT_STRING_PERMISSION_READ_ALLOW;
121
- }
122
- if (toolPermission === 'read-write') return AGENT_STRING_PERMISSION_READ_WRITE_ALLOW;
123
- if (toolPermission === 'none') return [];
124
- return null;
125
- }
126
-
127
- // Read-write agent bundle: full edit surface INCLUDING shell/task so
128
- // write-role agents can run their own verification (build/test) without
129
- // bouncing it back to Lead. Deploy/ship remains a workflow-level rule,
130
- // not a tool-surface restriction.
131
- const AGENT_STRING_PERMISSION_READ_WRITE_ALLOW = Object.freeze([
132
- 'code_graph',
133
- 'find',
134
- 'glob',
135
- 'list',
136
- 'grep',
137
- 'read',
138
- 'edit',
139
- 'apply_patch',
140
- 'git',
141
- 'git_stage',
142
- 'shell',
143
- 'task',
144
- 'web_search',
145
- 'web_fetch',
146
- 'Skill',
147
- ]);
148
-
149
- export function applyToolPermissionNarrowing(tools, toolPermission, warnRole = null) {
150
- if (toolPermission === 'none') return [];
151
- const allowList = stringToolPermissionAllowList(toolPermission, { retrieval: isRetrievalToolRole(warnRole) });
152
- if (allowList) {
153
- const allowSet = new Set(allowList.map((n) => String(n).toLowerCase()));
154
- return tools.filter((t) => allowSet.has(String(t?.name || '').toLowerCase()));
155
- }
156
- if (toolPermission && typeof toolPermission === 'object') {
157
- const allowSet = Array.isArray(toolPermission.allow) && toolPermission.allow.length > 0
158
- ? new Set(toolPermission.allow.map(n => String(n).toLowerCase()))
159
- : null;
160
- const denySet = Array.isArray(toolPermission.deny) && toolPermission.deny.length > 0
161
- ? new Set(toolPermission.deny.map(n => String(n).toLowerCase()))
162
- : null;
163
- if (allowSet || denySet) {
164
- const filtered = tools.filter(t => {
165
- const name = String(t?.name || '').toLowerCase();
166
- if (denySet && denySet.has(name)) return false;
167
- if (allowSet && !allowSet.has(name)) return false;
168
- return true;
169
- });
170
- if (filtered.length === 0) {
171
- process.stderr.write(`[session] WARN: role permission intersection produced 0 tools — failing closed (role=${warnRole || 'unknown'})\n`);
172
- }
173
- return filtered;
174
- }
175
- }
176
- return tools;
177
- }
178
-
179
82
  export function finalizeSessionToolList(tools, {
180
83
  schemaAllowedTools = null,
181
84
  disallowedTools = null,
@@ -194,7 +97,7 @@ export function finalizeSessionToolList(tools, {
194
97
  out = out.filter(t => !denySet.has(String(t?.name || '').toLowerCase()));
195
98
  }
196
99
  if (ownerIsAgent) {
197
- out = out.filter(t => !t?.annotations?.agentHidden);
100
+ out = out.filter(t => String(t?.name || '').toLowerCase() !== 'agent');
198
101
  out = orderSessionTools(out);
199
102
  }
200
103
  return out;
@@ -312,16 +215,7 @@ function _computeBaseTools(toolSpec, mcp, skillTools, { ownerIsAgentSession = fa
312
215
  return orderSessionTools(_dedupByName([...mcp, ...skillTools]));
313
216
  case 'readonly': {
314
217
  const readTools = ALL_BUILTIN_SESSION_TOOLS.filter(t => READONLY_TOOL_NAMES.has(t.name));
315
- // Read-ROLE agent sessions (reviewer) must self-verify, so
316
- // their base bundle carries shell/task defs. The 'read' permission
317
- // allowlist (AGENT_STRING_PERMISSION_READ_ALLOW) is a pure FILTER —
318
- // it can only keep tools already assembled here, so without this the
319
- // allowlist's shell/task entries were dead letters. Non-agent
320
- // 'readonly' profiles stay strictly read-only.
321
- const verifyTools = ownerIsAgentSession
322
- ? ALL_BUILTIN_SESSION_TOOLS.filter(t => t.name === 'shell' || t.name === 'task')
323
- : [];
324
- return orderSessionTools(_dedupByName([...readTools, ...verifyTools, ...mcp, ...skillTools]));
218
+ return orderSessionTools(_dedupByName([...readTools, ...mcp, ...skillTools]));
325
219
  }
326
220
  case 'full':
327
221
  default:
@@ -15,9 +15,10 @@
15
15
  // before the provider ever runs, and store-summary-reader
16
16
  // keeps probing this exact path to detect a live turn.
17
17
  // <session>.jsonl append-only JSONL deltas — NEW/CHANGED turn messages (by
18
- // turn-relative index), APPENDED partial text/reasoning and
19
- // small interruption-state transitions. A flush costs
20
- // O(new bytes), never O(turn).
18
+ // turn-relative index), a one-time full replacement when
19
+ // compaction removes the opening prompt, APPENDED partial
20
+ // text/reasoning and small interruption-state transitions.
21
+ // A normal flush costs O(new bytes), never O(turn).
21
22
  // Appends ride a bounded coalescing lane (one in-flight write, one pending
22
23
  // buffer, synchronous drain above the byte ceiling) so the engine thread never
23
24
  // blocks on disk and the queue can never grow without limit.
@@ -434,7 +435,8 @@ export function replayTurnJournal(header) {
434
435
  } catch {
435
436
  return header;
436
437
  }
437
- const messages = header.turnMessages.slice();
438
+ let messages = header.turnMessages.slice();
439
+ let fullTranscript = header.fullTranscript === true;
438
440
  const state = interruptionStateFromSnapshot(header.interruption);
439
441
  let updatedAt = Number(header.updatedAt) || 0;
440
442
  let head = false;
@@ -469,6 +471,9 @@ export function replayTurnJournal(header) {
469
471
  if (Number.isInteger(index) && index >= 0 && index <= messages.length) {
470
472
  messages[index] = record.m;
471
473
  }
474
+ } else if (record.t === 'full' && Array.isArray(record.ms)) {
475
+ messages = record.ms.slice();
476
+ fullTranscript = true;
472
477
  } else if (record.t === 'len') {
473
478
  const next = Number(record.n);
474
479
  if (Number.isInteger(next) && next >= 0 && next <= messages.length) {
@@ -484,6 +489,7 @@ export function replayTurnJournal(header) {
484
489
  return {
485
490
  ...header,
486
491
  turnMessages: messages,
492
+ ...(fullTranscript ? { fullTranscript: true } : {}),
487
493
  interruption: interruptionSnapshotFromState(state),
488
494
  updatedAt,
489
495
  };
@@ -496,6 +502,7 @@ export function replayTurnJournal(header) {
496
502
  */
497
503
  export function createTurnJournalEncoder({ turnToken } = {}) {
498
504
  let journaled = [];
505
+ let fullTranscript = false;
499
506
  let cursor = null;
500
507
  // Per-turn record sequence. It is assigned where records are produced, so
501
508
  // the number line is exactly the enqueue order the write lane preserves.
@@ -513,7 +520,8 @@ export function createTurnJournalEncoder({ turnToken } = {}) {
513
520
  seed(turnOutgoing, currentUserContent, interruption) {
514
521
  const source = Array.isArray(turnOutgoing) ? turnOutgoing : [];
515
522
  const start = findTurnStart(source, currentUserContent);
516
- journaled = start >= 0 ? source.slice(start) : [];
523
+ fullTranscript = start < 0;
524
+ journaled = fullTranscript ? source.slice() : source.slice(start);
517
525
  const seeded = typeof interruption?.journalDelta === 'function'
518
526
  ? interruption.journalDelta(null)
519
527
  : null;
@@ -523,23 +531,39 @@ export function createTurnJournalEncoder({ turnToken } = {}) {
523
531
  if (seeded?.changed) openLines.push(line({ t: 'x', d: seeded.delta, at: Date.now() }));
524
532
  return {
525
533
  turnMessages: journaled.map(checkpointMessage),
534
+ fullTranscript,
526
535
  openLines,
527
536
  };
528
537
  },
529
538
  encode({ turnOutgoing, currentUserContent, interruption }) {
530
539
  const source = Array.isArray(turnOutgoing) ? turnOutgoing : [];
531
540
  const start = findTurnStart(source, currentUserContent);
532
- const length = start >= 0 ? source.length - start : 0;
533
541
  const records = [];
534
- for (let index = 0; index < length; index += 1) {
535
- const raw = source[start + index];
536
- if (journaled[index] === raw) continue;
537
- journaled[index] = raw;
538
- records.push({ t: 'm', i: index, m: checkpointMessage(raw) });
539
- }
540
- if (journaled.length > length) {
541
- journaled.length = length;
542
- records.push({ t: 'len', n: length });
542
+ if (!fullTranscript && start < 0) {
543
+ // Pre-send compaction may replace the whole history, including
544
+ // the user message that defined our turn-relative origin. The
545
+ // replacement is normally small; persist it once so reconnect
546
+ // and crash recovery never splice an empty turn onto the stale
547
+ // pre-compaction prefix.
548
+ fullTranscript = true;
549
+ journaled = source.slice();
550
+ records.push({
551
+ t: 'full',
552
+ ms: journaled.map(checkpointMessage),
553
+ });
554
+ } else {
555
+ const offset = fullTranscript ? 0 : start;
556
+ const length = offset >= 0 ? source.length - offset : 0;
557
+ for (let index = 0; index < length; index += 1) {
558
+ const raw = source[offset + index];
559
+ if (journaled[index] === raw) continue;
560
+ journaled[index] = raw;
561
+ records.push({ t: 'm', i: index, m: checkpointMessage(raw) });
562
+ }
563
+ if (journaled.length > length) {
564
+ journaled.length = length;
565
+ records.push({ t: 'len', n: length });
566
+ }
543
567
  }
544
568
  if (typeof interruption?.journalDelta === 'function') {
545
569
  const next = interruption.journalDelta(cursor);
@@ -40,6 +40,9 @@ export function projectTurnCheckpointMessages(session, checkpoint) {
40
40
  if (checkpointGeneration !== sessionGeneration
41
41
  || (markerToken && !markerMatches)
42
42
  || (!markerToken && !checkpointIsNewer)) return source;
43
+ if (checkpoint.fullTranscript === true) {
44
+ return checkpoint.turnMessages.slice();
45
+ }
43
46
  let start = -1;
44
47
  for (let index = source.length - 1; index >= 0; index -= 1) {
45
48
  if (matchingUserMessage(source[index], checkpoint.currentUserContent)) {
@@ -80,6 +83,7 @@ export function createTurnCheckpointRecorder({ sessionId, generation, turnToken,
80
83
  startedAt,
81
84
  currentUserContent,
82
85
  turnMessages: seed.turnMessages,
86
+ ...(seed.fullTranscript ? { fullTranscript: true } : {}),
83
87
  interruption: typeof interruption?.snapshot === 'function'
84
88
  ? interruption.snapshot()
85
89
  : emptyInterruptionSnapshot(),
@@ -196,10 +200,16 @@ export function recoverTurnCheckpoint(session) {
196
200
  });
197
201
  const current = Array.isArray(session.messages) ? session.messages : [];
198
202
  const start = findTurnStart(current, checkpoint.currentUserContent);
199
- session.messages = [
200
- ...(start >= 0 ? current.slice(0, start) : current),
201
- ...finalized.messages,
202
- ];
203
+ session.messages = checkpoint.fullTranscript === true
204
+ ? finalized.messages
205
+ : [
206
+ ...(start >= 0 ? current.slice(0, start) : current),
207
+ ...finalized.messages,
208
+ ];
209
+ // Keep the last provider reading across restart. Pressure resolution
210
+ // validates its message-prefix signature against this recovered transcript:
211
+ // a matching durable prefix stays authoritative, while any rewritten
212
+ // prefix falls back to a local estimate until the next provider response.
203
213
  delete session.activeTurnCheckpoint;
204
214
  delete session.providerState;
205
215
  delete session._providerPrefixGuardState;
@@ -12,6 +12,7 @@ import {
12
12
  shouldCompactForSession,
13
13
  countPrunedToolOutputs,
14
14
  rememberCompactTelemetry,
15
+ recordContextUsageSnapshot,
15
16
  emitCompactEvent,
16
17
  compactEventType,
17
18
  resolveSemanticSummaryModel,
@@ -25,6 +26,7 @@ import {
25
26
  } from './compact.mjs';
26
27
  import { runRecallFastTrackCompact } from './loop/recall-fasttrack.mjs';
27
28
  import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
29
+ import { hasPendingNativeTokenCounts, withSyncTokenCounts } from './context-utils.mjs';
28
30
  import { messagesArrayChanged } from './loop/tool-helpers.mjs';
29
31
  import { normalizeUsage, addUsage } from './loop/usage.mjs';
30
32
  import { agentContextOverflowError } from './loop/context-overflow.mjs';
@@ -69,23 +71,46 @@ export async function runPreSendCompactPass(state) {
69
71
  // a best-effort JSON.stringify length — close enough to the
70
72
  // payload we hand the provider for prefix-cache analysis.
71
73
  const beforeCount = messages.length;
72
- const messageTokensEst = estimateMessagesTokensSafe(messages);
74
+ let messageTokensEst = estimateMessagesTokensSafe(messages);
73
75
  const reactivePending = reactiveOverflowRetryPending === true;
74
- const pressureTokens = compactionTelemetryPressureTokens(messageTokensEst, compactPolicy, {
76
+ let pressureTokens = compactionTelemetryPressureTokens(messageTokensEst, compactPolicy, {
75
77
  reactivePending,
76
78
  messages,
77
79
  sessionRef,
78
80
  });
79
- // This is the exact canonical value used by the decision below.
80
- // Reactive overflow recovery floors it at the trigger so the gauge,
81
- // telemetry, and forced compact still describe the same event.
82
- const gaugeBeforeTokens = pressureTokens;
83
- const shouldCompact = shouldCompactForSession(messageTokensEst, compactPolicy, {
81
+ let shouldCompact = shouldCompactForSession(messageTokensEst, compactPolicy, {
84
82
  forceReactive: reactivePending,
85
83
  messages,
86
84
  sessionRef,
87
85
  pressureTokens,
88
86
  });
87
+ // Large fresh strings (>=32KB) may still be counting on the native
88
+ // token server; until the exact count lands, estimateTokens serves
89
+ // the CONSERVATIVE legacy heuristic. Never fire a compaction off
90
+ // that transient overshoot: when the decision says compact while
91
+ // counts are pending, redo the estimate synchronously (exact BPE)
92
+ // and re-decide. The fast path pays nothing; a genuine trigger
93
+ // pays one exact encode whose result seeds the shared cache.
94
+ if (shouldCompact && !reactivePending && hasPendingNativeTokenCounts()) {
95
+ withSyncTokenCounts(() => {
96
+ messageTokensEst = estimateMessagesTokensSafe(messages);
97
+ pressureTokens = compactionTelemetryPressureTokens(messageTokensEst, compactPolicy, {
98
+ reactivePending,
99
+ messages,
100
+ sessionRef,
101
+ });
102
+ });
103
+ shouldCompact = shouldCompactForSession(messageTokensEst, compactPolicy, {
104
+ forceReactive: reactivePending,
105
+ messages,
106
+ sessionRef,
107
+ pressureTokens,
108
+ });
109
+ }
110
+ // This is the exact canonical value used by the decision above.
111
+ // Reactive overflow recovery floors it at the trigger so the gauge,
112
+ // telemetry, and forced compact still describe the same event.
113
+ const gaugeBeforeTokens = pressureTokens;
89
114
  // Gauge sync (user: 컴팩트될 때 컨텍스트 표기량이 달랐다). The host
90
115
  // refreshes its context readout on provider usage deltas and turn
91
116
  // end, so a tool batch that pushed the transcript over the trigger
@@ -482,7 +507,7 @@ export async function runPreSendCompactPass(state) {
482
507
  afterTokens,
483
508
  messageTokensEst,
484
509
  pressureTokens,
485
- compactChanged,
510
+ compactChanged: compactChanged || summaryChanged,
486
511
  semanticCompact: semanticCompactResult?.semantic === true,
487
512
  semanticError: semanticCompactError?.message || null,
488
513
  recallFastTrack: recallFastTrackResult?.recallFastTrack === true,
@@ -491,6 +516,14 @@ export async function runPreSendCompactPass(state) {
491
516
  pruneCount,
492
517
  durationMs: compactDurationMs,
493
518
  });
519
+ if (compactChanged || summaryChanged || pruneCount > 0) {
520
+ recordContextUsageSnapshot(sessionRef, compactPolicy, {
521
+ messages,
522
+ usedTokens: afterTokens,
523
+ messageTokensEst: afterMessageTokensEst,
524
+ source: 'post_compact',
525
+ });
526
+ }
494
527
  let afterBytes = null;
495
528
  try { afterBytes = Buffer.byteLength(JSON.stringify(messages), 'utf8'); } catch { afterBytes = null; }
496
529
  traceAgentCompact({
@@ -15,13 +15,63 @@ function digest(value) {
15
15
  return createHash('sha256').update(encoded).digest('hex');
16
16
  }
17
17
 
18
- function snapshot(messages, requestPrefix) {
18
+ function cacheRelevantRequestPrefix(requestPrefix, provider) {
19
+ const prefix = requestPrefix && typeof requestPrefix === 'object'
20
+ ? requestPrefix
21
+ : {};
22
+ const anthropic = /^(?:anthropic|anthropic-oauth)$/i.test(String(provider || ''));
23
+ const cacheRelevantTools = (tools) => (
24
+ Array.isArray(tools)
25
+ ? tools.filter((tool) => !anthropic
26
+ || (tool?.deferLoading !== true && tool?.defer_loading !== true))
27
+ : []
28
+ );
29
+ return {
30
+ ...prefix,
31
+ tools: cacheRelevantTools(prefix.tools),
32
+ nativeTools: cacheRelevantTools(prefix.nativeTools),
33
+ };
34
+ }
35
+
36
+ function snapshot(messages, requestPrefix, options = {}) {
37
+ const provider = String(options.provider || '');
38
+ const model = String(options.model || '');
39
+ const relevantPrefix = cacheRelevantRequestPrefix(requestPrefix, provider);
19
40
  return {
20
41
  messageHashes: messages.map(digest),
21
- requestPrefixHash: digest(requestPrefix),
42
+ requestPrefixHash: digest({ provider, model, requestPrefix: relevantPrefix }),
43
+ provider,
44
+ model,
45
+ toolSchemaHash: digest(relevantPrefix.tools),
46
+ nativeToolSchemaHash: digest(relevantPrefix.nativeTools),
22
47
  };
23
48
  }
24
49
 
50
+ function firstChangedMessageIndex(previousHashes, nextHashes) {
51
+ const limit = Math.min(previousHashes.length, nextHashes.length);
52
+ for (let index = 0; index < limit; index += 1) {
53
+ if (previousHashes[index] !== nextHashes[index]) return index;
54
+ }
55
+ return nextHashes.length < previousHashes.length ? nextHashes.length : null;
56
+ }
57
+
58
+ function notifyCacheBreak(options, details) {
59
+ try { options.onCacheBreak?.(details); } catch { /* observability only */ }
60
+ }
61
+
62
+ function requestPrefixChangeReason(previous, next) {
63
+ if (previous.provider !== undefined && previous.provider !== next.provider) return 'provider_changed';
64
+ if (previous.model !== undefined && previous.model !== next.model) return 'model_changed';
65
+ if (previous.toolSchemaHash !== undefined && previous.toolSchemaHash !== next.toolSchemaHash) {
66
+ return 'tool_schema_changed';
67
+ }
68
+ if (previous.nativeToolSchemaHash !== undefined
69
+ && previous.nativeToolSchemaHash !== next.nativeToolSchemaHash) {
70
+ return 'native_tool_schema_changed';
71
+ }
72
+ return 'request_properties_changed';
73
+ }
74
+
25
75
  export class ProviderPrefixMutationError extends Error {
26
76
  constructor(message, details = {}) {
27
77
  super(message);
@@ -36,24 +86,91 @@ export function isCompactionPrefixReset(intent) {
36
86
  }
37
87
 
38
88
  export function prepareProviderPrefixGuard(previous, messages, requestPrefix, options = {}) {
39
- const next = snapshot(Array.isArray(messages) ? messages : [], requestPrefix);
40
- if (!previous || isCompactionPrefixReset(options.cacheBreakIntent)) return next;
89
+ const next = snapshot(Array.isArray(messages) ? messages : [], requestPrefix, options);
90
+ if (!previous) return next;
91
+
92
+ const previousHashes = Array.isArray(previous.messageHashes) ? previous.messageHashes : [];
93
+ const nextHashes = next.messageHashes;
94
+ const changedIndex = firstChangedMessageIndex(previousHashes, nextHashes);
95
+ const requestPrefixChanged = previous.requestPrefixHash !== next.requestPrefixHash;
96
+ if (isCompactionPrefixReset(options.cacheBreakIntent)) {
97
+ if (changedIndex !== null || requestPrefixChanged) {
98
+ notifyCacheBreak(options, {
99
+ classification: 'intentional',
100
+ reason: String(options.cacheBreakIntent),
101
+ source: 'compaction',
102
+ provider: options.provider || null,
103
+ model: options.model || null,
104
+ index: changedIndex,
105
+ previousCount: previousHashes.length,
106
+ nextCount: nextHashes.length,
107
+ previousHash: changedIndex === null ? null : previousHashes[changedIndex],
108
+ nextHash: changedIndex === null ? null : nextHashes[changedIndex],
109
+ previousRequestPrefixHash: previous.requestPrefixHash,
110
+ nextRequestPrefixHash: next.requestPrefixHash,
111
+ requestPrefixChanged,
112
+ });
113
+ }
114
+ return next;
115
+ }
41
116
 
42
- if (next.messageHashes.length < previous.messageHashes.length) {
43
- throw new ProviderPrefixMutationError('provider message history shrank outside compaction', {
117
+ if (nextHashes.length < previousHashes.length) {
118
+ const details = {
119
+ classification: 'unexpected',
120
+ reason: 'history_shrink',
44
121
  provider: options.provider || null,
122
+ model: options.model || null,
45
123
  kind: 'history_shrink',
46
- });
124
+ source: options.mutationSource || null,
125
+ index: nextHashes.length,
126
+ previousCount: previousHashes.length,
127
+ nextCount: nextHashes.length,
128
+ previousHash: previousHashes[nextHashes.length] || null,
129
+ nextHash: null,
130
+ previousRequestPrefixHash: previous.requestPrefixHash,
131
+ nextRequestPrefixHash: next.requestPrefixHash,
132
+ requestPrefixChanged,
133
+ };
134
+ notifyCacheBreak(options, details);
135
+ throw new ProviderPrefixMutationError('provider message history shrank outside compaction', details);
47
136
  }
48
- for (let index = 0; index < previous.messageHashes.length; index += 1) {
49
- if (previous.messageHashes[index] === next.messageHashes[index]) continue;
50
- throw new ProviderPrefixMutationError('provider message prefix changed outside compaction', {
137
+ for (let index = 0; index < previousHashes.length; index += 1) {
138
+ if (previousHashes[index] === nextHashes[index]) continue;
139
+ const details = {
140
+ classification: 'unexpected',
141
+ reason: 'message_prefix',
51
142
  provider: options.provider || null,
143
+ model: options.model || null,
52
144
  kind: 'message_prefix',
145
+ source: options.mutationSource || null,
53
146
  index,
54
- });
147
+ previousCount: previousHashes.length,
148
+ nextCount: nextHashes.length,
149
+ previousHash: previousHashes[index],
150
+ nextHash: nextHashes[index],
151
+ previousRequestPrefixHash: previous.requestPrefixHash,
152
+ nextRequestPrefixHash: next.requestPrefixHash,
153
+ requestPrefixChanged,
154
+ };
155
+ notifyCacheBreak(options, details);
156
+ throw new ProviderPrefixMutationError('provider message prefix changed outside compaction', details);
55
157
  }
56
- if (previous.requestPrefixHash !== next.requestPrefixHash) {
158
+ if (requestPrefixChanged) {
159
+ notifyCacheBreak(options, {
160
+ classification: 'intentional',
161
+ reason: requestPrefixChangeReason(previous, next),
162
+ source: 'request_configuration',
163
+ provider: options.provider || null,
164
+ model: options.model || null,
165
+ index: null,
166
+ previousCount: previousHashes.length,
167
+ nextCount: nextHashes.length,
168
+ previousHash: null,
169
+ nextHash: null,
170
+ previousRequestPrefixHash: previous.requestPrefixHash,
171
+ nextRequestPrefixHash: next.requestPrefixHash,
172
+ requestPrefixChanged: true,
173
+ });
57
174
  // Tool schemas are request metadata, not durable conversation state.
58
175
  // App updates may change them between turns, so rebaseline the provider
59
176
  // cache prefix after the transcript itself has passed integrity checks.
@@ -20,7 +20,7 @@ import {
20
20
  persistenceMessagesForConfirmedImageRejection,
21
21
  promptHasInlineImages,
22
22
  shouldStripImagesForRetry,
23
- stripInlineImages,
23
+ stripInlineImagesFromLatestTurn,
24
24
  } from './image-strip-recovery.mjs';
25
25
  import { setTimeout as sleepMs } from 'timers/promises';
26
26
  import { readStreamOutcome } from '../providers/lib/stream-outcome.mjs';
@@ -508,7 +508,7 @@ export async function sendWithRecovery(ctx) {
508
508
  && relayWitness.toolCallsDispatched === 0
509
509
  && (outcome.replaySafe === true || await retractExposedTextForReplay())
510
510
  ) {
511
- const stripped = stripInlineImages(recoveryMessages);
511
+ const stripped = stripInlineImagesFromLatestTurn(recoveryMessages);
512
512
  if (stripped.stripped > 0) {
513
513
  try {
514
514
  process.stderr.write(
@@ -34,6 +34,7 @@ import {
34
34
  import { _saveAsyncQueued, _saveWorkerPending } from './save-worker.mjs';
35
35
  import { STORED_SESSION_UNREADABLE, _ensureLifecycleFields, _storedSessionFromFile } from './serialize.mjs';
36
36
  import { _savePending, deleteSession, markSessionClosed } from '../store.mjs';
37
+ import { isOrdinarySession } from '../store-summary-visibility.mjs';
37
38
 
38
39
  // Disk mtime of the summary index when the in-memory cache was last refreshed
39
40
  // from it — the cross-process staleness detector.
@@ -98,9 +99,15 @@ export function listStoredSessions(options = {}) {
98
99
  if (/^[A-Za-z0-9_-]+$/.test(storageId)) invalidStorageIds.add(storageId);
99
100
  }
100
101
  const stored = [...sessionsById.values()];
101
- return options.includeLive === true
102
+ const listed = options.includeLive === true
102
103
  ? _withUnpersistedSessions(stored, invalidStorageIds)
103
104
  : stored.sort((a, b) => b.updatedAt - a.updatedAt);
105
+ // This full-record API is the ordinary catalog boundary. Agent discovery
106
+ // uses the durable summary/worker indexes instead; callers that explicitly
107
+ // own an Agent surface can request the unfiltered internal rows.
108
+ return options.includeAgentOnly === true
109
+ ? listed
110
+ : listed.filter(isOrdinarySession);
104
111
  }
105
112
 
106
113
  function _withUnpersistedSessions(stored, invalidStorageIds = new Set()) {
@@ -139,11 +139,20 @@ let _faultSyncedKey = null;
139
139
  // optimistic summary row is published: foreign/ambiguous records must never be
140
140
  // cached as owned, not even transiently.
141
141
  let _writeAuthorityCheck = null;
142
+ let _liveSessionPublisher = null;
142
143
 
143
144
  export function _setSessionWriteAuthorityCheck(check) {
144
145
  _writeAuthorityCheck = typeof check === 'function' ? check : null;
145
146
  }
146
147
 
148
+ export function _setLiveSessionPublisher(publish) {
149
+ _liveSessionPublisher = typeof publish === 'function' ? publish : null;
150
+ }
151
+
152
+ function _publishAdmittedLiveSession(session) {
153
+ try { _liveSessionPublisher?.(session); } catch { /* observers never affect persistence */ }
154
+ }
155
+
147
156
  function _refuseUnownedWrite(id) {
148
157
  if (!_writeAuthorityCheck || !id) return null;
149
158
  let refusal = null;
@@ -528,6 +537,7 @@ export function saveSessionAsync(session, opts, options = {}) {
528
537
  // Pin the live snapshot BEFORE anything can fail: every failure below
529
538
  // leaves this process's memory as the only copy of the newest turn.
530
539
  setLiveSession(session);
540
+ if (options.publishLive !== false) _publishAdmittedLiveSession(session);
531
541
  summaryVersion = _cacheSessionSummary(session);
532
542
  // The guard carries this snapshot's epoch across the thread boundary,
533
543
  // so the worker's own commit is fenced against a newer landed write.
@@ -601,6 +611,7 @@ export function saveSessionAsyncDeferred(session, opts) {
601
611
  try {
602
612
  _ensureLifecycleFields(session);
603
613
  setLiveSession(session);
614
+ _publishAdmittedLiveSession(session);
604
615
  _cacheSessionSummary(session);
605
616
  } catch (err) {
606
617
  _failWorkerWrite(session?.id, null, [], err, epoch, null, err?.attemptSnapshot ?? null);
@@ -625,7 +636,10 @@ export function saveSessionAsyncDeferred(session, opts) {
625
636
  // The promoted save takes its own stamp.
626
637
  _releaseSessionIncarnation(pending.incarnation);
627
638
  try {
628
- saveSessionAsync(pending.session, pending.opts, { epoch: pending.epoch }).then(resolve, reject);
639
+ saveSessionAsync(pending.session, pending.opts, {
640
+ epoch: pending.epoch,
641
+ publishLive: false,
642
+ }).then(resolve, reject);
629
643
  } catch (err) {
630
644
  _failWorkerWrite(session?.id, null, [], err, epoch, null, err?.attemptSnapshot ?? null);
631
645
  reject(err);