mixdog 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/package.json +3 -2
  2. package/scripts/build-shipped-history.mjs +115 -33
  3. package/scripts/check-voice-runtime-inputs.mjs +25 -0
  4. package/scripts/gate-local.mjs +183 -63
  5. package/scripts/latest-guard.mjs +21 -9
  6. package/scripts/lib/gate-scheduler.mjs +138 -32
  7. package/scripts/lib/gate-timing-reporter.mjs +16 -0
  8. package/scripts/lib/isolated-test-env.mjs +10 -5
  9. package/scripts/lib/run-node-tests.mjs +91 -66
  10. package/scripts/lib/scoped-temp.d.mts +16 -0
  11. package/scripts/lib/scoped-temp.mjs +124 -0
  12. package/scripts/lib/stale-temp.mjs +44 -0
  13. package/scripts/lib/test-failure-records.mjs +14 -1
  14. package/scripts/lib/test-failure-summary.mjs +16 -4
  15. package/scripts/lib/test-spawn-ledger.mjs +12 -3
  16. package/scripts/post-release-check.mjs +1 -6
  17. package/scripts/pptx-kit-index.mjs +17 -5
  18. package/scripts/suite-health.mjs +115 -24
  19. package/scripts/test-data-dir.mjs +10 -7
  20. package/scripts/test.mjs +9 -14
  21. package/src/agents/advisor/AGENT.md +4 -4
  22. package/src/agents/advisor/agent.json +1 -1
  23. package/src/agents/explore/AGENT.md +22 -0
  24. package/src/agents/explore/agent.json +6 -0
  25. package/src/app.mjs +4 -0
  26. package/src/cli.mjs +7 -0
  27. package/src/defaults/agents.json +7 -0
  28. package/src/defaults/shipped-history.json +23 -2
  29. package/src/defaults/skills/browser-use/SKILL.md +10 -5
  30. package/src/defaults/skills/code-tidy/SKILL.md +71 -102
  31. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
  32. package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
  33. package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
  34. package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
  35. package/src/defaults/skills/computer-use/SKILL.md +16 -4
  36. package/src/defaults/skills/goal-management/SKILL.md +1 -1
  37. package/src/defaults/skills/setup/references/actions.md +12 -6
  38. package/src/defaults/skills/setup/references/surfaces.md +2 -2
  39. package/src/defaults/skills/setup-guide/SKILL.md +5 -2
  40. package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
  41. package/src/defaults/skills/skill-creator/SKILL.md +85 -190
  42. package/src/defaults/skills/skill-creator/references/design.md +70 -162
  43. package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
  44. package/src/defaults/skills/test-suite/SKILL.md +194 -0
  45. package/src/defaults/skills/test-suite/references/case-management.md +51 -0
  46. package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
  47. package/src/defaults/skills/test-suite/references/reliability.md +49 -0
  48. package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
  49. package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
  50. package/src/defaults/skills/visualize/SKILL.md +6 -2
  51. package/src/lib/rules-builder.cjs +35 -42
  52. package/src/output-styles/common.md +2 -2
  53. package/src/output-styles/simple.md +1 -1
  54. package/src/rules/agent/AGENT.md +5 -4
  55. package/src/rules/lead/LEAD.md +12 -7
  56. package/src/rules/shared/10-tool-workflow.md +3 -1
  57. package/src/rules/shared/20-research.md +3 -6
  58. package/src/rules/shared/40-editing.md +1 -0
  59. package/src/rules/shared/45-destructive.md +0 -2
  60. package/src/rules/shared/50-execution.md +2 -0
  61. package/src/rules/shared/60-verification.md +10 -9
  62. package/src/rules/shared/70-delivery.md +8 -9
  63. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
  64. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
  65. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
  66. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
  67. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
  68. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
  69. package/src/runtime/agent/orchestrator/config.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
  71. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
  72. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
  73. package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
  74. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
  75. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
  76. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
  77. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
  78. package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
  79. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
  80. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
  81. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
  82. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
  83. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
  84. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
  85. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
  86. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
  87. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
  89. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
  90. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
  91. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
  92. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
  93. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
  94. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
  95. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  96. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
  97. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
  99. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
  100. package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
  101. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
  102. package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
  103. package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
  105. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
  106. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
  108. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
  109. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
  110. package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
  113. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
  114. package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
  115. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
  117. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
  118. package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
  119. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  120. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
  122. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  125. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
  127. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  128. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
  129. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
  130. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
  131. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
  132. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
  133. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
  135. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
  136. package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
  137. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
  139. package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
  140. package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
  141. package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
  142. package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
  143. package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
  145. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
  146. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
  147. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
  148. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  149. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
  150. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
  151. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
  152. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
  153. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
  154. package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
  155. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
  156. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
  157. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  158. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
  159. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
  160. package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
  161. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
  162. package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
  163. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
  164. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
  165. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
  166. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
  167. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
  168. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
  169. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
  170. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
  171. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
  172. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
  173. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
  174. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
  175. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
  176. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
  177. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
  178. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
  179. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
  180. package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
  181. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
  182. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
  183. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
  184. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
  185. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
  186. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
  187. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
  188. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
  189. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
  190. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
  191. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
  192. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
  193. package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
  194. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
  195. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
  196. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
  197. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
  198. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
  199. package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
  200. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
  201. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
  202. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
  203. package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
  204. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
  205. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
  206. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
  207. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
  208. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
  209. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
  210. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
  211. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
  212. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
  213. package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
  214. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
  215. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
  216. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
  217. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
  218. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
  219. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
  220. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
  221. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
  222. package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
  223. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
  224. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
  225. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
  226. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
  227. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
  228. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
  229. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
  230. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
  231. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
  232. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
  233. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
  234. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  235. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  236. package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
  237. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
  238. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
  239. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
  240. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
  241. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
  242. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
  243. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
  244. package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
  245. package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
  246. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  248. package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
  249. package/src/runtime/channels/lib/whisper-port.mjs +29 -11
  250. package/src/runtime/channels/lib/whisper-server.mjs +10 -1
  251. package/src/runtime/computer-bridge/action-schema.mjs +2 -2
  252. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  253. package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
  254. package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
  255. package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
  256. package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
  257. package/src/runtime/connectors/gmail-app-password.mjs +3 -3
  258. package/src/runtime/explore/explorer.mjs +93 -0
  259. package/src/runtime/explore/tool-defs.mjs +35 -0
  260. package/src/runtime/explore/tool.mjs +123 -0
  261. package/src/runtime/local-provider/server-process.mjs +2 -1
  262. package/src/runtime/media/adapters/gemini-video.mjs +4 -1
  263. package/src/runtime/media/adapters/xai-media.mjs +2 -1
  264. package/src/runtime/memory/index.mjs +4 -0
  265. package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
  266. package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
  267. package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
  268. package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
  269. package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
  270. package/src/runtime/office/design/design-art-direction.mjs +29 -27
  271. package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
  272. package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
  273. package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
  274. package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
  275. package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
  276. package/src/runtime/office/quality/design-review.mjs +1 -5
  277. package/src/runtime/office/quality/document-brief.mjs +1 -5
  278. package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
  279. package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
  280. package/src/runtime/shared/agent-route-config.mjs +17 -2
  281. package/src/runtime/shared/atomic-file.mjs +103 -2
  282. package/src/runtime/shared/background-tasks.mjs +15 -6
  283. package/src/runtime/shared/bot-owned-work.mjs +56 -0
  284. package/src/runtime/shared/connector-accounts.mjs +5 -5
  285. package/src/runtime/shared/connector-catalog.d.mts +11 -4
  286. package/src/runtime/shared/connector-catalog.mjs +60 -52
  287. package/src/runtime/shared/custom-provider-keys.mjs +20 -0
  288. package/src/runtime/shared/defaults-separation.mjs +1 -4
  289. package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
  290. package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
  291. package/src/runtime/shared/file-has-data.mjs +26 -2
  292. package/src/runtime/shared/file-lock.mjs +13 -15
  293. package/src/runtime/shared/git-launch.d.mts +9 -0
  294. package/src/runtime/shared/git-launch.mjs +213 -0
  295. package/src/runtime/shared/git-runner-worker.mjs +2 -1
  296. package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
  297. package/src/runtime/shared/handoff-artifacts.mjs +18 -8
  298. package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
  299. package/src/runtime/shared/llm/cost.mjs +11 -2
  300. package/src/runtime/shared/llm/http-agent.mjs +313 -29
  301. package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
  302. package/src/runtime/shared/llm/model-catalog.mjs +46 -6
  303. package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
  304. package/src/runtime/shared/llm/system-proxy.mjs +452 -0
  305. package/src/runtime/shared/loopback-listener.mjs +25 -2
  306. package/src/runtime/shared/model-route-chain.d.mts +7 -2
  307. package/src/runtime/shared/model-route-chain.mjs +22 -4
  308. package/src/runtime/shared/open-url.mjs +10 -0
  309. package/src/runtime/shared/provider-accounts.mjs +52 -2
  310. package/src/runtime/shared/session-activity-signal.mjs +14 -0
  311. package/src/runtime/shared/session-scratch.mjs +37 -0
  312. package/src/runtime/shared/staged-install-worker.mjs +5 -0
  313. package/src/runtime/shared/staged-update.mjs +47 -2
  314. package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
  315. package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
  316. package/src/runtime/shared/tool-result-summary.mjs +1 -1
  317. package/src/runtime/shared/tool-surface.mjs +113 -36
  318. package/src/runtime/shared/tool-work-units.mjs +6 -0
  319. package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
  320. package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
  321. package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
  322. package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
  323. package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
  324. package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
  325. package/src/runtime/shared/worktrees/contract.mjs +4 -1
  326. package/src/runtime/shared/worktrees/git.mjs +72 -0
  327. package/src/runtime/shared/worktrees/manager.mjs +285 -168
  328. package/src/runtime/shared/worktrees/test-support.mjs +6 -10
  329. package/src/runtime/tidy/install.mjs +95 -15
  330. package/src/runtime/tidy/languages.mjs +17 -15
  331. package/src/runtime/tidy/process.mjs +7 -4
  332. package/src/runtime/tidy/resolve.mjs +19 -16
  333. package/src/runtime/tidy/runners/biome.mjs +3 -3
  334. package/src/runtime/tidy/tool.mjs +1 -1
  335. package/src/runtime/tidy/worktree.mjs +10 -6
  336. package/src/runtime/visualize-bridge/client.mjs +6 -1
  337. package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
  338. package/src/runtime/web-search/index.mjs +1 -1
  339. package/src/session-runtime/agent-home-delivery.mjs +41 -0
  340. package/src/session-runtime/agent-task-sessions.mjs +458 -312
  341. package/src/session-runtime/agent-tool-routing.mjs +8 -41
  342. package/src/session-runtime/boot/tools.mjs +7 -1
  343. package/src/session-runtime/bot-status.mjs +107 -101
  344. package/src/session-runtime/bot-target.mjs +38 -0
  345. package/src/session-runtime/channel-config-api.mjs +6 -1
  346. package/src/session-runtime/connector-merge-migration.mjs +18 -15
  347. package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
  348. package/src/session-runtime/conversation-import/discovery.mjs +401 -36
  349. package/src/session-runtime/conversation-import/service.mjs +35 -8
  350. package/src/session-runtime/handoff-attachments.mjs +82 -31
  351. package/src/session-runtime/held-requests.mjs +3 -1
  352. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
  353. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
  354. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
  355. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
  356. package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
  357. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
  358. package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
  359. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
  360. package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
  361. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  362. package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
  363. package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
  364. package/src/session-runtime/mixy-heartbeat.mjs +1 -1
  365. package/src/session-runtime/model-route/set-route.mjs +11 -2
  366. package/src/session-runtime/provider-auth/login.mjs +2 -2
  367. package/src/session-runtime/provider-catalog-cache.mjs +82 -36
  368. package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
  369. package/src/session-runtime/provider-models.mjs +4 -2
  370. package/src/session-runtime/resource-mcp-api.mjs +98 -35
  371. package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
  372. package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
  373. package/src/session-runtime/resource-mcp-servers.mjs +5 -4
  374. package/src/session-runtime/resource-recall-api.mjs +6 -1
  375. package/src/session-runtime/runtime-feature-gates.mjs +5 -1
  376. package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
  377. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
  378. package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
  379. package/src/session-runtime/services/agent-tool/render.mjs +71 -17
  380. package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
  381. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
  382. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  383. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
  384. package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
  385. package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
  386. package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
  387. package/src/session-runtime/services/daemon-candidate.mjs +4 -1
  388. package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
  389. package/src/session-runtime/services/plugin-admin.mjs +10 -6
  390. package/src/session-runtime/services/provider-admin.mjs +283 -11
  391. package/src/session-runtime/session-turn-api.mjs +2 -0
  392. package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
  393. package/src/session-runtime/settings-system-api.mjs +1 -1
  394. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
  395. package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
  396. package/src/session-runtime/setup-tool/executor.mjs +6 -2
  397. package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
  398. package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
  399. package/src/session-runtime/turn/agent-ops.mjs +23 -0
  400. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
  401. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
  402. package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
  403. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
  404. package/src/session-runtime/workspace/activity-guards.mjs +176 -0
  405. package/src/session-runtime/workspace/session-view.mjs +149 -0
  406. package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
  407. package/src/session-runtime/workspace/transition-runner.mjs +188 -0
  408. package/src/session-runtime/workspace/transitions.mjs +108 -1041
  409. package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
  410. package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
  411. package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
  412. package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
  413. package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
  414. package/src/standalone/daemon-desktop-runtime.mjs +2 -0
  415. package/src/standalone/daemon-session-control.mjs +169 -138
  416. package/src/standalone/daemon.mjs +7 -0
  417. package/src/standalone/desktop-service-registry.mjs +9 -0
  418. package/src/standalone/local-session-runtime.mjs +5 -1
  419. package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
  420. package/src/standalone/session-client.mjs +5 -1
  421. package/src/standalone/session-frame-stream.mjs +83 -11
  422. package/src/standalone/session-protocol.mjs +4 -0
  423. package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
  424. package/src/standalone/session-service/agent-tree.mjs +45 -9
  425. package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
  426. package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
  427. package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
  428. package/src/standalone/session-service/stored-reader.mjs +127 -40
  429. package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
  430. package/src/tui/App.jsx +154 -164
  431. package/src/tui/app/app-view/floating-panel.jsx +10 -4
  432. package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
  433. package/src/tui/app/clipboard.mjs +21 -10
  434. package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
  435. package/src/tui/app/model-picker/route-selection.mjs +5 -6
  436. package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
  437. package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
  438. package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
  439. package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
  440. package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
  441. package/src/tui/app/provider-setup/provider-items.mjs +8 -2
  442. package/src/tui/app/provider-setup-picker.mjs +29 -18
  443. package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
  444. package/src/tui/app/slash-commands.mjs +1 -0
  445. package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
  446. package/src/tui/app/slash-dispatch.mjs +1 -1
  447. package/src/tui/app/use-btw-panel.mjs +29 -0
  448. package/src/tui/app/use-context-panel-refresh.mjs +31 -0
  449. package/src/tui/app/use-model-catalog-cache.mjs +13 -10
  450. package/src/tui/app/use-mouse-input.mjs +11 -0
  451. package/src/tui/app/use-onboarding-start.mjs +8 -3
  452. package/src/tui/app/use-panel-transition.mjs +10 -1
  453. package/src/tui/app/use-pasted-buffers.mjs +28 -20
  454. package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
  455. package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
  456. package/src/tui/app/use-prompt-queue-history.mjs +3 -2
  457. package/src/tui/app/use-selection-focus.mjs +11 -2
  458. package/src/tui/app/use-selection-geometry.mjs +18 -9
  459. package/src/tui/app/use-selection-paint.mjs +4 -4
  460. package/src/tui/app/use-selection-stitch.mjs +3 -3
  461. package/src/tui/app/use-slash-palette.mjs +62 -0
  462. package/src/tui/app/use-terminal-chrome.mjs +2 -2
  463. package/src/tui/app/use-transcript-activity.mjs +47 -32
  464. package/src/tui/app/use-transcript-follow.mjs +12 -4
  465. package/src/tui/app/use-transcript-scroll.mjs +46 -17
  466. package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
  467. package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
  468. package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
  469. package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
  470. package/src/tui/app/use-transcript-window.mjs +21 -11
  471. package/src/tui/app/use-tui-ready.mjs +18 -0
  472. package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
  473. package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
  474. package/src/tui/components/Picker.jsx +27 -3
  475. package/src/tui/components/QuestionPanel.jsx +13 -4
  476. package/src/tui/components/SignInUrl.jsx +68 -0
  477. package/src/tui/components/TextEntryPanel.jsx +76 -46
  478. package/src/tui/components/sign-in-url-layout.mjs +24 -0
  479. package/src/tui/dist/index.mjs +1963 -1361
  480. package/src/tui/session/goal-turn-state.mjs +6 -2
  481. package/src/tui/session/session-api/controls.mjs +4 -0
  482. package/src/tui/session/session-api/extensions.mjs +8 -0
  483. package/src/tui/session/session-api/intake/abort.mjs +2 -1
  484. package/src/tui/session/session-api/intake.mjs +2 -0
  485. package/src/tui/session/session-api/integrations.mjs +2 -2
  486. package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
  487. package/src/tui/session/session-api/settings.mjs +2 -2
  488. package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
  489. package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
  490. package/src/tui/session/transcript-spill.mjs +92 -4
  491. package/src/tui/session/transcript-store.mjs +2 -0
  492. package/src/ui/statusline-agents.mjs +1 -3
  493. package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
