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
@@ -1,11 +1,18 @@
1
1
  /**
2
2
  * Codex CLI rollout (`rollout-*.jsonl`) → raw import turns. Kept: the first
3
3
  * `session_meta` (foreign id, cwd), user/assistant `message` items as text and
4
- * tool calls as `[ran tool: <name>]` notes. Dropped: developer/system
4
+ * tool calls as tool notes. Dropped: developer/system
5
5
  * messages, Codex's own wrapper user messages, tool outputs, reasoning,
6
6
  * event_msg, token counts and turn_context.
7
7
  */
8
- import { IMAGE_OMITTED_TEXT, IMPORT_WARNING_IMAGE_OMITTED, isWholeElement, textPart, toolNote } from './turns.mjs';
8
+ import {
9
+ IMAGE_OMITTED_TEXT,
10
+ IMPORT_WARNING_IMAGE_OMITTED,
11
+ isWholeElement,
12
+ stringOrNull,
13
+ textPart,
14
+ toolNote,
15
+ } from './turns.mjs';
9
16
 
10
17
  // User-role envelopes Codex injects itself (environment, instructions, abort
11
18
  // notices, plugin hints, shell-command echoes, image wrappers).
@@ -17,6 +24,7 @@ const CODEX_ENVELOPE_TAGS = [
17
24
  'user_shell_command',
18
25
  'subagent_notification',
19
26
  'skills_instructions',
27
+ 'external_codex_apps_open_page',
20
28
  ];
21
29
  const TOOL_CALL_TYPES = new Set(['function_call', 'custom_tool_call', 'local_shell_call']);
22
30
 
@@ -29,6 +37,17 @@ export function isCodexEnvelopeText(text) {
29
37
  return /^<image\b[^>]*>$/.test(trimmed) || trimmed === '</image>';
30
38
  }
31
39
 
40
+ // Codex shows a user message after the last of these headings: what precedes it
41
+ // is context the app adds for the model (IDE state, mentioned files, response
42
+ // annotations, an earlier request it already carries).
43
+ const USER_REQUEST_HEADING = '## My request for Codex:';
44
+
45
+ /** A user text part as Codex displays it. */
46
+ function codexUserDisplayText(text) {
47
+ const at = text.lastIndexOf(USER_REQUEST_HEADING);
48
+ return at < 0 ? text : text.slice(at + USER_REQUEST_HEADING.length).trim();
49
+ }
50
+
32
51
  function messageParts(payload, warnings) {
33
52
  const content = typeof payload.content === 'string' ? [{ type: 'text', text: payload.content }] : payload.content;
34
53
  if (!Array.isArray(content)) return [];
@@ -41,8 +60,8 @@ function messageParts(payload, warnings) {
41
60
  continue;
42
61
  }
43
62
  if (typeof item.text !== 'string') continue;
44
- if (payload.role === 'user' && isCodexEnvelopeText(item.text)) continue;
45
- parts.push(textPart(item.text));
63
+ if (payload.role !== 'user') parts.push(textPart(item.text));
64
+ else if (!isCodexEnvelopeText(item.text)) parts.push(textPart(codexUserDisplayText(item.text)));
46
65
  }
47
66
  return parts;
48
67
  }
@@ -52,7 +71,30 @@ function toolName(payload) {
52
71
  return payload.type === 'local_shell_call' ? 'local_shell' : '';
53
72
  }
54
73
 
55
- const stringOrNull = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
74
+ // `session_meta.thread_source` values Codex gives threads it runs for itself.
75
+ const INTERNAL_THREAD_SOURCES = {
76
+ subagent: 'subagent',
77
+ guardian_review: 'guardianReview',
78
+ memory_consolidation: 'internal',
79
+ };
80
+
81
+ /**
82
+ * What kind of Codex-internal record a `session_meta` payload marks, else null
83
+ * for a conversation the user had. Markers are Codex's own: `thread_source`
84
+ * (`subagent`, `guardian_review`, `memory_consolidation`) and a `source` of the
85
+ * form `{ subagent: … }` (spawned agent, review, compact; `{ other: "guardian" }`
86
+ * is the approval reviewer) or `{ internal: … }`. Other sources (cli, vscode,
87
+ * exec, mcp, custom) and features (automation, voice chat) are the user's.
88
+ */
89
+ function codexInternalKind(payload) {
90
+ const byThread = INTERNAL_THREAD_SOURCES[payload?.thread_source];
91
+ if (byThread) return byThread;
92
+ const source = payload?.source;
93
+ if (!source || typeof source !== 'object') return null;
94
+ if ('subagent' in source) return source.subagent?.other === 'guardian' ? 'guardianReview' : 'subagent';
95
+ if ('internal' in source) return source.internal === 'guardian' ? 'guardianReview' : 'internal';
96
+ return null;
97
+ }
56
98
  // Rough fixed cost of one retained turn object, so tiny turns still count.
