mixdog 1.2.0 → 1.2.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 (386) hide show
  1. package/README.md +7 -5
  2. package/package.json +1 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/src/agents/reviewer/AGENT.md +3 -3
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +5 -0
  8. package/src/defaults/skills/pptx/SKILL.md +6 -6
  9. package/src/defaults/skills/pptx/references/review.md +6 -4
  10. package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
  11. package/src/defaults/skills/setup/references/actions.md +13 -3
  12. package/src/defaults/skills/visualize/SKILL.md +12 -9
  13. package/src/repl/slash-commands.mjs +16 -1
  14. package/src/rules/shared/60-verification.md +3 -1
  15. package/src/rules/shared/70-delivery.md +4 -2
  16. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  17. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  18. package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
  19. package/src/runtime/agent/orchestrator/config.mjs +10 -4
  20. package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
  21. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
  22. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
  23. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +122 -15
  24. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +54 -11
  25. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +104 -9
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +358 -140
  27. package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
  28. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  29. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  30. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  36. package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
  37. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +294 -44
  38. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  40. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
  41. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  42. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
  45. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  46. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  48. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  49. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  50. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  51. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
  53. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  54. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  55. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +53 -18
  57. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  58. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  59. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
  60. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
  61. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
  62. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
  63. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
  64. package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
  65. package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
  66. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +447 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
  68. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  69. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  70. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
  71. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  72. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
  73. package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
  74. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
  75. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +86 -19
  76. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
  78. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
  79. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  80. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  81. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  82. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  83. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
  84. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
  85. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
  86. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
  87. package/src/runtime/agent/orchestrator/providers/gemini.mjs +97 -51
  88. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  89. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
  90. package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
  91. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  92. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
  94. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
  95. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
  96. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  97. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
  99. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +5 -3
  100. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
  101. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  102. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  103. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
  106. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
  107. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  108. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
  109. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  110. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  111. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
  112. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
  113. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +6 -3
  114. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
  115. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +26 -6
  116. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
  117. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  118. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  119. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
  120. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
  121. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
  123. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  124. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  125. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
  126. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
  127. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  128. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
  129. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
  130. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  132. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  133. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  134. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  135. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +35 -24
  136. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
  137. package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
  138. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  139. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
  140. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  141. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  142. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  143. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  144. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
  145. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
  146. package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
  147. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  148. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  149. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  150. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +268 -181
  151. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
  152. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
  153. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
  154. package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
  155. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +270 -0
  156. package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
  157. package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
  158. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
  159. package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
  160. package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
  161. package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
  162. package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
  163. package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
  164. package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
  165. package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
  166. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
  167. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
  168. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
  169. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -4
  170. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
  171. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
  172. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
  173. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
  174. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +42 -5
  175. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +14 -3
  176. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
  177. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +50 -23
  178. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
  179. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +22 -14
  180. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
  181. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  182. package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
  183. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
  184. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
  185. package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
  186. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  187. package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
  188. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  189. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  190. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +819 -0
  191. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +12 -0
  192. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +22 -12
  193. package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
  194. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
  195. package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
  196. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
  197. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +41 -3
  198. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
  199. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  200. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  201. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
  202. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
  203. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
  205. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
  206. package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
  207. package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
  208. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
  209. package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
  210. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
  211. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
  212. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
  213. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
  214. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
  215. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
  216. package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
  217. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
  218. package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
  219. package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
  220. package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
  221. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
  222. package/src/runtime/attachments/store.d.mts +3 -0
  223. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
  224. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
  225. package/src/runtime/channels/lib/worker-main.mjs +17 -14
  226. package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
  227. package/src/runtime/effort-judge/judge-client.mjs +56 -18
  228. package/src/runtime/memory/lib/memory.mjs +1 -1
  229. package/src/runtime/memory/lib/pg/process.mjs +34 -24
  230. package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
  231. package/src/runtime/memory/lib/session-ingest.mjs +2 -2
  232. package/src/runtime/office/README.md +1 -1
  233. package/src/runtime/office/shared/values.mjs +0 -5
  234. package/src/runtime/shared/atomic-file.mjs +25 -4
  235. package/src/runtime/shared/background-tasks.mjs +92 -14
  236. package/src/runtime/shared/err-text.mjs +25 -0
  237. package/src/runtime/shared/error-presentation.mjs +159 -19
  238. package/src/runtime/shared/extension-scopes.mjs +5 -3
  239. package/src/runtime/shared/file-lock.mjs +37 -11
  240. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  241. package/src/runtime/shared/llm/anthropic-effort.mjs +134 -131
  242. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  243. package/src/runtime/shared/llm/model-catalog.mjs +53 -52
  244. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  245. package/src/runtime/shared/llm/provider-catalog-registry.mjs +17 -0
  246. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  247. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  248. package/src/runtime/shared/model-display.mjs +13 -0
  249. package/src/runtime/shared/plugin-manifest.mjs +18 -9
  250. package/src/runtime/shared/pristine-execution-contract.json +9 -2
  251. package/src/runtime/shared/pristine-execution.mjs +28 -4
  252. package/src/runtime/shared/provider-accounts.mjs +124 -20
  253. package/src/runtime/shared/provider-api-key.mjs +12 -0
  254. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  255. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  256. package/src/runtime/shared/shell-display.mjs +9 -3
  257. package/src/runtime/shared/skill-mentions.d.mts +10 -0
  258. package/src/runtime/shared/skill-mentions.mjs +133 -0
  259. package/src/runtime/shared/skill-script-runtime.mjs +264 -0
  260. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  261. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  262. package/src/runtime/shared/tool-primitives.mjs +35 -2
  263. package/src/runtime/shared/turn-changes.mjs +447 -219
  264. package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
  265. package/src/runtime/shared/turn-snapshot.mjs +170 -48
  266. package/src/runtime/shared/turn-worktree-snapshot.mjs +160 -51
  267. package/src/runtime/shared/worker-requests.mjs +15 -2
  268. package/src/runtime/shared/worktrees/contract.mjs +0 -24
  269. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  270. package/src/session-runtime/automation-agents.mjs +8 -2
  271. package/src/session-runtime/boot/apis.mjs +61 -0
  272. package/src/session-runtime/boot/config.mjs +5 -2
  273. package/src/session-runtime/boot/facade.mjs +1 -0
  274. package/src/session-runtime/boot/infrastructure.mjs +35 -18
  275. package/src/session-runtime/conversation-import/discovery.mjs +698 -0
  276. package/src/session-runtime/conversation-import/service.mjs +369 -0
  277. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
  278. package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
  279. package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
  280. package/src/session-runtime/lifecycle/teardown.mjs +1 -1
  281. package/src/session-runtime/mcp-glue/connect.mjs +83 -16
  282. package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
  283. package/src/session-runtime/mcp-glue/server-catalog.mjs +135 -33
  284. package/src/session-runtime/mcp-glue.mjs +25 -2
  285. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  286. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  287. package/src/session-runtime/model-route/set-route.mjs +83 -15
  288. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  289. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  290. package/src/session-runtime/new-session-config.mjs +3 -2
  291. package/src/session-runtime/notification-bus.mjs +9 -2
  292. package/src/session-runtime/provider-auth/accounts.mjs +33 -15
  293. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  294. package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
  295. package/src/session-runtime/provider-readiness.mjs +16 -10
  296. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  297. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  298. package/src/session-runtime/resource-extension-sync.mjs +24 -5
  299. package/src/session-runtime/resource-mcp-api.mjs +198 -78
  300. package/src/session-runtime/resource-plugins-api.mjs +29 -41
  301. package/src/session-runtime/resource-scope-decorate.mjs +6 -8
  302. package/src/session-runtime/resource-skills-api.mjs +23 -2
  303. package/src/session-runtime/runtime-review-api.mjs +13 -0
  304. package/src/session-runtime/schedule-session-run.mjs +1 -1
  305. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  306. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  307. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  308. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  309. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  310. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  311. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  312. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  313. package/src/session-runtime/services/channel-session-router.mjs +4 -1
  314. package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
  315. package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
  316. package/src/session-runtime/services/provider-admin.mjs +64 -18
  317. package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
  318. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  319. package/src/session-runtime/setup-tool/executor.mjs +12 -1
  320. package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
  321. package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
  322. package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
  323. package/src/session-runtime/skills-api.mjs +7 -3
  324. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  325. package/src/session-runtime/webhook-session-run.mjs +16 -4
  326. package/src/skill-script-entry.mjs +36 -0
  327. package/src/standalone/daemon-drain-policy.mjs +8 -0
  328. package/src/standalone/daemon-stored-session-views.mjs +11 -3
  329. package/src/standalone/daemon-telemetry.mjs +55 -0
  330. package/src/standalone/daemon.mjs +44 -11
  331. package/src/standalone/session-protocol.mjs +12 -0
  332. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  333. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  334. package/src/standalone/session-service/agent-tree.mjs +12 -0
  335. package/src/standalone/session-service/quota-resume.mjs +8 -0
  336. package/src/standalone/session-service/retention/busy.mjs +15 -1
  337. package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
  338. package/src/standalone/session-service-api.mjs +5 -0
  339. package/src/standalone/session-service.mjs +1 -0
  340. package/src/tui/App.jsx +9 -1
  341. package/src/tui/app/app-view/floating-panel.jsx +9 -1
  342. package/src/tui/app/app-view.jsx +7 -0
  343. package/src/tui/app/create-app-pickers.mjs +4 -0
  344. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  345. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  346. package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
  347. package/src/tui/app/extension-pickers.mjs +2 -1
  348. package/src/tui/app/model-options.mjs +13 -16
  349. package/src/tui/app/skill-mention-palette.mjs +88 -0
  350. package/src/tui/app/slash-commands.mjs +7 -2
  351. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  352. package/src/tui/app/slash-palette.mjs +23 -7
  353. package/src/tui/app/transcript-row-estimate.mjs +11 -3
  354. package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
  355. package/src/tui/app/use-prompt-state.mjs +5 -0
  356. package/src/tui/components/Message.jsx +22 -1
  357. package/src/tui/components/PromptInput.jsx +21 -3
  358. package/src/tui/components/SlashCommandPalette.jsx +12 -3
  359. package/src/tui/components/StatusLine.jsx +2 -0
  360. package/src/tui/components/TranscriptItem.jsx +9 -1
  361. package/src/tui/components/prompt-input/key-handler.mjs +21 -8
  362. package/src/tui/components/status-line/local-line.mjs +5 -2
  363. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  364. package/src/tui/components/user-message-skills.mjs +38 -0
  365. package/src/tui/dist/index.mjs +1323 -214
  366. package/src/tui/session/queue-helpers.mjs +7 -2
  367. package/src/tui/session/quota-resume.mjs +46 -14
  368. package/src/tui/session/session-api/commands.mjs +33 -24
  369. package/src/tui/session/session-api/extensions.mjs +26 -2
  370. package/src/tui/session/session-api/integrations.mjs +7 -0
  371. package/src/tui/session/session-api/lifecycle.mjs +5 -1
  372. package/src/tui/session/session-api/routes.mjs +71 -7
  373. package/src/tui/session/session-api/settings.mjs +12 -14
  374. package/src/tui/session/session-flow/drain.mjs +21 -5
  375. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  376. package/src/tui/session/tui-steering-persist.mjs +25 -9
  377. package/src/tui/session/turn.mjs +55 -15
  378. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  379. package/src/ui/statusline-agents.mjs +15 -4
  380. package/src/ui/statusline.mjs +7 -3
  381. package/src/ui/streaming-markdown-heal.mjs +34 -3
  382. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  383. package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
  384. package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
  385. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  386. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -1,6 +1,10 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { selectedSkillName } from '../../../shared/skill-selection.mjs';
