mixdog 1.2.3 → 1.2.5

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 (469) hide show
  1. package/package.json +1 -1
  2. package/scripts/gate-local.mjs +6 -4
  3. package/scripts/lib/gate-scheduler.mjs +29 -11
  4. package/scripts/mobile-release.mjs +5 -4
  5. package/scripts/probe-provider-catalog.mjs +31 -0
  6. package/scripts/provider-pingpong-live.mjs +128 -38
  7. package/scripts/session-sweep.mjs +5 -25
  8. package/src/agents/maintainer/AGENT.md +1 -1
  9. package/src/agents/maintainer/agent.json +1 -1
  10. package/src/agents/reviewer/AGENT.md +2 -4
  11. package/src/agents/security/AGENT.md +1 -2
  12. package/src/agents/writer/AGENT.md +2 -3
  13. package/src/defaults/shipped-history.json +7 -1
  14. package/src/defaults/skills/image/SKILL.md +21 -23
  15. package/src/lib/keychain-cjs.cjs +13 -11
  16. package/src/output-styles/common.md +2 -1
  17. package/src/rules/agent/AGENT.md +7 -5
  18. package/src/rules/lead/LEAD.md +13 -13
  19. package/src/rules/shared/45-destructive.md +4 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  21. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
  22. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
  23. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
  24. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
  25. package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
  26. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
  27. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
  28. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  29. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
  31. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
  32. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
  34. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
  35. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
  36. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
  37. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
  38. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
  40. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  41. package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
  42. package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
  43. package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
  44. package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
  46. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
  47. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
  48. package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
  49. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
  50. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
  51. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
  52. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
  53. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
  54. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
  55. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
  56. package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
  57. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
  58. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
  59. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
  60. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
  61. package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
  62. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
  64. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
  65. package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
  66. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
  67. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
  68. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
  70. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
  72. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
  80. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
  81. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
  82. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
  83. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
  86. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
  87. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
  88. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
  89. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
  90. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
  91. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
  92. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
  93. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
  94. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
  96. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
  97. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
  98. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
  99. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
  100. package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
  101. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
  103. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
  109. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
  110. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
  111. package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
  112. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
  113. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
  114. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
  115. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
  116. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
  117. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
  118. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  119. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
  120. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
  121. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
  123. package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
  124. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
  125. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
  126. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
  127. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
  128. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
  129. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
  130. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
  131. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
  132. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
  133. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
  134. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
  135. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
  136. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
  140. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
  141. package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
  146. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
  147. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
  148. package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
  149. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
  150. package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
  151. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
  155. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
  156. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
  157. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
  158. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
  161. package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
  162. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
  163. package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
  164. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
  165. package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
  166. package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
  167. package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
  169. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
  171. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
  173. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
  174. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
  178. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
  179. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
  180. package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
  181. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +160 -212
  182. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
  183. package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
  184. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
  185. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
  186. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
  187. package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
  188. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
  189. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
  190. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
  191. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
  192. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
  193. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
  194. package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
  197. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
  199. package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
  200. package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
  201. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
  203. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
  208. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
  209. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
  210. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
  211. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
  212. package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
  213. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
  214. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
  215. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
  216. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
  217. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
  218. package/src/runtime/attachments/store.d.mts +9 -0
  219. package/src/runtime/attachments/store.mjs +80 -9
  220. package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
  221. package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
  222. package/src/runtime/computer-bridge/action-schema.mjs +178 -178
  223. package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
  224. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
  225. package/src/runtime/effort-judge/judge-client.mjs +21 -4
  226. package/src/runtime/effort-judge/judge-worker.mjs +4 -1
  227. package/src/runtime/github/tool.mjs +118 -17
  228. package/src/runtime/media/adapters/codex-image.mjs +140 -21
  229. package/src/runtime/media/catalog.mjs +4 -4
  230. package/src/runtime/media/chatgpt-usage.mjs +55 -0
  231. package/src/runtime/media/defaults.mjs +37 -0
  232. package/src/runtime/media/jobs.mjs +12 -2
  233. package/src/runtime/media/lanes.mjs +12 -0
  234. package/src/runtime/media/tool-defs.mjs +15 -4
  235. package/src/runtime/media/tool.mjs +41 -12
  236. package/src/runtime/media/upstream-error.mjs +72 -6
  237. package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
  238. package/src/runtime/memory/lib/pg/process.mjs +5 -7
  239. package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
  240. package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
  241. package/src/runtime/office/authoring/html-browser.mjs +18 -8
  242. package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
  243. package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
  244. package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
  245. package/src/runtime/office/core/office-write-ledger.mjs +1 -5
  246. package/src/runtime/office/index.mjs +2 -3
  247. package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
  248. package/src/runtime/session-search/session-search-ingest.mjs +1 -1
  249. package/src/runtime/shared/agent-reap-state.mjs +13 -0
  250. package/src/runtime/shared/atomic-file.d.mts +1 -1
  251. package/src/runtime/shared/background-tasks.mjs +6 -1
  252. package/src/runtime/shared/bounded-line-diff.mjs +11 -2
  253. package/src/runtime/shared/browser-launch.mjs +15 -0
  254. package/src/runtime/shared/err-text.mjs +42 -4
  255. package/src/runtime/shared/file-lock.mjs +6 -5
  256. package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
  257. package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
  258. package/src/runtime/shared/model-route-chain.d.mts +8 -0
  259. package/src/runtime/shared/model-route-chain.mjs +48 -0
  260. package/src/runtime/shared/model-route-fallback.mjs +22 -0
  261. package/src/runtime/shared/pid-liveness.mjs +12 -0
  262. package/src/runtime/shared/provider-api-key.mjs +6 -0
  263. package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
  264. package/src/runtime/shared/quiet-progress.d.mts +35 -0
  265. package/src/runtime/shared/quiet-progress.mjs +101 -0
  266. package/src/runtime/shared/quota-resume-store.mjs +32 -1
  267. package/src/runtime/shared/read-row-numbers.mjs +44 -0
  268. package/src/runtime/shared/remote-intent.mjs +2 -42
  269. package/src/runtime/shared/slow-span.mjs +8 -1
  270. package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
  271. package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
  272. package/src/runtime/shared/tool-output-budget.mjs +2 -0
  273. package/src/runtime/shared/tool-result-summary.mjs +16 -2
  274. package/src/runtime/shared/tool-status.mjs +3 -2
  275. package/src/runtime/shared/tool-surface.mjs +2 -3
  276. package/src/runtime/shared/tool-work-units.mjs +22 -15
  277. package/src/runtime/shared/transcript-metadata.mjs +49 -1
  278. package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
  279. package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
  280. package/src/runtime/shared/transcript-writer.mjs +14 -4
  281. package/src/runtime/shared/turn-changes.mjs +2 -1
  282. package/src/runtime/shared/turn-snapshot.mjs +11 -3
  283. package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
  284. package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
  285. package/src/runtime/shared/worktrees/carry.mjs +6 -4
  286. package/src/runtime/shared/worktrees/contract.mjs +27 -1
  287. package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
  288. package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
  289. package/src/runtime/shared/worktrees/git-head.mjs +22 -13
  290. package/src/runtime/shared/worktrees/include.mjs +125 -7
  291. package/src/runtime/shared/worktrees/lock.mjs +1 -1
  292. package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
  293. package/src/runtime/shared/worktrees/manager.mjs +378 -32
  294. package/src/runtime/shared/worktrees/owner.mjs +2 -1
  295. package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
  296. package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
  297. package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
  298. package/src/runtime/terminal-bridge/client.mjs +14 -10
  299. package/src/session-runtime/boot/apis.mjs +7 -1
  300. package/src/session-runtime/boot/config.mjs +2 -0
  301. package/src/session-runtime/boot/facade.mjs +3 -0
  302. package/src/session-runtime/boot/session.mjs +3 -2
  303. package/src/session-runtime/boot/tools.mjs +3 -0
  304. package/src/session-runtime/conversation-import/service.mjs +1 -1
  305. package/src/session-runtime/goal-facade-api.mjs +2 -2
  306. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
  307. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
  308. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
  309. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  310. package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
  311. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
  312. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
  313. package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
  314. package/src/session-runtime/lifecycle/shared.mjs +5 -6
  315. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
  316. package/src/session-runtime/lifecycle/teardown.mjs +7 -16
  317. package/src/session-runtime/media-api.mjs +8 -0
  318. package/src/session-runtime/model-route/route-ownership.mjs +60 -0
  319. package/src/session-runtime/model-route/route-persist.mjs +3 -2
  320. package/src/session-runtime/model-route/set-route.mjs +114 -34
  321. package/src/session-runtime/model-route/web-search-route.mjs +7 -2
  322. package/src/session-runtime/model-route-api.mjs +4 -2
  323. package/src/session-runtime/native-web-search/routes.mjs +37 -24
  324. package/src/session-runtime/native-web-search.mjs +9 -8
  325. package/src/session-runtime/provider-auth/setup.mjs +4 -0
  326. package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
  327. package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
  328. package/src/session-runtime/provider-models.mjs +1 -0
  329. package/src/session-runtime/provider-usage.mjs +4 -0
  330. package/src/session-runtime/route-resolution.mjs +3 -1
  331. package/src/session-runtime/runtime-core.mjs +4 -0
  332. package/src/session-runtime/runtime-facade.mjs +6 -0
  333. package/src/session-runtime/services/agent-task-status.mjs +2 -0
  334. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
  335. package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
  336. package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
  337. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
  338. package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
  339. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
  340. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
  341. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
  342. package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
  343. package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
  344. package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
  345. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
  346. package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
  347. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
  348. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
  349. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
  350. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
  351. package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
  352. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  353. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
  354. package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
  355. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
  356. package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
  357. package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
  358. package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
  359. package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
  360. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  361. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
  362. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
  363. package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
  364. package/src/session-runtime/services/agent-tool.mjs +4 -0
  365. package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
  366. package/src/session-runtime/services/provider-admin.mjs +77 -0
  367. package/src/session-runtime/session-create/session-options.mjs +7 -5
  368. package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
  369. package/src/session-runtime/session-transcript.mjs +4 -5
  370. package/src/session-runtime/session-turn-api.mjs +3 -9
  371. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
  372. package/src/session-runtime/setup-tool/executor.mjs +23 -5
  373. package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
  374. package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
  375. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
  376. package/src/session-runtime/tool-policy-refresh.mjs +83 -32
  377. package/src/session-runtime/turn/agent-ops.mjs +49 -2
  378. package/src/session-runtime/turn/side-question-ops.mjs +1 -4
  379. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
  380. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
  381. package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
  382. package/src/session-runtime/turn/turn-run.mjs +1 -36
  383. package/src/session-runtime/usage-stats-api.mjs +4 -1
  384. package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
  385. package/src/session-runtime/workspace/admission-gate.mjs +44 -15
  386. package/src/session-runtime/workspace/project-branches.mjs +28 -36
  387. package/src/session-runtime/workspace/transition-steps.mjs +96 -95
  388. package/src/session-runtime/workspace/transitions.mjs +248 -68
  389. package/src/standalone/channel-transport.mjs +3 -4
  390. package/src/standalone/daemon-desktop-runtime.mjs +32 -1
  391. package/src/standalone/daemon-pg-stop.mjs +1 -3
  392. package/src/standalone/daemon-session-control.mjs +88 -0
  393. package/src/standalone/daemon-stored-session-views.mjs +23 -3
  394. package/src/standalone/daemon.mjs +2 -0
  395. package/src/standalone/desktop-service-registry.mjs +32 -5
  396. package/src/standalone/local-session-runtime.mjs +11 -12
  397. package/src/standalone/session-protocol.mjs +7 -0
  398. package/src/standalone/session-runtime-inline-host.mjs +2 -0
  399. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
  400. package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
  401. package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
  402. package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
  403. package/src/standalone/session-service/agent-tree.mjs +28 -2
  404. package/src/standalone/session-service/entries/session-loads.mjs +16 -5
  405. package/src/standalone/session-service/entries.mjs +1 -0
  406. package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
  407. package/src/standalone/session-service/quota-resume.mjs +28 -13
  408. package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
  409. package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
  410. package/src/standalone/session-tool-output.mjs +82 -0
  411. package/src/tui/app/app-view.jsx +2 -1
  412. package/src/tui/app/provider-setup/provider-items.mjs +8 -0
  413. package/src/tui/app/provider-setup-picker.mjs +15 -3
  414. package/src/tui/app/slash-commands.mjs +6 -0
  415. package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
  416. package/src/tui/components/Spinner.jsx +4 -0
  417. package/src/tui/components/SpinnerByline.jsx +21 -2
  418. package/src/tui/components/status-line/local-line.mjs +10 -2
  419. package/src/tui/dist/index.mjs +529 -270
  420. package/src/tui/session/active-tool-summary.mjs +1 -1
  421. package/src/tui/session/context-state.mjs +0 -3
  422. package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
  423. package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
  424. package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
  425. package/src/tui/session/quota-resume.mjs +140 -61
  426. package/src/tui/session/session-api/commands.mjs +4 -10
  427. package/src/tui/session/session-api/controls.mjs +12 -0
  428. package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
  429. package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
  430. package/src/tui/session/session-api/lifecycle.mjs +13 -11
  431. package/src/tui/session/session-api/media.mjs +2 -0
  432. package/src/tui/session/session-api/routes.mjs +81 -57
  433. package/src/tui/session/session-api/settings.mjs +3 -3
  434. package/src/tui/session/session-api/worktree.mjs +3 -2
  435. package/src/tui/session/session-flow/auto-clear.mjs +1 -3
  436. package/src/tui/session/session-flow/drain.mjs +6 -4
  437. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
  438. package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
  439. package/src/tui/session/session-flow/queue.mjs +1 -1
  440. package/src/tui/session/tool-approval.mjs +17 -8
  441. package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
  442. package/src/tui/session/tui-steering-persist.mjs +3 -5
  443. package/src/tui/session/turn-quiet.mjs +53 -0
  444. package/src/tui/session/turn-stream.mjs +5 -1
  445. package/src/tui/session/turn.mjs +38 -31
  446. package/src/tui/session/worktree-setup-notice.mjs +38 -0
  447. package/src/tui/spinner-meta.mjs +10 -0
  448. package/src/tui/spinner-verbs.mjs +3 -0
  449. package/src/ui/statusline.mjs +13 -3
  450. package/src/ui/streaming-markdown-heal.mjs +70 -18
  451. package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
  452. package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
  453. package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
  454. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
  455. package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
  456. package/src/session-runtime/turn/share-ops.mjs +0 -77
  457. package/src/tui/session/cross-surface-share.mjs +0 -406
  458. package/src/tui/session/live-share/delta.mjs +0 -153
  459. package/src/tui/session/live-share/mirror.mjs +0 -106
  460. package/src/tui/session/live-share/owner.mjs +0 -254
  461. package/src/tui/session/live-share/retry.mjs +0 -54
  462. package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
  463. package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
  464. package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
  465. package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
  466. package/src/tui/session/live-share/viewer.mjs +0 -99
  467. package/src/tui/session/live-share/wire.mjs +0 -44
  468. package/src/tui/session/live-share.mjs +0 -154
  469. package/src/tui/session/shared-dir-watch.mjs +0 -69
