mixdog 1.1.1 → 1.2.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 (317) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/scripts/lib/run-node-tests.mjs +9 -0
  4. package/scripts/prune-desktop-runtime.mjs +3 -0
  5. package/scripts/release-paths.mjs +39 -2
  6. package/scripts/test-direct.mjs +4 -2
  7. package/src/defaults/shipped-history.json +6 -0
  8. package/src/defaults/skills/setup/references/actions.md +5 -2
  9. package/src/defaults/skills/visualize/SKILL.md +157 -0
  10. package/src/defaults/skills/visualize/references/craft.md +88 -0
  11. package/src/headless-command.mjs +56 -3
  12. package/src/headless-exec.mjs +17 -1
  13. package/src/headless-role.mjs +2 -2
  14. package/src/help.mjs +2 -0
  15. package/src/lib/rules-builder.cjs +0 -23
  16. package/src/rules/lead/LEAD.md +2 -2
  17. package/src/rules/shared/70-delivery.md +2 -2
  18. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
  19. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +1 -0
  20. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -0
  21. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  22. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +17 -11
  23. package/src/runtime/agent/orchestrator/config-storage.mjs +1 -0
  24. package/src/runtime/agent/orchestrator/config.mjs +9 -1
  25. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +47 -7
  26. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +51 -8
  27. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +25 -5
  28. package/src/runtime/agent/orchestrator/mcp/client.mjs +348 -122
  29. package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +3 -0
  31. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +15 -1
  32. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +13 -1
  33. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -0
  34. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +2 -0
  35. package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +12 -4
  37. package/src/runtime/agent/orchestrator/providers/effort-rejection.mjs +244 -0
  38. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  40. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +8 -40
  41. package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +51 -12
  43. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +37 -2
  44. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +67 -0
  45. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +521 -0
  46. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +251 -0
  47. package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +2 -0
  48. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +69 -102
  49. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +3 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
  51. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +1 -1
  52. package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +5 -2
  53. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +3 -0
  54. package/src/runtime/agent/orchestrator/providers/registry.mjs +15 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +10 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +36 -3
  57. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +12 -11
  58. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +16 -1
  59. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +11 -0
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +11 -8
  61. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -0
  62. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +9 -5
  63. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +18 -8
  64. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +10 -3
  65. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +18 -11
  66. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +13 -5
  67. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +362 -0
  68. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +253 -0
  69. package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
  70. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/diagnostic.mjs +1 -7
  72. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/refusal.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +3 -2
  74. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +1 -0
  76. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +17 -7
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +3 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -2
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -0
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +5 -3
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +5 -2
  82. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +37 -16
  83. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +4 -0
  84. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +90 -0
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +22 -12
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +27 -0
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -1
  89. package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
  92. package/src/runtime/agent/orchestrator/session/message-ids.mjs +123 -0
  93. package/src/runtime/agent/orchestrator/session/message-ids.test-support.mjs +29 -0
  94. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +29 -19
  95. package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
  96. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +3 -0
  97. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +10 -3
  98. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +11 -0
  99. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +29 -0
  100. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +5 -1
  101. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +23 -4
  102. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +20 -3
  103. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +28 -8
  104. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +140 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +52 -2
  106. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +32 -22
  107. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +10 -3
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-cache-policy.mjs +8 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +31 -12
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-single-fast-paths.mjs +32 -35
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +11 -12
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +9 -6
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -66
  114. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +8 -3
  115. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +16 -7
  117. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
  118. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +137 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +11 -10
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +21 -11
  121. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +12 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +56 -13
  123. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +58 -0
  124. package/src/runtime/agent/orchestrator/tools/code-graph/trusted-roots.mjs +25 -1
  125. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +78 -5
  126. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +2 -0
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +0 -10
  128. package/src/runtime/attachments/store.mjs +38 -0
  129. package/src/runtime/channels/index.mjs +1 -5
  130. package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
  131. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
  132. package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
  133. package/src/runtime/channels/lib/worker-main.mjs +4 -34
  134. package/src/runtime/shared/atomic-file.mjs +8 -2
  135. package/src/runtime/shared/best-effort-stderr.mjs +8 -0
  136. package/src/runtime/shared/code-graph-state.mjs +12 -0
  137. package/src/runtime/shared/defaults-separation.mjs +2 -0
  138. package/src/runtime/shared/git-runner-worker.mjs +3 -2
  139. package/src/runtime/shared/git-runner.mjs +4 -3
  140. package/src/runtime/shared/llm/default-context-window.mjs +6 -0
  141. package/src/runtime/shared/llm/model-catalog-projection.mjs +5 -4
  142. package/src/runtime/shared/llm/model-catalog.mjs +240 -118
  143. package/src/runtime/shared/llm/provider-catalog-cache.mjs +36 -28
  144. package/src/runtime/shared/llm/provider-catalog-registry.mjs +156 -0
  145. package/src/runtime/shared/llm/provider-seed-rosters.mjs +130 -0
  146. package/src/runtime/shared/model-proxy-mode.d.mts +3 -0
  147. package/src/runtime/shared/model-proxy-mode.mjs +19 -0
  148. package/src/runtime/shared/provider-accounts.mjs +2 -2
  149. package/src/runtime/shared/provider-api-key.mjs +21 -1
  150. package/src/runtime/shared/schedule-model-ref.mjs +11 -2
  151. package/src/runtime/shared/semver.d.mts +1 -0
  152. package/src/runtime/shared/semver.mjs +57 -0
  153. package/src/runtime/shared/session-usage-totals.mjs +19 -0
  154. package/src/runtime/shared/sharp-electron-linux.mjs +25 -0
  155. package/src/runtime/shared/singleton-owner.mjs +36 -6
  156. package/src/runtime/shared/statusline/statusline-segments.mjs +68 -14
  157. package/src/runtime/shared/tool-surface.mjs +9 -2
  158. package/src/runtime/shared/tool-work-units.mjs +14 -0
  159. package/src/runtime/shared/transcript-metadata.mjs +17 -1
  160. package/src/runtime/shared/turn-changes.mjs +791 -0
  161. package/src/runtime/shared/turn-snapshot-store.mjs +346 -12
  162. package/src/runtime/shared/turn-snapshot.mjs +690 -81
  163. package/src/runtime/shared/turn-worktree-snapshot.mjs +161 -11
  164. package/src/runtime/shared/update-checker.mjs +2 -31
  165. package/src/runtime/shared/worktrees/cache-cleanups.mjs +52 -0
  166. package/src/runtime/shared/worktrees/contract.mjs +551 -0
  167. package/src/runtime/shared/worktrees/disk.mjs +57 -0
  168. package/src/runtime/shared/worktrees/git.mjs +247 -0
  169. package/src/runtime/shared/worktrees/include.mjs +480 -0
  170. package/src/runtime/shared/worktrees/lock.mjs +46 -0
  171. package/src/runtime/shared/worktrees/maintenance.mjs +96 -0
  172. package/src/runtime/shared/worktrees/manager.mjs +1215 -0
  173. package/src/runtime/shared/worktrees/merge.mjs +182 -0
  174. package/src/runtime/shared/worktrees/owner.mjs +46 -0
  175. package/src/runtime/shared/worktrees/paths.mjs +62 -0
  176. package/src/runtime/shared/worktrees/registry.mjs +110 -0
  177. package/src/runtime/shared/worktrees/settings.mjs +84 -0
  178. package/src/runtime/shared/worktrees/slug.mjs +62 -0
  179. package/src/runtime/shared/worktrees/test-support.mjs +96 -0
  180. package/src/runtime/visualize-bridge/client.mjs +181 -0
  181. package/src/runtime/visualize-bridge/tool-defs.mjs +97 -0
  182. package/src/session-runtime/boot/apis.mjs +35 -0
  183. package/src/session-runtime/boot/begin.mjs +0 -2
  184. package/src/session-runtime/boot/config.mjs +2 -0
  185. package/src/session-runtime/boot/facade.mjs +20 -0
  186. package/src/session-runtime/boot/infrastructure.mjs +1 -0
  187. package/src/session-runtime/boot/session.mjs +1 -0
  188. package/src/session-runtime/boot/tools.mjs +9 -4
  189. package/src/session-runtime/config-lifecycle.mjs +10 -1
  190. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +11 -1
  191. package/src/session-runtime/global-extensions.mjs +2 -2
  192. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +27 -2
  193. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +32 -1
  194. package/src/session-runtime/internal-tool-executor.mjs +9 -1
  195. package/src/session-runtime/lifecycle/session-catalog.mjs +8 -1
  196. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +5 -0
  197. package/src/session-runtime/lifecycle/session-switch/resume.mjs +21 -3
  198. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +16 -0
  199. package/src/session-runtime/mcp-glue/connect.mjs +11 -6
  200. package/src/session-runtime/mcp-glue/server-catalog.mjs +7 -3
  201. package/src/session-runtime/mcp-glue/server-input.mjs +29 -0
  202. package/src/session-runtime/model-route/route-persist.mjs +3 -0
  203. package/src/session-runtime/model-route/set-route.mjs +15 -2
  204. package/src/session-runtime/notification-bus.mjs +9 -1
  205. package/src/session-runtime/provider-models/row-hydration.mjs +2 -0
  206. package/src/session-runtime/resource-extension-sync.mjs +30 -7
  207. package/src/session-runtime/resource-mcp-api.mjs +27 -10
  208. package/src/session-runtime/route-resolution.mjs +10 -1
  209. package/src/session-runtime/route-state.mjs +16 -2
  210. package/src/session-runtime/runtime-bootstrap.mjs +0 -4
  211. package/src/session-runtime/runtime-core.mjs +5 -0
  212. package/src/session-runtime/runtime-facade.mjs +8 -0
  213. package/src/session-runtime/runtime-paths.mjs +0 -1
  214. package/src/session-runtime/runtime-review-api.mjs +9 -0
  215. package/src/session-runtime/services/agent-tool/helpers.mjs +5 -0
  216. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +3 -0
  217. package/src/session-runtime/services/agent-tool/job-views/job-lookup.mjs +1 -0
  218. package/src/session-runtime/services/agent-tool/job-views/spawn-meta.mjs +1 -0
  219. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +15 -0
  220. package/src/session-runtime/services/agent-tool/send-flow.mjs +1 -0
  221. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +10 -2
  222. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -7
  223. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
  224. package/src/session-runtime/services/agent-tool/tag-registry.mjs +2 -1
  225. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -0
  226. package/src/session-runtime/services/agent-tool/worker-rows.mjs +1 -0
  227. package/src/session-runtime/services/agent-tool.mjs +1 -0
  228. package/src/session-runtime/services/channel-worker.mjs +1 -1
  229. package/src/session-runtime/services/daemon-candidate.mjs +3 -2
  230. package/src/session-runtime/services/daemon-crash-capture/spawn-capture.mjs +55 -0
  231. package/src/session-runtime/session-create/session-options.mjs +10 -1
  232. package/src/session-runtime/session-turn-api.mjs +1 -0
  233. package/src/session-runtime/settings-api.mjs +4 -2
  234. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -2
  235. package/src/session-runtime/settings-profile-api.mjs +27 -2
  236. package/src/session-runtime/settings-worktree-api.mjs +17 -0
  237. package/src/session-runtime/setup-tool/executor.mjs +11 -4
  238. package/src/session-runtime/setup-tool/settings-contract.mjs +3 -1
  239. package/src/session-runtime/setup-tool/tool-defs.mjs +31 -3
  240. package/src/session-runtime/starter-guide-state.mjs +62 -0
  241. package/src/session-runtime/turn/agent-ops.mjs +12 -1
  242. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +13 -3
  243. package/src/session-runtime/turn/turn-run/snapshot-tracker.mjs +20 -15
  244. package/src/session-runtime/turn/turn-run/turn-open.mjs +4 -5
  245. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +29 -1
  246. package/src/session-runtime/turn/turn-run/turn-settle.mjs +6 -1
  247. package/src/session-runtime/turn/turn-run.mjs +6 -7
  248. package/src/session-runtime/workflow-agents-api/agent-route.mjs +15 -3
  249. package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
  250. package/src/session-runtime/workspace/admission-gate.mjs +197 -0
  251. package/src/session-runtime/workspace/project-branches.mjs +136 -0
  252. package/src/session-runtime/workspace/transition-steps.mjs +364 -0
  253. package/src/session-runtime/workspace/transitions.mjs +651 -0
  254. package/src/session-runtime/workspace/workspace-state.mjs +112 -0
  255. package/src/standalone/channel-binding.mjs +1 -2
  256. package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
  257. package/src/standalone/channel-transport/http-routes.mjs +17 -0
  258. package/src/standalone/channel-transport.mjs +13 -1
  259. package/src/standalone/daemon-channels-loader.mjs +2 -3
  260. package/src/standalone/daemon-desktop-runtime.mjs +1 -0
  261. package/src/standalone/daemon-owner.mjs +372 -0
  262. package/src/standalone/daemon-stored-session-views.mjs +4 -1
  263. package/src/standalone/daemon.mjs +139 -5
  264. package/src/standalone/desktop-service-registry.mjs +20 -0
  265. package/src/standalone/local-session-runtime.mjs +13 -78
  266. package/src/standalone/session-client/attachment-calls.mjs +3 -1
  267. package/src/standalone/session-client/daemon-unavailable.mjs +62 -0
  268. package/src/standalone/session-client/lease-heartbeat.mjs +47 -0
  269. package/src/standalone/session-client.mjs +104 -13
  270. package/src/standalone/session-protocol.mjs +17 -0
  271. package/src/standalone/session-proxy.mjs +3 -0
  272. package/src/standalone/session-service/agent-tree/agent-child.mjs +4 -0
  273. package/src/standalone/session-service/agent-tree/agent-rehydrate.mjs +1 -0
  274. package/src/standalone/session-service/entries/lifecycle.mjs +6 -0
  275. package/src/standalone/session-service/projection/revisions.mjs +2 -0
  276. package/src/standalone/session-service/projection/transcript-window.mjs +149 -19
  277. package/src/standalone/session-service/projection/workspace.mjs +14 -0
  278. package/src/standalone/session-service/session-calls/session-action.mjs +11 -3
  279. package/src/standalone/session-transport/call-route.mjs +13 -0
  280. package/src/standalone/session-transport/client-registry/client-grace.mjs +45 -3
  281. package/src/standalone/session-transport/client-registry/registration-replays.mjs +1 -0
  282. package/src/standalone/session-transport/client-registry.mjs +20 -3
  283. package/src/standalone/session-transport/routes.mjs +79 -2
  284. package/src/standalone/session-transport/stack.mjs +2 -0
  285. package/src/standalone/session-transport.mjs +24 -1
  286. package/src/tui/app/message-selector.mjs +2 -1
  287. package/src/tui/app/model-options.mjs +35 -5
  288. package/src/tui/app/model-picker/provider-models-list.mjs +2 -2
  289. package/src/tui/app/model-picker.mjs +4 -2
  290. package/src/tui/dist/index.mjs +332 -36
  291. package/src/tui/index.jsx +10 -2
  292. package/src/tui/session/compaction-view.mjs +147 -0
  293. package/src/tui/session/context-state.mjs +6 -0
  294. package/src/tui/session/initial-state.mjs +3 -0
  295. package/src/tui/session/quota-resume.mjs +2 -1
  296. package/src/tui/session/session-api/commands.mjs +10 -2
  297. package/src/tui/session/session-api/controls.mjs +4 -0
  298. package/src/tui/session/session-api/extensions.mjs +12 -8
  299. package/src/tui/session/session-api/intake/rewind.mjs +2 -1
  300. package/src/tui/session/session-api/integrations.mjs +18 -0
  301. package/src/tui/session/session-api/lifecycle/dispose.mjs +6 -1
  302. package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -0
  303. package/src/tui/session/session-api/lifecycle/resume.mjs +3 -0
  304. package/src/tui/session/session-api/routes.mjs +10 -1
  305. package/src/tui/session/session-api/settings.mjs +2 -0
  306. package/src/tui/session/session-api/shared.mjs +10 -6
  307. package/src/tui/session/session-api/worktree.mjs +60 -0
  308. package/src/tui/session/session-api.mjs +2 -0
  309. package/src/tui/session/session-flow/auto-clear.mjs +31 -12
  310. package/src/tui/session/session-flow/drain.mjs +15 -1
  311. package/src/tui/session/session-flow/session-reset.mjs +4 -3
  312. package/src/tui/session/session-flow.mjs +2 -0
  313. package/src/tui/session/transcript-intake.mjs +14 -5
  314. package/src/tui/session/turn-stream.mjs +32 -4
  315. package/src/tui/session/turn-tool-cards.mjs +10 -2
  316. package/src/tui/session/turn.mjs +39 -10
  317. package/src/runtime/channels/tool-defs.mjs +0 -17
