mixdog 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -1,11 +1,18 @@
1
1
  // Grok OAuth browser login + PKCE exchange against xAI's shared OAuth client
2
- // (the consent screen renders it as "Grok Build").
2
+ // (the consent screen renders it as "Grok Build"). A machine with no browser
3
+ // (`openBrowser: false`) signs in by RFC 8628 device code instead.
3
4
  import { createServer } from 'node:http';
4
5
  import { randomBytes } from 'node:crypto';
5
6
  import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
6
7
  import { createOAuthPkce, parseOAuthCodeInput } from './lib/oauth-pkce.mjs';
7
8
  import { OAUTH_PAGE_CONTENT_TYPE, oauthErrorHtml, oauthSuccessHtml } from './lib/oauth-page.mjs';
8
- import { emailFromJwts } from './lib/oauth-token-utils.mjs';
9
+ import {
10
+ emailFromJwts,
11
+ oauthFailureReason,
12
+ oauthHttpError,
13
+ oauthRequestError,
14
+ oauthResponseJson,
15
+ } from './lib/oauth-token-utils.mjs';
9
16
  import {
10
17
  CLIENT_ID,
11
18
  SCOPE,
@@ -22,47 +29,30 @@ import {
22
29
  _scrubTokens,
23
30
  } from './grok-oauth-tokens.mjs';
24
31
 
32
+ // The device flow is xAI's own headless path (`grok login --device-auth`): the
33
+ // user opens the URL on any device and confirms the code while this host polls,
34
+ // so no loopback listener, port forward or pasted redirect is involved.
35
+ const DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code';
36
+ // The path the issuer's discovery document advertises as its
37
+ // device_authorization_endpoint; the token endpoint is the discovered one.
38
+ const DEVICE_CODE_PATH = '/oauth2/device/code';
39
+ const DEFAULT_DEVICE_INTERVAL_S = 5;
40
+ const SLOW_DOWN_STEP_S = 5;
41
+ // The Grok CLI keeps polling for at least ten minutes whatever expires_in says.
42
+ const MIN_DEVICE_EXPIRY_S = 600;
43
+ const SAFE_USER_CODE = /^[A-Za-z0-9-]+$/;
44
+
25
45
  export function generatePKCE() {
26
46
  return createOAuthPkce();
27
47
  }
28
48
 
29
- export async function exchangeAuthorizationCode({ discovery, pkce, code, signal = null }) {
30
- signal?.throwIfAborted();
31
- const cleanCode = String(code || '').trim();
32
- if (!cleanCode) throw new Error('[grok-oauth] authorization code is required');
33
- const tokenRes = await fetch(discovery.token_endpoint, {
34
- method: 'POST',
35
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
36
- body: new URLSearchParams({
37
- grant_type: 'authorization_code',
38
- client_id: CLIENT_ID,
39
- code: cleanCode,
40
- code_verifier: pkce.verifier,
41
- redirect_uri: REDIRECT_URI,
42
- // xAI re-validates the PKCE challenge at token exchange
43
- // (not just the verifier), so echo it back. Omitting
44
- // these makes the exchange fail. Matches the Grok CLI.
45
- code_challenge: pkce.challenge,
46
- code_challenge_method: 'S256',
47
- }),
48
- // Secret-bearing (authorization code + verifier): refuse
49
- // redirects so they can't be replayed to an untrusted host.
50
- redirect: 'error',
51
- signal: signal
52
- ? AbortSignal.any([signal, AbortSignal.timeout(TOKEN_TIMEOUT_MS)])
53
- : AbortSignal.timeout(TOKEN_TIMEOUT_MS),
54
- dispatcher: getLlmDispatcher(),
55
- });
56
- if (!tokenRes.ok) {
57
- const text = await tokenRes.text().catch(() => '');
58
- throw new Error(`[grok-oauth] token exchange ${tokenRes.status}: ${_scrubTokens(text).slice(0, 500)}`);
59
- }
60
- const json = await tokenRes.json();
49
+ // The tokens a successful grant (authorization code or device code) yields.
50
+ function tokensFromGrant(json, discovery) {
61
51
  if (!json.access_token || !json.refresh_token) {
62
52
  throw new Error('[grok-oauth] token exchange response missing access_token or refresh_token');
63
53
  }
64
54
  const identity = _identityFromAccessToken(json.access_token);
65
- const tokens = {
55
+ return {
66
56
  access_token: json.access_token,
67
57
  refresh_token: json.refresh_token,
68
58
  expires_at:
@@ -75,14 +65,229 @@ export async function exchangeAuthorizationCode({ discovery, pkce, code, signal
75
65
  ? { email: emailFromJwts(json.id_token, json.access_token) }
76
66
  : {}),
77
67
  };
68
+ }
69
+
70
+ export async function exchangeAuthorizationCode({ discovery, pkce, code, signal = null }) {
71
+ signal?.throwIfAborted();
72
+ const cleanCode = String(code || '').trim();
73
+ if (!cleanCode) throw new Error('[grok-oauth] authorization code is required');
74
+ let tokenRes;
75
+ try {
76
+ tokenRes = await fetch(discovery.token_endpoint, {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
79
+ body: new URLSearchParams({
80
+ grant_type: 'authorization_code',
81
+ client_id: CLIENT_ID,
82
+ code: cleanCode,
83
+ code_verifier: pkce.verifier,
84
+ redirect_uri: REDIRECT_URI,
85
+ // xAI re-validates the PKCE challenge at token exchange
86
+ // (not just the verifier), so echo it back. Omitting
87
+ // these makes the exchange fail. Matches the Grok CLI.
88
+ code_challenge: pkce.challenge,
89
+ code_challenge_method: 'S256',
90
+ }),
91
+ // Secret-bearing (authorization code + verifier): refuse
92
+ // redirects so they can't be replayed to an untrusted host.
93
+ redirect: 'error',
94
+ signal: signal
95
+ ? AbortSignal.any([signal, AbortSignal.timeout(TOKEN_TIMEOUT_MS)])
96
+ : AbortSignal.timeout(TOKEN_TIMEOUT_MS),
97
+ dispatcher: getLlmDispatcher(),
98
+ });
99
+ } catch (err) {
100
+ throw oauthRequestError('[grok-oauth] token exchange', err);
101
+ }
102
+ if (!tokenRes.ok) {
103
+ const text = await tokenRes.text().catch(() => '');
104
+ throw oauthHttpError('[grok-oauth] token exchange', tokenRes.status, text, [
105
+ cleanCode,
106
+ pkce.verifier,
107
+ pkce.challenge,
108
+ ]);
109
+ }
110
+ const tokens = tokensFromGrant(await oauthResponseJson(tokenRes, '[grok-oauth] token exchange'), discovery);
78
111
  // A cancelled login never stores the account it was exchanging.
79
112
  signal?.throwIfAborted();
80
113
  saveTokens(tokens);
81
114
  return tokens;
82
115
  }
83
116
 
84
- export async function beginOAuthLogin({ openBrowser = true } = {}) {
117
+ // Not sleepWithAbort: grok-oauth-isolated.test.mjs loads this file in a VM
118
+ // sandbox that admits no dependency beyond its reviewed list.
119
+ const defaultSleep = (ms, signal) =>
120
+ new Promise((resolve, reject) => {
121
+ if (signal.aborted) return reject(signal.reason);
122
+ const onAbort = () => {
123
+ clearTimeout(timer);
124
+ reject(signal.reason);
125
+ };
126
+ const timer = setTimeout(() => {
127
+ signal.removeEventListener('abort', onAbort);
128
+ resolve();
129
+ }, ms);
130
+ signal.addEventListener('abort', onAbort, { once: true });
131
+ });
132
+
133
+ const hasAsciiControl = (text) => [...text].some((char) => char.charCodeAt(0) < 0x20 || char.charCodeAt(0) === 0x7f);
134
+
135
+ // The Grok CLI's verification-URI rules (validate_verification_uri): a string
136
+ // without ASCII control characters (they could reach the terminal) that parses
137
+ // as a URL and is https, or http only for localhost / 127.0.0.1.
138
+ function verificationUri(value, field) {
139
+ if (typeof value !== 'string') throw new Error(`[grok-oauth] device authorization ${field} must be a string`);
140
+ if (hasAsciiControl(value) || !URL.canParse(value)) {
141
+ throw new Error('[grok-oauth] device authorization returned an invalid verification URI');
142
+ }
143
+ const { protocol, hostname } = new URL(value);
144
+ const loopback = protocol === 'http:' && (hostname === 'localhost' || hostname === '127.0.0.1');
145
+ if (protocol !== 'https:' && !loopback) {
146
+ throw new Error('[grok-oauth] device authorization returned an unsupported verification URI scheme');
147
+ }
148
+ return value;
149
+ }
150
+
151
+ // The vendor reads these as typed integers: a string or a fraction is a
152
+ // malformed response.
153
+ function integerField(value, field) {
154
+ if (!Number.isInteger(value)) {
155
+ throw new Error(`[grok-oauth] device authorization ${field} must be an integer`);
156
+ }
157
+ return value;
158
+ }
159
+
160
+ const requestSignal = (signal) => AbortSignal.any([signal, AbortSignal.timeout(TOKEN_TIMEOUT_MS)]);
161
+
162
+ function postDeviceForm(url, params, signal) {
163
+ return fetch(url, {
164
+ method: 'POST',
165
+ headers: { Accept: 'application/json', 'Content-Type': 'application/x-www-form-urlencoded' },
166
+ body: new URLSearchParams(params),
167
+ // The device code is secret-bearing: refuse redirects.
168
+ redirect: 'error',
169
+ signal: requestSignal(signal),
170
+ dispatcher: getLlmDispatcher(),
171
+ });
172
+ }
173
+
174
+ // null when the issuer has no device flow (404), as the Grok CLI reads it:
175
+ // the caller then falls back to the loopback login.
176
+ async function requestDeviceCode(discovery, signal) {
177
+ const res = await postDeviceForm(
178
+ new URL(DEVICE_CODE_PATH, discovery.token_endpoint).href,
179
+ { client_id: CLIENT_ID, scope: SCOPE },
180
+ signal
181
+ );
182
+ if (res.status === 404) return null;
183
+ if (!res.ok) {
184
+ const text = await res.text().catch(() => '');
185
+ throw new Error(`[grok-oauth] device authorization ${res.status}: ${_scrubTokens(text).slice(0, 300)}`);
186
+ }
187
+ const json = await res.json().catch(() => null);
188
+ const deviceCode = typeof json?.device_code === 'string' ? json.device_code : '';
189
+ const userCode = typeof json?.user_code === 'string' ? json.user_code : '';
190
+ if (!deviceCode || !SAFE_USER_CODE.test(userCode)) {
191
+ throw new Error('[grok-oauth] device authorization response is missing or has invalid required fields');
192
+ }
193
+ // Both addresses are checked on their own: an invalid one fails the login
194
+ // rather than yielding to the other. The complete one (code pre-filled) wins.
195
+ const plainUrl = verificationUri(json.verification_uri, 'verification_uri');
196
+ const completeUrl = json.verification_uri_complete ?? null;
197
+ const verificationUrl = completeUrl === null ? plainUrl : verificationUri(completeUrl, 'verification_uri_complete');
198
+ const interval = json.interval ?? null;
199
+ return {
200
+ deviceCode,
201
+ userCode,
202
+ verificationUrl,
203
+ // 5 s only when the interval is omitted; zero or a negative value clamps to one second.
204
+ intervalS: Math.max(interval === null ? DEFAULT_DEVICE_INTERVAL_S : integerField(interval, 'interval'), 1),
205
+ expiresInS: Math.max(integerField(json.expires_in, 'expires_in'), MIN_DEVICE_EXPIRY_S),
206
+ };
207
+ }
208
+
209
+ const deviceExpired = () => new Error('[grok-oauth] device code expired — start the login again');
210
+
211
+ /** Poll until approval, denial or expiry; resolves the token response of the grant. */
212
+ async function pollDeviceGrant(device, discovery, { signal, sleep, now }) {
213
+ const deadline = now() + device.expiresInS * 1000;
214
+ let intervalMs = device.intervalS * 1000;
215
+ for (;;) {
216
+ // A poll straight after the code was issued only answers authorization_pending.
217
+ await sleep(intervalMs, signal);
218
+ if (now() > deadline) throw deviceExpired();
219
+ let res;
220
+ let text;
221
+ try {
222
+ res = await postDeviceForm(
223
+ discovery.token_endpoint,
224
+ { grant_type: DEVICE_GRANT_TYPE, device_code: device.deviceCode, client_id: CLIENT_ID },
225
+ signal
226
+ );
227
+ text = await res.text();
228
+ } catch (err) {
229
+ // As in the Grok CLI, a transport failure ends the login at once.
230
+ throw new Error(`[grok-oauth] device token request failed: ${_scrubTokens(String(err?.message || err))}`);
231
+ }
232
+ let json = null;
233
+ try {
234
+ json = text ? JSON.parse(text) : null;
235
+ } catch {
236
+ /* a non-JSON body is reported below */
237
+ }
238
+ if (res.ok && json?.access_token) return json;
239
+ const error = typeof json?.error === 'string' ? json.error : '';
240
+ // Only these two answers keep the poll going; anything else ends the login.
241
+ if (error === 'slow_down') {
242
+ intervalMs += SLOW_DOWN_STEP_S * 1000;
243
+ } else if (error === 'access_denied') {
244
+ throw new Error('[grok-oauth] sign-in was denied');
245
+ } else if (error === 'expired_token') {
246
+ throw deviceExpired();
247
+ } else if (error !== 'authorization_pending') {
248
+ throw new Error(`[grok-oauth] device token ${res.status}: ${error || _scrubTokens(text).slice(0, 300)}`);
249
+ }
250
+ }
251
+ }
252
+
253
+ async function beginDeviceLogin(discovery, { sleep, now }) {
254
+ const controller = new AbortController();
255
+ const device = await requestDeviceCode(discovery, controller.signal);
256
+ if (!device) return null;
257
+ process.stderr.write(
258
+ `\n[grok-oauth] Enter code: ${device.userCode}\n[grok-oauth] Open: ${device.verificationUrl}\n\n`
259
+ );
260
+ const waitForCallback = pollDeviceGrant(device, discovery, { signal: controller.signal, sleep, now })
261
+ .then((json) => {
262
+ const tokens = tokensFromGrant(json, discovery);
263
+ // A cancelled login never stores the account it was granted.
264
+ controller.signal.throwIfAborted();
265
+ saveTokens(tokens);
266
+ return tokens;
267
+ })
268
+ .catch((error) => {
269
+ // Cancelling settles quietly, as it does for the loopback login.
270
+ if (controller.signal.aborted) return null;
271
+ throw error;
272
+ });
273
+ // A caller may only ever cancel; a later failure must not be unhandled.
274
+ waitForCallback.catch(() => {});
275
+ return {
276
+ provider: 'grok-oauth',
277
+ url: device.verificationUrl,
278
+ manualUrl: null,
279
+ userCode: device.userCode,
280
+ waitForCallback,
281
+ cancel: () => controller.abort(new Error('Grok login cancelled')),
282
+ };
283
+ }
284
+
285
+ export async function beginOAuthLogin({ openBrowser = true, sleep = defaultSleep, now = Date.now } = {}) {
85
286
  const discovery = await fetchDiscovery();
287
+ if (!openBrowser) {
288
+ const device = await beginDeviceLogin(discovery, { sleep, now });
289
+ if (device) return device;
290
+ }
86
291
  const pkce = generatePKCE();
87
292
  const state = randomBytes(16).toString('hex');
88
293
  const nonce = randomBytes(16).toString('hex');
@@ -148,14 +353,16 @@ export async function beginOAuthLogin({ openBrowser = true } = {}) {
148
353
  res.end(oauthErrorHtml('The sign-in callback was invalid or did not match this login.'));
149
354
  return;
150
355
  }
356
+ // The login settles (closing its server) before this answer is sent, so
357
+ // the connection must not stay open to the closed login's handler.
151
358
  try {
152
359
  await acceptCode(code);
153
- res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
360
+ res.writeHead(200, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE, Connection: 'close' });
154
361
  res.end(oauthSuccessHtml('Grok'));
155
- } catch {
156
- // The shared exchange reports its error through waitForCallback.
157
- res.writeHead(500, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE });
158
- res.end(oauthErrorHtml('Grok could not complete the token exchange.'));
362
+ } catch (err) {
363
+ // The shared exchange also reports its error through waitForCallback.
364
+ res.writeHead(500, { 'Content-Type': OAUTH_PAGE_CONTENT_TYPE, Connection: 'close' });
365
+ res.end(oauthErrorHtml(oauthFailureReason('Grok could not complete the token exchange.', err)));
159
366
  }
160
367
  });
161
368
  timeout = setTimeout(() => finish(null), LOGIN_TIMEOUT_MS);
@@ -1,9 +1,13 @@
1
1
  // Diagnostic per-block prefix fingerprint of an Anthropic request: the tools,
2
- // each system block (BP1/BP2/BP3/env, the routing prefix included) and the
3
- // cache_control marker positions, as short hashes only — never any text, so
4
- // it is safe in shipped builds. The last two requests per session are kept in
5
- // memory so a usage-detected cache miss (session/loop/send-phase.mjs) can say
6
- // which block or marker moved.
2
+ // each system block (BP1/BP2/BP3/env, the routing prefix included), the
3
+ // thinking parameters (they key the message cache), the cache_control marker
4
+ // positions and, at each marked message, a chained hash of the messages up to
5
+ // it — as short hashes only, never any text, so it is safe in shipped builds.
6
+ // The last two requests per session are kept in memory so a usage-detected
7
+ // cache miss (session/loop/send-phase.mjs) can say which block, marker or
8
+ // message breakpoint moved. The latest one also rides on the session record
9
+ // (persistableAnthropicPrefix / seedAnthropicPrefix), so the first request
10
+ // after a restart is compared with the last one before it.
7
11
  import { createHash } from 'node:crypto';
8
12
 
9
13
  const HASH_CHARS = 12;
@@ -22,9 +26,26 @@ const withoutMarker = (block) => {
22
26
  return rest;
23
27
  };
24
28
 
29
+ // A message as the cache reads it: markers stripped, a string content as the
30
+ // one text block a marker would turn it into.
31
+ const messageWithoutMarkers = (message) => {
32
+ const content =
33
+ typeof message?.content === 'string' ? [{ type: 'text', text: message.content }] : message?.content || [];
34
+ return { ...message, content: Array.isArray(content) ? content.map(withoutMarker) : content };
35
+ };
36
+
25
37
  const markerTtl = (marker) => (marker?.ttl ? String(marker.ttl) : '5m');
26
38
 
27
- /** `{ tools, system: [hash…], markers: ['s1:1h', 'm12:5m', …] }` of a request body. */
39
+ /** The session id a request's prefix is recorded under: the visible Mixdog
40
+ * session, not the account-scoped id an account roster sends under. */
41
+ export function anthropicPrefixSessionId(opts) {
42
+ return opts?.usageSessionId || opts?.sessionId || opts?.session?.id || null;
43
+ }
44
+
45
+ /** `{ tools, system: [hash…], params, markers: ['s1:1h', 'm12:5m', …],
46
+ * messageMarks: [{ i, h }] }` of a request body; the full message hash chain
47
+ * rides along non-enumerable (memory only) for the next request's
48
+ * comparison. */
28
49
  export function anthropicPrefixFingerprint(body) {
29
50
  const system = Array.isArray(body?.system) ? body.system : [];
30
51
  const messages = Array.isArray(body?.messages) ? body.messages : [];
@@ -32,12 +53,47 @@ export function anthropicPrefixFingerprint(body) {
32
53
  system.forEach((block, index) => {
33
54
  if (block?.cache_control) markers.push(`s${index}:${markerTtl(block.cache_control)}`);
34
55
  });
56
+ const params = hash({ thinking: body?.thinking ?? null, output_config: body?.output_config ?? null });
57
+ const chain = [];
58
+ const messageMarks = [];
59
+ let link = params;
35
60
  messages.forEach((message, index) => {
61
+ link = hash([link, messageWithoutMarkers(message)]);
62
+ chain.push(link);
36
63
  const content = Array.isArray(message?.content) ? message.content : [];
37
64
  const marked = content.find((block) => block?.cache_control);
38
- if (marked) markers.push(`m${index}:${markerTtl(marked.cache_control)}`);
65
+ if (marked) {
66
+ markers.push(`m${index}:${markerTtl(marked.cache_control)}`);
67
+ messageMarks.push({ i: index, h: link });
68
+ }
39
69
  });
40
- return { tools: hash(body?.tools || []), system: system.map((block) => hash(withoutMarker(block))), markers };
70
+ const fingerprint = {
71
+ tools: hash(body?.tools || []),
72
+ system: system.map((block) => hash(withoutMarker(block))),
73
+ params,
74
+ markers,
75
+ messageMarks,
76
+ };
77
+ Object.defineProperty(fingerprint, 'chain', { value: chain, enumerable: false });
78
+ return fingerprint;
79
+ }
80
+
81
+ /** `fingerprint` as the session record keeps it (hashes only, no chain). */
82
+ export function persistableAnthropicPrefix(fingerprint) {
83
+ if (!fingerprint) return null;
84
+ const { tools, system, params, markers, messageMarks } = fingerprint;
85
+ return { tools, system, params, markers, messageMarks };
86
+ }
87
+
88
+ /** A restarted process has no previous request for the session: take the
89
+ * one its record kept (persistableAnthropicPrefix) as the latest, so the
90
+ * next request is compared with it. A no-op once the session has one. */
91
+ export function seedAnthropicPrefix(sessionId, fingerprint) {
92
+ if (!sessionId || !fingerprint || typeof fingerprint !== 'object') return;
93
+ const key = String(sessionId);
94
+ if (bySession.has(key)) return;
95
+ bySession.set(key, { previous: null, current: persistableAnthropicPrefix(fingerprint) });
96
+ if (bySession.size > MAX_SESSIONS) bySession.delete(bySession.keys().next().value);
41
97
  }
42
98
 
43
99
  /** Keep `fingerprint` as the session's latest request (the one before it stays as previous). */
@@ -55,8 +111,11 @@ export function latestAnthropicPrefix(sessionId) {
55
111
  return bySession.get(String(sessionId || ''))?.current || null;
56
112
  }
57
113
 
58
- /** `{ changedBlocks: 'tools,s3' | 'none', markersChanged }` between the session's
59
- * last two requests, or null before it has two. */
114
+ /** `{ changedBlocks: 'tools,s3,params' | 'none', markersChanged,
115
+ * lostMessageBreakpoints? }` between the session's last two requests, or
116
+ * null before it has two. `lostMessageBreakpoints` ('m12,m40' | 'none')
117
+ * names the previous request's message breakpoints whose prefix the latest
118
+ * request does not repeat (absent when either side predates the chain). */
60
119
  export function anthropicPrefixChanges(sessionId) {
61
120
  const entry = bySession.get(String(sessionId || ''));
62
121
  if (!entry?.previous) return null;
@@ -67,10 +126,16 @@ export function anthropicPrefixChanges(sessionId) {
67
126
  for (let index = 0; index < blocks; index += 1) {
68
127
  if (previous.system[index] !== current.system[index]) changed.push(`s${index}`);
69
128
  }
129
+ if (previous.params && current.params && previous.params !== current.params) changed.push('params');
70
130
  // Message markers move every turn; only the system markers are a layout change.
71
131
  const systemMarkers = (print) => print.markers.filter((marker) => marker.startsWith('s')).join(',');
72
- return {
132
+ const changes = {
73
133
  changedBlocks: changed.length ? changed.join(',') : 'none',
74
134
  markersChanged: systemMarkers(previous) !== systemMarkers(current),
75
135
  };
136
+ if (Array.isArray(previous.messageMarks) && Array.isArray(current.chain)) {
137
+ const lost = previous.messageMarks.filter(({ i, h }) => current.chain[i] !== h).map(({ i }) => `m${i}`);
138
+ changes.lostMessageBreakpoints = lost.length ? lost.join(',') : 'none';
139
+ }
140
+ return changes;
76
141
  }
@@ -108,11 +108,16 @@ export function appendAnthropicTailReminder(anthropicMessages, reminder) {
108
108
  // 5.5, Sonnet 5.5) and with explicit lists naming Sonnet 5 or Opus 4.8, and
109
109
  // fallback runs only on a safety-classifier refusal.
110
110
  export function anthropicTurnScopedReminders(provider, model, config = {}) {
111
+ return anthropicFirstPartyRoute(provider, config) && supportsAnthropicTurnScopedSystemMessages(model);
112
+ }
113
+
114
+ // The first-party Messages routes: native provider, beta headers allowed, no
115
+ // gateway base URL. Request features a proxy answers with a 400 are gated here.
116
+ export function anthropicFirstPartyRoute(provider, config = {}) {
111
117
  return (
112
118
  ANTHROPIC_NATIVE_PROVIDERS.has(provider) &&
113
119
  config?.disableBetaHeaders !== true &&
114
- (!config?.baseURL || /^https:\/\/api\.anthropic\.com(?:\/|$)/.test(config.baseURL)) &&
115
- supportsAnthropicTurnScopedSystemMessages(model)
120
+ (!config?.baseURL || /^https:\/\/api\.anthropic\.com(?:\/|$)/.test(config.baseURL))
116
121
  );
117
122
  }
118
123
 
@@ -333,6 +338,9 @@ export function toAnthropicMessages(transcript, availableTools, provider = '') {
333
338
  const ownProvider = String(provider || '').toLowerCase();
334
339
  const availableNames = anthropicToolNameSet(availableTools);
335
340
  const result = [];
341
+ // Tool-result user messages a provider request ended with (see the
342
+ // `sentRequestTail` meta finalizeInterruptedTurn stamps).
343
+ const sent = new WeakSet();
336
344
  for (let idx = 0; idx < messages.length; idx++) {
337
345
  const m = messages[idx];
338
346
  if (m.role === 'system') continue;
@@ -407,6 +415,17 @@ export function toAnthropicMessages(transcript, availableTools, provider = '') {
407
415
  } else {
408
416
  result.push({ role: 'user', content: [block, ...hoisted] });
409
417
  }
418
+ if (m.meta?.sentRequestTail === true) sent.add(result[result.length - 1]);
419
+ continue;
420
+ }
421
+ // Text after a tool_result message a provider request already carried
422
+ // (an interrupted request's tail: the restart or cancel marker, the next
423
+ // prompt and anything riding with it) goes in as sibling blocks of that
424
+ // user message. Folding it into the tool_result would rewrite a block the
425
+ // provider cached, so the whole message prefix would be written again.
426
+ const tail = result[result.length - 1];
427
+ if (m.role === 'user' && m.meta?.source !== 'steering' && sent.has(tail)) {
428
+ tail.content.push(...siblingBlocks(normalizeContentForAnthropic(m.content)));
410
429
  continue;
411
430
  }
412
431
  // First-party client parity: fold a user text turn that directly follows a
@@ -424,6 +443,8 @@ export function toAnthropicMessages(transcript, availableTools, provider = '') {
424
443
  !isSteering &&
425
444
  foldUserTextIntoToolResultTail(result, normalizeContentForAnthropic(m.content))
426
445
  ) {
446
+ // A reminder folded before the request was sent ends that request.
447
+ if (m.meta?.sentRequestTail === true) sent.add(tail);
427
448
  continue;
428
449
  }
429
450
  // An assistant turn with no text and no blocks (a refused or empty final
@@ -436,6 +457,15 @@ export function toAnthropicMessages(transcript, availableTools, provider = '') {
436
457
  return sanitizeAnthropicContentPairs(result);
437
458
  }
438
459
 
460
+ // A user message's content as blocks of the message it joins; blank text is
461
+ // dropped (a blank text block is a hard 400).
462
+ function siblingBlocks(content) {
463
+ let blocks = [];
464
+ if (typeof content === 'string') blocks = [{ type: 'text', text: content }];
465
+ else if (Array.isArray(content)) blocks = content;
466
+ return blocks.filter((block) => !(block?.type === 'text' && isBlankText(block.text)));
467
+ }
468
+
439
469
  function isBlankText(content) {
440
470
  return content == null || (typeof content === 'string' && !content.trim());
441
471
  }
@@ -619,7 +649,10 @@ export function anthropicToolExclusion(tool, logTag = 'anthropic') {
619
649
 
620
650
  // A Messages endpoint rejects the whole request for one invalid input_schema,
621
651
  // so a tool broken even after lowering is reported and left out.
622
- function toAnthropicTools(tools, logTag, onToolExcluded) {
652
+ // `eager`: fine-grained tool streaming — the API streams each tool parameter as
653
+ // it is generated instead of buffering it until complete, so a large tool input
654
+ // does not leave the stream silent. First-party routes only.
655
+ function toAnthropicTools(tools, logTag, onToolExcluded, eager = false) {
623
656
  const out = [];
624
657
  tools.forEach((tool, index) => {
625
658
  const result = anthropicInputSchema(tool, logTag);
@@ -629,6 +662,7 @@ function toAnthropicTools(tools, logTag, onToolExcluded) {
629
662
  }
630
663
  const entry = { name: tool.name, description: tool.description, input_schema: result.schema };
631
664
  if (tool.deferLoading === true || tool.defer_loading === true) entry.defer_loading = true;
665
+ if (eager) entry.eager_input_streaming = true;
632
666
  out.push(entry);
633
667
  });
634
668
  return out;
@@ -727,20 +761,25 @@ export function deferredAnthropicTools(activeTools, messages, opts, provider) {
727
761
  .map((tool) => ({ ...tool, deferLoading: true }));
728
762
  }
729
763
 
730
- export function requestAnthropicTools(tools, messages, opts, provider) {
764
+ // `config`: the provider config (baseURL, disableBetaHeaders) deciding the route.
765
+ export function requestAnthropicTools(tools, messages, opts, provider, config = {}) {
731
766
  const activeTools = Array.isArray(tools) ? tools : [];
767
+ const eager = anthropicFirstPartyRoute(provider, config);
732
768
  if (opts?.providerToolSnapshotAuthoritative === true) {
733
769
  const nativePrefixCount = providerNativeToolPrefixCount(activeTools, opts.providerNativeToolPrefixCount);
734
770
  return [
735
771
  ...activeTools.slice(0, nativePrefixCount),
736
- ...toAnthropicTools(activeTools.slice(nativePrefixCount), provider, opts.onToolExcluded),
772
+ ...toAnthropicTools(activeTools.slice(nativePrefixCount), provider, opts.onToolExcluded, eager),
737
773
  ];
738
774
  }
739
775
  const deferredTools = deferredAnthropicTools(activeTools, messages, opts, provider);
740
776
  const nativeTools = Array.isArray(opts?.nativeTools)
741
777
  ? opts.nativeTools.filter((tool) => tool && typeof tool === 'object')
742
778
  : [];
743
- return [...nativeTools, ...toAnthropicTools([...activeTools, ...deferredTools], provider, opts?.onToolExcluded)];
779
+ return [
780
+ ...nativeTools,
781
+ ...toAnthropicTools([...activeTools, ...deferredTools], provider, opts?.onToolExcluded, eager),
782
+ ];
744
783
  }
745
784
 
746
785
  // Applies cache_control markers to the FINAL, already-sanitized Anthropic
@@ -0,0 +1,58 @@
1
+ // The first-output window one provider stream runs under. A logical model
2
+ // request owns ONE window (stall-policy.mjs PROVIDER_FIRST_OUTPUT_TIMEOUT_MS)
3
+ // from its start, whatever attempts serve it: a provider retry, a loop replay
4
+ // or a route fallback opens a new stream for the same request and inherits
5
+ // its absolute deadline and whether it already produced output (a stream for
6
+ // a request that had output runs under the semantic idle from its opening).
7
+ // Only a new logical request (runtime-liveness) starts a new window.
8
+ //
9
+ // A stream joins the session's request only when it reports its progress
10
+ // into it (the loop's onStreamDelta). Forks of the request — a /btw side
11
+ // question, a cache warm, a compaction summary — carry the session id without
12
+ // that callback and keep a window of their own.
13
+ //
14
+ // The window is shared by all four transports: the timer-driven ones (WS,
15
+ // HTTP/SSE) use arm/noteOutput/clear, the iterator-driven compat streams use
16
+ // idleWatch.
17
+ import { getLogicalModelRequest } from '../../session/manager/runtime-liveness.mjs';
18
+
19
+ /**
20
+ * @param {object} input
21
+ * @param {string|null} [input.sessionId]
22
+ * @param {Function} [input.onStreamDelta] the stream's progress callback
23
+ * @param {number} input.timeoutMs the window length
24
+ */
25
+ export function streamFirstOutputWindow({ sessionId, onStreamDelta, timeoutMs }) {
26
+ const request = typeof onStreamDelta === 'function' ? getLogicalModelRequest(sessionId) : null;
27
+ const deadlineAt = (request?.startedAt || Date.now()) + timeoutMs;
28
+ let timer = null;
29
+ const clear = () => {
30
+ clearTimeout(timer);
31
+ timer = null;
32
+ };
33
+ const window = {
34
+ timeoutMs,
35
+ /** Whether the request (or, after noteOutput, this stream) has produced semantic output. */
36
+ outputSeen: request?.outputSeen === true,
37
+ /** Calls `onExpire` at the request's deadline. */
38
+ arm(onExpire) {
39
+ clear();
40
+ timer = setTimeout(onExpire, Math.max(0, deadlineAt - Date.now()));
41
+ timer.unref?.();
42
+ },
43
+ clear,
44
+ /** The stream's semantic output: ends the window. */
45
+ noteOutput() {
46
+ window.outputSeen = true;
47
+ clear();
48
+ },
49
+ /** The `nextAsyncWithWatchdog` idle bounds: the semantic idle once output
50
+ * started (`semanticDeadlineAt` set), the window before. */
51
+ idleWatch(semanticDeadlineAt, idleMs) {
52
+ return semanticDeadlineAt > 0
53
+ ? { idleMs, idleDeadlineAt: semanticDeadlineAt }
54
+ : { idleMs: timeoutMs, idleDeadlineAt: deadlineAt };
55
+ },
56
+ };
57
+ return window;
58
+ }
@@ -0,0 +1,14 @@
1
+ import { listenOnFetchSafePort } from '../../../../shared/fetch-safe-listen.mjs';
2
+
3
+ // Port 0 takes an OS-assigned port the browser can be redirected to.
4
+ export function listenOn(server, port, host) {
5
+ return new Promise((resolve, reject) => {
6
+ server.once('error', reject);
7
+ const listening = () => {
8
+ server.off('error', reject);
9
+ resolve();
10
+ };
11
+ if (port === 0) listenOnFetchSafePort(server, host, listening);
12
+ else server.listen(port, host, listening);
13
+ });
14
+ }