57
99
  const ENTRY_OVERHEAD_BYTES = 64;
58
100
 
@@ -67,6 +109,8 @@ export function createCodexReducer() {
67
109
  let recognized = false;
68
110
  let foreignSessionId = null;
69
111
  let cwd = null;
112
+ let internalKind = null;
113
+ let historyBase = null;
70
114
  let metaSeen = false;
71
115
  let chars = 0;
72
116
  let metadataBytes = 0;
@@ -86,15 +130,19 @@ export function createCodexReducer() {
86
130
  get metadataBytes() {
87
131
  return metadataBytes;
88
132
  },
89
- push(record) {
133
+ /** `inherited` records come from a history base: its session metadata is not this thread's. */
134
+ push(record, inherited = false) {
90
135
  const payload = record?.payload && typeof record.payload === 'object' ? record.payload : null;
91
136
  if (!payload) return;
92
137
  if (record.type === 'session_meta') {
138
+ if (inherited) return;
93
139
  recognized = true;
94
140
  if (!metaSeen) {
95
141
  metaSeen = true;
96
142
  foreignSessionId = stringOrNull(payload.id) || stringOrNull(payload.session_id);
97
143
  cwd = stringOrNull(payload.cwd);
144
+ internalKind = codexInternalKind(payload);
145
+ if (payload.history_base && typeof payload.history_base === 'object') historyBase = payload.history_base;
98
146
  }
99
147
  return;
100
148
  }
@@ -108,23 +156,95 @@ export function createCodexReducer() {
108
156
  }
109
157
  },
110
158
  result() {
111
- return { recognized, foreignSessionId, cwd, rawTurns, warnings };
159
+ return { recognized, foreignSessionId, cwd, internalKind, historyBase, rawTurns, warnings };
112
160
  },
113
161
  };
114
162
  }
115
163
 
116
- /** id/cwd of a `session_meta` first line cut off by a bounded prefix read. */
117
- export function codexMetaFromTruncatedLine(line) {
118
- const text = String(line || '');
119
- if (!/"type"\s*:\s*"session_meta"/.test(text)) return null;
120
- const field = (name) => {
121
- const match = new RegExp(`"${name}"\\s*:\\s*("(?:[^"\\\\]|\\\\.)*")`).exec(text);
122
- if (!match) return null;
123
- try {
124
- return stringOrNull(JSON.parse(match[1]));
125
- } catch {
126
- return null;
164
+ // ── bounded-prefix `session_meta` scan ───────────────────────────────────────
165
+ // Index after the string starting at `i`, or -1 when the text ends inside it.
166
+ function scanString(text, i) {
167
+ for (let j = i + 1; j < text.length; j++) {
168
+ if (text[j] === '\\') j++;
169
+ else if (text[j] === '"') return j + 1;
170
+ }
171
+ return -1;
172
+ }
173
+
174
+ // Index after the JSON value starting at `i`, or -1 when the text ends inside it.
175
+ function scanValue(text, i) {
176
+ if (text[i] === '"') return scanString(text, i);
177
+ if (text[i] === '{' || text[i] === '[') {
178
+ let depth = 0;
179
+ for (let j = i; j < text.length; j++) {
180
+ if (text[j] === '"') {
181
+ j = scanString(text, j) - 1;
182
+ if (j < 0) return -1;
183
+ } else if (text[j] === '{' || text[j] === '[') depth++;
184
+ else if ((text[j] === '}' || text[j] === ']') && --depth === 0) return j + 1;
185
+ }
186
+ return -1;
187
+ }
188
+ const literal = /[^,}\]\s]+/y;
189
+ literal.lastIndex = i;
190
+ literal.exec(text);
191
+ return literal.lastIndex < text.length ? literal.lastIndex : -1;
192
+ }
193
+
194
+ /**
195
+ * Complete top-level values (raw JSON text) of the record object and of its
196
+ * `payload` object in a possibly cut-off line. Nested values are never
197
+ * inspected, so a key inside custom metadata is not mistaken for a payload field.
198
+ */
199
+ function recordFields(text) {
200
+ const top = {};
201
+ const payload = {};
202
+ const walk = (start, target, descend) => {
203
+ let j = start + 1;
204
+ for (;;) {
205
+ while (/[\s,]/.test(text[j] ?? '')) j++;
206
+ if (text[j] !== '"') return;
207
+ const keyEnd = scanString(text, j);
208
+ if (keyEnd < 0) return;
209
+ const key = JSON.parse(text.slice(j, keyEnd));
210
+ j = keyEnd;
211
+ while (/\s/.test(text[j] ?? '')) j++;
212
+ if (text[j] !== ':') return;
213
+ j++;
214
+ while (/\s/.test(text[j] ?? '')) j++;
215
+ if (descend && key === 'payload' && text[j] === '{') return walk(j, payload, false);
216
+ const end = scanValue(text, j);
217
+ if (end < 0) return;
218
+ target[key] = text.slice(j, end);
219
+ j = end;
127
220
  }
128
221
  };
129
- return { foreignSessionId: field('id') || field('session_id'), cwd: field('cwd') };
222
+ const open = text.indexOf('{');
223
+ if (open >= 0) walk(open, top, true);
224
+ return { top, payload };
225
+ }
226
+
227
+ const jsonOrNull = (raw) => {
228
+ try {
229
+ return raw === undefined ? null : JSON.parse(raw);
230
+ } catch {
231
+ return null;
232
+ }
233
+ };
234
+
235
+ /**
236
+ * id/cwd/internal kind of a `session_meta` first line cut off by a bounded
237
+ * prefix read, from the payload fields that arrived whole. A marker that did
238
+ * not arrive whole leaves the record unclassified (`internalKind: null`).
239
+ */
240
+ export function codexMetaFromTruncatedLine(line) {
241
+ const { top, payload } = recordFields(String(line || ''));
242
+ if (jsonOrNull(top.type) !== 'session_meta') return null;
243
+ const field = (name) => stringOrNull(jsonOrNull(payload[name]));
244
+ const meta = { thread_source: field('thread_source'), source: jsonOrNull(payload.source) };
245
+ return {
246
+ foreignSessionId: field('id') || field('session_id'),
247
+ cwd: field('cwd'),
248
+ internalKind: codexInternalKind(meta),
249
+ };
130
250
  }
@@ -6,7 +6,13 @@
6
6
  */
7
7
  import { createClaudeReducer } from './claude.mjs';
8
8
  import { createCodexReducer } from './codex.mjs';
9
- import { IMPORT_WARNING_MALFORMED, conversationTitle, normalizeTurns } from './turns.mjs';
9
+ import {
10
+ IMPORT_ERROR_INTERNAL,
11
+ IMPORT_WARNING_HISTORY_PARTIAL,
12
+ IMPORT_WARNING_MALFORMED,
13
+ conversationTitle,
14
+ normalizeTurns,
15
+ } from './turns.mjs';
10
16
 
11
17
  export const IMPORT_SOURCES = Object.freeze(['codex', 'claude']);
12
18
  /** A line or converted transcript beyond the import limits fails the file. */
@@ -35,7 +41,9 @@ const transcriptChars = (turns) => turns.reduce((total, turn) => total + turn.te
35
41
  * (an over-long line or file). `result()` may be called repeatedly and also
36
42
  * holds the normalized turns to `maxTranscriptChars`.
37
43
  * `errorCode` is null for an importable result, else one of `empty` |
38
- * `malformed` | `unsupported` | `tooLarge` (whose turns are not returned).
44
+ * `malformed` | `unsupported` | `internalRecord` (the source app's own
45
+ * subagent/review record; `internalKind` names it) | `tooLarge` (whose turns
46
+ * are not returned).
39
47
  */
40
48
  export function createConversationConverter(
41
49
  source,
@@ -47,18 +55,37 @@ export function createConversationConverter(
47
55
  ) {
48
56
  if (!isImportSource(source)) throw new TypeError('source must be codex or claude.');
49
57
  const reducer = source === 'codex' ? createCodexReducer() : createClaudeReducer();
50
- let lines = 0;
51
58
  let records = 0;
52
59
  let malformed = 0;
53
60
  let tooLarge = false;
61
+ let historyPartial = null;
54
62
  return {
63
+ /** True while every message seen so far is a Claude sidechain message. */
64
+ get sidechainOnly() {
65
+ return reducer.sidechainOnly === true;
66
+ },
55
67
  markTooLarge() {
56
68
  tooLarge = true;
57
69
  },
58
- pushLine(raw) {
70
+ /** A history base could not be followed in full; `warning` names why. */
71
+ markHistoryPartial(warning = IMPORT_WARNING_HISTORY_PARTIAL) {
72
+ historyPartial = warning;
73
+ },
74
+ /** Feed a line for the stored title only (Claude Code's tail window); other sources ignore it. */
75
+ pushTitleLine(raw) {
76
+ let value;
77
+ try {
78
+ value = JSON.parse(String(raw).trim());
79
+ } catch {
80
+ return;
81
+ }
82
+ if (value && typeof value === 'object') reducer.pushTitle?.(value);
83
+ },
84
+ /** `inherited` lines belong to a history base followed ahead of this file's own lines. */
85
+ pushLine(raw, { inherited = false } = {}) {
59
86
  if (tooLarge) return false;
60
87
  let line = String(raw);
61
- if (lines++ === 0 && line.charCodeAt(0) === 0xfeff) line = line.slice(1);
88
+ if (line.charCodeAt(0) === 0xfeff) line = line.slice(1);
62
89
  if (line.endsWith('\r')) line = line.slice(0, -1);
63
90
  if (!line.trim()) return true;
64
91
  let value;
@@ -73,7 +100,7 @@ export function createConversationConverter(
73
100
  return true;
74
101
  }
75
102
  records += 1;
76
- reducer.push(value);
103
+ reducer.push(value, inherited);
77
104
  if (
78
105
  reducer.chars > maxTranscriptChars ||
79
106
  reducer.entries > maxRetainedEntries ||
@@ -90,8 +117,10 @@ export function createConversationConverter(
90
117
  if (transcriptChars(turns) > maxTranscriptChars) tooLarge = true;
91
118
  const warnings = new Set(parsed.warnings);
92
119
  if (malformed) warnings.add(IMPORT_WARNING_MALFORMED);
120
+ if (historyPartial) warnings.add(historyPartial);
93
121
  let errorCode = null;
94
122
  if (tooLarge) errorCode = IMPORT_ERROR_TOO_LARGE;
123
+ else if (parsed.internalKind) errorCode = IMPORT_ERROR_INTERNAL;
95
124
  else if (!records) errorCode = malformed ? 'malformed' : 'empty';
96
125
  else if (!parsed.recognized) errorCode = 'unsupported';
97
126
  else if (!turns.length) errorCode = 'empty';
@@ -100,8 +129,10 @@ export function createConversationConverter(
100
129
  errorCode,
101
130
  foreignSessionId: parsed.foreignSessionId,
102
131
  cwd: parsed.cwd,
132
+ internalKind: parsed.internalKind,
133
+ historyBase: parsed.historyBase ?? null,
103
134
  turns: tooLarge ? [] : turns,
104
- title: conversationTitle(turns),
135
+ title: parsed.storedTitle || conversationTitle(turns),
105
136
  messageCount: tooLarge ? 0 : turns.length,
106
137
  warnings: [...warnings],
107
138
  };
@@ -70,6 +70,9 @@ export function importedTranscriptMessages(turns, importedAt) {
70
70
  content: turn.text,
71
71
  meta: {
72
72
  source: IMPORTED_MESSAGE_SOURCE,
73
+ // Where the text holds a run of tool use, and which tools: the desktop
74
+ // draws each run as a native activity row.
75
+ ...(turn.toolRuns?.length ? { importedToolRuns: turn.toolRuns } : {}),
73
76
  transcript: { messageId: newTranscriptMessageId(), at: turn.at || importedAt },
74
77
  },
75
78
  }));
@@ -9,6 +9,18 @@
9
9
  export const IMPORT_WARNING_IMAGE_OMITTED = 'imageOmitted';
10
10
  export const IMPORT_WARNING_MALFORMED = 'malformed';
11
11
  export const IMPORT_WARNING_CWD_MISSING = 'cwdMissing';
12
+ /** The conversation continues a history stored in another file that was not followed. */
13
+ export const IMPORT_WARNING_HISTORY_PARTIAL = 'historyPartial';
14
+ /** Earlier history was left out because following it would pass the import size limit. */
15
+ export const IMPORT_WARNING_HISTORY_SIZE_LIMIT = 'historySizeLimit';
16
+ /** Earlier history was left out because its file is missing or could not be read. */
17
+ export const IMPORT_WARNING_HISTORY_UNAVAILABLE = 'historyUnavailable';
18
+
19
+ /** A compressed rollout needs a Node.js runtime with zstd; used as the failure code and as the warning of an unreadable base. */
20
+ export const IMPORT_ERROR_NEEDS_NEWER_NODE = 'needsNewerNode';
21
+
22
+ /** Failure code of a record the source app writes for its own use (not a conversation). */
23
+ export const IMPORT_ERROR_INTERNAL = 'internalRecord';
12
24
 
13
25
  /** Stands in for an image the importer does not carry over. */
14
26
  export const IMAGE_OMITTED_TEXT = '[image omitted]';
@@ -17,14 +29,54 @@ export const IMPORTED_USER_STUB_TEXT = '[Imported conversation continues from an
17
29
 
18
30
  const TITLE_MAX_CHARS = 100;
19
31
 
20
- /** Plain-text note for a tool call: its name only, never its arguments. */
32
+ /** Note for a tool call: its name only, never its arguments. A run of notes
33
+ * becomes one compact line in the text and one structured entry in `toolRuns`. */
21
34
  export function toolNote(name) {
22
- const clean = String(name || '')
23
- .replace(/\s+/g, ' ')
24
- .trim();
25
- return { text: `[ran tool: ${clean || 'unknown'}]`, note: true };
35
+ const clean =
36
+ String(name || '')
37
+ .replace(/\s+/g, ' ')
38
+ .trim() || 'unknown';
39
+ return { text: `[ran tool: ${clean}]`, note: true, tool: clean };
40
+ }
41
+
42
+ /** Longest model-facing run line, and the longest tool name shown in it. */
43
+ const TOOL_RUN_LINE_MAX_CHARS = 400;
44
+ const TOOL_RUN_NAME_MAX_CHARS = 40;
45
+
46
+ /**
47
+ * Model-facing line for one run of tool notes: `[ran tools: shell ×6, Read ×2]`.
48
+ * Bounded: names are clipped, and tools that do not fit are counted as
49
+ * `, +N more`. Deterministic in `tools`, so a stored line can be re-derived.
50
+ */
51
+ export function toolRunLine(tools) {
52
+ const entries = tools.map(({ name, count }) => {
53
+ const chars = Array.from(String(name));
54
+ const shown =
55
+ chars.length > TOOL_RUN_NAME_MAX_CHARS ? `${chars.slice(0, TOOL_RUN_NAME_MAX_CHARS - 1).join('')}…` : name;
56
+ return `${shown} ×${count}`;
57
+ });
58
+ const omittedSuffix = (omitted) => (omitted > 0 ? `, +${omitted} more` : '');
59
+ let body = '';
60
+ let shownCount = 0;
61
+ for (const entry of entries) {
62
+ const next = shownCount ? `${body}, ${entry}` : entry;
63
+ const length = `[ran tools: ${next}${omittedSuffix(entries.length - shownCount - 1)}]`.length;
64
+ if (shownCount && length > TOOL_RUN_LINE_MAX_CHARS) break;
65
+ body = next;
66
+ shownCount += 1;
67
+ }
68
+ return `[ran tools: ${body}${omittedSuffix(entries.length - shownCount)}]`;
69
+ }
70
+
71
+ function toolRunCounts(notes) {
72
+ const counts = new Map();
73
+ for (const note of notes) counts.set(note.tool, (counts.get(note.tool) || 0) + 1);
74
+ return [...counts].map(([name, count]) => ({ name, count }));
26
75
  }
27
76
 
77
+ /** A trimmed non-blank string, else null. */
78
+ export const stringOrNull = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
79
+
28
80
  export function textPart(text) {
29
81
  return { text: String(text), note: false };
30
82
  }
@@ -42,18 +94,31 @@ export function timestampMs(value) {
42
94
  return Number.isFinite(ms) && ms > 0 ? ms : null;
43
95
  }
44
96
 
45
- // Consecutive tool notes stay one per line; every other boundary is a blank line.
97
+ // Each run of consecutive tool notes collapses into one compact line; every
98
+ // boundary is a blank line. `toolRuns` locates those lines in `text`.
46
99
  function joinParts(parts) {
47
- let text = '';
48
- let previous = null;
100
+ const blocks = [];
49
101
  for (const part of parts) {
50
102
  const piece = String(part.text || '').trim();
51
103
  if (!piece) continue;
52
- if (previous) text += previous.note && part.note ? '\n' : '\n\n';
53
- text += piece;
54
- previous = part;
104
+ const last = blocks[blocks.length - 1];
105
+ if (part.note && last?.notes) last.notes.push(part);
106
+ else blocks.push(part.note ? { notes: [part] } : { text: piece });
107
+ }
108
+ let text = '';
109
+ const toolRuns = [];
110
+ for (const block of blocks) {
111
+ if (text) text += '\n\n';
112
+ if (block.notes) {
113
+ const tools = toolRunCounts(block.notes);
114
+ const line = toolRunLine(tools);
115
+ toolRuns.push({ start: text.length, end: text.length + line.length, tools });
116
+ text += line;
117
+ } else {
118
+ text += block.text;
119
+ }
55
120
  }
56
- return { text, last: previous };
121
+ return { text, toolRuns };
57
122
  }
58
123
 
59
124
  /**
@@ -72,9 +137,15 @@ export function normalizeTurns(rawTurns) {
72
137
  else merged.push({ role: raw.role, parts: [...parts], at: timestampMs(raw.at) });
73
138
  }
74
139
  const turns = merged.map(({ role, parts, at }) => {
75
- const turn = { role, text: joinParts(parts).text, at };
76
- // A carried-over compaction summary never names the conversation.
77
- if (parts.some((part) => part.summary)) turn.titleText = joinParts(parts.filter((part) => !part.summary)).text;
140
+ const joined = joinParts(parts);
141
+ const turn = { role, text: joined.text, at };
142
+ if (joined.toolRuns.length) turn.toolRuns = joined.toolRuns;
143
+ // A carried-over compaction summary never names the conversation; a slash
144
+ // command only when no typed prompt does.
145
+ if (parts.some((part) => part.summary || part.command)) {
146
+ turn.titleText = joinParts(parts.filter((part) => !part.summary && !part.command)).text;
147
+ }
148
+ if (parts.some((part) => part.command)) turn.commandText = joinParts(parts.filter((part) => part.command)).text;
78
149
  return turn;
79
150
  });
80
151
  if (turns[0]?.role === 'assistant') {
@@ -83,19 +154,26 @@ export function normalizeTurns(rawTurns) {
83
154
  return turns;
84
155
  }
85
156
 
86
- const titleLines = (turn) =>
87
- (turn.titleText ?? turn.text)
157
+ const textLines = (text) =>
158
+ String(text ?? '')
88
159
  .split('\n')
89
160
  .map((value) => value.replace(/\s+/g, ' ').trim())
90
161
  .filter(Boolean);
91
162
 
163
+ const titleLines = (turn) => textLines(turn.titleText ?? turn.text);
164
+
165
+ /** First non-empty line of a title text, whitespace-normalized, ≤100 chars. */
166
+ export function titleFromText(text) {
167
+ const line = textLines(text)[0];
168
+ if (!line) return null;
169
+ return Array.from(line).slice(0, TITLE_MAX_CHARS).join('').trim() || null;
170
+ }
171
+
92
172
  /** First line of the first genuine user turn, whitespace-normalized, ≤100 chars
93
- * (an image marker only when the turn has no text line). */
173
+ * (an image marker only when the turn has no text line), else the first slash command. */
94
174
  export function conversationTitle(turns) {
95
175
  const first = turns.find((turn) => turn.role === 'user' && !turn.stub && titleLines(turn).length);
96
- if (!first) return null;
176
+ if (!first) return titleFromText(turns.find((turn) => turn.role === 'user' && turn.commandText)?.commandText);
97
177
  const lines = titleLines(first);
98
- const line = lines.find((value) => value !== IMAGE_OMITTED_TEXT) || lines[0];
99
- if (!line) return null;
100
- return Array.from(line).slice(0, TITLE_MAX_CHARS).join('').trim() || null;
178
+ return titleFromText(lines.find((value) => value !== IMAGE_OMITTED_TEXT) || lines[0]);
101
179
  }
@@ -39,9 +39,10 @@ export function createEagerDispatcher({
39
39
  repeatFailLimit,
40
40
  });
41
41
  const barriers = createEagerBarriers({ cwd });
42
- // A capped turn runs its calls in order, so none past its cap starts early
43
- // (loop/turn-limits.mjs).
44
- const capped = turnLimitsOf(opts) !== null;
42
+ // A turn capped by calls or time runs its calls in order, so none past its
43
+ // cap starts early (loop/turn-limits.mjs).
44
+ const limits = turnLimitsOf(opts);
45
+ const capped = limits !== null && (limits.maxToolCalls !== Infinity || limits.wrapUpAt !== Infinity);
45
46
  const execute = (call, entry) =>
46
47
  executeToolFn(call.name, call.arguments, cwd, sessionId, sessionRef, {
47
48
  toolCallId: call.id,
@@ -7,12 +7,19 @@
7
7
  // secret — the user types a value into a target field; the request names
8
8
  // the target and the field label, never a value;
9
9
  // takeover — the user does a step themselves and says "done";
10
- // permission — the user picks one of the offered scopes.
10
+ // permission — the user picks one of the offered scopes;
11
+ // connector — a bot asks for more authority on one connector tool of its
12
+ // own: the card names the tool, its current level and the one
13
+ // asked for, and the person picks the level (`scope`: one of
14
+ // `choices`) or denies. Answered once, never remembered.
11
15
  // A response is one of HUMAN_RESPONSES plus a `remember` scope; a policy lists
12
16
  // the scopes it allows and can leave out `always`. Nothing here ever carries a
13
17
  // secret value: a decision that names one is stripped to its verdict.
14
18
 
15
- const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission']);
19
+ const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission', 'connector']);
20
+ /** The levels a connector card may raise a tool to. */
21
+ export const CONNECTOR_WIDEN_LEVELS = Object.freeze(['needs_approval', 'auto']);
22
+ const CONNECTOR_LEVELS = ['disabled', ...CONNECTOR_WIDEN_LEVELS];
16
23
  const HUMAN_RESPONSES = Object.freeze(['approve', 'deny', 'edit-and-approve', 'provide-secret', 'skip', 'done']);
17
24
  const REMEMBER_SCOPES = Object.freeze(['once', 'agent', 'always']);
18
25
  export const SECRET_REQUEST_TIMEOUT_MS = 120_000;
@@ -36,6 +43,7 @@ const RESPONSES_BY_KIND = Object.freeze({
36
43
  secret: ['provide-secret', 'skip'],
37
44
  takeover: ['done', 'deny'],
38
45
  permission: ['approve', 'deny'],
46
+ connector: ['approve', 'deny'],
39
47
  });
40
48
  const GRANTING_RESPONSES = new Set(['approve', 'edit-and-approve', 'provide-secret', 'done']);
41
49
  const TEXT_LIMIT = 200;
@@ -89,12 +97,15 @@ function rememberScopes(value) {
89
97
  return ['once', ...new Set(listed.filter((scope) => scope !== 'once'))];
90
98
  }
91
99
 
92
- /** The kind-specific fields of a request, whitelisted. Spread onto the
93
- * presented request; `args` is replaced for a secret so no value rides it. */
100
+ /** The kind-specific fields of a request, whitelisted, plus the connector
101
+ * `account` a tool call is bound to (any kind; the card names it). Spread
102
+ * onto the presented request; `args` is replaced for a secret so no value
103
+ * rides it. */
94
104
  export function humanRequestFields(input = {}) {
105
+ const bound = text(input?.account) ? { account: text(input.account) } : {};
95
106
  const kind = input?.kind;
96
- if (!HUMAN_REQUEST_KINDS.includes(kind)) return {};
97
- const fields = { remember: rememberScopes(input.remember) };
107
+ if (!HUMAN_REQUEST_KINDS.includes(kind)) return bound;
108
+ const fields = { ...bound, remember: rememberScopes(input.remember) };
98
109
  // The agent the request acts for (its name on the card and the remembered grant).
99
110
  if (text(input.agentId)) fields.agentId = text(input.agentId);
100
111
  if (kind === 'outbound' && isRecord(input.outbound)) {
@@ -187,6 +198,20 @@ export function humanRequestFields(input = {}) {
187
198
  })),
188
199
  };
189
200
  }
201
+ if (kind === 'connector') {
202
+ const connector = isRecord(input.connector) ? input.connector : {};
203
+ const level = (value) => (CONNECTOR_LEVELS.includes(value) ? value : '');
204
+ fields.connector = {
205
+ server: text(connector.server),
206
+ tool: text(connector.tool),
207
+ from: level(connector.from),
208
+ requested: level(connector.requested),
209
+ choices: (Array.isArray(connector.choices) ? connector.choices : []).filter((choice) =>
210
+ CONNECTOR_WIDEN_LEVELS.includes(choice)
211
+ ),
212
+ };
213
+ fields.remember = ['once'];
214
+ }
190
215
  return fields;
191
216
  }
192
217
 
@@ -226,6 +251,11 @@ export function normalizeHumanResponse(request, approved, extra = {}) {
226
251
  const ids = (request?.permission?.scopes || []).map((scope) => scope.id);
227
252
  if (ids.includes(extra?.scope)) result.scope = extra.scope;
228
253
  }
254
+ // The level the person picked; none of the offered ones picks nothing.
255
+ if (kind === 'connector' && polarity) {
256
+ const choices = Array.isArray(request?.connector?.choices) ? request.connector.choices : [];
257
+ if (choices.includes(extra?.scope)) result.scope = extra.scope;
258
+ }
229
259
  return result;
230
260
  }
231
261
 
@@ -16,6 +16,9 @@ import { CONTEXT_SHARE_RATIO, COMPACT_TARGET_MIN_TOKENS, COMPACT_SAFETY_PERCENT
16
16
  import { HANDOFF_TIMEOUT_MAX_MS } from '../compact/constants.mjs';
17
17
  import { envFlag, envPositiveInt } from '../../../../shared/env.mjs';
18
18
  import { positiveInt } from '../../../../shared/numbers.mjs';
19
+ import { cacheWarmUseFor, lastCacheWarmUseAt } from '../../../../shared/idle-auto-clear-gate.mjs';
20
+ import { ACCOUNT_PROVIDERS } from '../../../../shared/provider-accounts.mjs';
21
+ import { plannedAccountId } from '../../providers/account-pool.mjs';
19
22
  import { isAgentOwner } from '../../agent-owner.mjs';
20
23
  import { isAgentModeSession } from '../agent-mode.mjs';
21
24
  import { providerInputExcludesCache, providerNativePdf } from '../../providers/registry.mjs';
@@ -703,13 +706,40 @@ export function shouldCompactForRequestMedia(messages, provider = '') {
703
706
  // Anthropic's 5m message-tail TTL (agent-runtime/cache-strategy.mjs gives it
704
707
  // to agent sessions). A send this long after the previous one finds the tail
705
708
  // cache expired and would rewrite the whole accumulated transcript; compacting
706
- // first makes that cold write the compacted transcript instead.
709
+ // first makes that cold write the compacted transcript instead. The tail was
710
+ // last used by the session's own send or by a successful cache warm
711
+ // (cache-warm.mjs), which reads and refreshes it without moving
712
+ // lastProviderSendAt: a warmed tail has not expired — for a request on the
713
+ // warm's provider, model and account only, since the entry lives there.
707
714
  const AGENT_MESSAGE_CACHE_TTL_MS = 5 * 60_000;
708
715
 
709
- export function shouldCompactForExpiredAgentCache(sessionRef, opts) {
716
+ // The account the request would be served by: the roster's first pick for a
717
+ // provider with accounts (account-pool.mjs), '' for one without a roster.
718
+ function requestAccountId(provider, model, sessionRef, opts, messages) {
719
+ if (!ACCOUNT_PROVIDERS.includes(provider)) return '';
720
+ return plannedAccountId(provider, {
721
+ sessionId: opts?.sessionId || sessionRef?.id || null,
722
+ messages,
723
+ providerState: opts?.providerState,
724
+ model,
725
+ });
726
+ }
727
+
728
+ /** Whether the agent session's 5m message tail expired before this request
729
+ * on `provider` / `model` (default: the session's route). */
730
+ export function shouldCompactForExpiredAgentCache(sessionRef, opts, { provider, model, messages = [] } = {}) {
710
731
  if (!isAgentOwner(sessionRef) || opts?.cacheStrategy?.messages !== '5m') return false;
711
732
  const lastSendAt = Number(sessionRef.lastProviderSendAt);
712
- return lastSendAt > 0 && Date.now() - lastSendAt > AGENT_MESSAGE_CACHE_TTL_MS;
733
+ if (!(lastSendAt > 0)) return false;
734
+ const now = Date.now();
735
+ if (now - lastSendAt <= AGENT_MESSAGE_CACHE_TTL_MS) return false;
736
+ // No warm since the last send: the tail expired.
737
+ if (!(lastCacheWarmUseAt(sessionRef.id) > lastSendAt)) return true;
738
+ const route = { provider: provider || sessionRef.provider || '', model: model || sessionRef.model || '' };
739
+ const accountId = requestAccountId(route.provider, route.model, sessionRef, opts, messages);
740
+ if (accountId === null) return true;
741
+ const warmAt = cacheWarmUseFor(sessionRef.id, { ...route, accountId });
742
+ return now - Math.max(lastSendAt, warmAt) > AGENT_MESSAGE_CACHE_TTL_MS;
713
743
  }
714
744
 
715
745
  export function shouldCompactForSession(