mixdog 0.9.147 → 0.9.149

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 (265) hide show
  1. package/package.json +23 -3
  2. package/scripts/build-voice-runtime-unix.sh +101 -0
  3. package/scripts/embedding-quality-probe.mjs +96 -0
  4. package/scripts/generate-voice-runtime-manifest.mjs +300 -0
  5. package/scripts/generate-voice-runtime-manifest.test.mjs +135 -0
  6. package/scripts/native-binary-arch.mjs +30 -13
  7. package/scripts/native-binary-arch.test.mjs +40 -4
  8. package/scripts/plan-voice-runtime-release.mjs +73 -0
  9. package/scripts/prepare-native-assets.mjs +50 -9
  10. package/scripts/prune-embedding-runtime.mjs +3 -3
  11. package/scripts/sync-voice-runtime-manifest.mjs +46 -0
  12. package/scripts/verify-release-assets.mjs +106 -6
  13. package/scripts/verify-voice-runtime-release.mjs +89 -0
  14. package/scripts/voice-runtime-config.json +52 -0
  15. package/src/app.mjs +42 -5
  16. package/src/headless-exec.mjs +58 -32
  17. package/src/headless-exec.test.mjs +74 -13
  18. package/src/rules/lead/01-general.md +2 -0
  19. package/src/rules/shared/10-tool-workflow.md +7 -4
  20. package/src/rules/shared/30-exploration.md +5 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +28 -9
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/config.mjs +53 -18
  24. package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +139 -0
  25. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +34 -5
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -10
  27. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +98 -0
  29. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +90 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +67 -16
  31. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +4 -82
  32. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +0 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -121
  34. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +48 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +215 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +44 -48
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +54 -31
  38. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +56 -2
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +34 -2
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +61 -1
  41. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -1
  42. package/src/runtime/agent/orchestrator/providers/gemini.mjs +61 -15
  43. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +120 -1
  44. package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +9 -0
  45. package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +0 -3
  46. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +187 -84
  47. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +32 -9
  48. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +78 -33
  49. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +21 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
  53. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +20 -11
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +112 -36
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +220 -50
  57. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +112 -9
  58. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
  59. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +130 -60
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
  62. package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
  63. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +69 -3
  64. package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
  65. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
  66. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
  67. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
  68. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
  69. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
  70. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
  71. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
  72. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +43 -5
  73. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
  74. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
  75. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
  76. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
  77. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
  78. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +2 -0
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  80. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
  81. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
  82. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
  83. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
  84. package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
  85. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
  86. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
  87. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
  88. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -9
  89. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
  95. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
  97. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +14 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +180 -31
  100. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
  101. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
  102. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
  103. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
  104. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
  105. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
  109. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
  110. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
  112. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
  113. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
  114. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
  115. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
  116. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
  117. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
  118. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
  119. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
  120. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
  124. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
  125. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
  126. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
  128. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
  130. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
  131. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
  132. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
  133. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
  134. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
  136. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
  137. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
  138. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
  139. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  140. package/src/runtime/attachments/store.mjs +11 -3
  141. package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
  142. package/src/runtime/channels/lib/event-queue.mjs +51 -41
  143. package/src/runtime/channels/lib/scheduler.mjs +23 -28
  144. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
  146. package/src/runtime/channels/lib/webhook.mjs +39 -20
  147. package/src/runtime/channels/lib/whisper-server.mjs +46 -1
  148. package/src/runtime/channels/lib/worker-main.mjs +3 -1
  149. package/src/runtime/media/adapters/gemini-video.mjs +20 -4
  150. package/src/runtime/media/jobs.mjs +13 -0
  151. package/src/runtime/media/renditions.mjs +7 -1
  152. package/src/runtime/media/store.mjs +42 -22
  153. package/src/runtime/memory/index.mjs +4 -9
  154. package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
  155. package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
  156. package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
  157. package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
  158. package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
  159. package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
  160. package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
  161. package/src/runtime/memory/lib/ko-morph.mjs +46 -15
  162. package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
  163. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
  164. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
  165. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
  166. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
  167. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
  168. package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
  169. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  170. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
  171. package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
  172. package/src/runtime/memory/lib/memory.mjs +50 -12
  173. package/src/runtime/memory/lib/pg/process.mjs +1 -10
  174. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
  175. package/src/runtime/memory/lib/query-handlers.mjs +2 -4
  176. package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
  177. package/src/runtime/shared/background-tasks.mjs +15 -0
  178. package/src/runtime/shared/background-tasks.test.mjs +78 -0
  179. package/src/runtime/shared/child-guardian.mjs +2 -10
  180. package/src/runtime/shared/pid-liveness.mjs +27 -0
  181. package/src/runtime/shared/pristine-execution-contract.json +0 -1
  182. package/src/runtime/shared/runtime-root.mjs +1 -11
  183. package/src/runtime/shared/service-discovery.mjs +1 -17
  184. package/src/runtime/shared/singleton-owner.mjs +1 -11
  185. package/src/runtime/shared/staged-update.mjs +2 -12
  186. package/src/runtime/shared/tool-status.mjs +5 -1
  187. package/src/runtime/shared/tool-surface.mjs +15 -7
  188. package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
  189. package/src/runtime/shared/turn-snapshot.mjs +137 -13
  190. package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
  191. package/src/runtime/shared/webhook-session-run.mjs +16 -2
  192. package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
  193. package/src/runtime/web-search/lib/state.mjs +15 -15
  194. package/src/runtime/web-search/lib/web-tools.mjs +46 -5
  195. package/src/session-runtime/context-status.mjs +4 -12
  196. package/src/session-runtime/runtime-core.mjs +20 -1
  197. package/src/session-runtime/session-lifecycle.mjs +9 -0
  198. package/src/standalone/agent-dispatch-broker.mjs +33 -2
  199. package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
  200. package/src/standalone/agent-tool/notify.mjs +3 -1
  201. package/src/standalone/agent-tool/spawn-flow.mjs +52 -70
  202. package/src/standalone/agent-tool/tag-registry.mjs +116 -33
  203. package/src/standalone/agent-tool/tool-def.mjs +2 -2
  204. package/src/standalone/agent-tool/worker-index.mjs +8 -11
  205. package/src/standalone/agent-tool/worker-rows.mjs +4 -1
  206. package/src/standalone/agent-tool.mjs +20 -55
  207. package/src/standalone/agent-watchdog-registry.mjs +28 -4
  208. package/src/standalone/channel-client.mjs +47 -15
  209. package/src/standalone/channel-transport.mjs +44 -25
  210. package/src/standalone/daemon.mjs +31 -2
  211. package/src/standalone/hook-bus/config.mjs +25 -9
  212. package/src/standalone/hook-bus/handlers.mjs +16 -52
  213. package/src/standalone/hook-bus.mjs +29 -23
  214. package/src/standalone/memory-runtime-proxy.mjs +1 -16
  215. package/src/standalone/session-client.mjs +86 -23
  216. package/src/standalone/session-protocol.mjs +0 -2
  217. package/src/standalone/session-runtime-host-health.test.mjs +5 -4
  218. package/src/standalone/session-runtime-host.mjs +101 -6
  219. package/src/standalone/session-runtime-record.mjs +1 -5
  220. package/src/standalone/session-runtime-worker.mjs +52 -57
  221. package/src/standalone/session-service.mjs +4 -1
  222. package/src/standalone/session-transport.mjs +56 -25
  223. package/src/tui/App.jsx +40 -49
  224. package/src/tui/app/app-view.jsx +14 -21
  225. package/src/tui/app/core-memory-picker.mjs +29 -16
  226. package/src/tui/app/create-app-pickers.mjs +15 -36
  227. package/src/tui/app/extension-pickers.mjs +60 -49
  228. package/src/tui/app/maintenance-pickers.mjs +100 -57
  229. package/src/tui/app/message-selector.mjs +6 -5
  230. package/src/tui/app/model-picker.mjs +30 -13
  231. package/src/tui/app/onboarding-steps.mjs +21 -12
  232. package/src/tui/app/panel-epoch.mjs +56 -0
  233. package/src/tui/app/panel-epoch.test.mjs +1009 -0
  234. package/src/tui/app/panel-surface.mjs +98 -0
  235. package/src/tui/app/project-picker.mjs +30 -29
  236. package/src/tui/app/prompt-submit.mjs +192 -97
  237. package/src/tui/app/prompt-submit.test.mjs +206 -0
  238. package/src/tui/app/provider-setup-picker.mjs +74 -43
  239. package/src/tui/app/resume-picker.mjs +11 -8
  240. package/src/tui/app/route-pickers.mjs +45 -28
  241. package/src/tui/app/settings-picker.mjs +69 -18
  242. package/src/tui/app/shell-layout.mjs +1 -3
  243. package/src/tui/app/slash-commands.mjs +0 -2
  244. package/src/tui/app/slash-dispatch.mjs +4 -2
  245. package/src/tui/app/text-entry-policy.mjs +25 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +11 -16
  247. package/src/tui/app/usage-context-panels.mjs +21 -25
  248. package/src/tui/app/use-global-key-input.mjs +3 -2
  249. package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
  250. package/src/tui/app/use-prompt-handlers.mjs +3 -2
  251. package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
  252. package/src/tui/components/Picker.jsx +1 -1
  253. package/src/tui/components/TextEntryPanel.jsx +7 -2
  254. package/src/tui/dist/index.mjs +652 -561
  255. package/src/tui/lib/voice-setup.mjs +1 -2
  256. package/src/tui/session/agent-job-feed.mjs +73 -11
  257. package/src/tui/session/session-api-ext.mjs +5 -6
  258. package/src/tui/session/session-api.mjs +1 -0
  259. package/src/tui/session/tool-approval.mjs +15 -5
  260. package/src/tui/session/tui-steering-persist.mjs +33 -19
  261. package/src/tui/session/tui-steering-persist.test.mjs +104 -0
  262. package/src/tui/session-local.mjs +0 -22
  263. package/scripts/.tmp-coverage-report.mjs +0 -96
  264. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
  265. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.test.mjs +0 -57
