mixdog 1.3.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +2 -2
  3. package/package.json +2 -11
  4. package/scripts/daemon-stub.mjs +1 -1
  5. package/scripts/gate-local.mjs +4 -2
  6. package/scripts/native-release-status.mjs +2 -2
  7. package/scripts/recall-history-cases.json +1 -1
  8. package/scripts/suite-health.mjs +10 -1
  9. package/scripts/test.mjs +7 -11
  10. package/src/agents/reviewer/AGENT.md +7 -11
  11. package/src/agents/reviewer/agent.json +1 -1
  12. package/src/defaults/shipped-history.json +17 -1
  13. package/src/defaults/skills/browser-use/SKILL.md +59 -11
  14. package/src/defaults/skills/code-tidy/SKILL.md +142 -270
  15. package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
  16. package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
  17. package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
  18. package/src/defaults/skills/computer-use/SKILL.md +33 -9
  19. package/src/defaults/skills/setup/SKILL.md +22 -0
  20. package/src/defaults/skills/setup/references/actions.md +39 -5
  21. package/src/defaults/skills/setup/references/surfaces.md +11 -2
  22. package/src/defaults/skills/visualize/SKILL.md +2 -1
  23. package/src/lib/rules-builder.cjs +10 -8
  24. package/src/rules/agent/AGENT.md +6 -6
  25. package/src/rules/lead/LEAD.md +18 -20
  26. package/src/rules/shared/05-tool-calls.md +5 -6
  27. package/src/rules/shared/10-tool-workflow.md +2 -0
  28. package/src/rules/shared/60-verification.md +7 -5
  29. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
  30. package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
  31. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
  32. package/src/runtime/agent/orchestrator/config.mjs +17 -1
  33. package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
  35. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
  36. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
  37. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
  38. package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
  39. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
  40. package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
  41. package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
  42. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  43. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
  44. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
  45. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
  46. package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
  47. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
  48. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
  49. package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
  50. package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
  51. package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
  52. package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
  53. package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
  54. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
  55. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
  56. package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
  57. package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
  58. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
  59. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
  62. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
  64. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
  65. package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
  68. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
  69. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
  70. package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
  71. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
  72. package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
  73. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
  75. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
  76. package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
  77. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
  78. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
  79. package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
  80. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
  81. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
  82. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
  83. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
  84. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
  85. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
  86. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
  87. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
  88. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
  89. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
  90. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
  91. package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
  92. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
  93. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
  94. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
  95. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
  97. package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
  98. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
  99. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
  100. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
  101. package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
  102. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
  103. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
  104. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
  105. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
  106. package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
  107. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
  108. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
  109. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
  110. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
  111. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
  112. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
  113. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
  114. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
  115. package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
  116. package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
  117. package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
  118. package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
  119. package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
  120. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
  121. package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
  122. package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
  123. package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
  124. package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
  125. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
  126. package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
  127. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
  128. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
  129. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
  130. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
  131. package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
  132. package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
  133. package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
  134. package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
  135. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
  136. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
  137. package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
  138. package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
  139. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
  140. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
  141. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
  142. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
  143. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
  144. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
  145. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
  146. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
  147. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
  148. package/src/runtime/browser-bridge/action-schema.mjs +13 -2
  149. package/src/runtime/browser-bridge/client.mjs +73 -8
  150. package/src/runtime/browser-bridge/input-fields.mjs +8 -9
  151. package/src/runtime/browser-bridge/mail-links.mjs +107 -0
  152. package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
  153. package/src/runtime/channels/lib/config.mjs +6 -61
  154. package/src/runtime/channels/lib/crash-log.mjs +1 -1
  155. package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
  156. package/src/runtime/channels/lib/event-queue.mjs +3 -18
  157. package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
  158. package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
  159. package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
  160. package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
  161. package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
  162. package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
  163. package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
  164. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  165. package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
  166. package/src/runtime/channels/lib/scheduler.mjs +33 -147
  167. package/src/runtime/channels/lib/schedules-db.mjs +34 -11
  168. package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
  169. package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
  170. package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
  171. package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
  172. package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
  173. package/src/runtime/channels/lib/worker-main.mjs +10 -34
  174. package/src/runtime/computer-bridge/action-schema.mjs +42 -1
  175. package/src/runtime/computer-bridge/actions.mjs +9 -1
  176. package/src/runtime/computer-bridge/client.mjs +180 -8
  177. package/src/runtime/computer-bridge/core-actions.mjs +2 -2
  178. package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
  179. package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
  180. package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
  181. package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
  182. package/src/runtime/connectors/gmail-app-password.mjs +172 -39
  183. package/src/runtime/connectors/google-mcp.mjs +10 -12
  184. package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
  185. package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
  186. package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
  187. package/src/runtime/session-search/session-search-db.mjs +73 -9
  188. package/src/runtime/session-search/session-search-ingest.mjs +110 -20
  189. package/src/runtime/shared/abort-race.mjs +89 -0
  190. package/src/runtime/shared/agent-avatar.d.mts +0 -1
  191. package/src/runtime/shared/agent-kind.mjs +4 -3
  192. package/src/runtime/shared/bounded-download.mjs +8 -3
  193. package/src/runtime/shared/config.mjs +2 -13
  194. package/src/runtime/shared/connector-accounts.mjs +20 -10
  195. package/src/runtime/shared/connector-catalog.d.mts +29 -0
  196. package/src/runtime/shared/connector-catalog.mjs +150 -0
  197. package/src/runtime/shared/connector-triggers.mjs +22 -19
  198. package/src/runtime/shared/defaults-separation.mjs +2 -14
  199. package/src/runtime/shared/delegated-approval-host.mjs +103 -11
  200. package/src/runtime/shared/err-text.mjs +24 -15
  201. package/src/runtime/shared/file-permissions.d.mts +0 -8
  202. package/src/runtime/shared/file-permissions.mjs +0 -11
  203. package/src/runtime/shared/git-direct.test-support.mjs +19 -0
  204. package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
  205. package/src/runtime/shared/heartbeat-interval.mjs +19 -0
  206. package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
  207. package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
  208. package/src/runtime/shared/mixy-presence.mjs +22 -0
  209. package/src/runtime/shared/orchestration.d.mts +2 -0
  210. package/src/runtime/shared/pg-schema-db.mjs +27 -16
  211. package/src/runtime/shared/profile-config.cjs +1 -1
  212. package/src/runtime/shared/prompt-origin.d.mts +4 -0
  213. package/src/runtime/shared/prompt-origin.mjs +21 -0
  214. package/src/runtime/shared/provider-accounts.mjs +7 -1
  215. package/src/runtime/shared/provider-display-name.mjs +29 -0
  216. package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
  217. package/src/runtime/shared/routine-trigger.d.mts +2 -0
  218. package/src/runtime/shared/routine-trigger.mjs +10 -0
  219. package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
  220. package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
  221. package/src/runtime/shared/session-activity-signal.mjs +34 -1
  222. package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
  223. package/src/runtime/shared/session-sidebar-host.mjs +7 -3
  224. package/src/runtime/shared/session-turn-prelude.mjs +18 -0
  225. package/src/runtime/shared/tool-result-summary.mjs +1 -14
  226. package/src/runtime/shared/worktrees/test-support.mjs +9 -4
  227. package/src/runtime/terminal-bridge/client.mjs +10 -1
  228. package/src/runtime/visualize-bridge/client.mjs +27 -8
  229. package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
  230. package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
  231. package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
  232. package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
  233. package/src/session-runtime/agent-home-delivery.mjs +448 -185
  234. package/src/session-runtime/agent-task-sessions.mjs +329 -180
  235. package/src/session-runtime/boot/apis.mjs +9 -0
  236. package/src/session-runtime/boot/infrastructure.mjs +4 -0
  237. package/src/session-runtime/boot/providers.mjs +3 -0
  238. package/src/session-runtime/boot/tools.mjs +6 -1
  239. package/src/session-runtime/config-lifecycle.mjs +6 -0
  240. package/src/session-runtime/connector-merge-migration.mjs +305 -0
  241. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
  242. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
  243. package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
  244. package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
  245. package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
  246. package/src/session-runtime/lifecycle/teardown.mjs +23 -5
  247. package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
  248. package/src/session-runtime/mcp-oauth-login.mjs +5 -3
  249. package/src/session-runtime/mixy-heartbeat.mjs +153 -33
  250. package/src/session-runtime/notification-bus.mjs +31 -117
  251. package/src/session-runtime/notification-completion-queue.mjs +54 -0
  252. package/src/session-runtime/notification-listeners.mjs +76 -0
  253. package/src/session-runtime/owned-automation-run.mjs +2 -1
  254. package/src/session-runtime/prewarm/channel-start.mjs +3 -7
  255. package/src/session-runtime/resource-mcp-api.mjs +143 -224
  256. package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
  257. package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
  258. package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
  259. package/src/session-runtime/resource-mcp-servers.mjs +78 -0
  260. package/src/session-runtime/resource-mcp-store.mjs +49 -0
  261. package/src/session-runtime/routine-owner.mjs +40 -9
  262. package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
  263. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
  264. package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
  265. package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
  266. package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
  267. package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
  268. package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
  269. package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
  270. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
  271. package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
  272. package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
  273. package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
  274. package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
  275. package/src/session-runtime/services/channel-admin.mjs +7 -13
  276. package/src/session-runtime/services/channel-session-router.mjs +4 -2
  277. package/src/session-runtime/services/seeds.mjs +0 -28
  278. package/src/session-runtime/session-compaction-sync.mjs +27 -0
  279. package/src/session-runtime/session-create/session-options.mjs +1 -0
  280. package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
  281. package/src/session-runtime/settings-compaction-api.mjs +2 -40
  282. package/src/session-runtime/settings-system-api.mjs +13 -1
  283. package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
  284. package/src/session-runtime/setup-tool/executor.mjs +145 -22
  285. package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
  286. package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
  287. package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
  288. package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
  289. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
  290. package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
  291. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
  292. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
  293. package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
  294. package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
  295. package/src/standalone/channel-transport/client-registry.mjs +24 -163
  296. package/src/standalone/channel-transport.mjs +1 -1
  297. package/src/standalone/daemon-channels-loader.mjs +10 -8
  298. package/src/standalone/daemon-session-control/list-page.mjs +72 -0
  299. package/src/standalone/daemon-session-control/request.mjs +51 -0
  300. package/src/standalone/daemon-session-control/room-host.mjs +57 -0
  301. package/src/standalone/daemon-session-control/route-control.mjs +48 -0
  302. package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
  303. package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
  304. package/src/standalone/daemon-session-control.mjs +124 -188
  305. package/src/standalone/daemon.mjs +26 -3
  306. package/src/standalone/session-client.mjs +7 -3
  307. package/src/standalone/session-protocol.mjs +5 -0
  308. package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
  309. package/src/standalone/session-service/quota-resume.mjs +3 -0
  310. package/src/standalone/session-service/retention/busy.mjs +65 -1
  311. package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
  312. package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
  313. package/src/standalone/session-tool-output.mjs +15 -7
  314. package/src/tui/app/model-options.mjs +3 -28
  315. package/src/tui/dist/index.mjs +58 -64
  316. package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
  317. package/src/tui/session/initial-state.mjs +3 -0
  318. package/src/tui/session/notice-surface.mjs +7 -1
  319. package/src/tui/session/queue-helpers.mjs +3 -42
  320. package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
  321. package/src/tui/session/session-api/routes-write.mjs +5 -1
  322. package/src/tui/session/session-api/routes.mjs +1 -0
  323. package/src/tui/session/session-api/settings.mjs +17 -1
  324. package/src/tui/session/session-flow/auto-clear.mjs +4 -33
  325. package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
  326. package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
  327. package/src/tui/session/session-flow/drain-retry.mjs +35 -0
  328. package/src/tui/session/session-flow/drain.mjs +118 -149
  329. package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
  330. package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
  331. package/src/tui/session/tool-approval-delegation.mjs +64 -0
  332. package/src/tui/session/tool-approval-request.mjs +39 -0
  333. package/src/tui/session/tool-approval-settle.mjs +62 -0
  334. package/src/tui/session/tool-approval.mjs +69 -115
  335. package/src/tui/session/tui-steering-persist.mjs +8 -1
  336. package/src/tui/session/turn-stream.mjs +19 -2
  337. package/src/tui/session/turn.mjs +1 -0
  338. package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
  339. package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
  340. package/scripts/code-graph-description-contract.mjs +0 -188
  341. package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