3
+ import { mentionedSkillNames, skillMentionCandidates } from '../../../shared/skill-mentions.mjs';
4
+ import { isAttachmentReference } from '../../../attachments/store.mjs';
5
+ import { collectPromptSkillsCached } from '../context/collect.mjs';
3
6
  import { executeTool } from './loop/tool-exec.mjs';
7
+ import { preDispatchDenyForSession } from './loop/pre-dispatch-deny.mjs';
4
8
  import { parseNativeToolSearchPayload } from './loop/tool-helpers.mjs';
5
9
  import { normalizeToolEnvelope } from './tool-envelope.mjs';
6
10
  import { throwIfAborted } from '../../../shared/abort-race.mjs';
@@ -20,19 +24,91 @@ function nativeDefinitionsPresent(native, messages) {
20
24
  );
21
25
  }
22
26
 
27
+ // At most this many skills are prepared for one user turn; further selections
28
+ // stay prose.
29
+ const MAX_EXPLICIT_SKILLS = 5;
30
+ const LOADED_SKILL_RE = /^Loaded (?:built-in )?skill: /;
31
+
32
+ // The user's own typed text: plain strings and text parts, never attachment
33
+ // references (file contents the user attached, not words they typed).
34
+ function userPromptText(prompt) {
35
+ if (typeof prompt === 'string') return prompt;
36
+ if (!Array.isArray(prompt)) return '';
37
+ return prompt
38
+ .map((part) => {
39
+ if (typeof part === 'string') return part;
40
+ if (part?.type === 'text' && typeof part.text === 'string' && !isAttachmentReference(part)) return part.text;
41
+ return '';
42
+ })
43
+ .join('\n');
44
+ }
45
+
46
+ /** The skills a prompt explicitly selects: the leading `Skill: "name"` header,
47
+ * then every inline `$name`, each only when it names a skill enabled and in
48
+ * scope for `cwd`, in first-mention order, deduplicated and capped. */
49
+ export function explicitSkillNames(prompt, cwd) {
50
+ const header = selectedSkillName(prompt);
51
+ const text = userPromptText(prompt);
52
+ if (!header && !skillMentionCandidates(text).length) return [];
53
+ const available = new Set(collectPromptSkillsCached(cwd).map((skill) => skill.name));
54
+ const isSkill = (name) => available.has(name);
55
+ const names = header && isSkill(header) ? [header] : [];
56
+ for (const name of mentionedSkillNames(text, isSkill)) {
57
+ if (!names.includes(name)) names.push(name);
58
+ }
59
+ return names.slice(0, MAX_EXPLICIT_SKILLS);
60
+ }
61
+
62
+ /** Stamps the skills a user turn actually loaded on its transcript metadata,
63
+ * where the live row, the persisted transcript and restore all read it.
64
+ * The stamped message REPLACES `message` in `messages` (returned): the
65
+ * session store ships reference-identical messages as an append-only delta,
66
+ * so an in-place edit of a user message an earlier save already sent would
67
+ * never reach disk. */
68
+ export function recordAppliedSkills(messages, message, names) {
69
+ if (!message || !Array.isArray(names) || !names.length) return message;
70
+ const meta = message.meta && typeof message.meta === 'object' ? message.meta : {};
71
+ const transcript = meta.transcript && typeof meta.transcript === 'object' ? meta.transcript : {};
72
+ const stamped = { ...message, meta: { ...meta, transcript: { ...transcript, appliedSkills: [...names] } } };
73
+ const index = Array.isArray(messages) ? messages.indexOf(message) : -1;
74
+ if (index >= 0) messages[index] = stamped;
75
+ return stamped;
76
+ }
77
+
78
+ /** Tells the host which skills a user turn loaded; a host callback failure
79
+ * never fails the turn. */
80
+ export function notifySkillsApplied(onSkillsApplied, applied, message) {
81
+ if (!applied.length) return;
82
+ try {
83
+ onSkillsApplied?.(applied, { messageId: message?.meta?.transcript?.messageId || null });
84
+ } catch {
85
+ /* best-effort host callback */
86
+ }
87
+ }
88
+
23
89
  // Called before the first model request for a real user turn. This performs the
