mixdog 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (423) hide show
  1. package/NOTICE.md +52 -9
  2. package/README.md +8 -4
  3. package/package.json +2 -2
  4. package/scripts/native-release-status.mjs +238 -0
  5. package/src/defaults/shipped-history.json +8 -1
  6. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  7. package/src/defaults/skills/setup/SKILL.md +23 -1
  8. package/src/defaults/skills/setup/references/actions.md +54 -4
  9. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  10. package/src/defaults/skills/setup-guide/SKILL.md +17 -10
  11. package/src/defaults/skills/visualize/SKILL.md +9 -6
  12. package/src/defaults/skills/visualize/references/craft.md +8 -1
  13. package/src/help.mjs +1 -1
  14. package/src/lib/keychain-cjs.cjs +4 -1
  15. package/src/lib/rules-builder.cjs +14 -7
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent-mode/AGENT_MODE.md +9 -6
  18. package/src/rules/agent-mode/MIXY.md +11 -6
  19. package/src/rules/lead/LEAD.md +2 -2
  20. package/src/rules/shared/10-tool-workflow.md +4 -0
  21. package/src/rules/shared/20-research.md +2 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  24. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  25. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  26. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  27. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  29. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  30. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  31. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  32. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  33. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
  34. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
  35. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  36. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  37. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
  38. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
  41. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  43. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  44. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
  45. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  46. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  47. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  48. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  49. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
  50. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  51. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  52. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  53. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  54. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  55. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  56. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  57. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  58. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  59. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
  60. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  61. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  62. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  64. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  65. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  66. package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
  67. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  68. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  69. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  71. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  72. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  73. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
  74. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  75. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
  78. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
  79. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
  80. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  81. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  83. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  84. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  85. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
  86. package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -45
  87. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  88. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  89. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +14 -11
  90. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  91. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
  92. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  93. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  94. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
  96. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
  97. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  99. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
  100. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
  101. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  102. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
  103. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  104. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  105. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  106. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  107. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
  108. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  109. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  110. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
  111. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
  112. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  113. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
  114. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
  115. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  116. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  117. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  118. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
  119. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  120. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  121. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  123. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  124. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  125. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
  126. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  127. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  128. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  129. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  130. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  131. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  132. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  134. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +62 -47
  135. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
  136. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
  137. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  138. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  139. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  140. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
  141. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  142. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
  143. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
  144. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  145. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
  146. package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
  147. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
  148. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  149. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  150. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  151. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  152. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  153. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
  154. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
  155. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
  156. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  157. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  158. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  159. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  160. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  161. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
  162. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
  163. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
  164. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  165. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
  166. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  167. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  168. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  169. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  170. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
  171. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  172. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  173. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  174. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  175. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  176. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
  177. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  178. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  179. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  180. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  181. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  182. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  183. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  184. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  185. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  186. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  187. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  188. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  189. package/src/runtime/attachments/limits.mjs +5 -1
  190. package/src/runtime/attachments/office-extract.mjs +3 -6
  191. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  192. package/src/runtime/attachments/store.mjs +55 -30
  193. package/src/runtime/browser-bridge/client.mjs +73 -20
  194. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  195. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  196. package/src/runtime/channels/lib/state-file.mjs +15 -15
  197. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  198. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  199. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  200. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  201. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  202. package/src/runtime/effort-judge/judge-client.mjs +6 -3
  203. package/src/runtime/effort-judge/model-install.mjs +16 -8
  204. package/src/runtime/effort-judge/model-manifest.json +7 -7
  205. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  206. package/src/runtime/local-provider/catalog.mjs +5 -2
  207. package/src/runtime/local-provider/registered-models.mjs +64 -27
  208. package/src/runtime/local-provider/server.mjs +2 -3
  209. package/src/runtime/media/adapters/image-api.mjs +89 -0
  210. package/src/runtime/media/auth.mjs +64 -1
  211. package/src/runtime/media/catalog.mjs +66 -4
  212. package/src/runtime/media/jobs.mjs +4 -0
  213. package/src/runtime/media/lanes.mjs +46 -5
  214. package/src/runtime/media/media-usage.mjs +17 -0
  215. package/src/runtime/media/renditions.mjs +15 -15
  216. package/src/runtime/media/store.mjs +36 -35
  217. package/src/runtime/media/tool.mjs +8 -1
  218. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  219. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  220. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  221. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  222. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  223. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  224. package/src/runtime/office/core/journal.mjs +7 -14
  225. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  226. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  227. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  228. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  229. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  230. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  231. package/src/runtime/office/portable/portable-soffice.mjs +4 -0
  232. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  233. package/src/runtime/shared/agent-kind.mjs +152 -0
  234. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  235. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  236. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  237. package/src/runtime/shared/agent-route-config.d.mts +1 -0
  238. package/src/runtime/shared/agent-route-config.mjs +22 -8
  239. package/src/runtime/shared/agent-store.mjs +149 -0
  240. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  241. package/src/runtime/shared/atomic-file.d.mts +50 -0
  242. package/src/runtime/shared/atomic-file.mjs +394 -132
  243. package/src/runtime/shared/background-tasks.mjs +7 -4
  244. package/src/runtime/shared/bot-hops.mjs +30 -0
  245. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  246. package/src/runtime/shared/bounded-download.mjs +4 -2
  247. package/src/runtime/shared/config-patch.mjs +20 -1
  248. package/src/runtime/shared/config.mjs +39 -9
  249. package/src/runtime/shared/connector-catalog.d.mts +1 -2
  250. package/src/runtime/shared/connector-catalog.mjs +2 -4
  251. package/src/runtime/shared/connector-triggers.mjs +2 -0
  252. package/src/runtime/shared/defaults-separation.mjs +6 -2
  253. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  254. package/src/runtime/shared/err-text.mjs +7 -1
  255. package/src/runtime/shared/error-presentation.mjs +113 -10
  256. package/src/runtime/shared/file-has-data.mjs +28 -0
  257. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  258. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  259. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  260. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  261. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  262. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  263. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  264. package/src/runtime/shared/plain-preview.mjs +22 -17
  265. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  266. package/src/runtime/shared/projects.mjs +50 -41
  267. package/src/runtime/shared/provider-accounts.mjs +17 -12
  268. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  269. package/src/runtime/shared/routine-trigger.mjs +14 -6
  270. package/src/runtime/shared/run-child.d.mts +3 -0
  271. package/src/runtime/shared/run-child.mjs +24 -3
  272. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  273. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  274. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  275. package/src/runtime/shared/singleton-owner.mjs +7 -6
  276. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  277. package/src/runtime/shared/staged-update.mjs +4 -2
  278. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  279. package/src/runtime/shared/tool-execution-contract.mjs +6 -0
  280. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  281. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  282. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  283. package/src/runtime/shared/turn-snapshot.mjs +44 -2
  284. package/src/runtime/shared/usage-readout-totals.mjs +15 -13
  285. package/src/runtime/shared/user-cwd.mjs +9 -4
  286. package/src/runtime/shared/user-data-guard.mjs +46 -44
  287. package/src/runtime/shared/utf8-cut.mjs +10 -0
  288. package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
  289. package/src/runtime/shared/worktrees/registry.mjs +26 -26
  290. package/src/runtime/tidy/core-install.mjs +26 -4
  291. package/src/runtime/tidy/install.mjs +91 -23
  292. package/src/runtime/web-search/lib/config.mjs +4 -16
  293. package/src/session-runtime/agent-home-delivery.mjs +859 -136
  294. package/src/session-runtime/agent-room-delivery.mjs +167 -19
  295. package/src/session-runtime/agent-task-sessions.mjs +831 -69
  296. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  297. package/src/session-runtime/boot/apis.mjs +58 -24
  298. package/src/session-runtime/boot/config.mjs +1 -0
  299. package/src/session-runtime/boot/facade.mjs +11 -1
  300. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  301. package/src/session-runtime/boot/providers.mjs +16 -6
  302. package/src/session-runtime/boot/shared.mjs +12 -6
  303. package/src/session-runtime/boot/tools.mjs +2 -2
  304. package/src/session-runtime/bot-status.mjs +432 -0
  305. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  306. package/src/session-runtime/config-lifecycle.mjs +78 -32
  307. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  308. package/src/session-runtime/handoff-attachments.mjs +593 -0
  309. package/src/session-runtime/held-requests.mjs +36 -33
  310. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  311. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  312. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
  313. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  314. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  315. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  316. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  317. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  318. package/src/session-runtime/media-api.mjs +5 -2
  319. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  320. package/src/session-runtime/model-route/route-tuning.mjs +11 -2
  321. package/src/session-runtime/model-route/set-route.mjs +82 -2
  322. package/src/session-runtime/native-web-search.mjs +2 -2
  323. package/src/session-runtime/new-session-config.mjs +6 -1
  324. package/src/session-runtime/owned-automation-run.mjs +8 -1
  325. package/src/session-runtime/room-member-gate.mjs +16 -3
  326. package/src/session-runtime/route-resolution.mjs +21 -19
  327. package/src/session-runtime/route-state.mjs +1 -59
  328. package/src/session-runtime/routine-home-turn.mjs +5 -3
  329. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  330. package/src/session-runtime/runtime-paths.mjs +11 -5
  331. package/src/session-runtime/runtime-tunables.mjs +6 -0
  332. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  333. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  334. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  335. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  336. package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
  337. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  338. package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
  339. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
  340. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  341. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
  342. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  343. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  344. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  345. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  346. package/src/session-runtime/services/agent-tool.mjs +6 -0
  347. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  348. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  349. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  350. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  351. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  352. package/src/session-runtime/services/provider-admin.mjs +14 -4
  353. package/src/session-runtime/services/schedule-access.mjs +33 -4
  354. package/src/session-runtime/services/seeds.mjs +4 -3
  355. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  356. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  357. package/src/session-runtime/session-create/materialize.mjs +2 -3
  358. package/src/session-runtime/session-create/session-options.mjs +15 -2
  359. package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
  360. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  361. package/src/session-runtime/setup-tool/executor.mjs +20 -3
  362. package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
  363. package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
  364. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  365. package/src/session-runtime/skills-api.mjs +12 -5
  366. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
  367. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  368. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  369. package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
  370. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  371. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  372. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  373. package/src/session-runtime/tool-policy-refresh.mjs +47 -358
  374. package/src/session-runtime/tool-surface.mjs +5 -3
  375. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
  376. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  377. package/src/session-runtime/turn/turn-run.mjs +2 -1
  378. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  379. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
  380. package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
  381. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  382. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  383. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  384. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  385. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  386. package/src/standalone/daemon-agent-control.mjs +4 -1
  387. package/src/standalone/daemon-session-control.mjs +60 -11
  388. package/src/standalone/daemon.mjs +34 -4
  389. package/src/standalone/local-session-runtime.mjs +22 -14
  390. package/src/standalone/session-client.mjs +7 -2
  391. package/src/standalone/session-protocol.mjs +7 -0
  392. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  393. package/src/standalone/session-service/agent-tree.mjs +4 -3
  394. package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
  395. package/src/standalone/session-service.mjs +11 -0
  396. package/src/standalone/session-state-projection.mjs +19 -9
  397. package/src/standalone/session-tool-output.mjs +35 -9
  398. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  399. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  400. package/src/tui/app/slash-commands.mjs +1 -1
  401. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  402. package/src/tui/dist/index.mjs +599 -342
  403. package/src/tui/prompt-history-store.mjs +16 -21
  404. package/src/tui/session/context-state.mjs +4 -0
  405. package/src/tui/session/notice-surface.mjs +3 -4
  406. package/src/tui/session/session-api/intake/abort.mjs +19 -1
  407. package/src/tui/session/session-api/media.mjs +2 -1
  408. package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
  409. package/src/tui/session/session-api/routes-write.mjs +203 -0
  410. package/src/tui/session/session-api/routes.mjs +23 -236
  411. package/src/tui/session/session-api/settings.mjs +14 -1
  412. package/src/tui/session/session-flow/drain.mjs +28 -3
  413. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  414. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  415. package/src/tui/session/session-flow/steering.mjs +8 -3
  416. package/src/tui/session/tool-approval.mjs +185 -20
  417. package/src/tui/session/transcript-intake.mjs +4 -0
  418. package/src/tui/session/turn-live-call.mjs +11 -3
  419. package/src/tui/session/turn.mjs +36 -17
  420. package/src/tui/transcript-tool-failures.mjs +8 -2
  421. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  422. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  423. package/src/rules/agent-mode/ROUTINE.md +0 -6
