mixdog 0.9.152 → 0.9.154

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/LICENSES/browser-import-NOTICE.txt +11 -0
  2. package/NOTICE.md +11 -0
  3. package/README.md +8 -10
  4. package/package.json +19 -3
  5. package/scripts/computer-task-cost-report.mjs +144 -0
  6. package/scripts/run-office-live-tests.mjs +32 -0
  7. package/src/agents/front-worker/AGENT.md +13 -0
  8. package/src/agents/front-worker/agent.json +6 -0
  9. package/src/agents/heavy-worker/AGENT.md +10 -11
  10. package/src/agents/heavy-worker/agent.json +1 -1
  11. package/src/agents/maintainer/AGENT.md +0 -2
  12. package/src/agents/reviewer/AGENT.md +9 -13
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/agents/security/AGENT.md +14 -0
  15. package/src/agents/security/agent.json +6 -0
  16. package/src/agents/worker/AGENT.md +8 -8
  17. package/src/agents/worker/agent.json +1 -1
  18. package/src/agents/writer/AGENT.md +14 -0
  19. package/src/agents/writer/agent.json +6 -0
  20. package/src/rules/agent/00-common.md +4 -7
  21. package/src/rules/lead/01-general.md +0 -3
  22. package/src/rules/shared/10-tool-workflow.md +10 -2
  23. package/src/rules/shared/60-verification.md +2 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
  25. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
  26. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
  27. package/src/runtime/agent/orchestrator/config.mjs +7 -2
  28. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
  29. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -1
  30. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
  31. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
  32. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +25 -2
  33. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -10
  34. package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
  35. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
  36. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +140 -86
  37. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -13
  38. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +72 -4
  39. package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
  40. package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +0 -22
  41. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +11 -23
  43. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +28 -269
  44. package/src/runtime/agent/orchestrator/session/compact.mjs +0 -6
  45. package/src/runtime/agent/orchestrator/session/context-utils.mjs +86 -11
  46. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
  47. package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
  48. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
  49. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
  50. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +80 -18
  51. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
  52. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +3 -0
  53. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
  54. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +80 -21
  55. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +33 -72
  56. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +21 -2
  57. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -51
  58. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
  59. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
  60. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +39 -15
  61. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +14 -4
  62. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +41 -8
  63. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
  64. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +2 -2
  65. package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
  66. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
  67. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
  68. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
  69. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
  70. package/src/runtime/agent/orchestrator/session/store.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
  72. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
  73. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +4 -0
  74. package/src/runtime/attachments/pdf-extract.mjs +3 -0
  75. package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
  76. package/src/runtime/browser-bridge/action-schema.mjs +251 -0
  77. package/src/runtime/browser-bridge/client.mjs +75 -26
  78. package/src/runtime/browser-bridge/tool-defs.mjs +132 -15
  79. package/src/runtime/computer-bridge/action-schema.mjs +653 -0
  80. package/src/runtime/computer-bridge/client.mjs +211 -28
  81. package/src/runtime/computer-bridge/tool-defs.mjs +25 -45
  82. package/src/runtime/office/assurance-benchmark.mjs +358 -0
  83. package/src/runtime/office/assurance.mjs +833 -0
  84. package/src/runtime/office/benchmark.mjs +496 -0
  85. package/src/runtime/office/capabilities.mjs +551 -0
  86. package/src/runtime/office/com-adapter.mjs +283 -0
  87. package/src/runtime/office/content-model.mjs +183 -0
  88. package/src/runtime/office/contract-benchmark.mjs +105 -0
  89. package/src/runtime/office/design-library.mjs +1562 -0
  90. package/src/runtime/office/design-system.mjs +1966 -0
  91. package/src/runtime/office/index.mjs +2413 -0
  92. package/src/runtime/office/journal.mjs +82 -0
  93. package/src/runtime/office/office-com-host.ps1 +3781 -0
  94. package/src/runtime/office/office-com-session-host.ps1 +855 -0
  95. package/src/runtime/office/ooxml-validator.mjs +271 -0
  96. package/src/runtime/office/pagination.mjs +132 -0
  97. package/src/runtime/office/pdf-adapter.mjs +591 -0
  98. package/src/runtime/office/pdf-analysis.mjs +372 -0
  99. package/src/runtime/office/pdf-render.mjs +167 -0
  100. package/src/runtime/office/pdf-security.mjs +63 -0
  101. package/src/runtime/office/polish-benchmark.mjs +190 -0
  102. package/src/runtime/office/portable-ooxml.mjs +1867 -0
  103. package/src/runtime/office/quality-live-benchmark.mjs +486 -0
  104. package/src/runtime/office/quality-pipeline.mjs +136 -0
  105. package/src/runtime/office/tabular.mjs +302 -0
  106. package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
  107. package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
  108. package/src/runtime/office/tool-defs.mjs +113 -0
  109. package/src/runtime/office/visual-diff.mjs +89 -0
  110. package/src/runtime/office/xlsx-assertions.mjs +172 -0
  111. package/src/runtime/office/xlsx-contract.mjs +147 -0
  112. package/src/runtime/shared/agent-route-config.mjs +9 -0
  113. package/src/runtime/shared/tool-execution-contract.mjs +16 -0
  114. package/src/runtime/shared/tool-surface.mjs +9 -0
  115. package/src/session-runtime/context-status.mjs +9 -3
  116. package/src/session-runtime/deferred-tool-delta.mjs +104 -0
  117. package/src/session-runtime/goal-runtime.mjs +617 -197
  118. package/src/session-runtime/lifecycle-api.mjs +50 -8
  119. package/src/session-runtime/provider-request-snapshot.mjs +33 -9
  120. package/src/session-runtime/resource-api.mjs +31 -94
  121. package/src/session-runtime/runtime-core.mjs +78 -26
  122. package/src/session-runtime/session-lifecycle.mjs +34 -12
  123. package/src/session-runtime/session-title.mjs +2 -2
  124. package/src/session-runtime/session-turn-api.mjs +15 -14
  125. package/src/session-runtime/tool-catalog-data.mjs +1 -1
  126. package/src/session-runtime/tool-catalog.mjs +38 -99
  127. package/src/session-runtime/tool-defs.mjs +6 -1
  128. package/src/session-runtime/tool-surface.mjs +18 -8
  129. package/src/session-runtime/workflow-agents-api.mjs +8 -25
  130. package/src/session-runtime/workflow.mjs +8 -6
  131. package/src/standalone/agent-tool/helpers.mjs +2 -2
  132. package/src/standalone/agent-tool/job-views.mjs +3 -1
  133. package/src/standalone/agent-tool/notify.mjs +5 -1
  134. package/src/standalone/agent-tool/render.mjs +3 -0
  135. package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
  136. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  137. package/src/standalone/agent-tool/worker-index.mjs +123 -0
  138. package/src/standalone/agent-tool/worker-rows.mjs +2 -1
  139. package/src/standalone/agent-tool.mjs +57 -10
  140. package/src/standalone/daemon.mjs +105 -6
  141. package/src/standalone/session-runtime-host.mjs +61 -48
  142. package/src/standalone/session-runtime-inline-host.mjs +34 -127
  143. package/src/standalone/session-runtime-worker.mjs +20 -198
  144. package/src/standalone/session-service.mjs +632 -7
  145. package/src/tui/dist/index.mjs +24 -1
  146. package/src/tui/lib/voice-setup.mjs +4 -1
  147. package/src/tui/session/context-state.mjs +2 -13
  148. package/src/tui/session/goal-continuation.mjs +38 -7
  149. package/src/tui/session/session-api-ext.mjs +9 -0
  150. package/src/tui/session/session-api.mjs +15 -1
  151. package/src/tui/session/session-flow.mjs +25 -1
  152. package/src/tui/session/turn.mjs +88 -16
  153. package/src/tui/session-local.mjs +25 -3
  154. package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
  155. package/scripts/generate-voice-runtime-manifest.test.mjs +0 -135
  156. package/scripts/native-binary-arch.test.mjs +0 -111
  157. package/scripts/patch-replay.test.mjs +0 -94
  158. package/src/headless-exec.test.mjs +0 -539
  159. package/src/runtime/agent/orchestrator/agent-trace-timing.test.mjs +0 -57
  160. package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +0 -153
  161. package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +0 -139
  162. package/src/runtime/agent/orchestrator/mcp/features.test.mjs +0 -66
  163. package/src/runtime/agent/orchestrator/mcp/security.test.mjs +0 -80
  164. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +0 -90
  165. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +0 -215
  166. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.test.mjs +0 -165
  167. package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +0 -142
  168. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -153
  169. package/src/runtime/agent/orchestrator/providers/openai-codex-model.test.mjs +0 -24
  170. package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +0 -190
  171. package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +0 -235
  172. package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +0 -719
  173. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.test.mjs +0 -40
  174. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +0 -176
  175. package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +0 -161
  176. package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +0 -115
  177. package/src/runtime/agent/orchestrator/session/evidence-union.test.mjs +0 -228
  178. package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +0 -245
  179. package/src/runtime/agent/orchestrator/session/lossless-tool-output.test.mjs +0 -125
  180. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +0 -325
  181. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +0 -70
  182. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +0 -457
  183. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.test.mjs +0 -117
  184. package/src/runtime/agent/orchestrator/session/reduction-metrics.test.mjs +0 -87
  185. package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +0 -60
  186. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +0 -583
  187. package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +0 -78
  188. package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +0 -43
  189. package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +0 -63
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +0 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +0 -224
  192. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +0 -224
  193. package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +0 -56
  194. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +0 -103
  195. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.test.mjs +0 -30
  196. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +0 -390
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.test.mjs +0 -83
  198. package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +0 -31
  199. package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +0 -106
  200. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.test.mjs +0 -102
  201. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +0 -152
  202. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +0 -24
  203. package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +0 -24
  204. package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +0 -69
  205. package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +0 -71
  206. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +0 -56
  207. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +0 -16
  208. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-top-k.test.mjs +0 -39
  209. package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +0 -153
  210. package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +0 -55
  211. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +0 -88
  212. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +0 -72
  213. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +0 -54
  214. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.test.mjs +0 -20
  215. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +0 -55
  216. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +0 -268
  217. package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +0 -57
  218. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.test.mjs +0 -19
  219. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +0 -104
  220. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +0 -661
  221. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +0 -552
  222. package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +0 -141
  223. package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +0 -57
  224. package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +0 -81
  225. package/src/runtime/agent/orchestrator/tools/spawn-binary-fetcher.test.mjs +0 -52
  226. package/src/runtime/attachments/store.test.mjs +0 -209
  227. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +0 -17
  228. package/src/runtime/channels/lib/webhook/deliveries.test.mjs +0 -18
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.test.mjs +0 -68
  230. package/src/runtime/media/adapters/codex-image.test.mjs +0 -22
  231. package/src/runtime/media/download.test.mjs +0 -52
  232. package/src/runtime/media/renditions.test.mjs +0 -99
  233. package/src/runtime/media/store.test.mjs +0 -159
  234. package/src/runtime/memory/lib/archive-security.test.mjs +0 -128
  235. package/src/runtime/memory/lib/embedding-model-config.test.mjs +0 -33
  236. package/src/runtime/memory/lib/http-wire.test.mjs +0 -15
  237. package/src/runtime/memory/lib/memory-cycle-packets.test.mjs +0 -49
  238. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +0 -78
  239. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +0 -50
  240. package/src/runtime/memory/lib/pg/process.test.mjs +0 -36
  241. package/src/runtime/memory/lib/trace-store.test.mjs +0 -57
  242. package/src/runtime/memory/lib/transcript-ingest.test.mjs +0 -52
  243. package/src/runtime/shared/background-tasks.test.mjs +0 -78
  244. package/src/runtime/shared/bounded-download.test.mjs +0 -41
  245. package/src/runtime/shared/channel-notification-routing.test.mjs +0 -25
  246. package/src/runtime/shared/child-spawn-remote.test.mjs +0 -124
  247. package/src/runtime/shared/remote-intent.test.mjs +0 -40
  248. package/src/runtime/shared/resource-admission.test.mjs +0 -43
  249. package/src/runtime/shared/runtime-root.test.mjs +0 -34
  250. package/src/runtime/shared/schedule-time.test.mjs +0 -29
  251. package/src/runtime/shared/session-runtime-health.test.mjs +0 -83
  252. package/src/runtime/shared/skill-document.test.mjs +0 -81
  253. package/src/runtime/shared/tool-surface.test.mjs +0 -87
  254. package/src/runtime/shared/update-checker.test.mjs +0 -33
  255. package/src/runtime/shared/user-cwd.test.mjs +0 -43
  256. package/src/runtime/shared/webhook-session-run.test.mjs +0 -14
  257. package/src/session-runtime/agent-disable.test.mjs +0 -123
  258. package/src/session-runtime/cwd-plugins.test.mjs +0 -75
  259. package/src/session-runtime/cwd-tool-routing.test.mjs +0 -61
  260. package/src/session-runtime/global-extensions.test.mjs +0 -28
  261. package/src/session-runtime/goal-runtime.test.mjs +0 -108
  262. package/src/session-runtime/lifecycle-api.test.mjs +0 -21
  263. package/src/session-runtime/model-settings-persist.test.mjs +0 -105
  264. package/src/session-runtime/notification-bus.test.mjs +0 -148
  265. package/src/session-runtime/plugin-mcp-standard.test.mjs +0 -177
  266. package/src/session-runtime/prewarm.test.mjs +0 -52
  267. package/src/session-runtime/provider-request-snapshot.test.mjs +0 -37
  268. package/src/session-runtime/resource-api-global.test.mjs +0 -69
  269. package/src/session-runtime/session-lifecycle.test.mjs +0 -125
  270. package/src/session-runtime/session-title.test.mjs +0 -36
  271. package/src/session-runtime/skills-api.test.mjs +0 -99
  272. package/src/session-runtime/tool-policy-surface.test.mjs +0 -331
  273. package/src/standalone/agent-tool/helpers.test.mjs +0 -62
  274. package/src/standalone/agent-tool/notify.test.mjs +0 -37
  275. package/src/standalone/agent-tool/spawn-flow.test.mjs +0 -63
  276. package/src/standalone/channel-restart.test.mjs +0 -145
  277. package/src/standalone/channel-session-router.test.mjs +0 -37
  278. package/src/standalone/channel-worker-heartbeat.test.mjs +0 -30
  279. package/src/standalone/hook-bus/handlers.test.mjs +0 -88
  280. package/src/standalone/plugin-admin.test.mjs +0 -173
  281. package/src/standalone/session-runtime-agent-control-client.test.mjs +0 -71
  282. package/src/standalone/session-runtime-dispatch-cancel.test.mjs +0 -139
  283. package/src/standalone/session-runtime-host-factory.test.mjs +0 -31
  284. package/src/standalone/session-runtime-host-health.test.mjs +0 -160
  285. package/src/standalone/session-runtime-inline-host.test.mjs +0 -83
  286. package/src/standalone/session-runtime-provider-cooldown.test.mjs +0 -80
  287. package/src/standalone/session-runtime-shard-host.test.mjs +0 -569
  288. package/src/standalone/session-runtime-shard-router.test.mjs +0 -101
  289. package/src/tui/app/live-spinner-visibility.test.mjs +0 -44
  290. package/src/tui/app/panel-epoch.test.mjs +0 -1009
  291. package/src/tui/app/panel-handoff.test.mjs +0 -311
  292. package/src/tui/app/prompt-submit.test.mjs +0 -206
  293. package/src/tui/session/completion-card-restore.test.mjs +0 -111
  294. package/src/tui/session/goal-continuation.test.mjs +0 -84
  295. package/src/tui/session/oauth-flows.test.mjs +0 -134
  296. package/src/tui/session/queue-helpers.test.mjs +0 -46
  297. package/src/tui/session/session-action-surface.test.mjs +0 -108
  298. package/src/tui/session/task-wait-submit.test.mjs +0 -80
  299. package/src/tui/session/tool-result-status.test.mjs +0 -131
  300. package/src/tui/session/tui-steering-persist.test.mjs +0 -104
