mixdog 1.2.1 → 1.2.3

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 (557) hide show
  1. package/README.md +18 -0
  2. package/package.json +2 -1
  3. package/scripts/gate-local.mjs +19 -3
  4. package/scripts/model-catalog-audit.mjs +1 -15
  5. package/scripts/provider-pingpong-live.mjs +509 -0
  6. package/src/agents/advisor/AGENT.md +1 -1
  7. package/src/agents/advisor/agent.json +1 -1
  8. package/src/agents/reviewer/AGENT.md +3 -3
  9. package/src/agents/reviewer/agent.json +1 -1
  10. package/src/defaults/shipped-history.json +6 -0
  11. package/src/defaults/skills/setup/SKILL.md +6 -1
  12. package/src/defaults/skills/setup/references/actions.md +44 -22
  13. package/src/defaults/skills/setup/references/surfaces.md +10 -8
  14. package/src/headless-exec.mjs +15 -8
  15. package/src/lib/keychain-cjs.cjs +7 -4
  16. package/src/output-styles/common.md +11 -10
  17. package/src/repl/slash-commands.mjs +16 -1
  18. package/src/rules/agent/AGENT.md +5 -5
  19. package/src/rules/lead/LEAD.md +5 -5
  20. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
  22. package/src/runtime/agent/orchestrator/agent-trace-io.mjs +92 -61
  23. package/src/runtime/agent/orchestrator/cache-mtime.mjs +10 -1
  24. package/src/runtime/agent/orchestrator/config-providers-merge.mjs +7 -0
  25. package/src/runtime/agent/orchestrator/config.mjs +5 -0
  26. package/src/runtime/agent/orchestrator/context/collect.mjs +1 -0
  27. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +26 -7
  28. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +112 -7
  29. package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
  30. package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
  31. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
  32. package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
  33. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
  34. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
  35. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
  36. package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
  37. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
  38. package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
  39. package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
  40. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
  41. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +48 -20
  44. package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
  45. package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +98 -0
  46. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
  47. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
  48. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +48 -37
  49. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +10 -7
  50. package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
  51. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
  52. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
  54. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +102 -45
  58. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
  59. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-blocks.mjs +20 -1
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-delta.mjs +15 -2
  62. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +11 -4
  63. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/message-events.mjs +3 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +12 -5
  65. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-outcome.mjs +1 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-state.mjs +14 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +10 -1
  68. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +9 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +40 -7
  70. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
  71. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
  72. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +88 -21
  73. package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +30 -10
  74. package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +106 -0
  75. package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +10 -61
  76. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +94 -15
  77. package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
  78. package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +117 -72
  79. package/src/runtime/agent/orchestrator/providers/compat-request-policy.mjs +78 -2
  80. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +75 -10
  81. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  82. package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
  83. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +103 -23
  84. package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +43 -2
  85. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
  86. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +32 -6
  87. package/src/runtime/agent/orchestrator/providers/cursor.mjs +27 -3
  88. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +64 -1
  89. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +70 -6
  90. package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +34 -7
  91. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +32 -6
  92. package/src/runtime/agent/orchestrator/providers/deepseek.mjs +67 -0
  93. package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +80 -18
  94. package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +11 -4
  95. package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +46 -8
  96. package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +273 -0
  97. package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
  99. package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +41 -5
  100. package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
  101. package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
  102. package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +369 -112
  103. package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +3 -1
  104. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +140 -35
  105. package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +70 -24
  106. package/src/runtime/agent/orchestrator/providers/gemini.mjs +80 -29
  107. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  108. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
  109. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +69 -19
  110. package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +60 -0
  111. package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +1 -0
  112. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +246 -42
  113. package/src/runtime/agent/orchestrator/providers/lib/anthropic-server-tool-usage.mjs +18 -0
  114. package/src/runtime/agent/orchestrator/providers/lib/anthropic-system-blocks.mjs +27 -6
  115. package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +388 -0
  116. package/src/runtime/agent/orchestrator/providers/lib/inclusive-usage.mjs +54 -0
  117. package/src/runtime/agent/orchestrator/providers/lib/origin-bound-fetch.mjs +16 -0
  118. package/src/runtime/agent/orchestrator/providers/lib/responses-terminal-fields.mjs +11 -0
  119. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +8 -0
  120. package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +48 -22
  121. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +119 -18
  122. package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +13 -15
  123. package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
  124. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
  125. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +1 -1
  126. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  127. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +33 -1
  128. package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
  129. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -0
  130. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +2 -0
  131. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +123 -61
  132. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +35 -14
  133. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +27 -2
  134. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +37 -7
  135. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/tool-call-acc.mjs +2 -9
  136. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
  137. package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
  138. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +47 -2
  139. package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +47 -25
  140. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +5 -1
  141. package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +151 -0
  142. package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +64 -23
  143. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +79 -33
  144. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-head-parity.json +2901 -0
  145. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +172 -0
  146. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
  147. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +144 -15
  148. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
  149. package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
  150. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +9 -2
  151. package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +2 -0
  152. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +109 -26
  153. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +27 -6
  154. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +34 -20
  155. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +23 -4
  156. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +37 -28
  157. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
  158. package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
  159. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +23 -10
  160. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +43 -15
  161. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +10 -0
  162. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-items.mjs +18 -7
  163. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +5 -2
  164. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
  165. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +7 -3
  166. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/text-relay.mjs +9 -1
  167. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
  168. package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
  169. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +80 -0
  170. package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +37 -4
  171. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +12 -1
  172. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
  173. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +12 -1
  174. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +76 -33
  175. package/src/runtime/agent/orchestrator/providers/openai-responses-payload-parity.fixture.json +323 -0
  176. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +44 -16
  177. package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
  178. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +7 -16
  179. package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
  180. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/refusals.mjs +60 -0
  181. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +51 -35
  182. package/src/runtime/agent/orchestrator/providers/openai-ws-response-state.mjs +30 -0
  183. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
  184. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
  185. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +12 -0
  186. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/terminal-frames.mjs +25 -6
  187. package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
  188. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +41 -22
  189. package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +170 -0
  190. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +75 -70
  191. package/src/runtime/agent/orchestrator/providers/openrouter.mjs +75 -0
  192. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +720 -0
  193. package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -1
  194. package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
  195. package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +123 -219
  196. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
  197. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
  198. package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
  199. package/src/runtime/agent/orchestrator/providers/transport-scope.d.mts +6 -0
  200. package/src/runtime/agent/orchestrator/providers/transport-scope.mjs +28 -0
  201. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +157 -0
  202. package/src/runtime/agent/orchestrator/providers/wire-route.mjs +512 -0
  203. package/src/runtime/agent/orchestrator/providers/xai-tool-search-head-parity.json +1131 -0
  204. package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +183 -0
  205. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +100 -0
  206. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
  207. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +21 -0
  208. package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +15 -0
  209. package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
  210. package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
  211. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
  212. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
  213. package/src/runtime/agent/orchestrator/runtime-core/provider-request-snapshot.mjs +7 -3
  214. package/src/runtime/agent/orchestrator/runtime-core/session-title.mjs +21 -1
  215. package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +14 -3
  216. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +526 -0
  217. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
  218. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +10 -7
  219. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +176 -71
  220. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +62 -10
  221. package/src/runtime/agent/orchestrator/runtime-core/tool-exposure.mjs +96 -0
  222. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +5 -1
  223. package/src/runtime/agent/orchestrator/session/compact/constants.mjs +3 -2
  224. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +14 -93
  225. package/src/runtime/agent/orchestrator/session/compact/summary-anchor.mjs +5 -0
  226. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +120 -0
  227. package/src/runtime/agent/orchestrator/session/context-tool-schema.mjs +17 -2
  228. package/src/runtime/agent/orchestrator/session/context-utils.mjs +8 -4
  229. package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +2 -1
  230. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +6 -1
  231. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +91 -1
  232. package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +1 -1
  233. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +56 -0
  234. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +18 -8
  235. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -0
  236. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +21 -10
  237. package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +132 -0
  238. package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +30 -0
  239. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +67 -20
  240. package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +104 -19
  241. package/src/runtime/agent/orchestrator/session/loop/tool-call-resolution.mjs +95 -0
  242. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +30 -3
  243. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -4
  244. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
  245. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +150 -0
  246. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +7 -2
  247. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
  248. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
  249. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +2 -1
  250. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +34 -12
  251. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +57 -21
  252. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +57 -25
  253. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  254. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  255. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +8 -0
  256. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
  257. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +208 -14
  258. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +16 -4
  259. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +25 -3
  260. package/src/runtime/agent/orchestrator/session/manager/session-fallback-title.mjs +82 -0
  261. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +19 -2
  262. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
  263. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -1
  264. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
  265. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -1
  266. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +6 -0
  267. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +168 -29
  268. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +96 -0
  269. package/src/runtime/agent/orchestrator/session/side-question.mjs +192 -0
  270. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +17 -3
  271. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +23 -0
  272. package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +58 -2
  273. package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
  274. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +41 -28
  275. package/src/runtime/agent/orchestrator/session/store/session-observers.mjs +4 -2
  276. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +12 -1
  277. package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +3 -1
  278. package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
  279. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
  280. package/src/runtime/agent/orchestrator/session/store-summary-archived-agent.mjs +34 -15
  281. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +6 -0
  282. package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +13 -0
  283. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +13 -1
  284. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +10 -2
  285. package/src/runtime/agent/orchestrator/session/store.mjs +19 -3
  286. package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +1 -0
  287. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +1 -1
  288. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +1 -1
  289. package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +7 -1
  290. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -0
  291. package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +77 -0
  292. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
  293. package/src/runtime/agent/orchestrator/tools/bash-policy-scan.mjs +8 -2
  294. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
  295. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +3 -3
  296. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
  297. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
  298. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
  299. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +14 -10
  300. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +49 -34
  301. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +23 -1
  302. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +1 -1
  303. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1 -1
  304. package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +73 -39
  305. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +18 -1
  306. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -4
  307. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +88 -12
  308. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +2 -0
  309. package/src/runtime/computer-bridge/action-schema.mjs +44 -21
  310. package/src/runtime/github/tool.mjs +92 -2
  311. package/src/runtime/memory/index.mjs +1 -0
  312. package/src/runtime/memory/lib/http-router/ingest-routes.mjs +22 -1
  313. package/src/runtime/memory/lib/ingest/transcript-offsets.mjs +2 -0
  314. package/src/runtime/memory/lib/pg/adapter.mjs +98 -1
  315. package/src/runtime/memory/lib/pg/process.mjs +65 -50
  316. package/src/runtime/memory/lib/pg/run-child.mjs +49 -0
  317. package/src/runtime/memory/lib/pg/supervisor.mjs +124 -16
  318. package/src/runtime/memory/lib/runtime-fetcher.mjs +12 -13
  319. package/src/runtime/memory/lib/transcript-ingest.mjs +14 -0
  320. package/src/runtime/office/README.md +2 -2
  321. package/src/runtime/office/authoring/html-document-author.mjs +2 -0
  322. package/src/runtime/office/authoring/pptx-contact-sheet.mjs +2 -0
  323. package/src/runtime/office/authoring/pptx-html-measure.mjs +2 -0
  324. package/src/runtime/office/authoring/pptx-html-runner.mjs +2 -0
  325. package/src/runtime/office/authoring/xlsx-html-runner.mjs +2 -0
  326. package/src/runtime/office/core/office-actions-read.mjs +3 -0
  327. package/src/runtime/office/core/office-render-preview.mjs +2 -0
  328. package/src/runtime/office/core/office-session-dispatch.mjs +7 -0
  329. package/src/runtime/office/core/office-sessionless-actions.mjs +8 -0
  330. package/src/runtime/office/core/office-sessions.mjs +5 -0
  331. package/src/runtime/office/core/office-transactions.mjs +3 -0
  332. package/src/runtime/office/core/office-write-ledger.mjs +98 -0
  333. package/src/runtime/office/design/library/templates/mixdog-executive.mixdog-edit.pptx +0 -0
  334. package/src/runtime/office/index.mjs +30 -0
  335. package/src/runtime/office/pdf/pdf-edit-document.mjs +2 -0
  336. package/src/runtime/office/pdf/pdf-render.mjs +23 -7
  337. package/src/runtime/office/pdf/pdf-security.mjs +2 -0
  338. package/src/runtime/office/portable/portable-opc.mjs +4 -1
  339. package/src/runtime/office/quality/visual-diff.mjs +2 -0
  340. package/src/runtime/office/shared/values.mjs +0 -5
  341. package/src/runtime/shared/background-tasks.mjs +92 -14
  342. package/src/runtime/shared/bounded-file-read.mjs +13 -0
  343. package/src/runtime/shared/bounded-line-diff.mjs +182 -0
  344. package/src/runtime/shared/buffered-appender.mjs +18 -0
  345. package/src/runtime/shared/config.mjs +78 -18
  346. package/src/runtime/shared/err-text.mjs +31 -0
  347. package/src/runtime/shared/error-presentation.mjs +159 -19
  348. package/src/runtime/shared/file-permissions.d.mts +6 -1
  349. package/src/runtime/shared/file-permissions.mjs +54 -6
  350. package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
  351. package/src/runtime/shared/llm/anthropic-effort.mjs +129 -134
  352. package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
  353. package/src/runtime/shared/llm/model-catalog.mjs +53 -40
  354. package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
  355. package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
  356. package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
  357. package/src/runtime/shared/llm/usage-accounting.mjs +5 -2
  358. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +3 -0
  359. package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
  360. package/src/runtime/shared/loopback-listener.mjs +9 -0
  361. package/src/runtime/shared/model-display.mjs +13 -0
  362. package/src/runtime/shared/provider-api-key.mjs +2 -0
  363. package/src/runtime/shared/quota-resume-store.mjs +11 -0
  364. package/src/runtime/shared/review-diff.mjs +52 -0
  365. package/src/runtime/shared/session-baseline-store.mjs +960 -0
  366. package/src/runtime/shared/session-sidebar-host.d.mts +4 -0
  367. package/src/runtime/shared/session-sidebar-host.mjs +38 -0
  368. package/src/runtime/shared/slow-span.mjs +114 -0
  369. package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
  370. package/src/runtime/shared/statusline/statusline-segments.mjs +28 -4
  371. package/src/runtime/shared/task-notification-envelope.mjs +2 -2
  372. package/src/runtime/shared/tool-card-model/card-header.mjs +6 -1
  373. package/src/runtime/shared/tool-card-model/generic-detail.mjs +39 -5
  374. package/src/runtime/shared/tool-card-model.mjs +5 -2
  375. package/src/runtime/shared/tool-output-limit.mjs +4 -0
  376. package/src/runtime/shared/tool-primitives.mjs +13 -0
  377. package/src/runtime/shared/tool-result-summary.mjs +8 -1
  378. package/src/runtime/shared/tool-surface.mjs +18 -3
  379. package/src/runtime/shared/tool-work-units.mjs +40 -4
  380. package/src/runtime/shared/transcript-writer/purge.mjs +74 -0
  381. package/src/runtime/shared/transcript-writer.mjs +10 -2
  382. package/src/runtime/shared/turn-changes.mjs +670 -274
  383. package/src/runtime/shared/turn-review-reply.mjs +8 -39
  384. package/src/runtime/shared/turn-snapshot-store.mjs +94 -2
  385. package/src/runtime/shared/turn-snapshot.mjs +986 -690
  386. package/src/runtime/shared/turn-worktree-snapshot.mjs +279 -262
  387. package/src/runtime/shared/user-data-guard.mjs +262 -49
  388. package/src/runtime/shared/worktrees/carry.mjs +531 -0
  389. package/src/runtime/shared/worktrees/contract.mjs +34 -24
  390. package/src/runtime/shared/worktrees/file-recovery.mjs +68 -0
  391. package/src/runtime/shared/worktrees/git-head.mjs +205 -0
  392. package/src/runtime/shared/worktrees/git.mjs +8 -3
  393. package/src/runtime/shared/worktrees/maintenance.mjs +1 -1
  394. package/src/runtime/shared/worktrees/manager.mjs +156 -8
  395. package/src/runtime/shared/worktrees/merge.mjs +31 -21
  396. package/src/runtime/shared/worktrees/paths.d.mts +6 -0
  397. package/src/runtime/shared/worktrees/test-support.mjs +19 -4
  398. package/src/runtime/terminal-bridge/client.mjs +44 -11
  399. package/src/runtime/terminal-bridge/tool-defs.mjs +13 -6
  400. package/src/runtime/tidy/apply.mjs +47 -2
  401. package/src/runtime/tidy/run-engines.mjs +11 -5
  402. package/src/session-runtime/agent-tool-routing.mjs +19 -13
  403. package/src/session-runtime/boot/apis.mjs +19 -5
  404. package/src/session-runtime/boot/facade.mjs +1 -0
  405. package/src/session-runtime/boot/infrastructure.mjs +12 -1
  406. package/src/session-runtime/boot/session.mjs +0 -1
  407. package/src/session-runtime/boot/tools.mjs +4 -1
  408. package/src/session-runtime/cwd-plugins/cwd-apply.mjs +12 -5
  409. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +65 -0
  410. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +3 -1
  411. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +11 -4
  412. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +115 -18
  413. package/src/session-runtime/internal-tool-executor.mjs +6 -2
  414. package/src/session-runtime/lifecycle/cold-heir.mjs +188 -0
  415. package/src/session-runtime/lifecycle/inheritance.mjs +71 -27
  416. package/src/session-runtime/lifecycle/session-catalog.mjs +14 -3
  417. package/src/session-runtime/lifecycle/session-switch/resume.mjs +6 -0
  418. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +29 -6
  419. package/src/session-runtime/lifecycle/teardown.mjs +73 -8
  420. package/src/session-runtime/lifecycle-api.mjs +1 -0
  421. package/src/session-runtime/mcp-glue/connect.mjs +46 -14
  422. package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
  423. package/src/session-runtime/mcp-glue.mjs +3 -0
  424. package/src/session-runtime/mcp-oauth-login.mjs +174 -0
  425. package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
  426. package/src/session-runtime/model-route/set-route.mjs +85 -17
  427. package/src/session-runtime/model-route/web-search-route.mjs +1 -1
  428. package/src/session-runtime/native-web-search/routes.mjs +22 -3
  429. package/src/session-runtime/native-web-search-usage.mjs +51 -0
  430. package/src/session-runtime/native-web-search.mjs +10 -1
  431. package/src/session-runtime/new-session-config.mjs +4 -2
  432. package/src/session-runtime/provider-auth/login.mjs +4 -0
  433. package/src/session-runtime/provider-models/model-meta.mjs +12 -3
  434. package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
  435. package/src/session-runtime/quick-web-search-models.mjs +13 -38
  436. package/src/session-runtime/resource-extension-sync.mjs +4 -0
  437. package/src/session-runtime/resource-mcp-api.mjs +98 -2
  438. package/src/session-runtime/resource-scope-decorate.mjs +10 -3
  439. package/src/session-runtime/runtime-review-api.mjs +21 -2
  440. package/src/session-runtime/services/agent-tool/index-write-queue.mjs +81 -20
  441. package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
  442. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
  443. package/src/session-runtime/services/agent-tool/lead-worker-index/forgotten-leads.mjs +29 -0
  444. package/src/session-runtime/services/agent-tool/lead-worker-index/index-file.mjs +1 -1
  445. package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +73 -7
  446. package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
  447. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
  448. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
  449. package/src/session-runtime/services/agent-tool/tag-registry.mjs +15 -1
  450. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
  451. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
  452. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +10 -1
  453. package/src/session-runtime/services/agent-tool/worker-index.mjs +46 -7
  454. package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
  455. package/src/session-runtime/services/agent-tool.mjs +3 -0
  456. package/src/session-runtime/services/channel-admin.mjs +19 -3
  457. package/src/session-runtime/services/memory-runtime-proxy/calls.mjs +4 -0
  458. package/src/session-runtime/services/memory-runtime-proxy.mjs +1 -0
  459. package/src/session-runtime/services/provider-admin.mjs +81 -15
  460. package/src/session-runtime/session-transcript-purge.mjs +32 -0
  461. package/src/session-runtime/session-turn-api.mjs +14 -5
  462. package/src/session-runtime/settings-builtin-tools-api.mjs +9 -0
  463. package/src/session-runtime/settings-compaction-api.mjs +1 -0
  464. package/src/session-runtime/setup-tool/executor.mjs +36 -6
  465. package/src/session-runtime/setup-tool/extended-actions.mjs +86 -6
  466. package/src/session-runtime/setup-tool/settings-contract.mjs +13 -3
  467. package/src/session-runtime/setup-tool/tool-defs.mjs +13 -4
  468. package/src/session-runtime/skills-api.mjs +3 -5
  469. package/src/session-runtime/tool-surface.mjs +5 -1
  470. package/src/session-runtime/turn/compaction-gate.mjs +25 -0
  471. package/src/session-runtime/turn/session-ops.mjs +7 -5
  472. package/src/session-runtime/turn/side-question-ops.mjs +22 -0
  473. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +5 -1
  474. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +8 -3
  475. package/src/session-runtime/turn/turn-run/turn-settle.mjs +10 -0
  476. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  477. package/src/session-runtime/turn/turn-run.mjs +25 -9
  478. package/src/session-runtime/usage-stats-api.mjs +43 -11
  479. package/src/session-runtime/workspace/project-branches.mjs +7 -5
  480. package/src/session-runtime/workspace/transition-steps.mjs +129 -31
  481. package/src/session-runtime/workspace/transitions.mjs +190 -15
  482. package/src/standalone/daemon-agent-control.mjs +9 -1
  483. package/src/standalone/daemon-drain-policy.mjs +8 -0
  484. package/src/standalone/daemon-local-session-bridge.mjs +14 -1
  485. package/src/standalone/daemon-owner.mjs +17 -4
  486. package/src/standalone/daemon-pg-stop.mjs +51 -0
  487. package/src/standalone/daemon-stop-budget.d.mts +2 -0
  488. package/src/standalone/daemon-stop-budget.mjs +18 -0
  489. package/src/standalone/daemon-stored-session-views.mjs +2 -0
  490. package/src/standalone/daemon-telemetry.mjs +74 -0
  491. package/src/standalone/daemon.mjs +64 -8
  492. package/src/standalone/desktop-service-registry.mjs +5 -0
  493. package/src/standalone/local-session-runtime.mjs +70 -19
  494. package/src/standalone/session-client.mjs +42 -0
  495. package/src/standalone/session-protocol.mjs +14 -0
  496. package/src/standalone/session-proxy/projection.mjs +41 -4
  497. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +15 -1
  498. package/src/standalone/session-runtime-inline-host.mjs +1 -0
  499. package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
  500. package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
  501. package/src/standalone/session-service/agent-tree.mjs +12 -0
  502. package/src/standalone/session-service/quota-resume.mjs +8 -0
  503. package/src/standalone/session-service/retention/busy.mjs +15 -1
  504. package/src/standalone/session-service/session-calls/session-action.mjs +18 -3
  505. package/src/standalone/session-service-api.mjs +11 -1
  506. package/src/standalone/session-service.mjs +1 -0
  507. package/src/standalone/session-state-patch.d.mts +2 -1
  508. package/src/standalone/session-state-patch.mjs +41 -1
  509. package/src/tui/App.jsx +25 -0
  510. package/src/tui/app/app-view/floating-panel.jsx +26 -1
  511. package/src/tui/app/app-view.jsx +1 -0
  512. package/src/tui/app/btw-controller.mjs +118 -0
  513. package/src/tui/app/create-app-pickers.mjs +6 -0
  514. package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
  515. package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
  516. package/src/tui/app/extension-pickers.mjs +2 -1
  517. package/src/tui/app/model-options.mjs +11 -16
  518. package/src/tui/app/shell-layout/row-budgets.mjs +11 -1
  519. package/src/tui/app/shell-layout/surfaces.mjs +3 -1
  520. package/src/tui/app/slash-commands.mjs +20 -5
  521. package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
  522. package/src/tui/app/slash-dispatch/session-commands.mjs +19 -6
  523. package/src/tui/app/use-global-key-input/approval-shortcut.mjs +12 -0
  524. package/src/tui/app/use-global-key-input.mjs +5 -12
  525. package/src/tui/app/use-prompt-handlers.mjs +11 -0
  526. package/src/tui/components/BtwPanel.jsx +53 -0
  527. package/src/tui/components/QuestionPanel.jsx +275 -0
  528. package/src/tui/components/StatusLine.jsx +2 -0
  529. package/src/tui/components/question-panel-model.mjs +156 -0
  530. package/src/tui/components/status-line/local-line.mjs +5 -2
  531. package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
  532. package/src/tui/dist/index.mjs +3221 -1500
  533. package/src/tui/session/context-state.mjs +4 -2
  534. package/src/tui/session/quota-resume.mjs +46 -14
  535. package/src/tui/session/session-api/commands.mjs +43 -24
  536. package/src/tui/session/session-api/controls.mjs +2 -2
  537. package/src/tui/session/session-api/extensions.mjs +22 -0
  538. package/src/tui/session/session-api/integrations.mjs +16 -2
  539. package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
  540. package/src/tui/session/session-api/lifecycle.mjs +7 -2
  541. package/src/tui/session/session-api/routes.mjs +71 -7
  542. package/src/tui/session/session-api/settings.mjs +11 -14
  543. package/src/tui/session/session-api.mjs +1 -0
  544. package/src/tui/session/session-flow/drain.mjs +7 -1
  545. package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
  546. package/src/tui/session/tool-approval.mjs +10 -7
  547. package/src/tui/session/transcript-spill.mjs +14 -7
  548. package/src/tui/session/turn-stream.mjs +174 -31
  549. package/src/tui/session/turn.mjs +46 -16
  550. package/src/tui/statusline-ansi-bridge.mjs +1 -1
  551. package/src/ui/statusline-agents.mjs +15 -4
  552. package/src/ui/statusline.mjs +7 -3
  553. package/src/ui/streaming-markdown-heal.mjs +34 -3
  554. package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
  555. package/src/workflows/default/WORKFLOW.md +3 -1
  556. package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
  557. package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
