mixdog 1.4.0 → 1.4.1

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 (236) hide show
  1. package/package.json +1 -1
  2. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  3. package/scripts/gate-local.mjs +12 -3
  4. package/scripts/latest-guard.mjs +21 -9
  5. package/scripts/lib/isolated-test-env.mjs +10 -5
  6. package/scripts/lib/run-node-tests.mjs +37 -37
  7. package/scripts/lib/scoped-temp.d.mts +16 -0
  8. package/scripts/lib/scoped-temp.mjs +123 -0
  9. package/scripts/lib/stale-temp.mjs +44 -0
  10. package/scripts/post-release-check.mjs +1 -6
  11. package/scripts/pptx-kit-index.mjs +17 -4
  12. package/scripts/suite-health.mjs +10 -1
  13. package/scripts/test-data-dir.mjs +10 -7
  14. package/src/app.mjs +4 -0
  15. package/src/defaults/shipped-history.json +5 -0
  16. package/src/defaults/skills/browser-use/SKILL.md +6 -4
  17. package/src/defaults/skills/computer-use/SKILL.md +4 -2
  18. package/src/lib/rules-builder.cjs +29 -12
  19. package/src/output-styles/common.md +2 -2
  20. package/src/output-styles/simple.md +1 -1
  21. package/src/rules/agent/AGENT.md +5 -4
  22. package/src/rules/lead/LEAD.md +10 -5
  23. package/src/rules/shared/10-tool-workflow.md +3 -1
  24. package/src/rules/shared/45-destructive.md +0 -2
  25. package/src/rules/shared/50-execution.md +2 -0
  26. package/src/rules/shared/70-delivery.md +7 -9
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -6
  28. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  29. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +1 -1
  30. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +5 -2
  32. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  34. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +7 -3
  35. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  36. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  37. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  40. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  42. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  43. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  44. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  46. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +210 -20
  47. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  49. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  50. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  51. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  53. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  54. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  60. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  61. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  62. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  63. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  64. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  65. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  66. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +136 -5
  67. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +20 -12
  68. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  69. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +48 -22
  70. package/src/runtime/agent/orchestrator/providers/registry.mjs +12 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  72. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -6
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -1
  75. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  76. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  77. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +8 -2
  78. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  79. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  80. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  81. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  82. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  83. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  84. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +163 -22
  85. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +132 -17
  86. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  87. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  88. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +90 -22
  89. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  90. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  92. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  93. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  94. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +2 -2
  96. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  97. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  99. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  100. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  101. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +27 -9
  102. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +61 -6
  103. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -8
  104. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +12 -5
  105. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -3
  106. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  107. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  108. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  110. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  111. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  112. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  113. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  114. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  115. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  116. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +9 -1
  117. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  118. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  120. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  121. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  123. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  124. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +31 -5
  125. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +22 -8
  126. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  127. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  128. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  129. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  130. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  131. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  132. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  134. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  135. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  136. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  137. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +26 -4
  138. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  139. package/src/runtime/channels/lib/whisper-port.mjs +10 -1
  140. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  141. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  142. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  143. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  144. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  145. package/src/runtime/local-provider/server-process.mjs +2 -1
  146. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  147. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  148. package/src/runtime/memory/lib/embedding-worker.mjs +6 -3
  149. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  150. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  151. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  152. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  153. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  154. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  155. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  156. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  157. package/src/runtime/office/quality/assurance-structure-docx.mjs +3 -1
  158. package/src/runtime/office/quality/design-review.mjs +1 -5
  159. package/src/runtime/office/quality/document-brief.mjs +1 -5
  160. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  161. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  162. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  163. package/src/runtime/shared/defaults-separation.mjs +1 -4
  164. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  165. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  166. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  167. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  168. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  169. package/src/runtime/shared/llm/cost.mjs +5 -1
  170. package/src/runtime/shared/loopback-listener.mjs +25 -2
  171. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  172. package/src/runtime/shared/model-route-chain.mjs +22 -4
  173. package/src/runtime/shared/open-url.mjs +10 -0
  174. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  175. package/src/runtime/shared/session-scratch.mjs +37 -0
  176. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  177. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  178. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  179. package/src/runtime/shared/tool-surface.mjs +3 -0
  180. package/src/runtime/shared/tool-work-units.mjs +2 -0
  181. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  182. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  183. package/src/runtime/shared/worktrees/git.mjs +15 -0
  184. package/src/runtime/shared/worktrees/manager.mjs +87 -38
  185. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  186. package/src/runtime/visualize-bridge/client.mjs +2 -0
  187. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  188. package/src/session-runtime/agent-task-sessions.mjs +152 -63
  189. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  190. package/src/session-runtime/boot/tools.mjs +5 -1
  191. package/src/session-runtime/bot-status.mjs +12 -6
  192. package/src/session-runtime/bot-target.mjs +38 -0
  193. package/src/session-runtime/channel-config-api.mjs +6 -1
  194. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  195. package/src/session-runtime/conversation-import/discovery.mjs +271 -25
  196. package/src/session-runtime/conversation-import/service.mjs +26 -5
  197. package/src/session-runtime/handoff-attachments.mjs +82 -31
  198. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  199. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  200. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  201. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +8 -2
  202. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  203. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  204. package/src/session-runtime/model-route/set-route.mjs +11 -2
  205. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  206. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  207. package/src/session-runtime/provider-models.mjs +4 -2
  208. package/src/session-runtime/resource-recall-api.mjs +6 -1
  209. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  210. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  211. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  212. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  213. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  214. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  215. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  216. package/src/session-runtime/services/provider-admin.mjs +2 -1
  217. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  218. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  219. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -4
  220. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  221. package/src/session-runtime/workspace/transitions.mjs +25 -0
  222. package/src/session-runtime/workspace/worktree-tool.mjs +26 -9
  223. package/src/standalone/daemon-session-control.mjs +18 -2
  224. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  225. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  226. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  227. package/src/tui/app/app-view/floating-panel.jsx +7 -4
  228. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  229. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  230. package/src/tui/app/provider-setup/oauth-actions.mjs +38 -20
  231. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  232. package/src/tui/app/provider-setup-picker.mjs +9 -5
  233. package/src/tui/components/QuestionPanel.jsx +13 -4
  234. package/src/tui/dist/index.mjs +75 -28
  235. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  236. package/src/ui/statusline-agents.mjs +1 -3