@@ -8,7 +8,10 @@ import {
8
8
  mintUuidV7,
9
9
  } from '../session/manager/session-id.mjs';
10
10
  import { buildStableProviderPromptCacheKey } from '../agent-runtime/cache-strategy.mjs';
11
- import { _withCodexWsClientMetadata } from './openai-codex-metadata.mjs';
11
+ import {
12
+ _codexWsCompatibilityHeaders,
13
+ _withCodexWsClientMetadata,
14
+ } from './openai-codex-metadata.mjs';
12
15
  import { codexOriginator, codexUserAgent } from './codex-client-meta.mjs';
13
16
 
14
17
  test('Codex wire identity is a real time-based UUIDv7 and remains session-stable', () => {
@@ -26,14 +29,7 @@ test('Codex wire identity is a real time-based UUIDv7 and remains session-stable
26
29
  assert.equal(ensureCodexWireSessionId(session), sessionId);
27
30
  });
28
31
 
29
- test('Codex cache key and every wire session identity use the same UUIDv7', (t) => {
30
- const previousParity = process.env.MIXDOG_OAI_CODEX_WIRE_PARITY;
31
- process.env.MIXDOG_OAI_CODEX_WIRE_PARITY = '1';
32
- t.after(() => {
33
- if (previousParity == null) delete process.env.MIXDOG_OAI_CODEX_WIRE_PARITY;
34
- else process.env.MIXDOG_OAI_CODEX_WIRE_PARITY = previousParity;
35
- });
36
-
32
+ test('Codex cache key and every wire session identity use the same UUIDv7', () => {
37
33
  const sessionId = mintUuidV7();
38
34
  const turnStartedAtUnixMs = Date.now();
39
35
  const turnId = mintUuidV7(turnStartedAtUnixMs);
@@ -80,11 +76,38 @@ test('Codex cache key and every wire session identity use the same UUIDv7', (t)
80
76
  assert.equal(turnMetadata.session_id, sessionId);
81
77
  assert.equal(turnMetadata.thread_id, sessionId);
82
78
  assert.equal(turnMetadata.turn_id, turnId);
79
+ assert.match(turnMetadata.installation_id, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
83
80
  assert.equal(turnMetadata.agent_name, '/root');
84
81
  assert.equal(turnMetadata.sandbox, 'none');
85
82
  assert.equal(turnMetadata.sandbox_mode, 'danger-full-access');
86
83
  assert.equal(turnMetadata.auto_review_enabled, false);
84
+ assert.equal(turnMetadata.node_repl_auto_review_required, false);
85
+ assert.equal(turnMetadata.node_repl_disabled, false);
87
86
  assert.equal(turnMetadata.turn_started_at_unix_ms, turnStartedAtUnixMs);
87
+
88
+ const prewarmHeaders = _codexWsCompatibilityHeaders({
89
+ cacheKey: promptCacheKey,
90
+ poolKey: session.id,
91
+ model: 'gpt-5.6-sol',
92
+ handshake: true,
93
+ useResponsesLite: true,
94
+ sendOpts: {
95
+ codexSessionId: sessionId,
96
+ codexThreadId: sessionId,
97
+ requestKind: 'prewarm',
98
+ session,
99
+ },
100
+ });
101
+ assert.equal(prewarmHeaders['session-id'], sessionId);
102
+ assert.equal(prewarmHeaders['thread-id'], sessionId);
103
+ assert.equal(prewarmHeaders['x-client-request-id'], sessionId);
104
+ assert.equal('x-codex-installation-id' in prewarmHeaders, false);
105
+ assert.equal('x-openai-internal-codex-responses-lite' in prewarmHeaders, false);
106
+ const prewarmMetadata = JSON.parse(prewarmHeaders['x-codex-turn-metadata']);
107
+ assert.equal(prewarmMetadata.request_kind, 'prewarm');
108
+ assert.equal(prewarmMetadata.turn_id, '');
109
+ assert.equal(prewarmMetadata.installation_id, turnMetadata.installation_id);
110
+ assert.equal('turn_started_at_unix_ms' in prewarmMetadata, false);
88
111
  });
89
112
 
90
113
  // A compaction summary is a request of the same session: same thread identity,
@@ -2,7 +2,13 @@
2
2
  // installation/session/thread/turn identity block that rides every frame, its
3
3
  // handshake-header projection, and the per-turn x-codex-turn-state guard.
4
4
  // Extracted from openai-oauth-ws.mjs, which now owns transport flow only.
5
- import { createHash } from 'crypto';
5
+ import { createHash, randomUUID } from 'crypto';
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { getPluginData } from '../config.mjs';
9
+
10
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11
+ let _installationId = null;
6
12
 
7
13
  function _cleanMetaString(value) {
8
14
  return typeof value === 'string' ? value.trim() : '';
@@ -49,8 +55,38 @@ function _codexRequestKind(sendOpts, sessionId) {
49
55
  }
50
56
 
51
57
  function _codexInstallationId(sendOpts) {
52
- return _cleanMetaString(sendOpts?.installationId || sendOpts?.codexInstallationId || process.env.MIXDOG_CODEX_INSTALLATION_ID)
53
- || `mixdog-${_hashText(`${process.env.USERPROFILE || process.env.HOME || ''}:${process.cwd()}`, 32)}`;
58
+ const explicit = _cleanMetaString(
59
+ sendOpts?.installationId
60
+ || sendOpts?.codexInstallationId
61
+ || process.env.MIXDOG_CODEX_INSTALLATION_ID,
62
+ ).toLowerCase();
63
+ if (UUID_RE.test(explicit)) return explicit;
64
+ if (_installationId) return _installationId;
65
+ const dir = getPluginData();
66
+ const file = join(dir, 'installation_id');
67
+ try {
68
+ const existing = existsSync(file) ? readFileSync(file, 'utf8').trim().toLowerCase() : '';
69
+ if (UUID_RE.test(existing)) {
70
+ _installationId = existing;
71
+ return _installationId;
72
+ }
73
+ } catch {}
74
+ const generated = randomUUID();
75
+ try {
76
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
77
+ try {
78
+ writeFileSync(file, generated, { encoding: 'utf8', flag: 'wx', mode: 0o644 });
79
+ } catch {
80
+ const winner = readFileSync(file, 'utf8').trim().toLowerCase();
81
+ if (UUID_RE.test(winner)) {
82
+ _installationId = winner;
83
+ return _installationId;
84
+ }
85
+ writeFileSync(file, generated, { encoding: 'utf8', mode: 0o644 });
86
+ }
87
+ } catch {}
88
+ _installationId = generated;
89
+ return _installationId;
54
90
  }
55
91
 
56
92
  // The identity block is rebuilt per request: never cached on the pooled
@@ -75,51 +111,42 @@ function _codexMetadataBase(entry, { poolKey, cacheKey, sendOpts, handshake = fa
75
111
  )
76
112
  || rawSessionId;
77
113
  const rawInstallationId = _codexInstallationId(sendOpts);
78
- const wireParity = process.env.MIXDOG_OAI_CODEX_WIRE_PARITY === '1';
79
- const sessionId = wireParity ? _codexUuidV7(rawSessionId) : rawSessionId;
80
- const threadId = wireParity ? _codexUuidV7(rawThreadId) : rawThreadId;
81
- const installationId = wireParity ? _codexUuidV7(rawInstallationId) : rawInstallationId;
114
+ const sessionId = _codexUuidV7(rawSessionId);
115
+ const threadId = _codexUuidV7(rawThreadId);
116
+ const installationId = rawInstallationId;
82
117
  const startedAt = Number.isFinite(Number(sendOpts?.turnStartedAtUnixMs))
83
118
  ? Math.floor(Number(sendOpts.turnStartedAtUnixMs))
84
119
  : _sessionStartedAtUnixMs(rawSessionId);
85
120
  const requestKind = _codexRequestKind(sendOpts, rawSessionId);
86
121
  // The reference client opens the WS with a prewarm (empty turn_id) BEFORE
87
- // the real turn. Under wire parity the handshake IS that prewarm, so its
88
- // turn_id empties and its request_kind becomes 'prewarm' instead of
89
- // presenting the handshake as a live turn. Parity off is unchanged.
122
+ // the real turn, so the handshake is always identified as a prewarm rather
123
+ // than as a live turn.
90
124
  const isPrewarm = requestKind === 'prewarm' || handshake === true;
91
125
  const rawExplicitTurnId = _cleanMetaString(sendOpts?.turnId || sendOpts?.codexTurnId || sendOpts?.session?.turnId);
92
126
  const explicitWindowId = _cleanMetaString(sendOpts?.windowId || sendOpts?.codexWindowId || sendOpts?.session?.windowId);
93
- const turnId = wireParity && isPrewarm
127
+ const turnId = isPrewarm
94
128
  ? ''
95
- : wireParity
96
- ? _codexUuidV7(rawExplicitTurnId || `${rawSessionId}:turn`)
97
- : (rawExplicitTurnId || sessionId);
98
- const effectiveRequestKind = wireParity && isPrewarm ? 'prewarm' : requestKind;
129
+ : _codexUuidV7(rawExplicitTurnId || `${rawSessionId}:turn`);
130
+ const effectiveRequestKind = isPrewarm ? 'prewarm' : requestKind;
99
131
  // Window id is `<thread-id>:<auto-compact window number>`, and that counter
100
132
  // starts at 0: a thread that never auto-compacted reports generation 0 and
101
- // only advances when a new context window opens. The legacy non-parity
102
- // wire kept :1 and is left alone so measured default behavior is unchanged.
103
- const windowId = explicitWindowId || `${threadId}:${wireParity ? 0 : 1}`;
133
+ // only advances when a new context window opens.
134
+ const windowId = explicitWindowId || `${threadId}:0`;
104
135
  const turnMetadata = {
105
136
  installation_id: installationId,
106
137
  session_id: sessionId,
107
138
  thread_id: threadId,
139
+ agent_name: '/root',
108
140
  turn_id: turnId,
109
141
  window_id: windowId,
110
142
  request_kind: effectiveRequestKind,
111
- // Turn-metadata fields the reference client fills on every request.
112
- // They were behind a probe knob after a 2026-07-04 A/B showed no
113
- // isolated effect; they are unconditional now because a partial blob is
114
- // a shape no real client sends. Absolute agent path, not a bare name;
115
- // the sandbox pair reports this runtime honestly (tools run with full
116
- // host access, so there is no sandbox to declare).
117
- agent_name: '/root',
118
143
  thread_source: 'user',
119
144
  sandbox: 'none',
120
145
  sandbox_mode: 'danger-full-access',
121
146
  auto_review_enabled: false,
122
- turn_started_at_unix_ms: startedAt,
147
+ node_repl_auto_review_required: false,
148
+ node_repl_disabled: false,
149
+ ...(!isPrewarm ? { turn_started_at_unix_ms: startedAt } : {}),
123
150
  };
124
151
  return {
125
152
  'x-codex-installation-id': installationId,
@@ -144,16 +171,18 @@ export function _metadataTrace(metadata) {
144
171
  };
145
172
  }
146
173
 
147
- // Handshake projection of the same identity: window id, the turn-metadata
148
- // blob, the installation id, and the routing hint. The reference client sends
149
- // all of them on every request, and a 2026-07-04 A/B measured the blob alone
150
- // lifting prefix-cache hits.
174
+ // The WebSocket handshake carries compatibility identity and routing fields.
175
+ // Installation and Responses Lite data stay in per-request client_metadata.
151
176
  export function _codexWsCompatibilityHeaders(context = {}) {
152
177
  const metadata = _codexMetadataBase(null, context);
153
178
  const headers = {};
179
+ if (metadata.session_id) headers['session-id'] = metadata.session_id;
180
+ if (metadata.thread_id) {
181
+ headers['thread-id'] = metadata.thread_id;
182
+ headers['x-client-request-id'] = metadata.thread_id;
183
+ }
154
184
  if (metadata['x-codex-window-id']) headers['x-codex-window-id'] = metadata['x-codex-window-id'];
155
185
  if (metadata['x-codex-turn-metadata']) headers['x-codex-turn-metadata'] = metadata['x-codex-turn-metadata'];
156
- if (metadata['x-codex-installation-id']) headers['x-codex-installation-id'] = metadata['x-codex-installation-id'];
157
186
  // Routing hint. The reference client attaches this to EVERY request whose
158
187
  // auth is the ChatGPT backend — no flag, no mode, and with `model=` alone
159
188
  // when no service tier is selected. It is how the backend lands the request
@@ -177,9 +206,14 @@ export function _codexWsCompatibilityHeaders(context = {}) {
177
206
  export function _withCodexWsClientMetadata(frame, entry, enabled, context = {}) {
178
207
  if (!enabled || !frame || typeof frame !== 'object') return frame;
179
208
  const base = _codexMetadataBase(entry, context);
209
+ const requestKind = _codexRequestKind(context?.sendOpts, context?.poolKey || '');
210
+ const isPrewarmRequest = requestKind === 'prewarm';
180
211
  const metadata = {
181
212
  ...base,
182
213
  ...(frame.client_metadata && typeof frame.client_metadata === 'object' ? frame.client_metadata : {}),
214
+ ...(context?.useResponsesLite === true
215
+ ? { ws_request_header_x_openai_internal_codex_responses_lite: 'true' }
216
+ : {}),
183
217
  'x-codex-ws-stream-request-start-ms': String(Date.now()),
184
218
  };
185
219
  if (entry && typeof entry === 'object') {
@@ -192,10 +226,21 @@ export function _withCodexWsClientMetadata(frame, entry, enabled, context = {})
192
226
  if (entry.turnStateTurnId == null) {
193
227
  entry.turnStateTurnId = base.turn_id;
194
228
  } else if (entry.turnStateTurnId !== base.turn_id) {
195
- entry.turnState = null;
196
- entry.turnStateTurnId = null;
229
+ // Codex startup prewarm owns the handshake turn-state until
230
+ // the first real turn consumes that prewarmed client session.
231
+ // Adopt it once across prewarm→turn; ordinary turn changes
232
+ // still retire the old token.
233
+ if (entry.turnStateFromPrewarm === true && !isPrewarmRequest) {
234
+ entry.turnStateTurnId = base.turn_id;
235
+ } else {
236
+ entry.turnState = null;
237
+ entry.turnStateTurnId = null;
238
+ }
197
239
  }
198
240
  }
241
+ if (!isPrewarmRequest && entry.turnStateFromPrewarm === true) {
242
+ entry.turnStateFromPrewarm = false;
243
+ }
199
244
  entry.currentTurnId = base.turn_id;
200
245
  }
201
246
  if (entry?.turnState) metadata['x-codex-turn-state'] = String(entry.turnState);
@@ -7,6 +7,23 @@
7
7
  * _displayCodexModel for existing importers.
8
8
  */
9
9
 
10
+ const CODEX_RESPONSES_LITE_CAPABILITY = Object.freeze({
11
+ 'gpt-5.6-sol': true,
12
+ 'gpt-5.6-terra': true,
13
+ 'gpt-5.6-luna': true,
14
+ 'gpt-5.5': false,
15
+ 'gpt-5.4': false,
16
+ 'gpt-5.4-mini': false,
17
+ 'gpt-5.2': false,
18
+ 'codex-auto-review': false,
19
+ });
20
+
21
+ export function _codexUsesResponsesLite(id, modelInfo = null) {
22
+ const explicit = modelInfo?.useResponsesLite ?? modelInfo?.use_responses_lite;
23
+ if (typeof explicit === 'boolean') return explicit;
24
+ return CODEX_RESPONSES_LITE_CAPABILITY[String(id || '').trim()] === true;
25
+ }
26
+
10
27
  // OAuth catalog returns dated ids (gpt-5.4-mini-2026-03-17). Strip the trailing
11
28
  // -YYYY-MM-DD to get the version alias (gpt-5.4-mini). Unknown shapes pass
12
29
  // through unchanged.
@@ -82,6 +99,10 @@ export function _normalizeCodexModel(m) {
82
99
  latest: false,
83
100
  description: m?.description || '',
84
101
  reasoningLevels: (m?.supported_reasoning_levels || []).map(r => r.effort),
102
+ supportVerbosity: m?.support_verbosity === true,
103
+ defaultVerbosity: m?.default_verbosity || null,
104
+ useResponsesLite: m?.use_responses_lite === true,
105
+ supportsReasoningSummaries: m?.supports_reasoning_summaries === true,
85
106
  serviceTiers,
86
107
  defaultServiceTier: m?.default_service_tier || null,
87
108
  additionalSpeedTiers,
@@ -152,11 +152,16 @@ function mergeToolCallDelta(accByIndex, deltaCalls, bucketState) {
152
152
  } else if (tc.function?.name) {
153
153
  const anonId = ++bucketState._nextAnonId;
154
154
  key = `anon:${anonId}`;
155
- bucketState._lastAnonKey = key;
156
155
  } else {
157
156
  key = bucketState._lastAnonKey;
158
157
  if (!key) continue;
159
158
  }
159
+ // Whatever bucket this chunk resolved to becomes the continuation
160
+ // target for later argument-only chunks (no index, no id, no name).
161
+ // Only the anonymous branch used to record it, so a provider that
162
+ // identifies a tool call by id alone and then streams bare argument
163
+ // deltas had every one of those deltas silently discarded.
164
+ bucketState._lastAnonKey = key;
160
165
  let prev = accByIndex.get(key);
161
166
  if (!prev) {
162
167
  prev = {
@@ -210,7 +215,7 @@ function emitCompatToolCallOnce(state, call, onToolCall) {
210
215
  // Fix 2: cross-path name+args dedupe. A synthesized text-leaked call and an
211
216
  // identical native tool_call must fire onToolCall exactly once. state._toolDedupe
212
217
  // is created per stream; when absent (older callers) behavior is unchanged.
213
- if (state._toolDedupe && !state._toolDedupe.shouldDispatch(call.name, call.arguments)) {
218
+ if (state._toolDedupe && !state._toolDedupe.shouldDispatch(call.name, call.arguments, call.id)) {
214
219
  // Still mark the id as emitted so later id-frames for the same native
215
220
  // call don't retry, but do NOT invoke onToolCall (already dispatched).
216
221
  state.emittedToolCallKeys.add(key);
@@ -556,6 +561,33 @@ export async function consumeCompatChatCompletionStream(stream, {
556
561
  };
557
562
  }
558
563
 
564
+ // Reconcile a COMPLETED function_call item into state.toolCalls: fill in the
565
+ // id/name a streamed call may still be missing, or adopt the item as a new
566
+ // call. `response.output_item.done` and the `response.completed` sweep see the
567
+ // same item shape and must land on the same single emit, so both route here.
568
+ // Returns the item id the done-branch still needs for its pendingCalls /
569
+ // tool-tracker bookkeeping.
570
+ function reconcileCompatFunctionCallItem(state, item, label, onToolCall) {
571
+ const itemId = item.id || '';
572
+ const tc = state.toolCalls.find(t => t._pendingItemId === itemId)
573
+ || (item.call_id ? state.toolCalls.find(t => t.id === item.call_id) : null);
574
+ if (tc) {
575
+ if (!tc.id && item.call_id) tc.id = item.call_id;
576
+ if (!tc.name && item.name) tc.name = item.name;
577
+ if (tc.id && tc.name) delete tc._pendingItemId;
578
+ emitCompatToolCallOnce(state, tc, onToolCall);
579
+ } else if (item.call_id && item.name) {
580
+ const call = {
581
+ id: item.call_id,
582
+ name: item.name,
583
+ arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id, name: item.name, finishReason: 'done' }),
584
+ };
585
+ state.toolCalls.push(call);
586
+ emitCompatToolCallOnce(state, call, onToolCall);
587
+ }
588
+ return itemId;
589
+ }
590
+
559
591
  function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesToolCalls, responseOutputText, onStreamDelta, onToolCall, onTextDelta, relayLeakText }) {
560
592
  if (!event || typeof event.type !== 'string') return;
561
593
  const pushToolSearchCall = (item) => {
@@ -671,23 +703,7 @@ function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesT
671
703
  case 'response.output_item.done': {
672
704
  const item = event.item || {};
673
705
  if (item.type === 'function_call') {
674
- const itemId = item.id || '';
675
- const tc = state.toolCalls.find(t => t._pendingItemId === itemId)
676
- || (item.call_id ? state.toolCalls.find(t => t.id === item.call_id) : null);
677
- if (tc) {
678
- if (!tc.id && item.call_id) tc.id = item.call_id;
679
- if (!tc.name && item.name) tc.name = item.name;
680
- if (tc.id && tc.name) delete tc._pendingItemId;
681
- emitCompatToolCallOnce(state, tc, onToolCall);
682
- } else if (item.call_id && item.name) {
683
- const call = {
684
- id: item.call_id,
685
- name: item.name,
686
- arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id, name: item.name, finishReason: 'done' }),
687
- };
688
- state.toolCalls.push(call);
689
- emitCompatToolCallOnce(state, call, onToolCall);
690
- }
706
+ const itemId = reconcileCompatFunctionCallItem(state, item, label, onToolCall);
691
707
  // Drop the resolved function item from pendingCalls before
692
708
  // recomputing toolInFlight — otherwise a completed call keeps
693
709
  // pendingCalls.size > 0 and the latch never clears, so a later
@@ -732,23 +748,7 @@ function handleCompatResponsesStreamEvent(event, state, { label, parseResponsesT
732
748
  }
733
749
  for (const item of resp.output || []) {
734
750
  if (item?.type === 'function_call') {
735
- const itemId = item.id || '';
736
- const tc = state.toolCalls.find(t => t._pendingItemId === itemId)
737
- || (item.call_id ? state.toolCalls.find(t => t.id === item.call_id) : null);
738
- if (tc) {
739
- if (!tc.id && item.call_id) tc.id = item.call_id;
740
- if (!tc.name && item.name) tc.name = item.name;
741
- if (tc.id && tc.name) delete tc._pendingItemId;
742
- emitCompatToolCallOnce(state, tc, onToolCall);
743
- } else if (item.call_id && item.name) {
744
- const call = {
745
- id: item.call_id,
746
- name: item.name,
747
- arguments: parseCompletedToolCallArgumentsJson(item.arguments, label, { id: item.call_id, name: item.name, finishReason: 'done' }),
748
- };
749
- state.toolCalls.push(call);
750
- emitCompatToolCallOnce(state, call, onToolCall);
751
- }
751
+ reconcileCompatFunctionCallItem(state, item, label, onToolCall);
752
752
  try { onStreamDelta?.('tool'); } catch {}
753
753
  reportedBundleProgress = true;
754
754
  } else if (item?.type === 'tool_search_call') {
@@ -44,13 +44,15 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
44
44
  // strict providers (xai) reject unknown roles/types and would 400 the
45
45
  // request. Documented in v0.1.160 (GPT reasoning replay).
46
46
  //
47
- // DeepSeek thinking models require the prior turn's `reasoning_content`
48
- // string to be echoed back inside the assistant message, otherwise the API
49
- // returns 400. xAI reasoning models also preserve their official multi-turn
50
- // shape and cache prefix stability when prior assistant reasoning_content
51
- // is replayed; reasoning_effort itself remains caller/user-selected.
47
+ // DeepSeek splits by model: thinking-mode models with tools REQUIRE the
48
+ // prior turn's `reasoning_content` echoed back (400 without it), while
49
+ // `deepseek-reasoner` REJECTS it with a 400. The caller decides per model
50
+ // through options.replaysReasoningContent — see
51
+ // deepseekReplaysReasoningContent below. xAI reasoning models preserve
52
+ // their official multi-turn shape and cache prefix stability when prior
53
+ // assistant reasoning_content is replayed; reasoning_effort itself remains
54
+ // caller/user-selected.
52
55
  const replaysReasoningContent = options.replaysReasoningContent === true
53
- || providerName === 'deepseek'
54
56
  || providerName === 'xai';
55
57
  const out = [];
56
58
  const pendingToolMedia = [];
@@ -94,6 +96,14 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
94
96
  return out;
95
97
  }
96
98
 
99
+ // deepseek-reasoner returns 400 when `reasoning_content` appears in the input
100
+ // messages; every other DeepSeek thinking model needs it back once tools are in
101
+ // play. Unknown DeepSeek ids default to replaying: a missing echo breaks the
102
+ // tool loop, while a stray field on a non-thinking model is ignored.
103
+ export function deepseekReplaysReasoningContent(model) {
104
+ return !/reasoner/.test(String(model || '').trim().toLowerCase());
105
+ }
106
+
97
107
  export function toOpenAITools(tools) {
98
108
  return tools.map((t) => ({
99
109
  type: 'function',