@@ -179,20 +179,128 @@ function maintenanceRouteToPreset(routeOrName, agent) {
179
179
  return out;
180
180
  }
181
181
 
182
+ const AGENT_DISPATCH_LIVE_CALLBACKS = Object.freeze([
183
+ 'onSessionStart',
184
+ 'onStageChange',
185
+ 'onReasoningDelta',
186
+ 'onTextDelta',
187
+ 'onTextReset',
188
+ 'onAssistantText',
189
+ 'onAssistantMessageCommitted',
190
+ 'onToolCall',
191
+ 'onToolResult',
192
+ ]);
193
+
194
+ function pickDispatchCallback(factoryOpts, callArgs, name) {
195
+ if (typeof callArgs?.[name] === 'function') return callArgs[name];
196
+ if (typeof factoryOpts?.[name] === 'function') return factoryOpts[name];
197
+ return undefined;
198
+ }
199
+
182
200
  /**
183
- * Build an agent-backed dispatch callback.
201
+ * Resolve live Agent-dispatch callbacks.
184
202
  *
185
- * @param {object} opts
186
- * @param {string} opts.agent — REQUIRED; canonical agent name (worker, cycle1-agent, ...)
187
- * @param {string} [opts.taskType] — optional internal classification stamped on the session
188
- * @param {string} [opts.preset] — explicit preset override (bypasses agent → preset lookup)
189
- * @param {string} [opts.parentSessionId] — parent agent session for trace aggregation
190
- * @param {string|null} [opts.ownerSessionId] — owning Mixdog session for statusline isolation
191
- * @param {AbortSignal} [opts.parentSignal] — optional AbortSignal from the fan-out coordinator;
192
- * when aborted the agent session's own controller is also aborted so the
193
- * provider call tears down promptly (parent→child cascade).
194
- * @returns {(args: { prompt, preset?, sourceName? }) => Promise<string>}
203
+ * `liveProjection` is a send-opt. It is true when the caller explicitly
204
+ * sets it or supplies a live-text callback (onTextDelta / onAssistantText /
205
+ * onTextReset). Tool/stage/reasoning observers do not un-suppress provider
206
+ * streaming. The flag must never be written onto the session object.
207
+ *
208
+ * @param {object} [factoryOpts]
209
+ * @param {object} [callArgs]
210
+ * @returns {{ callbacks: object, liveProjection: boolean }}
211
+ */
212
+ export function resolveAgentDispatchLiveCallbacks(factoryOpts = {}, callArgs = {}) {
213
+ const callbacks = {};
214
+ for (const name of AGENT_DISPATCH_LIVE_CALLBACKS) {
215
+ const fn = pickDispatchCallback(factoryOpts, callArgs, name);
216
+ if (fn) callbacks[name] = fn;
217
+ }
218
+ const liveProjection = factoryOpts.liveProjection === true
219
+ || callArgs.liveProjection === true
220
+ || typeof callbacks.onTextDelta === 'function'
221
+ || typeof callbacks.onAssistantText === 'function'
222
+ || typeof callbacks.onTextReset === 'function';
223
+ return { callbacks, liveProjection };
224
+ }
225
+
226
+ /**
227
+ * Adapt askSession's session-less callback signatures to the host contract
228
+ * by prepending the prepared session on every call:
229
+ * onSessionStart(session)
230
+ * onStageChange(session, stage, detail)
231
+ * onReasoningDelta(session, chunk)
232
+ * onTextDelta(session, chunk)
233
+ * onTextReset(session, detail) — return value is passed through unchanged
234
+ * onAssistantText(session, text)
235
+ * onAssistantMessageCommitted(session)
236
+ * onToolCall(session, iteration, calls)
237
+ * onToolResult(session, message)
238
+ */
239
+ export function bindAgentDispatchHostCallbacks(hostCallbacks = {}, session) {
240
+ const bound = {};
241
+ if (typeof hostCallbacks.onSessionStart === 'function') {
242
+ bound.onSessionStart = () => hostCallbacks.onSessionStart(session);
243
+ }
244
+ if (typeof hostCallbacks.onStageChange === 'function') {
245
+ bound.onStageChange = (stage, detail) => hostCallbacks.onStageChange(session, stage, detail);
246
+ }
247
+ if (typeof hostCallbacks.onReasoningDelta === 'function') {
248
+ bound.onReasoningDelta = (chunk) => hostCallbacks.onReasoningDelta(session, chunk);
249
+ }
250
+ if (typeof hostCallbacks.onTextDelta === 'function') {
251
+ bound.onTextDelta = (chunk) => hostCallbacks.onTextDelta(session, chunk);
252
+ }
253
+ if (typeof hostCallbacks.onTextReset === 'function') {
254
+ bound.onTextReset = (detail) => hostCallbacks.onTextReset(session, detail);
255
+ }
256
+ if (typeof hostCallbacks.onAssistantText === 'function') {
257
+ bound.onAssistantText = (text) => hostCallbacks.onAssistantText(session, text);
258
+ }
259
+ if (typeof hostCallbacks.onAssistantMessageCommitted === 'function') {
260
+ bound.onAssistantMessageCommitted = () => hostCallbacks.onAssistantMessageCommitted(session);
261
+ }
262
+ if (typeof hostCallbacks.onToolCall === 'function') {
263
+ bound.onToolCall = (iteration, calls) => hostCallbacks.onToolCall(session, iteration, calls);
264
+ }
265
+ if (typeof hostCallbacks.onToolResult === 'function') {
266
+ bound.onToolResult = (message) => hostCallbacks.onToolResult(session, message);
267
+ }
268
+ return bound;
269
+ }
270
+
271
+ /**
272
+ * Build the positional `onToolCall` + askOpts bag forwarded to askSession.
273
+ * `interactiveSessionSurface` is intentionally dropped — that in-memory
274
+ * hint is owned by the standalone surface and must not be persisted or
275
+ * re-homed onto a dispatch session.
195
276
  */