@@ -5,16 +5,31 @@
5
5
  // turns an outbound call (outbound-policy.mjs) into an `ask` whatever the
6
6
  // hooks or the agent's permission said short of a deny, unless the agent's
7
7
  // permission for that action kind (store.mjs) is `allow`; `block` denies it
8
- // without a card. Other surfaces (group rooms) call it with their own
8
+ // without a card. The outbound rule applies ONLY while a bot acts (see
9
+ // actingBotId); an ordinary coding session's outbound call passes with the
10
+ // hook decision unchanged. Other surfaces (group rooms) call it with their own
9
11
  // decision to get the same outbound rule. After the user answered,
10
12
  // recordHumanDecision stores a "don't ask again" grant and the audit line. Unattended runs (schedule, webhook, room member) register a
11
13
  // port here (session-runtime/held-requests.mjs) that turns their asks into
12
14
  // held requests instead of an approval card.
13
- import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../agent-home.mjs';
15
+ import { MIXY_AGENT_ID, agentHomeSessionId, agentIdOfHomeSession } from '../agent-home.mjs';
14
16
  import { serverConnector } from '../../mcp/server-connectors.mjs';
17
+ import { readAgentDefinitionKind } from '../../../../shared/agent-kind.mjs';
18
+ import { isContactAgentId } from '../../../../shared/agent-route-config.mjs';
19
+ import { mixdogRoot, resolvePluginData } from '../../../../shared/plugin-paths.mjs';
15
20
  import { roomMemberOfSession } from '../agent-room.mjs';