24
90
  // same policy-checked Skill operation, without asking the model to request it.
25
91
  // Native schemas still travel as a properly paired loader execution, never as
26
- // new eager definitions or an orphan tool_search_output.
92
+ // new eager definitions or an orphan tool_search_output. Returns the names of
93
+ // the skills that loaded successfully, in selection order.
94
+ // `selection`, when given (even empty), is the turn's TRUSTED selection input
95
+ // and the only text skills are selected from: a host whose model-visible
96
+ // prompt carries external data (webhook payloads) passes just the
97
+ // operator-authored part, so nothing inside the data can load a skill.
27
98
  export async function prepareExplicitSkills(
28
99
  prompt,
29
100
  messages,
30
101
  session,
31
- { cwd = session?.cwd, signal, execute = executeTool } = {}
102
+ { cwd = session?.cwd, signal, execute = executeTool, selection } = {}
32
103
  ) {
33
104
  throwIfAborted(signal);
34
- const name = selectedSkillName(prompt);
35
- if (name) {
105
+ const applied = [];
106
+ // A session that cannot call Skill (schema allowlist, denied tools, read-only
107
+ // surface — the dispatcher's own gate) has nothing to select: preparing would
108
+ // only push a refused-tool reminder into history on every matching turn.
109
+ if (preDispatchDenyForSession(session, { name: 'Skill' })) return applied;
110
+ const source = selection === undefined || selection === null ? prompt : selection;
111
+ for (const name of explicitSkillNames(source, cwd)) {
36
112
  const id = `skill-selection-${randomUUID()}`;
37
113
  const args = { name };
38
114
  const raw = await execute('Skill', args, cwd, session?.id, session, { signal, toolCallId: id });
@@ -55,6 +131,8 @@ export async function prepareExplicitSkills(
55
131
  });
56
132
  }
57
133
  messages.push(...envelope.newMessages);
134
+ if (LOADED_SKILL_RE.test(String(summary))) applied.push(name);
135
+ throwIfAborted(signal);
58
136
  }