277
+ export function buildAgentDispatchAskSessionArgs(factoryOpts = {}, callArgs = {}, extraAskOpts = {}, session = null) {
278
+ const { callbacks, liveProjection } = resolveAgentDispatchLiveCallbacks(factoryOpts, callArgs);
279
+ const bound = session && typeof session === 'object'
280
+ ? bindAgentDispatchHostCallbacks(callbacks, session)
281
+ : callbacks;
282
+ const onToolCall = typeof bound.onToolCall === 'function' ? bound.onToolCall : null;
283
+ const askOpts = { liveProjection };
284
+ for (const name of AGENT_DISPATCH_LIVE_CALLBACKS) {
285
+ if (name === 'onToolCall') continue;
286
+ if (typeof bound[name] === 'function') askOpts[name] = bound[name];
287
+ }
288
+ const extraCompact = extraAskOpts.onCompactEvent;
289
+ if (typeof extraCompact === 'function') {
290
+ const callerCompact = askOpts.onCompactEvent;
291
+ askOpts.onCompactEvent = (event) => {
292
+ extraCompact(event);
293
+ try { callerCompact?.(event); } catch { /* best-effort */ }
294
+ };
295
+ }
296
+ for (const [key, value] of Object.entries(extraAskOpts)) {
297
+ if (key === 'onCompactEvent' || key === 'liveProjection') continue;
298
+ if (key === 'interactiveSessionSurface') continue;
299
+ if (value !== undefined) askOpts[key] = value;
300
+ }
301
+ return { onToolCall, askOpts };
302
+ }
303
+
196
304
  // runtime-liveness keeps one parent link per session because askSession swaps