16
- import { classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
21
+ import { agentDisplayName } from '../../context/role-instructions.mjs';
22
+ import { delegationOf } from './delegation.mjs';
23
+ import { setUnattendedApprovalPort, unattendedApprovalPort } from './unattended-port.mjs';
24
+ import { classifyAsDelete, classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
17
25
  import { humanGateStore } from './store.mjs';
26
+ import {
27
+ AUTOMATION_GRANT_REASON,
28
+ RUN_FORBIDDEN_REASON,
29
+ isGrantedRun,
30
+ isRemoteDeleteInRun,
31
+ savesAutomation,
32
+ } from './run-policy.mjs';
18
33
 
19
34
  export const OUTBOUND_APPROVAL_REASON = 'This sends or posts outside Mixdog; it needs your approval.';
20
35
  export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action.";
@@ -22,23 +37,100 @@ export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action
22
37
  * payments and deletes never. */
23
38
  const outboundRemember = (category) => (isRememberableAction(category) ? ['once', 'agent'] : ['once']);
24
39
 
25
- let unattendedPort = null;
26
- /** `{ runOf(sessionId) → { kind, agentId, name } | null, hold(request, { signal }) → Promise<approval>,
27
- * humanNeeded(sessionId, outcome, site) → Promise<boolean> }` — the last ends a schedule/webhook
28
- * run that would need a takeover (session-runtime/held-requests.mjs). */
29
- export function setUnattendedApprovalPort(port) {
30
- unattendedPort = port || null;
40
+ // The unattended runs' port lives in unattended-port.mjs (delegation.mjs reads
41
+ // it when a session is created); its setter and getter are re-exported here.
42
+ export { setUnattendedApprovalPort, unattendedApprovalPort };
43
+
44
+ /** The agent a session is bound to by itself: its unattended run's owner, a
45
+ * room member, a home thread's agent; '' for any other session. */
46
+ function ownBoundAgentId(sessionId) {
47
+ const run = unattendedApprovalPort()?.runOf?.(sessionId);
48
+ if (run?.agentId) return run.agentId;
49
+ return roomMemberOfSession(sessionId)?.agentId || agentIdOfHomeSession(sessionId) || '';
31
50
  }
32
- export function unattendedApprovalPort() {
33
- return unattendedPort;
51
+
52
+ /** The agent a session is bound to: its own binding, else — a sub-agent at
53
+ * any depth, a task session a bot created — the root recorded on it when it
54
+ * was created (delegation.mjs); '' for a coding Lead and every agent under
55
+ * it, and for a delegated session whose root is unknown (see isUnresolved). */
56
+ function boundAgentId(sessionId) {
57
+ const own = ownBoundAgentId(sessionId);
58
+ if (own) return own;
59
+ const delegation = delegationOf(sessionId);
60
+ return delegation.state === 'resolved' ? delegation.rootAgentId : '';
34
61
  }
35
62
 
63
+ /** A delegated session whose root could not be established: it may act for a
64
+ * bot, so the gate fails closed (outbound asks, nothing is remembered). */
65
+ const isUnresolved = (sessionId) => !ownBoundAgentId(sessionId) && delegationOf(sessionId).state === 'unresolved';
66
+
36
67
  /** The agent a session acts for: its unattended run's owner, a room member,
37
68
  * a home thread's agent, else Mixy. */
38
69
  export function agentIdForSession(sessionId) {
39
- const run = unattendedPort?.runOf?.(sessionId);
40
- if (run?.agentId) return run.agentId;
41
- return roomMemberOfSession(sessionId)?.agentId || agentIdOfHomeSession(sessionId) || MIXY_AGENT_ID;
70
+ return boundAgentId(sessionId) || MIXY_AGENT_ID;
71
+ }
72
+
73
+ /** The bot a session acts for, or '' when none: Mixy in her own home thread
74
+ * (or her unattended run / room member session), or a contact agent (`kind:
75
+ * bot`, isContactAgentId) in its home thread, room member session or
76
+ * unattended run — or any session delegated from one of those (a sub-agent,
77
+ * a task session the bot created, nested at any depth), which acts as that
78
+ * root bot. A coding Lead (also id 'lead'), its workflow agents and their
79
+ * sub-agent sessions are bound to nothing here, so they are no bot. */
80
+ function actingBotId(sessionId) {
81
+ const id = boundAgentId(sessionId);
82
+ if (!id) return '';
83
+ if (id === MIXY_AGENT_ID) return id;
84
+ const kind = readAgentDefinitionKind({ dataDir: resolvePluginData(), shippedRoot: mixdogRoot(), id });
85
+ return isContactAgentId(id, kind) ? id : '';
86
+ }
87
+
88
+ /** The contact bot (`kind: bot`) a session acts for, or '' for Mixy, a coding
89
+ * Lead, a workflow agent and every other session. Mixy shares the `lead` id
90
+ * with the coding Lead (her connector policy is hers alone, never the Lead's:
91
+ * connectorPolicyAgentOf), and a standing allow is a contact bot's own policy
92
+ * entry: none is offered to Mixy, a Lead or a workflow agent. */
93
+ export function actingContactBotId(sessionId) {
94
+ const id = actingBotId(sessionId);
95
+ // Only where a remembering answer can be given in the bot's OWN home thread
96
+ // (rememberSurfaceOf). A room member, an unattended run (a schedule, a
97
+ // webhook) and work they started act for the bot in a session that is not its chat.
98
+ return id && id !== MIXY_AGENT_ID && rememberSurfaceOf(sessionId) === agentHomeSessionId(id) ? id : '';
99
+ }
100
+
101
+ /** The one session where an answer to `sessionId`'s request may be
102
+ * remembered ("don't ask again", "always allow"): the bot's home chat when
103
+ * the request lives there, or surfaces there because the work was started
104
+ * from that attended chat; '' otherwise (a room, a run, a coding Lead, an
105
+ * unresolved delegation). The approval queue honours a remembering answer
106
+ * only from that session (tui/session/tool-approval.mjs). */
107
+ export function rememberSurfaceOf(sessionId) {
108
+ // A schedule/webhook running in the home itself is not the attended chat.
109
+ if (unattendedApprovalPort()?.runOf?.(sessionId)) return '';
110
+ if (agentIdOfHomeSession(sessionId)) return sessionId;
111
+ if (ownBoundAgentId(sessionId)) return '';
112
+ const delegation = delegationOf(sessionId);
113
+ return delegation.state === 'resolved' && delegation.via === 'home' ? delegation.rootSessionId : '';
114
+ }
115
+
116
+ // Chain tokens the desktop translates (ApprovalCard approvalChainLabel).
117
+ const LEAD_NAME = 'Lead';
118
+ const TASK_SESSION_NAME = 'Task session';
119
+
120
+ /** Where a delegated session's approval cards also surface, and who is
121
+ * asking: `{ sessionId, chain }` — the top-level conversation the user
122
+ * watches (the root bot's home chat, a room member session, a coding Lead's
123
+ * session) and the names from there down to the asker ("사무관 › Worker",
124
+ * "사무관 › Task session", "Lead › Reviewer"). A sub-agent at any depth and
125
+ * a task session a bot created have one; null for a top-level session. */
126
+ export function approvalSurface(sessionId) {
127
+ const delegation = delegationOf(sessionId);
128
+ if (delegation.state !== 'resolved') return null;
129
+ const chain = [delegation.rootAgentId ? agentDisplayName(delegation.rootAgentId) : LEAD_NAME];
130
+ for (const step of delegation.path) {
131
+ chain.push(step.kind === 'agent' ? agentDisplayName(step.agent) || step.agent : TASK_SESSION_NAME);
132
+ }
133
+ return { sessionId: delegation.rootSessionId, chain };
42
134
  }
43
135
 
44
136
  const actionOf = (decision) => String(decision?.action || decision?.decision || '').toLowerCase();
@@ -88,20 +180,47 @@ async function describeEmails(call, outbound, ctx) {
88
180
  * @param {{ name: string, args: any }} call
89
181
  * @param {object|null} decision the PreToolUse decision so far
90
182
  * @param {{ sessionId?: string, mcpScopeId?: string, annotations?: object, store?: object }} ctx
91
- * @returns {Promise<{ decision: object|null, request: object|null, gate: object|null }>}
183
+ * @returns {Promise<{ decision: object|null, request: object|null, gate: object|null, remembered?: true }>}
92
184
  * `request` holds the kind-specific fields an `ask` presents; `gate` the
93
- * audit identity `{ agentId, kind, action, tool, target }`.
185
+ * audit identity `{ agentId, kind, action, tool, target }`; `remembered`
186
+ * marks an outbound call a stored grant already answered.
94
187
  */
95
188
  export async function humanGateDecision(call, decision, ctx = {}) {
96
189
  const action = actionOf(decision);
97
190
  if (action === 'deny' || action === 'block') return { decision, request: null, gate: null };
98
- const agentId = agentIdForSession(ctx.sessionId);
191
+ // Delegated, but its root is unknown: treated as a bot whose identity, grants
192
+ // and remember surface are unknown — every outbound call asks, once only.
193
+ const unresolved = isUnresolved(ctx.sessionId);
194
+ const agentId = unresolved ? '' : agentIdForSession(ctx.sessionId);
99
195
  const annotations = ctx.annotations ?? (await mcpAnnotations(call?.name, ctx.mcpScopeId));
100
- const outbound = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
196
+ let classified = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
197
+ const remoteDelete = isRemoteDeleteInRun({ name: call?.name, annotations }, classified);
198
+ // In a schedule/webhook run only a recoverable trash may delete remote items.
199
+ if (isGrantedRun(unattendedApprovalPort()?.runOf?.(ctx.sessionId)) && remoteDelete) {
200
+ const store = ctx.store || (await humanGateStore());
201
+ await store.recordAudit({
202
+ agentId,
203
+ kind: 'outbound',
204
+ action: 'delete',
205
+ tool: String(call?.name || ''),
206
+ target: '',
207
+ decision: 'run-forbidden',
208
+ device: '',
209
+ });
210
+ const base = decision && typeof decision === 'object' ? decision : {};
211
+ return { decision: { ...base, action: 'deny', reason: RUN_FORBIDDEN_REASON }, request: null, gate: null };
212
+ }
213
+ // Where a bot is attended, the same connector delete the runs refuse
214
+ // (delete_message, remove_member…) is a permanent delete: it asks, never remembered.
215
+ if (remoteDelete && classified?.category !== 'delete' && classified?.category !== 'payment') {
216
+ classified = classifyAsDelete({ name: call?.name, args: call?.args });
217
+ }
218
+ const outbound = classified && (unresolved || actingBotId(ctx.sessionId)) ? classified : null;
101
219
  if (!outbound) {
102
220
  if (action !== 'ask') return { decision, request: null, gate: null };
103
221
  return {
104
- decision,
222
+ // Saving a schedule or webhook says plainly what the approval grants.
223
+ decision: savesAutomation(call) ? { ...decision, reason: AUTOMATION_GRANT_REASON } : decision,
105
224
  request: { kind: 'approval', agentId, remember: ['once'] },
106
225
  gate: { agentId, kind: 'approval', tool: String(call?.name || ''), target: '' },
107
226
  };
@@ -118,10 +237,16 @@ export async function humanGateDecision(call, decision, ctx = {}) {
118
237
  editable: outbound.editable,
119
238
  details: outbound.details,
120
239
  },
121
- remember: outboundRemember(outbound.category),
240
+ remember: ['once'],
122
241
  };
242
+ // "Don't ask again" only where it can be answered in the bot's own chat.
243
+ const rememberAt = unresolved ? '' : rememberSurfaceOf(ctx.sessionId);
244
+ if (rememberAt && isRememberableAction(outbound.category)) {
245
+ request.remember = outboundRemember(outbound.category);
246
+ request.rememberAt = rememberAt;
247
+ }
123
248
  const store = ctx.store || (await humanGateStore());
124
- const mode = await store.modeFor({ agentId, action: outbound.category });
249
+ const mode = unresolved ? 'ask' : await store.modeFor({ agentId, action: outbound.category });
125
250
  const base = decision && typeof decision === 'object' ? decision : {};
126
251
  if (mode === 'block') {
127
252
  await store.recordAudit({ ...gate, decision: 'blocked', device: '' });
@@ -129,7 +254,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
129
254
  }
130
255
  if (action !== 'ask' && mode === 'allow') {
131
256
  await store.recordAudit({ ...gate, decision: 'remembered', device: '' });
132
- return { decision, request: null, gate: null };
257
+ // The remembered grant answers this call: no later layer asks it again.
258
+ return { decision, request: null, gate: null, remembered: true };
133
259
  }
134
260
  const described = await describeEmails(call, outbound, ctx);
135
261
  if (described) {
@@ -146,7 +272,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
146
272
  return {
147
273
  decision: { ...(rewriting ? base : unrewritten), action: 'ask', reason: base.reason || OUTBOUND_APPROVAL_REASON },
148
274
  request,
149
- gate,
275
+ // The scopes this request offered: a grant is stored only for one of them.
276
+ gate: { ...gate, remember: request.remember },
150
277
  };
151
278
  }
152
279
 
@@ -158,8 +285,12 @@ export async function recordHumanDecision(gate, approval, { store } = {}) {
158
285
  let decision = approval?.response || (approval?.approved === true ? 'approve' : 'deny');
159
286
  if (approval?.timedOut) decision = 'timeout';
160
287
  const device = typeof approval?.device === 'string' ? approval.device : '';
161
- if (approval?.approved === true && gate.action) {
288
+ // A scope the request did not offer (an answer from a place that may not
289
+ // remember) stores nothing.
290
+ const offered = !Array.isArray(gate.remember) || gate.remember.includes(approval?.remember);
291
+ if (approval?.approved === true && gate.action && offered) {
162
292
  await target.grant({ agentId: gate.agentId, action: gate.action, scope: approval.remember, device });
163
293
  }
164
- await target.recordAudit({ ...gate, decision, device });
294
+ const { remember: _offered, ...audit } = gate;
295
+ await target.recordAudit({ ...audit, decision, device });
165
296
  }
@@ -23,6 +23,14 @@ export const ACTION_KINDS = Object.freeze([
23
23
  'payment',
24
24
  'delete',
25
25
  'trash',
26
+ // Browser Use gated actions (main/browser/action-approval.ts): a form
27
+ // submit / Enter, a click on a send or post control, and a file upload. They
28
+ // are not classified here (the desktop browser host asks); only their grants
29
+ // live in this store. Operator-confirmed actions
30
+ // (MIXDOG_BROWSER_CONFIRM_ACTIONS) have no kind: they always ask.
31
+ 'browser_submit',
32
+ 'browser_post',
33
+ 'browser_upload',
26
34
  ]);
27
35
  const UNREMEMBERED_ACTIONS = new Set(['payment', 'delete']);
28
36
 
@@ -204,6 +212,18 @@ export function classifyOutboundCall({ name, args, annotations } = {}) {
204
212
  if (!category && annotations?.readOnlyHint === true) return null;
205
213
  if (!category && annotations?.outboundHint === true) category = 'message';
206
214
  if (!category) return null;
215
+ return mcpOutbound(toolName, server, input, category);
216
+ }
217
+
218
+ /** The classification of connector tool `name` as a permanent delete — for a
219
+ * delete the name-based list above does not know (run-policy.mjs decides which). */
220
+ export function classifyAsDelete({ name, args } = {}) {
221
+ const toolName = String(name || '');
222
+ const [, server = ''] = toolName.split('__');
223
+ return mcpOutbound(toolName, server, isRecord(args) ? args : {}, 'delete');
224
+ }
225
+
226
+ function mcpOutbound(toolName, server, input, category) {
207
227
  const details = detailsOf(category, input);
208
228
  const spends = category === 'payment' || category === 'delete';
209
229
  return {
@@ -84,6 +84,8 @@ export function humanRequestFields(input = {}) {
84
84
  const fields = { remember: rememberScopes(input.remember) };
85
85
  // The agent the request acts for (its name on the card and the remembered grant).
86
86
  if (text(input.agentId)) fields.agentId = text(input.agentId);
87
+ // The one session a remembering answer may come from (gate.mjs rememberSurfaceOf).
88
+ if (text(input.rememberAt)) fields.rememberAt = text(input.rememberAt);
87
89
  if (kind === 'outbound' && isRecord(input.outbound)) {
88
90
  const outbound = input.outbound;
89
91
  const details = isRecord(outbound.details) ? outbound.details : {};
@@ -119,13 +121,16 @@ export function humanRequestFields(input = {}) {
119
121
  // browser page ID, or a computer window id and app, so the card can bring
120
122
  // exactly that window forward on the user's click.
121
123
  const token = (value, limit) => (/^[\w.:#@-]+$/.test(text(value, limit)) ? text(value, limit) : '');
122
- const identity =
123
- surface === 'browser'
124
- ? { ...(token(takeover.tab, 64) ? { tab: token(takeover.tab, 64) } : {}) }
125
- : {
126
- ...(token(takeover.windowId, 128) ? { windowId: token(takeover.windowId, 128) } : {}),
127
- ...(text(takeover.app, 120) ? { app: text(takeover.app, 120) } : {}),
128
- };
124
+ const tab = token(takeover.tab, 64);
125
+ const windowId = token(takeover.windowId, 128);
126
+ const app = text(takeover.app, 120);
127
+ const identity = {};
128
+ if (surface === 'browser') {
129
+ if (tab) identity.tab = tab;
130
+ } else {
131
+ if (windowId) identity.windowId = windowId;
132
+ if (app) identity.app = app;
133
+ }
129
134
  // A login takeover (purpose 'login') opens the desktop's sign-in window on
130
135
  // its validated page instead of handing over the side panel.
131
136
  const login = surface === 'browser' && takeover.purpose === 'login' ? loginPageUrl(takeover.url) : null;
@@ -143,7 +148,13 @@ export function humanRequestFields(input = {}) {
143
148
  fields.permission = {
144
149
  scopes: scopes
145
150
  .filter((scope) => isRecord(scope) && text(scope.id))
146
- .map((scope) => ({ id: text(scope.id), label: text(scope.label) || text(scope.id) })),
151
+ .map((scope) => ({
152
+ id: text(scope.id),
153
+ label: text(scope.label) || text(scope.id),
154
+ // The option's kind (allow_always / allow_once / reject, as an ACP or
155
+ // MCP permission request names them); the card maps buttons by it.
156
+ ...(text(scope.kind) ? { kind: text(scope.kind) } : {}),
157
+ })),
147
158
  };
148
159
  }
149
160
  return fields;
@@ -191,5 +202,6 @@ export function normalizeHumanResponse(request, approved, extra = {}) {
191
202
  /** `args` with the card's edited fields in place of the model's. */
192
203
  export function applyEditedArgs(args, editedArgs, editable) {
193
204
  const edits = editableArgs(editedArgs, editable);
194
- return Object.keys(edits).length ? { ...(isRecord(args) ? args : {}), ...edits } : args;
205
+ if (!Object.keys(edits).length) return args;
206
+ return { ...(isRecord(args) ? args : {}), ...edits };
195
207
  }
@@ -0,0 +1,73 @@
1
+ // session/human-gate/run-policy.mjs
2
+ // Schedule and webhook runs never ask. Saving a schedule or webhook is the
3
+ // user's explicit grant for its runs (and for the work they start: sub-agents,
4
+ // a bot's task sessions): outbound actions and ordinary tool approvals are
5
+ // allowed without a card, a held row or a push, inside the bot's own
6
+ // permissions (a `block` is applied before any ask, gate.mjs). Payments and
7
+ // permanent deletes (the action kinds that can never be remembered) are never
8
+ // done in a run: denied at once, with a result the bot can report. Group-room
9
+ // members are not runs of this kind and keep their held/approval behaviour.
10
+ import { ACTION_KINDS, isRememberableAction } from './outbound-policy.mjs';
11
+
12
+ export const RUN_FORBIDDEN_REASON =
13
+ "Payments and permanent deletes aren't allowed in schedule/webhook runs; do it from the chat.";
14
+
15
+ /** The ask reason of a schedule/webhook save: the grant, said plainly. */
16
+ export const AUTOMATION_GRANT_REASON =
17
+ "Approving lets this schedule's or webhook's runs act without asking, within the bot's permissions. Payments and permanent deletes are never done in runs.";
18
+
19
+ /** Whether `run` (the unattended port's `runOf`) is a schedule or webhook run. */
20
+ export const isGrantedRun = (run) => Boolean(run) && run.kind !== 'room';
21
+
22
+ /** Whether an outbound request is a payment or permanent delete. */
23
+ export function isForbiddenInRun(request) {
24
+ const category = request?.kind === 'outbound' ? request.outbound?.category : '';
25
+ return ACTION_KINDS.includes(category) && !isRememberableAction(category);
26
+ }
27
+
28
+ const READ_VERBS = new Set([
29
+ 'get',
30
+ 'list',
31
+ 'search',
32
+ 'read',
33
+ 'fetch',
34
+ 'retrieve',
35
+ 'find',
36
+ 'view',
37
+ 'describe',
38
+ 'lookup',
39
+ ]);
40
+ const DELETE_TOKENS = new Set(['delete', 'remove', 'destroy', 'erase', 'purge', 'wipe']);
41
+
42
+ /** Whether a connector/MCP tool deletes remote items without being a confirmed
43
+ * recoverable `trash`: in a run that counts as a permanent delete. Read-verb
44
+ * names (`list_deleted_items`) stay reads. `classified` is the outbound
45
+ * classification of the call (outbound-policy.mjs), if any. */
46
+ export function isRemoteDeleteInRun({ name, annotations }, classified) {
47
+ const toolName = String(name || '');
48
+ if (!toolName.startsWith('mcp__')) return false;
49
+ if (classified?.category === 'trash') return false;
50
+ if (classified?.category === 'delete') return true;
51
+ const bare = String(annotations?.mcpToolName || toolName.split('__').slice(2).join('__'))
52
+ .toLowerCase()
53
+ .replace(/-/g, '_');
54
+ const tokens = bare.split(/[^a-z0-9]+/).filter(Boolean);
55
+ if (READ_VERBS.has(tokens[0])) return false;
56
+ return tokens.some((token) => DELETE_TOKENS.has(token));
57
+ }
58
+
59
+ /** Whether `call` saves a schedule or webhook (the setup tool's `save_automation`). */
60
+ export const savesAutomation = (call) => call?.name === 'setup' && call.args?.action === 'save_automation';
61
+
62
+ /** The answer a granted run gets instead of a card: `{ approval }` to run the
63
+ * call, or `{ denial, approval }` for a payment or permanent delete. The
64
+ * `response` is the audit decision (`run-allowed`, `run-forbidden`). */
65
+ export function runAskOutcome({ name, request }) {
66
+ if (isForbiddenInRun(request)) {
67
+ return {
68
+ denial: `Error: tool "${name}" denied: ${RUN_FORBIDDEN_REASON} Report this to the user; do not retry it or reach the same result another way.`,
69
+ approval: { approved: false, response: 'run-forbidden', reason: RUN_FORBIDDEN_REASON },
70
+ };
71
+ }
72
+ return { approval: { approved: true, response: 'run-allowed', reason: 'schedule/webhook run' } };
73
+ }
@@ -11,7 +11,7 @@
11
11
  import { createHash } from 'node:crypto';
12
12
  import { appendFile, mkdir, readFile } from 'node:fs/promises';
13
13
  import { dirname, join } from 'node:path';
14
- import { parseJsonText, updateJsonAtomic } from '../../../../shared/atomic-file.mjs';
14
+ import { readJsonState, updateJsonAtomic } from '../../../../shared/atomic-file.mjs';
15
15
  import { ACTION_KINDS, isRememberableAction } from './outbound-policy.mjs';
16
16
 
17
17
  const ANY_AGENT = '*';
@@ -22,52 +22,64 @@ export const PERMISSION_MODES = Object.freeze(['ask', 'allow', 'block']);
22
22
  const field = (value) => String(value ?? '').slice(0, FIELD_LIMIT);
23
23
  const grantId = (agentId, action) =>
24
24
  `grant_${createHash('sha256').update(`${agentId}\n${action}`).digest('hex').slice(0, 24)}`;
25
+ const MODE_RANK = Object.freeze({ block: 0, ask: 1, allow: 2 });
26
+ export const NO_WIDEN_MESSAGE = 'This change would widen what the bot may do without asking; make it on the desktop.';
25
27
  const isGrant = (row) => ACTION_KINDS.includes(row?.action) && (row.mode === 'allow' || row.mode === 'block');
26
28
 
29
+ /** The effective mode among `grants`: the agent's own grant, else an every-agent one. */
30
+ function effectiveMode(grants, agentId, action) {
31
+ const rows = grants.filter((grant) => grant.action === action);
32
+ const own = rows.find((grant) => grant.agentId === agentId) || rows.find((grant) => grant.agentId === ANY_AGENT);
33
+ if (own?.mode === 'allow' && !isRememberableAction(action)) return 'ask';
34
+ return own?.mode || 'ask';
35
+ }
36
+
27
37
  export function createHumanGateStore(directory) {
28
38
  const grantsFile = join(directory, 'grants.json');
29
39
  const auditFile = join(directory, 'audit.jsonl');
30
40
  const grantsOf = (doc) => (Array.isArray(doc?.grants) ? doc.grants.filter(isGrant) : []);
31
- async function readGrants() {
32
- try {
33
- return grantsOf(parseJsonText(await readFile(grantsFile, 'utf8')));
34
- } catch (error) {
35
- if (error?.code === 'ENOENT') return [];
36
- throw error;
37
- }
38
- }
41
+ // A damaged file (empty, NUL, torn) is kept aside by readJsonState and reads
42
+ // as no grants: every action asks again, never "allowed". No `.bak` is ever
43
+ // written or restored (`restoreBackup: false`, also for a stale one left by
44
+ // an older build) — an older copy could bring back a revoked allow.
45
+ const readGrants = async () => grantsOf(await readJsonState(grantsFile, { fallback: null, restoreBackup: false }));
39
46
  async function updateGrants(mutate) {
40
47
  await mkdir(directory, { recursive: true });
41
48
  let result;
42
- await updateJsonAtomic(grantsFile, (doc) => {
43
- const out = mutate(grantsOf(doc));
44
- if (!out) return undefined;
45
- result = out.result;
46
- return { grants: out.grants };
47
- });
49
+ await updateJsonAtomic(
50
+ grantsFile,
51
+ (doc) => {
52
+ const out = mutate(grantsOf(doc));
53
+ if (!out) return undefined;
54
+ result = out.result;
55
+ return { grants: out.grants };
56
+ },
57
+ { restoreBackup: false }
58
+ );
48
59
  return result;
49
60
  }
50
61
  /** Set `agentId`'s mode for `action`; `ask` removes its grant. Returns the
51
62
  * grant, or null for `ask` and for an `allow` that may not exist. */
52
- async function setMode({ agentId, action, mode, device = '' }) {
63
+ async function setMode({ agentId, action, mode, device = '', noWiden = false }) {
53
64
  if (!ACTION_KINDS.includes(action) || !PERMISSION_MODES.includes(mode)) return null;
54
65
  if (mode === 'allow' && !isRememberableAction(action)) return null;
55
66
  const owner = field(agentId);
56
67
  const id = grantId(owner, action);
57
68
  const grant = mode === 'ask' ? null : { id, agentId: owner, action, mode, device: field(device), at: Date.now() };
58
- await updateGrants((grants) => ({
59
- grants: [...grants.filter((row) => row.id !== id), ...(grant ? [grant] : [])],
60
- result: grant,
61
- }));
69
+ await updateGrants((grants) => {
70
+ const next = [...grants.filter((row) => row.id !== id), ...(grant ? [grant] : [])];
71
+ // `noWiden` (a remote client's call): judged on the effective mode inside the
72
+ // same locked read-modify-write, so a block landing meanwhile is never lifted.
73
+ if (noWiden && MODE_RANK[effectiveMode(next, owner, action)] > MODE_RANK[effectiveMode(grants, owner, action)]) {
74
+ throw new Error(NO_WIDEN_MESSAGE);
75
+ }
76
+ return { grants: next, result: grant };
77
+ });
62
78
  return grant;
63
79
  }
64
80
  /** `allow`, `block` or `ask` for `agentId` + `action`; the agent's own grant wins. */
65
81
  async function modeFor({ agentId, action }) {
66
- const grants = (await readGrants()).filter((grant) => grant.action === action);
67
- const own =
68
- grants.find((grant) => grant.agentId === agentId) || grants.find((grant) => grant.agentId === ANY_AGENT);
69
- if (own?.mode === 'allow' && !isRememberableAction(action)) return 'ask';
70
- return own?.mode || 'ask';
82
+ return effectiveMode(await readGrants(), agentId, action);
71
83
  }
72
84
  return {
73
85
  setMode,
@@ -151,16 +163,32 @@ export async function agentPermissions(agentId) {
151
163
  }
152
164
 
153
165
  /** Set one row of that table (capability setAgentPermission); returns the table. */
154
- export async function setAgentPermission(agentId, action, mode) {
166
+ export async function setAgentPermission(agentId, action, mode, options) {
155
167
  const agent = requiredAgentId(agentId);
156
168
  if (!ACTION_KINDS.includes(action)) throw new Error(`action must be one of ${ACTION_KINDS.join(', ')}`);
157
169
  if (!PERMISSION_MODES.includes(mode)) throw new Error(`mode must be one of ${PERMISSION_MODES.join(', ')}`);
158
170
  if (mode === 'allow' && !isRememberableAction(action)) throw new Error(`${action} always asks; it cannot be allowed`);
159
171
  const store = await humanGateStore();
160
- await store.setMode({ agentId: agent, action, mode });
172
+ await store.setMode({ agentId: agent, action, mode, noWiden: options?.noWiden === true });
161
173
  return store.permissions(agent);
162
174
  }
163
175
 
176
+ const BROWSER_AUDIT_DECISIONS = Object.freeze(['approve', 'deny', 'granted', 'remembered', 'run-allowed', 'blocked']);
177
+
178
+ /** Audit one Browser Use approval of a bot (capability recordAgentApprovalAudit):
179
+ * the bot, the action kind and what happened — `approve` / `deny` for the card,
180
+ * `granted` when "always allow" was saved, `remembered` when a saved allow let
181
+ * a step run unasked. Never a page address, target or value. */
182
+ export async function recordAgentApprovalAudit(agentId, action, decision) {
183
+ const agent = requiredAgentId(agentId);
184
+ if (!ACTION_KINDS.includes(action)) throw new Error(`action must be one of ${ACTION_KINDS.join(', ')}`);
185
+ if (!BROWSER_AUDIT_DECISIONS.includes(decision)) {
186
+ throw new Error(`decision must be one of ${BROWSER_AUDIT_DECISIONS.join(', ')}`);
187
+ }
188
+ await (await humanGateStore()).recordAudit({ agentId: agent, kind: 'browser', action, tool: 'browser', decision });
189
+ return null;
190
+ }
191
+
164
192
  /** Replace the process store (tests); null restores the default. */
165
193
  export function setHumanGateStore(store) {
166
194
  processStore = store || null;
@@ -0,0 +1,15 @@
1
+ // session/human-gate/unattended-port.mjs
2
+ // The port unattended runs (schedule, webhook, room member) register
3
+ // (session-runtime/held-requests.mjs): `{ runOf(sessionId) → { kind, agentId,
4
+ // name } | null, hold(request, { signal }) → Promise<approval>,
5
+ // humanNeeded(sessionId, outcome, site) → Promise<boolean> }` — the last ends
6
+ // a schedule/webhook run that would need a takeover. gate.mjs re-exports it.
7
+ let unattendedPort = null;
8
+
9
+ export function setUnattendedApprovalPort(port) {
10
+ unattendedPort = port || null;
11
+ }
12
+
13
+ export function unattendedApprovalPort() {
14
+ return unattendedPort;
15
+ }
@@ -838,10 +838,13 @@ function compactionTelemetryRecord(sessionRef, policy, meta, prev) {
838
838
  meta.compactError ?? meta.lastError,
839
839
  prev.lastError
840
840
  ),
841
+ // The last compaction's wall time: replaced only by a stage that reports
842
+ // a finished compaction's duration; a plain pre-send check keeps it (the
843
+ // other timing fields stay with `...prev`).
841
844
  lastDurationMs:
842
845
  meta.durationMs != null && Number.isFinite(Number(meta.durationMs))
843
846
  ? Math.max(0, Math.round(Number(meta.durationMs)))
844
- : null,
847
+ : (prev.lastDurationMs ?? null),
845
848
  compactCount: (prev.compactCount || 0) + (changed ? 1 : 0),
846
849
  singleShotConsumed:
847
850
  policy.singleShot === true && meta.stage === 'compacting' ? true : prev.singleShotConsumed === true,