59
- throwIfAborted(signal);
137
+ return applied;
60
138
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Provenance of messages carried over from another app's conversation
3
+ * (conversation-import). Their text is prose someone wrote elsewhere: it may
4
+ * look like a runtime reminder, a task envelope or a compaction summary, but
5
+ * restore and the model wire treat it as plain user/assistant text. Leaf, no IO.
6
+ */
7
+ export const IMPORTED_MESSAGE_SOURCE = 'import';
8
+
9
+ export function isImportedMessage(message) {
10
+ return message?.meta?.source === IMPORTED_MESSAGE_SOURCE;
11
+ }
@@ -2,6 +2,7 @@
2
2
  // (signature + stub) and the edit-progress classification.
3
3
  // Pure string/signature helpers; the loop body only wires state + messages.
4
4
  // No provider/manager coupling.
5
+ import { knownMcpToolIdentity } from '../../../../shared/tool-primitives.mjs';
5
6
 
6
7
  // Deterministic, key-sorted stringify for cross-turn call signatures (recursive,
7
8
  // unlike tool-classify's top-level-only _canonicalArgs).
@@ -43,7 +44,7 @@ const NON_PROGRESS_TOOLS = new Set(['Skill', 'recall', 'agent', 'task', 'cwd', '
43
44
  // not in the meta/non-progress set. apply_patch and shell/bash always count.
44
45
  export function isEditProgressTool(name, isEager) {
45
46
  if (isEager) return false;
46
- const bare = name?.startsWith('mcp__') ? name.split('__').pop() : name;
47
+ const bare = name?.startsWith('mcp__') ? knownMcpToolIdentity(name)?.tool || name.split('__').pop() : name;
47
48
  if (bare === 'apply_patch' || bare === 'shell' || bare === 'bash' || bare === 'bash_session') return true;
48
49
  return !NON_PROGRESS_TOOLS.has(bare);
49
50
  }
@@ -146,7 +146,7 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
146
146
  messages,
147
147
  opts,
148
148
  sessionId,
149
- onSkillPrompt: (content) => state.pendingSkillPrompts.push(content),
149
+ onSkillPrompt: (content, message) => state.pendingSkillPrompts.push({ content, message }),
150
150
  });
151
151
  // Bounded recovery for provider turns that returned no client tool calls
152
152
  // (max-output ladder, refusal retry, provider continuation, empty-turn
