mixdog 1.2.2 → 1.2.3

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 (415) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/provider-pingpong-live.mjs +509 -0
  4. package/src/agents/advisor/AGENT.md +1 -1
  5. package/src/agents/advisor/agent.json +1 -1
  6. package/src/defaults/shipped-history.json +5 -0
  7. package/src/defaults/skills/setup/SKILL.md +6 -1
  8. package/src/defaults/skills/setup/references/actions.md +44 -22
  9. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  10. package/src/headless-exec.mjs +15 -8
  11. package/src/lib/keychain-cjs.cjs +7 -4
  12. package/src/output-styles/common.md +11 -10
  13. package/src/rules/agent/AGENT.md +5 -5
  14. package/src/rules/lead/LEAD.md +5 -5
  15. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  16. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  17. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  18. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  19. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  21. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  22. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  23. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +2 -0
  24. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +24 -17
  25. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +7 -6
  26. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +29 -10
  28. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  30. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  31. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  32. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  34. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +58 -7
  39. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +29 -10
  40. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +16 -3
  41. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  42. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +12 -6
  43. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  44. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +114 -72
  45. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  46. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +37 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  50. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +3 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor.mjs +9 -2
  52. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  53. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  54. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  55. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  56. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  57. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  59. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +25 -8
  60. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +87 -5
  61. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  62. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +13 -13
  63. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +355 -37
  64. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -2
  65. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +93 -47
  66. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +55 -10
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +26 -8
  68. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +1 -0
  70. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  71. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  72. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  73. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  74. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  75. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  76. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  77. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  78. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  79. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  80. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  81. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  82. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  83. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  85. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  86. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  88. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +121 -61
  89. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  90. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +5 -2
  91. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  92. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  93. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  95. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  96. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  97. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  98. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +42 -24
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +63 -25
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +53 -5
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -1
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +103 -26
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +23 -4
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +11 -6
  108. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  109. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +15 -23
  110. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  111. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +25 -4
  112. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  114. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +4 -0
  115. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +5 -0
  116. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +3 -1
  119. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +7 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +7 -1
  121. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +71 -24
  122. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  123. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  124. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  125. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  126. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  127. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  130. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +11 -3
  131. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  132. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  133. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  134. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +147 -8
  135. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  136. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +5 -0
  137. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  138. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  139. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  140. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  141. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  142. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  143. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  147. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  148. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  150. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  151. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  154. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
  156. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  157. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  158. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  159. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  160. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  161. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  162. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  163. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  164. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  165. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  167. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  168. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  169. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  170. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  171. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  172. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +63 -20
  173. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +2 -1
  174. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  175. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  179. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +4 -1
  180. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  181. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +16 -0
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  184. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  185. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  186. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  187. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  188. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +15 -3
  189. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  190. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  192. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  193. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  195. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  196. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +13 -0
  197. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  198. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  199. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  200. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  201. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  202. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  203. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  204. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  205. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  206. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  207. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  208. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +8 -0
  209. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +10 -1
  210. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  211. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  212. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  213. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  214. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  216. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  217. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  218. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  219. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  220. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  221. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  223. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  224. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  225. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  226. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  227. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  228. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  229. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  230. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  231. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  232. package/src/runtime/github/tool.mjs +92 -2
  233. package/src/runtime/memory/index.mjs +1 -0
  234. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  235. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  236. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  237. package/src/runtime/memory/lib/pg/process.mjs +31 -26
  238. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +108 -3
  240. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  241. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  242. package/src/runtime/office/README.md +1 -1
  243. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  244. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  245. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  246. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  247. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  248. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  249. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  250. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  251. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  252. package/src/runtime/office/core/office-sessions.mjs +5 -0
  253. package/src/runtime/office/core/office-transactions.mjs +3 -0
  254. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  255. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  256. package/src/runtime/office/index.mjs +30 -0
  257. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  258. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  259. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  260. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  261. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  262. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  263. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  264. package/src/runtime/shared/buffered-appender.mjs +18 -0
  265. package/src/runtime/shared/config.mjs +78 -18
  266. package/src/runtime/shared/err-text.mjs +6 -0
  267. package/src/runtime/shared/file-permissions.d.mts +6 -1
  268. package/src/runtime/shared/file-permissions.mjs +54 -6
  269. package/src/runtime/shared/llm/anthropic-effort.mjs +6 -0
  270. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  271. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  272. package/src/runtime/shared/loopback-listener.mjs +9 -0
  273. package/src/runtime/shared/provider-api-key.mjs +2 -0
  274. package/src/runtime/shared/review-diff.mjs +52 -0
  275. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  276. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  277. package/src/runtime/shared/slow-span.mjs +114 -0
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  279. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  280. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  281. package/src/runtime/shared/tool-card-model.mjs +5 -2
  282. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  283. package/src/runtime/shared/tool-primitives.mjs +13 -0
  284. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  285. package/src/runtime/shared/tool-surface.mjs +18 -3
  286. package/src/runtime/shared/tool-work-units.mjs +40 -4
  287. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  288. package/src/runtime/shared/transcript-writer.mjs +10 -2
  289. package/src/runtime/shared/turn-changes.mjs +268 -100
  290. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  291. package/src/runtime/shared/turn-snapshot-store.mjs +19 -108
  292. package/src/runtime/shared/turn-snapshot.mjs +857 -683
  293. package/src/runtime/shared/turn-worktree-snapshot.mjs +183 -255
  294. package/src/runtime/shared/user-data-guard.mjs +262 -49
  295. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  296. package/src/runtime/shared/worktrees/contract.mjs +34 -0
  297. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  298. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  299. package/src/runtime/shared/worktrees/git.mjs +8 -3
  300. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  301. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  302. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  303. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  304. package/src/runtime/terminal-bridge/client.mjs +44 -11
  305. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  306. package/src/runtime/tidy/apply.mjs +47 -2
  307. package/src/runtime/tidy/run-engines.mjs +11 -5
  308. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  309. package/src/session-runtime/boot/apis.mjs +13 -5
  310. package/src/session-runtime/boot/facade.mjs +1 -0
  311. package/src/session-runtime/boot/infrastructure.mjs +6 -1
  312. package/src/session-runtime/boot/session.mjs +0 -1
  313. package/src/session-runtime/boot/tools.mjs +4 -1
  314. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  315. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  316. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  317. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  318. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  319. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  320. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  321. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  322. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -3
  323. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  324. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  325. package/src/session-runtime/lifecycle/teardown.mjs +72 -7
  326. package/src/session-runtime/lifecycle-api.mjs +1 -0
  327. package/src/session-runtime/mcp-glue/connect.mjs +17 -5
  328. package/src/session-runtime/model-route/set-route.mjs +2 -2
  329. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  330. package/src/session-runtime/native-web-search.mjs +10 -1
  331. package/src/session-runtime/new-session-config.mjs +4 -2
  332. package/src/session-runtime/provider-auth/login.mjs +4 -0
  333. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  334. package/src/session-runtime/runtime-review-api.mjs +8 -2
  335. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  336. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  337. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  338. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  339. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  340. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  341. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  342. package/src/session-runtime/services/agent-tool.mjs +3 -0
  343. package/src/session-runtime/services/channel-admin.mjs +19 -3
  344. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  345. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  346. package/src/session-runtime/services/provider-admin.mjs +81 -15
  347. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  348. package/src/session-runtime/session-turn-api.mjs +14 -5
  349. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  350. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  351. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  352. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  353. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  354. package/src/session-runtime/skills-api.mjs +3 -5
  355. package/src/session-runtime/tool-surface.mjs +5 -1
  356. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  357. package/src/session-runtime/turn/session-ops.mjs +7 -5
  358. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  359. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  360. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  361. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  362. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  363. package/src/session-runtime/turn/turn-run.mjs +25 -9
  364. package/src/session-runtime/usage-stats-api.mjs +43 -11
  365. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  366. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  367. package/src/session-runtime/workspace/transitions.mjs +190 -15
  368. package/src/standalone/daemon-agent-control.mjs +9 -1
  369. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  370. package/src/standalone/daemon-owner.mjs +17 -4
  371. package/src/standalone/daemon-pg-stop.mjs +51 -0
  372. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  373. package/src/standalone/daemon-stop-budget.mjs +18 -0
  374. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  375. package/src/standalone/daemon-telemetry.mjs +19 -0
  376. package/src/standalone/daemon.mjs +61 -1
  377. package/src/standalone/desktop-service-registry.mjs +5 -0
  378. package/src/standalone/local-session-runtime.mjs +70 -19
  379. package/src/standalone/session-client.mjs +42 -0
  380. package/src/standalone/session-protocol.mjs +7 -0
  381. package/src/standalone/session-proxy/projection.mjs +41 -4
  382. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  383. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  384. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  385. package/src/standalone/session-service-api.mjs +6 -1
  386. package/src/standalone/session-state-patch.d.mts +2 -1
  387. package/src/standalone/session-state-patch.mjs +41 -1
  388. package/src/tui/App.jsx +25 -0
  389. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  390. package/src/tui/app/btw-controller.mjs +118 -0
  391. package/src/tui/app/create-app-pickers.mjs +2 -0
  392. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  393. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  394. package/src/tui/app/slash-commands.mjs +13 -3
  395. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  396. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  397. package/src/tui/app/use-global-key-input.mjs +5 -12
  398. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  399. package/src/tui/components/BtwPanel.jsx +53 -0
  400. package/src/tui/components/QuestionPanel.jsx +275 -0
  401. package/src/tui/components/question-panel-model.mjs +156 -0
  402. package/src/tui/dist/index.mjs +2413 -1465
  403. package/src/tui/session/context-state.mjs +4 -2
  404. package/src/tui/session/session-api/commands.mjs +10 -0
  405. package/src/tui/session/session-api/controls.mjs +2 -2
  406. package/src/tui/session/session-api/integrations.mjs +9 -2
  407. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  408. package/src/tui/session/session-api/lifecycle.mjs +2 -1
  409. package/src/tui/session/session-api/settings.mjs +6 -0
  410. package/src/tui/session/session-api.mjs +1 -0
  411. package/src/tui/session/tool-approval.mjs +10 -7
  412. package/src/tui/session/transcript-spill.mjs +14 -7
  413. package/src/tui/session/turn-stream.mjs +174 -31
  414. package/src/tui/session/turn.mjs +12 -5
  415. package/src/workflows/default/WORKFLOW.md +3 -1