@@ -12,10 +12,18 @@
12
12
  * The owner marker disappears when the job settles and a `.done` marker keeps
13
13
  * the terminal JSON out of live counters. Terminal metadata remains available
14
14
  * for a bounded manual-recovery window.
15
+ *
16
+ * Ordering: a promoted job keeps its foreground id, so the foreground marker's
17
+ * retire and the promoted job's publish address the same files. Every publish,
18
+ * complete and retire for one jobId therefore runs on a per-job chain, strictly
19
+ * in call order and never overlapping. A record is written atomically (temp
20
+ * file + rename) and always before its owner marker, so a reader never sees a
21
+ * partial `.json`, and a marker never exists without its `.json`.
15
22
  */
16
23
  import { access, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
17
24
  import { homedir } from 'node:os';
18
25
  import { join } from 'node:path';
26
+ import { writeJsonAtomicAsync } from '../../../../../shared/atomic-file.mjs';
19
27
  import { isPidAlive } from '../../../../../shared/pid-liveness.mjs';
20
28
  import { displayShellCommand } from '../../../../../shared/shell-display.mjs';
21
29
 
@@ -24,12 +32,27 @@ import { displayShellCommand } from '../../../../../shared/shell-display.mjs';
24
32
  const ORPHAN_GRACE_MS = 60 * 60 * 1000;
25
33
  const COMPLETED_SHELL_JOB_TTL_MS = 30 * 60 * 1000;
26
34
 
27
- const publishing = new Map();
35
+ const chains = new Map();
28
36
  const paths = new Map();
29
37
  const records = new Map();
30
- const retired = new Set();
38
+ // jobId -> token of the live terminal record. Every publish/retire clears it and
39
+ // every completion replaces it, so a TTL timer armed for an older incarnation
40
+ // can tell its files are no longer the ones it was armed for.
41
+ const incarnations = new Map();
42
+ let incarnationSeq = 0;
31
43
  let sweepStarted = false;
32
44
 
45
+ /** Run `operation` after every earlier operation for `jobId` has finished. */
46
+ function enqueue(jobId, operation) {
47
+ const run = (chains.get(jobId) || Promise.resolve()).then(operation);
48
+ const tail = run.catch(() => {});
49
+ chains.set(jobId, tail);
50
+ void tail.then(() => {
51
+ if (chains.get(jobId) === tail) chains.delete(jobId);
52
+ });
53
+ return run;
54
+ }
55
+
33
56
  function dataDir() {
34
57
  return process.env.MIXDOG_DATA_DIR || join(process.env.MIXDOG_HOME || join(homedir(), '.mixdog'), 'data');
35
58
  }
@@ -140,9 +163,8 @@ export function publishShellJobRecord(
140
163
  const dir = jobsDir();
141
164
  const jsonPath = join(dir, `${jobId}.json`);
142
165
  const ownerPath = join(dir, `${jobId}.owner-${ownerPid}`);
143
- retired.delete(jobId);
144
166
  paths.set(jobId, [jsonPath, ownerPath]);
145
- const work = (async () => {
167
+ void enqueue(jobId, async () => {
146
168
  try {
147
169
  await mkdir(dir, { recursive: true });
148
170
  if (!sweepStarted) {
@@ -172,18 +194,13 @@ export function publishShellJobRecord(
172
194
  ...(foreground ? { foreground: true } : {}),
173
195
  };
174
196
  records.set(jobId, record);
175
- await writeFile(jsonPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
197
+ incarnations.delete(jobId);
198
+ // The record lands whole before the marker that advertises it.
199
+ await writeJsonAtomicAsync(jsonPath, record, { fsync: false });
176
200
  await writeFile(ownerPath, '', 'utf8');
177
201
  } catch {
178
202
  /* the record is presentation only; never fail a job for it */
179
203
  }
180
- // A job that settled while the write was in flight must not leave a
181
- // record behind.
182
- if (retired.has(jobId)) await removePaths([jsonPath, ownerPath]);
183
- })();
184
- publishing.set(jobId, work);
185
- void work.finally(() => {
186
- if (publishing.get(jobId) === work) publishing.delete(jobId);
187
204
  });
188
205
  }
189
206
 
@@ -196,11 +213,7 @@ export function completeShellJobRecord(jobId, detail) {
196
213
  const donePath = join(dir, `${key}.done`);
197
214
  const list = paths.get(key) || [jsonPath];
198
215
  const ownerPaths = list.filter((path) => path.includes('.owner-'));
199
- const pending = publishing.get(key);
200
- const work = (async () => {
201
- try {
202
- await pending;
203
- } catch {}
216
+ return enqueue(key, async () => {
204
217
  await mkdir(dir, { recursive: true });
205
218
  let base = records.get(key) || null;
206
219
  if (!base) {
@@ -210,19 +223,26 @@ export function completeShellJobRecord(jobId, detail) {
210
223
  }
211
224
  const record = terminalRecord(base, { ...detail, jobId: key });
212
225
  records.set(key, record);
213
- await writeFile(jsonPath, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
226
+ await writeJsonAtomicAsync(jsonPath, record, { fsync: false });
214
227
  await writeFile(donePath, '', 'utf8');
215
228
  await removePaths(ownerPaths);
216
229
  paths.set(key, [jsonPath, donePath]);
230
+ const incarnation = ++incarnationSeq;
231
+ incarnations.set(key, incarnation);
217
232
  const timer = setTimeout(() => {
218
- records.delete(key);
219
- paths.delete(key);
220
- void removePaths([jsonPath, donePath]);
233
+ // Expire through the job's queue and only if no publish, retire or newer
234
+ // completion has replaced the files this timer was armed for.
235
+ void enqueue(key, async () => {
236
+ if (incarnations.get(key) !== incarnation) return;
237
+ incarnations.delete(key);
238
+ records.delete(key);
239
+ paths.delete(key);
240
+ await removePaths([jsonPath, donePath]);
241
+ });
221
242
  }, COMPLETED_SHELL_JOB_TTL_MS);
222
243
  timer.unref?.();
223
244
  return true;
224
- })().catch(() => false);
225
- return work;
245
+ }).catch(() => false);
226
246
  }
227
247
 
228
248
  export async function readShellJobRecord(jobId) {
@@ -266,18 +286,13 @@ export async function listShellJobRecords() {
266
286
  export function retireShellJobRecord(jobId) {
267
287
  const key = String(jobId || '').trim();
268
288
  if (!key) return;
269
- retired.add(key);
270
- records.delete(key);
271
289
  const list = paths.get(key);
272
290
  paths.delete(key);
273
- const pending = publishing.get(key);
274
- void (async () => {
275
- try {
276
- await pending;
277
- } catch {
278
- /* the publish path owns its failure */
279
- }
291
+ // Queued behind every earlier publish and ahead of any later one: a promoted
292
+ // job publishing after this call is written only once this removal is done.
293
+ void enqueue(key, async () => {
294
+ records.delete(key);
295
+ incarnations.delete(key);
280
296
  await removePaths(list);
281
- retired.delete(key);
282
- })();
297
+ });
283
298
  }
@@ -41,7 +41,29 @@ function _posixStartupShell(candidates = ['/bin/bash', '/usr/bin/bash', '/bin/sh
41
41
  return null;
42
42
  }
43
43
 
44
- export function describeShellToolsStartupState({
44
+ // Every session composes this line. Probing it means up to names×PATHEXT×PATH
45
+ // stats on Windows (~40ms) or a login-shell spawn on POSIX, both on the event
46
+ // loop, so one answer per PATH/shell is reused for a short window — the same
47
+ // rule as gitExecutablePresent: a session opened after an install still picks
48
+ // the tool up once the window passes.
49
+ const SHELL_TOOLS_STARTUP_TTL_MS = 30_000;
50
+ let _shellToolsStartup = null;
51
+
52
+ export function describeShellToolsStartupState(options = {}) {
53
+ if (options._spawnSync) return _probeShellToolsStartupState(options);
54
+ const { platform = process.platform, names = SHELL_TOOL_STARTUP_NAMES, shellPath = null } = options;
55
+ const pathValue = options.pathValue ?? (process.env.PATH || '');
56
+ const key = JSON.stringify([platform, names, shellPath, pathValue]);
57
+ const now = Date.now();
58
+ if (_shellToolsStartup?.key === key && now - _shellToolsStartup.at < SHELL_TOOLS_STARTUP_TTL_MS) {
59
+ return _shellToolsStartup.value;
60
+ }
61
+ const value = _probeShellToolsStartupState(options);
62
+ _shellToolsStartup = { key, at: now, value };
63
+ return value;
64
+ }
65
+
66
+ function _probeShellToolsStartupState({
45
67
  platform = process.platform,
46
68
  names = SHELL_TOOL_STARTUP_NAMES,
47
69
  shellPath = null,
@@ -136,7 +136,7 @@ export function resolveDirectoryRoot(name, effectiveCwd, { filesystemRootCwd })
136
136
  `${_PROJECT_ROOT_SENTINELS.join('/')} at it or any ancestor)` +
137
137
  `${childRoots.length > 1 ? ` and holds ${childRoots.length} separate project roots (${listed})` : ''}. ` +
138
138
  `${boundaryRoot ? `The nearest sentinel is at '${boundaryRoot}' (home/temp), which is never auto-adopted. ` : ''}` +
139
- `Refusing to index an arbitrary tree. Run 'cwd set <repo>', or pass an explicit ` +
139
+ `Refusing to index an arbitrary tree. Switch Project with the session tool (set-project), or pass an explicit ` +
140
140
  `'cwd' (repo root) or a 'file' anchor.`
141
141
  );
142
142
  }
@@ -854,6 +854,6 @@ export function _augmentNoHitDiagnostic(result, emptyToken, graph, cwd, symbol)
854
854
  }
855
855
  return (
856
856
  `${emptyToken}\n# '${symbol}' not present in graph rooted at ${cwd} (${n} files indexed${trunc}). ` +
857
- `If it should exist, the target is likely outside this cwd — pass an explicit 'cwd' (repo root) or 'file' anchor, or run 'cwd set <repo>'.`
857
+ `If it should exist, the target is likely outside this cwd — pass an explicit 'cwd' (repo root) or 'file' anchor, or switch Project with the session tool (set-project).`
858
858
  );
859
859
  }
@@ -1,8 +1,9 @@
1
1
  // UI-diff side channel: a committed apply_patch (or edit-tool write) records
2
2
  // a turn-review change entry and a per-call diff the desktop/TUI tool cards
3
3
  // pick up. Display only — never affects edit semantics.
4
- import { createTwoFilesPatch } from 'diff';
4
+ import { createLineDiffBudget, lineDiffHunks, unifiedHunksBody } from '../../../../shared/bounded-line-diff.mjs';
5
5
  import { boundReviewPatch } from '../../../../shared/review-diff.mjs';
6
+ import { decodeText } from '../../../../shared/turn-changes.mjs';
6
7
  import { recordTurnDiffChanges } from '../../../../shared/turn-snapshot.mjs';
7
8
  import { pathKey, resolveV4AEntryPath } from './paths.mjs';
8
9
  import { patchHeaderPathForResolved } from './read-redirects.mjs';
@@ -38,31 +39,21 @@ function headerPath(value) {
38
39
  .replace(/\\/g, '/');
39
40
  }
40
41
 
41
- const DIFF_TIMEOUT_MS = 100;
42
-
43
- const utf8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true });
44
-
45
42
  function isOmitted(value) {
46
43
  return Boolean(value) && typeof value === 'object' && !(value instanceof Uint8Array) && value.omitted === true;
47
44
  }
48
45
 
49
- // Text of one side: '' for an absent file, null when it is not UTF-8 text.
46
+ // Text of one side: '' for an absent file, null when it is binary (not UTF-8
47
+ // or holding a NUL byte) — the same rule as the turn review's tracker diffs.
50
48
  function sideText(value) {
51
- if (value == null) return '';
52
- if (typeof value === 'string') return value;
53
- if (value instanceof Uint8Array) {
54
- try {
55
- return utf8.decode(value);
56
- } catch {
57
- return null;
58
- }
59
- }
49
+ if (value == null || typeof value === 'string' || value instanceof Uint8Array) return decodeText(value);
60
50
  return null;
61
51
  }
62
52
 
63
53
  // One file's change, from the call's own before and after states: absent
64
54
  // (null), bytes or text, or an omitted marker when the bytes were not kept.
65
- function renderCallChange({ displayPath, newDisplayPath, before, after }) {
55
+ // `budget` is the call's line-diff budget.
56
+ function renderCallChange({ displayPath, newDisplayPath, before, after }, budget) {
66
57
  const oldName = headerPath(displayPath) || 'unknown file';
67
58
  const newName = headerPath(newDisplayPath) || oldName;
68
59
  const oldHeader = before == null ? '/dev/null' : `a/${oldName}`;
@@ -82,23 +73,46 @@ function renderCallChange({ displayPath, newDisplayPath, before, after }) {
82
73
  return oldName === newName ? '' : `${head}similarity index 100%\nrename from ${oldName}\nrename to ${newName}\n`;
83
74
  }
84
75
  const rename = oldName === newName ? '' : `rename from ${oldName}\nrename to ${newName}\n`;
85
- try {
86
- const generated = createTwoFilesPatch(oldHeader, newHeader, oldText, newText, '', '', {
87
- context: 3,
88
- timeout: DIFF_TIMEOUT_MS,
89
- });
90
- const start = generated.search(/^--- /m);
91
- if (start >= 0) return `${head}${rename}${generated.slice(start).replace(/\n*$/, '\n')}`;
92
- } catch {
93
- // A pathological diff timing out must not stall tool completion.
76
+ const { hunks } = lineDiffHunks(oldText, newText, budget, [before ?? null, after ?? null]);
77
+ return `${head}${rename}${unifiedHunksBody(oldHeader, newHeader, hunks)}`;
78
+ }
79
+
80
+ // Added/deleted lines of one rendered change, counted in its hunks only.
81
+ function hunkLineCounts(patch) {
82
+ let additions = 0;
83
+ let deletions = 0;
84
+ let inHunk = false;
85
+ for (const line of patch.split('\n')) {
86
+ if (line.startsWith('@@')) inHunk = true;
87
+ else if (inHunk && line.startsWith('+')) additions += 1;
88
+ else if (inHunk && line.startsWith('-')) deletions += 1;
94
89
  }
95
- return `${head}${rename}Binary files ${oldHeader} and ${newHeader} differ\n`;
90
+ return { additions, deletions };
91
+ }
92
+
93
+ // Each change with the line counts of its own unbounded diff (the turn review
94
+ // sums them per file), plus the call's whole diff, within the call's budget.
95
+ export function measureCallChanges(changes, budget = createLineDiffBudget()) {
96
+ const patches = (changes || []).map((change) => renderCallChange(change, budget));
97
+ return {
98
+ diff: patches.join(''),
99
+ changes: (changes || []).map((change, index) => ({ ...change, ...hunkLineCounts(patches[index]) })),
100
+ };
96
101
  }
97
102
 
98
103
  // A call's card shows the change that call made — its own before against its
99
104
  // own after — never the turn's running total for the same files.
100
105
  export function callUiDiff(changes) {
101
- return (changes || []).map(renderCallChange).join('');
106
+ return measureCallChanges(changes).diff;
107
+ }
108
+
109
+ // Fold the call's measured changes into the turn review and publish its card
110
+ // diff. Both share one line-diff budget: the call is one operation.
111
+ function recordCallChanges(callId, sessionId, changes) {
112
+ const budget = createLineDiffBudget();
113
+ const measured = measureCallChanges(changes, budget);
114
+ recordTurnDiffChanges(sessionId, measured.changes, budget);
115
+ registerApplyPatchUiDiff(callId, measured.diff);
102
116
  }
103
117
 
104
118
  // Edit-tool UI diff: a successful single-file str-replace edit records the
@@ -108,21 +122,42 @@ export function callUiDiff(changes) {
108
122
  export function registerEditToolUiDiff({ callId, sessionId, basePath, fullPath, before, after }) {
109
123
  if (!callId || !sessionId || typeof fullPath !== 'string') return;
110
124
  try {
111
- const changes = [
112
- {
113
- path: fullPath,
114
- displayPath: patchHeaderPathForResolved(basePath || '', fullPath),
115
- before: before ?? null,
116
- after: after ?? null,
117
- },
118
- ];
119
- recordTurnDiffChanges(sessionId, changes);
120
- registerApplyPatchUiDiff(callId, callUiDiff(changes));
125
+ recordCallChanges(callId, sessionId, [toolFileChange(basePath, { fullPath, before, after })]);
121
126
  } catch {
122
127
  // best-effort display channel
123
128
  }
124
129
  }
125
130
 
131
+ function toolFileChange(basePath, { fullPath, before, after }) {
132
+ return {
133
+ path: fullPath,
134
+ displayPath: patchHeaderPathForResolved(basePath || '', fullPath),
135
+ before: before ?? null,
136
+ after: after ?? null,
137
+ };
138
+ }
139
+
140
+ // Writes of a tool without a per-call card (tidy, office): each change
141
+ // `{ fullPath, before, after }` (null = absent, bytes, or an omitted marker)
142
+ // joins the turn review with its own line counts, like an edit-tool write.
143
+ // Side channel only — never affects the tool result.
144
+ export function recordToolFileChanges({ sessionId, basePath, changes }) {
145
+ if (!sessionId || !changes?.length) return;
146
+ try {
147
+ const budget = createLineDiffBudget();
148
+ recordTurnDiffChanges(
149
+ sessionId,
150
+ measureCallChanges(
151
+ changes.map((change) => toolFileChange(basePath, change)),
152
+ budget
153
+ ).changes,
154
+ budget
155
+ );
156
+ } catch {
157
+ // best-effort review channel
158
+ }
159
+ }
160
+
126
161
  // Review content of one snapshot: null when absent, the bytes when captured,
127
162
  // and an explicit omitted marker (never empty bytes) when they were not.
128
163
  function snapshotContent(snapshot) {
@@ -184,8 +219,7 @@ export function registerCommittedPatchUiDiff({
184
219
  after: snapshotContent(after),
185
220
  });
186
221
  }
187
- recordTurnDiffChanges(sessionId, changes);
188
- registerApplyPatchUiDiff(callId, callUiDiff(changes));
222
+ recordCallChanges(callId, sessionId, changes);
189
223
  } catch {
190
224
  // Review collection is a side channel and must never affect committed
191
225
  // apply_patch success/failure semantics.
@@ -105,8 +105,25 @@ export function formatToolStartProgress(name, args = {}) {
105
105
  // ── host_input / cwd ─────────────────────────────────────────────
106
106
  case 'inject_input':
107
107
  return 'injecting input';
108
- case 'cwd':
108
+ case 'cwd': // legacy: old transcripts
109
109
  return a.action === 'set' ? 'setting cwd' : 'resolving cwd';
110
+ case 'session':
111
+ switch (a.action) {
112
+ case 'set-project':
113
+ return 'setting project';
114
+ case 'rename':
115
+ return 'renaming session';
116
+ case 'compact':
117
+ return 'scheduling compaction';
118
+ case 'fork':
119
+ return 'forking into a new session';
120
+ case 'favorite':
121
+ return a.value === false ? 'unfavoriting session' : 'favoriting session';
122
+ case 'archive':
123
+ return a.value === false ? 'unarchiving session' : 'archiving session';
124
+ default:
125
+ return 'checking session';
126
+ }
110
127
 
111
128
  default:
112
129
  return `running ${name}`;
@@ -27,9 +27,9 @@ export {
27
27
  } from './shell-powershell.mjs';
28
28
  export { stripAnsi, ExecResult } from './shell-exec-output.mjs';
29
29
 
30
- async function _execPolicyBlockMessage(command) {
30
+ async function _execPolicyBlockMessage(command, elevationApproved = false) {
31
31
  const { checkExecPolicyMessage } = await import('./bash-policy-scan.mjs');
32
- return checkExecPolicyMessage(command);
32
+ return checkExecPolicyMessage(command, { elevationApproved });
33
33
  }
34
34
 
35
35
  // Admission-wait ceiling. Without it a saturated shell lane (all leases held
@@ -233,7 +233,7 @@ const failedResult = (run, stderr, failureReason) =>
233
233
  // Admission lease, policy preflight, then the spawn. Resolves the run with a
234
234
  // tool-phase failure and returns false when the command never reaches a shell.
235
235
  async function admitAndSpawn(run, params, { admissionWaitMs, onSaturated }) {
236
- const { admission, command, ownerSessionId, abortSignal } = params;
236
+ const { admission, command, ownerSessionId, abortSignal, elevationApproved } = params;
237
237
  try {
238
238
  run.resourceLease = await acquireShellLeaseBounded(admission, {
239
239
  abortSignal,
@@ -241,7 +241,7 @@ async function admitAndSpawn(run, params, { admissionWaitMs, onSaturated }) {
241
241
  label: `[task_id: ${run.foregroundRecordId}] ${String(command || '').slice(0, 120)}`,
242
242
  ownerKey: ownerSessionId,
243
243
  });
244
- const policyErr = await _execPolicyBlockMessage(command);
244
+ const policyErr = await _execPolicyBlockMessage(command, elevationApproved === true);
245
245
  if (policyErr) {
246
246
  await releaseResourceLease(run);
247
247
  run.resolveResult(failedResult(run, policyErr, 'preflight failed'));
@@ -544,6 +544,8 @@ export function execShellCommand(options) {
544
544
  // hands the script to the shell out-of-band — see the POSIX env+eval path in
545
545
  // bash-tool.mjs, which keeps the command body out of the child's argv.
546
546
  execScript = null,
547
+ // The user approved this call's elevation 'ask' decision (runtime-set).
548
+ elevationApproved = false,
547
549
  } = options;
548
550
  return new Promise(async (resolve) => {
549
551
  const run = createShellRun({ abortSignal, resolve, jobId });
@@ -565,6 +567,7 @@ export function execShellCommand(options) {
565
567
  abortSignal,
566
568
  directArgv,
567
569
  execScript,
570
+ elevationApproved,
568
571
  ownerSessionId,
569
572
  clientHostPid,
570
573
  },
@@ -2,7 +2,7 @@ import { extractHeredocBodies, extractShellCInner, stripQuotedAndHeredoc } from
2
2
  import { extractPowerShellCommandInner } from './shell-powershell.mjs';
3
3
  import { decodePowerShellEncodedCommand, isBlockedCommand, WRAPPER_NAMES } from './shell-policy.mjs';
4
4
 
5
- /** @typedef {'allow'|'deny'} ExecPolicyDecision */
5
+ /** @typedef {'allow'|'ask'|'deny'} ExecPolicyDecision */
6
6
 
7
7
  const EXEC_POLICY_DENY_PATTERNS = [
8
8
  /\b(curl|wget|fetch|Invoke-WebRequest|iwr)\b[^\n|&;]*\|[^\n|&;]*\b(sh|bash|zsh|dash|pwsh|powershell)(?:\.exe)?\b/i,
@@ -16,9 +16,28 @@ const EXEC_POLICY_DENY_PATTERNS = [
16
16
  // lookahead instead of the previous bare `iex\s+`.
17
17
  /\|\s*iex\b/i,
18
18
  /\biex\s+[$("'@]/i,
19
- /\bStart-Process\b[^\n]*\s-Verb\s+RunAs\b/i,
20
19
  ];
21
20
 
21
+ // Elevated launch: `Start-Process` (or its aliases `saps` / `start`) with
22
+ // `-Verb RunAs`. Not a hard deny — the user is asked (parity with Claude Code's
23
+ // "Command requests elevated privileges"). `-Verb` may be abbreviated to any
24
+ // prefix (`-v`, `-ve`, `-ver`) and take its value after a space or a colon.
25
+ // `Start-Process` matches anywhere (as the former deny rule did); the short
26
+ // aliases only at a command position so `npm start -v RunAs` stays allowed.
27
+ const ELEVATION_ASK_PATTERN =
28
+ /(?:\bStart-Process\b|(?:^|[;&|({=\n])\s*(?:&\s*)?(?:saps|start)(?![\w-]))[^\n]*?\s-v(?:e(?:rb?)?)?(?:\s*:\s*|\s+)RunAs\b/i;
29
+ const ELEVATION_ASK_REASON = 'Command requests elevated privileges';
30
+
31
+ // PowerShell spellings of the same token that the quote-stripping pass would
32
+ // otherwise hide or split: backtick line continuation, backtick-escaped
33
+ // letters (`-V`erb`), and a quoted `-Verb` value (`-Verb:'RunAs'`).
34
+ function _normalizeElevationSyntax(text) {
35
+ return String(text || '')
36
+ .replace(/`\r?\n/g, ' ')
37
+ .replace(/`([A-Za-z])/g, '$1')
38
+ .replace(/(\s-v\w*(?:\s*:\s*|\s+))(['"])\s*RunAs\s*\2/gi, '$1RunAs');
39
+ }
40
+
22
41
  // Verbs with no non-destructive invocation. `dd` is deliberately ABSENT: the
23
42
  // disk-destroying form (`of=/dev/<disk>`) is hard-blocked at every command
24
43
  // position by shell-policy, while `dd if=/dev/zero of=file` is the ordinary
@@ -39,12 +58,44 @@ const EXEC_POLICY_DENY_COMMANDS = new Set([
39
58
  'parted',
40
59
  ]);
41
60
 
42
- const _POLICY_RANK = { allow: 0, deny: 1 };
61
+ const _POLICY_RANK = { allow: 0, ask: 1, deny: 2 };
43
62
 
44
63
  // EVERY command position, not just the first: a deny-listed verb after `&&`,
45
64
  // `;`, `|` or a newline is still an execution request, and reading only
46
65
  // segment 0 let `true && shutdown /s` through. Callers pass quote-stripped
47
66
  // text, so prose inside a string ("graceful shutdown") never reaches here.
67
+ const _ELEVATION_WRAPPERS = new Set(['sudo', 'doas', 'pkexec', 'gsudo']);
68
+ const _bareName = (token) =>
69
+ token
70
+ .replace(/^.*[\\/]/, '')
71
+ .toLowerCase()
72
+ .replace(/\.(exe|cmd|bat|com)$/i, '');
73
+
74
+ // Returns the first privilege-escalation wrapper (sudo, doas, pkexec, gsudo,
75
+ // with or without path/extension) found at any command position, else null.
76
+ function _elevationWrapperAtPositions(command) {
77
+ for (const seg of String(command || '').split(/[;&|\n]+/)) {
78
+ const tokens = seg.trim().split(/\s+/).filter(Boolean);
79
+ let i = 0;
80
+ while (i < tokens.length) {
81
+ const t = tokens[i];
82
+ if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(t)) {
83
+ i++;
84
+ continue;
85
+ }
86
+ const base = _bareName(t);
87
+ if (_ELEVATION_WRAPPERS.has(base)) return base;
88
+ if (WRAPPER_NAMES.has(base)) {
89
+ i++;
90
+ while (i < tokens.length && (/^[-+]/.test(tokens[i]) || /^\d+[smhd]?$/.test(tokens[i]))) i++;
91
+ continue;
92
+ }
93
+ break;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+
48
99
  function _commandNamesAtPositions(command) {
49
100
  const names = [];
50
101
  for (const seg of String(command || '').split(/[;&|\n]+/)) {
@@ -56,7 +107,7 @@ function _commandNamesAtPositions(command) {
56
107
  i++;
57
108
  continue;
58
109
  }
59
- if (WRAPPER_NAMES.has(t.toLowerCase())) {
110
+ if (WRAPPER_NAMES.has(_bareName(t))) {
60
111
  i++;
61
112
  while (i < tokens.length && (/^[-+]/.test(tokens[i]) || /^\d+[smhd]?$/.test(tokens[i]))) i++;
62
113
  continue;
@@ -74,14 +125,15 @@ function classifyExecPolicy(command) {
74
125
  if (!text.trim()) return { decision: 'allow', reason: '' };
75
126
  // Scan executable syntax, not quoted log/search text. Recursively preserve
76
127
  // actual shell, PowerShell, heredoc, and encoded-command payload coverage.
77
- const executableTargets = [
78
- stripQuotedAndHeredoc(text),
79
- ...extractShellCInner(text).map(stripQuotedAndHeredoc),
80
- ...extractPowerShellCommandInner(text).map(stripQuotedAndHeredoc),
81
- ...extractHeredocBodies(text).map(stripQuotedAndHeredoc),
128
+ const rawTargets = [
129
+ text,
130
+ ...extractShellCInner(text),
131
+ ...extractPowerShellCommandInner(text),
132
+ ...extractHeredocBodies(text),
82
133
  ];
83
134
  const decodedPowerShell = decodePowerShellEncodedCommand(text);
84
- if (decodedPowerShell) executableTargets.push(stripQuotedAndHeredoc(decodedPowerShell));
135
+ if (decodedPowerShell) rawTargets.push(decodedPowerShell);
136
+ const executableTargets = rawTargets.map(stripQuotedAndHeredoc);
85
137
  if (executableTargets.some((target) => isBlockedCommand(target))) {
86
138
  return {
87
139
  decision: 'deny',
@@ -115,6 +167,24 @@ function classifyExecPolicy(command) {
115
167
  };
116
168
  }
117
169
  }
170
+ // Elevation asks only after every deny rule above has had its say.
171
+ for (const target of rawTargets) {
172
+ const hit = ELEVATION_ASK_PATTERN.exec(stripQuotedAndHeredoc(_normalizeElevationSyntax(target)));
173
+ if (hit) {
174
+ return {
175
+ decision: 'ask',
176
+ reason: ELEVATION_ASK_REASON,
177
+ rule: 'elevation',
178
+ match: hit[0].trim().slice(0, 80),
179
+ };
180
+ }
181
+ }
182
+ for (const target of executableTargets) {
183
+ const wrapper = _elevationWrapperAtPositions(target);
184
+ if (wrapper) {
185
+ return { decision: 'ask', reason: ELEVATION_ASK_REASON, rule: 'elevation', match: wrapper };
186
+ }
187
+ }
118
188
  // Destructive-but-legitimate commands are NOT judged here. The caller
119
189
  // computes that non-blocking warning once (getDedupedDestructiveWarnings)
120
190
  // and prepends it to the result; deriving it again per scan target only
@@ -140,9 +210,15 @@ export function evaluateExecPolicyFromTargets(targets) {
140
210
  return worst;
141
211
  }
142
212
 
143
- /** Pre-spawn block message — deny is the only decision this scan produces. */
144
- export function formatExecPolicyBlockMessage(policyResult) {
213
+ /** Pre-spawn block message. An 'ask' decision blocks unless the runtime
214
+ * already obtained the user's approval for this call (`elevationApproved`). */
215
+ export function formatExecPolicyBlockMessage(policyResult, { elevationApproved = false } = {}) {
145
216
  const r = policyResult || { decision: 'allow' };
217
+ if (r.decision === 'ask') {
218
+ if (elevationApproved === true) return null;
219
+ const rule = r.rule ? ` [rule ${r.rule}${r.match ? `: "${r.match}"` : ''}]` : '';
220
+ return `Error: command requires user approval for elevation — ${r.reason || ELEVATION_ASK_REASON}${rule}; no approval was granted for this call`;
221
+ }
146
222
  if (r.decision === 'deny') {
147
223
  const rule = r.rule ? ` [rule ${r.rule}${r.match ? `: "${r.match}"` : ''}]` : '';
148
224
  const alt = r.alternative ? `; instead: ${r.alternative}` : '';
@@ -17,6 +17,8 @@ export const WRAPPER_NAMES = new Set([
17
17
  'env',
18
18
  'sudo',
19
19
  'doas',
20
+ 'pkexec',
21
+ 'gsudo',
20
22
  'nice',
21
23
  'stdbuf',
22
24
  'chronic',