mixdog 1.4.0 → 1.4.2

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 (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -17,9 +17,9 @@
17
17
 
18
18
  import { createHash } from 'node:crypto';
19
19
 
20
- // Serialized (UTF-8 JSON) bytes of the retained PROJECTIONS. A tail-window
21
- // projection is ~0.1-1 MB; a legacy 512-item page of a worker transcript can
22
- // reach ~16 MB, so this keeps a couple of those or dozens of tail windows.
20
+ // Serialized (UTF-8 JSON) bytes of the retained PROJECTIONS: windows and pages
21
+ // sliced from a session's display history (which itself stays with the thread
22
+ // that built it); one larger than the whole budget is not retained.
23
23
  const STORED_TRANSCRIPT_CACHE_MAX_BYTES = 32 * 1024 * 1024;
24
24
  // Coarse filesystems stamp mtime at whole seconds (FAT: two). A write landing
25
25
  // inside that window after our read could keep the same stat, so only a file
@@ -70,22 +70,42 @@ export function nextProjectionStamp() {
70
70
  return `${stampEpoch}:${stampSequence}`;
71
71
  }
72
72
 
73
- export function createStoredTranscriptCache({
74
- // Bound retained content, not the number of small sessions. An eight-entry
75
- // LRU reparsed every unchanged record when nine visible sessions refreshed.
76
- maxEntries = Number.POSITIVE_INFINITY,
77
- maxBytes = STORED_TRANSCRIPT_CACHE_MAX_BYTES,
78
- maxIdleBytes = STORED_TRANSCRIPT_IDLE_MAX_BYTES,
79
- } = {}) {
73
+ const producedSlices = new WeakSet();
74
+ /** What a `produce` returns for a slice of a display history it may already
75
+ * hold: the value, its serialized `bytes` (measured by the producer) and
76
+ * whether a history was built for it (`historyBuilt`). */
77
+ export function producedSlice(value, { bytes = null, historyBuilt = true } = {}) {
78
+ const slice = { value, bytes, historyBuilt };
79
+ producedSlices.add(slice);
80
+ return slice;
81
+ }
82
+
83
+ /** This realm's stamp epoch (a worker composes history stamps with its
84
+ * caller's). */
85
+ export function projectionStampEpoch() {
86
+ return stampEpoch;
87
+ }
88
+
89
+ /** The identity of one display history: its session, record content and
90
+ * sidecar fingerprint, within one process epoch (a restart re-proves held
91
+ * rows by digest). Every slice of the history shares it, whichever thread
92
+ * built it and however often, so held rows stay proven across pages,
93
+ * evictions and worker restarts. */
94
+ export function historyStampOf(sessionId, textHash, textChars, fingerprint, epoch = stampEpoch) {
95
+ const id = createHash('sha256')
96
+ .update(`${sessionId}|${textHash}|${textChars}|${fingerprint}`)
97
+ .digest('base64url')
98
+ .slice(0, 22);
99
+ return `${epoch}:h${id}`;
100
+ }
101
+
102
+ /** The retained entries and their eviction policy: byte/entry budget, paging
103
+ * pins, ghost admission and idle trimming. */
104
+ function createEntryStore({ maxEntries, maxBytes, maxIdleBytes }) {
80
105
  // Retain a content digest, not a second full transcript beside its projection.
81
106
  // The bound is the size of what is actually retained: the projection.
82
107
  /** key -> { textHash, textChars, bytes, group, fingerprint, value, usedAt } (Map order is LRU order). */
83
108
  const entries = new Map();
84
- /** key -> { text, fingerprint, promise } for reads still parsing. */
85
- const inFlight = new Map();
86
- /** key -> { fingerprint, fileStat, promise } for reads not yet answered,
87
- * from before their body is loaded. */
88
- const reading = new Map();
89
109
  /** Keys evicted for space (insertion order = age), bounded. */
90
110
  const ghosts = new Set();
91
111
  let retainedBytes = 0;
@@ -104,8 +124,21 @@ export function createStoredTranscriptCache({
104
124
  };
105
125
  const overBudget = (extraEntries = 0, extraBytes = 0) =>
106
126
  entries.size + extraEntries > maxEntries || retainedBytes + extraBytes > maxBytes;
107
- const prune = () => {
108
- while (entries.size > 0 && overBudget()) evict(entries.keys().next().value);
127
+ // A history a reader is paging through (read with `pin` within
128
+ // ACTIVE_ENTRY_MS) stays cached under pressure: every page would otherwise
129
+ // build it again.
130
+ const paging = (entry, now) => now - entry.pinnedAt <= ACTIVE_ENTRY_MS;
131
+ // The newest entry stays (never larger than the budget: remember refuses
132
+ // those). For an unpinned newcomer, entries being paged are not evicted; a
133
+ // pinned one evicts whatever exceeds the budget, pinned or not.
134
+ const prune = (now, keepPaging) => {
135
+ const listed = [...entries];
136
+ const newest = listed.at(-1)?.[0];
137
+ for (const [key, entry] of listed) {
138
+ if (!overBudget()) return;
139
+ if (key === newest || (keepPaging && paging(entry, now))) continue;
140
+ evict(key);
141
+ }
109
142
  };
110
143
  // A returning ghost may displace only idle entries (oldest first).
111
144
  const makeRoomForGhost = (bytes, now) => {
@@ -123,25 +156,37 @@ export function createStoredTranscriptCache({
123
156
  for (const other of idle) evict(other);
124
157
  return true;
125
158
  };
126
- const remember = (key, group, textChars, textHash, fingerprint, fileStat, value, now) => {
159
+ const remember = (key, group, textChars, textHash, fingerprint, fileStat, value, now, pin, measured) => {
127
160
  drop(key);
128
161
  // One entry per group: a growing history window replaces its smaller
129
162
  // predecessor instead of accumulating beside it.
130
163
  if (group !== null) {
131
164
  for (const [other, entry] of [...entries]) if (entry.group === group) drop(other);
132
165
  }
133
- const bytes = Buffer.byteLength(JSON.stringify(value));
166
+ // Values are slices (a window or page), never a whole history; the
167
+ // producer's own measure is used when it has one.
168
+ const bytes = Number.isSafeInteger(measured) && measured >= 0 ? measured : Buffer.byteLength(JSON.stringify(value));
169
+ // Larger than the whole budget: served uncached, pinned or not.
134
170
  if (bytes > maxBytes) return;
135
- if (ghosts.has(key)) {
136
- if (!makeRoomForGhost(bytes, now)) return;
137
- ghosts.delete(key);
171
+ if (pin) ghosts.delete(key);
172
+ else {
173
+ if (ghosts.has(key)) {
174
+ if (!makeRoomForGhost(bytes, now)) return;
175
+ ghosts.delete(key);
176
+ }
177
+ // Never in place of a history being paged: served uncached instead.
178
+ let pagedBytes = 0;
179
+ for (const entry of entries.values()) if (paging(entry, now)) pagedBytes += entry.bytes;
180
+ if (pagedBytes > 0 && pagedBytes + bytes > maxBytes) return;
138
181
  }
139
- entries.set(key, { textChars, textHash, bytes, group, fingerprint, fileStat, value, usedAt: now });
182
+ const pinnedAt = pin ? now : Number.NEGATIVE_INFINITY;
183
+ entries.set(key, { textChars, textHash, bytes, group, fingerprint, fileStat, value, usedAt: now, pinnedAt });
140
184
  retainedBytes += bytes;
141
- prune();
185
+ prune(now, !pin);
142
186
  };
143
- const touch = (key, entry, now) => {
187
+ const touch = (key, entry, now, pin = false) => {
144
188
  entry.usedAt = now;
189
+ if (pin) entry.pinnedAt = now;
145
190
  entries.delete(key);
146
191
  entries.set(key, entry);
147
192
  };
@@ -168,14 +213,59 @@ export function createStoredTranscriptCache({
168
213
  : null;
169
214
  };
170
215
 
216
+ return {
217
+ get: (key) => entries.get(key),
218
+ remember,
219
+ touch,
220
+ trimIdle,
221
+ entryWithContent,
222
+ release(keyPrefix, now) {
223
+ for (const [key, entry] of entries) {
224
+ if (key.startsWith(keyPrefix)) entry.usedAt = Math.min(entry.usedAt, now - ACTIVE_ENTRY_MS - 1);
225
+ }
226
+ trimIdle(now);
227
+ },
228
+ forget(keyPrefix) {
229
+ for (const key of [...entries.keys()]) {
230
+ if (key.startsWith(keyPrefix)) drop(key);
231
+ }
232
+ for (const key of [...ghosts]) {
233
+ if (key.startsWith(keyPrefix)) ghosts.delete(key);
234
+ }
235
+ },
236
+ clear() {
237
+ entries.clear();
238
+ ghosts.clear();
239
+ retainedBytes = 0;
240
+ },
241
+ stats: () => ({ entries: entries.size, retainedBytes, ghosts: ghosts.size }),
242
+ };
243
+ }
244
+
245
+ export function createStoredTranscriptCache({
246
+ // Bound retained content, not the number of small sessions. An eight-entry
247
+ // LRU reparsed every unchanged record when nine visible sessions refreshed.
248
+ maxEntries = Number.POSITIVE_INFINITY,
249
+ maxBytes = STORED_TRANSCRIPT_CACHE_MAX_BYTES,
250
+ maxIdleBytes = STORED_TRANSCRIPT_IDLE_MAX_BYTES,
251
+ } = {}) {
252
+ const store = createEntryStore({ maxEntries, maxBytes, maxIdleBytes });
253
+ /** key -> { text, fingerprint, promise } for reads still parsing. */
254
+ const inFlight = new Map();
255
+ /** key -> { fingerprint, fileStat, promise } for reads not yet answered,
256
+ * from before their body is loaded. */
257
+ const reading = new Map();
258
+ // Projections produced (here or by `project`), cached or not.
259
+ let builds = 0;
260
+
171
261
  // `project(known)` reads, digests and projects the record elsewhere (a
172
262
  // worker thread) in one step: `{ textChars, textHash, value }`, or
173
263
  // `{ textChars, textHash, same: true }` without a projection when the
174
264
  // content is the `known` digest of the cached entry; null when the record
175
265
  // cannot be read. Undefined when it could not answer at all.
176
- async function readProjected({ key, group, fingerprint, fileStat, project, now }) {
266
+ async function readProjected({ key, group, fingerprint, fileStat, project, now, pin }) {
177
267
  for (let attempt = 0; attempt < 2; attempt += 1) {
178
- const cached = entries.get(key);
268
+ const cached = store.get(key);
179
269
  const known =
180
270
  attempt === 0 && cached?.fingerprint === fingerprint
181
271
  ? { textChars: cached.textChars, textHash: cached.textHash }
@@ -195,38 +285,52 @@ export function createStoredTranscriptCache({
195
285
  if (answer === undefined) return undefined;
196
286
  if (answer === null || typeof answer !== 'object') return { value: null, hit: false, read: true };
197
287
  if (answer.same === true) {
198
- const entry = entryWithContent(key, fingerprint, answer);
288
+ const entry = store.entryWithContent(key, fingerprint, answer);
199
289
  // Evicted meanwhile: project it after all.
200
290
  if (!entry) continue;
201
291
  entry.fileStat = fileStat;
202
- touch(key, entry, now);
292
+ store.touch(key, entry, now, pin);
203
293
  return { value: entry.value, hit: true, read: true };
204
294
  }
205
295
  const value = answer.value ?? null;
296
+ // A worker that served the slice from a history it already held built nothing.
297
+ const built = Boolean(value && typeof value === 'object' && answer.historyBuilt !== false);
298
+ if (built) builds += 1;
206
299
  if (current && value && typeof value === 'object') {
207
- remember(key, group, answer.textChars, answer.textHash, fingerprint, fileStat, value, now);
300
+ store.remember(
301
+ key,
302
+ group,
303
+ answer.textChars,
304
+ answer.textHash,
305
+ fingerprint,
306
+ fileStat,
307
+ value,
308
+ now,
309
+ pin,
310
+ answer.bytes
311
+ );
208
312
  }
209
- return { value, hit: false, read: true };
313
+ return { value, hit: false, read: true, built };
210
314
  }
211
315
  return undefined;
212
316
  }
213
317
 
214
- async function readFresh({ key, group, fingerprint, fileStat, loadText, produce, project, now }) {
318
+ async function readFresh({ key, group, fingerprint, fileStat, loadText, produce, project, now, pin }) {
215
319
  if (typeof project === 'function') {
216
- const answered = await readProjected({ key, group, fingerprint, fileStat, project, now });
320
+ const answered = await readProjected({ key, group, fingerprint, fileStat, project, now, pin });
217
321
  if (answered !== undefined) return answered;
218
322
  // It could not answer (worker unavailable): load and produce here.
219
323
  }
220
- const cached = entries.get(key);
324
+ const cached = store.get(key);
221
325
  // `loadText` may answer asynchronously (a large record is read without
222
326
  // blocking the loop); a plain string is unchanged.
223
327
  let text = loadText();
224
328
  if (typeof text?.then === 'function') text = await text;
225
329
  if (typeof text !== 'string') return { value: null, hit: false, read: true };
226
330
  const textHash = transcriptTextHash(text);
227
- if (cached && entryWithContent(key, fingerprint, { textChars: text.length, textHash }) === cached) {
331
+ if (cached && store.entryWithContent(key, fingerprint, { textChars: text.length, textHash }) === cached) {
228
332
  cached.fileStat = fileStat;
229
- touch(key, cached, now);
333
+ store.touch(key, cached, now, pin);
230
334
  return { value: cached.value, hit: true, read: true };
231
335
  }
232
336
  const pending = inFlight.get(key);
@@ -234,17 +338,23 @@ export function createStoredTranscriptCache({
234
338
  return { value: await pending.promise, hit: true, read: true };
235
339
  }
236
340
  const record = { text, fingerprint, promise: null };
341
+ let built = false;
237
342
  const promise = (async () => {
238
- const value = await produce(text);
343
+ const produced = await produce(text);
344
+ const slice = producedSlices.has(produced) ? produced : null;
345
+ const value = slice ? slice.value : produced;
346
+ built = Boolean(value && typeof value === 'object' && slice?.historyBuilt !== false);
347
+ if (built) builds += 1;
239
348
  if (inFlight.get(key) === record && value && typeof value === 'object') {
240
- remember(key, group, text.length, textHash, fingerprint, fileStat, value, now);
349
+ store.remember(key, group, text.length, textHash, fingerprint, fileStat, value, now, pin, slice?.bytes);
241
350
  }
242
351
  return value;
243
352
  })();
244
353
  record.promise = promise;
245
354
  inFlight.set(key, record);
246
355
  try {
247
- return { value: await promise, hit: false, read: true };
356
+ const value = await promise;
357
+ return { value, hit: false, read: true, built };
248
358
  } finally {
249
359
  if (inFlight.get(key) === record) inFlight.delete(key);
250
360
  }
@@ -256,7 +366,9 @@ export function createStoredTranscriptCache({
256
366
  * `project` run only when stat alone cannot vouch for the entry.
257
367
  * Concurrent callers with the same content share one parse; callers of
258
368
  * one unchanged, settled file share one read before it is even loaded.
259
- * Entries naming the same `group` replace each other. */
369
+ * Entries naming the same `group` replace each other. `pin`: a reader is
370
+ * paging through this entry — it is cached however full the cache is,
371
+ * and for ACTIVE_ENTRY_MS no unpinned newcomer evicts it. */
260
372
  async read({
261
373
  key,
262
374
  group = null,
@@ -266,11 +378,12 @@ export function createStoredTranscriptCache({
266
378
  produce,
267
379
  project = null,
268
380
  now = Date.now(),
381
+ pin = false,
269
382
  }) {
270
- trimIdle(now);
271
- const cached = entries.get(key);
383
+ store.trimIdle(now);
384
+ const cached = store.get(key);
272
385
  if (cached && cached.fingerprint === fingerprint && settledSameFile(cached.fileStat, fileStat, now)) {
273
- touch(key, cached, now);
386
+ store.touch(key, cached, now, pin);
274
387
  return { value: cached.value, hit: true, read: false };
275
388
  }
276
389
  const joined = reading.get(key);
@@ -280,7 +393,7 @@ export function createStoredTranscriptCache({
280
393
  if (value) return { value, hit: true, read: false };
281
394
  }
282
395
  const record = { fingerprint, fileStat, promise: null };
283
- record.promise = readFresh({ key, group, fingerprint, fileStat, loadText, produce, project, now });
396
+ record.promise = readFresh({ key, group, fingerprint, fileStat, loadText, produce, project, now, pin });
284
397
  reading.set(key, record);
285
398
  try {
286
399
  return await record.promise;
@@ -291,10 +404,7 @@ export function createStoredTranscriptCache({
291
404
  /** The last view of these projections left: they turn idle now and stay
292
405
  * only within the idle budget, ready for a revisit. */
293
406
  release(keyPrefix, now = Date.now()) {
294
- for (const [key, entry] of entries) {
295
- if (key.startsWith(keyPrefix)) entry.usedAt = Math.min(entry.usedAt, now - ACTIVE_ENTRY_MS - 1);
296
- }
297
- trimIdle(now);
407
+ store.release(keyPrefix, now);
298
408
  },
299
409
  forget(keyPrefix) {
300
410
  for (const pending of [inFlight, reading]) {
@@ -302,28 +412,17 @@ export function createStoredTranscriptCache({
302
412
  if (key.startsWith(keyPrefix)) pending.delete(key);
303
413
  }
304
414
  }
305
- for (const key of [...entries.keys()]) {
306
- if (key.startsWith(keyPrefix)) drop(key);
307
- }
308
- for (const key of [...ghosts]) {
309
- if (key.startsWith(keyPrefix)) ghosts.delete(key);
310
- }
415
+ store.forget(keyPrefix);
311
416
  },
312
417
  clear() {
313
- entries.clear();
418
+ store.clear();
314
419
  inFlight.clear();
315
420
  reading.clear();
316
- ghosts.clear();
317
- retainedBytes = 0;
421
+ builds = 0;
318
422
  },
319
423
  stats() {
320
- return {
321
- entries: entries.size,
322
- retainedBytes,
323
- inFlight: inFlight.size,
324
- reading: reading.size,
325
- ghosts: ghosts.size,
326
- };
424
+ const { entries, retainedBytes, ghosts } = store.stats();
425
+ return { entries, retainedBytes, inFlight: inFlight.size, reading: reading.size, ghosts, builds };
327
426
  },
328
427
  };
329
428
  }
@@ -107,27 +107,186 @@ export async function prepareStoredTranscriptBase(sessionId, doc, options) {
107
107
  };
108
108
  }
109
109
 
110
- /** One transcript window (`options.itemLimit`, default whole) of a base. */
110
+ /** The digest a paged caller proves its held rows with: over their wire form,
111
+ * as the caller serializes them. */
112
+ export async function heldRowsDigester() {
113
+ const [{ sanitizeForWire }, { transcriptItemsDigest }] = await Promise.all([
114
+ import('../../../../standalone/session-wire-values.mjs'),
115
+ import('../../../../standalone/session-state-patch.mjs'),
116
+ ]);
117
+ return (rows) => transcriptItemsDigest(rows.map((row) => sanitizeForWire(row, 2) ?? null));
118
+ }
119
+
120
+ /** The rows a page reveals never go below this many whatever their size, so
121
+ * paging always progresses (the caller's own floor). */
122
+ const PAGE_MIN_ROWS = 8;
123
+
124
+ /** The slice of a whole display history (windowStoredTranscript with
125
+ * `history`) one read receives (`request`):
126
+ * - `held` (`{ firstId, count, digest }`, the newest rows a paged caller
127
+ * holds): when the history's newest `held.count` rows are those rows (same
128
+ * first id and — unless `proven` — the same `digestOf` digest), only the
129
+ * rows above them: at most `maxRows` (and `limit - held.count`) within
130
+ * `byteBudget` (`rowBytes(index)`), never fewer than PAGE_MIN_ROWS, with
131
+ * `heldProven`. Unproven: a tail of at most `maxRows` rows instead
132
+ * (`heldProven: false`).
133
+ * - otherwise its newest `limit` rows but the newest `skip` (with
134
+ * `skippedFirstId`, the oldest skipped row) — or, without a finite limit
135
+ * (the legacy whole read), every restored row and none of the compaction
136
+ * archive.
137
+ * `windowStamp` names the slice within the history (the caller composes the
138
+ * projection stamp). Slicing only: whatever the depth, a slice carries only
139
+ * its own rows, and the history never leaves the thread that built it. */
140
+ export function storedHistorySlice(history, request = {}, { digestOf = null, rowBytes = null } = {}) {
141
+ if (!history || typeof history !== 'object' || !Array.isArray(history.items)) return history;
142
+ const { transcriptArchivedRows: archivedRows = 0, items, projectionStamp: _stamp, ...fields } = history;
143
+ const sizeOf =
144
+ typeof rowBytes === 'function' ? rowBytes : (index) => Buffer.byteLength(JSON.stringify(items[index] ?? null));
145
+ if (request.cursor) return historyCursorSlice(fields, items, request.cursor, history.transcriptHasOlder, sizeOf);
146
+ const itemLimit = Number(request.limit);
147
+ if (!Number.isFinite(itemLimit)) {
148
+ return {
149
+ ...fields,
150
+ windowStamp: 'whole',
151
+ items: archivedRows > 0 ? items.slice(archivedRows) : items.slice(),
152
+ transcriptHasOlder: false,
153
+ };
154
+ }
155
+ const limit = Math.max(0, Math.floor(itemLimit));
156
+ const held = request.held;
157
+ if (held) {
158
+ const maxRows = Math.max(1, Math.floor(Number(request.maxRows) || limit));
159
+ const end = items.length - held.count;
160
+ const holds =
161
+ end >= 0 &&
162
+ items[end]?.id === held.firstId &&
163
+ (request.proven === true || (typeof digestOf === 'function' && digestOf(items.slice(end)) === held.digest));
164
+ if (!holds) {
165
+ // A fresh tail of one answer, within the same byte budget (and floor).
166
+ const start = budgetedEdge(items.length, -1, items.length, Math.min(limit, maxRows), request.byteBudget, sizeOf);
167
+ return {
168
+ ...fields,
169
+ windowStamp: `${items.length - start}`,
170
+ items: items.slice(start),
171
+ transcriptHasOlder: start > 0 || history.transcriptHasOlder === true,
172
+ heldProven: false,
173
+ };
174
+ }
175
+ const rows = Math.max(0, Math.min(maxRows, limit - held.count));
176
+ const start = budgetedEdge(end, -1, items.length, rows, request.byteBudget, sizeOf);
177
+ return {
178
+ ...fields,
179
+ windowStamp: `${items.length - start}@${held.count}`,
180
+ items: items.slice(start, end),
181
+ transcriptHasOlder: start > 0 || history.transcriptHasOlder === true,
182
+ skippedFirstId: held.firstId,
183
+ heldProven: true,
184
+ };
185
+ }
186
+ const skipped = Math.max(0, Math.min(Math.floor(Number(request.skip) || 0), limit, items.length));
187
+ const end = items.length - skipped;
188
+ const start = Math.max(0, items.length - limit);
189
+ return {
190
+ ...fields,
191
+ windowStamp: skipped ? `${limit}@${skipped}` : `${limit}`,
192
+ items: items.slice(start, end),
193
+ transcriptHasOlder: start > 0 || history.transcriptHasOlder === true,
194
+ ...(skipped ? { skippedFirstId: items[end]?.id ?? null } : {}),
195
+ };
196
+ }
197
+
198
+ /** The far edge of a run of at most `rows` rows within `byteBudget`
199
+ * (`sizeOf(index)`), never fewer than PAGE_MIN_ROWS whatever their size:
200
+ * older (`step` -1) ending just before index `from`, or newer (`step` +1)
201
+ * starting at it. */
202
+ function budgetedEdge(from, step, length, rows, byteBudget, sizeOf) {
203
+ const budget = Number(byteBudget) > 0 ? Number(byteBudget) : null;
204
+ const floor = Math.min(rows, PAGE_MIN_ROWS);
205
+ let edge = from;
206
+ let taken = 0;
207
+ let bytes = 0;
208
+ while (taken < rows) {
209
+ const index = step < 0 ? edge - 1 : edge;
210
+ if (index < 0 || index >= length) break;
211
+ const next = budget ? sizeOf(index) : 0;
212
+ if (budget && taken >= floor && bytes + next > budget) break;
213
+ bytes += next;
214
+ taken += 1;
215
+ edge += step;
216
+ }
217
+ return edge;
218
+ }
219
+
220
+ /** The most rows one historical-position read carries, whatever it asks. */
221
+ export const CURSOR_PAGE_MAX_ROWS = 512;
222
+
223
+ /** A cursor's page size: 1 to CURSOR_PAGE_MAX_ROWS rows. */
224
+ export function cursorPageRows(maxRows) {
225
+ return Math.min(CURSOR_PAGE_MAX_ROWS, Math.max(1, Math.floor(Number(maxRows) || 1)));
226
+ }
227
+
228
+ /** A historical-position read (`cursor`: `{ before | after, maxRows,
229
+ * byteBudget }`): the rows directly older than row `before`, or newer than
230
+ * row `after`, at most `maxRows` within `byteBudget` (PAGE_MIN_ROWS floor),
231
+ * with whether rows remain past them each way. Rows are located by their
232
+ * stable id, so a page stays valid while the history only grows; a row the
233
+ * history no longer holds (compaction, rewind, clear) answers
234
+ * `cursorMissing`. */
235
+ function historyCursorSlice(fields, items, cursor, olderBeyond, sizeOf) {
236
+ const older = cursor.before != null;
237
+ const id = older ? cursor.before : cursor.after;
238
+ const at = items.findIndex((item) => item?.id === id);
239
+ if (at < 0) return { ...fields, windowStamp: 'cursor', items: [], cursorMissing: true };
240
+ const rows = cursorPageRows(cursor.maxRows);
241
+ if (older) {
242
+ const start = budgetedEdge(at, -1, items.length, rows, cursor.byteBudget, sizeOf);
243
+ return {
244
+ ...fields,
245
+ windowStamp: 'cursor',
246
+ items: items.slice(start, at),
247
+ transcriptHasOlder: start > 0 || olderBeyond === true,
248
+ transcriptHasNewer: true,
249
+ };
250
+ }
251
+ const end = budgetedEdge(at + 1, 1, items.length, rows, cursor.byteBudget, sizeOf);
252
+ return {
253
+ ...fields,
254
+ windowStamp: 'cursor',
255
+ items: items.slice(at + 1, end),
256
+ transcriptHasOlder: true,
257
+ transcriptHasNewer: end < items.length,
258
+ };
259
+ }
260
+
261
+ /** One transcript window (`options.itemLimit`, default whole) of a base.
262
+ * `options.history`: the whole display history instead — every restored row
263
+ * after every row of the visible compaction archive, with
264
+ * `transcriptArchivedRows` naming how many lead it. Every window is a tail
265
+ * of it (storedHistoryWindow). */
111
266
  export async function windowStoredTranscript(sessionId, base, options) {
112
267
  const { session, messages, contextFields } = base;
113
268
  const { restoreTranscriptItems } = await import('./transcript-restore/restore.mjs');
269
+ const history = options.history === true;
114
270
  // Restoring one item past a finite limit proves whether older history exists
115
271
  // without projecting it; restore ids are message-positional, so the kept
116
272
  // tail is identical either way.
117
- const itemLimit = Number.isFinite(options.itemLimit) ? options.itemLimit : Number.POSITIVE_INFINITY;
273
+ const itemLimit = !history && Number.isFinite(options.itemLimit) ? options.itemLimit : Number.POSITIVE_INFINITY;
118
274
  const restored = restoreTranscriptItems(messages, { sessionId, itemLimit: itemLimit + 1 });
119
275
  let transcriptHasOlder = restored.length > itemLimit;
120
276
  let items = transcriptHasOlder ? restored.slice(-itemLimit) : restored;
277
+ let archivedRows = 0;
121
278
  // A paged window continues past the first stored message into the
122
279
  // display-only compaction archive (newest segment first). A legacy whole
123
280
  // read never receives it.
124
- const archiveRange = Number.isFinite(itemLimit) && !transcriptHasOlder ? compactionArchiveRange(session) : null;
281
+ const archiveRange =
282
+ history || (Number.isFinite(itemLimit) && !transcriptHasOlder) ? compactionArchiveRange(session) : null;
125
283
  if (archiveRange) {
126
284
  const archived = await readCompactionArchivePage(join(dataDir(), 'sessions'), sessionId, {
127
285
  ...archiveRange,
128
- limit: itemLimit - restored.length,
286
+ limit: history ? Number.POSITIVE_INFINITY : itemLimit - restored.length,
129
287
  });
130
288
  if (archived.items.length) items = [...archived.items, ...restored];
289
+ archivedRows = archived.items.length;
131
290
  transcriptHasOlder = archived.hasOlder;
132
291
  }
133
292
  return {
@@ -136,6 +295,7 @@ export async function windowStoredTranscript(sessionId, base, options) {
136
295
  ...(options.includeMessages ? { messages } : {}),
137
296
  items,
138
297
  transcriptHasOlder,
298
+ ...(history ? { transcriptArchivedRows: archivedRows } : {}),
139
299
  provider: session.provider || '',
140
300
  model: session.model || '',
141
301
  effort: session.effort || '',