mixdog 0.9.63 → 0.9.65

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 (408) hide show
  1. package/README.md +3 -0
  2. package/package.json +9 -2
  3. package/scripts/code-graph-description-contract.mjs +3 -3
  4. package/scripts/verify-release-assets.mjs +2 -2
  5. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +11 -14
  6. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -6
  7. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -2
  8. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +1 -0
  9. package/src/runtime/agent/orchestrator/config.mjs +19 -124
  10. package/src/runtime/agent/orchestrator/context/collect.mjs +6 -6
  11. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +2 -2
  12. package/src/runtime/agent/orchestrator/internal-agents.mjs +2 -2
  13. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +1 -1
  14. package/src/runtime/agent/orchestrator/mcp/client.mjs +4 -4
  15. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +2 -2
  16. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +4 -4
  17. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +2 -2
  18. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +4 -4
  19. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +333 -0
  20. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +2 -2
  21. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -4
  22. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
  23. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -277
  24. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +2 -2
  25. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +1 -1
  26. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +2 -2
  27. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -6
  28. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +196 -0
  29. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +460 -0
  30. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +14 -593
  31. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +1 -1
  32. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +1 -1
  33. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
  34. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +4 -4
  35. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2 -2
  36. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -280
  37. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +357 -0
  38. package/src/runtime/agent/orchestrator/providers/openai-ws-headers.mjs +216 -0
  39. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +2 -188
  40. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  41. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/registry.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -2
  46. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +3 -3
  49. package/src/runtime/agent/orchestrator/session/compact/budget.mjs +2 -2
  50. package/src/runtime/agent/orchestrator/session/compact/file-reattach.mjs +2 -2
  51. package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +2 -2
  54. package/src/runtime/agent/orchestrator/session/context-utils.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +17 -0
  57. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +18 -26
  59. package/src/runtime/agent/orchestrator/session/loop/termination.mjs +1 -1
  60. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +3 -3
  61. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -0
  62. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +1 -1
  64. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -1
  65. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +49 -0
  66. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +49 -0
  67. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +260 -0
  68. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +72 -0
  69. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +163 -0
  70. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +1 -1
  72. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +4 -1
  73. package/src/runtime/agent/orchestrator/session/store.mjs +9 -561
  74. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +1 -1
  75. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  76. package/src/runtime/agent/orchestrator/stall-policy.mjs +11 -11
  77. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
  79. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  81. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -154
  83. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +1 -1
  84. package/src/runtime/agent/orchestrator/tools/builtin/cwd-utils.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +1 -1
  86. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/path-locks.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -2
  91. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +2 -3
  92. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +4 -4
  93. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +1 -1
  95. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +2 -2
  96. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -5
  97. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +2 -2
  99. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +2 -2
  100. package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +519 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +5 -449
  102. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +1 -1
  103. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +212 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin.mjs +1 -1
  105. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +1 -7
  106. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +9 -36
  107. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +15 -3
  108. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +429 -0
  109. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -395
  110. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +2 -2
  111. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +1 -1
  112. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +1 -1
  113. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  114. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +3 -3
  115. package/src/runtime/agent/orchestrator/tools/mutation-planner.mjs +5 -5
  116. package/src/runtime/agent/orchestrator/tools/next-call-utils.mjs +1 -1
  117. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +7 -7
  119. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +72 -9
  120. package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +4 -4
  121. package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  123. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +4 -4
  124. package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
  125. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +2 -444
  127. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +505 -0
  128. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +2 -2
  129. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +1 -1
  130. package/src/runtime/channels/lib/backend-dispatch.mjs +20 -5
  131. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  132. package/src/runtime/channels/lib/telegram-format.mjs +1 -1
  133. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +4 -4
  134. package/src/runtime/channels/tool-defs.mjs +2 -3
  135. package/src/runtime/memory/index.mjs +1 -5
  136. package/src/runtime/memory/lib/core-memory-candidates.mjs +357 -0
  137. package/src/runtime/memory/lib/core-memory-store.mjs +7 -357
  138. package/src/runtime/memory/lib/cycle-signatures.mjs +1 -1
  139. package/src/runtime/memory/lib/embedding-model-config.mjs +1 -1
  140. package/src/runtime/memory/lib/embedding-provider.mjs +1 -5
  141. package/src/runtime/memory/lib/embedding-worker.mjs +1 -5
  142. package/src/runtime/memory/lib/ko-morph.mjs +2 -2
  143. package/src/runtime/memory/lib/memory-config-flags.mjs +2 -5
  144. package/src/runtime/memory/lib/memory-cycle-requests.mjs +3 -7
  145. package/src/runtime/memory/lib/memory-cycle1.mjs +4 -8
  146. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -1
  147. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -1
  148. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +2 -5
  149. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -5
  150. package/src/runtime/memory/lib/memory-embed.mjs +2 -6
  151. package/src/runtime/memory/lib/memory-log.mjs +8 -0
  152. package/src/runtime/memory/lib/memory-ops-policy.mjs +4 -8
  153. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +1 -1
  154. package/src/runtime/memory/lib/memory-recall-store.mjs +2 -193
  155. package/src/runtime/memory/lib/memory-score.mjs +1 -1
  156. package/src/runtime/memory/lib/memory-text-utils.mjs +2 -2
  157. package/src/runtime/memory/lib/memory.mjs +2 -6
  158. package/src/runtime/memory/lib/pg/adapter.mjs +2 -6
  159. package/src/runtime/memory/lib/pg/process.mjs +1 -5
  160. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -5
  161. package/src/runtime/memory/lib/recall-format.mjs +2 -2
  162. package/src/runtime/memory/lib/recall-scoring.mjs +195 -0
  163. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -5
  164. package/src/runtime/memory/lib/trace-store.mjs +2 -6
  165. package/src/runtime/search/lib/http-fetch.mjs +6 -6
  166. package/src/runtime/search/lib/web-tools.mjs +2 -2
  167. package/src/runtime/shared/buffered-appender.mjs +1 -1
  168. package/src/runtime/shared/child-guardian.mjs +1 -1
  169. package/src/runtime/shared/child-spawn-gate.mjs +1 -1
  170. package/src/runtime/shared/config.mjs +4 -4
  171. package/src/runtime/shared/launcher-control.mjs +9 -10
  172. package/src/runtime/shared/llm/index.mjs +5 -20
  173. package/src/runtime/shared/markdown-frontmatter.mjs +1 -1
  174. package/src/runtime/shared/memory-snapshot.mjs +4 -4
  175. package/src/runtime/shared/pristine-execution.mjs +1 -1
  176. package/src/runtime/shared/process-lifecycle.mjs +0 -1
  177. package/src/runtime/shared/process-shutdown.mjs +1 -1
  178. package/src/runtime/shared/resource-admission.mjs +2 -2
  179. package/src/runtime/shared/schedule-session-run.mjs +1 -1
  180. package/src/runtime/shared/schedules-db.mjs +1 -1
  181. package/src/runtime/shared/service-discovery.mjs +2 -2
  182. package/src/runtime/shared/staged-update.mjs +6 -6
  183. package/src/runtime/shared/tool-execution-contract.mjs +1 -1
  184. package/src/runtime/shared/tool-primitives.mjs +1 -1
  185. package/src/runtime/shared/tool-surface.mjs +2 -2
  186. package/src/runtime/shared/turn-snapshot.mjs +184 -0
  187. package/src/runtime/shared/update-checker.mjs +1 -1
  188. package/src/runtime/shared/user-cwd.mjs +3 -3
  189. package/src/runtime/shared/user-data-guard.mjs +4 -4
  190. package/src/runtime/shared/workspace-router.mjs +5 -5
  191. package/src/session-runtime/config-helpers.mjs +2 -3
  192. package/src/session-runtime/config-lifecycle.mjs +1 -1
  193. package/src/session-runtime/hitch-profile.mjs +1 -1
  194. package/src/session-runtime/lifecycle-api.mjs +6 -4
  195. package/src/session-runtime/model-recency.mjs +5 -5
  196. package/src/session-runtime/plugin-mcp.mjs +1 -1
  197. package/src/session-runtime/provider-request-snapshot.mjs +338 -0
  198. package/src/session-runtime/runtime-core.mjs +4 -0
  199. package/src/session-runtime/session-text.mjs +4 -4
  200. package/src/session-runtime/session-turn-api.mjs +7 -0
  201. package/src/session-runtime/tool-catalog-schema.mjs +197 -0
  202. package/src/session-runtime/tool-catalog.mjs +6 -483
  203. package/src/session-runtime/tool-defs.mjs +1 -1
  204. package/src/standalone/agent-task-status.mjs +1 -1
  205. package/src/standalone/agent-tool/helpers.mjs +2 -2
  206. package/src/standalone/channel-admin.mjs +4 -4
  207. package/src/standalone/explore-tool.mjs +3 -3
  208. package/src/standalone/hook-bus/config.mjs +1 -1
  209. package/src/standalone/hook-bus/constants.mjs +1 -1
  210. package/src/standalone/hook-bus/rules.mjs +1 -1
  211. package/src/standalone/projects.mjs +1 -1
  212. package/src/standalone/provider-admin.mjs +2 -2
  213. package/src/tui/App.jsx +1 -1
  214. package/src/tui/app/app-format.mjs +2 -2
  215. package/src/tui/app/clipboard.mjs +3 -3
  216. package/src/tui/app/input-parsers.mjs +1 -1
  217. package/src/tui/app/text-layout.mjs +1 -1
  218. package/src/tui/app/transcript-row-estimate.mjs +360 -0
  219. package/src/tui/app/transcript-window.mjs +5 -347
  220. package/src/tui/app/use-mouse-input.mjs +1 -1
  221. package/src/tui/app/use-prompt-handlers.mjs +1 -1
  222. package/src/tui/components/tool-execution/surface-detail.mjs +9 -9
  223. package/src/tui/components/tool-execution/text-format.mjs +2 -2
  224. package/src/tui/components/tool-output-format.mjs +7 -7
  225. package/src/tui/display-width.mjs +4 -4
  226. package/src/tui/dist/index.mjs +187 -99
  227. package/src/tui/engine/agent-envelope.mjs +5 -5
  228. package/src/tui/engine/live-share.mjs +100 -0
  229. package/src/tui/engine/notification-plan.mjs +1 -1
  230. package/src/tui/engine/queue-helpers.mjs +3 -3
  231. package/src/tui/engine/render-timing.mjs +4 -5
  232. package/src/tui/engine/session-api-ext.mjs +6 -3
  233. package/src/tui/engine/tool-result-status.mjs +5 -5
  234. package/src/tui/engine/tool-result-text.mjs +1 -1
  235. package/src/tui/engine/transcript-spill.mjs +585 -0
  236. package/src/tui/engine/tui-steering-persist.mjs +1 -1
  237. package/src/tui/engine.mjs +29 -474
  238. package/src/tui/lib/voice-setup.mjs +1 -1
  239. package/src/tui/markdown/format-token.mjs +1 -1
  240. package/src/tui/markdown/render-ansi.mjs +1 -1
  241. package/src/tui/markdown/table-layout.mjs +4 -4
  242. package/src/tui/paste-attachments.mjs +1 -1
  243. package/src/tui/prompt-history-store.mjs +3 -3
  244. package/src/tui/statusline-ansi-bridge.mjs +2 -2
  245. package/src/tui/theme.mjs +1 -1
  246. package/src/tui/transcript-tool-failures.mjs +2 -2
  247. package/src/ui/ansi.mjs +0 -9
  248. package/src/ui/statusline-format.mjs +2 -4
  249. package/scripts/_bench-cwc.json +0 -20
  250. package/scripts/_jitter-fuzz.jsx +0 -42
  251. package/scripts/_jitter-fuzz2.jsx +0 -30
  252. package/scripts/_jitter-probe.jsx +0 -35
  253. package/scripts/_jp2.jsx +0 -16
  254. package/scripts/_smoke_wd.mjs +0 -7
  255. package/scripts/abort-recovery-test.mjs +0 -175
  256. package/scripts/agent-dispatch-abort-compose-test.mjs +0 -31
  257. package/scripts/agent-live-arg-guard-smoke.mjs +0 -20
  258. package/scripts/agent-live-path-suffix-smoke.mjs +0 -34
  259. package/scripts/agent-live-toolcall-args-smoke.mjs +0 -45
  260. package/scripts/agent-loop-policy-test.mjs +0 -37
  261. package/scripts/agent-model-liveness-test.mjs +0 -763
  262. package/scripts/agent-parallel-smoke.mjs +0 -435
  263. package/scripts/agent-route-batch-test.mjs +0 -40
  264. package/scripts/agent-tag-reuse-smoke.mjs +0 -441
  265. package/scripts/agent-terminal-reap-test.mjs +0 -252
  266. package/scripts/agent-trace-io-test.mjs +0 -133
  267. package/scripts/ansi-color-capability-test.mjs +0 -90
  268. package/scripts/anthropic-admission-retry-integration-test.mjs +0 -119
  269. package/scripts/anthropic-maxtokens-test.mjs +0 -119
  270. package/scripts/anthropic-oauth-refresh-race-test.mjs +0 -397
  271. package/scripts/anthropic-transport-policy-test.mjs +0 -726
  272. package/scripts/apply-patch-edit-smoke.mjs +0 -71
  273. package/scripts/arg-guard-test.mjs +0 -93
  274. package/scripts/async-notify-settlement-test.mjs +0 -99
  275. package/scripts/atomic-lock-tryonce-test.mjs +0 -125
  276. package/scripts/background-task-meta-smoke.mjs +0 -38
  277. package/scripts/bench-run.mjs +0 -508
  278. package/scripts/boot-smoke.mjs +0 -137
  279. package/scripts/build-runtime-windows.ps1 +0 -242
  280. package/scripts/channel-daemon-smoke.mjs +0 -1103
  281. package/scripts/code-graph-aggregate-cwd-test.mjs +0 -158
  282. package/scripts/code-graph-disk-hit-test.mjs +0 -290
  283. package/scripts/code-graph-dispatch-test.mjs +0 -96
  284. package/scripts/code-graph-root-federation-test.mjs +0 -273
  285. package/scripts/compact-active-turn-test.mjs +0 -68
  286. package/scripts/compact-file-reattach-test.mjs +0 -88
  287. package/scripts/compact-pressure-test.mjs +0 -788
  288. package/scripts/compact-prior-context-flatten-test.mjs +0 -252
  289. package/scripts/compact-recall-digest-test.mjs +0 -57
  290. package/scripts/compact-smoke.mjs +0 -1017
  291. package/scripts/compact-trigger-migration-smoke.mjs +0 -261
  292. package/scripts/compacted-placeholder-scrub-test.mjs +0 -63
  293. package/scripts/context-mcp-metering-test.mjs +0 -1406
  294. package/scripts/debounced-skills-async-save-test.mjs +0 -57
  295. package/scripts/deferred-tool-loading-test.mjs +0 -250
  296. package/scripts/desktop-session-bridge-test.mjs +0 -1013
  297. package/scripts/dispatch-persist-recovery-test.mjs +0 -141
  298. package/scripts/embedding-worker-exit-test.mjs +0 -76
  299. package/scripts/execution-completion-dedup-test.mjs +0 -205
  300. package/scripts/execution-pending-resume-kick-test.mjs +0 -151
  301. package/scripts/execution-resume-esc-integration-test.mjs +0 -176
  302. package/scripts/explore-bench-tmp.mjs +0 -36
  303. package/scripts/explore-bench.mjs +0 -248
  304. package/scripts/explore-prompt-policy-test.mjs +0 -256
  305. package/scripts/explore-timeout-cancel-test.mjs +0 -345
  306. package/scripts/find-fuzzy-hidden-test.mjs +0 -267
  307. package/scripts/forwarder-rebind-tail-test.mjs +0 -67
  308. package/scripts/freevar-smoke.mjs +0 -98
  309. package/scripts/gemini-provider-test.mjs +0 -1448
  310. package/scripts/generate-oc-icons.mjs +0 -11
  311. package/scripts/grok-oauth-refresh-race-test.mjs +0 -273
  312. package/scripts/headless-pristine-execution-test.mjs +0 -614
  313. package/scripts/hook-bus-test.mjs +0 -552
  314. package/scripts/ingest-pure-conversation-smoke.mjs +0 -175
  315. package/scripts/internal-comms-bench-test.mjs +0 -226
  316. package/scripts/internal-comms-bench.mjs +0 -853
  317. package/scripts/internal-comms-smoke.mjs +0 -277
  318. package/scripts/internal-tools-normalization-test.mjs +0 -62
  319. package/scripts/interrupted-turn-history-test.mjs +0 -399
  320. package/scripts/lead-workflow-smoke.mjs +0 -592
  321. package/scripts/lifecycle-api-test.mjs +0 -137
  322. package/scripts/live-share-test.mjs +0 -148
  323. package/scripts/live-worker-smoke.mjs +0 -333
  324. package/scripts/log-writer-guard-smoke.mjs +0 -131
  325. package/scripts/maintenance-default-routes-test.mjs +0 -164
  326. package/scripts/max-output-recovery-persist-test.mjs +0 -81
  327. package/scripts/max-output-recovery-test.mjs +0 -368
  328. package/scripts/mcp-client-normalization-test.mjs +0 -45
  329. package/scripts/mcp-grace-deferred-test.mjs +0 -225
  330. package/scripts/memory-core-input-test.mjs +0 -167
  331. package/scripts/memory-cycle-routing-test.mjs +0 -111
  332. package/scripts/memory-meta-concurrency-test.mjs +0 -20
  333. package/scripts/memory-pg-recovery-test.mjs +0 -59
  334. package/scripts/memory-rule-contract-test.mjs +0 -93
  335. package/scripts/model-list-sanitize-test.mjs +0 -37
  336. package/scripts/mouse-tracking-restore-smoke.mjs +0 -107
  337. package/scripts/native-edit-wire-test.mjs +0 -152
  338. package/scripts/notify-completion-mirror-test.mjs +0 -73
  339. package/scripts/openai-oauth-refresh-race-test.mjs +0 -120
  340. package/scripts/openai-oauth-ws-1006-retry-test.mjs +0 -954
  341. package/scripts/openai-ws-early-settle-test.mjs +0 -216
  342. package/scripts/output-style-bench.mjs +0 -292
  343. package/scripts/output-style-smoke.mjs +0 -145
  344. package/scripts/parent-abort-link-test.mjs +0 -90
  345. package/scripts/patch-binary-cache-test.mjs +0 -181
  346. package/scripts/path-suffix-test.mjs +0 -57
  347. package/scripts/pending-completion-drop-test.mjs +0 -239
  348. package/scripts/pending-messages-lock-nonblocking-test.mjs +0 -62
  349. package/scripts/pretool-ask-runtime-test.mjs +0 -54
  350. package/scripts/process-lifecycle-test.mjs +0 -461
  351. package/scripts/prompt-immediate-render-test.mjs +0 -89
  352. package/scripts/prompt-input-parity-test.mjs +0 -145
  353. package/scripts/provider-admission-scheduler-test.mjs +0 -681
  354. package/scripts/provider-contract-test.mjs +0 -632
  355. package/scripts/provider-stream-stall-test.mjs +0 -276
  356. package/scripts/provider-toolcall-test.mjs +0 -3063
  357. package/scripts/reactive-compact-persist-smoke.mjs +0 -203
  358. package/scripts/recall-bench.mjs +0 -359
  359. package/scripts/resource-admission-test.mjs +0 -792
  360. package/scripts/result-classification-test.mjs +0 -75
  361. package/scripts/rg-runner-test.mjs +0 -240
  362. package/scripts/routing-corpus-test.mjs +0 -349
  363. package/scripts/sanitize-tool-pairs-test.mjs +0 -260
  364. package/scripts/session-bench-cache-break-test.mjs +0 -102
  365. package/scripts/session-bench.mjs +0 -1708
  366. package/scripts/session-context-bench.mjs +0 -344
  367. package/scripts/session-heartbeat-lifecycle-test.mjs +0 -68
  368. package/scripts/session-ingest-compaction-smoke.mjs +0 -279
  369. package/scripts/session-ingest-smoke.mjs +0 -177
  370. package/scripts/session-orphan-sweep-test.mjs +0 -109
  371. package/scripts/set-effort-config-test.mjs +0 -41
  372. package/scripts/shell-failure-diagnostics-test.mjs +0 -280
  373. package/scripts/shell-hardening-test.mjs +0 -209
  374. package/scripts/shell-jobs-windows-hide-test.mjs +0 -164
  375. package/scripts/ship-mode-test.mjs +0 -98
  376. package/scripts/smoke-loop-failure-summary-test.mjs +0 -38
  377. package/scripts/smoke-loop-failure-summary.mjs +0 -16
  378. package/scripts/smoke-loop-report.mjs +0 -221
  379. package/scripts/smoke-loop.mjs +0 -202
  380. package/scripts/smoke-runtime-negative.ps1 +0 -106
  381. package/scripts/statusline-quota-hysteresis-test.mjs +0 -62
  382. package/scripts/steering-drain-buckets-test.mjs +0 -467
  383. package/scripts/steering-fold-provenance-test.mjs +0 -71
  384. package/scripts/streaming-tail-window-test.mjs +0 -175
  385. package/scripts/submit-commandbusy-race-test.mjs +0 -257
  386. package/scripts/task-bench.mjs +0 -205
  387. package/scripts/terminal-bench-isolation-guards-test.mjs +0 -102
  388. package/scripts/termio-input-smoke.mjs +0 -199
  389. package/scripts/tool-result-hook-test.mjs +0 -48
  390. package/scripts/tool-smoke.mjs +0 -2453
  391. package/scripts/tool-tui-presentation-test.mjs +0 -286
  392. package/scripts/toolcall-args-test.mjs +0 -239
  393. package/scripts/tui-ambiguous-width-test.mjs +0 -113
  394. package/scripts/tui-background-failure-smoke.mjs +0 -73
  395. package/scripts/tui-perf-run.ps1 +0 -26
  396. package/scripts/tui-render-smoke.mjs +0 -90
  397. package/scripts/tui-runtime-load-bench-entry.jsx +0 -608
  398. package/scripts/tui-runtime-load-bench.mjs +0 -50
  399. package/scripts/tui-store-frame-batch-test.mjs +0 -99
  400. package/scripts/tui-transcript-jitter-harness-entry.jsx +0 -383
  401. package/scripts/tui-transcript-perf-test.mjs +0 -934
  402. package/scripts/usage-metrics-epoch-smoke.mjs +0 -114
  403. package/scripts/verify-release-assets-test.mjs +0 -281
  404. package/scripts/web-fetch-routing-test.mjs +0 -158
  405. package/scripts/webhook-smoke.mjs +0 -201
  406. package/scripts/windows-hide-spawn-options-test.mjs +0 -19
  407. package/scripts/worker-notify-rejection-test.mjs +0 -51
  408. package/scripts/write-backpressure-test.mjs +0 -147