@@ -9,7 +9,6 @@
9
9
  * - buildAgentRoleContent — BP3: agent role/system rules
10
10
  * - buildAgentRetrievalInjectionContent — BP3: narrow read-only retrieval role
11
11
  * - buildLeadMetaContent — BP2: Lead profile/settings context
12
- * - buildInjectionContent — legacy joined Lead session content
13
12
  *
14
13
  * 4-BP cache layout (composeSystemPrompt):
15
14
  * BP1 = shared tool policy
@@ -400,27 +399,6 @@ function buildLeadLanguageContent({ DATA_DIR }) {
400
399
  return buildLanguageSection(DATA_DIR);
401
400
  }
402
401
 
403
- /**
404
- * Build the Lead injection content.
405
- */
406
- function buildInjectionContent({ PLUGIN_ROOT, DATA_DIR }) {
407
- const parts = [];
408
-
409
- const tool = buildSharedToolContent({ PLUGIN_ROOT });
410
- if (tool) parts.push(tool);
411
-
412
- const meta = buildLeadMetaContent({ PLUGIN_ROOT, DATA_DIR });
413
- if (meta) parts.push(meta);
414
-
415
- const role = buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR });
416
- if (role) parts.push(role);
417
-
418
- const language = buildLeadLanguageContent({ DATA_DIR });
419
- if (language) parts.push(language);
420
-
421
- return parts.join('\n\n');
422
- }
423
-
424
402
  function buildAgentRoleContent({ PLUGIN_ROOT, profile = 'full' }) {
425
403
  const AGENT_DIR = path.join(PLUGIN_ROOT, 'rules', 'agent');
426
404
  if (String(profile || 'full') === 'retrieval') {
@@ -455,7 +433,6 @@ module.exports = {
455
433
  buildLeadMetaContent,
456
434
  buildLeadLanguageContent,
457
435
  buildAgentRoleContent,
458
- buildInjectionContent,
459
436
  buildAgentRetrievalInjectionContent,
460
437
  omitToolRoutes,
461
438
  };
@@ -10,8 +10,8 @@
10
10
  never per-call narration or plan repeats.
11
11
  - Mid-task: a replacement supersedes, an addition folds in, a status question
12
12
  gets a brief answer while work continues.
13
- - Context is managed automatically: never propose stopping or stop on your own
14
- judgment.
13
+ - Context is managed automatically: never stop or propose stopping because of
14
+ context size.
15
15
  - Be a grounded, candid, and attentive collaborator: natural and respectful,
16
16
  without flattery, forced humor, mimicry, or stereotyping.
17
17
  <!-- tools: agent -->
@@ -8,8 +8,8 @@
8
8
  `src/app.ts:42`); files outside the Project always by absolute path; never a
9
9
  bare name, a path relative to another folder, or a `file://` URI.
10
10
  - Deliver requested visual artifacts (SVG, diagrams, HTML pages) as saved files
11
- and give their path; never paste their source as the answer unless the user
12
- asks for the code.
11
+ and give their path, which opens them in the browser panel; never open them
12
+ in the system browser or paste their source unless the user asks.
13
13
 
14
14
  <!-- tools: git -->
15
15
  - A commit request includes selecting and staging its changes; a stage-only
@@ -35,6 +35,7 @@ function maintenanceRouteToPreset(routeOrName, agent) {
35
35
  const effort = String(routeOrName.effort || '').trim();
36
36
  if (effort) out.effort = effort;
37
37
  if (routeOrName.fast === true) out.fast = true;
38
+ if (routeOrName.autoEffort === false) out.autoEffort = false;
38
39
  if (routeOrName.modelParameters && typeof routeOrName.modelParameters === 'object') {
39
40
  out.modelParameters = { ...routeOrName.modelParameters };
40
41
  }
@@ -12,6 +12,7 @@ function normalizeMaintenanceCandidate(candidate) {
12
12
  model,
13
13
  effort: String(candidate.effort || '').trim() || undefined,
14
14
  fast: candidate.fast === true,
15
+ ...(candidate.autoEffort === false ? { autoEffort: false } : {}),
15
16
  };
16
17
  }
17
18
 
@@ -77,6 +77,7 @@ export function prepareAgentSession({
77
77
  sessionId,
78
78
  mcpScopeId,
79
79
  contextPercent,
80
+ contextTokens,
80
81
  selectedContextWindow,
81
82
  }) {
82
83
  const effectivePermission = resolveAgentSessionPermission(agent, permission);
@@ -115,6 +116,7 @@ export function prepareAgentSession({
115
116
  mcpScopeId: mcpScopeId || null,
116
117
  };
117
118
  if (contextPercent) sessionOpts.contextPercent = contextPercent;
119
+ if (contextTokens) sessionOpts.contextTokens = contextTokens;
118
120
  if (selectedContextWindow) sessionOpts.selectedContextWindow = selectedContextWindow;
119
121
  if (agentTag) sessionOpts.agentTag = agentTag;
120
122
  if (effectivePermission) sessionOpts.permission = effectivePermission;
@@ -141,6 +141,8 @@ export function normalizePreset(preset) {
141
141
  const out = { id, name, type: 'agent', provider, model };
142
142
  if (preset.effort) out.effort = String(preset.effort).trim();
143
143
  if (preset.fast === true && FAST_CAPABLE_PRESET_PROVIDERS.has(provider)) out.fast = true;
144
+ // Auto effort is on unless the preset opts out; only the off state is kept.
145
+ if (preset.autoEffort === false) out.autoEffort = false;
144
146
  out.tools = ['full', 'readonly', 'mcp'].includes(preset.tools) ? preset.tools : 'full';
145
147
  return out;
146
148
  }
@@ -6,9 +6,21 @@
6
6
  * is dropped.
7
7
  */
8
8
  import { AGENT_PROVIDER_ENV, getAgentApiKey } from '../../shared/config.mjs';
9
+ import { hasAgentApiKey } from '../../shared/provider-api-key.mjs';
9
10
  import { OPENAI_COMPAT_PRESETS } from './providers/openai-compat-presets.mjs';
10
11
 
11
- export function mergeStoredProviders({ raw, defaults, includeSecrets }) {
12
+ /** `presenceOnly`: a stored key only marks its provider enabled; its value is
13
+ * neither read nor attached. */
14
+ export function mergeStoredProviders({ raw, defaults, includeSecrets, presenceOnly = false }) {
15
+ const storedKey = (name) => {
16
+ if (!presenceOnly) return getAgentApiKey(name);
17
+ return hasAgentApiKey(name) ? true : null;
18
+ };
19
+ const withKey = (entry, name, key) => ({
20
+ ...(entry || {}),
21
+ ...(presenceOnly ? {} : { apiKey: key }),
22
+ enabled: raw.providers?.[name]?.enabled !== false,
23
+ });
12
24
  // Deep-merge provider subkeys: unknown per-provider values are
13
25
  // preserved through save/load so future fields round-trip
14
26
  // without schema updates here.
@@ -39,14 +51,8 @@ export function mergeStoredProviders({ raw, defaults, includeSecrets }) {
39
51
  // stored key still ships 'no-key' → 401.
40
52
  if (includeSecrets) {
41
53
  for (const name of new Set([...Object.keys(AGENT_PROVIDER_ENV), ...Object.keys(OPENAI_COMPAT_PRESETS)])) {
42
- const kc = getAgentApiKey(name);
43
- if (kc) {
44
- mergedProviders[name] = {
45
- ...(mergedProviders[name] || {}),
46
- apiKey: kc,
47
- enabled: raw.providers?.[name]?.enabled !== false,
48
- };
49
- }
54
+ const kc = storedKey(name);
55
+ if (kc) mergedProviders[name] = withKey(mergedProviders[name], name, kc);
50
56
  }
51
57
  }
52
58
  // Custom providers: the keychain account is the generated custom-<uuid> id.
@@ -61,8 +67,8 @@ export function mergeStoredProviders({ raw, defaults, includeSecrets }) {
61
67
  if (includeSecrets) {
62
68
  for (const [name, val] of Object.entries(mergedProviders)) {
63
69
  if (val?.type !== 'custom' || !name.startsWith('custom-') || val.auth === 'none') continue;
64
- const kc = getAgentApiKey(name);
65
- if (kc) mergedProviders[name] = { ...val, apiKey: kc, enabled: raw.providers?.[name]?.enabled !== false };
70
+ const kc = storedKey(name);
71
+ if (kc) mergedProviders[name] = withKey(val, name, kc);
66
72
  }
67
73
  }
68
74
  // Cursor account access is OAuth-only. The dashboard's "API"
@@ -103,6 +103,7 @@ export function normalizeMaintenanceRoutes(rawMaint) {
103
103
  const effort = String(value.effort || '').trim();
104
104
  if (effort) route.effort = effort;
105
105
  if (value.fast === true) route.fast = true;
106
+ if (value.autoEffort === false) route.autoEffort = false;
106
107
  out[slot] = route;
107
108
  }
108
109
  return out;
@@ -44,6 +44,7 @@ import {
44
44
  } from './config-storage.mjs';
45
45
  import { oauthCredentialProbeState, isOAuthProviderAvailable } from './providers/oauth-credential-probes.mjs';
46
46
  import { normalizeDeveloperConfig } from '../../shared/developer-options.mjs';
47
+ import { normalizeWorktreeSettings } from '../../shared/worktrees/settings.mjs';
47
48
 
48
49
  export const {
49
50
  PROFILE_LANGUAGES,
@@ -204,6 +205,10 @@ function storedInstructionFiles(agent) {
204
205
 
205
206
  export function loadConfig(options = {}) {
206
207
  const includeSecrets = options.secrets !== false;
208
+ // `secrets: 'presence'`: credentials are detected (env, OAuth files, which
209
+ // keychain entries exist) but no keychain value is read or attached — the
210
+ // availability view the model proxy answers a server with.
211
+ const presenceOnly = options.secrets === 'presence';
207
212
  try {
208
213
  ensureDefaultsSeparated({ stripRouteDefaults: separateStoredAgent });
209
214
  } catch (err) {
@@ -219,7 +224,7 @@ export function loadConfig(options = {}) {
219
224
  const storageNeedsMigration = agentConfigStorageNeedsMigration(raw);
220
225
  raw = canonicalizeAgentStorage(raw);
221
226
  const defaults = buildDefaultConfig({ detectCredentials: includeSecrets });
222
- const mergedProviders = mergeStoredProviders({ raw, defaults, includeSecrets });
227
+ const mergedProviders = mergeStoredProviders({ raw, defaults, includeSecrets, presenceOnly });
223
228
  // Drop unknown maintenance keys (e.g. truly legacy slot names from
224
229
  // pre-removal installs). Every valid fallback slot lives in
225
230
  // DEFAULT_MAINTENANCE, so the allow-list below is the single
@@ -274,6 +279,7 @@ export function loadConfig(options = {}) {
274
279
  shell: raw.shell && typeof raw.shell === 'object' ? raw.shell : {},
275
280
  update: raw.update && typeof raw.update === 'object' ? { ...raw.update } : {},
276
281
  recap: recapConfig,
282
+ worktree: normalizeWorktreeSettings(raw.worktree),
277
283
  developer: normalizeDeveloperConfig(raw.developer),
278
284
  modules: canonicalizeModulesStorage(raw.modules) || {},
279
285
  ...(raw.builtins && typeof raw.builtins === 'object'
@@ -316,6 +322,7 @@ export function loadConfig(options = {}) {
316
322
  shell: {},
317
323
  update: {},
318
324
  recap: { enabled: true },
325
+ worktree: normalizeWorktreeSettings(null),
319
326
  developer: {},
320
327
  modules: {},
321
328
  // No stored section at all is a brand-new profile: its built-ins start
@@ -421,6 +428,7 @@ function buildAgentSaveBuilder(config) {
421
428
  shell,
422
429
  update: config.update || {},
423
430
  recap: config.recap || {},
431
+ ...(config.worktree ? { worktree: config.worktree } : {}),
424
432
  modules,
425
433
  builtins,
426
434
  };
@@ -15,8 +15,16 @@ export const AUTO_DETECT_PORTS = {
15
15
  'mixdog-memory': { discovery: 'memory', endpoint: '/mcp' },
16
16
  };
17
17
  const DEFAULT_MCP_CALL_TIMEOUT_MS = 120000;
18
- // Per-server STARTUP handshake budget (connect + listTools): 10s.
19
- const DEFAULT_MCP_STARTUP_TIMEOUT_MS = 10000;
18
+ // Per-server STARTUP handshake budget (connect + paginated listTools): 30s.
19
+ // Cold npx/uvx wrappers routinely need well over 10s to install and boot.
20
+ const DEFAULT_MCP_STARTUP_TIMEOUT_MS = 30000;
21
+ // Hard ceiling for a request that keeps reporting progress (each progress
22
+ // notification resets the per-call timeout, this caps the total): 30min.
23
+ const DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS = 1_800_000;
24
+ // Largest delay setTimeout honours; anything larger fires after 1ms. Used as
25
+ // the SDK request timeout when our own call timeout is disabled, so the SDK's
26
+ // built-in 60s default never applies.
27
+ export const MCP_MAX_TIMER_MS = 2_147_483_647;
20
28
 
21
29
  function isLoopbackMcpHost(hostname) {
22
30
  const host = String(hostname || '')
@@ -204,8 +212,9 @@ export function resolveMcpTransportKind(cfg) {
204
212
  // (e.g. a busy editor) must not stall a tool call indefinitely. Genuinely
205
213
  // long-running tools can raise/disable it via MIXDOG_MCP_CALL_TIMEOUT_MS or a
206
214
  // per-server timeoutMs/callTimeoutMs config value (0/off/none/false disables).
207
- // On expiry we close the transport so the next dispatch reconnects fresh, but
208
- // we do not retry the timed-out call automatically (avoids side-effect dupes).
215
+ // On expiry only the timed-out request is cancelled (notifications/cancelled);
216
+ // the server and its other in-flight calls stay up, and the timed-out call is
217
+ // never retried automatically (avoids side-effect dupes).
209
218
  export function resolveMcpCallTimeoutMs(cfg = {}, env = process.env) {
210
219
  const raw =
211
220
  cfg?.timeoutMs ?? cfg?.timeout_ms ?? cfg?.callTimeoutMs ?? cfg?.call_timeout_ms ?? env?.MIXDOG_MCP_CALL_TIMEOUT_MS;
@@ -213,11 +222,42 @@ export function resolveMcpCallTimeoutMs(cfg = {}, env = process.env) {
213
222
  if (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim())) return 0;
214
223
  const parsed = Number(raw);
215
224
  if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_CALL_TIMEOUT_MS;
216
- return Math.round(parsed);
225
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
226
+ }
227
+
228
+ // Total cap for a call whose server keeps reporting progress. Default 30min.
229
+ // Per-server override: maxTotalTimeoutMs / max_total_timeout_ms. Global env:
230
+ // MIXDOG_MCP_MAX_TOTAL_TIMEOUT_MS. 0/off/none/false removes the cap.
231
+ export function resolveMcpMaxTotalTimeoutMs(cfg = {}, env = process.env) {
232
+ const raw = cfg?.maxTotalTimeoutMs ?? cfg?.max_total_timeout_ms ?? env?.MIXDOG_MCP_MAX_TOTAL_TIMEOUT_MS;
233
+ if (raw == null || raw === '' || raw === false) return DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS;
234
+ if (raw === 0 || (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim()))) return 0;
235
+ const parsed = Number(raw);
236
+ if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_MAX_TOTAL_TIMEOUT_MS;
237
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
238
+ }
239
+
240
+ /**
241
+ * The effective limits of one MCP request:
242
+ * - callTimeoutMs: our per-call timeout (0 = disabled), reset on progress;
243
+ * - sdkTimeoutMs: what the SDK gets as `timeout` — the call timeout, or the
244
+ * largest timer when disabled so the SDK's 60s default never applies;
245
+ * - maxTotalTimeoutMs: the progress-reset ceiling (never below the call
246
+ * timeout), or undefined when the call is unbounded or the cap is off.
247
+ */
248
+ export function resolveMcpRequestTimeouts(cfg = {}, env = process.env) {
249
+ const callTimeoutMs = resolveMcpCallTimeoutMs(cfg, env);
250
+ if (!(callTimeoutMs > 0)) return { callTimeoutMs: 0, sdkTimeoutMs: MCP_MAX_TIMER_MS, maxTotalTimeoutMs: undefined };
251
+ const cap = resolveMcpMaxTotalTimeoutMs(cfg, env);
252
+ return {
253
+ callTimeoutMs,
254
+ sdkTimeoutMs: callTimeoutMs,
255
+ maxTotalTimeoutMs: cap > 0 ? Math.max(cap, callTimeoutMs) : undefined,
256
+ };
217
257
  }
218
258
 
219
259
  // MCP per-server STARTUP timeout: bounds the connect + listTools handshake so a
220
- // slow or hung server can't stall boot or the first turn. Default 10s.
260
+ // slow or hung server can't stall boot or the first turn. Default 30s.
221
261
  // Per-server override: startupTimeoutMs / startupTimeoutSec. Global
222
262
  // env: MIXDOG_MCP_STARTUP_TIMEOUT_MS. A value of 0/off/none/false disables it.
223
263
  export function resolveMcpStartupTimeoutMs(cfg = {}, env = process.env) {
@@ -235,5 +275,5 @@ export function resolveMcpStartupTimeoutMs(cfg = {}, env = process.env) {
235
275
  if (raw === 0 || (typeof raw === 'string' && /^(0|off|none|false)$/i.test(raw.trim()))) return 0;
236
276
  const parsed = Number(raw) * scale;
237
277
  if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_MCP_STARTUP_TIMEOUT_MS;
238
- return Math.round(parsed);
278
+ return Math.min(Math.round(parsed), MCP_MAX_TIMER_MS);
239
279
  }
@@ -4,14 +4,40 @@
4
4
  * and a discovery advert that refused the connection is distrusted.
5
5
  */
6
6
  import { markServiceUnreachable, isConnRefuseError } from '../../../shared/service-discovery.mjs';
7
- import { resolveMcpStartupTimeoutMs } from './client-config.mjs';
7
+ import { MCP_MAX_TIMER_MS, resolveMcpStartupTimeoutMs } from './client-config.mjs';
8
+ import { appendMcpStderrTail } from './client-transport.mjs';
8
9
 
9
- async function performHandshake(client, transport) {
10
- await client.connect(transport);
10
+ // tools/list pagination bounds: a server that never stops handing out cursors
11
+ // (or repeats one) cannot loop the client forever.
12
+ const MAX_MCP_TOOL_PAGES = 100;
13
+
14
+ /**
15
+ * Every tool of a server: follows `nextCursor` until it is exhausted, repeats,
16
+ * or MAX_MCP_TOOL_PAGES is reached. A page with an invalid shape is returned
17
+ * as-is so the caller's shape validation reports it.
18
+ */
19
+ export async function listAllMcpTools(client, requestOptions = undefined) {
20
+ const tools = [];
21
+ const seen = new Set();
22
+ let cursor = null;
23
+ for (let page = 0; page < MAX_MCP_TOOL_PAGES; page += 1) {
24
+ const result = await client.listTools(cursor ? { cursor } : undefined, requestOptions);
25
+ if (!result || !Array.isArray(result.tools)) return result;
26
+ tools.push(...result.tools);
27
+ const next = typeof result.nextCursor === 'string' && result.nextCursor ? result.nextCursor : null;
28
+ if (!next || seen.has(next)) break;
29
+ seen.add(next);
30
+ cursor = next;
31
+ }
32
+ return { tools };
33
+ }
34
+
35
+ async function performHandshake(client, transport, requestOptions) {
36
+ await client.connect(transport, requestOptions);
11
37
  const instructionsRaw = typeof client.getInstructions === 'function' ? client.getInstructions() : undefined;
12
38
  const instructions = typeof instructionsRaw === 'string' ? instructionsRaw.trim() : '';
13
39
  const capabilities = client.getServerCapabilities?.() || {};
14
- const toolsResult = capabilities.tools ? await client.listTools() : { tools: [] };
40
+ const toolsResult = capabilities.tools ? await listAllMcpTools(client, requestOptions) : { tools: [] };
15
41
  return { instructions, toolsResult, capabilities };
16
42
  }
17
43
 
@@ -28,12 +54,24 @@ function startupTimeoutError(name, startupTimeoutMs) {
28
54
  * boot or the first turn. On expiry the pending transport/child is torn down
29
55
  * (nothing leaks) and this server fails like any other connect failure — the
30
56
  * parallel Promise.allSettled means other servers are unaffected.
31
- * Returns { instructions, toolsResult, capabilities }.
57
+ * The SDK's per-request timeout follows the same budget (its own 60s default
58
+ * would otherwise cut a longer or disabled budget short). Failures carry the
59
+ * server's stderr tail. Returns { instructions, toolsResult, capabilities }.
32
60
  */
33
- export async function runBoundedHandshake({ name, cfg, client, transport, autoDetectAdvert, closeServer }) {
61
+ export async function runBoundedHandshake({
62
+ name,
63
+ cfg,
64
+ client,
65
+ transport,
66
+ autoDetectAdvert,
67
+ closeServer,
68
+ stderrTail = () => '',
69
+ }) {
34
70
  const startupTimeoutMs = resolveMcpStartupTimeoutMs(cfg);
35
71
  const guard = { timer: null, timedOut: false };
36
- const handshake = performHandshake(client, transport);
72
+ const handshake = performHandshake(client, transport, {
73
+ timeout: startupTimeoutMs > 0 ? startupTimeoutMs : MCP_MAX_TIMER_MS,
74
+ });
37
75
  try {
38
76
  if (startupTimeoutMs <= 0) return await handshake;
39
77
  const deadline = new Promise((_, rej) => {
@@ -63,7 +101,12 @@ export async function runBoundedHandshake({ name, cfg, client, transport, autoDe
63
101
  // Never let the late handshake settle into an unhandled rejection.
64
102
  handshake.catch(() => {});
65
103
  }
66
- throw err;
104
+ const tail = stderrTail();
105
+ if (tail && err instanceof Error) {
106
+ err.message = appendMcpStderrTail(err.message, tail);
107
+ throw err;
108
+ }
109
+ throw tail ? new Error(appendMcpStderrTail(String(err), tail)) : err;
67
110
  } finally {
68
111
  if (guard.timer) clearTimeout(guard.timer);
69
112
  }
@@ -32,19 +32,35 @@ function resolveAutoDetectUrl(name, cfg) {
32
32
  return { url: `http://127.0.0.1:${port}${spec.endpoint}`, advert: { service: spec.discovery, port } };
33
33
  }
34
34
 
35
+ // Bounded tail of a stdio server's stderr, kept for failure messages (a server
36
+ // that dies at startup or mid-session usually says why on stderr).
37
+ const MCP_STDERR_TAIL_CHARS = 4096;
38
+ const noStderrTail = () => '';
39
+
40
+ /** Append a server's stderr tail to a failure message (unchanged when empty). */
41
+ export function appendMcpStderrTail(message, tail) {
42
+ const text = String(tail || '').trim();
43
+ return text ? `${message}\n--- server stderr (tail) ---\n${text}` : String(message);
44
+ }
45
+
35
46
  function requestInitFor(cfg) {
36
47
  const headers = resolveMcpHttpHeaders(cfg);
37
48
  return headers && Object.keys(headers).length > 0 ? { requestInit: { headers } } : undefined;
38
49
  }
39
50
 
40
- /** Returns { transport, autoDetectAdvert }. */
51
+ /** Returns { transport, autoDetectAdvert, stderrTail } — stderrTail() is the
52
+ * last ~4 KB of a stdio server's (scrubbed) stderr, '' for remote transports. */
41
53
  export function createMcpTransport({ name, cfg, sdk, log }) {
42
54
  const { StdioClientTransport, StreamableHTTPClientTransport, SSEClientTransport, WebSocketClientTransport } = sdk;
43
55
  const kind = resolveMcpTransportKind(cfg);
44
56
  if (kind === 'autoDetect') {
45
57
  const { url, advert } = resolveAutoDetectUrl(name, cfg);
46
58
  log(`[mcp-client] Connecting "${name}" via autoDetect HTTP: ${url}\n`);
47
- return { transport: new StreamableHTTPClientTransport(new URL(url)), autoDetectAdvert: advert };
59
+ return {
60
+ transport: new StreamableHTTPClientTransport(new URL(url)),
61
+ autoDetectAdvert: advert,
62
+ stderrTail: noStderrTail,
63
+ };
48
64
  }
49
65
  if (kind === 'http' || kind === 'sse') {
50
66
  const Transport = kind === 'http' ? StreamableHTTPClientTransport : SSEClientTransport;
@@ -54,13 +70,14 @@ export function createMcpTransport({ name, cfg, sdk, log }) {
54
70
  return {
55
71
  transport: opts ? new Transport(new URL(url), opts) : new Transport(new URL(url)),
56
72
  autoDetectAdvert: null,
73
+ stderrTail: noStderrTail,
57
74
  };
58
75
  }
59
76
  if (kind === 'ws') {
60
77
  // WebSocketClientTransport ctor takes only a URL; headers are ignored.
61
78
  const url = normalizeMcpTransportUrl(expandEnvVars(String(cfg.url ?? '')), 'ws');
62
79
  log(`[mcp-client] Connecting "${name}" via WebSocket: ${mcpUrlForLog(url)}\n`);
63
- return { transport: new WebSocketClientTransport(new URL(url)), autoDetectAdvert: null };
80
+ return { transport: new WebSocketClientTransport(new URL(url)), autoDetectAdvert: null, stderrTail: noStderrTail };
64
81
  }
65
82
  if (kind === 'stdio') {
66
83
  const transport = new StdioClientTransport({
@@ -70,10 +87,13 @@ export function createMcpTransport({ name, cfg, sdk, log }) {
70
87
  env: resolveMcpStdioEnvironment(cfg),
71
88
  stderr: cfg.stderr ?? 'pipe',
72
89
  });
90
+ let tail = '';
73
91
  transport.stderr?.on?.('data', (chunk) => {
74
- log(`[mcp:${name}:stderr] ${scrubMcpConnectionMessage(chunk, cfg)}`);
92
+ const text = scrubMcpConnectionMessage(chunk, cfg);
93
+ tail = (tail + text).slice(-MCP_STDERR_TAIL_CHARS);
94
+ log(`[mcp:${name}:stderr] ${text}`);
75
95
  });
76
- return { transport, autoDetectAdvert: null };
96
+ return { transport, autoDetectAdvert: null, stderrTail: () => tail.trim() };
77
97
  }
78
98
  throw new Error(
79
99
  `Invalid config for "${name}": need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`