@@ -194,38 +194,63 @@ async function archiveIndexFor(path, handle) {
194
194
  return entries;
195
195
  }
196
196
 
197
- /** One page of a session's archive sidecar (see pageFromSegments). The
198
- * open handle pins the file a concurrent atomic replace may swap out, so
199
- * the index and the segment reads describe the same bytes. */
200
- export async function readCompactionArchivePage(storeDir, sessionId, range = {}) {
197
+ /** Runs `use(entries, load)` over a session's archive sidecar: its segment
198
+ * index and a loader of one segment's items. The open handle pins the file
199
+ * a concurrent atomic replace may swap out, so the index and the segment
200
+ * reads describe the same bytes. `fallback` when the archive is absent or
201
+ * unreadable (display-only history: it shows nothing then). */
202
+ async function withArchive(storeDir, sessionId, fallback, use) {
201
203
  const path = compactionArchivePath(storeDir, sessionId);
202
- if (!path) return { items: [], hasOlder: false };
204
+ if (!path) return fallback;
203
205
  let handle;
204
206
  try {
205
207
  handle = await open(path, 'r');
206
208
  } catch {
207
- // Display-only history: an absent or unreadable archive shows nothing.
208
209
  archiveIndexes.delete(path);
209
- return { items: [], hasOlder: false };
210
+ return fallback;
210
211
  }
211
212
  try {
212
213
  const entries = await archiveIndexFor(path, handle);
213
- return await pageFromSegments(
214
- entries,
215
- async (entry) => {
216
- const buffer = Buffer.alloc(entry.length);
217
- await handle.read(buffer, 0, entry.length, entry.start);
218
- return segmentItems(buffer.toString('utf8'));
219
- },
220
- range
221
- );
214
+ return await use(entries, async (entry) => {
215
+ const buffer = Buffer.alloc(entry.length);
216
+ await handle.read(buffer, 0, entry.length, entry.start);
217
+ return segmentItems(buffer.toString('utf8'));
218
+ });
222
219
  } catch {
223
- return { items: [], hasOlder: false };
220
+ return fallback;
224
221
  } finally {
225
222
  await handle.close().catch(() => {});
226
223
  }
227
224
  }
228
225
 
226
+ /** One page of a session's archive sidecar (see pageFromSegments). */
227
+ export function readCompactionArchivePage(storeDir, sessionId, range = {}) {
228
+ return withArchive(storeDir, sessionId, { items: [], hasOlder: false }, (entries, load) =>
229
+ pageFromSegments(entries, load, range)
230
+ );
231
+ }
232
+
233
+ /** Hands every visible segment of the archive (numbered in (after, before),
234
+ * oldest first) to `visit` as its items, one segment at a time, so a whole
235
+ * walk never holds more than one segment. True when the walk completed;
236
+ * false when the archive was absent or unreadable (the visits made so far
237
+ * are then incomplete). */
238
+ export function visitCompactionArchive(
239
+ storeDir,
240
+ sessionId,
241
+ { after = 0, before = Number.POSITIVE_INFINITY } = {},
242
+ visit
243
+ ) {
244
+ return withArchive(storeDir, sessionId, false, async (entries, load) => {
245
+ for (const entry of entries) {
246
+ const { seq, count } = entry.head;
247
+ if (seq <= after || seq >= before || count < 1) continue;
248
+ visit(await load(entry));
249
+ }
250
+ return true;
251
+ });
252
+ }
253
+
229
254
  // Delete fence: every deletion bumps the session's epoch. A writer captures
230
255
  // the epoch before it starts and publishes only if it is unchanged at the
231
256
  // (synchronous) rename, so a delete that lands while a segment is being
@@ -24,6 +24,7 @@ export function createEagerDispatcher({
24
24
  getIterations,
25
25
  getNextIteration,
26
26
  repeatFailLimit,
27
+ isTurnLimitNoticed = () => false,
27
28
  executeToolFn = executeTool,
28
29
  }) {
29
30
  const pending = new Map();
@@ -56,7 +57,9 @@ export function createEagerDispatcher({
56
57
  const startEagerTool = (wireCall) => {
57
58
  // Streamed call_tool requests are admitted, ordered and executed as their
58
59
  // targets; a projection error is answered by the serial batch instead.
59
- if (capped) return null;
60
+ // Once the turn was told to reply (any limit), no call runs: the batch
61
+ // refuses them, so none may start while the wrap-up response streams.
62
+ if (capped || isTurnLimitNoticed()) return null;
60
63
  const call = resolveExecutionToolCall(wireCall, sessionRef);
61
64
  if (!call?.id || pending.has(call.id) || !isParallelDispatchable(call.name)) return null;
62
65
  const admitted = admission.admit(call);
@@ -98,7 +98,8 @@ const LEAD_NAME = 'Lead';
98
98
  * bot-created task session, a bot's home chat or room member session) and
99
99
  * the names from there down to the asker ("Lead › Reviewer", "사무관 ›
100
100
  * Worker"). Only where the card shows: no bot identity, grant or remember
101
- * scope follows from it. Null for a top-level session. */
101
+ * scope follows from it. Null for a top-level session (whose requests show
102
+ * in the chats handing it work instead: tui/session/tool-approval.mjs). */
102
103
  export function approvalSurface(sessionId) {
103
104
  const delegation = delegationOf(sessionId);
104
105
  if (delegation.state !== 'resolved') return null;
@@ -156,7 +157,13 @@ async function describeEmails(call, outbound, ctx) {
156
157
  const { executeMcpTool } = await import('../../mcp/client.mjs');
157
158
  const ids = outbound.category === 'trash' && Array.isArray(call.args?.ids) ? call.args.ids.map(String) : [];
158
159
  const shown = ids.slice(0, PREVIEW_MESSAGES);
159
- const raw = await executeMcpTool(name, { ids: shown }, { scopeId: ctx.mcpScopeId });
160
+ // The account the call acts as (one server holds every Gmail account).
161
+ const chosen = typeof call.args?.account === 'string' ? call.args.account.trim() : '';
162
+ const raw = await executeMcpTool(
163
+ name,
164
+ { ids: shown, ...(chosen ? { account: chosen } : {}) },
165
+ { scopeId: ctx.mcpScopeId }
166
+ );
160
167
  const { account, emails } = JSON.parse(raw);
161
168
  const messages = (Array.isArray(emails) ? emails : []).map((mail) =>
162
169
  [mail.from, mail.subject, String(mail.date || '').slice(0, 10)].filter(Boolean).join(' · ')
@@ -231,10 +238,9 @@ export async function humanGateDecision(call, decision, ctx = {}) {
231
238
  editable: outbound.editable,
232
239
  details: outbound.details,
233
240
  },
234
- remember: ['once'],
241
+ // "Don't ask again" is the bot's permission.
242
+ remember: outboundRemember(outbound.category),
235
243
  };
236
- // "Don't ask again" is the bot's permission.
237
- request.remember = outboundRemember(outbound.category);
238
244
  const store = ctx.store || (await humanGateStore());
239
245
  const mode = await store.modeFor({ agentId, action: outbound.category });
240
246
  const base = decision && typeof decision === 'object' ? decision : {};
@@ -74,8 +74,6 @@ const MCP_OUTBOUND = new Map([
74
74
  'slack_post_message',
75
75
  'slack_reply_to_thread',
76
76
  'chat_post_message',
77
- 'discord_send_message',
78
- 'telegram_send_message',
79
77
  ].map((name) => [name, 'message']),
80
78
  ...['create_post', 'publish_post', 'create_tweet', 'post_tweet', 'send_tweet'].map((name) => [name, 'publish']),
81
79
  ['submit_form', 'form'],
@@ -12,10 +12,23 @@
12
12
  // the scopes it allows and can leave out `always`. Nothing here ever carries a
13
13
  // secret value: a decision that names one is stripped to its verdict.
14
14
 
15
- export const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission']);
16
- export const HUMAN_RESPONSES = Object.freeze(['approve', 'deny', 'edit-and-approve', 'provide-secret', 'skip', 'done']);
17
- export const REMEMBER_SCOPES = Object.freeze(['once', 'agent', 'always']);
15
+ const HUMAN_REQUEST_KINDS = Object.freeze(['approval', 'outbound', 'secret', 'takeover', 'permission']);
16
+ const HUMAN_RESPONSES = Object.freeze(['approve', 'deny', 'edit-and-approve', 'provide-secret', 'skip', 'done']);
17
+ const REMEMBER_SCOPES = Object.freeze(['once', 'agent', 'always']);
18
18
  export const SECRET_REQUEST_TIMEOUT_MS = 120_000;
19
+ /** What a secret's bound field is: a sign-in form (아이디 + 비밀번호), a lone
20
+ * password, a new password, or a one-time code; '' when unknown (computer). */
21
+ export const SECRET_FORMS = Object.freeze(['login', 'password', 'new-password', 'otp']);
22
+ /** Every result a secret handoff (and a declined or expired takeover) gives
23
+ * the agent: facts, never a verdict about the site. */
24
+ export const HANDOFF_OUTCOMES = Object.freeze([
25
+ 'entered',
26
+ 'user_skipped',
27
+ 'no_field',
28
+ 'page_changed',
29
+ 'blocked',
30
+ 'expired',
31
+ ]);
19
32
 
20
33
  const RESPONSES_BY_KIND = Object.freeze({
21
34
  approval: ['approve', 'deny'],
@@ -49,7 +62,7 @@ const text = (value, limit = TEXT_LIMIT) => (typeof value === 'string' ? value.t
49
62
 
50
63
  /** The human kind of a request; anything else (ask_user's 'question', a
51
64
  * hook's bare ask) is an ordinary approval for responses. */
52
- export function humanRequestKind(request) {
65
+ function humanRequestKind(request) {
53
66
  return HUMAN_REQUEST_KINDS.includes(request?.kind) ? request.kind : 'approval';
54
67
  }
55
68
 
@@ -103,11 +116,21 @@ export function humanRequestFields(input = {}) {
103
116
  }
104
117
  if (kind === 'secret') {
105
118
  const secret = isRecord(input.secret) ? input.secret : {};
119
+ // The bound page's origin and what its field is (the desktop read it at
120
+ // request time, ask-user-tool.mjs): the card's title and inputs; no value.
121
+ const origin = loginPageUrl(secret.origin)?.origin || '';
122
+ const form = SECRET_FORMS.includes(secret.form) ? secret.form : '';
123
+ // A code's length is the bound field's own (credential-autofill.ts
124
+ // codeLength); absent, it stays unspecified.
125
+ const length = Number.isInteger(secret.length) && secret.length > 0 ? secret.length : 0;
106
126
  fields.secret = {
107
127
  requestId: text(secret.requestId),
108
128
  surface: secret.surface === 'computer' ? 'computer' : 'browser',
109
129
  target: text(secret.target),
110
130
  field: text(secret.field),
131
+ ...(origin ? { origin } : {}),
132
+ ...(form ? { form } : {}),
133
+ ...(form === 'otp' && length ? { length } : {}),
111
134
  };
112
135
  fields.args = { surface: fields.secret.surface, target: fields.secret.target, field: fields.secret.field };
113
136
  fields.remember = ['once'];
@@ -132,12 +155,21 @@ export function humanRequestFields(input = {}) {
132
155
  // A login takeover (purpose 'login') opens the desktop's sign-in window on
133
156
  // its validated page instead of handing over the side panel.
134
157
  const login = surface === 'browser' && takeover.purpose === 'login' ? loginPageUrl(takeover.url) : null;
158
+ // The saved logins of the sign-in page's origin: opaque ids and masked
159
+ // labels only (profile-import.ts credentialSuggestions), never a password.
160
+ const accounts = (login && Array.isArray(takeover.accounts) ? takeover.accounts : [])
161
+ .filter((account) => isRecord(account) && token(account.id, 128) && text(account.label, 120))
162
+ .map((account) => ({ id: token(account.id, 128), label: text(account.label, 120) }));
163
+ // The page those logins may be filled into, bound by the desktop at
164
+ // request time (ask-user-tool.mjs fillId): without it none is offered.
165
+ const fillId = /^secret_[A-Za-z0-9-]{8,80}$/.test(text(takeover.fillId, 100)) ? text(takeover.fillId, 100) : '';
135
166
  fields.takeover = {
136
167
  surface,
137
168
  instructions: text(takeover.instructions, 2_000),
138
169
  ...(text(takeover.target) ? { target: text(takeover.target) } : {}),
139
170
  ...identity,
140
171
  ...(login ? { purpose: 'login', url: login.url, origin: login.origin } : {}),
172
+ ...(accounts.length && fillId ? { accounts, fillId } : {}),
141
173
  };
142
174
  fields.remember = ['once'];
143
175
  }
@@ -17,7 +17,7 @@ import { ACTION_KINDS, isRememberableAction } from './outbound-policy.mjs';
17
17
  const ANY_AGENT = '*';
18
18
  const FIELD_LIMIT = 200;
19
19
  const AUDIT_KEYS = ['agentId', 'kind', 'action', 'tool', 'target', 'decision', 'device'];
20
- const PERMISSION_MODES = Object.freeze(['ask', 'allow', 'block']);
20
+ export const PERMISSION_MODES = Object.freeze(['ask', 'allow', 'block']);
21
21
 
22
22
  const field = (value) => String(value ?? '').slice(0, FIELD_LIMIT);
23
23
  const grantId = (agentId, action) =>
@@ -21,6 +21,7 @@ import { isAgentModeSession } from '../agent-mode.mjs';
21
21
  import { providerInputExcludesCache, providerNativePdf } from '../../providers/registry.mjs';
22
22
  import { contentMediaBytes, contentMediaCount } from '../../providers/media-normalization.mjs';
23
23
  import { splitProtectedContext } from '../compact/messages.mjs';
24
+ import { withLiveCompactionConfig } from '../../runtime-core/compaction-config-keys.mjs';
24
25
 
25
26
  // Unified context-share rule (compact/constants.mjs CONTEXT_SHARE_RATIO): the
26
27
  // post-compaction target is 25% of the boundary/context window. One
@@ -394,13 +395,6 @@ export function invalidateProviderContextBaseline(sessionRef) {
394
395
  delete sessionRef.contextPressureUnanchoredReason;
395
396
  }
396
397
 
397
- // A baseline is refreshed on every provider_send/turn-end. When the transcript
398
- // has grown but no fresh recording landed for this long, recording is failing
399
- // (zero/absent usage keeps the OLD baseline because record() only returns
400
- // false) — distrust it and fall back to the estimate. An idle session whose
401
- // transcript did NOT grow keeps its baseline regardless of age.
402
- const BASELINE_MAX_STALE_GROWTH_MS = 30 * 60 * 1000;
403
-
404
398
  /**
405
399
  * Does this anchor still describe this message prefix?
406
400
  *
@@ -480,17 +474,19 @@ function providerBaselinePressureTokens(messages, sessionRef, policy, { includeC
480
474
  ? Math.max(0, tokens - Math.round(storedRequestReserve) + currentRequestReserve)
481
475
  : tokens + currentRequestReserve;
482
476
  }
483
- // Staleness means a baseline that stopped being refreshed WHILE the session
484
- // kept working — never age on the wall clock. Measured against `now`, this
485
- // discarded the reading of every session that simply sat unopened for a
486
- // while (88 of 983 stored sessions), and the whole-transcript estimate that
487
- // replaced it was the less accurate number in both directions: 1.12x over
488
- // on the median, 0.82x under on a session already past its window.
489
- // Comparing against the session's own last activity keeps the rule aimed at
490
- // its actual target — a live session whose usage recording is failing while
491
- // its transcript grows — and leaves a session at rest on its measurement.
492
- const activityAt = Math.max(Number(sessionRef.updatedAt) || 0, Number(sessionRef.lastContextTokensUpdatedAt) || 0);
493
- if (messages.length > count && baselineAt > 0 && activityAt - baselineAt > BASELINE_MAX_STALE_GROWTH_MS) return null;
477
+ // Staleness means usage recording is failing: a provider request completed
478
+ // AFTER this reading without replacing it (every send records its usage
479
+ // after it starts, so a working recorder always leaves the reading newer
480
+ // than the last send) — zero/absent usage keeps the OLD baseline because
481
+ // record() only returns false. Never age on a clock: measured against `now`
482
+ // it discarded the reading of every session that sat unopened for a while
483
+ // (88 of 983 stored sessions), and measured against the session's last
484
+ // activity it discarded it whenever anything was appended more than half an
485
+ // hour after the last request — a user back from a break, an agent's result
486
+ // posted to a waiting Lead — so the next send compacted on the
487
+ // whole-transcript estimate, far above the provider's own count.
488
+ const lastSendAt = Number(sessionRef.lastProviderSendAt) || 0;
489
+ if (messages.length > count && baselineAt > 0 && lastSendAt > baselineAt) return null;
494
490
  try {
495
491
  // Baseline tokens are authoritative provider billing; only the growth
496
492
  // after the baseline is a local estimate and needs billing calibration.
@@ -795,9 +791,9 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
795
791
  if (terminalSuccess) return null;
796
792
  return present ? (next ?? null) : (previous ?? null);
797
793
  };
798
- return {
799
- ...prev,
800
- auto: policy.auto !== false,
794
+ // Config-owned fields stay the live ones (withLiveCompactionConfig): `policy`
795
+ // was resolved before a compaction that a reload may have outlived.
796
+ return withLiveCompactionConfig(prev, {
801
797
  requestReserveTokens: policy.requestReserveTokens || 0,
802
798
  reserveTokens: policy.reserveTokens || 0,
803
799
  boundaryTokens: policy.boundaryTokens || null,
@@ -848,7 +844,7 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
848
844
  compactCount: (prev.compactCount || 0) + (changed ? 1 : 0),
849
845
  singleShotConsumed:
850
846
  policy.singleShot === true && meta.stage === 'compacting' ? true : prev.singleShotConsumed === true,
851
- };
847
+ });
852
848
  }
853
849
 
854
850
  // Postmortem ring buffer: the per-check telemetry record is overwritten on
@@ -18,6 +18,9 @@ import { summarizeOnSharedPrefix } from '../compact/forked-summary.mjs';
18
18
  import { normalizeCompactionSummaryMode } from '../compact/summary-mode.mjs';
19
19
  import { compactionCacheWarm, lighterSubscriptionModel } from '../compact/summary-route.mjs';
20
20
  import { retireCompactionFork } from '../side-question-store.mjs';
21
+ import { buildSkillToolEnvelope, collectSkillsCached, currentSkillDelivery } from '../../context/collect.mjs';
22
+ import { loadSkillToolDependencies } from '../../runtime-core/skill-tool-loading.mjs';
23
+ import { parseNativeToolSearchPayload } from './tool-helpers.mjs';
21
24
 
22
25
  // Conversation summarization triggers independently of the post-compact target:
23
26
  // at 5% of the window, capped so a large window does not carry tens of
@@ -262,11 +265,37 @@ function freshContextBudget({ compactPolicy, sessionRef, provider, compactBudget
262
265
  return { contextWindow, hardBudget, conversationInput, conversationTokens, conversationThresholdTokens };
263
266
  }
264
267
 
268
+ // Compact restores each active skill from its current source, as the Skill
269
+ // tool would deliver it in this session now (null: it no longer loads). The
270
+ // real pass also re-arms the skill's current tool dependencies on the session
271
+ // surface and answers the native tool-search payload they produce (or, with
272
+ // none, the Skill result summary); a preview never touches the surface and
273
+ // keeps the transcript's loader pairs.
274
+ function currentSkillResolver(sessionRef, { preview = false } = {}) {
275
+ const cwd = sessionRef?.cwd || null;
276
+ const config = loadConfig({ secrets: false });
277
+ const skills = collectSkillsCached(cwd);
278
+ return (name) => {
279
+ const delivery = currentSkillDelivery(name, cwd, config, skills);
280
+ if (!delivery) return null;
281
+ if (preview) return { body: delivery.body, native: undefined };
282
+ const { resource } = delivery;
283
+ const envelope = loadSkillToolDependencies(
284
+ buildSkillToolEnvelope(name, resource.content, resource.dir, resource),
285
+ sessionRef
286
+ );
287
+ const native = parseNativeToolSearchPayload('Skill', envelope.result);
288
+ return { body: delivery.body, native, summary: native?.summary ?? envelope.result };
289
+ };
290
+ }
291
+
265
292
  function freshContextBuildOptions(
266
- { compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
293
+ { sessionRef, compactPolicy = {}, sessionId, goalReminderText, activeTurn, referenceLatestAttachments, preview },
267
294
  budget
268
295
  ) {
269
296
  return {
297
+ // A sessionless call (no session gates to apply) keeps the transcript copies.
298
+ currentSkill: sessionRef ? currentSkillResolver(sessionRef, { preview }) : null,
270
299
  reserveTokens: compactPolicy.reserveTokens,
271
300
  maxBudgetTokens: budget.hardBudget,
272
301
  force: true,
@@ -287,7 +316,7 @@ export function previewFreshContextCompaction(input) {
287
316
  const budget = freshContextBudget(input);
288
317
  const summaryTriggered = budget.conversationTokens >= budget.conversationThresholdTokens;
289
318
  const result = freshContextCompactMessages(input.messages, input.compactBudgetTokens, {
290
- ...freshContextBuildOptions(input, budget),
319
+ ...freshContextBuildOptions({ ...input, preview: true }, budget),
291
320
  preview: true,
292
321
  previewSummary: summaryTriggered,
293
322
  });
@@ -346,7 +375,7 @@ export async function runFreshContextCompact({
346
375
  });
347
376
  const summaryTriggered = conversationTokens >= conversationThresholdTokens;
348
377
  const buildOptions = freshContextBuildOptions(
349
- { compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
378
+ { sessionRef, compactPolicy, sessionId, goalReminderText, activeTurn, referenceLatestAttachments },
350
379
  { contextWindow, hardBudget }
351
380
  );
352
381
  const build = (handoffText) =>
@@ -120,7 +120,9 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
120
120
  messages.push(message);
121
121
  try {
122
122
  opts.onToolResult?.(message);
123
- } catch {}
123
+ } catch {
124
+ // the tool-result observer is advisory
125
+ }
124
126
  };
125
127
  state.drainSteering = createSteeringDrain({
126
128
  messages,
@@ -44,7 +44,7 @@ export function createNoToolTurnResolver({
44
44
  // whether it answered that input.
45
45
  reactions: [],
46
46
  // Structured provider continuation signals honored this turn. Diagnostic
47
- // only: the loop has no iteration cap, so this count bounds nothing. A
47
+ // only: this count bounds nothing. A
48
48
  // text-only continuation runaway is bounded by continuationsSinceToolBatch
49
49
  // below; continuations interleaved with tool batches are not limited.
50
50
  providerContinuationCount: 0,
@@ -6,6 +6,12 @@ import { getProvider, initProviders } from '../../providers/registry.mjs';
6
6
  import { loadConfig } from '../../config.mjs';
7
7
  import { ensureProviderEnabled } from '../../runtime-core/config-helpers.mjs';
8
8
  import { updateSessionRoute } from '../manager/session-lifecycle.mjs';
9
+ import {
10
+ liveRouteFallbacks,
11
+ nextRouteFallbackIndex,
12
+ routeFallbackHopped,
13
+ routeLeaf,
14
+ } from '../manager/route-fallback-chain.mjs';
9
15
  import { pinSessionWireRoute } from '../../providers/wire-route.mjs';
10
16
  import { buildRequestSendOptions } from '../manager/ask-turn-loop-options.mjs';
11
17
  import { prepareTurnEffortConfiguration } from '../../providers/effort-configuration.mjs';
@@ -78,9 +84,13 @@ async function nextContextGrows(state, next) {
78
84
 
79
85
  export async function advanceAgentRoute(state, error) {
80
86
  const session = state.sessionRef;
81
- if (!session?.agent || !session.routeFallbacks?.length) return false;
82
- const index = session.routeFallbackIndex || 0;
83
- const next = session.routeFallbacks[index];
87
+ if (!session?.agent) return false;
88
+ // Read from the session's settings at this hop: a chain edited while it runs
89
+ // applies now, and one cleared to [] leaves the provider's own error.
90
+ const { chain } = liveRouteFallbacks(session, loadConfig({ secrets: false }));
91
+ if (!chain.length) return false;
92
+ const index = nextRouteFallbackIndex(session, chain);
93
+ const next = chain[index];
84
94
  // Only THIS request must have produced nothing (its stream outcome says so):
85
95
  // earlier requests of the turn may have run tools. Their calls and results
86
96
  // are already in the live turn, which the next model continues from, so no
@@ -97,6 +107,8 @@ export async function advanceAgentRoute(state, error) {
97
107
  const provider = getProvider(next.provider);
98
108
  if (!provider) throw new Error(`Provider "${next.provider}" not available`);
99
109
  const failed = routeFailure(session, error);
110
+ // The route a reset returns to (setup reset_route_fallback), kept from the first hop.
111
+ const primary = routeFallbackHopped(session) ? session.routeFallbackPrimary : routeLeaf(session);
100
112
  // The left model's share of the turn ends here: the running total at the
101
113
  // switch bounds it, so each model's tokens are booked at its own rates.
102
114
  state.routeUsageHops = [
@@ -131,6 +143,8 @@ export async function advanceAgentRoute(state, error) {
131
143
  { crossProvider: true, session }
132
144
  );
133
145
  if (!updated) throw new Error('agent session closed during model fallback');
146
+ updated.routeFallbacks = chain;
147
+ if (primary) updated.routeFallbackPrimary = primary;
134
148
  updated.routeFallbackIndex = index + 1;
135
149
  updated.routeFallbackFailures = [...(session.routeFallbackFailures || []), failed];
136
150
  await pinSessionWireRoute(updated, provider);
@@ -126,13 +126,16 @@ export async function beginIteration(state) {
126
126
  getIterations: () => state.iterations,
127
127
  getNextIteration: () => nextIteration,
128
128
  repeatFailLimit: REPEAT_FAIL_LIMIT,
129
+ isTurnLimitNoticed: () => state.turnLimitNoticed === true,
129
130
  });
130
131
  const resolveToolCallId = createToolCallIdResolver(messages);
131
132
  opts.onToolCall = (call) => {
132
133
  resolveToolCallId(call);
133
134
  try {
134
135
  opts.onAssistantToolCallObserved?.(call, { eagerStarted: false });
135
- } catch {}
136
+ } catch {
137
+ // the tool-call observer is advisory
138
+ }
136
139
  return eager.onToolCall(call);
137
140
  };
138
141
  return {
@@ -173,7 +176,9 @@ export async function sendProviderRequest(state, round) {
173
176
  });
174
177
  try {
175
178
  opts.onProviderSendStarted?.();
176
- } catch {}
179
+ } catch {
180
+ // the send-started observer is advisory
181
+ }
177
182
  // The request a /btw side question forks: kept while it is in flight, with
178
183
  // its reply once it proceeds, and dropped again if it fails.
179
184
  const sideQuestion = captureSideQuestionRequest({
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { normalizeToolEnvelope, makeToolEnvelope } from '../../tool-envelope.mjs';
6
6
  import { resolveToolResultAfterHook } from '../tool-helpers.mjs';
7
+ import { effectiveToolCwd } from '../../../tools/builtin/bash-tool/shell-cwd.mjs';
7
8
 
8
9
  export async function applyAfterToolHook(result, afterToolHook, { name, args, cwd, callerSessionId, toolCallId }) {
9
10
  if (typeof afterToolHook !== 'function') return result;
@@ -15,7 +16,7 @@ export async function applyAfterToolHook(result, afterToolHook, { name, args, cw
15
16
  const hookResult = await afterToolHook({
16
17
  name,
17
18
  args,
18
- cwd,
19
+ cwd: effectiveToolCwd(name, args, cwd),
19
20
  sessionId: callerSessionId,
20
21
  toolCallId,
21
22
  result: visible,
@@ -15,6 +15,7 @@ import {
15
15
  recordHumanDecision,
16
16
  } from '../../human-gate/gate.mjs';
17
17
  import { applyEditedArgs } from '../../human-gate/requests.mjs';
18
+ import { effectiveToolCwd } from '../../../tools/builtin/bash-tool/shell-cwd.mjs';
18
19
 
19
20
  const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
20
21
 
@@ -31,7 +32,7 @@ async function askApproval(
31
32
  resolvePreToolAskApproval({
32
33
  toolName: call.name,
33
34
  args: call.args,
34
- cwd: call.cwd,
35
+ cwd: effectiveToolCwd(call.name, call.args, call.cwd),
35
36
  sessionId: callerSessionId,
36
37
  toolCallId: executeOpts.toolCallId || null,
37
38
  askReason,
@@ -140,7 +141,7 @@ export async function applyBeforeToolHook({
140
141
  {
141
142
  name,
142
143
  args,
143
- cwd,
144
+ cwd: effectiveToolCwd(name, args, cwd),
144
145
  sessionId: callerSessionId,
145
146
  toolCallId: executeOpts.toolCallId || null,
146
147
  },
@@ -3,7 +3,14 @@
3
3
  * MCP, code graph, internal tools, shell, apply_patch, built-ins and foreign
4
4
  * CLI adapters.
5
5
  */
6
- import { executeMcpTool, isMcpTool, isRegisteredMcpTool, mcpToolHasField } from '../../../mcp/client.mjs';
6
+ import {
7
+ executeMcpTool,
8
+ isBuiltinMailRead,
9
+ isMcpTool,
10
+ isRegisteredMcpTool,
11
+ mcpToolHasField,
12
+ } from '../../../mcp/client.mjs';
13
+ import { recordMailLinks } from '../../../../../browser-bridge/mail-links.mjs';
7
14
  import {
8
15
  executeBuiltinTool,
9
16
  formatUnknownBuiltinToolMessage,
@@ -56,11 +63,18 @@ function dispatchMcp(name, args, { cwd, callerSessionId, sessionRef, executeOpts
56
63
  // an unknown argument.
57
64
  const needsCwdInjection = cwd && mcpToolHasField(name, 'cwd', mcpScopeId) && (args == null || args.cwd == null);
58
65
  const finalArgs = needsCwdInjection ? { ...(args || {}), cwd } : args;
59
- return executeMcpTool(name, finalArgs, {
66
+ const call = executeMcpTool(name, finalArgs, {
60
67
  signal: executeOpts.signal || null,
61
68
  ownerKey: callerSessionId,
62
69
  scopeId: mcpScopeId,
63
70
  });
71
+ if (!isBuiltinMailRead(name, mcpScopeId)) return call;
72
+ // The runtime's own record of the links in a mail it read for this session:
73
+ // the only way Browser Use may open a link carrying a token.
74
+ return call.then((result) => {
75
+ recordMailLinks(callerSessionId, result);
76
+ return result;
77
+ });
64
78
  }
65
79
 
66
80
  function dispatchInternal(
@@ -19,6 +19,7 @@ import { applyBeforeToolHook, applyConnectorApproval, applyShellElevationGate }
19
19
  import { dispatchToolCall } from './tool-exec/dispatch.mjs';
20
20
  import { applyAfterToolHook } from './tool-exec/after-hook.mjs';
21
21
  import { runReadOnlyIoWithDeadline } from './tool-exec/read-only-deadline.mjs';
22
+ import { guardShellCall, requestedShellType } from '../../tools/builtin/bash-tool/misuse-guard.mjs';
22
23
 
23
24
  export { runReadOnlyIoWithDeadline as _runReadOnlyIoWithDeadlineForTest };
24
25
  export {
@@ -43,9 +44,23 @@ async function executeToolOwned(name, args, cwd, callerSessionId, sessionRef, ex
43
44
  const prepared = prepareToolCall(name, args, cwd, callerSessionId, sessionRef, executeOpts);
44
45
  const { toolOpts, notifyFn, completionToolOpts } = prepared;
45
46
  const toolApprovalHook = hookFrom(executeOpts, sessionRef, 'toolApprovalHook');
47
+ // Shell misuse is judged before any hook or approval sees the call, so they
48
+ // read the command that will run and the directory it runs in.
49
+ let callArgs = prepared.args;
50
+ if (name === 'shell') {
51
+ const guarded = guardShellCall(callArgs, {
52
+ shellType: requestedShellType(callArgs),
53
+ agent: sessionRef?.agent || null,
54
+ sessionId: callerSessionId,
55
+ telemetry: executeOpts.resultTelemetry,
56
+ });
57
+ if (guarded.failure) return guarded.failure;
58
+ callArgs = guarded.args;
59
+ completionToolOpts.shellGuardNotes = guarded.notes;
60
+ }
46
61
  const hooked = await applyBeforeToolHook({
47
62
  name,
48
- args: prepared.args,
63
+ args: callArgs,
49
64
  cwd: prepared.cwd,
50
65
  callerSessionId,
51
66
  executeOpts,
@@ -18,6 +18,7 @@ import { isAgentOwner } from '../../agent-owner.mjs';
18
18
  import { honoredSchemaAllowlist } from '../manager/session-tool-surface.mjs';
19
19
  import { syncSystemPrefix } from '../manager/prompt-utils.mjs';
20
20
  import { invalidateProviderContextBaseline } from './compact-policy.mjs';
21
+ import { rebindToChangedPrefix } from '../thinking-binding.mjs';
21
22
 
22
23
  /** Drop everything measured against the previous tool surface. */
23
24
  function invalidateForToolSurfaceMigration(session) {
@@ -34,6 +35,14 @@ function invalidateForToolSurfaceMigration(session) {
34
35
  */
35
36
  export function markToolSurfaceMigrated(session, intent = TOOL_SURFACE_MIGRATION_INTENT) {
36
37
  invalidateForToolSurfaceMigration(session);
38
+ // The stored thinking was bound to the previous prefix and can never be
39
+ // replayed again (../thinking-binding.mjs); an open tool round a stopped
40
+ // turn left behind is ended first. Outside a turn only: a running turn sends
41
+ // from its own working transcript, whose latest tool round must keep its
42
+ // thinking.
43
+ if (Array.isArray(session.messages) && !Array.isArray(session.liveTurnMessages)) {
44
+ session.messages = rebindToChangedPrefix(session.messages);
45
+ }
37
46
  session.pendingCacheBreakIntent = intent;
38
47
  }
39
48
 
@@ -105,6 +114,7 @@ const AGENT_BRIDGE_FIELDS = [
105
114
  'deferredToolDeltaRevision',
106
115
  'nativeDeferredDiscovery',
107
116
  'mcpServerInstructions',
117
+ 'mcpInstructionsAnnounced',
108
118
  ];
109
119
 
110
120
  /**