mixdog 0.9.0 → 0.9.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 (240) hide show
  1. package/package.json +10 -3
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/session-ingest-smoke.mjs +2 -2
  23. package/scripts/task-bench.mjs +207 -0
  24. package/scripts/tool-failures.mjs +6 -6
  25. package/scripts/tool-smoke.mjs +306 -66
  26. package/scripts/toolcall-args-test.mjs +81 -0
  27. package/src/agents/debugger/AGENT.md +4 -4
  28. package/src/agents/heavy-worker/AGENT.md +4 -2
  29. package/src/agents/reviewer/AGENT.md +4 -4
  30. package/src/agents/worker/AGENT.md +4 -2
  31. package/src/app.mjs +10 -6
  32. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  33. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  34. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  35. package/src/headless-role.mjs +14 -14
  36. package/src/help.mjs +1 -0
  37. package/src/lib/mixdog-debug.cjs +0 -22
  38. package/src/lib/plugin-paths.cjs +1 -7
  39. package/src/lib/rules-builder.cjs +34 -56
  40. package/src/mixdog-session-runtime.mjs +710 -319
  41. package/src/output-styles/default.md +12 -7
  42. package/src/output-styles/minimal.md +25 -0
  43. package/src/output-styles/oneline.md +21 -0
  44. package/src/output-styles/simple.md +10 -9
  45. package/src/repl.mjs +12 -4
  46. package/src/rules/agent/00-common.md +7 -5
  47. package/src/rules/agent/30-explorer.md +7 -8
  48. package/src/rules/lead/01-general.md +3 -1
  49. package/src/rules/lead/lead-tool.md +7 -0
  50. package/src/rules/shared/01-tool.md +17 -12
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  52. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  54. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  55. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  56. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  57. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  59. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  60. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  61. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  62. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
  66. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
  67. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  68. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
  75. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  76. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  78. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
  79. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  80. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  81. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  82. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  83. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  85. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  86. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  87. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  92. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  99. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  100. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  104. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  105. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  106. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  107. package/src/runtime/channels/backends/discord.mjs +99 -9
  108. package/src/runtime/channels/backends/telegram.mjs +501 -0
  109. package/src/runtime/channels/index.mjs +441 -1254
  110. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  111. package/src/runtime/channels/lib/config.mjs +54 -3
  112. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  113. package/src/runtime/channels/lib/executor.mjs +0 -3
  114. package/src/runtime/channels/lib/format.mjs +4 -2
  115. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  116. package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
  117. package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
  118. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  119. package/src/runtime/channels/lib/session-discovery.mjs +0 -4
  120. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  121. package/src/runtime/channels/lib/tool-format.mjs +1 -2
  122. package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
  123. package/src/runtime/channels/lib/webhook.mjs +59 -31
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  126. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  127. package/src/runtime/memory/index.mjs +187 -43
  128. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  129. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  130. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  131. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  132. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  133. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  134. package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
  135. package/src/runtime/memory/lib/memory.mjs +101 -4
  136. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  137. package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
  138. package/src/runtime/memory/lib/session-ingest.mjs +116 -7
  139. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  140. package/src/runtime/memory/tool-defs.mjs +6 -3
  141. package/src/runtime/search/index.mjs +2 -7
  142. package/src/runtime/search/lib/config.mjs +0 -4
  143. package/src/runtime/search/lib/state.mjs +1 -15
  144. package/src/runtime/search/lib/web-tools.mjs +0 -1
  145. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  146. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  147. package/src/runtime/shared/child-spawn-gate.mjs +0 -6
  148. package/src/runtime/shared/config.mjs +9 -0
  149. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  150. package/src/runtime/shared/schedules-store.mjs +21 -19
  151. package/src/runtime/shared/tool-surface.mjs +98 -13
  152. package/src/runtime/shared/transcript-writer.mjs +129 -0
  153. package/src/runtime/shared/update-checker.mjs +214 -0
  154. package/src/standalone/agent-tool.mjs +255 -109
  155. package/src/standalone/channel-admin.mjs +133 -40
  156. package/src/standalone/channel-worker.mjs +8 -291
  157. package/src/standalone/explore-tool.mjs +2 -2
  158. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  159. package/src/standalone/provider-admin.mjs +11 -0
  160. package/src/standalone/seeds.mjs +1 -11
  161. package/src/standalone/usage-dashboard.mjs +1 -1
  162. package/src/tui/App.jsx +2137 -750
  163. package/src/tui/components/ConfirmBar.jsx +47 -0
  164. package/src/tui/components/ContextPanel.jsx +5 -3
  165. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  166. package/src/tui/components/Markdown.jsx +22 -98
  167. package/src/tui/components/Message.jsx +14 -35
  168. package/src/tui/components/Picker.jsx +87 -12
  169. package/src/tui/components/PromptInput.jsx +146 -9
  170. package/src/tui/components/QueuedCommands.jsx +1 -1
  171. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  172. package/src/tui/components/Spinner.jsx +7 -7
  173. package/src/tui/components/StatusLine.jsx +40 -21
  174. package/src/tui/components/TextEntryPanel.jsx +51 -7
  175. package/src/tui/components/ToolExecution.jsx +177 -100
  176. package/src/tui/components/TurnDone.jsx +4 -4
  177. package/src/tui/components/UsagePanel.jsx +1 -1
  178. package/src/tui/components/tool-output-format.mjs +312 -40
  179. package/src/tui/components/tool-output-format.test.mjs +180 -1
  180. package/src/tui/display-width.mjs +69 -0
  181. package/src/tui/display-width.test.mjs +35 -0
  182. package/src/tui/dist/index.mjs +7324 -2393
  183. package/src/tui/engine.mjs +287 -126
  184. package/src/tui/index.jsx +117 -7
  185. package/src/tui/keyboard-protocol.mjs +42 -0
  186. package/src/tui/lib/voice-recorder.mjs +453 -0
  187. package/src/tui/markdown/format-token.mjs +354 -142
  188. package/src/tui/markdown/format-token.test.mjs +155 -17
  189. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  190. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  191. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  192. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  193. package/src/tui/markdown/table-layout.mjs +9 -9
  194. package/src/tui/paste-attachments.mjs +0 -11
  195. package/src/tui/prompt-history-store.mjs +129 -0
  196. package/src/tui/prompt-history-store.test.mjs +52 -0
  197. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  198. package/src/tui/theme.mjs +41 -647
  199. package/src/tui/themes/base.mjs +86 -0
  200. package/src/tui/themes/basic.mjs +85 -0
  201. package/src/tui/themes/catppuccin.mjs +72 -0
  202. package/src/tui/themes/dracula.mjs +70 -0
  203. package/src/tui/themes/everforest.mjs +71 -0
  204. package/src/tui/themes/gruvbox.mjs +71 -0
  205. package/src/tui/themes/index.mjs +71 -0
  206. package/src/tui/themes/indigo.mjs +78 -0
  207. package/src/tui/themes/kanagawa.mjs +80 -0
  208. package/src/tui/themes/light.mjs +81 -0
  209. package/src/tui/themes/nord.mjs +72 -0
  210. package/src/tui/themes/onedark.mjs +16 -0
  211. package/src/tui/themes/rosepine.mjs +70 -0
  212. package/src/tui/themes/teal.mjs +81 -0
  213. package/src/tui/themes/tokyonight.mjs +79 -0
  214. package/src/tui/themes/utils.mjs +106 -0
  215. package/src/tui/themes/warm.mjs +79 -0
  216. package/src/tui/transcript-tool-failures.mjs +13 -2
  217. package/src/ui/markdown.mjs +1 -1
  218. package/src/ui/model-display.mjs +2 -2
  219. package/src/ui/statusline.mjs +26 -27
  220. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
  221. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  222. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  223. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  224. package/src/workflows/default/WORKFLOW.md +39 -12
  225. package/src/workflows/sequential/WORKFLOW.md +46 -0
  226. package/src/workflows/solo/WORKFLOW.md +7 -0
  227. package/vendor/ink/build/display-width.js +62 -0
  228. package/vendor/ink/build/ink.js +154 -20
  229. package/vendor/ink/build/measure-text.js +4 -1
  230. package/vendor/ink/build/output.js +115 -9
  231. package/vendor/ink/build/render-node-to-output.js +4 -1
  232. package/vendor/ink/build/render.js +4 -0
  233. package/src/hooks/lib/permission-rules.cjs +0 -170
  234. package/src/hooks/lib/settings-loader.cjs +0 -112
  235. package/src/lib/hook-pipe-path.cjs +0 -10
  236. package/src/output-styles/extreme-simple.md +0 -20
  237. package/src/rules/lead/04-workflow.md +0 -51
  238. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  239. package/src/workflows/default/workflow.json +0 -13
  240. package/src/workflows/solo/workflow.json +0 -7