@@ -6,15 +6,22 @@
6
6
  * account-verification URL Google hides in the error details), and the
7
7
  * per-attempt hand-off of the live stream to the turn collector.
8
8
  */
9
- import { emitProviderRetryStage, markProviderRecoveryExhausted, sleepWithAbort, withRetry } from './retry-classifier.mjs';
9
+ import {
10
+ emitProviderRetryStage,
11
+ markProviderRecoveryExhausted,
12
+ sleepWithAbort,
13
+ withRetry,
14
+ } from './retry-classifier.mjs';
10
15
  import {
11
16
  ANTIGRAVITY_RETRY,
17
+ ANTIGRAVITY_UNCLASSED_FIRST_BYTE,
12
18
  antigravity429WaitMs,
13
19
  isAntigravityCapacityError,
14
20
  } from './antigravity-retry-policy.mjs';
15
- import { createTimeoutSignal } from '../stall-policy.mjs';
21
+ import { createTimeoutSignal, firstByteWindowMs } from '../stall-policy.mjs';
22
+ import { MODEL_THINKING_STAGE } from '../../../shared/quiet-progress.mjs';
23
+ import { TIMEOUT_PHASE, isFirstByteTimeout } from '../../../shared/provider-timeout-phase.mjs';
16
24
  import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