@@ -8,6 +8,8 @@ dependencies:
8
8
  tools:
9
9
  - type: tool
10
10
  value: browser
11
+ - type: tool
12
+ value: handoff_user
11
13
  ---
12
14
 
13
15
  # Browser Use
@@ -170,7 +172,7 @@ not as two blocks joined.
170
172
  a submitted form's result.
171
173
  - **Handoff** — the user must act on the page: payment, approval, or an
172
174
  unfinished workflow they continue. A sign-in, CAPTCHA or 2FA step you
173
- wait on is an `ask_user` takeover instead (User step mid-flow).
175
+ wait on is a `handoff_user` takeover instead (User step mid-flow).
174
176
  - `open` replies with exactly one placement: "Shown in the browser panel",
175
177
  "Not on screen yet" (this conversation or its panel is not visible; the
176
178
  page appears when the user opens it), or an error. Tell the user that and
@@ -200,12 +202,12 @@ not as two blocks joined.
200
202
  pages.
201
203
  - **User step mid-flow** — a CAPTCHA or human check, 2FA, or identity check is
202
204
  a handoff, not completion (an e-mail check of a flow you started is not one:
203
- E-mail verification). With `ask_user`, call it with `handoff: { kind:
205
+ E-mail verification). With `handoff_user`, call it with `handoff: { kind:
204
206
  'takeover', surface: 'browser', tab, instructions, need }`: it shows the
205
207
  takeover card, notifies the user, opens that page beside the chat, and
206
208
  returns when they press Done; continue from a fresh observation. Never ask
207
209
  for the step in plain chat or tell the user to open the panel. Without
208
- `ask_user` (an agent), retain the page with `open`, report the required
210
+ `handoff_user` (an agent), retain the page with `open`, report the required
209
211
  user action, and stop.
210
212
  - Preserve pre-existing user tabs, unfinished forms, and pages needed for
211
213
  recovery. If ownership or disposability is unclear, preserve the page;
@@ -383,7 +385,7 @@ in the page instead of retrying the same `navigate`.
383
385
  `MIXDOG_BROWSER_CONFIRM_ACTIONS` and
384
386
  `MIXDOG_BROWSER_DENY_ACTIONS` optionally name comma-separated public actions
385
387
  (or `*`) the desktop app confirms once or refuses; denial takes precedence.
386
- - For a password or token, focus the field and call `ask_user` with
388
+ - For a password or token, focus the field and call `handoff_user` with
387
389
  `handoff: { kind: 'secret', surface: 'browser' }`; for sign-in, CAPTCHA or
388
390
  2FA use `kind: 'takeover'` with instructions. Never ask for secrets or
389
391
  these steps in plain chat.
@@ -8,6 +8,8 @@ dependencies:
8
8
  tools:
9
9
  - type: tool
10
10
  value: computer
11
+ - type: tool
12
+ value: handoff_user
11
13
  ---
12
14
 
13
15
  # Computer Use
@@ -241,9 +243,9 @@ your results and that browser's pixels are withheld until this turn ends
241
243
  (`ax` reads still work), but a page that redisplays it can expose it, and other
242
244
  programs of the same Windows user are outside this protection. A username is
243
245
  an identifier and is not hidden. Continue with keys (`act` key Enter). On a
244
- refusal, hand the secret to the user (`ask_user` kind `secret`,
246
+ refusal, hand the secret to the user (`handoff_user` kind `secret`,
245
247
  surface `computer`); one-time codes, passkeys, security keys and app approvals
246
- are the user's step (`ask_user` kind `takeover`). Never type a password or
248
+ are the user's step (`handoff_user` kind `takeover`). Never type a password or
247
249
  code with `type`/`set_value`, even one shown on screen.
248
250
 
249
251
  **Keyboard-driven navigation** — `act` with `key` steps such as `ctrl+s`
@@ -126,7 +126,10 @@ function buildProfilePreferencesContent(dataDir) {
126
126
  return lines.length ? `# Profile Preferences\n\n${lines.join('\n')}` : '';
127
127
  }
128
128
 