@@ -10,7 +10,7 @@ import {
10
10
  } from '../../providers/statusline-route-meta.mjs';
11
11
  import { estimateTranscriptContextUsage } from '../context-utils.mjs';
12
12
 
13
- export function standaloneStatusRouteInfo(session) {
13
+ function standaloneStatusRouteInfo(session) {
14
14
  if (!session) return null;
15
15
  // autoCompactTokenLimit is an EXPLICIT sub-boundary auto-compact limit only.
16
16
  // Do NOT fall back to compactBoundaryTokens/contextWindow here — that
@@ -0,0 +1,49 @@
1
+ import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
2
+
3
+ export const _lastSaveError = new Map(); // id -> { message, at }
4
+
5
+ /** Same-process authoritative session snapshots (createSession → loadSession / askSession). */
6
+ export const _liveSessions = new Map();
7
+
8
+ // Session ids whose most recent save attempt was DROPPED by the ownership
9
+ // guard (_shouldDrop: disk generation moved past the caller's expected
10
+ // generation) or failed outright. For these ids the local live snapshot is
11
+ // AHEAD of disk — the on-disk transcript froze at the last landed save — so
12
+ // it must not be evicted (data loss) nor shadowed by the stale disk copy.
13
+ export const _droppedSaveIds = new Set();
14
+
15
+ export function setLiveSession(session) {
16
+ if (!session?.id) return;
17
+ _liveSessions.set(session.id, session);
18
+ }
19
+
20
+ export function _clearLiveSession(id) {
21
+ if (id) _liveSessions.delete(id);
22
+ }
23
+
24
+ // Live snapshots that still carry raw media bytes (images are placeholder'd
25
+ // in the persisted JSON) stay resident for this long after their last use so
26
+ // multi-turn image recognition keeps working across an idle gap. Beyond the
27
+ // TTL the memory cost wins and the snapshot is reclaimed like any other.
28
+ export const LIVE_MEDIA_RETENTION_MS = 60 * 60 * 1000; // 1h
29
+
30
+ export function _messagesCarryLiveMedia(messages) {
31
+ if (!Array.isArray(messages)) return false;
32
+ for (const m of messages) {
33
+ if (!m || typeof m !== 'object') continue;
34
+ if (sanitizeContentForStoredHistory(m.content) !== m.content) return true;
35
+ }
36
+ return false;
37
+ }
38
+
39
+ /**
40
+ * Returns the last save error for a session id, or null if no error has occurred.
41
+ * Shape: { message: string, at: number } | null
42
+ */
43
+ export function getSessionSaveError(id) {
44
+ return _lastSaveError.get(id) ?? null;
45
+ }
46
+
47
+ export function clearSessionSaveError(id) {
48
+ _lastSaveError.delete(id);
49
+ }
@@ -0,0 +1,49 @@
1
+ import { statSync, existsSync, readFileSync } from 'fs';
2
+ import { getPluginData } from '../../config.mjs';
3
+
4
+ // Recent full-session reads are much hotter than writes while a user hops
5
+ // between conversations. Verify the file identity on every access, but reuse
6
+ // the parsed object while the atomic file has not changed.
7
+ const SESSION_LOAD_CACHE_LIMIT = 8;
8
+ const _sessionLoadCache = new Map(); // path → { signature, session }
9
+ let _sessionLoadCacheDataDir = null;
10
+
11
+ export function _readStoredSessionCached(id, path) {
12
+ const dataDir = getPluginData();
13
+ if (_sessionLoadCacheDataDir !== dataDir) {
14
+ _sessionLoadCacheDataDir = dataDir;
15
+ _sessionLoadCache.clear();
16
+ }
17
+ let signature;
18
+ try {
19
+ const info = statSync(path, { bigint: true });
20
+ signature = `${info.dev}:${info.ino}:${info.size}:${info.mtimeNs}`;
21
+ } catch {
22
+ _sessionLoadCache.delete(path);
23
+ return { exists: existsSync(path), session: null };
24
+ }
25
+ const cached = _sessionLoadCache.get(path);
26
+ if (cached?.signature === signature) {
27
+ _sessionLoadCache.delete(path);
28
+ _sessionLoadCache.set(path, cached);
29
+ return { exists: true, session: cached.session };
30
+ }
31
+ try {
32
+ const stored = JSON.parse(readFileSync(path, 'utf-8'));
33
+ if (stored?.id !== id) {
34
+ _sessionLoadCache.delete(path);
35
+ return { exists: true, session: null };
36
+ }
37
+ _sessionLoadCache.delete(path);
38
+ _sessionLoadCache.set(path, { signature, session: stored });
39
+ while (_sessionLoadCache.size > SESSION_LOAD_CACHE_LIMIT) {
40
+ const oldest = _sessionLoadCache.keys().next().value;
41
+ if (oldest === undefined) break;
42
+ _sessionLoadCache.delete(oldest);
43
+ }
44
+ return { exists: true, session: stored };
45
+ } catch {
46
+ _sessionLoadCache.delete(path);
47
+ return { exists: true, session: null };
48
+ }
49
+ }
@@ -0,0 +1,260 @@
1
+ import { Worker } from 'worker_threads';
2
+ import { guardedSaveOptions as _guardedSaveOptions } from './write-guards.mjs';
3
+ import { _ensureLifecycleFields } from './serialize.mjs';
4
+ import { setLiveSession, _droppedSaveIds, clearSessionSaveError } from './live-state.mjs';
5
+ import { _cacheSessionSummary, _rollbackCachedSessionSummary, _queueSessionSummaryUpsert } from './summary-cache.mjs';
6
+
7
+ // ── Worker-thread async save ──────────────────────────────────────────────────
8
+ // Single long-lived Worker serializes all saveSessionAsync calls.
9
+ // The worker's message queue preserves generation-race ordering.
10
+ let _saveWorker = null;
11
+ // In-flight writes, keyed by reqId. Value: { id, session, opts, waiters:[{resolve,reject}] }.
12
+ // At most ONE entry per session id at a time (single-in-flight-per-id).
13
+ export let _saveWorkerPending = new Map();
14
+ // Latest-wins queued payload per session, keyed by id. Value: { session, opts, waiters:[] }.
15
+ // At most ONE queued write per id: a newer saveSessionAsync while a write is in
16
+ // flight overwrites session/opts here and appends its resolver to waiters, so
17
+ // every superseded caller resolves when this single queued write finally lands.
18
+ export let _saveAsyncQueued = new Map();
19
+ // id → reqId of the in-flight write for that id (enforces one-in-flight-per-id).
20
+ export let _saveAsyncInflight = new Map();
21
+ let _saveWorkerReqId = 0;
22
+ let _saveWorkerRefCount = 0;
23
+ let _deferredSaveReqId = 0;
24
+ export const _deferredSessionSaves = new Map();
25
+
26
+ function _getOrSpawnWorker() {
27
+ if (_saveWorker) return _saveWorker;
28
+ _saveWorker = new Worker(new URL('../save-session-worker.mjs', import.meta.url), {
29
+ execArgv: [],
30
+ });
31
+ // Worker logs arrive as `{ __log }` messages, NOT via piped stdio
32
+ // (stdout:true/stderr:true): once the parent starts reading a worker's
33
+ // piped stdio, the underlying MessagePort stays ref'd for the worker's
34
+ // lifetime regardless of worker.unref(), so every process that ever
35
+ // saved a session could no longer exit (test runners hung after
36
+ // completion). The worker overrides its own process.stdout/stderr.write
37
+ // to forward through this channel, which keeps stray prints off the TUI
38
+ // frame (routed through the parent's guardable stderr) without holding
39
+ // the event loop.
40
+ _saveWorker.on('message', ({ __log, ok, saved, error, reqId }) => {
41
+ if (__log !== undefined) {
42
+ try { process.stderr.write(String(__log)); } catch { /* best-effort */ }
43
+ return;
44
+ }
45
+ const p = _saveWorkerPending.get(reqId);
46
+ if (!p) return;
47
+ _saveWorkerPending.delete(reqId);
48
+ // Drop the ref AFTER pending was registered ref-up'd so the worker
49
+ // becomes unref'd again once all in-flight writes settle. _saveWorker
50
+ // null-check covers the error/exit race where the worker died first.
51
+ if (--_saveWorkerRefCount === 0 && _saveWorker) _saveWorker.unref();
52
+ const { id, session, summaryVersion, waiters } = p;
53
+ _saveAsyncInflight.delete(id);
54
+ // Resolve/reject every caller whose payload this write represents
55
+ // (the originating call plus any that coalesced onto it before it was
56
+ // posted). A supersede never lands here as a rejection — only a real
57
+ // worker failure does.
58
+ if (ok) {
59
+ // A close/delete may have completed while the worker was writing.
60
+ // Do not let this older completion put an open row back in the
61
+ // process-local cache after its tombstone/removal.
62
+ if (saved) {
63
+ _queueSessionSummaryUpsert(session, summaryVersion);
64
+ _droppedSaveIds.delete(id);
65
+ } else {
66
+ // The worker's _shouldDrop declined the write: disk ownership
67
+ // moved past this snapshot — flag the split-brain so eviction
68
+ // and disk-over-live arbitration keep the richer local copy.
69
+ _rollbackCachedSessionSummary(id, summaryVersion);
70
+ _droppedSaveIds.add(id);
71
+ }
72
+ clearSessionSaveError(id);
73
+ for (const w of waiters) w.resolve();
74
+ }
75
+ else {
76
+ const e = new Error(`[session-store] worker save failed: ${error}`);
77
+ for (const w of waiters) w.reject(e);
78
+ }
79
+ // Promote the latest-wins queued payload (if any) into the now-free
80
+ // in-flight slot for this id. Runs regardless of ok: the queued write
81
+ // is a newer, independent payload and must still be attempted so its
82
+ // (possibly superseded) waiters resolve when it lands.
83
+ const q = _saveAsyncQueued.get(id);
84
+ if (q) {
85
+ _saveAsyncQueued.delete(id);
86
+ try {
87
+ _postAsyncWrite(id, q.session, q.opts, q.waiters, q.summaryVersion);
88
+ } catch (err) {
89
+ _rollbackCachedSessionSummary(id, q.summaryVersion);
90
+ for (const w of q.waiters) w.reject(err);
91
+ }
92
+ }
93
+ });
94
+ _saveWorker.on('error', (err) => {
95
+ for (const [, p] of _saveWorkerPending) {
96
+ _rollbackCachedSessionSummary(p.id, p.summaryVersion);
97
+ for (const w of p.waiters) w.reject(err);
98
+ }
99
+ _saveWorkerPending.clear();
100
+ for (const [id, q] of _saveAsyncQueued) {
101
+ _rollbackCachedSessionSummary(id, q.summaryVersion);
102
+ for (const w of q.waiters) w.reject(err);
103
+ }
104
+ _saveAsyncQueued.clear();
105
+ _saveAsyncInflight.clear();
106
+ _saveWorkerRefCount = 0;
107
+ _saveWorker = null;
108
+ });
109
+ _saveWorker.on('exit', (code) => {
110
+ // Reject pending resolvers on ANY exit (code 0 included) so an idle
111
+ // worker that races a pending postMessage cannot leak resolvers. The
112
+ // map is empty on the normal idle-exit path so the loop is a no-op,
113
+ // but it remains safe for the race window where exit fires after
114
+ // saveSessionAsync registered a resolver but before the worker
115
+ // received the message.
116
+ const err = new Error(`[session-store] save worker exited with code ${code}`);
117
+ for (const [, p] of _saveWorkerPending) {
118
+ _rollbackCachedSessionSummary(p.id, p.summaryVersion);
119
+ for (const w of p.waiters) w.reject(err);
120
+ }
121
+ _saveWorkerPending.clear();
122
+ for (const [id, q] of _saveAsyncQueued) {
123
+ _rollbackCachedSessionSummary(id, q.summaryVersion);
124
+ for (const w of q.waiters) w.reject(err);
125
+ }
126
+ _saveAsyncQueued.clear();
127
+ _saveAsyncInflight.clear();
128
+ _saveWorkerRefCount = 0;
129
+ _saveWorker = null;
130
+ });
131
+ _saveWorker.unref(); // don't keep process alive
132
+ return _saveWorker;
133
+ }
134
+
135
+ /**
136
+ * Post one in-flight write for `id` to the worker and register it as the
137
+ * single in-flight entry for that id. Callers guarantee no write is already
138
+ * in flight for `id`. Throws (after cleaning its own map entries) if the
139
+ * worker postMessage fails so the caller can reject the affected waiters.
140
+ */
141
+ function _postAsyncWrite(id, session, opts, waiters, summaryVersion) {
142
+ const reqId = ++_saveWorkerReqId;
143
+ _saveWorkerPending.set(reqId, {
144
+ id,
145
+ session,
146
+ opts,
147
+ summaryVersion,
148
+ waiters,
149
+ });
150
+ _saveAsyncInflight.set(id, reqId);
151
+ try {
152
+ const w = _getOrSpawnWorker();
153
+ w.postMessage({ session, opts, reqId });
154
+ // Ref AFTER successful postMessage so a queue/throw failure path does
155
+ // not leave the worker held alive with no pending message. Paired with
156
+ // the unref in the message handler when count hits 0.
157
+ if (++_saveWorkerRefCount === 1) w.ref();
158
+ } catch (err) {
159
+ _saveWorkerPending.delete(reqId);
160
+ _saveAsyncInflight.delete(id);
161
+ throw err;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Async save via a dedicated Worker thread.
167
+ * Errors surface as thrown Errors — callers must not silently swallow them.
168
+ *
169
+ * Per-session latest-wins coalescing: for a given id there is at most one
170
+ * write in flight plus one queued follow-up. N rapid saves for the same id in
171
+ * a turn collapse to (in-flight + one queued-latest), keeping the single
172
+ * worker's backlog bounded. Per-id write ORDERING is preserved (a queued write
173
+ * is only posted once the prior in-flight write for that id settles); different
174
+ * ids interleave freely as before.
175
+ */
176
+ export function saveSessionAsync(session, opts) {
177
+ _ensureLifecycleFields(session);
178
+ setLiveSession(session);
179
+ const id = session.id;
180
+ const summaryVersion = _cacheSessionSummary(session);
181
+ const safeOpts = opts?._sessionWriteGuard ? opts : _guardedSaveOptions(id, opts);
182
+ // The Worker `postMessage` below structured-clones the whole session on the
183
+ // main thread. `session.liveTurnMessages` (live working transcript) and
184
+ // `session.toolApprovalHook` (askOpts.onToolApproval callback) are transient
185
+ // in-flight aliases askSession sets for the turn duration; both carry
186
+ // non-cloneable values (a function, and raw messages that can hold functions),
187
+ // which makes structuredClone throw "could not be cloned" for every mid-turn
188
+ // iteration save. The worker strips both via _sessionForDisk anyway, so drop
189
+ // them from the cloned payload here WITHOUT mutating the live session object.
190
+ const clonePayload = (session && typeof session === 'object'
191
+ && (Object.prototype.hasOwnProperty.call(session, 'liveTurnMessages')
192
+ || Object.prototype.hasOwnProperty.call(session, 'toolApprovalHook')))
193
+ ? (() => { const { liveTurnMessages: _dropLTM, toolApprovalHook: _dropTAH, ...rest } = session; return rest; })()
194
+ : session;
195
+ return new Promise((resolve, reject) => {
196
+ const waiter = { resolve, reject };
197
+ if (_saveAsyncInflight.has(id)) {
198
+ // A write is already on disk for this id — coalesce into the single
199
+ // latest-wins queued slot. Existing queued waiters carry over so a
200
+ // superseded caller resolves when THIS newer write lands (never
201
+ // hang, never reject on supersede).
202
+ const q = _saveAsyncQueued.get(id);
203
+ if (q) {
204
+ q.session = clonePayload;
205
+ q.opts = safeOpts;
206
+ q.summaryVersion = summaryVersion;
207
+ q.waiters.push(waiter);
208
+ } else {
209
+ _saveAsyncQueued.set(id, {
210
+ session: clonePayload,
211
+ opts: safeOpts,
212
+ summaryVersion,
213
+ waiters: [waiter],
214
+ });
215
+ }
216
+ return;
217
+ }
218
+ // Idle for this id — post immediately as the in-flight write. The
219
+ // in-flight entry persists {session, opts} so drainSessionStore can
220
+ // sync-flush outstanding writes if process exit interrupts the queue.
221
+ try {
222
+ _postAsyncWrite(id, clonePayload, safeOpts, [waiter], summaryVersion);
223
+ } catch (err) {
224
+ _rollbackCachedSessionSummary(id, summaryVersion);
225
+ reject(err);
226
+ }
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Register a save for the exit drain now, but yield one check phase before
232
+ * Worker.postMessage performs its main-thread structured clone.
233
+ */
234
+ export function saveSessionAsyncDeferred(session, opts) {
235
+ _ensureLifecycleFields(session);
236
+ setLiveSession(session);
237
+ _cacheSessionSummary(session);
238
+ const reqId = ++_deferredSaveReqId;
239
+ return new Promise((resolve, reject) => {
240
+ _deferredSessionSaves.set(reqId, {
241
+ session,
242
+ opts: _guardedSaveOptions(session.id, opts),
243
+ resolve,
244
+ reject,
245
+ });
246
+ setImmediate(() => {
247
+ const pending = _deferredSessionSaves.get(reqId);
248
+ if (!pending) return;
249
+ _deferredSessionSaves.delete(reqId);
250
+ saveSessionAsync(pending.session, pending.opts).then(resolve, reject);
251
+ });
252
+ });
253
+ }
254
+
255
+ export function _resetSaveWorkerBookkeeping() {
256
+ _saveWorkerPending.clear();
257
+ _saveAsyncQueued.clear();
258
+ _saveAsyncInflight.clear();
259
+ _saveWorkerRefCount = 0;
260
+ }
@@ -0,0 +1,72 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { renameWithRetrySync } from '../../../../shared/atomic-file.mjs';
4
+ import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
5
+
6
+ // The live in-memory session (and every model request)
7
+ // retains attached image bytes across turns so multi-turn recognition works.
8
+ // The persisted session JSON, however, replaces image content with a short
9
+ // text placeholder at serialization time — keeping session files small without
10
+ // starving the model of the image mid-conversation. Returns the same object
11
+ // reference when nothing changed (no-image sessions pay only a shallow scan).
12
+ export function _sessionForDisk(session) {
13
+ // Strip transient in-flight aliases askSession sets for the turn duration:
14
+ // - liveTurnMessages: live working transcript (so contextStatus() can
15
+ // estimate live context growth) — a duplicate of the working transcript
16
+ // that must never be serialized (mid-turn saves would bloat the file and
17
+ // persist a non-canonical message array).
18
+ // - toolApprovalHook: the askOpts.onToolApproval callback wired for the
19
+ // turn — a function that must never be serialized.
20
+ const hasTransient = session && typeof session === 'object'
21
+ && (Object.prototype.hasOwnProperty.call(session, 'liveTurnMessages')
22
+ || Object.prototype.hasOwnProperty.call(session, 'toolApprovalHook'));
23
+ const messages = Array.isArray(session?.messages) ? session.messages : null;
24
+ if (!messages || messages.length === 0) {
25
+ if (!hasTransient) return session;
26
+ const { liveTurnMessages: _dropLTM, toolApprovalHook: _dropTAH, ...rest } = session;
27
+ return rest;
28
+ }
29
+ let changed = false;
30
+ const out = messages.map((m) => {
31
+ if (!m || typeof m !== 'object') return m;
32
+ const content = sanitizeContentForStoredHistory(m.content);
33
+ if (content !== m.content) { changed = true; return { ...m, content }; }
34
+ return m;
35
+ });
36
+ if (!changed) {
37
+ if (!hasTransient) return session;
38
+ const { liveTurnMessages: _dropLTM, toolApprovalHook: _dropTAH, ...rest } = session;
39
+ return rest;
40
+ }
41
+ const { liveTurnMessages: _dropLTM, toolApprovalHook: _dropTAH, ...rest } = session;
42
+ return { ...rest, messages: out };
43
+ }
44
+
45
+ export function _renameWithRetrySync(tmp, target) {
46
+ return renameWithRetrySync(tmp, target);
47
+ }
48
+
49
+ /**
50
+ * Ensure generation/closed defaults on every session object.
51
+ * Older persisted sessions predate these fields; we normalise at load and save.
52
+ */
53
+ export function _ensureLifecycleFields(session) {
54
+ if (typeof session.generation !== 'number') session.generation = 0;
55
+ if (typeof session.closed !== 'boolean') session.closed = false;
56
+ if (!Array.isArray(session.messages)) session.messages = [];
57
+ if (!Array.isArray(session.tools)) session.tools = [];
58
+ return session;
59
+ }
60
+
61
+ export function _storedSessionFromFile(dir, filename, ensureLifecycle = true) {
62
+ if (!filename.endsWith('.json')) return null;
63
+ const storageId = filename.slice(0, -5);
64
+ if (!storageId || !/^[A-Za-z0-9_-]+$/.test(storageId)) return null;
65
+ try {
66
+ const session = JSON.parse(readFileSync(join(dir, filename), 'utf-8'));
67
+ if (session?.id !== storageId) return null;
68
+ return ensureLifecycle ? _ensureLifecycleFields(session) : session;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
@@ -0,0 +1,163 @@
1
+ import { readdirSync, statSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getPluginData } from '../../config.mjs';
4
+ import { getStoreDir } from './paths-heartbeat.mjs';
5
+ import { _storedSessionFromFile } from './serialize.mjs';
6
+ import { _sessionSummary, _normalizeSummaryIndex, _upsertSessionSummary, _removeSessionSummary, _pruneSummaryIndexIds } from '../store-summary-index.mjs';
7
+
8
+ // Listing is much hotter than writing, especially while the desktop session
9
+ // browser is open. Keep the compact sidecar in memory after the first read;
10
+ // local durability mutations update this cache synchronously, while an
11
+ // explicit refresh remains the authoritative cross-process/disk reconciliation
12
+ // path. Pending overlays cover a write that lands before the first listing.
13
+ export let _summaryRowsCache = null;
14
+ const _summaryCacheUpserts = new Map();
15
+ export const _summaryCacheRemovals = new Set();
16
+ export const _summaryCacheVersions = new Map();
17
+ let _summaryCacheDataDir = null;
18
+
19
+ export function _ensureSummaryCacheDataDir() {
20
+ const dataDir = getPluginData();
21
+ if (_summaryCacheDataDir === dataDir) return;
22
+ _summaryCacheDataDir = dataDir;
23
+ _summaryRowsCache = null;
24
+ _summaryScanCache.clear();
25
+ _summaryCacheUpserts.clear();
26
+ _summaryCacheRemovals.clear();
27
+ _summaryCacheVersions.clear();
28
+ }
29
+
30
+ function _summaryRowsWithLocalMutations(rows, { discardLocalMutations = false } = {}) {
31
+ if (discardLocalMutations) {
32
+ _summaryCacheUpserts.clear();
33
+ _summaryCacheRemovals.clear();
34
+ }
35
+ const byId = new Map(_normalizeSummaryIndex({ rows }).rows.map((row) => [row.id, row]));
36
+ for (const id of _summaryCacheRemovals) byId.delete(id);
37
+ for (const [id, row] of _summaryCacheUpserts) byId.set(id, row);
38
+ return [...byId.values()].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
39
+ }
40
+
41
+ export function _setSummaryRowsCache(rows, options) {
42
+ if (options?.discardLocalMutations === true) {
43
+ _summaryCacheUpserts.clear();
44
+ _summaryCacheRemovals.clear();
45
+ }
46
+ _summaryRowsCache = _normalizeSummaryIndex({ rows }).rows;
47
+ return _summaryRowsWithLocalMutations(_summaryRowsCache);
48
+ }
49
+
50
+ export function _cachedSummaryRows() {
51
+ return _summaryRowsCache === null ? null : _summaryRowsWithLocalMutations(_summaryRowsCache);
52
+ }
53
+
54
+ function _setCachedBaseSummary(row) {
55
+ if (!row || _summaryRowsCache === null) return;
56
+ const byId = new Map(_summaryRowsCache.map((existing) => [existing.id, existing]));
57
+ byId.set(row.id, row);
58
+ _summaryRowsCache = [...byId.values()].sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
59
+ }
60
+
61
+ function _removeCachedBaseSummary(id) {
62
+ if (_summaryRowsCache === null) return;
63
+ _summaryRowsCache = _summaryRowsCache.filter((row) => row.id !== id);
64
+ }
65
+
66
+ export function _cacheSessionSummary(session) {
67
+ _ensureSummaryCacheDataDir();
68
+ const row = _sessionSummary(session);
69
+ if (!row) return;
70
+ _summaryCacheVersions.set(row.id, (_summaryCacheVersions.get(row.id) || 0) + 1);
71
+ _summaryCacheRemovals.delete(row.id);
72
+ _summaryCacheUpserts.set(row.id, row);
73
+ return _summaryCacheVersions.get(row.id);
74
+ }
75
+
76
+ export function _uncacheSessionSummary(id) {
77
+ _ensureSummaryCacheDataDir();
78
+ if (!id) return;
79
+ _summaryCacheVersions.set(id, (_summaryCacheVersions.get(id) || 0) + 1);
80
+ _summaryCacheUpserts.delete(id);
81
+ _summaryCacheRemovals.add(id);
82
+ _removeCachedBaseSummary(id);
83
+ }
84
+
85
+ export function _rollbackCachedSessionSummary(id, version) {
86
+ if ((_summaryCacheVersions.get(id) || 0) !== version) return;
87
+ _summaryCacheUpserts.delete(id);
88
+ }
89
+
90
+ export function _queueSessionSummaryUpsert(session, version = null) {
91
+ const row = _sessionSummary(session);
92
+ if (!row) return;
93
+ _setCachedBaseSummary(row);
94
+ if (version === null || (_summaryCacheVersions.get(row.id) || 0) === version) {
95
+ _summaryCacheUpserts.delete(row.id);
96
+ _summaryCacheRemovals.delete(row.id);
97
+ }
98
+ _upsertSessionSummary(session);
99
+ }
100
+
101
+ export function _queueSessionSummaryRemoval(id) {
102
+ _uncacheSessionSummary(id);
103
+ _removeSessionSummary(id);
104
+ }
105
+
106
+ export function _queueSummaryIndexPrune(ids) {
107
+ for (const id of ids) _uncacheSessionSummary(id);
108
+ _pruneSummaryIndexIds(ids);
109
+ }
110
+
111
+ // ── Incremental storage scan ────────────────────────────────────────────────
112
+ // refreshFromStorage used to re-parse EVERY session JSON (full transcripts,
113
+ // multi-MB files) on each desktop sidebar refresh. A summary only changes when
114
+ // its file changes, so key a per-file row cache on (mtimeMs, size): unchanged
115
+ // files reuse the cached row, changed/new files re-parse, vanished files drop
116
+ // out. Storage stays the truth source — the sidecar index is never trusted.
117
+ const _summaryScanCache = new Map(); // filename → { mtimeMs, size, row|null }
118
+
119
+ export function _scanStoredSessionSummaryRows() {
120
+ const dir = getStoreDir();
121
+ if (!existsSync(dir)) {
122
+ const changed = _summaryScanCache.size > 0;
123
+ _summaryScanCache.clear();
124
+ return { rows: [], invalidStorageIds: new Set(), changed };
125
+ }
126
+ const files = readdirSync(dir).filter(f => f.endsWith('.json'));
127
+ const present = new Set(files);
128
+ let changed = false;
129
+ for (const key of [..._summaryScanCache.keys()]) {
130
+ if (!present.has(key)) {
131
+ _summaryScanCache.delete(key);
132
+ changed = true;
133
+ }
134
+ }
135
+ const rows = [];
136
+ const invalidStorageIds = new Set();
137
+ const markInvalid = (filename) => {
138
+ const storageId = filename.slice(0, -5);
139
+ if (/^[A-Za-z0-9_-]+$/.test(storageId)) invalidStorageIds.add(storageId);
140
+ };
141
+ for (const f of files) {
142
+ let fileStat = null;
143
+ try { fileStat = statSync(join(dir, f)); } catch { /* deleted mid-scan */ }
144
+ if (!fileStat) {
145
+ if (_summaryScanCache.delete(f)) changed = true;
146
+ continue;
147
+ }
148
+ const cached = _summaryScanCache.get(f);
149
+ if (cached && cached.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
150
+ if (cached.row) rows.push(cached.row);
151
+ else markInvalid(f);
152
+ continue;
153
+ }
154
+ const session = _storedSessionFromFile(dir, f);
155
+ const row = session ? _sessionSummary(session) : null;
156
+ _summaryScanCache.set(f, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, row });
157
+ changed = true;
158
+ if (row) rows.push(row);
159
+ else markInvalid(f);
160
+ }
161
+ rows.sort((a, b) => (b.updatedAt || 0) - (a.updatedAt || 0));
162
+ return { rows, invalidStorageIds, changed };
163
+ }
@@ -1,7 +1,7 @@
1
1
  /** Module-level map tracking per-session cancellation generations and commit locks. */
2
2
  const _writeControls = new Map();
3
3
 
4
- export function writeControl(id) {
4
+ function writeControl(id) {
5
5
  let control = _writeControls.get(id);
6
6
  if (!control) {
7
7
  // [0] cancellation generation, [1] commit lock
@@ -27,7 +27,7 @@ function _messageText(content) {
27
27
  return sessionMessageText(content);
28
28
  }
29
29
 
30
- export function _cleanPreview(text, max = 240) {
30
+ function _cleanPreview(text, max = 240) {
31
31
  const value = cleanSessionPreview(text, max);
32
32
  return value.length > max ? value.slice(0, max).replace(/\s+\S*$/, '').trim() : value;
33
33
  }
@@ -76,7 +76,10 @@ function normalizedRow(row, heartbeatAt = 0) {
76
76
  updatedAt: positiveNumber(row.updatedAt, 0),
77
77
  createdAt: positiveNumber(row.createdAt, 0),
78
78
  lastHeartbeatAt: positiveNumber(row.lastHeartbeatAt, 0),
79
- heartbeatAt: Math.max(positiveNumber(row.heartbeatAt, 0), positiveNumber(heartbeatAt, 0)),
79
+ // Liveness comes from the .hb sidecar mtime alone: stored row fields
80
+ // (summary index / final session save) survive completion and must not
81
+ // keep the desktop working indicator on after the sidecar is deleted.
82
+ heartbeatAt: positiveNumber(heartbeatAt, 0),
80
83
  closed: row.closed === true,
81
84
  status: String(row.status || (row.closed === true ? 'closed' : 'idle')),
82
85
  owner: row.owner || 'user',