197
305
  // its controller at turn start. Agent dispatch can have several independent
198
306
  // cancellation sources, so collapse them before installing that one link.
@@ -227,16 +335,59 @@ export function composeAgentDispatchAbortSignal(signals) {
227
335
  };
228
336
  }
229
337
 
338
+ /**
339
+ * Build an agent-backed dispatch callback.
340
+ *
341
+ * @param {object} opts
342
+ * @param {string} opts.agent — REQUIRED; canonical agent name (worker, cycle1-agent, ...)
343
+ * @param {string} [opts.taskType] — optional internal classification stamped on the session
344
+ * @param {string} [opts.preset] — explicit preset override (bypasses agent → preset lookup)
345
+ * @param {string} [opts.parentSessionId] — parent agent session for trace aggregation
346
+ * @param {string|null} [opts.ownerSessionId] — owning Mixdog session for statusline isolation
347
+ * @param {AbortSignal} [opts.parentSignal] — optional AbortSignal from the fan-out coordinator;
348
+ * when aborted the agent session's own controller is also aborted so the
349
+ * provider call tears down promptly (parent→child cascade).
350
+ * @param {boolean} [opts.liveProjection] — request provider onTextDelta / mid-turn
351
+ * text for this Agent. Never persisted on the session.
352
+ * @param {function} [opts.onSessionStart]
353
+ * @param {function} [opts.onStageChange]
354
+ * @param {function} [opts.onReasoningDelta]
355
+ * @param {function} [opts.onTextDelta]
356
+ * @param {function} [opts.onTextReset] — must return `true` to ack a retry retraction
357
+ * @param {function} [opts.onAssistantText]
358
+ * @param {function} [opts.onAssistantMessageCommitted]
359
+ * @param {function} [opts.onToolCall]
360
+ * @param {function} [opts.onToolResult]
361
+ * @returns {(args: { prompt, preset?, sourceName?, liveProjection?, onSessionStart?, onStageChange?, onReasoningDelta?, onTextDelta?, onTextReset?, onAssistantText?, onAssistantMessageCommitted?, onToolCall?, onToolResult? }) => Promise<string>}
362
+ */
230
363
  export function makeAgentDispatch(opts = {}) {
231
364
  if (!opts.agent || typeof opts.agent !== 'string') {
232
365
  throw new Error('[agent-dispatch] opts.agent is required');
233
366
  }
234
367
  const agent = opts.agent;
235
368
 
236
- return async function agentDispatch({ prompt, preset: presetArg, sourceName: sourceNameArg, parentSignal: callParentSignal, idleTimeoutMs: callIdleTimeoutMs, cwd: callCwd, sessionId: callSessionId }) {
369
+ return async function agentDispatch(callArgs = {}) {
370
+ const {
371
+ prompt,
372
+ preset: presetArg,
373
+ sourceName: sourceNameArg,
374
+ parentSignal: callParentSignal,
375
+ idleTimeoutMs: callIdleTimeoutMs,
376
+ cwd: callCwd,
377
+ sessionId: callSessionId,
378
+ } = callArgs;
237
379
  if (typeof prompt !== 'string' || !prompt) {
238
380
  throw new Error(`[agent-dispatch] prompt required for agent "${agent}"`);
239
381
  }
382
+ const prepare = typeof opts.prepareAgentSession === 'function'
383
+ ? opts.prepareAgentSession
384
+ : prepareAgentSession;
385
+ const ask = typeof opts.askSession === 'function' ? opts.askSession : askSession;
386
+ const updateStatus = typeof opts.updateSessionStatus === 'function'
387
+ ? opts.updateSessionStatus
388
+ : updateSessionStatus;
389
+ const close = typeof opts.closeSession === 'function' ? opts.closeSession : closeSession;
390
+ const readSession = typeof opts.getSession === 'function' ? opts.getSession : getSession;
240
391
  const admission = opts.resourceAdmission || resourceAdmission;
241
392
  const admissionAbortLink = composeAgentDispatchAbortSignal([
242
393
  opts.parentSignal,
@@ -334,7 +485,7 @@ export function makeAgentDispatch(opts = {}) {
334
485
  // pooled or resumed. Cache prefix matching happens at the provider
335
486
  // layer (account-level), not the session level.
336
487
  const finalPrompt = prompt;
337
- const { session } = prepareAgentSession({
488
+ const { session } = prepare({
338
489
  agent,
339
490
  presetName,
340
491
  preset,
@@ -359,7 +510,7 @@ export function makeAgentDispatch(opts = {}) {
359
510
  process.stderr.write(`[agent-dispatch] agent=${agent} tool-list (${_toolNames.length}): ${_toolNames.join(',')}\n`);
360
511
  } catch { /* best-effort diagnostic */ }
361
512
 
362
- await updateSessionStatus(session.id, 'running');
513
+ await updateStatus(session.id, 'running');
363
514
  // Parent→child abort cascade: when opts.parentSignal (factory) or
364
515
  // callParentSignal (per-call) fires, abort the sub-session's own
365
516
  // controller so the provider call tears down promptly. Best-effort:
@@ -368,7 +519,7 @@ export function makeAgentDispatch(opts = {}) {
368
519
  const _linkSignal = _managerMod?.linkParentSignalToSession;
369
520
  const _getProgressSnapshot = _managerMod?.getSessionProgressSnapshot;
370
521
  const _getLastProgressAt = _managerMod?.getSessionLastProgressAt;
371
- const _getSession = _managerMod?.getSession || getSession;
522
+ const _getSession = _managerMod?.getSession || readSession;
372
523
  // Watchdog policy is split:
373
524
  // - firstResponseTimeoutMs cuts quickly only when the model produces no
374
525
  // first stream/tool activity at all.
@@ -442,12 +593,13 @@ export function makeAgentDispatch(opts = {}) {
442
593
  : null;
443
594
  if (_idleTimer && typeof _idleTimer.unref === 'function') _idleTimer.unref();
444
595
  let terminalStatus = 'idle';
596
+ let closeReason = 'ephemeral-done';
445
597
  process.stderr.write(`[agent-dispatch] agent=${agent} preset=${presetName} model=${preset.model} provider=${preset.provider} session=${session.id}\n`);
446
598
  const _agentDispatchT0 = Date.now();
447
599
  let _handoffMsgStart = 0;
448
600
  try {
449
- _handoffMsgStart = resolveHandoffMessageStartIndex(getSession(session.id));
450
- const result = await askSession(session.id, finalPrompt, null, null, cwd, undefined, {
601
+ _handoffMsgStart = resolveHandoffMessageStartIndex(readSession(session.id));
602
+ const { onToolCall, askOpts } = buildAgentDispatchAskSessionArgs(opts, callArgs, {
451
603
  onCompactEvent: (event) => {
452
604
  try {
453
605
  const label = agentCompactEventLabel(event);
@@ -458,7 +610,10 @@ export function makeAgentDispatch(opts = {}) {
458
610
  );
459
611
  } catch { /* best-effort compact visibility */ }
460
612
  },
461
- });
613
+ }, session);
614
+ // liveProjection is a send-opt on askOpts only. Do not stamp it
615
+ // (or interactiveSessionSurface) onto `session`.
616
+ const result = await ask(session.id, finalPrompt, null, onToolCall, cwd, undefined, askOpts);
462
617
  process.stderr.write(`[agent-dispatch] agent=${agent} session=${session.id} elapsed=${Date.now() - _agentDispatchT0}ms\n`);
463
618
  const raw = result?.content || '';
464
619
  // Brief cap. Agent role answers (recall/search)
@@ -468,25 +623,23 @@ export function makeAgentDispatch(opts = {}) {
468
623
  // majority of answers untouched. Opt-out via `brief:false` when
469
624
  // the caller explicitly wants the full synthesis.
470
625
  if (opts.brief === false) {
471
- try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
472
626
  return raw;
473
627
  }
474
628
  const out = applyBriefCap(raw);
475
- try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
476
629
  return out;
477
630
  } catch (err) {
478
- const partial = watchdogPartialHandoffFromError(err, getSession(session.id), _handoffMsgStart)
631
+ terminalStatus = 'error';
632
+ closeReason = 'ephemeral-error';
633
+ const partial = watchdogPartialHandoffFromError(err, readSession(session.id), _handoffMsgStart)
479
634
  ?? (salvagePartialRequested(err, _abortLink?.signal)
480
- ? partialHandoffTextFromSession(getSession(session.id), _handoffMsgStart)
635
+ ? partialHandoffTextFromSession(readSession(session.id), _handoffMsgStart)
481
636
  : null);
482
637
  if (partial) {
483
638
  terminalStatus = 'idle';
484
- try { closeSession(session.id, 'ephemeral-done'); } catch { /* ignore */ }
639
+ closeReason = 'ephemeral-done';
485
640
  if (opts.brief === false) return partial;
486
641
  return applyBriefCap(partial);
487
642
  }
488
- terminalStatus = 'error';
489
- try { closeSession(session.id, 'ephemeral-error'); } catch { /* ignore */ }
490
643
  throw err;
491
644
  } finally {
492
645
  _abortLink.dispose();
@@ -496,7 +649,11 @@ export function makeAgentDispatch(opts = {}) {
496
649
  // Always flip out of 'running' before returning so the sweep never
497
650
  // leaves a stateless Pool C session stuck in 'running' when the
498
651
  // try/catch falls through in unexpected ways.
499
- try { await updateSessionStatus(session.id, terminalStatus); } catch { /* ignore */ }
652
+ try { await updateStatus(session.id, terminalStatus); } catch { /* ignore */ }
653
+ // closeSession plants a tombstone, after which status writes are
654
+ // rejected. Publish the terminal projection before closing so
655
+ // live Agent panes cannot retain their preceding busy:true state.
656
+ try { close(session.id, closeReason); } catch { /* ignore */ }
500
657
  }
501
658
  });
502
659
  } finally {
@@ -59,6 +59,7 @@ function normalizeAgentCompactionConfig(value = {}) {
59
59
  * @param {number} [opts.maxLoopIterations]
60
60
  * @param {string} [opts.parentSessionId]
61
61
  * @param {string|null} [opts.ownerSessionId] - owning Mixdog MCP instance id for statusline isolation
62
+ * @param {string|null} [opts.visibility] - catalog visibility scope
62
63
  * @param {string|null} [opts.mcpScopeId] - owning session-runtime MCP registry scope
63
64
  * @returns {{ session: object, effectiveCwd: string|null }}
64
65
  */
@@ -77,6 +78,7 @@ export function prepareAgentSession({
77
78
  maxLoopIterations,
78
79
  parentSessionId,
79
80
  ownerSessionId,
81
+ visibility,
80
82
  clientHostPid,
81
83
  agentTag,
82
84
  cacheKeyOverride,
@@ -115,7 +117,9 @@ export function prepareAgentSession({
115
117
  maxLoopIterations: Number.isFinite(effectiveMaxLoopIterations) ? effectiveMaxLoopIterations : undefined,
116
118
  sourceType: sourceType || undefined,
117
119
  sourceName: sourceName || undefined,
120
+ parentSessionId: parentSessionId || null,
118
121
  ownerSessionId: effectiveOwnerSessionId || null,
122
+ visibility: visibility || null,
119
123
  clientHostPid: clientHostPid || null,
120
124
  compaction: compaction || undefined,
121
125
  mcpScopeId: mcpScopeId || null,
@@ -0,0 +1,109 @@
1
+ import { appendAgentTrace } from './agent-trace.mjs';
2
+
3
+ const HASH_CHARS = 16;
4
+
5
+ function cleanText(value, max = 120) {
6
+ if (value == null || value === '') return null;
7
+ return String(value).replace(/[\r\n\t]+/g, ' ').slice(0, max);
8
+ }
9
+
10
+ function cleanHash(value) {
11
+ const text = cleanText(value, 128);
12
+ return text ? text.slice(0, HASH_CHARS) : null;
13
+ }
14
+
15
+ function finiteNumber(value) {
16
+ const number = Number(value);
17
+ return Number.isFinite(number) ? number : null;
18
+ }
19
+
20
+ export function buildCacheBreakPayload(record = {}) {
21
+ const payload = {
22
+ classification: cleanText(record.classification) || 'unknown',
23
+ reason: cleanText(record.reason) || 'unknown',
24
+ source: cleanText(record.source),
25
+ provider: cleanText(record.provider),
26
+ model: cleanText(record.model),
27
+ transport: cleanText(record.transport),
28
+ intentional_transition: cleanText(record.intentionalTransition),
29
+ index: finiteNumber(record.index),
30
+ previous_count: finiteNumber(record.previousCount),
31
+ next_count: finiteNumber(record.nextCount),
32
+ previous_hash: cleanHash(record.previousHash),
33
+ next_hash: cleanHash(record.nextHash),
34
+ previous_request_prefix_hash: cleanHash(record.previousRequestPrefixHash),
35
+ next_request_prefix_hash: cleanHash(record.nextRequestPrefixHash),
36
+ request_prefix_changed: record.requestPrefixChanged === true,
37
+ cached_tokens: finiteNumber(record.cachedTokens),
38
+ prompt_tokens: finiteNumber(record.promptTokens),
39
+ uncached_tokens: finiteNumber(record.uncachedTokens),
40
+ cache_ratio: finiteNumber(record.cacheRatio),
41
+ actual_cache_miss: record.actualCacheMiss === true,
42
+ input_prefix_mismatch_index: finiteNumber(record.inputPrefixMismatchIndex),
43
+ input_prefix_mismatch_previous_count: finiteNumber(record.inputPrefixMismatchPreviousCount),
44
+ input_prefix_mismatch_current_count: finiteNumber(record.inputPrefixMismatchCurrentCount),
45
+ input_prefix_mismatch_expected_type: cleanText(record.inputPrefixMismatchExpectedType),
46
+ input_prefix_mismatch_expected_hash: cleanHash(record.inputPrefixMismatchExpectedHash),
47
+ input_prefix_mismatch_actual_type: cleanText(record.inputPrefixMismatchActualType),
48
+ input_prefix_mismatch_actual_hash: cleanHash(record.inputPrefixMismatchActualHash),
49
+ };
50
+ return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== null));
51
+ }
52
+
53
+ export function formatCacheBreakLine(record = {}) {
54
+ const payload = buildCacheBreakPayload(record);
55
+ const fields = [
56
+ ['classification', payload.classification],
57
+ ['reason', payload.reason],
58
+ ['source', payload.source],
59
+ ['session', cleanText(record.sessionId, 160)],
60
+ ['provider', payload.provider],
61
+ ['model', payload.model],
62
+ ['iteration', finiteNumber(record.iteration)],
63
+ ['index', payload.index],
64
+ ['previousCount', payload.previous_count],
65
+ ['nextCount', payload.next_count],
66
+ ['previousHash', payload.previous_hash],
67
+ ['nextHash', payload.next_hash],
68
+ ['inputMismatchIndex', payload.input_prefix_mismatch_index],
69
+ ['intent', payload.intentional_transition],
70
+ ['cachedTokens', payload.cached_tokens],
71
+ ['promptTokens', payload.prompt_tokens],
72
+ ].filter(([, value]) => value !== null && value !== undefined);
73
+ return `[cache-break] ${fields.map(([key, value]) => (
74
+ typeof value === 'string' ? `${key}=${JSON.stringify(value)}` : `${key}=${value}`
75
+ )).join(' ')}`;
76
+ }
77
+
78
+ export function traceCacheBreak(record = {}, options = {}) {
79
+ const payload = buildCacheBreakPayload(record);
80
+ const traceFn = options.traceFn === null
81
+ ? null
82
+ : (typeof options.traceFn === 'function' ? options.traceFn : appendAgentTrace);
83
+ if (traceFn) {
84
+ try {
85
+ traceFn({
86
+ sessionId: record.sessionId || null,
87
+ iteration: finiteNumber(record.iteration),
88
+ kind: 'cache_break',
89
+ classification: payload.classification,
90
+ reason: payload.reason,
91
+ source: payload.source || null,
92
+ provider: payload.provider || null,
93
+ model: payload.model || null,
94
+ transport: payload.transport || null,
95
+ payload,
96
+ });
97
+ } catch { /* cache diagnostics never affect provider execution */ }
98
+ }
99
+ const explicitStderr = typeof options.stderrFn === 'function';
100
+ if (options.stderr !== false && (explicitStderr || !process.env.NODE_TEST_CONTEXT)) {
101
+ try {
102
+ const write = explicitStderr
103
+ ? options.stderrFn
104
+ : (line) => process.stderr.write(line);
105
+ write(`${formatCacheBreakLine(record)}\n`);
106
+ } catch { /* cache diagnostics never affect provider execution */ }
107
+ }
108
+ return payload;
109
+ }
@@ -1,6 +1,7 @@
1
1
  import { resolvePluginData } from '../../shared/plugin-paths.mjs';
2
2
  import { readSection, updateSection, updateSectionAsync, getAgentApiKey, AGENT_PROVIDER_ENV } from '../../shared/config.mjs';
3
3
  import {
4
+ DEFAULT_DISABLED_AGENT_IDS,
4
5
  agentRouteStorageNeedsMigration,
5
6
  canonicalizeAgentRouteStorage,
6
7
  } from '../../shared/agent-route-config.mjs';
@@ -147,7 +148,7 @@ export const DEFAULT_PRESETS = Object.freeze([
147
148
  Object.freeze({ id: 'opus-mid', name: 'OPUS MID', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'medium', tools: 'full' }),
148
149
  Object.freeze({ id: 'opus-high', name: 'OPUS HIGH', type: 'agent', provider: 'anthropic-oauth', model: resolveAnthropicFamilyModel('opus'), effort: 'high', tools: 'full' }),
149
150
  ]);
150
- function buildDefaultConfig(options = {}) {
151
+ export function buildDefaultConfig(options = {}) {
151
152
  const detectCredentials = options.detectCredentials !== false;
152
153
  const providers = {};
153
154
  // API providers — enabled if env key exists
@@ -196,7 +197,11 @@ function buildDefaultConfig(options = {}) {
196
197
  // Local providers — opt-in via setup UI after HTTP ping confirms server is running
197
198
  providers.ollama = { enabled: false, baseURL: 'http://localhost:11434/v1' };
198
199
  providers.lmstudio = { enabled: false, baseURL: 'http://localhost:1234/v1' };
199
- return { providers, workflow: { active: 'default' } };
200
+ return {
201
+ providers,
202
+ disabledAgents: [...DEFAULT_DISABLED_AGENT_IDS],
203
+ workflow: { active: 'default' },
204
+ };
200
205
  }
201
206
 
202
207
  function hasKeys(value) {
@@ -201,7 +201,12 @@ export class ProviderAdmissionScheduler {
201
201
  // owns a slot, so reacquiring the same lane could deadlock a full wave.
202
202
  const current = this.context.getStore();
203
203
  if (current?.scheduler === this && current.key === laneKey) {
204
- return Promise.resolve().then(() => task(signal));
204
+ return Promise.resolve().then(() => task(signal, {
205
+ queueWaitMs: 0,
206
+ active: current.lane.active,
207
+ queued: current.lane.queue.length,
208
+ limit: Number.isFinite(current.lane.limit) ? current.lane.limit : null,
209
+ }));
205
210
  }
206
211
  if (this.closedReason) return Promise.reject(this.closedReason);
207
212
  if (signal?.aborted) return Promise.reject(abortError(signal));
@@ -323,7 +328,14 @@ export class ProviderAdmissionScheduler {
323
328
  priority: item.priority,
324
329
  rateLimited: false,
325
330
  };
326
- this.context.run(admission, () => Promise.resolve().then(() => item.task(controller.signal)))
331
+ const admissionMetrics = {
332
+ queueWaitMs: Math.max(0, this.now() - item.queuedAt),
333
+ active: lane.active,
334
+ queued: lane.queue.length,
335
+ limit: Number.isFinite(lane.limit) ? lane.limit : null,
336
+ };
337
+ this.context.run(admission, () => Promise.resolve().then(() =>
338
+ item.task(controller.signal, admissionMetrics)))
327
339
  .then((value) => {
328
340
  // A retry that eventually succeeds is not evidence that
329
341
  // the lane should begin recovery from the 429 it just saw.
@@ -582,7 +594,7 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
582
594
  // resource controller reacquires the agent lease before model output
583
595
  // continues through local context/tool processing.
584
596
  return resourceAdmission.runYielded(() =>
585
- scheduler.run(key, (admissionSignal) => {
597
+ scheduler.run(key, (admissionSignal, admissionMetrics) => {
586
598
  // Admission is the common request-clock boundary for WS/SSE/HTTP.
587
599
  // Queue wait therefore cannot consume first-byte or agent-watchdog
588
600
  // time. Provider-local retry remains the sole retry owner.
@@ -590,6 +602,7 @@ export function wrapProviderAdmission(provider, providerName, scheduler = provid
590
602
  return originalSend.call(this, messages, model, tools, {
591
603
  ...opts,
592
604
  signal: admissionSignal,
605
+ _providerAdmission: admissionMetrics,
593
606
  });
594
607
  }, {
595
608
  signal,
@@ -325,7 +325,15 @@ export function deferredAnthropicTools(activeTools, messages, opts, provider) {
325
325
  if (key) discovered.add(key);
326
326
  }
327
327
  }
328
- const catalog = Array.isArray(opts.session.deferredToolCatalog) ? opts.session.deferredToolCatalog : [];
328
+ const catalogByName = new Map();
329
+ for (const tool of [
330
+ ...(Array.isArray(opts.session.deferredToolCatalog) ? opts.session.deferredToolCatalog : []),
331
+ ...(Array.isArray(opts.session.deferredLateToolCatalog) ? opts.session.deferredLateToolCatalog : []),
332
+ ]) {
333
+ const name = String(tool?.name || '').trim();
334
+ if (name) catalogByName.set(name, tool);
335
+ }
336
+ const catalog = [...catalogByName.values()];
329
337
  return catalog
330
338
  .filter((tool) => tool?.name && discovered.has(String(tool.name)) && !active.has(String(tool.name)))
331
339
  .map((tool) => ({ ...tool, deferLoading: true }));
@@ -35,11 +35,25 @@ function geminiInlineInfo(block) {
35
35
  return { data, mimeType: cleanMimeType(inline.mimeType || inline.mime_type || inline.mediaType || inline.media_type) };
36
36
  }
37
37
 
38
- // Inline document/file part ({ type: 'file', data, mimeType, filename? }) as
39
- // submitted by the desktop composer (PDF attachments). Providers receive it
40
- // as their native document shape; estimators must never serialize its base64.
38
+ // Inline document/file parts arrive in two canonical runtime shapes:
39
+ // - desktop attachments: { type: 'file', data, mimeType, filename? }
40
+ // - read(PDF): { type: 'document', source: { type: 'base64', media_type, data } }
41
+ // Providers receive both as their native document shape; estimators and stored
42
+ // history must never serialize either base64 payload as text.
41
43
  function fileInfo(block) {
42
- if (!block || typeof block !== 'object' || block.type !== 'file') return null;
44
+ if (!block || typeof block !== 'object') return null;
45
+ if (block.type === 'document') {
46
+ const source = block.source;
47
+ if (source?.type !== 'base64' || typeof source.data !== 'string' || !source.data) return null;
48
+ const mimeType = String(
49
+ source.media_type || source.mediaType || block.mimeType || block.mediaType || 'application/pdf',
50
+ ).trim().toLowerCase() || 'application/pdf';
51
+ const filename = typeof block.title === 'string' && block.title
52
+ ? block.title
53
+ : (typeof block.filename === 'string' && block.filename ? block.filename : '');
54
+ return { data: source.data, mimeType, filename };
55
+ }
56
+ if (block.type !== 'file') return null;
43
57
  const data = isAttachmentReference(block)
44
58
  ? readAttachmentBase64(block)
45
59
  : (typeof block.data === 'string' ? block.data : '');
@@ -496,11 +510,18 @@ export function splitToolContentForOpenAIResponses(content) {
496
510
  }
497
511
 
498
512
  export function splitToolContentForXaiResponses(content) {
499
- const mediaContent = mediaPartsOnly(
500
- normalizeContentForOpenAIResponses(content, { role: 'user' }),
501
- new Set(['input_image']),
502
- );
503
- if (!mediaContent.length) return { output: contentToText(content, ''), mediaContent: null };
513
+ const normalized = normalizeContentForOpenAIResponses(content, { role: 'user' });
514
+ const mediaContent = mediaPartsOnly(normalized, new Set(['input_image']));
515
+ if (!mediaContent.length) {
516
+ const hasDocument = normalized.some((part) => part?.type === 'input_file');
517
+ return {
518
+ output: contentToText(
519
+ content,
520
+ hasDocument ? '[tool result included document content unavailable to xAI Responses]' : '',
521
+ ),
522
+ mediaContent: null,
523
+ };
524
+ }
504
525
  return {
505
526
  // xAI documents function_call_output as text/JSON; media remains a
506
527
  // following user input, without replaying the tool-result text there.
@@ -6,6 +6,7 @@ import { createHash, randomUUID } from 'crypto';
6
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
7
7
  import { join } from 'path';
8
8
  import { getPluginData } from '../config.mjs';
9
+ import { activateCodexTurnState } from './openai-turn-state.mjs';
9
10
 
10
11
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
12
  let _installationId = null;
@@ -206,39 +207,30 @@ export function _codexWsCompatibilityHeaders(context = {}) {
206
207
  export function _withCodexWsClientMetadata(frame, entry, enabled, context = {}) {
207
208
  if (!enabled || !frame || typeof frame !== 'object') return frame;
208
209
  const base = _codexMetadataBase(entry, context);
209
- const requestKind = _codexRequestKind(context?.sendOpts, context?.poolKey || '');
210
- const isPrewarmRequest = requestKind === 'prewarm';
210
+ const explicitLogicalTurnId = _cleanMetaString(
211
+ context?.sendOpts?.turnId
212
+ || context?.sendOpts?.codexTurnId
213
+ || context?.sendOpts?.session?.turnId,
214
+ );
215
+ const logicalTurnId = explicitLogicalTurnId
216
+ ? _codexUuidV7(explicitLogicalTurnId)
217
+ : base.turn_id;
218
+ const turnStateScope = _cleanMetaString(context?.poolKey);
211
219
  const metadata = {
212
220
  ...base,
213
221
  ...(frame.client_metadata && typeof frame.client_metadata === 'object' ? frame.client_metadata : {}),
214
222
  'x-codex-ws-stream-request-start-ms': String(Date.now()),
215
223
  };
216
224
  if (entry && typeof entry === 'object') {
217
- // x-codex-turn-state is scoped to ONE turn while
218
- // pooled sockets span turns: attribute a captured token to the FIRST
219
- // turn that observes it, then drop it once turn_id moves on. An empty
220
- // turn_id (parity prewarm) is a valid owner, so the check is against
221
- // null rather than falsiness.
222
- if (entry.turnState) {
223
- if (entry.turnStateTurnId == null) {
224
- entry.turnStateTurnId = base.turn_id;
225
- } else if (entry.turnStateTurnId !== base.turn_id) {
226
- // Codex startup prewarm owns the handshake turn-state until
227
- // the first real turn consumes that prewarmed client session.
228
- // Adopt it once across prewarm→turn; ordinary turn changes
229
- // still retire the old token.
230
- if (entry.turnStateFromPrewarm === true && !isPrewarmRequest) {
231
- entry.turnStateTurnId = base.turn_id;
232
- } else {
233
- entry.turnState = null;
234
- entry.turnStateTurnId = null;
235
- }
236
- }
237
- }
238
- if (!isPrewarmRequest && entry.turnStateFromPrewarm === true) {
239
- entry.turnStateFromPrewarm = false;
225
+ const priorTurnId = entry.turnStateTurnId;
226
+ if (turnStateScope && logicalTurnId) {
227
+ entry.turnState = activateCodexTurnState(turnStateScope, logicalTurnId, entry);
228
+ } else if (priorTurnId != null && priorTurnId !== logicalTurnId) {
229
+ entry.turnState = null;
240
230
  }
241
- entry.currentTurnId = base.turn_id;
231
+ entry.turnStateScope = turnStateScope || null;
232
+ entry.turnStateTurnId = logicalTurnId || null;
233
+ entry.currentTurnId = logicalTurnId || null;
242
234
  }
243
235
  if (entry?.turnState) metadata['x-codex-turn-state'] = String(entry.turnState);
244
236
  return { ...frame, client_metadata: metadata };