129
- function buildLanguageSection(dataDir) {
129
+ // `sharedFormatActive`: the prompt carries the shared Output Format partial,
130
+ // which already keeps literals verbatim (common.md), so the language block
131
+ // need not repeat it.
132
+ function buildLanguageSection(dataDir, sharedFormatActive = false) {
130
133
  const profile = normalizeProfileConfig(readAgentConfig(dataDir).profile);
131
134
  const language = profileLanguagePrompt(profile.language);
132
135
  if (!language?.prompt) return '';
@@ -139,7 +142,7 @@ function buildLanguageSection(dataDir) {
139
142
  // mistranslate, so the two clauses would be dead text.
140
143
  if (language.prompt !== 'English') {
141
144
  lines.push(
142
- `- Preserve identifiers, paths, commands, API names, and exact errors. Keep technical terms in their established form, usually the original English (blocker, cache, commit); add a short plain explanation only when the reader may not know it. Never invent a literal translation.`
145
+ `- ${sharedFormatActive ? '' : 'Preserve identifiers, paths, commands, API names, and exact errors. '}Keep technical terms in their established form, usually the original English (blocker, cache, commit); add a short plain explanation only when the reader may not know it. Never invent a literal translation.`
143
146
  );
144
147
  }
145
148
  return `# Language\n\n${lines.join('\n')}`;
@@ -153,7 +156,9 @@ function stripFrontmatter(markdown) {
153
156
 
154
157
  // Tool dependency is declared as metadata, not matched against prose. A
155
158
  // `<!-- tools: a, b -->` marker binds the block that follows it: the block
156
- // survives while any listed tool is permitted by the allow/deny policy.
159
+ // survives while any listed tool is permitted by the allow/deny policy. The
160
+ // pseudo-entry `lead` is not a tool: it is permitted for every non-agent
161
+ // session (whatever its tool profile) and never for an agent-owned one.
157
162
  // Unspecified allowlists are unrestricted. Markers never reach the model.
158
163
  const TOOL_MARKER_RE = /^[ \t]*<!--[ \t]*tools:[ \t]*([^>]*?)[ \t]*-->[ \t]*$/;
159
164
 
@@ -188,9 +193,10 @@ function omitKeySet(omitTools) {
188
193
  }
189
194
 
190
195
  /** Drop routing clauses for tools that are not on the session surface. */
191
- function omitToolRoutes(text, omitTools = [], allowTools = null) {
196
+ function omitToolRoutes(text, omitTools = [], allowTools = null, ownerIsAgent = false) {
192
197
  const deny = omitKeySet(omitTools);
193
198
  const allow = Array.isArray(allowTools) ? omitKeySet(allowTools) : null;
199
+ const permitted = (name) => (name === 'lead' ? !ownerIsAgent : !deny.has(name) && (!allow || allow.has(name)));
194
200
  const lines = String(text || '').split(/\r?\n/);
195
201
  const kept = [];
196
202
  let index = 0;
@@ -204,7 +210,7 @@ function omitToolRoutes(text, omitTools = [], allowTools = null) {
204
210
  index += 1;
205
211
  if (index >= lines.length) break;
206
212
  const end = markedBlockEnd(lines, index);
207
- if (!tools.length || tools.some((name) => !deny.has(name) && (!allow || allow.has(name)))) {
213
+ if (!tools.length || tools.some(permitted)) {
208
214
  kept.push(...lines.slice(index, end));
209
215
  }
210
216
  index = end;
@@ -252,22 +258,29 @@ function readSharedFormatPartial({ PLUGIN_ROOT, DATA_DIR }) {
252
258
  );
253
259
  }
254
260
 
255
- function loadOutputStyle({ PLUGIN_ROOT, DATA_DIR }) {
261
+ function resolveOutputStyle({ PLUGIN_ROOT, DATA_DIR }) {
256
262
  // Root `outputStyle` is the only configured location; the retired
257
263
  // `agent.outputStyle` key is dropped by config canonicalization.
258
264
  const configured = String(readUnifiedConfig(DATA_DIR).outputStyle || '').trim() || DEFAULT_OUTPUT_STYLE_ID;
259
265
  const styles = listOutputStyleEntries({ PLUGIN_ROOT, DATA_DIR });
260
266
  const style = matchOutputStyle(configured, styles) || matchOutputStyle(DEFAULT_OUTPUT_STYLE_ID, styles) || styles[0];
261
- if (!style) return '';
267
+ if (!style) return null;
262
268
  const selected = stripFrontmatter(style.raw);
263
- if (!selected) return '';
269
+ if (!selected) return null;
264
270
  const common = style.keepSharedFormat ? readSharedFormatPartial({ PLUGIN_ROOT, DATA_DIR }) : '';
271
+ return { style, selected, common };
272
+ }
273
+
274
+ function loadOutputStyle(paths) {
275
+ const resolved = resolveOutputStyle(paths);
276
+ if (!resolved) return '';
277
+ const { style, selected, common } = resolved;
265
278
  return [`# Output Style: ${style.label}`, OUTPUT_STYLE_ANCHOR, common, selected].filter(Boolean).join('\n\n');
266
279
  }
267
280
 
268
- function buildSharedToolContent({ PLUGIN_ROOT, omitTools = [], allowTools = null } = {}) {
281
+ function buildSharedToolContent({ PLUGIN_ROOT, omitTools = [], allowTools = null, ownerIsAgent = false } = {}) {
269
282
  const SHARED_DIR = path.join(PLUGIN_ROOT, 'rules', 'shared');
270
- return omitToolRoutes(readMarkdownDirectory(SHARED_DIR), omitTools, allowTools);
283
+ return omitToolRoutes(readMarkdownDirectory(SHARED_DIR), omitTools, allowTools, ownerIsAgent);
271
284
  }
272
285
 
273
286
  // Route policy: `rules/routes/*.md` files bound to a provider and/or model
@@ -444,8 +457,12 @@ function buildLeadMetaContent({ PLUGIN_ROOT, DATA_DIR, includeOutputStyle = true
444
457
  * preamble and progress lines follow the most recent instruction, so nothing
445
458
  * English (session/shell/git lines included) may trail it.
446
459
  */
447
- function buildLeadLanguageContent({ DATA_DIR }) {
448
- return buildLanguageSection(DATA_DIR);
460
+ function buildLeadLanguageContent({ PLUGIN_ROOT, DATA_DIR, includeOutputStyle = true }) {
461
+ const resolved = includeOutputStyle ? resolveOutputStyle({ PLUGIN_ROOT, DATA_DIR }) : null;
462
+ // Only the shipped partial is known to keep literals verbatim; a user
463
+ // override of common.md may not, so it keeps the language guard.
464
+ const shipped = stripFrontmatter(readOptional(path.join(PLUGIN_ROOT, 'output-styles', 'common.md')));
465
+ return buildLanguageSection(DATA_DIR, Boolean(shipped) && resolved?.common === shipped);
449
466
  }
450
467
 
451
468
  /** The configured response language as {id, name} (name = the language block's); null when none. */
@@ -13,8 +13,8 @@ partial: true
13
13
  - Distinguish facts, inference, and uncertainty; keep exact literals and
14
14
  material risks. Caveats only when they change the reader's next step.
15
15
  - Show only what the reader needs: the decision, the result, the blocker, and
16
- the action. No tangents, raw dumps, or process narration. Explicit depth
17
- requests win; depth applies to substantive answers, not the one-line
16
+ the action. No tangents, raw dumps, or process narration. Depth
17
+ applies to substantive answers, not the one-line
18
18
  pre-tool briefing.
19
19
  - At every depth keep decision-relevant failures, security warnings, and
20
20
  destructive-action confirmations; quote needed error text exactly.
@@ -7,7 +7,7 @@ aliases: default, concise, handoff
7
7
 
8
8
  ## Depth
9
9
 
10
- - Give a self-contained summary: the answer first, then the core causal
10
+ - Give a self-contained summary: the core causal
11
11
  sequence, the strongest evidence, and the direct consequence or next step.
12
12
  - Omit secondary evidence, implementation detail, and extended examples
13
13
  unless requested.
@@ -15,7 +15,8 @@
15
15
  - Reporting: send one final message, then stop; never ask whether to
16
16
  continue. Start with `Status: done|partial|blocked`, then:
17
17
  Result: the answer or outcome, mapped to the brief's done-when.
18
- Evidence: path+symbol and checks run (failures: decisive error only).
19
- Open: risks, gaps, or the blocker. Omit empty fields; no logs, narration,
20
- or restated brief. Role contracts only add fields.
21
- Lead owns the independent review.
18
+ Evidence: path+symbol and checks with outcomes (failures: decisive error
19
+ only), not a retelling of Result. Open: risks, gaps, or the blocker.
20
+ State each fact once; explain a choice only where it departs from the
21
+ brief. Omit empty fields; no logs, narration, nested headings or restated
22
+ brief. Role contracts only add fields. Lead owns the independent review.
@@ -19,11 +19,16 @@
19
19
  - Be a grounded, candid, and attentive collaborator: natural and respectful,
20
20
  without flattery, forced humor, mimicry, or stereotyping.
21
21
  <!-- tools: agent -->
22
- - Briefing an agent: one self-contained English brief with goal and why,
23
- owned paths, non-goals and done-when; a running agent gets only changes.
24
- Outcomes only: no tools, steps or mid-flight checks; evidence by path.
25
- Decide first; send scope changes as one replacement brief; invent no
26
- restrictions or copies.
22
+ - Briefing an agent: one self-contained English brief with the goal and the
23
+ context it needs, owned paths, non-goals, constraints the agent does not
24
+ already receive, and done-when; a running agent gets only changes.
25
+ Done-when is the end state of the product or answer, never a list of
26
+ checks: name no tools, steps, commands or suites; the agent's own rules
27
+ choose its checks. A Reviewer gets the diff scope, the acceptance criteria
28
+ and the author's Evidence verbatim, not the whole report, so passed checks
29
+ are not rerun; a re-review carries only the fixes. Reference evidence by
30
+ path instead of retelling it. Decide first; send scope changes as one
31
+ replacement brief; invent no other restrictions or copies.
27
32
  <!-- tools: agent -->
28
33
  - Reports arrive as `<task-notification>`: never wait, poll or guess them;
29
34
  do independent work and end the turn. On an agent report, each feature
@@ -8,7 +8,9 @@
8
8
  has its evidence; never widen past what was asked, and never ask
9
9
  permission for work the request already covers.
10
10
  - When blocked, first look for a workaround you can carry out yourself with
11
- the available tools; ask the user only when none exists.
11
+ the available tools.
12
+ <!-- tools: lead -->
13
+ Ask the user only when none exists.
12
14
  - Do not add work whose need depends on a pending result. Reuse content
13
15
  already delivered; changed sources and omitted ranges are new evidence.
14
16
  Trust documented guarantees; no availability checks or defensive branches —
@@ -7,8 +7,6 @@
7
7
  files you did not create: it may hold other agents' or the user's
8
8
  uncommitted work. Use a copy or managed snapshot for before/after
9
9
  comparisons.
10
- - Worktrees live only in the app-managed store; never create one through
11
- shell or in a temp folder.
12
10
  <!-- tools: git -->
13
11
  - Separate-branch work goes into a managed worktree through `git`.
14
12
  - Back up only when changes risk irreversible loss, not for routine code edits;
@@ -4,4 +4,6 @@
4
4
  data transformation, generated output, unsupported-format decoding. An open
5
5
  shell is never a routing reason.
6
6
  - Structured service operations go through the service's MCP tool or CLI.
7
+ - Open no window, dialog or elevation prompt unless showing or operating
8
+ that UI is the task.
7
9
 
@@ -1,8 +1,9 @@
1
1
  # Delivery
2
2
 
3
3
  - Unless the user explicitly requests them, do not author separate record-keeping
4
- artifacts (reports, progress logs, notes, or checklists). Report progress and
5
- results in the conversation instead.
4
+ artifacts (reports, progress logs, notes, or checklists).
5
+ <!-- tools: lead -->
6
+ Report progress and results in the conversation instead.
6
7
  - Reference files and folders in inline code by a Project-root-relative or
7
8
  absolute path, optionally `:line[:column]` (`deliverables/report.docx`,
8
9
  `src/app.ts:42`); files outside the Project always by absolute path; never a
@@ -11,14 +12,11 @@
11
12
  and give their path; never open them in the system browser or paste their
12
13
  source unless the user asks.
13
14
  <!-- tools: visualize -->
14
- - Open each finished artifact beside the chat with `visualize open` when available.
15
- This covers any deliverable a visual presents better: mockups, diagrams,
16
- plans, and comparisons or research results with tables. Present these as
17
- a visualize page and keep the chat to a short summary.
15
+ - Show every finished visual artifact with `visualize open`, loading its schema
16
+ first if deferred: images, video, HTML, documents, decks, mockups, diagrams,
17
+ plans; comparisons or research tables go in a visualize page. Keep the chat
18
+ to a short summary.
18
19
 
19
20
  <!-- tools: git -->
20
21
  - A commit request includes selecting and staging its changes; a stage-only
21
22
  request stops before commit. Do not stage changes without either request.
22
- <!-- tools: git -->
23
- - Stage selected diff changes with `git` using `action:"stage"`. Keep this
24
- internal step within the requested commit workflow, not a separate approval.
@@ -243,11 +243,13 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
243
243
  // Role budget, floored so the provider recovery window always wins.
244
244
  idleStaleMs = Math.max(abort * 1000, PROVIDER_RECOVERY_FLOOR_MS);
245
245
  } else {
246
- // Part B: the primary mid-stream stall catch is now the provider-level
247
- // SEMANTIC idle abort (~120s, ping-immune). This public-agent idle is a
248
- // BACKSTOP only, so it must not exceed the stall abort (600s default) —
249
- // the old 30-min value meant a ping-only wedge that slipped past the
250
- // provider layer would still hang the owner for half an hour. Cap it at
246
+ // Part B: the provider-level idle abort is the first mid-stream catch, but
247
+ // it is transport-based for Anthropic (pings reset it; other providers
248
+ // judge semantic idle). This public-agent idle is a BACKSTOP only, so it
249
+ // must not exceed the stall abort (600s default) — the old 30-min value
250
+ // meant a ping-only wedge that slipped past the provider layer would still
251
+ // hang the owner for half an hour. Anthropic pings extend it only up to
252
+ // KEEPALIVE_MAX_IDLE_MULTIPLIER times (evaluateAgentWatchdogAbort). Cap it at
251
253
  // the stall abort while keeping 30 min as an absolute ceiling. Long tool
252
254
  // calls refresh progress through the activity heartbeat, so they never
253
255
  // trip this backstop.
@@ -270,6 +272,23 @@ export function resolveAgentWatchdogPolicy(agent, overrides = {}) {
270
272
  };
271
273
  }
272
274
 
275
+ // How far keepalives may extend liveness past the last real progress, as a
276
+ // multiple of the idle budget. Anthropic pings across legitimate generation
277
+ // gaps (adaptive thinking, a buffered tool input), but a wedged stream that
278
+ // pings forever must still die.
279
+ export const KEEPALIVE_MAX_IDLE_MULTIPLIER = 3;
280
+
281
+ // A keepalive newer than the last real progress holds the idle abort while it
282
+ // is itself fresh and the silence is within the cap. The runtime records one
283
+ // only after the request's first semantic event, so a ping before it never
284
+ // reaches here (the first-output window owns that phase).
285
+ function keepaliveHoldsIdle(snapshot, last, now, idleStaleMs) {
286
+ const keepaliveAt = snapshot.lastKeepaliveAt || 0;
287
+ return (
288
+ keepaliveAt > last && now - keepaliveAt <= idleStaleMs && now - last <= idleStaleMs * KEEPALIVE_MAX_IDLE_MULTIPLIER
289
+ );
290
+ }
291
+
273
292
  export function evaluateAgentWatchdogAbort(snapshot, now, policy) {
274
293
  if (!snapshot || !policy) return null;
275
294
  // A turn blocked on the user's tool approval (its own, or a sub-agent's that
@@ -285,7 +304,10 @@ export function evaluateAgentWatchdogAbort(snapshot, now, policy) {
285
304
  const firstSemanticMs = policy.firstSemanticMs ?? policy.firstVisibleCeilingMs ?? 0;
286
305
  // Independent fixed deadlines from request start. Transport can satisfy
287
306
  // only the transport deadline; it never switches, extends, or resets the
288
- // semantic-response deadline.
307
+ // semantic-response deadline. Both are judged per logical model request
308
+ // (runtime-liveness): the request that carries tool results gets the same
309
+ // first-output allowance as the turn's first request, while a retry of a
310
+ // request keeps that request's clock and marks.
289
311
  if (snapshot.waitingForTransport && firstTransportMs > 0 && startedAt && now - startedAt > firstTransportMs) {
290
312
  return new AgentStallAbortError(`agent first transport stale (${firstTransportMs}ms)`);
291
313
  }
@@ -298,6 +320,7 @@ export function evaluateAgentWatchdogAbort(snapshot, now, policy) {
298
320
 
299
321
  const last = snapshot.lastProgressAt || snapshot.firstActivityAt;
300
322
  if (policy.idleStaleMs > 0 && last && now - last > policy.idleStaleMs) {
323
+ if (keepaliveHoldsIdle(snapshot, last, now, policy.idleStaleMs)) return null;
301
324
  return new AgentStallAbortError(`agent task stale (${policy.idleStaleMs}ms without stream/tool progress)`);
302
325
  }
303
326
 
@@ -53,6 +53,7 @@ export function buildCacheBreakPayload(record = {}) {
53
53
  input_prefix_mismatch_actual_hash: cleanHash(record.inputPrefixMismatchActualHash),
54
54
  changed_blocks: cleanText(record.changedBlocks),
55
55
  system_markers_changed: typeof record.markersChanged === 'boolean' ? record.markersChanged : null,
56
+ lost_message_breakpoints: cleanText(record.lostMessageBreakpoints),
56
57
  };
57
58
  return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== null));
58
59
  }
@@ -81,6 +82,7 @@ function formatCacheBreakLine(record = {}) {
81
82
  ['noGuardBaseline', payload.missing_prefix_guard_baseline],
82
83
  ['changedBlocks', payload.changed_blocks],
83
84
  ['systemMarkersChanged', payload.system_markers_changed],
85
+ ['lostMessageBreakpoints', payload.lost_message_breakpoints],
84
86
  ].filter(([, value]) => value !== null && value !== undefined);
85
87
  return `[cache-break] ${fields.map(([key, value]) => (typeof value === 'string' ? `${key}=${JSON.stringify(value)}` : `${key}=${value}`)).join(' ')}`;
86
88
  }
@@ -100,7 +100,7 @@ export function buildDeferredToolManifest(entries) {
100
100
  list.sort((a, b) => a.name.localeCompare(b.name));
101
101
  return [
102
102
  '<available-deferred-tools>',
103
- 'Deferred tool names and selection triggers; schemas load on demand.',
103
+ 'Deferred tools; load_tool loads their schemas.',
104
104
  ...list.map((entry) => (entry.description ? `- ${entry.name}: ${entry.description}` : `- ${entry.name}`)),
105
105
  '</available-deferred-tools>',
106
106
  ].join('\n');
@@ -669,7 +669,9 @@ export function buildSkillManifest(skills, { limit = 80, charBudget = SKILL_MANI
669
669
  for (const skill of visible) skill.trigger = compactPromptManifestText(skill.trigger, triggerCap);
670
670
  const lines = [
671
671
  '# available-skills',
672
- 'Selection triggers and linked tools for Skill({"name":"<skill-name>"}). mcp:<server> denotes that server’s tools.',
672
+ visible.some((skill) => skill.linkedTools.some((tool) => tool.startsWith('mcp:')))
673
+ ? 'Selection triggers and linked tools. mcp:<server> denotes that server’s tools.'
674
+ : 'Selection triggers and linked tools.',
673
675
  '<available_skills>',
674
676
  ...visible.map(
675
677
  (skill) => (skill.trigger ? `- ${skill.name}: ${skill.trigger}` : `- ${skill.name}`) + toolSuffix(skill)
@@ -1,4 +1,5 @@
1
1
  import { createServer } from 'node:http';
2
+ import { listenOnFetchSafePort } from '../../../shared/fetch-safe-listen.mjs';
2
3
  import { randomBytes } from 'node:crypto';
3
4
  import { normalizeMcpTransportUrl } from './client-config.mjs';
4
5
  import { identityFromTokenResponse } from './oauth-account.mjs';
@@ -48,8 +49,10 @@ export function createLoginFlow({ url, cache, flow, provider, lifetimeSignal, op
48
49
  await new Promise((resolve, reject) => {
49
50
  flow.listener.once('error', reject);
50
51
  // Retain the registered redirect URI if this connection later needs
51
- // refresh or reauthorization; never register a different callback.
52
- flow.listener.listen(creds.redirectUrl ? Number(new URL(creds.redirectUrl).port) : 0, '127.0.0.1', resolve);
52
+ // refresh or reauthorization; never register a different callback. A new
53
+ // one is a port the browser can be redirected to.
54
+ if (creds.redirectUrl) flow.listener.listen(Number(new URL(creds.redirectUrl).port), '127.0.0.1', resolve);
55
+ else listenOnFetchSafePort(flow.listener, '127.0.0.1', resolve);
53
56
  });
54
57
  creds.redirectUrl = `http://127.0.0.1:${flow.listener.address().port}/oauth/callback`;
55
58
  }
@@ -244,6 +244,17 @@ function sessionAccountId(providerName, key, messages, options) {
244
244
  return null;
245
245
  }
246
246
 
247
+ /** The account a send of `model` for `sessionId` would try first right now —
248
+ * the session's binding and the choice poolSend makes for its first attempt
249
+ * — or null when the provider has no roster account or none is eligible. */
250
+ export function plannedAccountId(providerName, { sessionId = null, messages = [], providerState, model = '' } = {}) {
251
+ const pool = readProviderAccountPool(providerName);
252
+ if (!pool.accounts.length) return null;
253
+ const sessionKey = sessionId ? `${providerName}:${sessionId}` : null;
254
+ const boundId = sessionAccountId(providerName, sessionKey, messages, { providerState });
255
+ return chooseProviderAccount(pool, new Set(), Date.now(), model, { sessionAccountId: boundId })?.id || null;
256
+ }
257
+
247
258
  // Stamp the serving account on the answer, independent of transport state,
248
259
  // with the account the session stays bound to (`bindingId`).
249
260
  function recordServingAccount(providerName, result, id, bindingId) {
@@ -52,7 +52,7 @@ export function createAnthropicApiRecovery({
52
52
  : PROVIDER_NONSTREAM_TOTAL_TIMEOUT_MS;
53
53
  const lifetime = createTimeoutSignal(totalSignal, timeoutMs, `${label} Anthropic non-streaming fallback`);
54
54
  try {
55
- const message = await transport.requestNonStreamingMessage(nonStreamingParams, lifetime.signal);
55
+ const message = await transport.requestNonStreamingMessage(nonStreamingParams, lifetime.signal, timeoutMs);
56
56
  return buildTurnResult(normalizeAnthropicNonStreamingResponse(message, useModel));
57
57
  } catch (error) {
58
58
  if (error instanceof AnthropicFallbackTriggeredError || totalSignal?.aborted) throw error;
@@ -10,7 +10,11 @@
10
10
  * anthropic-turn-result.mjs); nothing here touches the socket.
11
11
  */
12
12
  import { prepareAnthropicImages } from './lib/anthropic-image-input.mjs';
13
- import { anthropicPrefixFingerprint, recordAnthropicPrefix } from './lib/anthropic-prefix-trace.mjs';
13
+ import {
14
+ anthropicPrefixFingerprint,
15
+ anthropicPrefixSessionId,
16
+ recordAnthropicPrefix,
17
+ } from './lib/anthropic-prefix-trace.mjs';
14
18
  import { buildAnthropicBetaHeaders, supportsAnthropicFastMode } from './anthropic-betas.mjs';
15
19
  import { applyAnthropicServerFallback } from './anthropic-server-fallback.mjs';
16
20
  import { fastModeAvailable } from './anthropic-fast-mode.mjs';
@@ -93,7 +97,7 @@ export async function buildAnthropicApiRequest({
93
97
  // Tools are resolved BEFORE the messages: the lowering needs the final
94
98
  // tool list to drop tool_reference blocks whose tool no longer ships
95
99
  // in this request (otherwise the API rejects the whole turn).
96
- const requestTools = requestAnthropicTools(tools, chatMsgs, opts, name);
100
+ const requestTools = requestAnthropicTools(tools, chatMsgs, opts, name, config);
97
101
  // Message-tail cache budget (4-BP layout + ANTHROPIC_MSG_SLOTS) is
98
102
  // shared with anthropic-oauth — see resolveAnthropicMessageCacheSlots.
99
103
  const messageCacheSlots = resolveAnthropicMessageCacheSlots(systemBlocks, ttls, { tools: requestTools });
@@ -183,7 +187,7 @@ export async function buildAnthropicApiRequest({
183
187
  // (once) → mark → prepare image bytes → send.
184
188
  params.stream = true;
185
189
  // Diagnostic: per-block prefix hashes of exactly what is sent.
186
- recordAnthropicPrefix(opts.sessionId || opts.session?.id || null, anthropicPrefixFingerprint(params));
190
+ recordAnthropicPrefix(anthropicPrefixSessionId(opts), anthropicPrefixFingerprint(params));
187
191
 
188
192
  const requestHeaders = requestHeadersFor(params, {
189
193
  config,
@@ -215,12 +215,18 @@ export function createAnthropicApiTransport({
215
215
  }
216
216
  };
217
217
 
218
- const requestNonStreamingMessage = (nonStreamingParams, signal) =>
218
+ // `timeoutMs` is the replay's own lifetime, handed to the SDK as the request
219
+ // timeout: without an explicit one the SDK refuses any stream:false body
220
+ // whose max_tokens it estimates past 10 minutes before sending it.
221
+ const requestNonStreamingMessage = (nonStreamingParams, signal, timeoutMs) =>
219
222
  withRetry(
220
223
  withBodyRecovery(nonStreamingParams, async ({ signal: attemptSignal }) => {
221
224
  noteRequestServiceTier(nonStreamingParams.speed);
222
225
  try {
223
- return await client.messages.create(nonStreamingParams, requestOptions(attemptSignal));
226
+ return await client.messages.create(nonStreamingParams, {
227
+ ...requestOptions(attemptSignal),
228
+ timeout: timeoutMs,
229
+ });
224
230
  } catch (err) {
225
231
  throw annotateHttpFailure(err);
226
232
  }
@@ -22,11 +22,15 @@ export function parseMaxTokensContextOverflow(status, message) {
22
22
  const match = OVERFLOW_RE.exec(String(message || ''));
23
23
  if (!match) return null;
24
24
  const [inputTokens, maxTokens, contextLimit] = match.slice(1).map((value) => Number.parseInt(value, 10));
25
- return [inputTokens, maxTokens, contextLimit].every(Number.isFinite) ? { inputTokens, maxTokens, contextLimit } : null;
25
+ return [inputTokens, maxTokens, contextLimit].every(Number.isFinite)
26
+ ? { inputTokens, maxTokens, contextLimit }
27
+ : null;
26
28
  }
27
29
 
28
30
  const thinkingBudget = (body) =>
29
- body?.thinking?.type === 'enabled' && Number(body.thinking.budget_tokens) > 0 ? Number(body.thinking.budget_tokens) : 0;
31
+ body?.thinking?.type === 'enabled' && Number(body.thinking.budget_tokens) > 0
32
+ ? Number(body.thinking.budget_tokens)
33
+ : 0;
30
34
 
31
35
  /**
32
36
  * Shrink `body.max_tokens` to fit the reported context. Returns true when the
@@ -159,8 +159,8 @@ function deferredAnthropicTools(activeTools, messages, opts, provider = 'anthrop
159
159
  return sharedDeferredAnthropicTools(activeTools, messages, opts, provider);
160
160
  }
161
161
  // `provider`: the engine's name (a gateway Messages engine runs under its gateway's).
162
- export function requestAnthropicTools(tools, messages, opts, provider = 'anthropic') {
163
- return sharedRequestAnthropicTools(tools, messages, opts, provider);
162
+ export function requestAnthropicTools(tools, messages, opts, provider = 'anthropic', config = {}) {
163
+ return sharedRequestAnthropicTools(tools, messages, opts, provider, config);
164
164
  }
165
165
  // Test-only: expose the lowering so the steering-provenance test can assert
166
166
  // the API-key provider keeps steering-tagged user turns distinct (mirrors
@@ -50,7 +50,9 @@ export function anthropicQuotaError(status, headers, bodyText = '') {
50
50
  const detail = bodyText ? `: ${String(bodyText).slice(0, 200)}` : '';
51
51
  const retry = retryAfter ? ` retryAfter=${retryAfter}` : '';
52
52
  const quota = hasQuotaHeaders(headers);
53
- const err = new Error(`Anthropic OAuth API ${status} ${quota ? 'quota/rate limit' : 'request rejected'}${retry}${detail}`);
53
+ const err = new Error(
54
+ `Anthropic OAuth API ${status} ${quota ? 'quota/rate limit' : 'request rejected'}${retry}${detail}`
55
+ );
54
56
  err.name = quota ? 'ProviderQuotaError' : 'ProviderRequestRejectedError';
55
57
  err.headers = headers;
56
58
  err.response = { status, headers };
@@ -8,6 +8,7 @@
8
8
  import { traceAgentSse, traceAgentUsage } from '../agent-trace.mjs';
9
9
  import {
10
10
  anthropicPrefixFingerprint,
11
+ anthropicPrefixSessionId,
11
12
  latestAnthropicPrefix,
12
13
  recordAnthropicPrefix,
13
14
  } from './lib/anthropic-prefix-trace.mjs';
@@ -555,7 +556,7 @@ export class AnthropicOAuthProvider {
555
556
  const body = buildRequestBody(messages, useModel, tools, sendOpts);
556
557
  body.messages = await prepareAnthropicImages(body.messages, { signal: externalSignal });
557
558
  // Diagnostic: per-block prefix hashes of exactly what is sent.
558
- recordAnthropicPrefix(opts.sessionId || null, anthropicPrefixFingerprint(body));
559
+ recordAnthropicPrefix(anthropicPrefixSessionId(opts), anthropicPrefixFingerprint(body));
559
560
  if (body.speed === 'fast') {
560
561
  this.fastModeBetaHeaderLatched = true;
561
562
  }
@@ -652,6 +653,7 @@ export class AnthropicOAuthProvider {
652
653
  iteration,
653
654
  useModel,
654
655
  requestKind: opts.requestKind || null,
656
+ prefixSessionId: anthropicPrefixSessionId(opts),
655
657
  });
656
658
  tagMidstreamRetries(result, attemptIndex);
657
659
  return result;
@@ -887,7 +889,16 @@ export class AnthropicOAuthProvider {
887
889
  }
888
890
 
889
891
  // Traces + catalog upkeep for a streamed turn, then the empty-stream guard.
890
- _settleStreamedTurn({ result, midState, sseStartedAt, sessionId, iteration, useModel, requestKind }) {
892
+ _settleStreamedTurn({
893
+ result,
894
+ midState,
895
+ sseStartedAt,
896
+ sessionId,
897
+ iteration,
898
+ useModel,
899
+ requestKind,
900
+ prefixSessionId,
901
+ }) {
891
902
  const ttftMs = midState.ttftAt ? midState.ttftAt - sseStartedAt : null;
892
903
  const liveModel = result.model || useModel;
893
904
  traceAgentSse({
@@ -912,7 +923,7 @@ export class AnthropicOAuthProvider {
912
923
  rawUsage: result.usage?.raw || null,
913
924
  provider: 'anthropic-oauth',
914
925
  requestKind,
915
- prefixBlocks: latestAnthropicPrefix(sessionId),
926
+ prefixBlocks: latestAnthropicPrefix(prefixSessionId),
916
927
  });
917
928
 
918
929
  // Phase I: if the live response surfaced a model id we don't know
@@ -161,6 +161,12 @@ function applyAnthropicSseEvent(framedEvent, { turn, watchdogs, state, progress,
161
161
  return false;
162
162
  case 'message_delta':
163
163
  return turn.onMessageDelta(event);
164
+ case 'ping':
165
+ // Anthropic bridges long generation gaps (adaptive thinking, a buffered
166
+ // tool input) with `ping` events. A distinct kind, not 'semantic': the
167
+ // agent watchdog decides how far a keepalive may extend liveness.
168
+ progress('keepalive');
169
+ return false;
164
170
  case 'message_stop':
165
171
  turn.onMessageStop();
166
172
  // Anthropic streams can keep emitting `:ping` keepalive frames after
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { traceAgentUsage } from '../agent-trace.mjs';
13
13
  import { reportedCostUsd } from './lib/usage-primitives.mjs';
14
- import { latestAnthropicPrefix } from './lib/anthropic-prefix-trace.mjs';
14
+ import { anthropicPrefixSessionId, latestAnthropicPrefix } from './lib/anthropic-prefix-trace.mjs';
15
15
 
16
16
  /**
17
17
  * @param {object} parseResult the assembled turn (streaming or normalized non-streaming)
@@ -48,7 +48,7 @@ export function buildAnthropicTurnResult(parseResult, { provider, useModel, opts
48
48
  // Anthropic usage is additive at this inner transport
49
49
  // boundary, even when OpenCode Go supplies the provider id.
50
50
  inputTokensInclusive: false,
51
- prefixBlocks: latestAnthropicPrefix(opts.sessionId || opts.session?.id || null),
51
+ prefixBlocks: latestAnthropicPrefix(anthropicPrefixSessionId(opts)),
52
52
  });
53
53
  }
54
54