@@ -1,5 +1,6 @@
1
1
  // Deferred-pool and MCP-instruction manifests injected into BP2.
2
2
  import { parseMcpToolName } from '../../../shared/tool-primitives.mjs';
3
+ import { isToolBridgeEnabled, TOOL_BRIDGE_GUIDANCE } from '../runtime-core/tool-exposure.mjs';
3
4
 
4
5
  export function compactPromptManifestText(value, max = 250) {
5
6
  const text = String(value || '')
@@ -13,7 +14,10 @@ export function compactPromptManifestText(value, max = 250) {
13
14
  return `${cut.replace(/[\s,.;:!?/-]+$/, '')}...`;
14
15
  }
15
16
 
16
- const DEFERRED_TOOLS_BLOCK_RE = /(\n\n---\n*)?<available-deferred-tools>[\s\S]*?<\/available-deferred-tools>\s*/gi;
17
+ // The native deferred-pool manifest and the static bridge guidance occupy the
18
+ // same BP2 slot: one replaces the other when the exposure changes.
19
+ const DEFERRED_TOOLS_BLOCK_RE =
20
+ /(\n\n---\n*)?<(available-deferred-tools|tool-bridge)>[\s\S]*?<\/(?:available-deferred-tools|tool-bridge)>\s*/gi;
17
21
  const MCP_INSTRUCTIONS_BLOCK_RE = /(\n\n---\n*)?<mcp-instructions>[\s\S]*?<\/mcp-instructions>\s*/gi;
18
22
  // Tool and MCP server names enter the manifests only as plain identifiers
19
23
  // (no markup can be smuggled into the prompt through a name).
@@ -47,6 +51,7 @@ function hasDeferredToolManifestBlock(text) {
47
51
  const raw = String(text || '');
48
52
  return (
49
53
  /<available-deferred-tools>[\s\S]*?<\/available-deferred-tools>/i.test(raw) ||
54
+ /<tool-bridge>[\s\S]*?<\/tool-bridge>/i.test(raw) ||
50
55
  /<mcp-instructions>[\s\S]*?<\/mcp-instructions>/i.test(raw)
51
56
  );
52
57
  }
@@ -173,11 +178,17 @@ function deferredManifestEntries(session, pool) {
173
178
  }
174
179
 
175
180
  function composeDeferredBp2Manifest(session, pool) {
181
+ // Bridge exposure gets the native roster and MCP instructions (the pool is
182
+ // already limited to what the session's policy lets it run), followed by
183
+ // the static bridge guidance. A tool-free bridge session gets nothing.
184
+ const bridge = isToolBridgeEnabled(session);
185
+ if (bridge && !(session.tools || []).some((tool) => tool?.name === 'call_tool')) return '';
176
186
  const parts = [];
177
187
  const deferredManifest = buildDeferredToolManifest(deferredManifestEntries(session, pool));
178
188
  if (deferredManifest) parts.push(deferredManifest);
179
189
  const mcpManifest = buildMcpInstructionsManifest(session.mcpServerInstructions, pool, session.deferredToolCatalog);
180
190
  if (mcpManifest) parts.push(mcpManifest);
191
+ if (bridge) parts.push(TOOL_BRIDGE_GUIDANCE);
181
192
  return parts.join('\n\n');
182
193
  }
183
194
 
@@ -186,12 +197,20 @@ function markDeferredBp2Applied(session) {
186
197
  delete session.deferredToolBp1Applied;
187
198
  }
188
199
 
200
+ // Replace, never mutate: session-store delta saves treat stable message
201
+ // references as an unchanged prefix, so a rewritten prompt block must be a
202
+ // fresh object for the next save to persist it.
203
+ function setSystemContent(session, index, content) {
204
+ const message = session.messages[index];
205
+ if (message.content !== content) session.messages[index] = { ...message, content };
206
+ }
207
+
189
208
  function clearDeferredManifestBlocks(session) {
190
- for (const message of session.messages) {
209
+ session.messages.forEach((message, index) => {
191
210
  if (message?.role === 'system' && typeof message.content === 'string') {
192
- message.content = stripDeferredToolManifestBlock(message.content);
211
+ setSystemContent(session, index, stripDeferredToolManifestBlock(message.content));
193
212
  }
194
- }
213
+ });
195
214
  session.messages = session.messages.filter(
196
215
  (message) => message?.role !== 'system' || String(message.content || '').trim()
197
216
  );
@@ -214,15 +233,15 @@ function writeDeferredManifestAt(session, idx, manifest, rebuild) {
214
233
  for (let i = 0; i < session.messages.length; i++) {
215
234
  if (i === idx || session.messages[i]?.role !== 'system' || typeof session.messages[i].content !== 'string')
216
235
  continue;
217
- session.messages[i].content = stripDeferredToolManifestBlock(session.messages[i].content);
236
+ setSystemContent(session, i, stripDeferredToolManifestBlock(session.messages[i].content));
218
237
  }
219
238
  const raw = typeof session.messages[idx].content === 'string' ? session.messages[idx].content : '';
220
239
  if (rebuild && hasDeferredToolManifestBlock(raw)) {
221
- session.messages[idx].content = rebuildDeferredToolManifestBlock(raw, manifest);
240
+ setSystemContent(session, idx, rebuildDeferredToolManifestBlock(raw, manifest));
222
241
  return;
223
242
  }
224
243
  const base = stripDeferredToolManifestBlock(raw);
225
- session.messages[idx].content = base ? `${base}\n\n---\n\n${manifest}` : manifest;
244
+ setSystemContent(session, idx, base ? `${base}\n\n---\n\n${manifest}` : manifest);
226
245
  }
227
246
 
228
247
  /**
@@ -1,9 +1,10 @@
1
1
  // Skill discovery, cache, resource loading, envelopes, manifests, and the
2
2
  // Skill meta-tool.
3
3
 
4
- import { readFileSync, existsSync, readdirSync } from 'node:fs';
4
+ import { createHash } from 'node:crypto';
5
+ import { readFileSync, existsSync, readdirSync, statSync, watch as watch_ } from 'node:fs';
5
6
  import { basename, dirname, join } from 'node:path';
6
- import { maxMtimeRecursive, mtimeSignatureRecursive } from '../cache-mtime.mjs';
7
+ import { maxMtimeRecursive, mtimeSignatureRecursive, mtimeSignatureText } from '../cache-mtime.mjs';
7
8
  import { mixdogHome, resolvePluginData, mixdogRoot } from '../../../shared/plugin-paths.mjs';
8
9
  import { PLUGIN_MANIFEST_FILES, pluginSkillsRoots } from '../../../shared/plugin-manifest.mjs';
9
10
  import {
@@ -257,6 +258,31 @@ export function isSkillDisabled(name, config = null, skills = null) {
257
258
  return Boolean(skill && missingFeature(skill, featureConfig(config)));
258
259
  }
259
260
 
261
+ /**
262
+ * `enabled` for every skill of one collectSkillsCached() snapshot, exactly as
263
+ * `!(isSkillDisabled(name, config, skills) || skillMissingFeature(name, config,
264
+ * skills))` per skill — but with ONE config read, one disabled-name set and one
265
+ * name index for the whole list instead of three config reads and a linear
266
+ * lookup per skill (the status listing blocked the event loop on them).
267
+ */
268
+ export function skillEnabledFlags(skills, config = null) {
269
+ const list = Array.isArray(skills) ? skills : [];
270
+ const cfg = config || loadConfig({ secrets: false });
271
+ const disabled = getDisabledSkillNameSet(cfg);
272
+ const features = featureConfig(cfg);
273
+ const byKey = new Map();
274
+ for (const entry of list) {
275
+ const key = normalizeSkillNameKey(entry.name);
276
+ if (!byKey.has(key)) byKey.set(key, entry);
277
+ }
278
+ return list.map((entry) => {
279
+ const key = normalizeSkillNameKey(entry.name);
280
+ const skill = byKey.get(key);
281
+ if (key && !followsBuiltinFeature(skill) && disabled.has(key)) return false;
282
+ return !(skill && missingFeature(skill, features));
283
+ });
284
+ }
285
+
260
286
  /** The built-in feature a skill needs that is not active, or null. */
261
287
  export function skillMissingFeature(name, config = null, skills = null) {
262
288
  const skill = findCachedSkillByKey(normalizeSkillNameKey(name), skills);
@@ -303,6 +329,81 @@ export function collectPromptSkillsCached(cwd, config = null) {
303
329
  // --- Global skill cache (mtime-based) ---
304
330
  const _skillsCache = new Map();
305
331
 
332
+ // Change watchers over the recursive skill roots. Re-walking every skill
333
+ // folder (one stat per entry) on every status/prompt read blocked the event
334
+ // loop for tens to hundreds of ms with a large skill set. A root whose
335
+ // recursive watcher has reported nothing since its last walk reuses that
336
+ // walk's signature text instead; any event, watcher error or explicit
337
+ // invalidation re-walks it. A watched root is trusted only once an event-loop
338
+ // turn has passed since its walk.
339
+ // Freshness contract: every write made through the app (addSkill, saveSkill,
340
+ // setDisabledSkills, reloadSkills, settings/plugin mutations, peer-runtime
341
+ // announcements) calls invalidateSkillsCache synchronously, which drops every
342
+ // watch, so the very next read re-walks and is exact. A passive edit made
343
+ // outside the app is eventually consistent: it is seen once the watcher
344
+ // delivers its event (watcher latency), or at the next explicit invalidation.
345
+ // Roots that cannot be watched (missing, or unsupported by the platform) and
346
+ // every plain-file gate path are stat'ed on every call as before.
347
+ const _ROOT_WATCH_LIMIT = 64;
348
+ const _rootWatches = new Map(); // root -> { watcher, dirty, text, settled }
349
+
350
+ function _closeRootWatch(root) {
351
+ const watch = _rootWatches.get(root);
352
+ if (!watch) return;
353
+ _rootWatches.delete(root);
354
+ try {
355
+ watch.watcher.close();
356
+ } catch {
357
+ /* already closed */
358
+ }
359
+ }
360
+
361
+ function _closeAllRootWatches() {
362
+ for (const root of [..._rootWatches.keys()]) _closeRootWatch(root);
363
+ }
364
+
365
+ function _openRootWatch(root) {
366
+ let isDir = false;
367
+ try {
368
+ isDir = statSync(root).isDirectory();
369
+ } catch {
370
+ return null;
371
+ }
372
+ if (!isDir) return null;
373
+ if (_rootWatches.size >= _ROOT_WATCH_LIMIT) _closeAllRootWatches();
374
+ const watch = { watcher: null, dirty: true, text: null, settled: false };
375
+ try {
376
+ // A change anywhere below drops the root's reused walk; the watcher is
377
+ // closed with it (a re-walk re-opens one), so a churning folder never
378
+ // keeps a handle on directories being deleted.
379
+ watch.watcher = watch_(root, { recursive: true, persistent: false }, () => _closeRootWatch(root));
380
+ watch.watcher.on('error', () => _closeRootWatch(root));
381
+ } catch {
382
+ return null;
383
+ }
384
+ _rootWatches.set(root, watch);
385
+ return watch;
386
+ }
387
+
388
+ /** Signature text of one recursive gate root (see mtimeSignatureText). */
389
+ function _rootSignatureText(root) {
390
+ const current = _rootWatches.get(root);
391
+ if (current && !current.dirty && current.settled) return current.text;
392
+ // Open the watcher BEFORE the walk: a change racing the walk then either
393
+ // shows in it or arrives as an event that drops the reused text.
394
+ const watch = current || _openRootWatch(root);
395
+ const text = mtimeSignatureText([root]);
396
+ if (watch && _rootWatches.get(root) === watch) {
397
+ watch.text = text;
398
+ watch.dirty = false;
399
+ watch.settled = false;
400
+ setImmediate(() => {
401
+ if (_rootWatches.get(root) === watch && watch.text === text) watch.settled = true;
402
+ }).unref?.();
403
+ }
404
+ return text;
405
+ }
406
+
306
407
  export function collectSkillsCached(cwd) {
307
408
  if (skillsDisabled()) return [];
308
409
  void cwd;
@@ -313,12 +414,15 @@ export function collectSkillsCached(cwd) {
313
414
  skillsDirs.push(...pluginSkillDirs().map(({ dir }) => dir), ...builtinSkillDirs());
314
415
  // registry.json itself gates plugin add/remove: removal deletes the
315
416
  // plugin's skills dir (so no dir mtime advances), but saveRegistry()
316
- // always rewrites this file. mtimeSignatureRecursive stats plain files directly.
417
+ // always rewrites this file. It is a plain file: stat'ed directly.
317
418
  const registryPath = pluginRegistryPath();
318
- if (registryPath) skillsDirs.push(registryPath);
319
- // Exact, un-timed gate: stat every call. The shallow watch list covers
320
- // manifest edits and skill roots that do not exist yet.
321
- const signature = `${mtimeSignatureRecursive(skillsDirs)}|${mtimeSignatureRecursive(pluginShallowWatchPaths(), 0)}`;
419
+ // Exact gate: unwatched roots and plain files are stat'ed every call; a
420
+ // watched root reuses its last walk only while its watcher stayed quiet.
421
+ // The shallow watch list covers manifest edits and skill roots that do not
422
+ // exist yet.
423
+ const rootTexts = skillsDirs.map((dir) => _rootSignatureText(dir));
424
+ if (registryPath) rootTexts.push(mtimeSignatureText([registryPath]));
425
+ const signature = `${createHash('sha1').update(rootTexts.join('\n')).digest('hex')}|${mtimeSignatureRecursive(pluginShallowWatchPaths(), 0)}`;
322
426
  const entry = _skillsCache.get(key);
323
427
  if (entry && entry.signature === signature) {
324
428
  return entry.value;
@@ -331,6 +435,7 @@ export function collectSkillsCached(cwd) {
331
435
  export function invalidateSkillsCache(cwd) {
332
436
  void cwd;
333
437
  _skillsCache.clear();
438
+ _closeAllRootWatches();
334
439
  }
335
440
 
336
441
  /**
@@ -16,10 +16,12 @@ import { fastModeAvailable } from './anthropic-fast-mode.mjs';
16
16
  import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic-effort.mjs';
17
17
  import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
18
18
  import {
19
+ anthropicThinkingActive,
19
20
  applyAnthropicCacheMarkers,
20
21
  clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
21
22
  resolveAnthropicCacheTtls as resolveCacheTtls,
22
23
  resolveAnthropicMessageCacheSlots,
24
+ sharedCachePrefixBoundary,
23
25
  toAnthropicToolChoice,
24
26
  } from './lib/anthropic-request-utils.mjs';
25
27
  import {
@@ -54,17 +56,21 @@ export async function buildAnthropicApiRequest({
54
56
  opts,
55
57
  fastModeLatched,
56
58
  modelInfo = null,
59
+ // The Claude id a gateway slug stands for (OpenRouter `anthropic/claude-opus-4.8`
60
+ // → `claude-opus-4.8`): the output cap and the thinking/effort shape are read
61
+ // from it, while the wire still names `useModel`.
62
+ capabilityModel = useModel,
63
+ // A gateway-supplied output limit for the model; wins over the Anthropic catalog.
64
+ modelMaxOutputTokens = opts?.modelMaxOutputTokens ?? null,
57
65
  }) {
58
66
  // A user-defined provider's reasoning comes from its own model row only:
59
67
  // the global by-model-id capability index and the Claude-id ladders are
60
68
  // first-party knowledge and must not leak onto (or from) a custom endpoint.
61
69
  const isCustom = isCustomProviderName(name);
62
- const maxTokens = resolveMaxTokens(useModel);
63
- const effortProjection = projectEffortConfiguration(messages, name, useModel, {
64
- ...config,
65
- ...opts,
66
- disableBetaHeaders: config?.disableBetaHeaders,
67
- });
70
+ const maxTokens = resolveMaxTokens(capabilityModel, { modelMaxOutputTokens });
71
+ const effortOptions = { ...config, ...opts, disableBetaHeaders: config?.disableBetaHeaders };
72
+ const gatewayEffort = typeof config?.messagesEffort === 'function' ? config.messagesEffort(opts) : null;
73
+ const effortProjection = projectEffortConfiguration(messages, name, useModel, effortOptions);
68
74
  const ttls = resolveCacheTtls(opts);
69
75
 
70
76
  const systemMsgs = messages.filter((m) => m.role === 'system');
@@ -73,20 +79,29 @@ export async function buildAnthropicApiRequest({
73
79
  // (cacheTier:'tier3') at ttls.tier3 — each its own system content block.
74
80
  const systemBlocks = buildSystemBlocks(systemMsgs, ttls.system, ttls.tier3);
75
81
 
76
- // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is
77
- // shared with anthropic-oauth — see resolveAnthropicMessageCacheSlots.
78
- const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls);
79
82
  // Tools are resolved BEFORE the messages: the lowering needs the final
80
83
  // tool list to drop tool_reference blocks whose tool no longer ships
81
84
  // in this request (otherwise the API rejects the whole turn).
82
- const requestTools = requestAnthropicTools(tools, chatMsgs, opts);
85
+ const requestTools = requestAnthropicTools(tools, chatMsgs, opts, name);
86
+ // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is
87
+ // shared with anthropic-oauth — see resolveAnthropicMessageCacheSlots.
88
+ const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls, { tools: requestTools });
83
89
  // Build → sanitize (once, inside toAnthropicMessages) → mark. Markers
84
90
  // are applied to the FINAL sanitized array by invariant, so block
85
91
  // drops / inserts / reorders performed by the sanitizer can never move
86
92
  // or delete a marked block. NEVER sanitize again after this.
93
+ const lowerHistory = (history, projection) =>
94
+ lowerAnthropicEffortHistory(
95
+ history.filter((m) => m.role !== 'system'),
96
+ (segment) => toAnthropicMessages(segment, requestTools, name),
97
+ projection
98
+ );
87
99
  const anthropicMessages = applyAnthropicCacheMarkers(
88
- lowerAnthropicEffortHistory(chatMsgs, (segment) => toAnthropicMessages(segment, requestTools), effortProjection),
89
- messageCacheSlots
100
+ lowerHistory(chatMsgs, effortProjection),
101
+ messageCacheSlots,
102
+ sharedCachePrefixBoundary(messages, opts, (prefix) =>
103
+ lowerHistory(prefix, projectEffortConfiguration(prefix, name, useModel, effortOptions))
104
+ )
90
105
  );
91
106
 
92
107
  const params = {
@@ -108,10 +123,9 @@ export async function buildAnthropicApiRequest({
108
123
  // tool_choice only when tools are actually present (Anthropic rejects
109
124
  // tool_choice without tools). 'none' rides the hard-cap final turn to
110
125
  // forbid tool USE while keeping the tools prefix stable for cache reuse.
111
- if (params.tools) {
112
- const toolChoice = toAnthropicToolChoice(opts.toolChoice);
113
- if (toolChoice) params.tool_choice = toolChoice;
114
- }
126
+ // The key is reserved here (wire key order) and resolved once thinking is
127
+ // known: forced any/tool is invalid while thinking is enabled.
128
+ if (params.tools) params.tool_choice = undefined;
115
129
  const hasDeferredTools = Array.isArray(params.tools) && params.tools.some((tool) => tool?.defer_loading === true);
116
130
  // Known tool names for the shared parseSSEStream leaked-tool-call guard
117
131
  // (same guard fixes both Anthropic providers). Recovered leaked calls
@@ -129,16 +143,30 @@ export async function buildAnthropicApiRequest({
129
143
  });
130
144
  if (fields.thinking) params.thinking = fields.thinking;
131
145
  if (fields.output_config) params.output_config = { ...(params.output_config || {}), ...fields.output_config };
132
- } else {
146
+ } else if (gatewayEffort !== 'none') {
147
+ // A gateway's own reasoning control (`messagesEffort`) replaces the turn
148
+ // effort; 'none' sends no thinking at all.
149
+ const turnOpts = effortProjection ? { ...opts, effort: effortProjection.initialEffort } : opts;
133
150
  applyAnthropicEffortToBody(params, {
134
- model: useModel,
135
- opts: effortProjection ? { ...opts, effort: effortProjection.initialEffort } : opts,
151
+ model: capabilityModel,
152
+ opts: gatewayEffort ? { ...turnOpts, effort: gatewayEffort } : turnOpts,
136
153
  maxTokens,
137
154
  clampThinkingBudgetTokens,
138
155
  logTag: name,
139
156
  progressUpdates: config?.disableBetaHeaders !== true,
140
157
  });
141
158
  }
159
+ if (params.tools) {
160
+ const toolChoice = toAnthropicToolChoice(opts.toolChoice, {
161
+ thinking: anthropicThinkingActive(params),
162
+ disableParallelToolUse: opts.disableParallelToolUse === true,
163
+ // A gateway that cannot honour disable_parallel_tool_use declares so.
164
+ supportsDisableParallelToolUse: config?.supportsDisableParallelToolUse !== false,
165
+ onThinkingConflict: opts.toolChoiceThinkingConflict,
166
+ });
167
+ if (toolChoice) params.tool_choice = toolChoice;
168
+ else delete params.tool_choice;
169
+ }
142
170
  // Fast mode → speed: "fast" on models Anthropic marks as speed-capable.
143
171
  // Suppressed while the fast capacity pool is cooling down.
144
172
  let fastLatched = fastModeLatched;
@@ -169,7 +197,7 @@ export async function buildAnthropicApiRequest({
169
197
  fastMode: fastLatched,
170
198
  toolSearch: hasDeferredTools,
171
199
  // Custom: the beta follows the resolver's output, not the model id.
172
- effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(useModel, opts),
200
+ effort: isCustom ? Boolean(params.output_config?.effort) : shouldIncludeEffortBeta(capabilityModel, opts),
173
201
  serverFallback: params.fallbacks === 'default',
174
202
  thinkingDisplayUpdates: params.thinking?.display === 'updates',
175
203
  }),
@@ -25,9 +25,11 @@ const STATUS_ERROR_TYPE = new Map([
25
25
  [401, 'authentication_error'],
26
26
  [403, 'permission_error'],
27
27
  [404, 'not_found_error'],
28
+ [409, 'conflict_error'],
28
29
  [413, REQUEST_TOO_LARGE],
29
30
  [429, 'rate_limit_error'],
30
31
  [500, 'api_error'],
32
+ [504, 'timeout_error'],
31
33
  [529, 'overloaded_error'],
32
34
  ]);
33
35
 
@@ -4,10 +4,10 @@
4
4
  //
5
5
  // Resolution order:
6
6
  // 1. MIXDOG_ANTHROPIC_MAX_OUTPUT_TOKENS env override, if set to a positive
7
- // int, wins outright (returned as the safety cap itself).
7
+ // int, wins, capped by the known model maximum when there is one.
8
8
  // 2. Catalog outputTokens for the model id (trusted over hardcoded
9
9
  // heuristics when present — the API reports real per-model limits),
10
- // clamped to [MAX_TOKENS_FLOOR, safetyCap].
10
+ // clamped to safetyCap (never raised: a declared limit is a ceiling).
11
11
  // 3. The shared model catalog's outputTokens for the model (cached roster /
12
12
  // LiteLLM / models.dev), same clamp.
13
13
  // 4. The pre-catalog family limits for a model no catalog describes, else
@@ -62,21 +62,28 @@ function sharedCatalogOutputTokens(model) {
62
62
  // catalogLookup(model) -> number|null. Providers supply their own strategy
63
63
  // for sourcing the catalog array (in-memory mirror + disk fallback for OAuth,
64
64
  // plain disk read for the API-key twin — see anthropic.mjs).
65
- export function resolveAnthropicMaxTokens(model, { catalogLookup } = {}) {
66
- const envOverride = envAnthropicMaxOutputOverride();
67
- if (envOverride != null) return envOverride;
68
- const safetyCap = DEFAULT_SAFETY_CAP;
69
- let catalogValue = null;
70
- if (typeof catalogLookup === 'function') {
71
- try {
72
- catalogValue = catalogLookup(model);
73
- } catch {
74
- catalogValue = null;
65
+ //
66
+ // `modelMaxOutputTokens` (number|null): a gateway-supplied model limit. When
67
+ // positive it takes precedence over every catalog lookup. A declared model
68
+ // maximum is a ceiling: it is never raised to the floor, and the env override
69
+ // is capped by it.
70
+ export function resolveAnthropicMaxTokens(model, { catalogLookup, modelMaxOutputTokens = null } = {}) {
71
+ const isLimit = (value) => Number.isFinite(value) && value > 0;
72
+ let declared = Number(modelMaxOutputTokens);
73
+ if (!isLimit(declared)) {
74
+ declared = null;
75
+ if (typeof catalogLookup === 'function') {
76
+ try {
77
+ declared = catalogLookup(model);
78
+ } catch {
79
+ declared = null;
80
+ }
75
81
  }
82
+ if (!isLimit(declared)) declared = sharedCatalogOutputTokens(model);
76
83
  }
77
- if (!(Number.isFinite(catalogValue) && catalogValue > 0)) catalogValue = sharedCatalogOutputTokens(model);
78
- if (Number.isFinite(catalogValue) && catalogValue > 0) {
79
- return Math.max(MAX_TOKENS_FLOOR, Math.min(catalogValue, safetyCap));
80
- }
81
- return Math.min(fallbackAnthropicMaxTokens(model), safetyCap);
84
+ declared = isLimit(declared) ? Math.floor(declared) : null;
85
+ const envOverride = envAnthropicMaxOutputOverride();
86
+ if (envOverride != null) return declared == null ? envOverride : Math.min(envOverride, declared);
87
+ if (declared != null) return Math.min(declared, DEFAULT_SAFETY_CAP);
88
+ return Math.min(fallbackAnthropicMaxTokens(model), DEFAULT_SAFETY_CAP);
82
89
  }
@@ -131,8 +131,8 @@ function _catalogOutputTokensFromSharedCache(model) {
131
131
  }
132
132
  }
133
133
 
134
- export function resolveMaxTokens(model) {
135
- return resolveAnthropicMaxTokens(model, { catalogLookup: _catalogOutputTokensFromSharedCache });
134
+ export function resolveMaxTokens(model, { modelMaxOutputTokens = null } = {}) {
135
+ return resolveAnthropicMaxTokens(model, { catalogLookup: _catalogOutputTokensFromSharedCache, modelMaxOutputTokens });
136
136
  }
137
137
 
138
138
  // Test-only escape hatch for the provider contract scripts
@@ -144,11 +144,12 @@ export const _test = {
144
144
  sanitizeInputSchema: (schema, toolName) => sanitizeAnthropicInputSchema(schema, toolName, 'anthropic'),
145
145
  };
146
146
 
147
- function deferredAnthropicTools(activeTools, messages, opts) {
148
- return sharedDeferredAnthropicTools(activeTools, messages, opts, 'anthropic');
147
+ function deferredAnthropicTools(activeTools, messages, opts, provider = 'anthropic') {
148
+ return sharedDeferredAnthropicTools(activeTools, messages, opts, provider);
149
149
  }
150
- export function requestAnthropicTools(tools, messages, opts) {
151
- return sharedRequestAnthropicTools(tools, messages, opts, 'anthropic');
150
+ // `provider`: the engine's name (a gateway Messages engine runs under its gateway's).
151
+ export function requestAnthropicTools(tools, messages, opts, provider = 'anthropic') {
152
+ return sharedRequestAnthropicTools(tools, messages, opts, provider);
152
153
  }
153
154
  // Test-only: expose the lowering so the steering-provenance test can assert
154
155
  // the API-key provider keeps steering-tagged user turns distinct (mirrors
@@ -10,7 +10,7 @@ import { createNpmVersionSource, maxSemver } from './npm-cli-version.mjs';
10
10
  // source release just to advance the user-agent version.
11
11
  // Claude Opus 5.5 requires 2.1.280 or newer. The release workflow raises this
12
12
  // to the published CLI version (scripts/sync-client-version-floors.mjs).
13
- export const DEFAULT_CLI_VERSION = '2.1.300';
13
+ export const DEFAULT_CLI_VERSION = '2.1.301';
14
14
  export const CLAUDE_CLI_NPM_PACKAGE = '@anthropic-ai/claude-code';
15
15
 
16
16
  const CACHE_SCHEMA_VERSION = 1;
@@ -55,6 +55,7 @@ import { applyAnthropicEffortToBody, shouldIncludeEffortBeta } from './anthropic
55
55
  import { ANTHROPIC_EFFORT_LEVELS, withEffortRejectionFallback } from './effort-rejection.mjs';
56
56
  import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
57
57
  import {
58
+ anthropicThinkingActive,
58
59
  applyAnthropicCacheMarkers,
59
60
  clampAnthropicThinkingBudget as clampThinkingBudgetTokens,
60
61
  deferredAnthropicTools as sharedDeferredAnthropicTools,
@@ -62,6 +63,7 @@ import {
62
63
  resolveAnthropicCacheTtls as resolveCacheTtls,
63
64
  resolveAnthropicMessageCacheSlots,
64
65
  sanitizeAnthropicInputSchema,
66
+ sharedCachePrefixBoundary,
65
67
  toAnthropicMessages,
66
68
  toAnthropicToolChoice,
67
69
  } from './lib/anthropic-request-utils.mjs';
@@ -168,8 +170,8 @@ function buildSystemBlocks(systemMsgs, systemTtl, tier3Ttl) {
168
170
  // clamped to [MAX_TOKENS_FLOOR, safetyCap].
169
171
  // 3. Shared model catalog outputTokens, then the floor, when the live
170
172
  // roster has no entry for this model.
171
- function resolveMaxTokens(model) {
172
- return resolveAnthropicMaxTokens(model, { catalogLookup: _catalogOutputTokens });
173
+ function resolveMaxTokens(model, { modelMaxOutputTokens = null } = {}) {
174
+ return resolveAnthropicMaxTokens(model, { catalogLookup: _catalogOutputTokens, modelMaxOutputTokens });
173
175
  }
174
176
 
175
177
  // --- Message conversion ---
@@ -191,8 +193,8 @@ function requestAnthropicTools(tools, messages, opts) {
191
193
  function buildRequestBody(messages, model, tools, sendOpts) {
192
194
  const systemMsgs = messages.filter((m) => m.role === 'system');
193
195
  const chatMsgs = messages.filter((m) => m.role !== 'system');
194
- const maxTokens = resolveMaxTokens(model);
195
196
  const opts = sendOpts || {};
197
+ const maxTokens = resolveMaxTokens(model, { modelMaxOutputTokens: opts.modelMaxOutputTokens });
196
198
  const effortProjection = projectEffortConfiguration(messages, 'anthropic-oauth', model, opts);
197
199
  const ttls = resolveCacheTtls(opts);
198
200
  // Each system message becomes its own Anthropic content block with its own
@@ -202,18 +204,27 @@ function buildRequestBody(messages, model, tools, sendOpts) {
202
204
 
203
205
  // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is shared
204
206
  // with anthropic.mjs — see resolveAnthropicMessageCacheSlots.
205
- const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls);
206
207
  // Tools are resolved BEFORE the messages: the lowering needs the final
207
208
  // tool list to drop tool_reference blocks whose tool no longer ships in
208
209
  // this request (otherwise the API rejects the whole turn).
209
210
  const requestTools = requestAnthropicTools(tools, chatMsgs, opts);
211
+ const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls, { tools: requestTools });
210
212
  // Build → sanitize (once, inside toAnthropicMessages) → mark. Markers are
211
213
  // applied to the FINAL sanitized array by invariant, so block drops /
212
214
  // inserts / reorders performed by the sanitizer can never move or delete a
213
215
  // marked block. NEVER sanitize again after this (see send path).
216
+ const lowerHistory = (history, projection) =>
217
+ lowerAnthropicEffortHistory(
218
+ history.filter((m) => m.role !== 'system'),
219
+ (segment) => toAnthropicMessages(segment, requestTools),
220
+ projection
221
+ );
214
222
  const anthropicMessages = applyAnthropicCacheMarkers(
215
- lowerAnthropicEffortHistory(chatMsgs, (segment) => toAnthropicMessages(segment, requestTools), effortProjection),
216
- messageCacheSlots
223
+ lowerHistory(chatMsgs, effortProjection),
224
+ messageCacheSlots,
225
+ sharedCachePrefixBoundary(messages, opts, (prefix) =>
226
+ lowerHistory(prefix, projectEffortConfiguration(prefix, 'anthropic-oauth', model, opts))
227
+ )
217
228
  );
218
229
 
219
230
  const body = {
@@ -238,10 +249,8 @@ function buildRequestBody(messages, model, tools, sendOpts) {
238
249
  // tool_choice only when tools are actually present (Anthropic rejects
239
250
  // tool_choice without tools). 'none' rides the hard-cap final turn to
240
251
  // forbid tool USE while keeping the tools prefix stable for cache reuse.
241
- if (body.tools) {
242
- const toolChoice = toAnthropicToolChoice(opts.toolChoice);
243
- if (toolChoice) body.tool_choice = toolChoice;
244
- }
252
+ // Key reserved for wire order; resolved after thinking is applied.
253
+ if (body.tools) body.tool_choice = undefined;
245
254
 
246
255
  applyAnthropicEffortToBody(body, {
247
256
  model,
@@ -252,6 +261,16 @@ function buildRequestBody(messages, model, tools, sendOpts) {
252
261
  progressUpdates: true,
253
262
  });
254
263
 
264
+ if (body.tools) {
265
+ const toolChoice = toAnthropicToolChoice(opts.toolChoice, {
266
+ thinking: anthropicThinkingActive(body),
267
+ disableParallelToolUse: opts.disableParallelToolUse === true,
268
+ onThinkingConflict: opts.toolChoiceThinkingConflict,
269
+ });
270
+ if (toolChoice) body.tool_choice = toolChoice;
271
+ else delete body.tool_choice;
272
+ }
273
+
255
274
  // Skipped while the fast capacity pool is in cooldown (or permanently
256
275
  // unavailable) so a drained pool is not re-probed every turn.
257
276
  if (opts.fast === true && supportsAnthropicFastMode(model) && fastModeAvailable()) {
@@ -36,6 +36,20 @@ export function createTurnBlocks() {
36
36
  const appendText = (index, chunk) => {
37
37
  text.set(index, (text.get(index) || '') + (chunk || ''));
38
38
  };
39
+ // Per-index citations of a text block (content_block_start `citations` plus
40
+ // streamed citations_delta), replayed verbatim on the text block they cite.
41
+ const citations = new Map();
42
+ const appendCitation = (index, citation) => {
43
+ if (!citation || typeof citation !== 'object') return;
44
+ if (!text.has(index)) text.set(index, '');
45
+ let copy;
46
+ try {
47
+ copy = structuredClone(citation);
48
+ } catch {
49
+ copy = { ...citation };
50
+ }
51
+ citations.set(index, [...(citations.get(index) || []), copy]);
52
+ };
39
53
  // Client tool_use blocks that were actually dispatched, kept for the same
40
54
  // ordered replay list (deduped/skipped calls are intentionally absent).
41
55
  const clientToolUse = new Map();
@@ -61,7 +75,11 @@ export function createTurnBlocks() {
61
75
  ...thinking.entries(),
62
76
  ...[...text.entries()].map(([index, value]) => [
63
77
  index,
64
- { type: 'text', text: typeof value === 'string' ? value : '' },
78
+ {
79
+ type: 'text',
80
+ text: typeof value === 'string' ? value : '',
81
+ ...(citations.get(index)?.length ? { citations: citations.get(index) } : {}),
82
+ },
65
83
  ]),
66
84
  ...completedNativeBlocks(),
67
85
  ...clientToolUse.entries(),
@@ -105,6 +123,7 @@ export function createTurnBlocks() {
105
123
  pendingToolInputs,
106
124
  clientToolUse,
107
125
  appendText,
126
+ appendCitation,
108
127
  toolInputInFlight,
109
128
  orderedThinkingBlocks,
110
129
  orderedAssistantBlocks,
@@ -88,14 +88,27 @@ export function createContentBlockDelta({ turn, blocks, state, leak, relayText,
88
88
  progress('tool');
89
89
  };
90
90
 
91
- return (index, delta) => {
91
+ const stampTtft = () => {
92
+ if (state && !state.ttftAt) state.ttftAt = Date.now();
93
+ };
94
+
95
+ const onContentBlockDelta = (index, delta) => {
92
96
  if (delta?.type) turn.contentBlockTypes.add(delta.type);
93
97
  // Time-to-first-token: stamp the first content delta (text / thinking /
94
98
  // tool input_json) exactly once so the SSE trace can separate first-byte
95
99
  // latency from total stream/generation time.
96
- if (state && !state.ttftAt) state.ttftAt = Date.now();
100
+ stampTtft();
97
101
  if (delta?.type === 'text_delta') onTextDelta(index, delta.text);
102
+ if (delta?.type === 'citations_delta') blocks.appendCitation(index, delta.citation);
98
103
  if (delta?.type === 'thinking_delta' || delta?.type === 'signature_delta') onThinkingDelta(index, delta);
99
104
  if (delta?.type === 'input_json_delta') onInputJsonDelta(index, delta.partial_json);
100
105
  };
106
+ // A text block may open with non-empty `text` on content_block_start: it is
107
+ // model output and takes the same path as a text_delta (live relay, leak
108
+ // guard, returned content, ordered replay copy).
109
+ onContentBlockDelta.seedText = (index, initialText) => {
110
+ stampTtft();
111
+ onTextDelta(index, initialText);
112
+ };
113
+ return onContentBlockDelta;
101
114
  }