@@ -40,7 +40,6 @@ const COPY = {
40
40
  failureTitle: '로그인 실패',
41
41
  failureBody: 'Mixdog로 돌아가 다시 시도해 주세요.',
42
42
  reason: '사유',
43
- close: '닫기',
44
43
  },
45
44
  en: {
46
45
  successTitle: 'Sign-in complete',
@@ -48,7 +47,6 @@ const COPY = {
48
47
  failureTitle: 'Sign-in failed',
49
48
  failureBody: 'Return to Mixdog and try again.',
50
49
  reason: 'Reason',
51
- close: 'Close',
52
50
  },
53
51
  };
54
52
 
@@ -69,7 +67,6 @@ function renderPage({ kind, providerName, reason, lang }) {
69
67
  const reasonBlock = reason
70
68
  ? `<div class="reason"><span>${langs.map((code) => COPY[code].reason).join(' / ')}</span>${escapeHtml(reason)}</div>`
71
69
  : '';
72
- const close = langs.map((code) => COPY[code].close).join(' · ');
73
70
  const documentTitle = escapeHtml(
74
71
  `${langs.map((code) => COPY[code][kind === 'success' ? 'successTitle' : 'failureTitle']).join(' / ')} · Mixdog`
75
72
  );
@@ -130,20 +127,6 @@ function renderPage({ kind, providerName, reason, lang }) {
130
127
  word-break: break-word;
131
128
  }
132
129
  .reason span { display: block; margin-bottom: 4px; color: var(--muted); font-family: inherit; font-size: 12px; }
133
- button {
134
- margin-top: 24px;
135
- min-width: 120px;
136
- height: 40px;
137
- padding: 0 20px;
138
- border: 1px solid var(--line);
139
- border-radius: 10px;
140
- background: transparent;
141
- color: var(--fg);
142
- font: inherit;
143
- font-size: 14px;
144
- cursor: pointer;
145
- }
146
- button:hover { border-color: var(--muted); }
147
130
  </style>
148
131
  </head>
149
132
  <body>
@@ -151,7 +134,6 @@ function renderPage({ kind, providerName, reason, lang }) {
151
134
  <div class="logo">${LOGO_SVG}</div>
152
135
  ${blocks}
153
136
  ${reasonBlock}
154
- <button type="button" onclick="window.close()">${escapeHtml(close)}</button>
155
137
  </main>
156
138
  </body>
157
139
  </html>`;
@@ -140,14 +140,28 @@ export function isDefinitiveOAuthFailure(text, status = 0) {
140
140
  return s === 401;
141
141
  }
142
142
 
143
- export function scrubOAuthSecrets(text, secretValues = []) {
144
- let scrubbed = String(text || '')
143
+ // Fields whose string values are secrets, snake_case or camelCase. `code` is
144
+ // not one: it is also every API's error/status code. The authorization code
145
+ // is redacted by value — exchange paths pass it in `secretValues`.
146
+ const SECRET_FIELD =
147
+ '(?:access_?token|refresh_?token|id_?token|client_?secret|code_?verifier|code_?challenge|device_?code|api_?key|token|key|secret|password|assertion)';
148
+ const SECRET_KEY_RE = new RegExp(`^${SECRET_FIELD}$`, 'i');
149
+ // Fallback for text that is not JSON. `"field": value` — a string (possibly
150
+ // cut off) or a bare non-numeric value in malformed JSON.
151
+ const JSON_SECRET_RE = new RegExp(
152
+ `"(${SECRET_FIELD})"\\s*:\\s*(?:"(?:[^"\\\\]|\\\\.)*"?|(?!-?\\d|true\\b|false\\b|null\\b)[^,}\\]\\s]+)`,
153
+ 'gi'
154
+ );
155
+ // Form-encoded or query `field=value`.
156
+ const FORM_SECRET_RE = new RegExp(`(^|[^A-Za-z0-9_])(${SECRET_FIELD})=[^&\\s"']*`, 'gi');
157
+ const JWT_RE = /\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}(?:\.[A-Za-z0-9_-]*)?/g;
158
+
159
+ // Bearer/JWT/Anthropic tokens and the given secret values, anywhere in `text`.
160
+ function scrubTokenText(text, secretValues) {
161
+ let scrubbed = text
145
162
  .replace(/Bearer [A-Za-z0-9._-]+/gi, 'Bearer [REDACTED]')
146
163
  .replace(/sk-ant-[A-Za-z0-9._-]+/g, '[REDACTED]')
147
- .replace(
148
- /"(accessToken|refreshToken|access_token|refresh_token|code|key)"\s*:\s*"[^"]+"/g,
149
- (_match, key) => `"${key}":"[REDACTED]"`
150
- );
164
+ .replace(JWT_RE, '[REDACTED]');
151
165
  for (const secret of secretValues) {
152
166
  if (typeof secret === 'string' && secret) {
153
167
  scrubbed = scrubbed.split(secret).join('[REDACTED]');
@@ -155,3 +169,121 @@ export function scrubOAuthSecrets(text, secretValues = []) {
155
169
  }
156
170
  return scrubbed;
157
171
  }
172
+
173
+ // A JSON object or array is redacted on its parsed form, so escaped key names
174
+ // (`client\u005fsecret`) cannot slip past a pattern. Numbers keep their exact
175
+ // source text; an untouched body is returned as it came. Null when `text` is
176
+ // not a JSON object or array.
177
+ function scrubJsonSecrets(text, secretValues) {
178
+ let parsed;
179
+ try {
180
+ parsed = JSON.parse(text, (_key, value, context) =>
181
+ typeof value === 'number' ? JSON.rawJSON(context.source) : value
182
+ );
183
+ } catch {
184
+ return null;
185
+ }
186
+ if (!parsed || typeof parsed !== 'object' || JSON.isRawJSON(parsed)) return null;
187
+ let changed = false;
188
+ // Each decoded string (keys too) gets the full redaction: it may hold
189
+ // form-encoded fields, a query string or JSON of its own.
190
+ const scrubString = (value) => {
191
+ const scrubbed = scrubOAuthSecrets(value, secretValues);
192
+ if (scrubbed !== value) changed = true;
193
+ return scrubbed;
194
+ };
195
+ const walk = (value) => {
196
+ if (typeof value === 'string') return scrubString(value);
197
+ if (!value || typeof value !== 'object' || JSON.isRawJSON(value)) return value;
198
+ if (Array.isArray(value)) return value.map(walk);
199
+ return Object.fromEntries(
200
+ Object.entries(value).map(([key, inner]) => {
201
+ if (SECRET_KEY_RE.test(key) && typeof inner === 'string') {
202
+ changed = true;
203
+ return [key, '[REDACTED]'];
204
+ }
205
+ return [scrubString(key), walk(inner)];
206
+ })
207
+ );
208
+ };
209
+ const scrubbed = walk(parsed);
210
+ return changed ? JSON.stringify(scrubbed) : text;
211
+ }
212
+
213
+ /** Redact OAuth secrets from a body or message: secret fields (decoded JSON
214
+ * keys, or JSON-like and form-encoded text), bearer/JWT/Anthropic tokens,
215
+ * and the given values. Numeric and status codes are kept. */
216
+ export function scrubOAuthSecrets(text, secretValues = []) {
217
+ const raw = String(text || '');
218
+ const json = scrubJsonSecrets(raw, secretValues);
219
+ if (json !== null) return json;
220
+ return scrubTokenText(
221
+ raw
222
+ .replace(JSON_SECRET_RE, (_match, key) => `"${key}":"[REDACTED]"`)
223
+ .replace(FORM_SECRET_RE, (_match, lead, key) => `${lead}${key}=[REDACTED]`),
224
+ secretValues
225
+ );
226
+ }
227
+
228
+ // --- Token-exchange failures a sign-in page may show ---
229
+
230
+ export const OAUTH_NETWORK_HINT = 'Check your proxy/VPN: Mixdog uses the system proxy, or HTTPS_PROXY.';
231
+ const NETWORK_CODE = /^(?:E[A-Z0-9]+|UND_ERR_[A-Z0-9_]+|ERR_[A-Z0-9_]+)$/;
232
+
233
+ /**
234
+ * The transport cause of a fetch that got no answer: its error code
235
+ * (ECONNRESET, UND_ERR_CONNECT_TIMEOUT, ENOTFOUND…) or "timed out". Only codes,
236
+ * never messages, which may quote anything. Null for a cancel or a non-network
237
+ * error.
238
+ */
239
+ export function oauthNetworkCause(error) {
240
+ if (!error || typeof error !== 'object' || error.name === 'AbortError') return null;
241
+ if (error.name === 'TimeoutError') return 'timed out';
242
+ for (let e = error, depth = 0; e && typeof e === 'object' && depth < 5; e = e.cause, depth += 1) {
243
+ if (typeof e.code === 'string' && NETWORK_CODE.test(e.code)) return e.code;
244
+ }
245
+ return error.name === 'TypeError' ? 'network error' : null;
246
+ }
247
+
248
+ // Errors whose message was built only from codes, statuses and redacted
249
+ // bodies: the only ones a failure page quotes.
250
+ const publicErrors = new WeakSet();
251
+
252
+ /** Mark `error` as safe to show on the sign-in failure page. */
253
+ export function oauthPublicError(error) {
254
+ publicErrors.add(error);
255
+ return error;
256
+ }
257
+
258
+ /** A fetch failure as `<operation> request failed (<cause>). <hint>`; other errors unchanged. */
259
+ export function oauthRequestError(operation, error) {
260
+ const cause = oauthNetworkCause(error);
261
+ return cause ? oauthPublicError(new Error(`${operation} request failed (${cause}). ${OAUTH_NETWORK_HINT}`)) : error;
262
+ }
263
+
264
+ /** A token endpoint's body for an error message: secrets redacted, whitespace folded, shortened. */
265
+ export function oauthBodySummary(text, secretValues = []) {
266
+ return scrubOAuthSecrets(text, secretValues).replace(/\s+/g, ' ').trim().slice(0, 500);
267
+ }
268
+
269
+ /** A token endpoint's HTTP failure as `<operation> <status>: <redacted body>`. */
270
+ export function oauthHttpError(operation, status, text, secretValues = []) {
271
+ const summary = oauthBodySummary(text, secretValues);
272
+ return oauthPublicError(new Error(`${operation} ${status}${summary ? `: ${summary}` : ''}`));
273
+ }
274
+
275
+ /** The token response's JSON. A body that does not parse is named by its
276
+ * status only: the parser's message quotes the body, tokens included. */
277
+ export async function oauthResponseJson(response, operation) {
278
+ try {
279
+ return await response.json();
280
+ } catch (error) {
281
+ if (error?.name !== 'SyntaxError') throw oauthRequestError(operation, error);
282
+ throw oauthPublicError(new Error(`${operation} returned a malformed response (HTTP ${response.status})`));
283
+ }
284
+ }
285
+
286
+ /** The failure page reason: the summary, plus the error's message only when it is page-safe. */
287
+ export function oauthFailureReason(summary, error) {
288
+ return error && publicErrors.has(error) ? `${summary}\n${error.message}` : summary;
289
+ }
@@ -2,6 +2,7 @@
2
2
  // X25519 public key we send in the authorize URL; the reply (loopback redirect
3
3
  // or a pasted code) carries `u` = ephemeralPub(32) | nonce(12) | ciphertext | tag(16).
4
4
  import { createServer } from 'node:http';
5
+ import { listenOn } from './lib/listen-on.mjs';
5
6
  import { lookup } from 'node:dns/promises';
6
7
  import { createDecipheriv, createHash, createPublicKey, diffieHellman, generateKeyPairSync } from 'node:crypto';
7
8
  import { openLoginUrl } from './lib/open-login-url.mjs';
@@ -112,16 +113,6 @@ async function loopbackAddresses() {
112
113
  return ['127.0.0.1'];
113
114
  }
114
115
 
115
- function listenOn(server, port, host) {
116
- return new Promise((resolve, reject) => {
117
- server.once('error', reject);
118
- server.listen(port, host, () => {
119
- server.off('error', reject);
120
- resolve();
121
- });
122
- });
123
- }
124
-
125
116
  export async function beginOAuthLogin({
126
117
  openBrowser = true,
127
118
  timeoutMs = LOGIN_TIMEOUT_MS,
@@ -195,7 +195,7 @@ export class MimoOAuthProvider {
195
195
  // publishes nothing. `_listRows` revalidates `seq` and the key before
196
196
  // touching shared state, since enrichment runs after this returns.
197
197
  let published = true;
198
- if (items.length) published = publish(items.map(rosterRow));
198
+ if (items.length) published = await publish(items.map(rosterRow));
199
199
  else items = seedModelItems();
200
200
  return { items, published, seq, apiKey };
201
201
  }
@@ -1,7 +1,31 @@
1
1
  import { readFileSync, existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { getPluginData } from '../config.mjs';
4
- import { updateJsonAtomicSync, writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
4
+ import { updateJsonAtomic, writeJsonAtomicAsync } from '../../../shared/atomic-file.mjs';
5
+
6
+ // Disk writes never block the event loop: they run on the async atomic-file
7
+ // primitives, one at a time per cache file, in the order they were requested
8
+ // (a later write is never overtaken by an earlier one finishing late). Every
9
+ // instance of one file shares these module-level tables, since several
10
+ // instances and several providers can address the same path.
11
+ const writeTails = new Map(); // path → tail of that file's write chain
12
+ // path → JSON text of the newest save() whose write has not finished. Sync
13
+ // readers see it, so a save is visible at once although its disk write is not.
14
+ const unsettledSaves = new Map();
15
+
16
+ function enqueueWrite(p, task) {
17
+ const run = (writeTails.get(p) ?? Promise.resolve()).then(task);
18
+ writeTails.set(p, run);
19
+ run.then(() => {
20
+ if (writeTails.get(p) === run) writeTails.delete(p);
21
+ });
22
+ return run;
23
+ }
24
+
25
+ /** Resolves once every cache write requested so far has settled (shutdown, tests). */
26
+ export async function flushModelCacheWrites() {
27
+ while (writeTails.size) await Promise.all([...writeTails.values()]);
28
+ }
5
29
 
6
30
  // Shared model-catalog disk-cache CRUD, parameterized over the small bits each
7
31
  // provider varies: cache file name, TTL, an optional schema-version gate, and an
@@ -15,9 +39,10 @@ export function makeModelCache({ fileName, ttlMs, version = null, onSave = null
15
39
  // allowStale skips the TTL only; the schema-version gate still applies.
16
40
  function loadEntrySync({ allowStale = false } = {}) {
17
41
  const p = path();
18
- if (!existsSync(p)) return null;
42
+ const unsettled = unsettledSaves.get(p);
43
+ if (unsettled === undefined && !existsSync(p)) return null;
19
44
  try {
20
- const raw = JSON.parse(readFileSync(p, 'utf-8'));
45
+ const raw = JSON.parse(unsettled ?? readFileSync(p, 'utf-8'));
21
46
  if (version != null && raw?.version !== version) return null;
22
47
  if (!raw?.fetchedAt || !Array.isArray(raw.models)) return null;
23
48
  if (!allowStale && Date.now() - raw.fetchedAt > ttlMs) return null;
@@ -31,56 +56,98 @@ export function makeModelCache({ fileName, ttlMs, version = null, onSave = null
31
56
  return loadEntrySync(options)?.models ?? null;
32
57
  }
33
58
 
59
+ // The in-memory state (sync readers, onSave mirror) updates at once; the
60
+ // returned promise settles, never rejecting, when the disk write has.
34
61
  function save(models, extra = {}) {
62
+ const p = path();
63
+ // The value as of this call: later mutation of `models` must not change
64
+ // what lands on disk (the old synchronous write captured it the same way).
65
+ let text;
66
+ let record;
67
+ try {
68
+ text = JSON.stringify({
69
+ ...extra,
70
+ ...(version != null ? { version } : {}),
71
+ fetchedAt: Date.now(),
72
+ models,
73
+ });
74
+ record = JSON.parse(text);
75
+ unsettledSaves.set(p, text);
76
+ } catch {
77
+ return Promise.resolve();
78
+ }
35
79
  try {
36
- writeJsonAtomicSync(
37
- path(),
38
- {
39
- ...extra,
40
- ...(version != null ? { version } : {}),
41
- fetchedAt: Date.now(),
42
- models,
43
- },
44
- { lock: true, fsyncDir: true }
45
- );
46
80
  if (onSave) onSave(models);
47
81
  } catch {
48
82
  /* best-effort */
49
83
  }
84
+ return enqueueWrite(p, async () => {
85
+ try {
86
+ await writeJsonAtomicAsync(p, record, { lock: true, fsyncDir: true });
87
+ } catch {
88
+ /* best-effort */
89
+ } finally {
90
+ if (unsettledSaves.get(p) === text) unsettledSaves.delete(p);
91
+ }
92
+ });
50
93
  }
51
94
 
52
95
  // A discovery request claims a generation when it starts. Its roster is
53
96
  // written only if no newer request started meanwhile — in this process
54
97
  // (generation) or another one (`requestedAt` checked under the file lock) —
55
98
  // and `isCurrent()` (the credential it was fetched with is still active) holds.
99
+ // The returned publish() resolves, never rejecting, to whether the roster was
100
+ // written; the cross-process check runs under the file lock, so the answer
101
+ // is only known once the queued write has run.
56
102
  let generation = 0;
57
103
  function beginSave(isCurrent) {
58
104
  const claimed = ++generation;
59
105
  const requestedAt = Date.now();
106
+ const stillWanted = () => claimed === generation && isCurrent();
60
107
  return (models, extra = {}) => {
61
- if (claimed !== generation || !isCurrent()) return false;
62
- let written = false;
108
+ if (!stillWanted()) return Promise.resolve(false);
109
+ const p = path();
110
+ // The roster as of this call, not as mutated while the write waits.
111
+ let snapshot;
63
112
  try {
64
- updateJsonAtomicSync(
65
- path(),
66
- (cur) => {
67
- if (Number(cur?.requestedAt) > requestedAt) return undefined;
68
- written = true;
69
- return {
70
- ...extra,
71
- ...(version != null ? { version } : {}),
72
- fetchedAt: Date.now(),
73
- requestedAt,
74
- models,
75
- };
76
- },
77
- { fsyncDir: true }
78
- );
113
+ snapshot = JSON.parse(JSON.stringify({ extra, models }));
79
114
  } catch {
80
- /* best-effort */
115
+ return Promise.resolve(false);
81
116
  }
82
- if (written && onSave) onSave(models);
83
- return written;
117
+ return enqueueWrite(p, async () => {
118
+ // A newer request may have started while this write waited its turn.
119
+ if (!stillWanted()) return false;
120
+ let written = false;
121
+ try {
122
+ await updateJsonAtomic(
123
+ p,
124
+ (cur) => {
125
+ if (Number(cur?.requestedAt) > requestedAt) return undefined;
126
+ // The lock wait is asynchronous: a newer request or a credential
127
+ // change may have landed since the checks above. Re-decide now,
128
+ // right before accepting the data.
129
+ if (!stillWanted()) return undefined;
130
+ written = true;
131
+ return {
132
+ ...snapshot.extra,
133
+ ...(version != null ? { version } : {}),
134
+ fetchedAt: Date.now(),
135
+ requestedAt,
136
+ models: snapshot.models,
137
+ };
138
+ },
139
+ { fsyncDir: true }
140
+ );
141
+ } catch {
142
+ /* best-effort */
143
+ }
144
+ try {
145
+ if (written && onSave) onSave(models);
146
+ } catch {
147
+ /* best-effort */
148
+ }
149
+ return written;
150
+ });
84
151
  };
85
152
  }
86
153
 
@@ -435,7 +435,7 @@ export class MuseCodeOAuthProvider {
435
435
  });
436
436
  const rows = discovered.map((row) => museRow(row.id, row));
437
437
  // A superseded or other-credential response never replaces the live roster either.
438
- if (publish(discovered)) inner._enrichedModels = rows;
438
+ if (await publish(discovered)) inner._enrichedModels = rows;
439
439
  return rows;
440
440
  }
441
441
 
@@ -0,0 +1,45 @@
1
+ // The authorization code a test login sends: redacted by value, since `code`
2
+ // alone is also an error/status field.
3
+ export const AUTH_CODE = 'SECRET-CODE';
4
+
5
+ // Every secret field a token endpoint might echo, each with a distinct value
6
+ // that a test can search for in pages, errors and logs.
7
+ export const SECRET_FIELDS = {
8
+ access_token: 'SECRET-ACCESS',
9
+ refresh_token: 'SECRET-REFRESH',
10
+ id_token: 'SECRET-ID',
11
+ client_secret: 'SECRET-CLIENT',
12
+ code: AUTH_CODE,
13
+ code_verifier: 'SECRET-VERIFIER',
14
+ code_challenge: 'SECRET-CHALLENGE',
15
+ device_code: 'SECRET-DEVICE',
16
+ accessToken: 'SECRET-CAMEL-ACCESS',
17
+ refreshToken: 'SECRET-CAMEL-REFRESH',
18
+ idToken: 'SECRET-CAMEL-ID',
19
+ clientSecret: 'SECRET-CAMEL-CLIENT',
20
+ token: 'SECRET-TOKEN',
21
+ };
22
+
23
+ export const SECRET_RE = /SECRET-/;
24
+
25
+ /** Valid JSON whose secret keys are written with escapes, nested in objects and arrays. */
26
+ export const ESCAPED_SECRET_BODY = String.raw`{"error":{"code":400,"details":[{"client\u005fsecret":"SECRET-ESC-CLIENT","Refresh\u005FToken":"SECRET-ESC-REFRESH"},["x",{"\u0069d_token":"SECRET-ESC-ID"}]],"meta":{"api\u005Fkey":"SECRET-ESC-KEY","acc\u0065ss\u005ftoken":"SECRET-ESC-ACCESS"}}}`;
27
+
28
+ /** Valid JSON whose secrets sit inside string values: form-encoded, a query string, nested JSON. */
29
+ export const STRING_SECRET_BODY = JSON.stringify({
30
+ error: 'invalid_request',
31
+ detail: 'access_token=SECRET-FORM-ACCESS&client_secret=SECRET-FORM-CLIENT',
32
+ error_uri: 'https://auth.example/cb?client_secret=SECRET-QUERY-CLIENT&state=ok',
33
+ nested: JSON.stringify({ refresh_token: 'SECRET-NESTED', inner: String.raw`{"client\u005fsecret":"SECRET-DEEP"}` }),
34
+ });
35
+
36
+ /** Token-endpoint failure bodies carrying every secret field (JSON, escaped JSON, strings, form-encoded). */
37
+ export const SECRET_BODIES = [
38
+ JSON.stringify({ error: 'invalid_grant', ...SECRET_FIELDS }),
39
+ ESCAPED_SECRET_BODY,
40
+ STRING_SECRET_BODY,
41
+ new URLSearchParams({ error: 'invalid_grant', ...SECRET_FIELDS }).toString(),
42
+ ];
43
+
44
+ /** A 200 body that is not JSON but carries tokens (the parser would quote it). */
45
+ export const MALFORMED_TOKEN_BODY = '{"access_token":"SECRET-ACCESS","refresh_token":"SECRET-REFRESH" oops';
@@ -184,6 +184,7 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
184
184
  signal: attemptSignal,
185
185
  label: provider.name,
186
186
  providerName: provider.name,
187
+ sessionId: opts.sessionId,
187
188
  onStreamDelta: opts.onStreamDelta,
188
189
  onToolCall: opts.onToolCall,
189
190
  onTextDelta: opts.onTextDelta,
@@ -5,7 +5,14 @@
5
5
  */
6
6
  import { mergeToolCallDelta } from './tool-call-acc.mjs';
7
7
  import { reportStreamItemStart } from '../lib/send-callbacks.mjs';
8
- import { appendText, drainLeakedCalls, relayText, reportProgress, reportTransport } from './stream-state.mjs';
8
+ import {
9
+ appendText,
10
+ drainLeakedCalls,
11
+ relayText,
12
+ reportLifecycle,
13
+ reportProgress,
14
+ reportTransport,
15
+ } from './stream-state.mjs';
9
16
  import { chatContentParts } from '../openai-compat-reasoning-field.mjs';
10
17
 
11
18
  function appendReasoning(state, text, source) {
@@ -50,7 +57,7 @@ export function applyCompatChunk(state, chunk) {
50
57
  const choice = chunk?.choices?.[0];
51
58
  const delta = choice?.delta;
52
59
  if (typeof delta?.role === 'string' && delta.role) {
53
- reportProgress(state, 'semantic');
60
+ reportLifecycle(state, 'semantic');
54
61
  }
55
62
  const contentParts = chatContentParts(delta?.content);
56
63
  if (contentParts) applyContentParts(state, contentParts);
@@ -70,6 +70,13 @@ export function reportProgress(state, kind) {
70
70
  } catch {}
71
71
  }
72
72
 
73
+ /** Chunk metadata without output (a role-only delta): progress once the
74
+ * model has produced output; before that it neither ends the first-output
75
+ * phase nor starts the semantic idle (the chunk already reported transport). */
76
+ export function reportLifecycle(state, kind) {
77
+ if (state.semanticIdleDeadlineAt > 0) reportProgress(state, kind);
78
+ }
79
+
73
80
  /** Text the model exposed: accumulate, refresh the idle deadline, relay live. */
74
81
  export function appendText(state, text) {
75
82
  state.content += text;
@@ -10,6 +10,7 @@ import {
10
10
  PROVIDER_SSE_IDLE_WATCHDOG_ENABLED,
11
11
  PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS,
12
12
  createTimeoutSignal,
13
+ providerFirstOutputTimeoutMs,
13
14
  } from '../stall-policy.mjs';
14
15
  import { TIMEOUT_PHASE } from '../../../shared/provider-timeout-phase.mjs';
15
16
  import {
@@ -28,6 +29,7 @@ import {
28
29
  toolWorkStarted,
29
30
  } from './openai-compat-chat-stream/stream-state.mjs';
30
31
  import { applyCompatChunk } from './openai-compat-chat-stream/chunk-events.mjs';
32
+ import { streamFirstOutputWindow } from './lib/first-output-window.mjs';
31
33
  import { incompleteStreamError, settleCompatStream } from './openai-compat-chat-stream/stream-result.mjs';
32
34
 
33
35
  export async function consumeCompatChatCompletionStream(
@@ -45,11 +47,20 @@ export async function consumeCompatChatCompletionStream(
45
47
  semanticIdleTimeoutMs,
46
48
  firstByteTimeoutMs,
47
49
  leakedToolCallRecovery = true,
50
+ sessionId = null,
48
51
  } = {}
49
52
  ) {
50
53
  const idleOverrideEnabled = Number.isFinite(Number(semanticIdleTimeoutMs)) && Number(semanticIdleTimeoutMs) > 0;
51
54
  const idleEnabled = idleOverrideEnabled || PROVIDER_SSE_IDLE_WATCHDOG_ENABLED;
52
55
  const idleMs = idleOverrideEnabled ? Number(semanticIdleTimeoutMs) : PROVIDER_SEMANTIC_IDLE_TIMEOUT_MS;
56
+ // Until the first semantic output the logical request's first-output window
57
+ // bounds the stream; a request that already had output runs under the
58
+ // semantic idle from the stream's opening.
59
+ const firstOutput = streamFirstOutputWindow({
60
+ sessionId,
61
+ onStreamDelta,
62
+ timeoutMs: providerFirstOutputTimeoutMs(idleMs),
63
+ });
53
64
  const state = createCompatStreamState({
54
65
  providerName,
55
66
  knownToolNames,
@@ -60,6 +71,7 @@ export async function consumeCompatChatCompletionStream(
60
71
  onStreamItemStart,
61
72
  leakedToolCallRecovery,
62
73
  });
74
+ if (firstOutput.outputSeen) state.semanticIdleDeadlineAt = Date.now() + idleMs;
63
75
  reportTransport(state);
64
76
  const iterator = stream[Symbol.asyncIterator]();
65
77
  let iteratorDone = false;
@@ -72,9 +84,8 @@ export async function consumeCompatChatCompletionStream(
72
84
  while (true) {
73
85
  const { value: chunk, done } = await nextAsyncWithWatchdog(iterator, {
74
86
  signal: state.sawFirstEvent ? signal : firstByteTimeout.signal,
75
- idleMs,
76
- idleDeadlineAt: state.semanticIdleDeadlineAt,
77
- idleEnabled: state.sawFirstEvent && idleEnabled && state.semanticIdleDeadlineAt > 0,
87
+ ...firstOutput.idleWatch(state.semanticIdleDeadlineAt, idleMs),
88
+ idleEnabled: state.sawFirstEvent && idleEnabled,
78
89
  idleLabel: `${label} SSE idle`,
79
90
  streamLabel: label,
80
91
  emittedToolCall: () => toolWorkStarted(state),
@@ -147,7 +147,8 @@ function settleMaxOutputIncomplete(event, state, ctx, reason) {
147
147
  function onCreated(event, state, ctx) {
148
148
  if (event.response?.model) state.model = event.response.model;
149
149
  if (event.response?.id) state.responseId = event.response.id;
150
- signal(ctx, 'semantic');
150
+ // Metadata, not output: progress only once the stream has seen output.
151
+ ctx.onLifecycle?.('semantic');
151
152
  }
152
153
 
153
154
  function onOutputTextDelta(event, state, ctx) {
@@ -196,7 +197,8 @@ function onOutputItemAdded(event, state, ctx) {
196
197
  state.toolInFlight = true;
197
198
  }
198
199
  reportStreamItemStart(ctx.onStreamItemStart, responsesItemStart(event.item));
199
- signal(ctx, state.toolInFlight ? 'tool' : 'semantic');
200
+ if (state.toolInFlight) signal(ctx, 'tool');
201
+ else ctx.onLifecycle?.('semantic');
200
202
  }
201
203
 
202
204
  // A tool call's args (or custom-tool input) are streaming — mark tool work
@@ -241,6 +241,7 @@ async function streamCompatResponses({ provider, params, label, signal, opts, he
241
241
  return await consumeCompatResponsesStream(stream, {
242
242
  signal: attemptSignal,
243
243
  label,
244
+ sessionId: opts.sessionId,
244
245
  onStreamDelta: opts.onStreamDelta,
245
246
  onToolCall: opts.onToolCall,
246
247
  onTextDelta: opts.onTextDelta,
@@ -168,9 +168,11 @@ export async function nextAsyncWithWatchdog(
168
168
  }
169
169
  // The SDK JSON-decodes each frame inside next(): a SyntaxError here is a
170
170
  // malformed wire frame, typed at this boundary and nowhere else.
171
- iterator.next().then(resolve, (error) =>
172
- reject(error instanceof SyntaxError ? malformedCompatStreamError(streamLabel || idleLabel, error) : error)
173
- );
171
+ iterator
172
+ .next()
173
+ .then(resolve, (error) =>
174
+ reject(error instanceof SyntaxError ? malformedCompatStreamError(streamLabel || idleLabel, error) : error)
175
+ );
174
176
  });
175
177
  } catch (err) {
176
178
  if (idleTimedOut)