mixdog 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/README.md +8 -4
  2. package/package.json +8 -8
  3. package/scripts/native-release-status.mjs +238 -0
  4. package/scripts/verify-runtime-dependency-versions.mjs +158 -0
  5. package/src/agents/reviewer/AGENT.md +5 -7
  6. package/src/agents/reviewer/agent.json +1 -1
  7. package/src/defaults/shipped-history.json +9 -1
  8. package/src/defaults/skills/browser-use/SKILL.md +6 -3
  9. package/src/defaults/skills/local-provider/SKILL.md +33 -6
  10. package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
  11. package/src/defaults/skills/setup/SKILL.md +23 -1
  12. package/src/defaults/skills/setup/references/actions.md +50 -3
  13. package/src/defaults/skills/setup/references/surfaces.md +23 -3
  14. package/src/defaults/skills/setup-guide/SKILL.md +7 -3
  15. package/src/defaults/skills/visualize/SKILL.md +9 -6
  16. package/src/defaults/skills/visualize/references/craft.md +8 -1
  17. package/src/help.mjs +1 -1
  18. package/src/lib/keychain-cjs.cjs +4 -1
  19. package/src/lib/rules-builder.cjs +14 -7
  20. package/src/rules/agent-mode/AGENT_MODE.md +8 -5
  21. package/src/rules/agent-mode/MIXY.md +6 -8
  22. package/src/rules/lead/LEAD.md +7 -10
  23. package/src/rules/shared/10-tool-workflow.md +4 -0
  24. package/src/rules/shared/20-research.md +2 -0
  25. package/src/rules/shared/45-destructive.md +5 -1
  26. package/src/rules/shared/60-verification.md +6 -4
  27. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
  29. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
  30. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
  31. package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
  32. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
  33. package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
  34. package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
  35. package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
  36. package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
  37. package/src/runtime/agent/orchestrator/config.mjs +11 -0
  38. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
  39. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
  40. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
  41. package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
  42. package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
  43. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
  44. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
  45. package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
  46. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  47. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
  48. package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
  49. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
  50. package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
  51. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
  52. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  53. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
  54. package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
  55. package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
  56. package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
  57. package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
  58. package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
  59. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
  60. package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
  61. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
  62. package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
  64. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
  65. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
  66. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
  67. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
  68. package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
  69. package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
  70. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
  72. package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
  73. package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
  74. package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
  75. package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
  76. package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
  77. package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
  78. package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
  79. package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
  80. package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
  81. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
  82. package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
  83. package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
  85. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
  86. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
  87. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
  89. package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
  90. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
  92. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
  93. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
  94. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
  95. package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
  96. package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
  97. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
  98. package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
  99. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
  100. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
  101. package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
  102. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
  103. package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
  104. package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
  105. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
  106. package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
  107. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
  108. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
  109. package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
  110. package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
  111. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
  112. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
  113. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
  114. package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
  115. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
  116. package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
  117. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
  118. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
  119. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
  120. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
  121. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
  122. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
  123. package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
  124. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
  125. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
  126. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
  127. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
  128. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
  129. package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
  130. package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
  131. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
  132. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
  133. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
  134. package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
  135. package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
  136. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
  137. package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
  138. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
  139. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
  140. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
  143. package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
  144. package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
  145. package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
  146. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
  147. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
  148. package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
  149. package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
  150. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
  151. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
  152. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
  153. package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
  154. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
  155. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
  156. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
  157. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
  158. package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
  159. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
  160. package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
  161. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
  163. package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
  166. package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
  167. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
  168. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
  169. package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
  170. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
  171. package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
  172. package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
  173. package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
  174. package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
  175. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
  176. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
  177. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
  178. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
  179. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
  180. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
  181. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
  182. package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
  183. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
  184. package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
  185. package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
  186. package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
  189. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
  190. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
  191. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
  196. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
  197. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
  198. package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
  199. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
  200. package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
  201. package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
  202. package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
  203. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
  204. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
  205. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
  206. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
  207. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
  208. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
  209. package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
  210. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
  211. package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
  212. package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
  213. package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
  214. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
  215. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
  216. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
  217. package/src/runtime/attachments/attachment-reference.mjs +155 -0
  218. package/src/runtime/attachments/legacy-office.mjs +110 -0
  219. package/src/runtime/attachments/limits.mjs +5 -1
  220. package/src/runtime/attachments/office-extract.mjs +3 -6
  221. package/src/runtime/attachments/pdf-extract.mjs +84 -10
  222. package/src/runtime/attachments/prompt-restore.mjs +70 -0
  223. package/src/runtime/attachments/store.mjs +109 -33
  224. package/src/runtime/browser-bridge/client.mjs +73 -20
  225. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  226. package/src/runtime/channels/lib/scheduler.mjs +14 -7
  227. package/src/runtime/channels/lib/state-file.mjs +15 -15
  228. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
  229. package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
  230. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
  231. package/src/runtime/channels/lib/worker-main.mjs +3 -2
  232. package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
  233. package/src/runtime/effort-judge/judge-client.mjs +66 -16
  234. package/src/runtime/effort-judge/model-install.mjs +47 -7
  235. package/src/runtime/local-provider/asset-installer.mjs +10 -4
  236. package/src/runtime/local-provider/catalog.mjs +5 -2
  237. package/src/runtime/local-provider/registered-models.mjs +64 -27
  238. package/src/runtime/local-provider/server.mjs +2 -3
  239. package/src/runtime/media/adapters/image-api.mjs +89 -0
  240. package/src/runtime/media/auth.mjs +64 -1
  241. package/src/runtime/media/catalog.mjs +66 -4
  242. package/src/runtime/media/jobs.mjs +4 -0
  243. package/src/runtime/media/lanes.mjs +46 -5
  244. package/src/runtime/media/media-usage.mjs +17 -0
  245. package/src/runtime/media/renditions.mjs +15 -15
  246. package/src/runtime/media/store.mjs +36 -35
  247. package/src/runtime/media/tool.mjs +8 -1
  248. package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
  249. package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
  250. package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
  251. package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
  252. package/src/runtime/memory/lib/pg/process.mjs +8 -4
  253. package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
  254. package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
  255. package/src/runtime/office/core/journal.mjs +7 -14
  256. package/src/runtime/office/design/library/design-library-core.mjs +4 -16
  257. package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
  258. package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
  259. package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
  260. package/src/runtime/office/pdf/pdf-render.mjs +30 -18
  261. package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
  262. package/src/runtime/office/portable/portable-soffice.mjs +135 -29
  263. package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
  264. package/src/runtime/shared/agent-avatar.mjs +3 -2
  265. package/src/runtime/shared/agent-kind-migration.mjs +200 -0
  266. package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
  267. package/src/runtime/shared/agent-message-envelope.mjs +44 -0
  268. package/src/runtime/shared/agent-reap-state.d.mts +4 -0
  269. package/src/runtime/shared/agent-room-host.d.mts +31 -0
  270. package/src/runtime/shared/agent-room-host.mjs +58 -0
  271. package/src/runtime/shared/agent-store.mjs +24 -7
  272. package/src/runtime/shared/agent-task-starts.mjs +27 -0
  273. package/src/runtime/shared/atomic-file.d.mts +50 -0
  274. package/src/runtime/shared/atomic-file.mjs +394 -132
  275. package/src/runtime/shared/background-tasks.mjs +7 -4
  276. package/src/runtime/shared/bot-hops.mjs +35 -0
  277. package/src/runtime/shared/bot-split-migration.mjs +1 -1
  278. package/src/runtime/shared/bounded-download.mjs +4 -2
  279. package/src/runtime/shared/config.mjs +39 -9
  280. package/src/runtime/shared/defaults-separation.mjs +6 -2
  281. package/src/runtime/shared/delegated-approval-host.mjs +74 -0
  282. package/src/runtime/shared/err-text.mjs +4 -0
  283. package/src/runtime/shared/error-presentation.mjs +22 -4
  284. package/src/runtime/shared/file-has-data.mjs +28 -0
  285. package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
  286. package/src/runtime/shared/pending-agent-work.mjs +54 -0
  287. package/src/runtime/shared/process-lifecycle.mjs +8 -12
  288. package/src/runtime/shared/projects.mjs +50 -41
  289. package/src/runtime/shared/provider-accounts.mjs +17 -12
  290. package/src/runtime/shared/redact-directories.mjs +18 -0
  291. package/src/runtime/shared/remove-directory.mjs +28 -0
  292. package/src/runtime/shared/routine-trigger.d.mts +6 -1
  293. package/src/runtime/shared/routine-trigger.mjs +14 -6
  294. package/src/runtime/shared/run-child.d.mts +3 -0
  295. package/src/runtime/shared/run-child.mjs +24 -3
  296. package/src/runtime/shared/session-baseline-store.mjs +8 -2
  297. package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
  298. package/src/runtime/shared/session-sidebar-host.mjs +16 -3
  299. package/src/runtime/shared/singleton-owner.mjs +7 -6
  300. package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
  301. package/src/runtime/shared/staged-update.mjs +4 -2
  302. package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
  303. package/src/runtime/shared/tool-execution-contract.mjs +10 -0
  304. package/src/runtime/shared/tool-work-units.mjs +12 -9
  305. package/src/runtime/shared/transcript-metadata.mjs +28 -3
  306. package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
  307. package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
  308. package/src/runtime/shared/turn-snapshot.mjs +277 -124
  309. package/src/runtime/shared/user-cwd.mjs +9 -4
  310. package/src/runtime/shared/user-data-guard.mjs +92 -61
  311. package/src/runtime/shared/utf8-cut.mjs +10 -0
  312. package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
  313. package/src/runtime/shared/worktrees/contract.mjs +11 -3
  314. package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
  315. package/src/runtime/shared/worktrees/manager.mjs +419 -52
  316. package/src/runtime/shared/worktrees/registry.mjs +29 -27
  317. package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
  318. package/src/runtime/tidy/core-install.mjs +26 -4
  319. package/src/runtime/tidy/install.mjs +91 -23
  320. package/src/runtime/tidy/process.mjs +41 -8
  321. package/src/runtime/tidy/resolve.mjs +10 -7
  322. package/src/runtime/web-search/lib/config.mjs +4 -16
  323. package/src/session-runtime/agent-home-delivery.mjs +542 -204
  324. package/src/session-runtime/agent-profile-changes.mjs +16 -0
  325. package/src/session-runtime/agent-room-delivery.mjs +81 -35
  326. package/src/session-runtime/agent-task-sessions.mjs +811 -179
  327. package/src/session-runtime/agent-tool-routing.mjs +1 -0
  328. package/src/session-runtime/boot/apis.mjs +48 -24
  329. package/src/session-runtime/boot/config.mjs +1 -0
  330. package/src/session-runtime/boot/facade.mjs +16 -7
  331. package/src/session-runtime/boot/infrastructure.mjs +14 -2
  332. package/src/session-runtime/boot/providers.mjs +2 -2
  333. package/src/session-runtime/boot/shared.mjs +12 -6
  334. package/src/session-runtime/boot/tools.mjs +23 -3
  335. package/src/session-runtime/bot-status.mjs +432 -0
  336. package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
  337. package/src/session-runtime/config-lifecycle.mjs +60 -55
  338. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
  339. package/src/session-runtime/held-requests.mjs +31 -33
  340. package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
  341. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
  342. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
  343. package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
  344. package/src/session-runtime/lifecycle/teardown.mjs +11 -3
  345. package/src/session-runtime/mcp-glue/connect.mjs +100 -186
  346. package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
  347. package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
  348. package/src/session-runtime/media-api.mjs +5 -2
  349. package/src/session-runtime/model-recency.mjs +1 -0
  350. package/src/session-runtime/model-route/route-tuning.mjs +4 -1
  351. package/src/session-runtime/model-route/set-route.mjs +61 -1
  352. package/src/session-runtime/owned-automation-run.mjs +8 -1
  353. package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
  354. package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
  355. package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
  356. package/src/session-runtime/route-resolution.mjs +16 -8
  357. package/src/session-runtime/route-state.mjs +1 -59
  358. package/src/session-runtime/routine-home-turn.mjs +5 -3
  359. package/src/session-runtime/runtime-bootstrap.mjs +5 -3
  360. package/src/session-runtime/runtime-paths.mjs +11 -5
  361. package/src/session-runtime/runtime-review-api.mjs +4 -1
  362. package/src/session-runtime/runtime-tunables.mjs +6 -0
  363. package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
  364. package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
  365. package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
  366. package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
  367. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
  368. package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
  369. package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
  370. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
  371. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
  372. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
  373. package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
  374. package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
  375. package/src/session-runtime/services/agent-tool.mjs +6 -0
  376. package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
  377. package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
  378. package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
  379. package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
  380. package/src/session-runtime/services/plugin-admin.mjs +26 -26
  381. package/src/session-runtime/services/provider-admin.mjs +26 -6
  382. package/src/session-runtime/services/schedule-access.mjs +33 -4
  383. package/src/session-runtime/services/seeds.mjs +4 -3
  384. package/src/session-runtime/services/usage-dashboard.mjs +1 -1
  385. package/src/session-runtime/services/usage-stats-model.mjs +2 -1
  386. package/src/session-runtime/services/worktree-access.mjs +27 -0
  387. package/src/session-runtime/session-create/materialize.mjs +2 -3
  388. package/src/session-runtime/session-create/session-options.mjs +15 -2
  389. package/src/session-runtime/session-hooks.mjs +9 -17
  390. package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
  391. package/src/session-runtime/settings-compaction-api.mjs +86 -39
  392. package/src/session-runtime/setup-tool/executor.mjs +12 -2
  393. package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
  394. package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
  395. package/src/session-runtime/skills-api.mjs +12 -5
  396. package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
  397. package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
  398. package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
  399. package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
  400. package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
  401. package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
  402. package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
  403. package/src/session-runtime/tool-policy-refresh.mjs +48 -369
  404. package/src/session-runtime/tool-surface.mjs +10 -4
  405. package/src/session-runtime/turn/session-ops.mjs +8 -0
  406. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
  407. package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
  408. package/src/session-runtime/turn/turn-run.mjs +7 -2
  409. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
  410. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
  411. package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
  412. package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
  413. package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
  414. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
  415. package/src/session-runtime/workspace/transition-steps.mjs +1 -18
  416. package/src/session-runtime/workspace/transitions.mjs +279 -67
  417. package/src/session-runtime/workspace/workspace-state.mjs +7 -6
  418. package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
  419. package/src/standalone/daemon-agent-control.mjs +4 -1
  420. package/src/standalone/daemon-lag-profiler.mjs +3 -1
  421. package/src/standalone/daemon-session-control.mjs +173 -15
  422. package/src/standalone/daemon-telemetry.mjs +9 -0
  423. package/src/standalone/daemon.mjs +33 -10
  424. package/src/standalone/local-session-runtime.mjs +33 -16
  425. package/src/standalone/session-client.mjs +7 -2
  426. package/src/standalone/session-protocol.mjs +8 -0
  427. package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
  428. package/src/standalone/session-service/agent-route-follow.mjs +69 -0
  429. package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
  430. package/src/standalone/session-service.mjs +21 -0
  431. package/src/standalone/session-state-projection.mjs +19 -9
  432. package/src/standalone/session-tool-output.mjs +36 -9
  433. package/src/standalone/staged-import.mjs +47 -0
  434. package/src/tui/App.jsx +2 -0
  435. package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
  436. package/src/tui/app/message-selector.mjs +15 -2
  437. package/src/tui/app/model-options.mjs +16 -2
  438. package/src/tui/app/model-picker.mjs +2 -1
  439. package/src/tui/app/route-pickers.mjs +3 -1
  440. package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
  441. package/src/tui/app/slash-commands.mjs +1 -1
  442. package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
  443. package/src/tui/dist/index.mjs +617 -369
  444. package/src/tui/paste-attachments.mjs +0 -1
  445. package/src/tui/paste-text-policy.mjs +4 -5
  446. package/src/tui/prompt-history-store.mjs +16 -21
  447. package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
  448. package/src/tui/session/context-state.mjs +4 -0
  449. package/src/tui/session/notice-surface.mjs +3 -0
  450. package/src/tui/session/session-api/intake/abort.mjs +52 -18
  451. package/src/tui/session/session-api/intake/rewind.mjs +39 -4
  452. package/src/tui/session/session-api/media.mjs +2 -1
  453. package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
  454. package/src/tui/session/session-api/routes-write.mjs +203 -0
  455. package/src/tui/session/session-api/routes.mjs +23 -247
  456. package/src/tui/session/session-api/settings.mjs +14 -1
  457. package/src/tui/session/session-flow/drain.mjs +28 -3
  458. package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
  459. package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
  460. package/src/tui/session/session-flow/steering.mjs +8 -3
  461. package/src/tui/session/tool-approval.mjs +142 -14
  462. package/src/tui/session/transcript-intake.mjs +2 -0
  463. package/src/tui/session/turn.mjs +23 -14
  464. package/src/tui/transcript-tool-failures.mjs +8 -2
  465. package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
  466. package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
  467. package/src/rules/agent-mode/ROUTINE.md +0 -6
  468. package/src/session-runtime/room-member-gate.mjs +0 -48