@@ -1,7 +1,8 @@
1
- import OpenAI from 'openai';
1
+ import { createRequire } from 'node:module';
2
2
  import { createHash } from 'crypto';
3
3
  import { loadConfig } from '../config.mjs';
4
- import { withRetry } from './retry-classifier.mjs';
4
+ import { shouldFallbackTransport, withRetry } from './retry-classifier.mjs';
5
+ import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
5
6
  import { sendViaWebSocket } from './openai-oauth-ws.mjs';
6
7
  import {
7
8
  consumeCompatChatCompletionStream,
@@ -32,6 +33,17 @@ import {
32
33
  customToolCallFromResponseItem,
33
34
  } from './custom-tool-wire.mjs';
34
35
  import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
36
+
37
+ const requireOpenAI = createRequire(import.meta.url);
38
+ let _OpenAI = null;
39
+
40
+ function loadOpenAI() {
41
+ if (!_OpenAI) {
42
+ const mod = requireOpenAI('openai');
43
+ _OpenAI = mod.default || mod.OpenAI || mod;
44
+ }
45
+ return _OpenAI;
46
+ }
35
47
  export { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
36
48
  const PRESETS = OPENAI_COMPAT_PRESETS;
37
49
  const MODEL_LIST_TIMEOUT_MS = resolveTimeoutMs(
@@ -246,33 +258,14 @@ function _envFlag(name, fallback = true) {
246
258
  return !['0', 'false', 'off', 'no'].includes(String(raw).toLowerCase());
247
259
  }
248
260
 
261
+ // xAI WS→HTTP transport fallback → shared shouldFallbackTransport
262
+ // (retry-classifier.mjs). Identical deny-order + allow-list; the per-provider
263
+ // env flag is computed here and passed via `enabled`.
249
264
  function _shouldFallbackXaiWsToHttp(err, signal) {
250
- if (!_envFlag('MIXDOG_XAI_WS_HTTP_FALLBACK', true)) return false;
251
- if (signal?.aborted) return false;
252
- if (err?.liveTextEmitted === true) return false;
253
- if (err?.emittedToolCall === true || err?.unsafeToRetry === true) return false;
254
- const status = Number(err?.httpStatus || err?.status || 0);
255
- if (status === 401 || status === 403 || status === 404 || status === 429) return false;
256
- if (status >= 500 && status < 600) return true;
257
- const code = String(err?.code || '');
258
- if (['EWSACQUIRETIMEOUT', 'ETIMEDOUT', 'ESOCKETTIMEDOUT', 'ECONNRESET', 'EAI_AGAIN', 'ENOTFOUND', 'EAI_NODATA', 'ECONNREFUSED', 'ENETUNREACH', 'EHOSTUNREACH', 'EPIPE'].includes(code)) {
259
- return true;
260
- }
261
- const classifier = String(err?.retryClassifier || err?.midstreamClassifier || '');
262
- if ([
263
- 'timeout', 'reset', 'dns', 'refused', 'network', 'acquire_timeout', 'http_5xx',
264
- 'first_byte_timeout', 'first_meaningful_timeout',
265
- 'ws_1006', 'ws_1011', 'ws_1012', 'ws_1000', 'ws_4000', 'agent_stall', 'stream_stalled',
266
- 'response_failed_disconnected', 'response_failed_network', 'response_failed_auth_expired',
267
- 'ws_send_failed',
268
- ].includes(classifier)) {
269
- return true;
270
- }
271
- if (/^http_5\d\d$/.test(classifier)) return true;
272
- if (err?.firstByteTimeout) return true;
273
- if (err?.firstMeaningfulTimeout) return true;
274
- const msg = String(err?.message || '');
275
- return /opening handshake has timed out|socket hang up|acquire timed out|no first server event|no meaningful output/i.test(msg);
265
+ return shouldFallbackTransport(err, {
266
+ signal,
267
+ enabled: _envFlag('MIXDOG_XAI_WS_HTTP_FALLBACK', true),
268
+ });
276
269
  }
277
270
 
278
271
  function useXaiResponsesWebSocketWarmup(opts, config, { previousResponseId, instructions, rawTools }) {
@@ -989,6 +982,25 @@ function nativeResponsesTools(opts) {
989
982
  ? opts.nativeTools.filter(t => t && typeof t === 'object')
990
983
  : [];
991
984
  }
985
+ // Known tool-name sets for the leaked-tool-call guard, derived from the exact
986
+ // request body so a recovered leaked call is only synthesized when it names a
987
+ // tool the model was actually offered. Chat tools nest the name under
988
+ // `function.name`; Responses tools carry a top-level `name`.
989
+ function knownToolNamesFromOpenAITools(tools) {
990
+ return new Set(
991
+ (Array.isArray(tools) ? tools : [])
992
+ .map((t) => (typeof t?.function?.name === 'string' ? t.function.name
993
+ : typeof t?.name === 'string' ? t.name : null))
994
+ .filter(Boolean),
995
+ );
996
+ }
997
+ function knownToolNamesFromResponsesTools(tools) {
998
+ return new Set(
999
+ (Array.isArray(tools) ? tools : [])
1000
+ .map((t) => (typeof t?.name === 'string' ? t.name : null))
1001
+ .filter(Boolean),
1002
+ );
1003
+ }
992
1004
  export function parseToolCalls(choice, label) {
993
1005
  const calls = choice.message?.tool_calls;
994
1006
  if (!calls?.length)
@@ -1217,10 +1229,20 @@ export class OpenAICompatProvider {
1217
1229
  // extraHeaders behave exactly as before.
1218
1230
  this.defaultHeaders = { ...(preset?.extraHeaders || {}), ...(config.extraHeaders || {}) };
1219
1231
  this.defaultModel = preset?.defaultModel || 'default';
1220
- this.client = new OpenAI({
1232
+ this.client = new (loadOpenAI())({
1221
1233
  baseURL,
1222
1234
  apiKey,
1223
1235
  defaultHeaders: this.defaultHeaders,
1236
+ // The SDK's own retry loop (default 2) would nest underneath our
1237
+ // withRetry wrapper and multiply tail latency on a transient
1238
+ // backend. We own retry/backoff via withRetry, so disable the SDK's.
1239
+ maxRetries: 0,
1240
+ // Force the shared long-keepalive undici dispatcher to be installed
1241
+ // globally (setGlobalDispatcher) so the SDK's global fetch rides a
1242
+ // warm socket pool instead of Node's short-keepalive default. The
1243
+ // return value is undefined once installed globally; the option is
1244
+ // a harmless no-op then.
1245
+ fetchOptions: { dispatcher: getLlmDispatcher() },
1224
1246
  });
1225
1247
  }
1226
1248
  reloadApiKey() {
@@ -1235,10 +1257,12 @@ export class OpenAICompatProvider {
1235
1257
  this.baseURL = baseURL;
1236
1258
  this.apiKey = newKey;
1237
1259
  this.defaultHeaders = { ...(preset?.extraHeaders || {}), ...(this.config.extraHeaders || {}) };
1238
- this.client = new OpenAI({
1260
+ this.client = new (loadOpenAI())({
1239
1261
  baseURL,
1240
1262
  apiKey: newKey,
1241
1263
  defaultHeaders: this.defaultHeaders,
1264
+ maxRetries: 0,
1265
+ fetchOptions: { dispatcher: getLlmDispatcher() },
1242
1266
  });
1243
1267
  }
1244
1268
  } catch { /* best effort */ }
@@ -1258,6 +1282,11 @@ export class OpenAICompatProvider {
1258
1282
  async _doSend(messages, model, tools, sendOpts) {
1259
1283
  const useModel = model || this.defaultModel;
1260
1284
  const opts = sendOpts || {};
1285
+ // Re-warm a kept-alive socket to the provider origin before the turn so
1286
+ // the request hot path lands on a live socket instead of paying a cold
1287
+ // TLS handshake after an idle gap. Fire-and-forget; never awaited. This
1288
+ // mirrors anthropic-oauth's send()-start preconnect.
1289
+ preconnect(this.baseURL);
1261
1290
  if (this.name === 'xai' && useXaiResponsesApi(opts, this.config)) {
1262
1291
  if (useXaiResponsesWebSocket(opts, this.config)) {
1263
1292
  try {
@@ -1358,6 +1387,11 @@ export class OpenAICompatProvider {
1358
1387
  ({ signal: openSignal }) => this.client.chat.completions.create(params, { signal: openSignal }),
1359
1388
  {
1360
1389
  signal: attemptSignal,
1390
+ // Single attempt: this inner wrapper exists only to
1391
+ // apply the first-byte per-attempt timeout. Retry is
1392
+ // owned by the outer withRetry — nesting retry loops
1393
+ // here multiplied tail latency (5x5).
1394
+ maxAttempts: 1,
1361
1395
  perAttemptTimeoutMs: PROVIDER_FIRST_BYTE_TIMEOUT_MS,
1362
1396
  perAttemptLabel: `${this.name} first byte`,
1363
1397
  },
@@ -1370,6 +1404,10 @@ export class OpenAICompatProvider {
1370
1404
  onToolCall: opts.onToolCall,
1371
1405
  onTextDelta: opts.onTextDelta,
1372
1406
  parseToolCalls,
1407
+ // Known tool names for the leaked-tool-call guard:
1408
+ // recovered leaked calls only synthesize when they name
1409
+ // a tool actually offered to this request.
1410
+ knownToolNames: knownToolNamesFromOpenAITools(params.tools),
1373
1411
  });
1374
1412
  },
1375
1413
  {
@@ -1453,6 +1491,13 @@ export class OpenAICompatProvider {
1453
1491
  model: response.model || useModel,
1454
1492
  toolCalls,
1455
1493
  stopReason,
1494
+ // P1 audit fix: a text-only completion that hit finish_reason=
1495
+ // 'length' (no tool calls, so not thrown above as
1496
+ // ProviderIncompleteError) previously returned as an ordinary
1497
+ // success with no signal that the content is a mid-sentence
1498
+ // cutoff. Flag it so loop.mjs can surface a one-line warning
1499
+ // instead of silently treating a truncated answer as complete.
1500
+ ...(stopReason === 'length' && (assembled.content || '').length > 0 ? { truncated: true } : {}),
1456
1501
  ...(reasoningContent ? { reasoningContent } : {}),
1457
1502
  usage: response.usage ? (() => {
1458
1503
  const input = response.usage.prompt_tokens ?? response.usage.input_tokens ?? 0;
@@ -1535,6 +1580,9 @@ export class OpenAICompatProvider {
1535
1580
  ({ signal: openSignal }) => this.client.responses.create(params, { signal: openSignal }),
1536
1581
  {
1537
1582
  signal: attemptSignal,
1583
+ // Single attempt: first-byte timeout only; retry
1584
+ // is owned by the outer withRetry (see chat path).
1585
+ maxAttempts: 1,
1538
1586
  perAttemptTimeoutMs: PROVIDER_FIRST_BYTE_TIMEOUT_MS,
1539
1587
  perAttemptLabel: 'xai responses first byte',
1540
1588
  },
@@ -1548,6 +1596,7 @@ export class OpenAICompatProvider {
1548
1596
  onTextDelta: opts.onTextDelta,
1549
1597
  parseResponsesToolCalls,
1550
1598
  responseOutputText,
1599
+ knownToolNames: knownToolNamesFromResponsesTools(params.tools),
1551
1600
  });
1552
1601
  },
1553
1602
  {
@@ -1617,6 +1666,10 @@ export class OpenAICompatProvider {
1617
1666
  model: response.model || useModel,
1618
1667
  toolCalls,
1619
1668
  stopReason: streamed.stopReason || null,
1669
+ // P1 audit fix: mirror the chat-completions truncated flag for
1670
+ // the xAI Responses HTTP path — a max-output cutoff with real
1671
+ // content must not look identical to a clean stop.
1672
+ ...(streamed.stopReason === 'length' && (streamed.content || '').length > 0 ? { truncated: true } : {}),
1620
1673
  citations: searchSources.citations.length ? searchSources.citations : undefined,
1621
1674
  webSearchCalls: searchSources.webSearchCalls.length ? searchSources.webSearchCalls : undefined,
1622
1675
  providerState: {
@@ -1772,6 +1825,9 @@ export class OpenAICompatProvider {
1772
1825
  model: result.model || useModel,
1773
1826
  toolCalls: result.toolCalls,
1774
1827
  stopReason: result.stopReason || null,
1828
+ // P1 audit fix: same truncated signal as the HTTP path (see
1829
+ // _doSendXaiResponses above) for the WebSocket transport.
1830
+ ...(result.stopReason === 'length' && (result.content || '').length > 0 ? { truncated: true } : {}),
1775
1831
  providerState: {
1776
1832
  ...(opts.providerState || {}),
1777
1833
  xaiResponses: {