17
- import { GEMINI_FIRST_BYTE_TIMEOUT_MS } from './gemini-stream.mjs';
18
25
  import { _scrubTokens } from './antigravity-oauth-tokens.mjs';
19
26
  import { applyGeminiErrorContract } from './gemini-error-contract.mjs';
20
27
  import { DETERMINISTIC_KINDS, providerErrorKind } from './provider-error-kind.mjs';
@@ -77,8 +84,10 @@ export function antigravityError(res, text, endpoint) {
77
84
  .map((link) => link?.url)
78
85
  .find((url) => typeof url === 'string' && url.length > 0);
79
86
  const metadataUrl = (key) =>
80
- detailEntries.find((entry) => entry?.reason === 'VALIDATION_REQUIRED' && typeof entry.metadata?.[key] === 'string' && entry.metadata[key])
81
- ?.metadata[key];
87
+ detailEntries.find(
88
+ (entry) =>
89
+ entry?.reason === 'VALIDATION_REQUIRED' && typeof entry.metadata?.[key] === 'string' && entry.metadata[key]
90
+ )?.metadata[key];
82
91
  const validationUrl =
83
92
  helpUrl ||
84
93
  metadataUrl('validation_url') ||
@@ -105,19 +114,53 @@ export function antigravityError(res, text, endpoint) {
105
114
  * @param {object} deps.opts send options (stage heartbeat)
106
115
  * @param {AbortSignal|null} deps.signal retry-loop signal (session pass-through)
107
116
  * @param {{ beginAttempt: Function, consume: Function }} deps.collector
117
+ * @param {{ timeoutMs: number, maxMs: number, retries: number|null }} [deps.firstByte]
118
+ * first-byte window per attempt (antigravityFirstBytePolicy)
108
119
  * @returns {() => Promise<object>} one retried request that resolves the final payload
109
120
  */
110
- export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opts, signal, collector, sleepFn }) {
121
+ export function createAntigravityRequest({
122
+ fetchFn,
123
+ endpoint,
124
+ headers,
125
+ body,
126
+ opts,
127
+ signal,
128
+ collector,
129
+ sleepFn,
130
+ firstByte = ANTIGRAVITY_UNCLASSED_FIRST_BYTE,
131
+ }) {
111
132
  const log = (attempt, lastErr, delayMs) => {
133
+ // A first-byte timeout runs on its own, shorter retry budget.
134
+ const firstByteLimited = isFirstByteTimeout(lastErr) && firstByte.retries != null;
112
135
  emitProviderRetryStage(opts.onStageChange, {
113
136
  attempt: attempt + 1,
114
- maxAttempts: ANTIGRAVITY_RETRY.maxAttempts,
137
+ maxAttempts: firstByteLimited
138
+ ? Math.min(ANTIGRAVITY_RETRY.maxAttempts, firstByte.retries + 1)
139
+ : ANTIGRAVITY_RETRY.maxAttempts,
115
140
  lastErr,
116
141
  delayMs,
117
142
  });
118
143
  process.stderr.write(`[antigravity] retry ${attempt + 1} after ${lastErr?.message || 'transient error'}\n`);
119
144
  };
120
- const attemptOnce = () =>
145
+ // Stamps what the attempt tells the user: how many attempts ran and, for a
146
+ // first-byte timeout, how long the model was given in total. A timeout past
147
+ // the first-byte retry budget ends the ladder here. A caller's cancellation
148
+ // reason may be shared by other requests and is never stamped.
149
+ const noteFailure = (err, { attemptSignal, windowMs, send }) => {
150
+ if (!err || typeof err !== 'object' || attemptSignal?.aborted) return err;
151
+ send.attempts += 1;
152
+ err.attempts = send.attempts;
153
+ if (!isFirstByteTimeout(err)) return err;
154
+ send.firstByteTimeouts += 1;
155
+ send.firstByteWaitedMs += windowMs;
156
+ err.waitedMs = send.firstByteWaitedMs;
157
+ err.providerLabel = 'Antigravity';
158
+ if (firstByte.retries != null && send.firstByteTimeouts > firstByte.retries) {
159
+ markProviderRecoveryExhausted(err, { owner: 'antigravity-first-byte', attempts: send.attempts, terminal: true });
160
+ }
161
+ return err;
162
+ };
163
+ const attemptOnce = (send) =>
121
164
  withRetry(
122
165
  async ({ signal: attemptSignal }) => {
123
166
  try {
@@ -125,37 +168,63 @@ export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opt
125
168
  } catch {
126
169
  /* heartbeat */
127
170
  }
128
- const firstByte = createTimeoutSignal(attemptSignal, GEMINI_FIRST_BYTE_TIMEOUT_MS, 'Antigravity first byte');
129
- let res;
171
+ const windowMs = firstByteWindowMs(firstByte, send.firstByteTimeouts);
130
172
  try {
131
- res = await fetchFn(`${endpoint}/v1internal:streamGenerateContent?alt=sse`, {
132
- method: 'POST',
133
- headers,
134
- body,
135
- signal: firstByte.signal,
136
- dispatcher: getLlmDispatcher(),
137
- });
138
- } catch (err) {
139
- // Fetch surfaces AbortError; rethrow the timer/parent
140
- // reason so same-host retry sees EPROVIDERTIMEOUT and
141
- // a caller cancel stays a cancel.
142
- if (firstByte.signal.aborted && firstByte.signal.reason instanceof Error) {
143
- throw firstByte.signal.reason;
173
+ const firstByteTimer = createTimeoutSignal(attemptSignal, windowMs, 'Antigravity first byte');
174
+ let res;
175
+ try {
176
+ const response = fetchFn(`${endpoint}/v1internal:streamGenerateContent?alt=sse`, {
177
+ method: 'POST',
178
+ headers,
179
+ body,
180
+ signal: firstByteTimer.signal,
181
+ dispatcher: getLlmDispatcher(),
182
+ });
183
+ // The request is on the wire and its headers are pending: the
184
+ // gateway sends them only with the first chunk, so this wait is
185
+ // the model's queueing and thinking time.
186
+ try {
187
+ opts.onStageChange?.(MODEL_THINKING_STAGE);
188
+ } catch {
189
+ /* heartbeat */
190
+ }
191
+ res = await response;
192
+ } catch (err) {
193
+ // Fetch surfaces AbortError; rethrow the timer/parent
194
+ // reason so same-host retry sees EPROVIDERTIMEOUT and
195
+ // a caller cancel stays a cancel.
196
+ if (firstByteTimer.signal.aborted && firstByteTimer.signal.reason instanceof Error) {
197
+ const reason = firstByteTimer.signal.reason;
198
+ if (!attemptSignal?.aborted) reason.timeoutPhase = TIMEOUT_PHASE.HEADERS;
199
+ throw reason;
200
+ }
201
+ throw err;
202
+ } finally {
203
+ firstByteTimer.cleanup();
144
204
  }
145
- throw err;
146
- } finally {
147
- firstByte.cleanup();
148
- }
149
- if (!res.ok) {
150
- const text = await res.text().catch(() => '');
151
- const failure = antigravityError(res, text, endpoint);
152
- // Capacity is paced by the local schedule below; the generic loop
153
- // would sleep whatever wait the server names.
154
- if (isCapacityFailure(failure)) markProviderRecoveryExhausted(failure, { owner: 'antigravity-capacity' });
155
- throw failure;
205
+ // Headers arrived: the connection is live even while the model
206
+ // has not produced its first chunk.
207
+ try {
208
+ opts.onStreamDelta?.('transport');
209
+ } catch {}
210
+ if (!res.ok) {
211
+ const text = await res.text().catch(() => '');
212
+ const failure = antigravityError(res, text, endpoint);
213
+ // Capacity is paced by the local schedule below; the generic loop
214
+ // would sleep whatever wait the server names.
215
+ if (isCapacityFailure(failure)) markProviderRecoveryExhausted(failure, { owner: 'antigravity-capacity' });
216
+ throw failure;
217
+ }
218
+ try {
219
+ opts.onStageChange?.('streaming');
220
+ } catch {
221
+ /* heartbeat */
222
+ }
223
+ collector.beginAttempt();
224
+ return await collector.consume(res, attemptSignal, { firstByteTimeoutMs: windowMs });
225
+ } catch (err) {
226
+ throw noteFailure(err, { attemptSignal, windowMs, send });
156
227
  }
157
- collector.beginAttempt();
158
- return collector.consume(res, attemptSignal);
159
228
  },
160
229
  {
161
230
  ...ANTIGRAVITY_RETRY,
@@ -170,9 +239,10 @@ export function createAntigravityRequest({ fetchFn, endpoint, headers, body, opt
170
239
  // gets one quick retry, a spent quota surfaces.
171
240
  return async () => {
172
241
  const state = { capacityRetries: 0, quickRetried: false };
242
+ const send = { attempts: 0, firstByteTimeouts: 0, firstByteWaitedMs: 0 };
173
243
  for (let tried = 1; ; tried++) {
174
244
  try {
175
- return await attemptOnce();
245
+ return await attemptOnce(send);
176
246
  } catch (err) {
177
247
  const waitMs = antigravity429WaitMs(err, state);
178
248
  if (waitMs == null) {
@@ -79,7 +79,10 @@ export function modelEndpoints(item) {
79
79
  // The route for a model with these declared endpoints (null: undeclared, Chat).
80
80
  // `undefined`: nothing describes the model, so no wire is ruled out.
81
81
  function commandCodeRoute(model, endpoints, ctx = {}) {
82
- return resolveWireRoute(PROVIDER_ID, model, { ...ctx, endpoints: endpoints === undefined ? null : (endpoints ?? ['chat']) });
82
+ return resolveWireRoute(PROVIDER_ID, model, {
83
+ ...ctx,
84
+ endpoints: endpoints === undefined ? null : (endpoints ?? ['chat']),
85
+ });
83
86
  }
84
87
 
85
88
  /**
@@ -366,7 +366,9 @@ export async function resolveCursorOAuthAccessToken({ forceRefresh = false, fetc
366
366
  try {
367
367
  saveCredentials({ ...persisted, reauth_required: true });
368
368
  } catch (saveError) {
369
- process.stderr.write(`[cursor-oauth] dead-account mark failed: ${String(saveError?.message || saveError).slice(0, 200)}\n`);
369
+ process.stderr.write(
370
+ `[cursor-oauth] dead-account mark failed: ${String(saveError?.message || saveError).slice(0, 200)}\n`
371
+ );
370
372
  }
371
373
  }
372
374
  }
@@ -4,7 +4,7 @@ import { createRemoteVersionSource } from './npm-cli-version.mjs';
4
4
  // documented version endpoint; the official installer script embeds the
5
5
  // current build (…/lab/<build>/…), parsed best-effort. Any format change or
6
6
  // failure falls back to the floor.
7
- export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.10.14-941492e';
7
+ export const CURSOR_CLIENT_VERSION_FLOOR = 'cli-2026.10.16-e3cb207';
8
8
  const BUILD_PATTERN = /downloads\.cursor\.com\/lab\/(\d{4}\.\d{2}\.\d{2}-[0-9a-f]{6,40})\//;
9
9
  export const CURSOR_INSTALL_URL = 'https://cursor.com/install';
10
10
 
@@ -0,0 +1,48 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+
4
+ const PREFIX = 'cursor_native_';
5
+ const pendingCalls = new Map();
6
+ const AUTHORIZATION_MS = 10 * 60 * 1000;
7
+
8
+ export function isCursorNativeCallId(id) {
9
+ return typeof id === 'string' && id.startsWith(PREFIX);
10
+ }
11
+
12
+ // Registered before streaming the call, including when a later frame is partial.
13
+ // Model arguments never grant this capability.
14
+ export function registerCursorNativeCall(ownerSessionId, pending, executions) {
15
+ if (!ownerSessionId || !pending.native) return;
16
+ if (pending.toolName !== 'read' && pending.toolName !== 'edit') return;
17
+ if (!['readResult', 'writeResult'].includes(pending.native.resultType)) return;
18
+ const now = Date.now();
19
+ for (const [id, entry] of pendingCalls) {
20
+ if (entry.expiresAt <= now) pendingCalls.delete(id);
21
+ }
22
+ if (pendingCalls.size >= 4096) throw new Error('Cursor native execution authorization capacity exceeded');
23
+ pending.toolCallId = `${PREFIX}${randomUUID()}`;
24
+ const entry = {
25
+ ownerSessionId,
26
+ pending,
27
+ executions,
28
+ expiresAt: now + AUTHORIZATION_MS,
29
+ arguments: JSON.parse(pending.decodedArgs),
30
+ };
31
+ pendingCalls.set(pending.toolCallId, entry);
32
+ }
33
+
34
+ export function cursorNativeCall(ownerSessionId, id, name, args) {
35
+ if (!isCursorNativeCallId(id)) return null;
36
+ const entry = pendingCalls.get(id);
37
+ if (!entry) throw new Error('Cursor native execution authorization is missing');
38
+ if (entry.expiresAt <= Date.now()) throw new Error('Cursor native execution authorization expired');
39
+ if (entry.ownerSessionId !== ownerSessionId) throw new Error('Cursor native execution authorization owner mismatch');
40
+ if (entry.pending.toolName !== name) throw new Error('Cursor native execution authorization tool mismatch');
41
+ if (!isDeepStrictEqual(entry.arguments, args))
42
+ throw new Error('Cursor native execution authorization argument mismatch');
43
+ return entry;
44
+ }
45
+
46
+ export function releaseCursorNativeCall(pending) {
47
+ pendingCalls.delete(pending.toolCallId);
48
+ }
@@ -1,11 +1,28 @@
1
1
  // Cursor exec/kv message handling: routing native exec requests to local
2
2
  // tools and sending results back on the bridge.
3
3
  import crypto from 'node:crypto';
4
+ import { BRIDGE_NAME_MAX_CHARS, isBridgeMetaTool } from '../runtime-core/tool-bridge-contract.mjs';
5
+ import { releaseCursorNativeCall } from './cursor-native-context.mjs';
4
6
  import { decodeJsonValue, encodeMessage } from './cursor-wire-protobuf.mjs';
5
7
  import { capCursorToolResult, storeCursorBlob } from './cursor-wire-guards.mjs';
6
8
  import { buildCursorExecThrow } from './cursor-wire-interactions.mjs';
7
9
  import { connectFrame, textDecoder, textEncoder } from './cursor-wire-transport.mjs';
8
10
  import { buildRequestContext } from './cursor-wire-request.mjs';
11
+ import { readSourceWindow } from '../../../shared/read-row-numbers.mjs';
12
+ import { splitGrepLineNumberOnlyPrefix, splitGrepCountPrefix } from '../tools/builtin/grep-formatting.mjs';
13
+
14
+ const nativeWriteDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
15
+
16
+ function nativeWriteText(args) {
17
+ if (!args.fileBytes?.length) return args.fileText || '';
18
+ try {
19
+ return nativeWriteDecoder.decode(args.fileBytes);
20
+ } catch {
21
+ throw Object.assign(new Error('Native Write fileBytes must be valid UTF-8; no file was written'), {
22
+ code: 'CURSOR_WRITE_ENCODING',
23
+ });
24
+ }
25
+ }
9
26
 
10
27
  export function heartbeatFrame() {
11
28
  return connectFrame(encodeMessage('AgentClientMessage', { clientHeartbeat: {} }));
@@ -70,12 +87,15 @@ const NATIVE_EXEC_CASES = [
70
87
  ],
71
88
  [
72
89
  'writeArgs',
73
- ['write'],
90
+ // Only the guarded edit tool: it creates missing files and fills empty
91
+ // ones, while its executor rejects overwrites of non-empty files. An
92
+ // explicit write tool would overwrite without any native capability
93
+ // (remote/proxied calls), so it never serves a native Write.
94
+ ['edit'],
74
95
  (tool, args) => ({
75
96
  [argumentName(tool, ['file_path', 'filePath', 'path'], 'file_path')]: args.path || '',
76
- [argumentName(tool, ['content', 'file_text', 'text'], 'content')]: args.fileBytes?.length
77
- ? textDecoder.decode(args.fileBytes)
78
- : args.fileText || '',
97
+ old_string: '',
98
+ new_string: nativeWriteText(args),
79
99
  }),
80
100
  'writeResult',
81
101
  ],
@@ -90,7 +110,9 @@ const NATIVE_EXEC_CASES = [
90
110
  pattern: args.pattern || '.',
91
111
  ...(args.path ? { path: args.path } : {}),
92
112
  ...(args.glob ? { glob: args.glob } : {}),
93
- mode: args.outputMode || 'content',
113
+ mode: args.outputMode === 'files_with_matches' ? 'files' : args.outputMode || 'content',
114
+ context: 0,
115
+ ...(args.headLimit > 0 ? { limit: args.headLimit } : {}),
94
116
  }),
95
117
  'grepResult',
96
118
  ],
@@ -130,6 +152,16 @@ const rejectedPath = (args) => ({ rejected: { path: args.path || '', reason: UNA
130
152
  const rejectedShell = (args) => ({
131
153
  rejected: { command: args.command || '', workingDirectory: args.workingDirectory || '', reason: UNAVAILABLE },
132
154
  });
155
+ const rejectedBackgroundShell = (args) => ({
156
+ rejected: {
157
+ command: args.command || '',
158
+ workingDirectory: args.workingDirectory || '',
159
+ reason:
160
+ 'Cursor background shell is not supported. Use the mixdog MCP shell tool with {"command":"..."}; ' +
161
+ 'if it returns task_id, use the mixdog task tool with {"action":"wait","task_id":"<returned id>"}. ' +
162
+ 'Use load_tool {"names":"task"} if task is not loaded.',
163
+ },
164
+ });
133
165
  const unavailableError = () => ({ error: { error: UNAVAILABLE } });
134
166
 
135
167
  // Native exec cases with no Mixdog tool behind them: [exec key, exec result
@@ -143,7 +175,7 @@ const UNAVAILABLE_EXEC_RESULTS = [
143
175
  ['fetchArgs', 'fetchResult', (args) => ({ error: { url: args.url || '', error: UNAVAILABLE } })],
144
176
  ['shellArgs', 'shellResult', rejectedShell],
145
177
  ['shellStreamArgs', 'shellResult', rejectedShell],
146
- ['backgroundShellSpawnArgs', 'backgroundShellSpawnResult', rejectedShell],
178
+ ['backgroundShellSpawnArgs', 'backgroundShellSpawnResult', rejectedBackgroundShell],
147
179
  ['writeShellStdinArgs', 'writeShellStdinResult', unavailableError],
148
180
  ['diagnosticsArgs', 'diagnosticsResult', () => ({})],
149
181
  ];
@@ -163,18 +195,44 @@ function rejectUnavailableExec(bridge, exec) {
163
195
  function handleMcpExec(bridge, exec, tools, onToolCall) {
164
196
  const args = exec.mcpArgs;
165
197
  const requestedName = args.toolName || args.name;
166
- const tool = toolByNames(tools, [requestedName]);
198
+ const directTool = toolByNames(tools, [requestedName]);
199
+ // Only the bridge granted in this request can resolve a deferred name.
200
+ // The ordinary runtime still projects the target and enforces its policy.
201
+ let tool =
202
+ directTool ||
203
+ (typeof requestedName === 'string' && requestedName.length > 0 ? toolByNames(tools, ['call_tool']) : null);
167
204
  if (!tool) {
168
205
  sendExecResult(bridge, exec, 'mcpResult', {
169
206
  error: { error: `Tool not available: ${requestedName || 'unknown'}` },
170
207
  });
171
208
  return;
172
209
  }
210
+ let decodedArgs = decodeMcpArgs(args.args);
211
+ if (
212
+ requestedName === 'call_tool' &&
213
+ directTool &&
214
+ Object.keys(decodedArgs).length === 2 &&
215
+ Object.hasOwn(decodedArgs, 'name') &&
216
+ Object.hasOwn(decodedArgs, 'arguments') &&
217
+ typeof decodedArgs.name === 'string' &&
218
+ decodedArgs.name.length > 0 &&
219
+ decodedArgs.name.length <= BRIDGE_NAME_MAX_CHARS &&
220
+ !isBridgeMetaTool(decodedArgs.name) &&
221
+ decodedArgs.arguments !== null &&
222
+ typeof decodedArgs.arguments === 'object' &&
223
+ !Array.isArray(decodedArgs.arguments)
224
+ ) {
225
+ const target = toolByNames(tools, [decodedArgs.name]);
226
+ if (target) {
227
+ tool = target;
228
+ decodedArgs = decodedArgs.arguments;
229
+ }
230
+ }
173
231
  onToolCall({
174
232
  exec,
175
233
  toolCallId: args.toolCallId || crypto.randomUUID(),
176
234
  toolName: tool.name,
177
- decodedArgs: JSON.stringify(decodeMcpArgs(args.args)),
235
+ decodedArgs: JSON.stringify(directTool ? decodedArgs : { name: requestedName, arguments: decodedArgs }),
178
236
  });
179
237
  }
180
238
 
@@ -191,7 +249,14 @@ export function handleExecMessage(bridge, exec, tools, cloudRule, onToolCall) {
191
249
  handleMcpExec(bridge, exec, tools, onToolCall);
192
250
  return;
193
251
  }
194
- const redirect = redirectNativeExec(exec, tools);
252
+ let redirect;
253
+ try {
254
+ redirect = redirectNativeExec(exec, tools);
255
+ } catch (error) {
256
+ if (error.code !== 'CURSOR_WRITE_ENCODING') throw error;
257
+ sendExecResult(bridge, exec, 'writeResult', { error: { path: exec.writeArgs.path || '', error: error.message } });
258
+ return;
259
+ }
195
260
  if (redirect) {
196
261
  onToolCall(redirect);
197
262
  return;
@@ -227,31 +292,41 @@ function parseListedPaths(text, rootPath) {
227
292
 
228
293
  function parseGrepResult(text, args) {
229
294
  const mode = args.outputMode || 'content';
295
+ const lines = text.split(/\r?\n/).filter((line) => line && !line.startsWith('[total ') && !line.startsWith('... ['));
230
296
  if (mode === 'files_with_matches') {
231
- const files = text
232
- .split(/\r?\n/)
233
- .map((line) => line.trim())
234
- .filter(Boolean);
297
+ const files = lines.map((line) => line.trim()).filter((line) => !line.startsWith('(no matches)'));
235
298
  return { files: { files, totalFiles: files.length } };
236
299
  }
237
300
  if (mode === 'count') {
238
301
  const counts = [];
239
302
  let totalMatches = 0;
240
- for (const line of text.split(/\r?\n/)) {
241
- const match = line.match(/^(.*):(\d+)$/);
242
- if (!match) continue;
243
- const count = Number(match[2]);
244
- counts.push({ file: match[1], count });
303
+ for (const line of lines) {
304
+ const parsed = splitGrepCountPrefix(line);
305
+ if (!parsed && !/^\d+$/.test(line)) continue;
306
+ const count = parsed ? parsed.count : Number(line);
307
+ counts.push({ file: parsed?.path || args.path, count });
245
308
  totalMatches += count;
246
309
  }
247
310
  return { count: { counts, totalFiles: counts.length, totalMatches } };
248
311
  }
249
312
  const byFile = new Map();
250
- for (const line of text.split(/\r?\n/)) {
251
- const match = line.match(/^(.+?):(\d+):(.*)$/);
252
- if (!match) continue;
253
- if (!byFile.has(match[1])) byFile.set(match[1], []);
254
- byFile.get(match[1]).push({ lineNumber: Number(match[2]), content: match[3] });
313
+ let currentFile = args.path;
314
+ for (const line of lines) {
315
+ const grouped = /^ {2}(\d+): (.*)$/.exec(line);
316
+ // Native searches request no context: only :N: is a line delimiter.
317
+ // A -N- segment may be part of the filename, not a context-line prefix.
318
+ const match = grouped ? null : /^(.+?):(\d+):/.exec(line);
319
+ const prefix = match ? { path: match[1], lineNo: Number(match[2]), markerEnd: match[0].length } : null;
320
+ const single = grouped || prefix ? null : splitGrepLineNumberOnlyPrefix(line);
321
+ if (!grouped && !prefix && !single) {
322
+ currentFile = line.replace(/ \(\d+ hits\)$/, '');
323
+ continue;
324
+ }
325
+ const file = prefix?.path || currentFile;
326
+ const lineNumber = grouped ? Number(grouped[1]) : (prefix || single).lineNo;
327
+ const content = grouped ? grouped[2] : line.slice((prefix || single).markerEnd);
328
+ if (!byFile.has(file)) byFile.set(file, []);
329
+ byFile.get(file).push({ lineNumber, content });
255
330
  }
256
331
  const matches = [...byFile].map(([file, fileMatches]) => ({ file, matches: fileMatches }));
257
332
  const matchedLines = matches.reduce((sum, entry) => sum + entry.matches.length, 0);
@@ -290,7 +365,7 @@ function sendShellStream(bridge, exec, text, code) {
290
365
  // rejected shell command.
291
366
  const NATIVE_FAILURE_PAYLOADS = {
292
367
  readResult: (args, text) => ({ rejected: { path: args.path || '', reason: text || 'Read failed' } }),
293
- writeResult: (args, text) => ({ rejected: { path: args.path || '', reason: text || 'Write failed' } }),
368
+ writeResult: (args, text) => ({ error: { path: args.path || '', error: text || 'Write failed' } }),
294
369
  fetchResult: (args, text) => ({ error: { url: args.url || '', error: text || 'Fetch failed' } }),
295
370
  lsResult: (args, text) => ({ rejected: { path: args.path || '', reason: text || 'List failed' } }),
296
371
  grepResult: (_args, text) => ({ error: { error: text || 'Grep failed' } }),
@@ -304,16 +379,30 @@ const NATIVE_FAILURE_PAYLOADS = {
304
379
  };
305
380
 
306
381
  const NATIVE_SUCCESS_PAYLOADS = {
307
- readResult: (args, text) => ({
308
- success: {
309
- path: args.path || '',
310
- content: text,
311
- totalLines: text ? text.split(/\r?\n/).length : 0,
312
- fileSize: textEncoder.encode(text).length,
313
- },
314
- }),
382
+ readResult: (args, text, pending, result) => {
383
+ const source = readSourceWindow(text);
384
+ // The read executor reports per-line clipping out of band: on the
385
+ // pending call (local) or on the result (remote/proxied).
386
+ if (!source || pending.nativeReadWindow?.lineClipped || result?.readWindow?.lineClipped)
387
+ return {
388
+ rejected: {
389
+ path: args.path || '',
390
+ reason:
391
+ 'This read result is not a complete contiguous source window. Use the mixdog MCP read tool with file_path, offset and limit; do not treat display markers as file content.',
392
+ },
393
+ };
394
+ return {
395
+ success: {
396
+ path: args.path || '',
397
+ content: source.content,
398
+ ...(source.totalLines === undefined ? {} : { totalLines: source.totalLines }),
399
+ truncated: source.truncated,
400
+ rangeApplied: Boolean(args.offset || args.limit || (source.startLine != null && source.startLine > 1)),
401
+ },
402
+ };
403
+ },
315
404
  writeResult: (args) => {
316
- const content = args.fileBytes?.length ? textDecoder.decode(args.fileBytes) : args.fileText || '';
405
+ const content = nativeWriteText(args);
317
406
  return {
318
407
  success: {
319
408
  path: args.path || '',
@@ -345,7 +434,9 @@ const NATIVE_SUCCESS_PAYLOADS = {
345
434
  };
346
435
 
347
436
  export function sendToolResult(bridge, pending, result, ok) {
437
+ releaseCursorNativeCall(pending);
348
438
  result = capCursorToolResult(result);
439
+ pending.completedResult = { result, ok };
349
440
  const { exec, native } = pending;
350
441
  const text = String(result?.content ?? '');
351
442
  const media = Array.isArray(result?.media) ? result.media : [];
@@ -354,6 +445,21 @@ export function sendToolResult(bridge, pending, result, ok) {
354
445
  return;
355
446
  }
356
447
  const { args, resultType } = native;
448
+ if (ok && resultType === 'readResult' && pending.nativeReadReply) {
449
+ let reply = pending.nativeReadReply;
450
+ if (pending.nativeUserMessage) {
451
+ reply = {
452
+ rejected: {
453
+ path: args.path || '',
454
+ reason: `Read interrupted by a new user instruction: ${pending.nativeUserMessage}`,
455
+ },
456
+ };
457
+ } else if (pending.nativeSnapshot) {
458
+ pending.nativeSnapshot.delivered = true;
459
+ }
460
+ sendExecResult(bridge, exec, resultType, reply);
461
+ return;
462
+ }
357
463
  if (resultType === 'shellStreamResult') {
358
464
  sendShellStream(bridge, exec, text, ok ? 0 : 1);
359
465
  return;
@@ -364,6 +470,10 @@ export function sendToolResult(bridge, pending, result, ok) {
364
470
  return;
365
471
  }
366
472
  if (Object.hasOwn(NATIVE_SUCCESS_PAYLOADS, resultType)) {
367
- sendExecResult(bridge, exec, resultType, NATIVE_SUCCESS_PAYLOADS[resultType](args, text));
473
+ const value = NATIVE_SUCCESS_PAYLOADS[resultType](args, text, pending, result);
474
+ if (resultType === 'writeResult' && Number.isInteger(pending.nativeWrittenBytes)) {
475
+ value.success.fileSize = pending.nativeWrittenBytes;
476
+ }
477
+ sendExecResult(bridge, exec, resultType, value);
368
478
  }
369
479
  }
@@ -87,7 +87,19 @@ const SCHEMAS = {
87
87
  ['clientHeartbeat', 7, 'ClientHeartbeat'],
88
88
  ]),
89
89
  TextDeltaUpdate: fields([['text', 1, 'string']]),
90
- ToolCall: fields([['mcpToolCall', 15, 'McpToolCall']]),
90
+ ToolCall: fields([
91
+ ['editToolCall', 12, 'NativeEditToolCall'],
92
+ ['mcpToolCall', 15, 'McpToolCall'],
93
+ ]),
94
+ NativeEditToolCall: fields([['result', 2, 'NativeEditResult']]),
95
+ NativeEditResult: fields([
96
+ ['success', 1, 'raw'],
97
+ ['fileNotFound', 2, 'raw'],
98
+ ['readPermissionDenied', 3, 'raw'],
99
+ ['writePermissionDenied', 4, 'raw'],
100
+ ['rejected', 6, 'raw'],
101
+ ['error', 7, 'raw'],
102
+ ]),
91
103
  McpToolCall: fields([
92
104
  ['args', 1, 'McpArgs'],
93
105
  ['result', 2, 'McpResult'],
@@ -422,6 +434,7 @@ const SCHEMAS = {
422
434
  ['totalLines', 3, 'int'],
423
435
  ['fileSize', 4, 'int'],
424
436
  ['truncated', 6, 'bool'],
437
+ ['rangeApplied', 8, 'bool'],
425
438
  ]),
426
439
  ReadRejected: fields([
427
440
  ['path', 1, 'string'],
@@ -430,7 +443,9 @@ const SCHEMAS = {
430
443
  ReadResult: fields([
431
444
  ['success', 1, 'ReadSuccess'],
432
445
  ['rejected', 3, 'ReadRejected'],
446
+ ['fileNotFound', 4, 'ReadFileNotFound'],
433
447
  ]),
448
+ ReadFileNotFound: fields([['path', 1, 'string']]),
434
449
  WriteSuccess: fields([
435
450
  ['path', 1, 'string'],
436
451
  ['linesCreated', 2, 'int'],
@@ -440,8 +455,13 @@ const SCHEMAS = {
440
455
  ['path', 1, 'string'],
441
456
  ['reason', 2, 'string'],
442
457
  ]),
458
+ WriteError: fields([
459
+ ['path', 1, 'string'],
460
+ ['error', 2, 'string'],
461
+ ]),
443
462
  WriteResult: fields([
444
463
  ['success', 1, 'WriteSuccess'],
464
+ ['error', 5, 'WriteError'],
445
465
  ['rejected', 6, 'WriteRejected'],
446
466
  ]),
447
467
  DeleteRejected: fields([
@@ -610,7 +630,7 @@ const SCHEMAS = {
610
630
  JsonValue: fields([
611
631
  ['nullValue', 1, 'int'],
612
632
  ['numberValue', 2, 'double'],
613
- ['stringValue', 3, 'string'],
633
+ ['stringValue', 3, 'string', { presence: true }],
614
634
  ['boolValue', 4, 'bool'],
615
635
  ['structValue', 5, 'JsonStruct'],
616
636
  ['listValue', 6, 'JsonList'],
@@ -620,7 +640,8 @@ const SCHEMAS = {
620
640
  };
621
641
 
622
642
  const textEncoder = new TextEncoder();
623
- const textDecoder = new TextDecoder();
643
+ // A protobuf string is data, not a text document with a disposable BOM.
644
+ const textDecoder = new TextDecoder('utf-8', { ignoreBOM: true });
624
645
 
625
646
  function concatBytes(parts) {
626
647
  const length = parts.reduce((sum, part) => sum + part.length, 0);
@@ -709,7 +730,10 @@ export function encodeMessage(type, value = {}) {
709
730
  let values = [fieldValue];
710
731
  if (descriptor.repeated) values = Array.isArray(fieldValue) ? fieldValue : [];
711
732
  for (const item of values) {
712
- if (shouldWrite(descriptor.type, item)) parts.push(encodeField(descriptor.no, descriptor.type, item));
733
+ // A oneof's explicit empty string is a value, not an absent field.
734
+ if ((descriptor.presence && item != null) || shouldWrite(descriptor.type, item)) {
735
+ parts.push(encodeField(descriptor.no, descriptor.type, item));
736
+ }
713
737
  }
714
738
  }
715
739
  return concatBytes(parts);