@@ -1,6 +1,10 @@
1
1
  // Text/token/message helpers and secret redaction for compaction.
2
2
  import { createHash } from 'node:crypto';
3
- import { attachmentTextForPart, isAttachmentReference, isPermanentAttachmentError } from '../../../../attachments/store.mjs';
3
+ import {
4
+ attachmentTextForPart,
5
+ isAttachmentReference,
6
+ isPermanentAttachmentError,
7
+ } from '../../../../attachments/store.mjs';
4
8
  import { estimateMessagesTokens } from '../context-utils.mjs';
5
9
  import { inlineFileKind } from '../../../../shared/inline-file-kind.mjs';
6
10
 
@@ -44,7 +48,7 @@ const SENSITIVE_RAW_KEY_RES = [
44
48
  // string. Consumes the WHOLE value after the key — spaces, `Bearer `/`Basic `
45
49
  // scheme words, quoted values with internal spaces, and `;`-separated cookie
46
50
  // pairs — so no secret fragment survives.
47
- function redactRawSecretString(text) {
51
+ export function redactRawSecretString(text) {
48
52
  let value = String(text ?? '');
49
53
  if (!value) return value;
50
54
  for (const keyRe of SENSITIVE_RAW_KEY_RES) {
@@ -5,6 +5,7 @@
5
5
  import { SUMMARY_PREFIX_ANCHOR } from './summary-anchor.mjs';
6
6
  import { isImportedMessage } from '../imported-message.mjs';
7
7
  import {
8
+ CONTROL_MARKER_RE,
8
9
  isInternalRuntimeNotificationText,
9
10
  isModelVisibleToolCompletionWrapper,
10
11
  } from '../../../../shared/tool-execution-contract.mjs';
@@ -86,6 +87,9 @@ export function isContextAckMessage(message, previous = null) {
86
87
  const SYNTHETIC_USER_SOURCES = new Set([
87
88
  // A schedule's output posted into its owner's home thread: untrusted data.
88
89
  'schedule-result',
90
+ // A delivered task or channel notification: turn input, never the human's
91
+ // request, whatever its text looks like.
92
+ 'task-notification',
89
93
  'goal-continuation',
90
94
  'goal-closeout',
91
95
  'compact-context',
@@ -132,7 +136,6 @@ export function isActualUserInstructionMessage(message) {
132
136
  // isActualUserInstructionMessage deliberately keeps treating "[truncated]" as
133
137
  // an instruction (compaction tail selection depends on it), so the real-turn
134
138
  // predicate below recognizes it on its own.
135
- const CONTROL_MARKER_RE = /^\[(?:truncated|request interrupted(?: by user(?: for tool use)?| by process restart)?)\]$/i;
136
139
 
137
140
  /** Whether a message stands for real human conversation: a compact summary
138
141
  * (it replaced earlier human turns), imported prose, or the user's own
@@ -226,7 +226,7 @@ const recordExists = (dir, sessionId) => existsSync(join(dir, `${sessionId}.json
226
226
  * since `epoch` was captured wins. Any outcome but a rename removes the temp
227
227
  * file. */
228
228
  async function publishArchive({ dir, path, sessionId, text, epoch }) {
229
- const tmp = join(dir, `.${basename(path)}.${randomBytes(8).toString('hex')}.tmp`);
229
+ const tmp = join(dir, `${basename(path)}.mixdog-tmp-${randomBytes(4).toString('hex')}`);
230
230
  const removeTmp = () => {
231
231
  try {
232
232
  unlinkSync(tmp);
@@ -260,24 +260,55 @@ function messageImageAllowance(m) {
260
260
  if (!m || typeof m !== 'object') return 0;
261
261
  return messageImageDescriptors(m).reduce((sum, descriptor) => sum + imageDescriptorAllowance(descriptor), 0);
262
262
  }
263
- // Inline document (PDF) allowance: Anthropic bills ~1,500-3,000 tokens per
264
- // page and a typical PDF page is ~50-100KB, so ~bytes/16 with a floor and a
265
- // cap. The base64 payload itself is excluded from text estimates (see
263
+ // Inline document (PDF) allowance. A native PDF reader turns every page into
264
+ // one image AND sends the text it extracts from that page (the provider PDF
265
+ // docs: each page is billed as an image plus its text, typically
266
+ // 1,500-3,000 text tokens for a dense page). The page image is bounded like
267
+ // any image — the reader downscales it to the vision ceiling — so a page costs
268
+ // at most IMAGE_TOKEN_ALLOWANCE for its picture whatever its file size. A
269
+ // scanned page has no text layer and costs only its picture.
270
+ //
271
+ // The base64 payload itself is excluded from text estimates (see
266
272
  // jsonFallbackFromPart), so this allowance is the document's entire cost.
267
273
  const FILE_TOKEN_ALLOWANCE_FLOOR = 1_500;
268
274
  const FILE_MAX_TOKEN_ALLOWANCE = 300_000;
269
- // A PDF that records its page count (intake and the read tool do) is priced per
270
- // page; the size-based figure stays as the floor, so a dense scan still costs
271
- // at least its bytes. Inline and stored parts carry the same count.
272
- const FILE_TOKENS_PER_PAGE = 2_500;
275
+ // Extracted PDF text is priced at two characters per token: the low end of
276
+ // the documented per-page range for Latin text, and close to the measured
277
+ // density of Korean/CJK text, so neither is read low.
278
+ const PDF_TEXT_CHARS_PER_TOKEN = 2;
279
+ // A PDF that records its page count but not its text layer (older parts, an
280
+ // inline part from a path that did not measure it) keeps the documented
281
+ // mid-range per-page figure.
282
+ const FILE_TOKENS_PER_PAGE_UNMEASURED = 2_500;
273
283
  // A PDF that lowers to text costs at most its extraction budget (~4 bytes/token).
274
284
  const PDF_TEXT_MAX_TOKENS = Math.ceil(PDF_TEXT_MAX_BYTES / 4);
285
+ // Without a page count only the file size is known: ~bytes/16 (a typical page
286
+ // is 50-100KB) with a floor and a cap. Never used once pages are known — a
287
+ // scan's bytes are its image data, which the reader downscales, so bytes/16
288
+ // priced a 5 MB, 37-page scan at ~330k tokens instead of ~58k.
275
289
  function fileDescriptorAllowance(descriptor, nativePdf = true) {
276
- const bySize = Math.ceil((descriptor.sizeBytes || 0) / 16);
277
- const byPage = (descriptor.pageCount || 0) * FILE_TOKENS_PER_PAGE;
278
- const native = Math.min(FILE_MAX_TOKEN_ALLOWANCE, Math.max(FILE_TOKEN_ALLOWANCE_FLOOR, bySize, byPage));
290
+ const pages = descriptor.pageCount || 0;
291
+ let native;
292
+ if (pages > 0 && Number.isFinite(descriptor.textChars)) {
293
+ native = pages * IMAGE_TOKEN_ALLOWANCE + Math.ceil(descriptor.textChars / PDF_TEXT_CHARS_PER_TOKEN);
294
+ } else if (pages > 0) {
295
+ native = pages * FILE_TOKENS_PER_PAGE_UNMEASURED;
296
+ } else {
297
+ native = Math.max(FILE_TOKEN_ALLOWANCE_FLOOR, Math.ceil((descriptor.sizeBytes || 0) / 16));
298
+ }
299
+ native = Math.min(FILE_MAX_TOKEN_ALLOWANCE, native);
279
300
  return nativePdf ? native : Math.min(native, PDF_TEXT_MAX_TOKENS);
280
301
  }
302
+
303
+ /** The tokens one PDF part costs inline (native reader unless `nativePdf` is false). */
304
+ export function pdfPartAllowance(part, { nativePdf = true } = {}) {
305
+ return contentFileDescriptors([part]).reduce((sum, d) => sum + fileDescriptorAllowance(d, nativePdf), 0);
306
+ }
307
+
308
+ /** The tokens one image part costs inline. */
309
+ export function imagePartAllowance(part) {
310
+ return contentImageDescriptors([part]).reduce((sum, d) => sum + imageDescriptorAllowance(d), 0);
311
+ }
281
312
  /**
282
313
  * How many tokens the native-PDF meter overstates `messages` for a provider
283
314
  * that takes PDFs as text (nativePdf === false); subtract it from the
@@ -0,0 +1,240 @@
1
+ // session/human-gate/delegation.mjs
2
+ // Who a delegated session works for, recorded when it is created and re-bound
3
+ // only when another session sends it work (rebindDelegation): a sub-agent (any
4
+ // depth) carries `delegation` on its session record —
5
+ // { rootSessionId, via, path }
6
+ // `rootSessionId` is the top-level conversation the work started in; `via`
7
+ // says what it is: 'coding' (a coding Lead's session or a task session a bot
8
+ // created) or 'bot' (a bot's own home chat or room member session); either way
9
+ // the agents under it mirror their ordinary approval cards into that root, but
10
+ // a 'bot' root's sub-agents are no bot: they ask no bot approval and use no
11
+ // bot grant. `path` is the agent steps from the root down
12
+ // ({ kind: 'agent', agent }).
13
+ // A task session a bot created is an ordinary coding Lead session: top-level,
14
+ // never delegated from the bot, and its sub-agents are rooted at it.
15
+ // It is copied from the parent's record (or bound from the parent itself when
16
+ // the parent is top-level) by the host at creation — never from tool input —
17
+ // so no parent walk happens at gate time. When a session sends an existing
18
+ // worker a new request, the record is re-bound to that sender's root. A
19
+ // record from before this field existed, or one stored in an older shape (a
20
+ // task step in its path, a home/room/run/unknown `via`), is derived again from
21
+ // its parent by the same rules (backfillOf) and kept on the in-memory record,
22
+ // which its next save persists. A session that has a parent link but no valid
23
+ // or derivable root has no delegation (it is no bot's own session either).
24
+ // Work a schedule/webhook run started (a sub-agent, a task session) carries the
25
+ // `run` it was started under on its record, stamped at creation from its
26
+ // creator (never from creation options or tool input) and re-stamped, whether
27
+ // or not a root resolves, when another session sends it work (`run: null` =
28
+ // attended). A record from before the stamp keeps the run it recorded (an old
29
+ // `via: 'run'` delegation, an `agent:<bot>@run` task source, or its parent's
30
+ // run when it recorded no delegation): that is stamped before its delegation is
31
+ // derived again, which would drop it.
32
+ import { agentIdOfHomeSession } from '../agent-home.mjs';
33
+ import { roomMemberOfSession } from '../agent-room.mjs';
34
+ import { loadSession } from '../store/load-session.mjs';
35
+ import { _liveSessions } from '../store/live-state.mjs';
36
+ import { saveSessionAsync } from '../store/save-worker.mjs';
37
+ import { isTurnInFlight, runtimeState } from '../manager/runtime-state.mjs';
38
+ import { unattendedApprovalPort } from './unattended-port.mjs';
39
+
40
+ const VIA = new Set(['coding', 'bot']);
41
+ const MAX_PATH = 32;
42
+ const clean = (value) => (typeof value === 'string' ? value.trim() : '');
43
+ const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
44
+
45
+ const agentOwned = (record) =>
46
+ clean(record?.owner).toLowerCase() === 'agent' || clean(record?.visibility).toLowerCase() === 'agent-only';
47
+
48
+ /** The session that spawned `record`, or '' for a top-level session (a task
49
+ * session a bot created is top-level, like any coding Lead). */
50
+ function parentOf(record) {
51
+ return agentOwned(record) ? clean(record.parentSessionId) || clean(record.ownerSessionId) : '';
52
+ }
53
+
54
+ /** The step a new session adds to its parent's path. */
55
+ const stepOf = (record) => ({ kind: 'agent', agent: clean(record.agent) });
56
+
57
+ /** A recorded delegation, or null when it is malformed or carries a step that
58
+ * is not an agent (a stored task step: derived again, never trusted). */
59
+ function validDelegation(value) {
60
+ if (!isRecord(value) || !clean(value.rootSessionId) || !VIA.has(value.via)) return null;
61
+ if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH) return null;
62
+ if (!value.path.every((step) => step?.kind === 'agent')) return null;
63
+ return value;
64
+ }
65
+
66
+ /** The recorded delegation of `record`, else its legacy backfill; null when
67
+ * it has none and none can be derived. */
68
+ function delegationOfRecord(id, record, depth) {
69
+ stampRun(id, record);
70
+ return validDelegation(record.delegation) || backfillOf(id, record, depth);
71
+ }
72
+
73
+ const RUN_TASK_SOURCE = /^agent:([A-Za-z0-9_-]+)@run$/;
74
+ const runStamp = (kind, agentId, name) =>
75
+ clean(agentId)
76
+ ? { kind: kind === 'webhook' ? 'webhook' : 'schedule', agentId: clean(agentId), name: String(name || '') }
77
+ : null;
78
+
79
+ /** The run a record from before the `run` stamp was started by, from what it
80
+ * recorded itself: an old `via: 'run'` delegation, a host-set `@run` task
81
+ * source, else — a sub-agent that recorded no delegation — its parent's run. */
82
+ function legacyRunOf(id, record, depth) {
83
+ const old = record.delegation;
84
+ if (isRecord(old) && old.via === 'run' && isRecord(old.run)) {
85
+ return runStamp(old.run.kind, old.rootAgentId || agentIdOfHomeSession(old.rootSessionId), old.run.name);
86
+ }
87
+ const taskBot =
88
+ !agentOwned(record) && clean(record.sourceType) === 'lead'
89
+ ? RUN_TASK_SOURCE.exec(clean(record.sourceName))?.[1]
90
+ : '';
91
+ if (taskBot) return runStamp('schedule', taskBot, '');
92
+ const parent = parentOf(record);
93
+ if (!parent || parent === id || isRecord(old) || depth >= MAX_PATH) return null;
94
+ const parentRecord = loadRecord(parent);
95
+ return parentRecord ? stampRun(parent, parentRecord, depth + 1) : null;
96
+ }
97
+
98
+ /** The `run` of `record`: its stamp (`null` = attended), else the run it
99
+ * recorded before stamps existed, kept on the live record. */
100
+ function stampRun(id, record, depth = 0) {
101
+ if (record.run !== undefined) return isRecord(record.run) ? record.run : null;
102
+ const run = legacyRunOf(id, record, depth);
103
+ if (run && _liveSessions.get(id) === record) record.run = run;
104
+ return run;
105
+ }
106
+
107
+ /** The run stamped on session `sessionId` (human-gate/delegation.mjs), or null. */
108
+ export function runStampOf(sessionId) {
109
+ const id = clean(sessionId);
110
+ const record = id ? loadRecord(id) : null;
111
+ return record ? stampRun(id, record) : null;
112
+ }
113
+
114
+ /** The schedule/webhook run `sessionId` is, or inherited from the session that
115
+ * started it, as `{ kind, agentId, name }`; null otherwise (a room member's
116
+ * own session is no run here). */
117
+ function runOfSession(sessionId) {
118
+ const run = unattendedApprovalPort()?.runOf?.(sessionId);
119
+ return run && run.kind !== 'room' ? { kind: run.kind, agentId: run.agentId, name: String(run.name || '') } : null;
120
+ }
121
+
122
+ /**
123
+ * The `run` to stamp on a session being created with `opts`: the run its
124
+ * creator (the spawning session, or the chat a task session was created from)
125
+ * is or inherited, else null. Work a run started stays the run's, however long
126
+ * ago the run ended; it is read from the creator, never from tool input.
127
+ */
128
+ export function runForNewSession(opts = {}) {
129
+ const creator = parentOf(opts) || clean(opts.taskOrigin?.sessionId);
130
+ return creator && creator !== clean(opts.id) ? runOfSession(creator) : null;
131
+ }
132
+
133
+ function loadRecord(sessionId) {
134
+ try {
135
+ return loadSession(sessionId);
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+
141
+ /** What a top-level session `sessionId` is to a child: a bot's own home chat
142
+ * or room member session ('bot', no record needed, the id names its agent),
143
+ * a record-bearing coding session ('coding'), else nothing. */
144
+ function viaOf(sessionId, record) {
145
+ if (roomMemberOfSession(sessionId) || agentIdOfHomeSession(sessionId)) return 'bot';
146
+ return record && !agentOwned(record) ? 'coding' : null;
147
+ }
148
+
149
+ /** The root of a top-level `parent` for a child taking `step`. */
150
+ function rootFrom(parent, record, step) {
151
+ const via = viaOf(parent, record);
152
+ return via ? { rootSessionId: parent, via, path: [step] } : null;
153
+ }
154
+
155
+ /** The delegation a legacy `record` (id `id`, a parent link, none valid
156
+ * recorded) is given, derived now from its parent, and kept on the live
157
+ * record; null when it cannot be derived. */
158
+ function backfillOf(id, record, depth = 0) {
159
+ const parent = parentOf(record);
160
+ if (!parent || parent === id || depth >= MAX_PATH) return null;
161
+ const parentRecord = loadRecord(parent);
162
+ const step = stepOf(record);
163
+ let delegation = null;
164
+ if (parentOf(parentRecord)) {
165
+ const inherited = delegationOfRecord(parent, parentRecord, depth + 1);
166
+ if (!inherited || inherited.path.length >= MAX_PATH) return null;
167
+ delegation = { ...inherited, path: [...inherited.path, step] };
168
+ } else {
169
+ delegation = rootFrom(parent, parentRecord, step);
170
+ }
171
+ if (delegation && _liveSessions.get(id) === record) record.delegation = delegation;
172
+ return delegation;
173
+ }
174
+
175
+ /**
176
+ * The `delegation` to record on a session being created with `opts` (its
177
+ * owner/visibility, parentSessionId/ownerSessionId and agent), or
178
+ * null: none for a top-level session, none for one whose parent's root cannot
179
+ * be established (it is no bot's session: nothing is asked or mirrored for it).
180
+ */
181
+ export function delegationForNewSession(opts = {}) {
182
+ const parent = parentOf(opts);
183
+ if (!parent || parent === clean(opts.id)) return null;
184
+ return delegationUnder(parent, stepOf(opts));
185
+ }
186
+
187
+ /** The delegation of a session taking `step` under the session `parent`. */
188
+ function delegationUnder(parent, step) {
189
+ const record = loadRecord(parent);
190
+ if (record && parentOf(record)) {
191
+ // The parent is delegated itself: its recorded root, one step deeper.
192
+ const inherited = delegationOfRecord(parent, record);
193
+ if (!inherited || inherited.path.length >= MAX_PATH) return null;
194
+ return { ...inherited, path: [...inherited.path, step] };
195
+ }
196
+ return rootFrom(parent, record, step);
197
+ }
198
+
199
+ /**
200
+ * Session `callerSessionId` sends the sub-agent or task session `sessionId`
201
+ * new work (`agent send`, a spawn of its tag, a task-session send): from now
202
+ * on it is run-stamped as it would have been created under the caller, and a
203
+ * sub-agent is rooted under the caller's root when one can be established; the
204
+ * record is saved, so a restart keeps it — work an unattended run started is
205
+ * attended when the chat later reuses it, and the reverse. The run is passed on
206
+ * whether or not a root resolves. A turn already running is never made a run
207
+ * mid-turn. Resolves true when the record changed; false when nothing applies
208
+ * (no record, neither a sub-agent nor a task session, no or the same caller,
209
+ * nothing different).
210
+ */
211
+ export async function rebindDelegation(sessionId, callerSessionId) {
212
+ const id = clean(sessionId);
213
+ const caller = clean(callerSessionId);
214
+ const record = id ? loadRecord(id) : null;
215
+ if (!record || record.closed === true || !caller || caller === id) return false;
216
+ const delegated = Boolean(parentOf(record));
217
+ if (!delegated && !record.taskOrigin) return false;
218
+ const next = delegated ? delegationUnder(caller, stepOf(record)) : null;
219
+ const run = runOfSession(caller);
220
+ const current = stampRun(id, record);
221
+ const same = (a, b) => JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
222
+ const reroot = Boolean(next) && !same(validDelegation(record.delegation), next);
223
+ if (!reroot && same(current, run)) return false;
224
+ if (run && !current && isTurnInFlight(runtimeState.get(id))) return false;
225
+ if (reroot) record.delegation = next;
226
+ if (run || current) record.run = run; // null = attended: legacy provenance no longer applies
227
+ await saveSessionAsync(record, { expectedGeneration: record.generation });
228
+ return true;
229
+ }
230
+
231
+ /** `{ state: 'resolved', ...delegation }` for a sub-agent whose root is known,
232
+ * else `{ state: 'top' }`: a top-level session, one with no record, or one
233
+ * with a parent link and no derivable root (no bot's session either). */
234
+ export function delegationOf(sessionId) {
235
+ const id = clean(sessionId);
236
+ const record = id ? loadRecord(id) : null;
237
+ const parent = record ? parentOf(record) : '';
238
+ const delegation = parent && parent !== id ? delegationOfRecord(id, record) : null;
239
+ return delegation ? { state: 'resolved', ...delegation } : { state: 'top' };
240
+ }
@@ -9,17 +9,29 @@
9
9
  // actingBotId); an ordinary coding session's outbound call passes with the
10
10
  // hook decision unchanged. Other surfaces (group rooms) call it with their own
11
11
  // decision to get the same outbound rule. After the user answered,
12
- // recordHumanDecision stores a "don't ask again" grant and the audit line. Unattended runs (schedule, webhook, room member) register a
13
- // port here (session-runtime/held-requests.mjs) that turns their asks into
14
- // held requests instead of an approval card.
12
+ // recordHumanDecision stores a "don't ask again" grant and the audit line.
13
+ // Schedule and webhook runs never ask (run-policy.mjs): ordinary actions are
14
+ // allowed, payments and permanent deletes denied. Group-room members register
15
+ // a port here (session-runtime/held-requests.mjs) that turns their asks into
16
+ // held requests when no approval card is attached.
15
17
  import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../agent-home.mjs';
16
18
  import { serverConnector } from '../../mcp/server-connectors.mjs';
17
19
  import { readAgentDefinitionKind } from '../../../../shared/agent-kind.mjs';
18
20
  import { isContactAgentId } from '../../../../shared/agent-route-config.mjs';
19
21
  import { mixdogRoot, resolvePluginData } from '../../../../shared/plugin-paths.mjs';
20
22
  import { roomMemberOfSession } from '../agent-room.mjs';
21
- import { classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
23
+ import { agentDisplayName } from '../../context/role-instructions.mjs';
24
+ import { delegationOf } from './delegation.mjs';
25
+ import { setUnattendedApprovalPort, unattendedApprovalPort } from './unattended-port.mjs';
26
+ import { classifyAsDelete, classifyOutboundCall, isRememberableAction } from './outbound-policy.mjs';
22
27
  import { humanGateStore } from './store.mjs';
28
+ import {
29
+ AUTOMATION_GRANT_REASON,
30
+ RUN_FORBIDDEN_REASON,
31
+ isGrantedRun,
32
+ isRemoteDeleteInRun,
33
+ savesAutomation,
34
+ } from './run-policy.mjs';
23
35
 
24
36
  export const OUTBOUND_APPROVAL_REASON = 'This sends or posts outside Mixdog; it needs your approval.';
25
37
  export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action.";
@@ -27,46 +39,70 @@ export const BLOCKED_ACTION_REASON = "This agent's permission blocks this action
27
39
  * payments and deletes never. */
28
40
  const outboundRemember = (category) => (isRememberableAction(category) ? ['once', 'agent'] : ['once']);
29
41
 
30
- let unattendedPort = null;
31
- /** `{ runOf(sessionId) → { kind, agentId, name } | null, hold(request, { signal }) → Promise<approval>,
32
- * humanNeeded(sessionId, outcome, site) → Promise<boolean> }` — the last ends a schedule/webhook
33
- * run that would need a takeover (session-runtime/held-requests.mjs). */
34
- export function setUnattendedApprovalPort(port) {
35
- unattendedPort = port || null;
36
- }
37
- export function unattendedApprovalPort() {
38
- return unattendedPort;
39
- }
42
+ // The unattended runs' port lives in unattended-port.mjs; its setter and
43
+ // getter are re-exported here.
44
+ export { setUnattendedApprovalPort, unattendedApprovalPort };
40
45
 
41
- /** The agent a session is bound to: its unattended run's owner, a room
42
- * member, a home thread's agent; '' for any other (coding) session. */
43
- function boundAgentId(sessionId) {
44
- const run = unattendedPort?.runOf?.(sessionId);
46
+ /** The agent a session is bound to by itself: its unattended run's owner, a
47
+ * room member, a home thread's agent; '' for any other session. */
48
+ function ownBoundAgentId(sessionId) {
49
+ const run = unattendedApprovalPort()?.runOf?.(sessionId);
45
50
  if (run?.agentId) return run.agentId;
46
51
  return roomMemberOfSession(sessionId)?.agentId || agentIdOfHomeSession(sessionId) || '';
47
52
  }
48
53
 
49
54
  /** The agent a session acts for: its unattended run's owner, a room member,
50
- * a home thread's agent, else Mixy. */
55
+ * a home thread's agent, else Mixy. Work a schedule/webhook run started (a
56
+ * sub-agent, a task session) acts for the run's owner; a chat's sub-agent, a
57
+ * chat-created task session and a coding Lead are bound to nothing. */
51
58
  export function agentIdForSession(sessionId) {
52
- return boundAgentId(sessionId) || MIXY_AGENT_ID;
59
+ return ownBoundAgentId(sessionId) || MIXY_AGENT_ID;
53
60
  }
54
61
 
55
62
  /** The bot a session acts for, or '' when none: Mixy in her own home thread
56
63
  * (or her unattended run / room member session), or a contact agent (`kind:
57
64
  * bot`, isContactAgentId) in its home thread, room member session or
58
- * unattended run. A coding Lead (also id 'lead'), its workflow agents and
59
- * their sub-agent sessions are bound to nothing here, so they are no bot.
60
- * A sub-agent of a bot has its own session id and no link back to the
61
- * caller in this layer, so it is not gated either. */
62
- function actingBotId(sessionId) {
63
- const id = boundAgentId(sessionId);
65
+ * unattended run. Work a schedule/webhook run started (a sub-agent, a task
66
+ * session) resolves to the run's bot (ownBoundAgentId → runOf). A coding Lead
67
+ * (also id 'lead'), a task session a bot created in a chat, their workflow
68
+ * agents and every sub-agent of a chat (also a bot's) are bound to nothing
69
+ * here, so they are no bot. */
70
+ export function actingBotId(sessionId) {
71
+ const id = ownBoundAgentId(sessionId);
64
72
  if (!id) return '';
65
73
  if (id === MIXY_AGENT_ID) return id;
66
74
  const kind = readAgentDefinitionKind({ dataDir: resolvePluginData(), shippedRoot: mixdogRoot(), id });
67
75
  return isContactAgentId(id, kind) ? id : '';
68
76
  }
69
77
 
78
+ /** The contact bot (`kind: bot`) a session acts for, or '' for Mixy, a coding
79
+ * Lead, a workflow agent and every other session. Mixy shares the `lead` id
80
+ * with the coding Lead (her connector policy is hers alone, never the Lead's:
81
+ * connectorPolicyAgentOf), and a standing allow is a contact bot's own policy
82
+ * entry: none is offered to Mixy, a Lead or a workflow agent. */
83
+ export function actingContactBotId(sessionId) {
84
+ const id = actingBotId(sessionId);
85
+ return id && id !== MIXY_AGENT_ID ? id : '';
86
+ }
87
+
88
+ // Chain token the desktop translates (ApprovalCard approvalChainLabel).
89
+ const LEAD_NAME = 'Lead';
90
+
91
+ /** Where a sub-agent's approval cards also surface, and who is asking:
92
+ * `{ sessionId, chain }` — the session it works under (a coding Lead, a
93
+ * bot-created task session, a bot's home chat or room member session) and
94
+ * the names from there down to the asker ("Lead › Reviewer", "사무관 ›
95
+ * Worker"). Only where the card shows: no bot identity, grant or remember
96
+ * scope follows from it. Null for a top-level session. */
97
+ export function approvalSurface(sessionId) {
98
+ const delegation = delegationOf(sessionId);
99
+ if (delegation.state !== 'resolved') return null;
100
+ const bot = delegation.via === 'bot' ? ownBoundAgentId(delegation.rootSessionId) : '';
101
+ const chain = [bot ? agentDisplayName(bot) || bot : LEAD_NAME];
102
+ for (const step of delegation.path) chain.push(agentDisplayName(step.agent) || step.agent);
103
+ return { sessionId: delegation.rootSessionId, chain };
104
+ }
105
+
70
106
  const actionOf = (decision) => String(decision?.action || decision?.decision || '').toLowerCase();
71
107
 
72
108
  async function mcpAnnotations(name, scopeId) {
@@ -114,21 +150,44 @@ async function describeEmails(call, outbound, ctx) {
114
150
  * @param {{ name: string, args: any }} call
115
151
  * @param {object|null} decision the PreToolUse decision so far
116
152
  * @param {{ sessionId?: string, mcpScopeId?: string, annotations?: object, store?: object }} ctx
117
- * @returns {Promise<{ decision: object|null, request: object|null, gate: object|null }>}
153
+ * @returns {Promise<{ decision: object|null, request: object|null, gate: object|null, remembered?: true }>}
118
154
  * `request` holds the kind-specific fields an `ask` presents; `gate` the
119
- * audit identity `{ agentId, kind, action, tool, target }`.
155
+ * audit identity `{ agentId, kind, action, tool, target }`; `remembered`
156
+ * marks an outbound call a stored grant already answered.
120
157
  */
121
158
  export async function humanGateDecision(call, decision, ctx = {}) {
122
159
  const action = actionOf(decision);
123
160
  if (action === 'deny' || action === 'block') return { decision, request: null, gate: null };
124
161
  const agentId = agentIdForSession(ctx.sessionId);
125
162
  const annotations = ctx.annotations ?? (await mcpAnnotations(call?.name, ctx.mcpScopeId));
126
- const classified = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
163
+ let classified = classifyOutboundCall({ name: call?.name, args: call?.args, annotations });
164
+ const remoteDelete = isRemoteDeleteInRun({ name: call?.name, annotations }, classified);
165
+ // In a schedule/webhook run only a recoverable trash may delete remote items.
166
+ if (isGrantedRun(unattendedApprovalPort()?.runOf?.(ctx.sessionId)) && remoteDelete) {
167
+ const store = ctx.store || (await humanGateStore());
168
+ await store.recordAudit({
169
+ agentId,
170
+ kind: 'outbound',
171
+ action: 'delete',
172
+ tool: String(call?.name || ''),
173
+ target: '',
174
+ decision: 'run-forbidden',
175
+ device: '',
176
+ });
177
+ const base = decision && typeof decision === 'object' ? decision : {};
178
+ return { decision: { ...base, action: 'deny', reason: RUN_FORBIDDEN_REASON }, request: null, gate: null };
179
+ }
180
+ // Where a bot is attended, the same connector delete the runs refuse
181
+ // (delete_message, remove_member…) is a permanent delete: it asks, never remembered.
182
+ if (remoteDelete && classified?.category !== 'delete' && classified?.category !== 'payment') {
183
+ classified = classifyAsDelete({ name: call?.name, args: call?.args });
184
+ }
127
185
  const outbound = classified && actingBotId(ctx.sessionId) ? classified : null;
128
186
  if (!outbound) {
129
187
  if (action !== 'ask') return { decision, request: null, gate: null };
130
188
  return {
131
- decision,
189
+ // Saving a schedule or webhook says plainly what the approval grants.
190
+ decision: savesAutomation(call) ? { ...decision, reason: AUTOMATION_GRANT_REASON } : decision,
132
191
  request: { kind: 'approval', agentId, remember: ['once'] },
133
192
  gate: { agentId, kind: 'approval', tool: String(call?.name || ''), target: '' },
134
193
  };
@@ -145,8 +204,10 @@ export async function humanGateDecision(call, decision, ctx = {}) {
145
204
  editable: outbound.editable,
146
205
  details: outbound.details,
147
206
  },
148
- remember: outboundRemember(outbound.category),
207
+ remember: ['once'],
149
208
  };
209
+ // "Don't ask again" is the bot's permission.
210
+ request.remember = outboundRemember(outbound.category);
150
211
  const store = ctx.store || (await humanGateStore());
151
212
  const mode = await store.modeFor({ agentId, action: outbound.category });
152
213
  const base = decision && typeof decision === 'object' ? decision : {};
@@ -156,7 +217,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
156
217
  }
157
218
  if (action !== 'ask' && mode === 'allow') {
158
219
  await store.recordAudit({ ...gate, decision: 'remembered', device: '' });
159
- return { decision, request: null, gate: null };
220
+ // The remembered grant answers this call: no later layer asks it again.
221
+ return { decision, request: null, gate: null, remembered: true };
160
222
  }
161
223
  const described = await describeEmails(call, outbound, ctx);
162
224
  if (described) {
@@ -173,7 +235,8 @@ export async function humanGateDecision(call, decision, ctx = {}) {
173
235
  return {
174
236
  decision: { ...(rewriting ? base : unrewritten), action: 'ask', reason: base.reason || OUTBOUND_APPROVAL_REASON },
175
237
  request,
176
- gate,
238
+ // The scopes this request offered: a grant is stored only for one of them.
239
+ gate: { ...gate, remember: request.remember },
177
240
  };
178
241
  }
179
242
 
@@ -185,8 +248,12 @@ export async function recordHumanDecision(gate, approval, { store } = {}) {
185
248
  let decision = approval?.response || (approval?.approved === true ? 'approve' : 'deny');
186
249
  if (approval?.timedOut) decision = 'timeout';
187
250
  const device = typeof approval?.device === 'string' ? approval.device : '';
188
- if (approval?.approved === true && gate.action) {
251
+ // A scope the request did not offer (a payment or permanent delete is only
252
+ // ever answered once) stores nothing.
253
+ const offered = !Array.isArray(gate.remember) || gate.remember.includes(approval?.remember);
254
+ if (approval?.approved === true && gate.action && offered) {
189
255
  await target.grant({ agentId: gate.agentId, action: gate.action, scope: approval.remember, device });
190
256
  }
191
- await target.recordAudit({ ...gate, decision, device });
257
+ const { remember: _offered, ...audit } = gate;
258
+ await target.recordAudit({ ...audit, decision, device });
192
259
  }
@@ -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 {