@@ -34,8 +34,9 @@ export function createNoToolTurnResolver({
34
34
  refusalRetryUsed: false,
35
35
  emptyNudgeStreak: 0,
36
36
  // Structured provider continuation signals honored this turn. Diagnostic
37
- // only; the hard iteration cap remains the sole bound on how long a
38
- // provider may keep declaring "not done" inside one user turn.
37
+ // only: the loop has no iteration cap, so this count bounds nothing. A
38
+ // text-only continuation runaway is bounded by continuationsSinceToolBatch
39
+ // below; continuations interleaved with tool batches are not limited.
39
40
  providerContinuationCount: 0,
40
41
  // Continuations since the last executed tool batch — bounds the text-only
41
42
  // continuation runaway (see PROVIDER_CONTINUATION_NO_TOOL_LIMIT).
@@ -5,9 +5,10 @@
5
5
  */
6
6
  import { traceCacheBreak } from '../../cache-break-trace.mjs';
7
7
  import { resolveLiveToolCwd } from './tool-exec.mjs';
8
- import { prepareExplicitSkills } from '../explicit-skills.mjs';
8
+ import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
9
9
  import { addUsage, usageDeltaEvent } from './usage.mjs';
10
10
  import { prepareProviderRequest } from './request-boundary.mjs';
11
+ import { applyStepEffort } from './step-auto-effort.mjs';
11
12
  import { projectProviderRequest } from './request-projection.mjs';
12
13
  import { traceProviderSend, traceOutputTruncation } from './diagnostics.mjs';
13
14
  import { createEagerDispatcher } from '../eager-dispatch.mjs';
@@ -44,10 +45,13 @@ export async function beginIteration(state) {
44
45
  // policy-checked selection before the next provider snapshot instead of
45
46
  // starting detached work from a drain callback.
46
47
  while (state.pendingSkillPrompts.length) {
47
- await prepareExplicitSkills(state.pendingSkillPrompts.shift(), messages, sessionRef, {
48
+ const { content, message: queued } = state.pendingSkillPrompts.shift();
49
+ const applied = await prepareExplicitSkills(content, messages, sessionRef, {
48
50
  cwd: state.cwd,
49
51
  signal: opts.signal,
50
52
  });
53
+ const message = recordAppliedSkills(messages, queued, applied);
54
+ notifySkillsApplied(opts.onSkillsApplied, applied, message);
51
55
  }
52
56
  const boundary = await prepareProviderRequest({
53
57
  provider,
@@ -129,6 +133,9 @@ export async function sendProviderRequest(state, round) {
129
133
  const sendStartedAt = Date.now();
130
134
  const preSendMs = sendStartedAt - round.iterT0;
131
135
  const toolResumeMs = state.lastToolBatchEndedAt ? sendStartedAt - state.lastToolBatchEndedAt : null;
136
+ // The step judgment ran beside opening the iteration; wait for it only up to
137
+ // its grace, then it rides on this request or on the next.
138
+ await applyStepEffort(state);
132
139
  // Every attempt is built from the live transcript, so a compaction between
133
140
  // attempts reaches the provider; a pending image strip only filters it.
134
141
  const messageSource = state.imageStrip
@@ -263,8 +270,7 @@ function traceActualCacheMiss(state, sent, response, { previousGuardState, previ
263
270
  const ttlMs = MESSAGES_CACHE_TTL_MS[sessionRef.providerCacheOpts?.cacheStrategy?.messages];
264
271
  const idleMs = sent.sendStartedAt - previousSendAt;
265
272
  const requestPrefixChanged =
266
- !!previousGuardState &&
267
- previousGuardState.requestPrefixHash !== sent.prefixGuardCandidate?.requestPrefixHash;
273
+ !!previousGuardState && previousGuardState.requestPrefixHash !== sent.prefixGuardCandidate?.requestPrefixHash;
268
274
  if (
269
275
  baseline > 0 &&
270
276
  ttlMs &&
@@ -156,7 +156,7 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
156
156
  const notification = merged.mode === 'task-notification';
157
157
  const execution =
158
158
  notification && merged.execution && typeof merged.execution === 'object' ? { ...merged.execution } : null;
159
- messages.push({
159
+ const message = {
160
160
  role: 'user',
161
161
  content: merged.content,
162
162
  meta: {
@@ -165,8 +165,9 @@ function injectSteeringEntry(merged, { messages, opts, stage, onSkillPrompt }) {
165
165
  ...(submissionIds.length ? { submissionIds } : {}),
166
166
  transcript: steeringTranscriptMeta,
167
167
  },
168
- });
169
- if (!notification) onSkillPrompt(merged.content);
168
+ };
169
+ messages.push(message);
170
+ if (!notification) onSkillPrompt(merged.content, message);
170
171
  const text = merged.text || steeringContentText(merged.content);
171
172
  try {
172
173
  opts.onSteerMessage?.(text, {
@@ -8,12 +8,13 @@
8
8
  * later request rebuilds the same history and the prompt cache keeps hitting.
9
9
  * A judge that is missing, slow or failing leaves the step at the effort in
10
10
  * force (the turn's or the previous step's).
11
- * A custom route (snapshot mode 'custom') has no mid-conversation update: the
12
- * judged effort, snapped onto the model's levels, becomes the effort of the
13
- * turn's following requests.
11
+ * A custom route snaps the judged effort onto the model's levels. Without a
12
+ * mid-conversation update (snapshot mode 'custom') it becomes the effort of
13
+ * the turn's following requests; a custom GPT-6+ Responses route (mode
14
+ * 'responses') also marks it as the update.
14
15
  */
15
16
  import { resolveAutoEffort } from '../../providers/auto-effort.mjs';
16
- import { resolveCustomReasoning } from '../../providers/custom-reasoning.mjs';
17
+ import { isCustomProviderName, resolveCustomReasoning } from '../../providers/custom-reasoning.mjs';
17
18
  import { autoEffortModel, stepEffortConfiguration } from '../../providers/effort-configuration.mjs';
18
19
  import { effortOptionsFor } from '../../runtime-core/effort.mjs';
19
20
  import { judgeTurn, recordEffortDecision } from '../../../../effort-judge/judge-client.mjs';
@@ -35,12 +36,58 @@ const argText = (args) =>
35
36
  .slice(0, 600)
36
37
  .join('');
37
38
 
39
+ // A judgment runs beside the next iteration instead of in front of it. Its
40
+ // record lives on the loop state (`state.stepEffort`), so it can only ever
41
+ // reach the turn that started it; its promise settles the record and touches
42
+ // nothing else, so an aborted loop is never mutated afterwards. The result
43
+ // lands on the batch's last tool result in one of two places:
44
+ // - resolved before the next request is projected: on that tool result, so
45
+ // the request carries it (applyStepEffort);
46
+ // - resolved later: the request already went out with the earlier effort and
47
+ // its history is never rewritten (that would break the prompt cache), so
48
+ // the result marks the last tool result of the next batch, and the
49
+ // following request carries it (markStepEffort).
50
+ function applyResult(state, target, result) {
51
+ if (result.level) state.opts.effort = result.level;
52
+ if (result.marked) target.meta = { ...target.meta, effortConfiguration: result.marked };
53
+ }
54
+
55
+ // How long a request waits for its step judgment (measured judge p90 ≈ 284 ms).
56
+ export const STEP_EFFORT_GRACE_MS = 300;
57
+
58
+ /**
59
+ * Before a provider request is projected: wait up to the grace for the pending
60
+ * judgment and apply it if it resolved. A slower one leaves this request at
61
+ * the effort in force and lands on the following request.
62
+ */
63
+ export async function applyStepEffort(state) {
64
+ const pending = state.stepEffort;
65
+ if (!pending || pending.sent) return;
66
+ if (!pending.settled && !state.signal?.aborted) {
67
+ let timer;
68
+ await Promise.race([
69
+ pending.promise,
70
+ new Promise((resolve) => {
71
+ timer = setTimeout(resolve, STEP_EFFORT_GRACE_MS);
72
+ }),
73
+ ]);
74
+ clearTimeout(timer);
75
+ // Another caller may have handled it while this one waited.
76
+ if (pending.sent) return;
77
+ }
78
+ pending.sent = true;
79
+ if (!pending.result || state.signal?.aborted || !state.messages.includes(pending.target)) return;
80
+ applyResult(state, pending.target, pending.result);
81
+ pending.applied = true;
82
+ }
83
+
38
84
  /**
39
- * Judge the step after this batch and mark its last tool result.
85
+ * Start judging the step after this batch (not awaited). The result applies to
86
+ * the next request if it is ready by then, else to the one after.
40
87
  * `state.opts.stepAutoEffort` ({ request, base }) is set only when auto effort
41
88
  * is on for this turn and the installed judge was trained on steps.
42
89
  */
43
- export async function markStepEffort(state, assistantTurnMsg) {
90
+ export function markStepEffort(state, assistantTurnMsg) {
44
91
  const auto = state.opts?.stepAutoEffort;
45
92
  const snapshot = state.opts?.effortConfiguration;
46
93
  if (!auto || !snapshot || state.signal?.aborted) return;
@@ -55,11 +102,27 @@ export async function markStepEffort(state, assistantTurnMsg) {
55
102
  }
56
103
  }
57
104
  if (!last) return;
105
+ const previous = state.stepEffort;
106
+ if (previous) {
107
+ // It resolved after its request went out: this batch carries it. One still
108
+ // running is superseded: the newest step's judgment queues behind it and
109
+ // any judgment still waiting in between is skipped (latest wins), so the
110
+ // judge never has more than one running and one queued.
111
+ if (previous.settled) {
112
+ if (previous.result && !previous.applied) applyResult(state, last, previous.result);
113
+ } else {
114
+ previous.superseded = true;
115
+ }
116
+ }
58
117
  state.autoEffortSteps = (state.autoEffortSteps || 0) + 1;
59
118
  const step = state.autoEffortSteps;
60
119
  const provider = state.sessionRef?.provider;
61
120
  const model = state.model;
62
- const judged = await judgeTurn({
121
+ const pending = { target: last, sent: false, settled: false, applied: false, superseded: false, result: null };
122
+ const behind = previous && !previous.settled ? previous.promise : Promise.resolve();
123
+ state.stepEffort = pending;
124
+ const context = { auto, snapshot, step, provider, model, providerClient: state.provider, sessionId: state.sessionId };
125
+ const input = {
63
126
  step: {
64
127
  request: auto.request,
65
128
  step,
@@ -70,11 +133,26 @@ export async function markStepEffort(state, assistantTurnMsg) {
70
133
  result: stepResultWindow(textOf(results.get(call.id)?.content)),
71
134
  })),
72
135
  },
73
- });
136
+ };
137
+ pending.promise = behind
138
+ .then(() => (pending.superseded ? null : judgeTurn(input)))
139
+ .then((judged) => (judged ? stepResult(context, judged) : null))
140
+ // A failed judgment leaves the step at the effort in force.
141
+ .catch(() => null)
142
+ .then((result) => {
143
+ // Only this record changes here; the loop reads it at its next boundary.
144
+ pending.result = result;
145
+ pending.settled = true;
146
+ });
147
+ }
148
+
149
+ // What a resolved judgment sets: `{ marked, level }` (the tool result's effort
150
+ // configuration, and the per-request effort of a custom route), or null.
151
+ function stepResult({ auto, snapshot, step, provider, model, providerClient, sessionId }, judged) {
74
152
  // `atStep` numbers the tool-result step; `step` in a resolved decision is the ladder move.
75
153
  const record = {
76
154
  at: new Date().toISOString(),
77
- sessionId: state.sessionId,
155
+ sessionId,
78
156
  provider,
79
157
  model,
80
158
  mode: 'on',
@@ -82,9 +160,9 @@ export async function markStepEffort(state, assistantTurnMsg) {
82
160
  };
83
161
  if (!judged.probs) {
84
162
  recordEffortDecision({ ...record, skipped: judged.skipped });
85
- return;
163
+ return null;
86
164
  }
87
- const modelMeta = autoEffortModel(provider, state.provider, model);
165
+ const modelMeta = autoEffortModel(provider, providerClient, model);
88
166
  const resolved = resolveAutoEffort({
89
167
  base: auto.base,
90
168
  options: effortOptionsFor(provider, modelMeta),
@@ -96,19 +174,25 @@ export async function markStepEffort(state, assistantTurnMsg) {
96
174
  ms: judged.ms,
97
175
  probs: judged.probs.map((value) => Number(value.toFixed(3))),
98
176
  });
99
- if (snapshot.mode === 'custom') {
177
+ if (isCustomProviderName(provider)) {
100
178
  const level =
101
179
  resolved &&
102
180
  resolveCustomReasoning({
103
- protocol: state.provider?.customConfig?.protocol,
181
+ protocol: providerClient?.customConfig?.protocol,
104
182
  model: modelMeta,
105
183
  effort: resolved.effort,
106
184
  }).level;
107
- if (!level) return;
108
- state.opts.effort = level;
109
- last.meta = { ...last.meta, effortConfiguration: { ...snapshot, effort: level } };
110
- return;
185
+ if (!level) return null;
186
+ // The per-request effort also carries the step on an update route,
187
+ // where a rejected update replays the request with it.
188
+ return {
189
+ level,
190
+ marked:
191
+ snapshot.mode === 'custom'
192
+ ? { ...snapshot, effort: level }
193
+ : stepEffortConfiguration(snapshot, provider, model, level),
194
+ };
111
195
  }
112
196
  const marked = resolved && stepEffortConfiguration(snapshot, provider, model, resolved.effort);
113
- if (marked) last.meta = { ...last.meta, effortConfiguration: marked };
197
+ return marked ? { level: null, marked } : null;
114
198
  }
@@ -12,6 +12,8 @@ import {
12
12
  filterSkillsExcludingDisabled,
13
13
  isSkillDisabled,
14
14
  skillMissingFeature,
15
+ skillOutsideScope,
16
+ skillOutsideScopeError,
15
17
  } from '../../context/collect.mjs';
16
18
 
17
19
  // Eager-dispatch: tools with readOnlyHint:true in their declaration are safe
@@ -96,6 +98,7 @@ export function viewSkill(cwd, name, session = null) {
96
98
  return `Error: skill "${skillName}" needs the ${missingFeature} built-in feature, which is not installed or is switched off in Extensions → Plugin → Built-in`;
97
99
  }
98
100
  if (isSkillDisabled(skillName)) return `Error: skill "${skillName}" is disabled`;
101
+ if (skillOutsideScope(skillName, cwd)) return skillOutsideScopeError(skillName);
99
102
  const res = loadSkillResource(skillName, cwd);
100
103
  if (!res) return `Error: skill "${skillName}" not found`;
101
104
  // Return the general tool envelope: the model-visible tool_result is the
@@ -70,7 +70,9 @@ export async function runToolPhase(state, round, sent, onToolCall) {
70
70
  // carries a runtime notice for the model, delivered after its results.
71
71
  if (response.recoveryNotice) messages.push(response.recoveryNotice);
72
72
  const toolsEndedAt = Date.now();
73
- await markStepEffort(state, assistantTurnMsg);
73
+ // Starts the step judgment; the next request uses it if it has resolved by
74
+ // then (send-phase.mjs applyStepEffort), else the one after.
75
+ markStepEffort(state, assistantTurnMsg);
74
76
  try {
75
77
  opts.onToolPhaseCompleted?.({
76
78
  iteration: round.nextIteration,
@@ -12,6 +12,17 @@ import {
12
12
 
13
13
  const SYNTHETIC_PROMPT_SOURCES = ['goal-continuation', 'goal-closeout'];
14
14
 
15
+ // A caller-declared prompt source the runtime trusts as provenance of the
16
+ // initial prompt: Goal turns are runtime-synthetic; a task notification (an
17
+ // idle-drained completion, an automation run's agent results) is stored like
18
+ // one the steering/queue drains inject, never as the user speaking — so none
19
+ // of these run explicit skill selection.
20
+ function initialPromptSourceFor(promptSource) {
21
+ if (SYNTHETIC_PROMPT_SOURCES.includes(promptSource)) return { source: promptSource, synthetic: true };
22
+ if (promptSource === PENDING_MODE_TASK_NOTIFICATION) return { source: promptSource };
23
+ return null;
24
+ }
25
+
15
26
  /** Prompt provenance for a drained queue turn: the source plus its execution when one exists. */
16
27
  const promptSourceFields = (source, execution) => {
17
28
  if (!source) return null;
@@ -42,9 +53,13 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
42
53
  // Local FIFO of follow-up prompts drained from the pending-message queue
43
54
  // after each turn — keeps queued `agent type=send` messages in order.
44
55
  const tail = [];
45
- let initialPromptSource = SYNTHETIC_PROMPT_SOURCES.includes(promptSource)
46
- ? { source: promptSource, synthetic: true }
47
- : null;
56
+ let initialPromptSource = initialPromptSourceFor(promptSource);
57
+ // Queue entries of the input nextTurn handed out that no turn owns yet.
58
+ // Once a turn starts, its failure path acks or releases them instead.
59
+ let unstartedEntries = [];
60
+ const release = (entries) => {
61
+ if (entries?.length > 0) releasePendingMessages(sessionId, entries);
62
+ };
48
63
 
49
64
  /** The next turn's input — prompt, caller context, consumed queue entries
50
65
  * and provenance — or null when nothing model-visible is left to run. */
@@ -74,7 +89,26 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
74
89
  tail.unshift(...groups.slice(1).map(tailTurnFor));
75
90
  input = queuedTurnInput(tailTurnFor(first));
76
91
  }
77
- return hasModelVisiblePromptContent(input.prompt) ? input : null;
92
+ if (!hasModelVisiblePromptContent(input.prompt)) {
93
+ release(input.pendingEntries);
94
+ return null;
95
+ }
96
+ unstartedEntries = input.pendingEntries;
97
+ return input;
98
+ };
99
+
100
+ /** The turn for the last nextTurn input started: it now owns its entries. */
101
+ const markStarted = () => {
102
+ unstartedEntries = [];
103
+ };
104
+
105
+ /** Hands every drained entry no turn started back to the pending queue —
106
+ * the waiting follow-up turns and an input whose turn never opened.
107
+ * Called once when the ask unwinds; each entry is released once. */
108
+ const releaseUnstarted = () => {
109
+ const entries = [...unstartedEntries, ...tail.splice(0).flatMap((turn) => turn.entries || [])];
110
+ unstartedEntries = [];
111
+ release(entries);
78
112
  };
79
113
 
80
114
  /** Queues drained pending-message entries as follow-up turns; true when
@@ -92,5 +126,8 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
92
126
  return true;
93
127
  };
94
128
 
95
- return { nextTurn, pushDrained };
129
+ /** True while drained follow-up turns are still waiting to run. */
130
+ const hasQueuedTurns = () => tail.length > 0;
131
+
132
+ return { nextTurn, pushDrained, hasQueuedTurns, markStarted, releaseUnstarted };
96
133
  }
@@ -148,7 +148,9 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
148
148
  // queued send always lands AFTER the in-flight prompt here, ordering is
149
149
  // preserved and the spawn/connecting startup race disappears.
150
150
  for (;;) {
151
- const input = queue.nextTurn({ prompt, context, explicitPrefetch });
151
+ // `skillSelection` (askOpts) is the trusted selection input of the
152
+ // caller's own prompt only; drained follow-up turns never inherit it.
153
+ const input = queue.nextTurn({ prompt, context, explicitPrefetch, skillSelection: askOpts.skillSelection });
152
154
  if (!input) {
153
155
  void takeoverHydration.then((count) => {
154
156
  if (count <= 0) return;
@@ -176,6 +178,9 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
176
178
  ...claimed,
177
179
  ...armTurnDurability({ sessionId, ...claimed, pendingEntries: input.pendingEntries }),
178
180
  };
181
+ // From here the turn's own unwind (ask-turn-failure) acks or releases
182
+ // its entries; the queue keeps owning only the not-yet-started ones.
183
+ queue.markStarted();
179
184
  const turn = await runAskTurn({
180
185
  sessionId,
181
186
  opened,
@@ -191,9 +196,12 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
191
196
  // that arrived while this turn was in flight runs next, in order, as a
192
197
  // follow-up user turn. The mutex is still held, so a send racing this
193
198
  // drain either landed before (picked up here) or enqueues for the next
194
- // loop. When the queue is empty we return the latest turn's result.
199
+ // loop. Follow-ups an idle ask already drained wait in the queue's
200
+ // tail and run before anything new. When both are empty we return the
201
+ // latest turn's result.
195
202
  const drained = turn.drained.length > 0 ? turn.drained : drainPendingMessages(sessionId);
196
- if (queue.pushDrained(drained)) {
203
+ queue.pushDrained(drained);
204
+ if (queue.hasQueuedTurns()) {
197
205
  // Carry the just-committed in-memory session into the follow-up
198
206
  // turn so the queued tail sees the preceding assistant/tool
199
207
  // context. loadSession() would return this same live snapshot
@@ -214,6 +222,9 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
214
222
  return result;
215
223
  }
216
224
  } finally {
225
+ // A failed/cancelled turn ends the ask: the follow-up turns drained
226
+ // behind it never ran, so their entries go back to the pending queue.
227
+ queue.releaseUnstarted();
217
228
  // A thrown setup/provider path must never leave a delayed checkpoint
218
229
  // writer alive after the turn lock is released.
219
230
  // Clear the controller only if it's still ours (closeSession may have
@@ -8,7 +8,12 @@
8
8
  // model declares effort levels (the effort is sent with each request).
9
9
  import { readSection } from '../../../../shared/config.mjs';
10
10
  import { autoEffortModel, turnEffortConfigurationMode } from '../../providers/effort-configuration.mjs';
11
- import { autoEffortBase, normalizeAutoEffortMode, resolveAutoEffort } from '../../providers/auto-effort.mjs';
11
+ import {
12
+ autoEffortApplicable,
13
+ autoEffortBase,
14
+ normalizeAutoEffortMode,
15
+ resolveAutoEffort,
16
+ } from '../../providers/auto-effort.mjs';
12
17
  import { builtinFeatureActive } from '../../runtime-core/builtin-features.mjs';
13
18
  import { effortOptionsFor } from '../../runtime-core/effort.mjs';
14
19
  import { effortJudgeSupportsSteps, judgeTurn, recordEffortDecision } from '../../../../effort-judge/judge-client.mjs';
@@ -78,12 +83,6 @@ export async function resolveTurnAutoEffort({ sessionId, session, provider, inpu
78
83
  if (!turnEffortConfigurationMode(session, provider)) return null;
79
84
  const chosen = String(session.effort || '').toLowerCase();
80
85
  const base = autoEffortBase(chosen);
81
- // The previous user request shows the work a short follow-up continues.
82
- const judged = await judgeTurn({
83
- request,
84
- prev: lastText(session.messages, 'assistant'),
85
- prevRequest: lastText(session.messages, 'user'),
86
- });
87
86
  const record = {
88
87
  at: new Date().toISOString(),
89
88
  sessionId,
@@ -94,15 +93,24 @@ export async function resolveTurnAutoEffort({ sessionId, session, provider, inpu
94
93
  base,
95
94
  requestChars: request.length,
96
95
  };
96
+ const options = effortOptionsFor(session.provider, autoEffortModel(session.provider, provider, session.model));
97
+ // No judged outcome can apply when the base is off the model's ladder, so
98
+ // the judge is not asked (in observe mode too: nothing would be logged).
99
+ if (!autoEffortApplicable({ base, options })) {
100
+ recordEffortDecision({ ...record, skipped: 'default-outside-auto-range' });
101
+ return null;
102
+ }
103
+ // The previous user request shows the work a short follow-up continues.
104
+ const judged = await judgeTurn({
105
+ request,
106
+ prev: lastText(session.messages, 'assistant'),
107
+ prevRequest: lastText(session.messages, 'user'),
108
+ });
97
109
  if (!judged.probs) {
98
110
  recordEffortDecision({ ...record, skipped: judged.skipped });
99
111
  return null;
100
112
  }
101
- const resolved = resolveAutoEffort({
102
- base,
103
- options: effortOptionsFor(session.provider, autoEffortModel(session.provider, provider, session.model)),
104
- probs: judged.probs,
105
- });
113
+ const resolved = resolveAutoEffort({ base, options, probs: judged.probs });
106
114
  recordEffortDecision({
107
115
  ...record,
108
116
  ...(resolved || { skipped: 'default-outside-auto-range' }),