mixdog 0.8.0 → 0.9.0

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 (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * OpenAI Direct API — WebSocket transport via Responses API.
3
3
  *
4
- * Uses the same `sendViaWebSocket` plumbing as openai-oauth (Codex), with two
4
+ * Uses the same `sendViaWebSocket` plumbing as openai-oauth, with two
5
5
  * differences encoded in the `auth.type === 'openai-direct'` branch inside
6
6
  * openai-oauth-ws.mjs:
7
7
  * 1. Authorization header: Bearer <OPENAI_API_KEY> (no account_id, no
@@ -18,7 +18,7 @@ import { enrichModels } from './model-catalog.mjs';
18
18
  import {
19
19
  resolveProviderCacheKey,
20
20
  resolveProviderPromptCacheLane,
21
- } from '../smart-bridge/cache-strategy.mjs';
21
+ } from '../agent-runtime/cache-strategy.mjs';
22
22
 
23
23
  const OPENAI_DIRECT_PRIORITY_MODEL_PATTERNS = Object.freeze([
24
24
  /^gpt-5\.5(?:-\d{4}|$)/,
@@ -67,13 +67,13 @@ export class OpenAIDirectProvider {
67
67
  promptCacheLane,
68
68
  });
69
69
  // Public OpenAI API priority support is documented separately from the
70
- // Codex OAuth catalog. Keep this provider's service-tier decision local
71
- // so gpt-5.4-mini can opt into Priority even when the Codex catalog does
70
+ // openai-oauth catalog. Keep this provider's service-tier decision local
71
+ // so gpt-5.4-mini can opt into Priority even when the OAuth catalog does
72
72
  // not advertise a Fast tier for its OAuth endpoint.
73
73
  applyOpenAIDirectFastTier(body, useModel, opts);
74
74
  // Public Responses API supports prompt_cache_retention='24h' at no
75
75
  // extra cost (same cached_input_tokens billing as the default 5–10
76
- // min in-memory cache). Codex/oauth rejects the parameter, so it's
76
+ // min in-memory cache). openai-oauth rejects the parameter, so it's
77
77
  // injected only on the direct path. See openai-oauth.mjs:290-294
78
78
  // for the rationale.
79
79
  body.prompt_cache_retention = '24h';
@@ -85,7 +85,7 @@ export class OpenAIDirectProvider {
85
85
  // cacheKey (prompt_cache_key) only groups the server-side prefix-cache
86
86
  // shard — safe to share across sessions, unlike the sessionId poolKey
87
87
  // above. buildRequestBody derives it from the base namespace plus a
88
- // model/system/tools hash, mirroring the Codex OAuth path while keeping
88
+ // model/system/tools hash, mirroring the openai-oauth path while keeping
89
89
  // public OpenAI's 24h retention below.
90
90
  const cacheKey = body.prompt_cache_key || resolveProviderCacheKey(opts, 'openai');
91
91
  const iteration = Number.isFinite(Number(opts.iteration)) ? Number(opts.iteration) : null;
@@ -1,21 +1,30 @@
1
- import { OpenAICompatProvider, OPENAI_COMPAT_PRESETS } from './openai-compat.mjs';
2
- import { AnthropicProvider } from './anthropic.mjs';
3
- import { OpenCodeGoProvider } from './opencode-go.mjs';
4
- import { GeminiProvider } from './gemini.mjs';
5
- import { OpenAIOAuthProvider, hasOpenAIOAuthCredentials } from './openai-oauth.mjs';
6
- import { AnthropicOAuthProvider, hasAnthropicOAuthCredentials } from './anthropic-oauth.mjs';
7
- import { GrokOAuthProvider, hasGrokOAuthCredentials } from './grok-oauth.mjs';
8
- import { OpenAIDirectProvider } from './openai-ws.mjs';
1
+ import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
2
+ import {
3
+ hasAnthropicOAuthCredentials,
4
+ hasOpenAIOAuthCredentials,
5
+ hasGrokOAuthCredentials,
6
+ } from './oauth-credential-probes.mjs';
9
7
  import { refreshCatalog as refreshMetadataCatalog, warmModelMetadataCatalogs } from './model-catalog.mjs';
10
- // OpenAI-compat provider names are self-declared by openai-compat.mjs via
8
+ // OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
11
9
  // OPENAI_COMPAT_PRESETS. No parallel list maintained here.
12
10
  const providers = new Map();
11
+ const providerCtors = new Map();
12
+ const providerModulePromises = new Map();
13
13
  // Parallel map: provider name -> signature of the config it was built from.
14
14
  // Lets initProviders() skip reconstructing a provider whose config is byte-for-
15
15
  // byte identical to the live one, so lazy-init misses that re-run initProviders
16
16
  // don't churn (tear down + rebuild) every live provider instance on every call.
17
17
  const signatures = new Map();
18
18
 
19
+ // Module-level init serialization. agent-tool.mjs's ensureProvider() already
20
+ // serializes inits per provider on a chain promise, but its gateOnPrior() lets
21
+ // a queued init PROCEED once a 120s gate expires even if the prior init has
22
+ // not settled — so a slow-but-still-running init and a newer one can reach
23
+ // initProviders() concurrently. This chain makes the clear()+rebuild section
24
+ // strictly sequential at the registry level, independent of any caller gating,
25
+ // so two different config signatures can never interleave their rebuilds.
26
+ let _initChain = Promise.resolve();
27
+
19
28
  // Deterministic structural signature of a provider config. Recursively sorts
20
29
  // object keys so signature equality reflects config-value equality regardless
21
30
  // of key insertion order. Invariant: same config in -> same string out.
@@ -37,7 +46,51 @@ function configSignature(cfg) {
37
46
  return null;
38
47
  }
39
48
  }
49
+
50
+ async function loadProviderExport(cacheKey, spec, exportName) {
51
+ if (!providerModulePromises.has(cacheKey)) {
52
+ providerModulePromises.set(cacheKey, import(spec));
53
+ }
54
+ const mod = await providerModulePromises.get(cacheKey);
55
+ const value = mod?.[exportName];
56
+ if (typeof value !== 'function') throw new Error(`provider export missing: ${exportName}`);
57
+ providerCtors.set(cacheKey, value);
58
+ return value;
59
+ }
60
+
61
+ async function loadProviderCtor(name) {
62
+ if (name === 'anthropic') return loadProviderExport('anthropic', './anthropic.mjs', 'AnthropicProvider');
63
+ if (name === 'gemini') return loadProviderExport('gemini', './gemini.mjs', 'GeminiProvider');
64
+ if (name === 'openai-oauth') return loadProviderExport('openai-oauth', './openai-oauth.mjs', 'OpenAIOAuthProvider');
65
+ if (name === 'anthropic-oauth') return loadProviderExport('anthropic-oauth', './anthropic-oauth.mjs', 'AnthropicOAuthProvider');
66
+ if (name === 'grok-oauth') return loadProviderExport('grok-oauth', './grok-oauth.mjs', 'GrokOAuthProvider');
67
+ if (name === 'openai') return loadProviderExport('openai', './openai-ws.mjs', 'OpenAIDirectProvider');
68
+ if (name === 'opencode-go') return loadProviderExport('opencode-go', './opencode-go.mjs', 'OpenCodeGoProvider');
69
+ if (Object.prototype.hasOwnProperty.call(OPENAI_COMPAT_PRESETS, name)) {
70
+ return loadProviderExport('openai-compat', './openai-compat.mjs', 'OpenAICompatProvider');
71
+ }
72
+ throw new Error(`unknown enabled provider: ${name}`);
73
+ }
74
+
75
+ function instantiateProvider(name, Ctor, cfg) {
76
+ if (Object.prototype.hasOwnProperty.call(OPENAI_COMPAT_PRESETS, name) && name !== 'opencode-go') {
77
+ return new Ctor(name, cfg);
78
+ }
79
+ return new Ctor(cfg);
80
+ }
81
+
40
82
  export async function initProviders(config) {
83
+ // Serialize ALL inits through a single chain so two different config
84
+ // signatures can never run their clear()+rebuild concurrently, regardless
85
+ // of caller-side gating (agent-tool gateOnPrior may release a queued init
86
+ // before the prior one settled). Errors do not poison the chain.
87
+ const run = () => _initProvidersUnsynchronized(config);
88
+ const next = _initChain.then(run, run);
89
+ _initChain = next.then(() => {}, () => {});
90
+ return next;
91
+ }
92
+
93
+ async function _initProvidersUnsynchronized(config) {
41
94
  // Invariant: never wipe the live registry based on an empty / all-disabled
42
95
  // config. Without this guard, a stale `loadAgentConfig()` (e.g. mid-reload
43
96
  // or a transient FS hiccup) would land here as `{}` or `{...,enabled:false}`,
@@ -51,54 +104,30 @@ export async function initProviders(config) {
51
104
  }
52
105
  const next = new Map();
53
106
  const nextSignatures = new Map();
54
- for (const [name, cfg] of entries) {
55
- if (!cfg.enabled)
56
- continue;
107
+ const enabledResults = await Promise.all(entries.map(async ([name, cfg]) => {
108
+ if (!cfg.enabled) return null;
57
109
  // Idempotent reuse: an enabled provider whose config signature is
58
110
  // unchanged from the live registry is carried forward as-is. Only
59
111
  // added or changed providers are (re)constructed below.
60
112
  const sig = configSignature(cfg);
61
113
  if (sig !== null && providers.has(name) && signatures.get(name) === sig) {
62
- next.set(name, providers.get(name));
63
- nextSignatures.set(name, sig);
64
- continue;
114
+ return { name, inst: providers.get(name), sig };
65
115
  }
66
116
  try {
67
- let inst;
68
- if (name === 'anthropic') {
69
- inst = new AnthropicProvider(cfg);
70
- }
71
- else if (name === 'gemini') {
72
- inst = new GeminiProvider(cfg);
73
- }
74
- else if (name === 'openai-oauth') {
75
- inst = new OpenAIOAuthProvider(cfg);
76
- }
77
- else if (name === 'anthropic-oauth') {
78
- inst = new AnthropicOAuthProvider(cfg);
79
- }
80
- else if (name === 'grok-oauth') {
81
- inst = new GrokOAuthProvider(cfg);
82
- }
83
- else if (name === 'openai') {
84
- inst = new OpenAIDirectProvider(cfg);
85
- }
86
- else if (name === 'opencode-go') {
87
- inst = new OpenCodeGoProvider(cfg);
88
- }
89
- else if (Object.prototype.hasOwnProperty.call(OPENAI_COMPAT_PRESETS, name)) {
90
- inst = new OpenAICompatProvider(name, cfg);
91
- }
92
- else {
93
- throw new Error(`unknown enabled provider: ${name}`);
94
- }
95
- next.set(name, inst);
96
- if (sig !== null) nextSignatures.set(name, sig);
117
+ const Ctor = await loadProviderCtor(name);
118
+ const inst = instantiateProvider(name, Ctor, cfg);
119
+ return { name, inst, sig };
97
120
  }
98
121
  catch (err) {
99
122
  const msg = err instanceof Error ? err.message : String(err);
100
- throw new Error(`[provider] Failed to init "${name}": ${msg}`);
123
+ return { name, error: new Error(`[provider] Failed to init "${name}": ${msg}`) };
101
124
  }
125
+ }));
126
+ for (const result of enabledResults) {
127
+ if (!result) continue;
128
+ if (result.error) throw result.error;
129
+ next.set(result.name, result.inst);
130
+ if (result.sig !== null) nextSignatures.set(result.name, result.sig);
102
131
  }
103
132
  // Second guard: every entry was disabled. Same reasoning — keep the
104
133
  // existing registry rather than going dark.
@@ -136,19 +165,27 @@ export function getProvider(name) {
136
165
  // failure) — initProviders then skipped the entry entirely so there is
137
166
  // nothing for the preservation guard to carry forward. Re-probe the
138
167
  // credential each miss: if the credential is now valid, register the
139
- // instance on the spot so subsequent calls hit the cached entry.
168
+ // instance on the spot when that provider module has already been loaded,
169
+ // so subsequent calls hit the cached entry without making registry import
170
+ // pay every provider runtime at boot.
140
171
  if (name === 'anthropic-oauth' && hasAnthropicOAuthCredentials()) {
141
- const inst = new AnthropicOAuthProvider({});
172
+ const Ctor = providerCtors.get('anthropic-oauth');
173
+ if (!Ctor) return undefined;
174
+ const inst = new Ctor({});
142
175
  providers.set(name, inst);
143
176
  return inst;
144
177
  }
145
178
  if (name === 'openai-oauth' && hasOpenAIOAuthCredentials()) {
146
- const inst = new OpenAIOAuthProvider({});
179
+ const Ctor = providerCtors.get('openai-oauth');
180
+ if (!Ctor) return undefined;
181
+ const inst = new Ctor({});
147
182
  providers.set(name, inst);
148
183
  return inst;
149
184
  }
150
185
  if (name === 'grok-oauth' && hasGrokOAuthCredentials()) {
151
- const inst = new GrokOAuthProvider({});
186
+ const Ctor = providerCtors.get('grok-oauth');
187
+ if (!Ctor) return undefined;
188
+ const inst = new Ctor({});
152
189
  providers.set(name, inst);
153
190
  return inst;
154
191
  }
@@ -173,7 +210,7 @@ export function getAllProviders() {
173
210
  }
174
211
  // Background catalog warm-up. Each provider's listModels() either hits its
175
212
  // own cached model list (no-op) or fires a single HTTP refresh. Called from
176
- // agent.init() after providers are registered so the first bridge LLM call
213
+ // agent.init() after providers are registered so the first agent dispatch call
177
214
  // (e.g. cycle1 on session start) does not pay the catalog refresh latency
178
215
  // inline. Fire-and-forget: failures are logged inside each provider.
179
216
  export function warmupCatalogs() {
@@ -27,10 +27,10 @@ import {
27
27
 
28
28
  // HTTP statuses considered transient — safe to retry with backoff.
29
29
  // 408 — request timeout
30
- // 429 — rate limit (caller may still respect Retry-After, but the kind
31
- // classification here only signals "retryable"; sub-error in the
32
- // provider can still treat 429 as permanent for quota-exhausted)
33
30
  // 500/502/503/504 — server errors (overload / bad gateway / timeout)
31
+ // 429 is deliberately excluded: rate-limit/quota windows are deterministic
32
+ // for the current call and must surface immediately instead of sleeping until
33
+ // an outer watchdog reports a misleading timeout.
34
34
  const TRANSIENT_STATUSES = new Set([408, 500, 502, 503, 504])
35
35
 
36
36
  // HTTP statuses that mean "permanent: stop retrying, surface to caller".
@@ -44,7 +44,7 @@ const PERMANENT_STATUSES = new Set([400, 404, 405, 410, 415, 422])
44
44
  // payload but no explicit status code — we sniff the text and assign the most
45
45
  // likely HTTP equivalent so the retry layer can use the same rules.
46
46
  const MESSAGE_PATTERNS = [
47
- // OpenAI/Codex sometimes surfaces generic backend failures only as
47
+ // OpenAI OAuth/API sometimes surfaces generic backend failures only as
48
48
  // message text plus a request ID, with no HTTP status on the WS event.
49
49
  { regex: /(?:an error occurred while processing your request|please include the request id)/i, status: 503 },
50
50
  // Overload / transient 5xx — server is asking us to back off. The `\b`
@@ -58,7 +58,7 @@ const MESSAGE_PATTERNS = [
58
58
  // by classifyError because per-call retry won't change the answer (the
59
59
  // window must elapse). Providers that want time-bounded retry should
60
60
  // honor Retry-After on the original response, not loop here.
61
- { regex: /(?:rate ?limit|quota)/i, status: 429 },
61
+ { regex: /(?:rate[_ -]?limit|quota|too many requests|resource exhausted|insufficient_quota|quota_exceeded)/i, status: 429 },
62
62
  // Auth — never retryable from our side.
63
63
  { regex: /\b(?:unauthorized|unauthorised|authentication|not authenticated|token expired|access token|invalid api key)\b/i, status: 401 },
64
64
  { regex: /\b(?:forbidden|permission denied|policy violation)\b/i, status: 403 },
@@ -212,6 +212,13 @@ export function isRetryable(err) {
212
212
  const DEFAULT_BACKOFF_MS = PROVIDER_RETRY_BACKOFF_MS
213
213
  const DEFAULT_MAX_ATTEMPTS = PROVIDER_RETRY_MAX_ATTEMPTS
214
214
 
215
+ export const MIDSTREAM_BACKOFF_MS = [250, 1000, 2000, 4000]
216
+
217
+ export function midstreamBackoffFor(retryNumber, schedule = MIDSTREAM_BACKOFF_MS) {
218
+ const raw = schedule[Math.min(Math.max(retryNumber, 1), schedule.length) - 1]
219
+ return jitterDelayMs(raw)
220
+ }
221
+
215
222
  export function jitterDelayMs(ms, ratio = PROVIDER_RETRY_JITTER_RATIO, mode = 'symmetric') {
216
223
  const base = Number(ms) || 0
217
224
  if (base <= 0) return 0
@@ -296,13 +303,17 @@ export async function withRetry(fn, opts = {}) {
296
303
  }
297
304
  lastErr = caught
298
305
  populateHttpStatusFromMessage(caught)
299
- const retryAfterMs = retryAfterMsFromError(caught)
300
306
  const status = Number(caught?.httpStatus || caught?.status || caught?.response?.status || 0)
301
307
  const kind = classifyError(caught)
302
- const retryableRateLimit = status === 429 && retryAfterMs != null
303
- if (kind !== 'transient' && !retryableRateLimit) throw caught
308
+ const unsafeToRetry = caught?.unsafeToRetry === true
309
+ || caught?.providerQuota === true
310
+ || caught?.quotaExceeded === true
311
+ if (unsafeToRetry) throw caught
312
+ if (status === 429) throw caught
313
+ if (kind !== 'transient') throw caught
304
314
  // Last attempt failed transiently — propagate to caller.
305
315
  if (attempt === maxAttempts - 1) throw caught
316
+ const retryAfterMs = retryAfterMsFromError(caught)
306
317
  if (retryAfterMs != null) {
307
318
  nextDelayMs = Math.max(0, Math.min(retryAfterMs, maxRetryAfterMs))
308
319
  nextDelayReason = 'retry-after'
@@ -316,7 +327,7 @@ export async function withRetry(fn, opts = {}) {
316
327
  }
317
328
 
318
329
  function _sleepWithAbort(ms, signal) {
319
- return new Promise((resolve) => {
330
+ return new Promise((resolve, reject) => {
320
331
  let onAbort = null
321
332
  const t = setTimeout(() => {
322
333
  if (signal && onAbort) {
@@ -325,8 +336,17 @@ function _sleepWithAbort(ms, signal) {
325
336
  resolve()
326
337
  }, ms)
327
338
  if (!signal) return
328
- if (signal.aborted) { clearTimeout(t); resolve(); return }
329
- onAbort = () => { clearTimeout(t); resolve() }
339
+ if (signal.aborted) {
340
+ clearTimeout(t)
341
+ const reason = signal.reason
342
+ reject(reason instanceof Error ? reason : new Error('sleep aborted'))
343
+ return
344
+ }
345
+ onAbort = () => {
346
+ clearTimeout(t)
347
+ const reason = signal.reason
348
+ reject(reason instanceof Error ? reason : new Error('sleep aborted'))
349
+ }
330
350
  signal.addEventListener('abort', onAbort, { once: true })
331
351
  })
332
352
  }
@@ -1,11 +1,44 @@
1
- let routeMeta;
1
+ let routeMeta = null;
2
+ let routeMetaPromise = null;
2
3
 
3
- try {
4
- routeMeta = await import('../../../../vendor/statusline/src/gateway/route-meta.mjs');
5
- } catch {
6
- routeMeta = await import('../../../gateway/route-meta.mjs');
4
+ async function loadRouteMetaModule() {
5
+ if (routeMeta) return routeMeta;
6
+ if (!routeMetaPromise) {
7
+ routeMetaPromise = import('../../../../vendor/statusline/src/gateway/route-meta.mjs').then((mod) => {
8
+ routeMeta = mod;
9
+ return mod;
10
+ });
11
+ }
12
+ try {
13
+ return await routeMetaPromise;
14
+ } catch (err) {
15
+ routeMetaPromise = null;
16
+ routeMeta = null;
17
+ throw err;
18
+ }
7
19
  }
8
20
 
9
- export const buildGatewayLimits = routeMeta.buildGatewayLimits;
10
- export const recordGatewayUsageEvent = routeMeta.recordGatewayUsageEvent;
11
- export const summarizeGatewayUsage = routeMeta.summarizeGatewayUsage;
21
+ /** Await before calling the sync exports when route-meta may not be ready yet. */
22
+ export async function ensureStatuslineRouteMetaLoaded() {
23
+ return loadRouteMetaModule();
24
+ }
25
+
26
+ function requireRouteMeta() {
27
+ if (!routeMeta) {
28
+ void loadRouteMetaModule().catch(() => {});
29
+ throw new Error('statusline route-meta not loaded');
30
+ }
31
+ return routeMeta;
32
+ }
33
+
34
+ export function buildGatewayLimits(routeInfo, providerOut = null, usageSnapshot = null) {
35
+ return requireRouteMeta().buildGatewayLimits(routeInfo, providerOut, usageSnapshot);
36
+ }
37
+
38
+ export function recordGatewayUsageEvent(summary) {
39
+ return requireRouteMeta().recordGatewayUsageEvent(summary);
40
+ }
41
+
42
+ export function summarizeGatewayUsage(routeInfo, providerOut, compact = null, durationMs = null) {
43
+ return requireRouteMeta().summarizeGatewayUsage(routeInfo, providerOut, compact, durationMs);
44
+ }
@@ -1,12 +1,12 @@
1
- // Post-edit advisory marks: one-shot sidecar on the next read after a write.
1
+ // Post-patch advisory marks: one-shot sidecar on the next read after a patch.
2
2
  import { _normalizeAbs } from './util.mjs';
3
3
 
4
4
  // sessionId -> Map<absPath, { ts, toolName }>
5
5
  const _postEditBySession = new Map();
6
6
 
7
7
  /**
8
- * Mark `path` as just-edited for `sessionId`. Caller invokes this after a
9
- * successful write/edit/apply_patch so the next read on the same path can
8
+ * Mark `path` as just-patched for `sessionId`. Caller invokes this after a
9
+ * successful apply_patch so the next read on the same path can
10
10
  * receive a one-shot advisory sidecar.
11
11
  */
12
12
  export function markPostEdit({ sessionId, path, cwd, toolName }) {
@@ -15,7 +15,7 @@ export function markPostEdit({ sessionId, path, cwd, toolName }) {
15
15
  if (!abs) return;
16
16
  let m = _postEditBySession.get(sessionId);
17
17
  if (!m) { m = new Map(); _postEditBySession.set(sessionId, m); }
18
- m.set(abs, { ts: Date.now(), toolName: String(toolName || 'edit') });
18
+ m.set(abs, { ts: Date.now(), toolName: String(toolName || 'apply_patch') });
19
19
  }
20
20
 
21
21
  /**
@@ -222,7 +222,7 @@ export function setReadCached({ sessionId, args, cwd, content, toolUseId }) {
222
222
 
223
223
  /**
224
224
  * Invalidate all cache entries for `path` in the given session. Called when
225
- * a write-class tool (write/edit/apply_patch) touches the path.
225
+ * a mutation tool (apply_patch) touches the path.
226
226
  */
227
227
  export function invalidatePathForSession(sessionId, path, cwd) {
228
228
  if (!sessionId) return;