mixdog 1.2.6 → 1.3.1

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 (294) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +71 -9
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/isolated-test-env.mjs +16 -0
  7. package/src/agents/reviewer/AGENT.md +4 -2
  8. package/src/agents/reviewer/agent.json +1 -1
  9. package/src/defaults/shipped-history.json +10 -0
  10. package/src/defaults/skills/browser-use/SKILL.md +5 -0
  11. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  12. package/src/defaults/skills/setup/references/actions.md +14 -4
  13. package/src/defaults/skills/setup-guide/SKILL.md +178 -0
  14. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  15. package/src/lib/rules-builder.cjs +55 -6
  16. package/src/output-styles/common.md +13 -8
  17. package/src/rules/agent/AGENT.md +4 -0
  18. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  19. package/src/rules/agent-mode/MIXY.md +28 -0
  20. package/src/rules/agent-mode/ROUTINE.md +6 -0
  21. package/src/rules/lead/LEAD.md +7 -3
  22. package/src/rules/shared/70-delivery.md +3 -0
  23. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  24. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  25. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
  26. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
  27. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
  28. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
  29. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  30. package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
  31. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  33. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  34. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  35. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  36. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  37. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  39. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
  40. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
  41. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
  42. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  43. package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
  44. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  45. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
  46. package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
  49. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  50. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  51. package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
  52. package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
  53. package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
  55. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  56. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  57. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
  58. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  59. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  60. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  61. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
  62. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
  63. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
  64. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  65. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  66. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  67. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  68. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  69. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  70. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
  71. package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
  72. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  73. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  74. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  75. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
  76. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  77. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  78. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  79. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  80. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  81. package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
  82. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  83. package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
  84. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
  85. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
  86. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
  87. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  88. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  89. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
  90. package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
  91. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  92. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  93. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  94. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  95. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  96. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  97. package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
  98. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  99. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  100. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
  101. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
  102. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  103. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  104. package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
  105. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  106. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  107. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
  108. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  109. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
  110. package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
  111. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  112. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
  113. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  114. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  115. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  116. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  117. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
  118. package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
  119. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  121. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
  122. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
  123. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
  124. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
  125. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
  126. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
  127. package/src/runtime/attachments/store.mjs +6 -0
  128. package/src/runtime/browser-bridge/client.mjs +24 -0
  129. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  130. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  131. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  132. package/src/runtime/channels/lib/webhook.mjs +0 -1
  133. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  134. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  135. package/src/runtime/computer-bridge/client.mjs +28 -1
  136. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  137. package/src/runtime/connectors/google-mcp.mjs +446 -0
  138. package/src/runtime/effort-judge/judge-client.mjs +1 -1
  139. package/src/runtime/effort-judge/model-manifest.json +7 -7
  140. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  141. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  142. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  143. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  144. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  145. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  146. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  147. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  148. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  149. package/src/runtime/memory/lib/memory.mjs +7 -1
  150. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  151. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  152. package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
  153. package/src/runtime/memory/tool-defs.mjs +5 -0
  154. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  155. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  156. package/src/runtime/shared/agent-avatar.mjs +320 -0
  157. package/src/runtime/shared/agent-kind.mjs +152 -0
  158. package/src/runtime/shared/agent-route-config.d.mts +4 -0
  159. package/src/runtime/shared/agent-route-config.mjs +30 -0
  160. package/src/runtime/shared/agent-store.mjs +132 -0
  161. package/src/runtime/shared/atomic-file.mjs +14 -1
  162. package/src/runtime/shared/bot-split-migration.mjs +457 -0
  163. package/src/runtime/shared/config-patch.mjs +20 -1
  164. package/src/runtime/shared/config.mjs +8 -1
  165. package/src/runtime/shared/connector-accounts.mjs +15 -0
  166. package/src/runtime/shared/connector-catalog.d.mts +51 -0
  167. package/src/runtime/shared/connector-catalog.mjs +228 -0
  168. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  169. package/src/runtime/shared/connector-triggers.mjs +45 -0
  170. package/src/runtime/shared/err-text.mjs +3 -1
  171. package/src/runtime/shared/error-presentation.mjs +91 -6
  172. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  173. package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
  174. package/src/runtime/shared/handoff-artifacts.mjs +61 -0
  175. package/src/runtime/shared/held-request-host.d.mts +12 -0
  176. package/src/runtime/shared/held-request-host.mjs +71 -0
  177. package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
  178. package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
  179. package/src/runtime/shared/model-route-fallback.mjs +79 -7
  180. package/src/runtime/shared/plain-preview.d.mts +1 -0
  181. package/src/runtime/shared/plain-preview.mjs +33 -0
  182. package/src/runtime/shared/routine-limits.d.mts +11 -0
  183. package/src/runtime/shared/routine-limits.mjs +21 -0
  184. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  185. package/src/runtime/shared/routine-trigger.mjs +44 -0
  186. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  187. package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
  188. package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
  189. package/src/runtime/shared/slow-span.mjs +4 -12
  190. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  191. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  192. package/src/runtime/shared/turn-changes.mjs +0 -8
  193. package/src/runtime/shared/usage-readout-totals.mjs +58 -0
  194. package/src/runtime/shared/user-data-guard.mjs +119 -38
  195. package/src/session-runtime/agent-home-delivery.mjs +826 -0
  196. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  197. package/src/session-runtime/agent-room-delivery.mjs +133 -0
  198. package/src/session-runtime/agent-task-sessions.mjs +460 -0
  199. package/src/session-runtime/boot/apis.mjs +63 -18
  200. package/src/session-runtime/boot/facade.mjs +5 -8
  201. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  202. package/src/session-runtime/boot/providers.mjs +14 -4
  203. package/src/session-runtime/boot/tools.mjs +3 -0
  204. package/src/session-runtime/channel-config-api.mjs +53 -5
  205. package/src/session-runtime/config-lifecycle.mjs +43 -2
  206. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  207. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  208. package/src/session-runtime/handoff-attachments.mjs +593 -0
  209. package/src/session-runtime/held-requests.mjs +228 -0
  210. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  211. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  212. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  213. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
  214. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  215. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  216. package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
  217. package/src/session-runtime/lifecycle/shared.mjs +6 -0
  218. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  219. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  220. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  221. package/src/session-runtime/model-route/route-persist.mjs +2 -2
  222. package/src/session-runtime/model-route/route-tuning.mjs +7 -1
  223. package/src/session-runtime/model-route/set-route.mjs +35 -4
  224. package/src/session-runtime/native-web-search.mjs +2 -2
  225. package/src/session-runtime/new-session-config.mjs +6 -1
  226. package/src/session-runtime/owned-automation-run.mjs +229 -0
  227. package/src/session-runtime/resource-api.mjs +5 -0
  228. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  229. package/src/session-runtime/resource-skills-api.mjs +12 -0
  230. package/src/session-runtime/room-member-gate.mjs +48 -0
  231. package/src/session-runtime/route-resolution.mjs +5 -11
  232. package/src/session-runtime/routine-home-turn.mjs +156 -0
  233. package/src/session-runtime/routine-owner.mjs +48 -0
  234. package/src/session-runtime/schedule-session-run.mjs +30 -64
  235. package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
  236. package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
  237. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  238. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  239. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
  240. package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
  241. package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
  242. package/src/session-runtime/services/channel-admin.mjs +93 -24
  243. package/src/session-runtime/services/memory-access.mjs +37 -0
  244. package/src/session-runtime/services/provider-admin.mjs +74 -39
  245. package/src/session-runtime/services/schedule-access.mjs +91 -0
  246. package/src/session-runtime/session-create/session-options.mjs +34 -7
  247. package/src/session-runtime/session-hooks.mjs +16 -8
  248. package/src/session-runtime/setup-tool/executor.mjs +79 -6
  249. package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
  250. package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
  251. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  252. package/src/session-runtime/skills-api.mjs +16 -1
  253. package/src/session-runtime/tool-policy-refresh.mjs +249 -29
  254. package/src/session-runtime/turn/session-ops.mjs +8 -1
  255. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
  256. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
  257. package/src/session-runtime/webhook-session-run.mjs +49 -67
  258. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  259. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
  260. package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
  261. package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
  262. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  263. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  264. package/src/standalone/daemon-agent-control.mjs +1 -8
  265. package/src/standalone/daemon-session-control.mjs +21 -4
  266. package/src/standalone/daemon.mjs +31 -5
  267. package/src/standalone/session-protocol.mjs +27 -0
  268. package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
  269. package/src/standalone/session-service/agent-tree.mjs +4 -3
  270. package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
  271. package/src/standalone/session-service.mjs +19 -0
  272. package/src/tui/app/slash-commands.mjs +20 -0
  273. package/src/tui/dist/index.mjs +234 -95
  274. package/src/tui/session/context-state.mjs +6 -1
  275. package/src/tui/session/notice-surface.mjs +34 -4
  276. package/src/tui/session/queue-helpers.mjs +2 -1
  277. package/src/tui/session/session-api/extensions.mjs +4 -0
  278. package/src/tui/session/session-api/intake/submission.mjs +5 -0
  279. package/src/tui/session/session-api/integrations.mjs +8 -0
  280. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  281. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  282. package/src/tui/session/session-api/routes.mjs +61 -5
  283. package/src/tui/session/session-flow/drain.mjs +12 -3
  284. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  285. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  286. package/src/tui/session/session-flow/steering.mjs +58 -24
  287. package/src/tui/session/tool-approval.mjs +26 -5
  288. package/src/tui/session/transcript-intake.mjs +8 -2
  289. package/src/tui/session/transcript-spill.mjs +5 -1
  290. package/src/tui/session/tui-steering-persist.mjs +65 -42
  291. package/src/tui/session/turn-live-call.mjs +64 -0
  292. package/src/tui/session/turn.mjs +34 -8
  293. package/src/session-runtime/automation-agents.mjs +0 -94
  294. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -6,10 +6,16 @@ import { resolvePreToolAskApproval } from '../tool-helpers.mjs';
6
6
  import { runAbortable, throwIfAborted } from '../../../../../shared/abort-race.mjs';
7
7
  import { evaluateExecPolicy } from '../../../tools/bash-policy-scan.mjs';
8
8
  import { beginSessionApprovalWait } from '../../manager/runtime-liveness.mjs';
9
+ import { agentMcpToolPermission } from '../../../mcp/agent-connectors.mjs';
10
+ import { humanGateDecision, recordHumanDecision } from '../../human-gate/gate.mjs';
11
+ import { applyEditedArgs } from '../../human-gate/requests.mjs';
9
12
 
10
13
  const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
11
14
 
12
- async function askApproval(call, { callerSessionId, executeOpts, toolApprovalHook, decision }) {
15
+ async function askApproval(
16
+ call,
17
+ { callerSessionId, executeOpts, toolApprovalHook, decision, request = null, gate = null }
18
+ ) {
13
19
  const askReason = String(decision?.reason || 'approval requested by hook').trim();
14
20
  // While the user decides, the session is waiting on them, not working.
15
21
  const releaseApprovalWait = beginSessionApprovalWait(callerSessionId);
@@ -24,17 +30,45 @@ async function askApproval(call, { callerSessionId, executeOpts, toolApprovalHoo
24
30
  toolCallId: executeOpts.toolCallId || null,
25
31
  askReason,
26
32
  toolApprovalHook,
33
+ request,
27
34
  })
28
35
  );
29
36
  } finally {
30
37
  releaseApprovalWait();
31
38
  }
32
- if (askOutcome.denial) return { denial: askOutcome.denial };
33
39
  const approval = askOutcome.approval;
40
+ await recordHumanDecision(gate, approval);
41
+ if (askOutcome.denial) return { denial: askOutcome.denial };
34
42
  if (approval && typeof approval === 'object' && isArgsObject(approval.args)) call.args = approval.args;
43
+ // The card's edited text replaces the model's before the call runs.
44
+ if (request?.kind === 'outbound' && approval?.editedArgs) {
45
+ call.args = applyEditedArgs(call.args, approval.editedArgs, request.outbound?.editable);
46
+ }
47
+ // The user approved this very call on the richer outbound card.
48
+ if (request?.kind === 'outbound') call.outboundApproved = true;
35
49
  return null;
36
50
  }
37
51
 
52
+ export const CONNECTOR_APPROVAL_REASON = 'This connector tool is set to ask before it runs for this agent.';
53
+
54
+ /**
55
+ * A connector tool the running agent set to `needs_approval` asks through the
56
+ * ordinary approval hook, whatever the PreToolUse hooks decided. Run after
57
+ * them, on the final call: a renamed call is judged under its final name.
58
+ * A call the outbound card already got approved is not asked a second time.
59
+ * @returns {Promise<{ denial: string } | null>} the denial, or null to run
60
+ */
61
+ export function applyConnectorApproval(call, { agentId, callerSessionId, executeOpts, toolApprovalHook }) {
62
+ if (call.outboundApproved) return null;
63
+ if (agentMcpToolPermission(agentId, call.name) !== 'needs_approval') return null;
64
+ return askApproval(call, {
65
+ callerSessionId,
66
+ executeOpts,
67
+ toolApprovalHook,
68
+ decision: { reason: CONNECTOR_APPROVAL_REASON },
69
+ });
70
+ }
71
+
38
72
  /**
39
73
  * Elevation pass for a shell call (or a terminal `run`) whose exec policy
40
74
  * decision is 'ask' (elevated launch). Runs after the final call is known.
@@ -62,22 +96,40 @@ export async function applyBeforeToolHook({
62
96
  executeOpts,
63
97
  beforeToolHook,
64
98
  toolApprovalHook,
99
+ mcpScopeId = null,
65
100
  }) {
66
101
  const call = { name, args, cwd };
67
102
  // ask_user is itself the user prompt; it never goes through the ordinary approval gate.
68
- if (!beforeToolHook || name === 'ask_user') return call;
103
+ if (name === 'ask_user') return call;
69
104
  try {
70
- const decision = await runAbortable(executeOpts.signal, () =>
71
- beforeToolHook(
72
- {
73
- name,
74
- args,
75
- cwd,
76
- sessionId: callerSessionId,
77
- toolCallId: executeOpts.toolCallId || null,
78
- },
79
- { signal: executeOpts.signal }
80
- )
105
+ const hookDecision = beforeToolHook
106
+ ? await runAbortable(executeOpts.signal, () =>
107
+ beforeToolHook(
108
+ {
109
+ name,
110
+ args,
111
+ cwd,
112
+ sessionId: callerSessionId,
113
+ toolCallId: executeOpts.toolCallId || null,
114
+ },
115
+ { signal: executeOpts.signal }
116
+ )
117
+ )
118
+ : null;
119
+ // Outbound sends ask the user whatever the hooks allowed (human-gate/gate.mjs).
120
+ // Judged as the call will run: only a rewriting decision changes name/args.
121
+ const hookAction = String(hookDecision?.action || hookDecision?.decision || '').toLowerCase();
122
+ const rewritten = hookAction === 'ask' || hookAction === 'modify' || hookAction === 'rewrite';
123
+ const { decision, request, gate } = await humanGateDecision(
124
+ {
125
+ name:
126
+ rewritten && typeof hookDecision?.name === 'string' && hookDecision.name.trim()
127
+ ? hookDecision.name.trim()
128
+ : name,
129
+ args: rewritten && isArgsObject(hookDecision?.args) ? hookDecision.args : args,
130
+ },
131
+ hookDecision,
132
+ { sessionId: callerSessionId, mcpScopeId }
81
133
  );
82
134
  const action = String(decision?.action || decision?.decision || '').toLowerCase();
83
135
  if (action === 'deny' || action === 'block') {
@@ -89,7 +141,14 @@ export async function applyBeforeToolHook({
89
141
  if (typeof decision?.name === 'string' && decision.name.trim()) call.name = decision.name.trim();
90
142
  }
91
143
  if (action === 'ask') {
92
- const denied = await askApproval(call, { callerSessionId, executeOpts, toolApprovalHook, decision });
144
+ const denied = await askApproval(call, {
145
+ callerSessionId,
146
+ executeOpts,
147
+ toolApprovalHook,
148
+ decision,
149
+ request,
150
+ gate,
151
+ });
93
152
  if (denied) return denied;
94
153
  }
95
154
  } catch (error) {
@@ -14,7 +14,7 @@ import { runWithToolExecutionOwner } from '../../../../shared/tool-execution-own
14
14
  import { runWithLocalSearchTelemetry } from '../../tools/builtin/local-search-telemetry.mjs';
15
15
  import { throwIfAborted } from '../../../../shared/abort-race.mjs';
16
16
  import { prepareToolCall } from './tool-exec/call-prep.mjs';
17
- import { applyBeforeToolHook, applyShellElevationGate } from './tool-exec/before-hook.mjs';
17
+ import { applyBeforeToolHook, applyConnectorApproval, applyShellElevationGate } from './tool-exec/before-hook.mjs';
18
18
  import { dispatchToolCall } from './tool-exec/dispatch.mjs';
19
19
  import { applyAfterToolHook } from './tool-exec/after-hook.mjs';
20
20
  import { runReadOnlyIoWithDeadline } from './tool-exec/read-only-deadline.mjs';
@@ -50,8 +50,16 @@ async function executeToolOwned(name, args, cwd, callerSessionId, sessionRef, ex
50
50
  executeOpts,
51
51
  beforeToolHook: hookFrom(executeOpts, sessionRef, 'beforeToolHook'),
52
52
  toolApprovalHook,
53
+ mcpScopeId: sessionRef?.mcpScopeId || null,
53
54
  });
54
55
  if (hooked.denial) return hooked.denial;
56
+ const connectorDenied = await applyConnectorApproval(hooked, {
57
+ agentId: sessionRef?.agent,
58
+ callerSessionId,
59
+ executeOpts,
60
+ toolApprovalHook,
61
+ });
62
+ if (connectorDenied) return connectorDenied.denial;
55
63
  // A decision can settle after its turn was cancelled. It must never
56
64
  // authorize a new mutation, even if the hook ignores the abort signal.
57
65
  throwIfAborted(executeOpts.signal);
@@ -27,12 +27,13 @@ function invalidateForToolSurfaceMigration(session) {
27
27
  }
28
28
 
29
29
  /**
30
- * After a surface re-apply outside a request (resume, provider switch)
31
- * migrated the exposure: the first send of the next turn carries the tag.
30
+ * After a surface re-apply outside a request (resume, provider switch, a
31
+ * workflow switch: `intent`) changed the request prefix: the first send of the
32
+ * next turn carries the tag.
32
33
  */
33
- export function markToolSurfaceMigrated(session) {
34
+ export function markToolSurfaceMigrated(session, intent = TOOL_SURFACE_MIGRATION_INTENT) {
34
35
  invalidateForToolSurfaceMigration(session);
35
- session.pendingCacheBreakIntent = TOOL_SURFACE_MIGRATION_INTENT;
36
+ session.pendingCacheBreakIntent = intent;
36
37
  }
37
38
 
38
39
  /** Provider switch: rebuild the surface for `provider`, migrating bridge ↔ native. */
@@ -2,6 +2,7 @@
2
2
  // message-array change detect, tool-kind classification, skills, hook overrides,
3
3
  // native tool-search payload parsing, bash-session routing, and approval.
4
4
  import { isMcpTool } from '../../mcp/client.mjs';
5
+ import { unattendedApprovalPort } from '../human-gate/gate.mjs';
5
6
  import { isBuiltinTool } from '../../tools/builtin.mjs';
6
7
  import { isInternalTool } from '../../internal-tools.mjs';
7
8
  import { loadSkillToolDependencies } from '../../runtime-core/skill-tool-loading.mjs';
@@ -207,15 +208,24 @@ export async function resolvePreToolAskApproval({
207
208
  toolCallId,
208
209
  askReason,
209
210
  toolApprovalHook,
211
+ request = null,
210
212
  }) {
211
213
  const name = String(toolName || 'tool');
212
214
  const reason = String(askReason || 'approval requested by hook').trim();
213
- if (typeof toolApprovalHook !== 'function') {
215
+ // Unattended runs never auto-approve and have no card: a schedule or webhook
216
+ // run always holds the request for its owner; a room member does when no
217
+ // approval UI is attached (session-runtime/held-requests.mjs).
218
+ const unattended = unattendedApprovalPort();
219
+ const run = unattended?.runOf?.(sessionId) || null;
220
+ const held = run && (run.kind !== 'room' || typeof toolApprovalHook !== 'function');
221
+ const ask = held ? unattended.hold : toolApprovalHook;
222
+ if (typeof ask !== 'function') {
214
223
  return { denial: formatMissingToolApprovalUiDenial(name, reason) };
215
224
  }
216
225
  let approval;
217
226
  try {
218
- approval = await toolApprovalHook({
227
+ approval = await ask({
228
+ ...(request || {}),
219
229
  name,
220
230
  args,
221
231
  cwd,
@@ -229,7 +239,7 @@ export async function resolvePreToolAskApproval({
229
239
  }
230
240
  if (!approvalGranted(approval)) {
231
241
  const detail = approvalReason(approval, reason || 'not approved');
232
- return { denial: `Error: tool "${name}" denied by hook: ${detail}` };
242
+ return { denial: `Error: tool "${name}" denied by hook: ${detail}`, approval };
233
243
  }
234
244
  return { approval };
235
245
  }
@@ -41,6 +41,7 @@ export function usageDeltaEvent({
41
41
  usage,
42
42
  sendTools,
43
43
  toolCalls = 0,
44
+ requestMs = 0,
44
45
  }) {
45
46
  return {
46
47
  sessionId,
@@ -56,6 +57,13 @@ export function usageDeltaEvent({
56
57
  deltaToolCalls: toolCalls,
57
58
  ...reasoningUsage(usage),
58
59
  deltaPrompt: usage.promptTokens || 0,
60
+ // Request start → end of this response (first-token wait included, as the
61
+ // ledger's duration_ms); 0 when the call was not timed. Only timed output
62
+ // enters speed figures.
63
+ requestMs,
64
+ // A provider that cannot measure the prompt or its cache split says so;
65
+ // its zeros are unknowns, not readings (no cache hit can be derived).
66
+ promptKnown: usage.inputTokensKnown !== false && usage.cacheTokensKnown !== false,
59
67
  // Cache delta carried alongside input/output so live metrics reflect
60
68
  // the same token classes the terminal aggregate adds; additive —
61
69
  // callers that ignore these fields keep working.
@@ -72,6 +80,47 @@ export function usageDeltaEvent({
72
80
  };
73
81
  }
74
82
 
83
+ const SUMMED_USAGE_FIELDS = [
84
+ 'inputTokens',
85
+ 'outputTokens',
86
+ 'cachedTokens',
87
+ 'cacheWriteTokens',
88
+ 'cacheWrite1hTokens',
89
+ 'promptTokens',
90
+ ];
91
+
92
+ /** What `total` added after the running total `earlier` (both from addUsage); null when nothing. */
93
+ export function usageSince(total, earlier) {
94
+ if (!total) return null;
95
+ if (!earlier) return { ...total };
96
+ const delta = {};
97
+ for (const field of SUMMED_USAGE_FIELDS) delta[field] = Math.max(0, (total[field] || 0) - (earlier[field] || 0));
98
+ if (total.reasoningTokens != null) {
99
+ delta.reasoningTokens = Math.max(0, total.reasoningTokens - (earlier.reasoningTokens ?? 0));
100
+ delta.reasoningTokensComplete = total.reasoningTokensComplete === true;
101
+ }
102
+ if (total.costUsd != null) delta.costUsd = Math.max(0, total.costUsd - (earlier.costUsd || 0));
103
+ if (total.contextTokens) delta.contextTokens = total.contextTokens;
104
+ return Object.values(delta).some((value) => typeof value === 'number' && value > 0) ? delta : null;
105
+ }
106
+
107
+ /**
108
+ * The turn's usage split per model when the route switched mid-turn:
109
+ * `hops` holds, per left model, the running total when it was left. Each
110
+ * segment carries only its own tokens so it is priced at its own rates.
111
+ */
112
+ export function routeUsageSegments(hops, total, current) {
113
+ if (!Array.isArray(hops) || !hops.length) return null;
114
+ const boundaries = [...hops, { ...current, usage: total }];
115
+ return boundaries
116
+ .map((segment, index) => ({
117
+ provider: segment.provider,
118
+ model: segment.model,
119
+ usage: usageSince(segment.usage, index ? boundaries[index - 1].usage : null),
120
+ }))
121
+ .filter((segment) => segment.usage);
122
+ }
123
+
75
124
  export function addUsage(total, usage) {
76
125
  const delta = normalizeUsage(usage);
77
126
  if (!delta) return total;
@@ -120,14 +120,24 @@ function recordAgentRuntimeCall(session, result) {
120
120
  export async function recordAskUsage({ session, result, askStartedAt, turnSignal }) {
121
121
  const prefixHashForLog = recordAgentRuntimeCall(session, result);
122
122
  if (!result.usage) return;
123
- await logSessionLlmUsage({
124
- session,
125
- usage: result.usage,
126
- startedAt: askStartedAt,
127
- signal: turnSignal,
128
- prefixHash: prefixHashForLog,
129
- transportTiming: result.transportTiming || null,
130
- });
123
+ // A turn that switched models mid-way books one row per model, each priced
124
+ // at that model's rates; otherwise one row for the whole turn.
125
+ const segments = result.usageSegments?.length
126
+ ? result.usageSegments
127
+ : [{ provider: session.provider, model: session.model, usage: result.usage }];
128
+ for (const segment of segments) {
129
+ await logSessionLlmUsage({
130
+ session:
131
+ segment.provider === session.provider && segment.model === session.model
132
+ ? session
133
+ : { ...session, provider: segment.provider, model: segment.model },
134
+ usage: segment.usage,
135
+ startedAt: askStartedAt,
136
+ signal: turnSignal,
137
+ prefixHash: prefixHashForLog,
138
+ transportTiming: result.transportTiming || null,
139
+ });
140
+ }
131
141
  recordStandaloneStatusTelemetry(session, result, Date.now() - askStartedAt);
132
142
  }
133
143
 
@@ -186,6 +186,7 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
186
186
  beforeCount: historyMessages.length + 1,
187
187
  deferredToolDelta: userTurn.deferredToolDelta,
188
188
  goalReminder: userTurn.goalReminder,
189
+ workflowNotice: userTurn.workflowNotice,
189
190
  workspaceNotice: userTurn.workspaceNotice,
190
191
  setupNotice: userTurn.setupNotice,
191
192
  };
@@ -8,6 +8,7 @@ import { createAskSteeringDrain } from './ask-turn-steering-drain.mjs';
8
8
  import { updateSessionStage, markSessionStreamDelta } from './runtime-liveness.mjs';
9
9
  import { codexWireSendOpts } from './session-id.mjs';
10
10
  import { persistIterationMetrics } from './usage-metrics.mjs';
11
+ import { isAgentModeSession } from '../agent-mode.mjs';
11
12
 
12
13
  const optionalFn = (value) => (typeof value === 'function' ? value : undefined);
13
14
  const relay = (fn, ...args) => {
@@ -184,7 +185,8 @@ export function buildAgentLoopOptions({
184
185
  onCompactEvent: (event) => {
185
186
  checkpoint.refreshContextState();
186
187
  checkpoint.schedule(true);
187
- relay(askOpts?.onCompactEvent, event);
188
+ // Agent mode compacts silently: no compaction notice reaches the host.
189
+ if (!isAgentModeSession(session)) relay(askOpts?.onCompactEvent, event);
188
190
  },
189
191
  // Pre-send gauge sync. Defined only when a host listens so the loop
190
192
  // never computes a display number nobody reads.
@@ -19,6 +19,7 @@ import {
19
19
  snapshotPendingWorkspaceNotice,
20
20
  snapshotPendingSetupNotice,
21
21
  } from './prompt-utils.mjs';
22
+ import { snapshotPendingWorkflowNotice } from './workflow-switch-notice.mjs';
22
23
  import { codexWireSendOpts } from './session-id.mjs';
23
24
  import { _tryBridgeExplicitPrefetch } from './prefetch-bridge.mjs';
24
25
  import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
@@ -180,7 +181,11 @@ export async function buildUserTurn({
180
181
  const workspaceNotice = snapshotPendingWorkspaceNotice(session);
181
182
  // What preparing a new worktree linked, installed, ran, failed or skipped.
182
183
  const setupNotice = snapshotPendingSetupNotice(session);
184
+ // A mid-conversation workflow switch: the prompt and tools were rebuilt
185
+ // for the new workflow; this turn tells the model once.
186
+ const workflowNotice = snapshotPendingWorkflowNotice(session);
183
187
  const reminderBlock = [
188
+ workflowNotice?.content || '',
184
189
  workspaceNotice?.content || '',
185
190
  setupNotice?.content || '',
186
191
  currentTimeBlock ? `<system-reminder>\n# Current Time\n${currentTimeBlock}\n</system-reminder>` : '',
@@ -209,6 +214,7 @@ export async function buildUserTurn({
209
214
  historyMessages,
210
215
  deferredToolDelta,
211
216
  goalReminder,
217
+ workflowNotice,
212
218
  workspaceNotice,
213
219
  setupNotice,
214
220
  userTurnMessage,
@@ -9,6 +9,7 @@ import { persistedAssistantTranscriptMetadata } from '../../../../shared/transcr
9
9
  import { acknowledgePendingDeferredToolDelta } from '../../runtime-core/deferred-tool-delta.mjs';
10
10
  import { acknowledgePendingGoalReminder } from '../../runtime-core/goal-reminder.mjs';
11
11
  import { acknowledgePendingSetupNotice, acknowledgePendingWorkspaceNotice } from './prompt-utils.mjs';
12
+ import { acknowledgePendingWorkflowNotice } from './workflow-switch-notice.mjs';
12
13
  import {
13
14
  finalizePendingMessageDelivery,
14
15
  drainPendingMessages,
@@ -139,6 +140,7 @@ export async function commitAskTurn({ sessionId, opened, prepared, result, rawTr
139
140
  if (prepared.goalReminder) {
140
141
  acknowledgePendingGoalReminder(session, prepared.goalReminder.revision);
141
142
  }
143
+ if (prepared.workflowNotice) acknowledgePendingWorkflowNotice(session, prepared.workflowNotice.revision);
142
144
  if (prepared.workspaceNotice) {
143
145
  acknowledgePendingWorkspaceNotice(session, prepared.workspaceNotice.revision);
144
146
  }
@@ -40,14 +40,6 @@ export function currentPendingLifecycleToken(sessionId) {
40
40
  return pendingLifecycleToken(pendingSessionLifecycle(sessionId));
41
41
  }
42
42
 
43
- // Refusal verdict of the lifecycle a token was minted from (tombstoned or
44
- // unreadable) — exactly pendingLifecycleInvalidated(sessionId) for that same
45
- // read, so a caller holding a fresh token needs no second durable read.
46
- export function lifecycleTokenClosed(token) {
47
- const { source } = parseLifecycleToken(token);
48
- return source === 'closed' || source === 'unreadable';
49
- }
50
-
51
43
  // Parses a `source:generation` epoch token.
52
44
  function parseLifecycleToken(token) {
53
45
  const text = String(token);
@@ -207,29 +207,46 @@ function bp3SliceAt(text, heading) {
207
207
 
208
208
  /**
209
209
  * Replace the workspace-scoped tail of a BP3 core (composeSystemPrompt order:
210
- * …, `# Core Memory`, `# Instruction Files`). The instruction-files slice is
211
- * always last and may itself contain separators, so it is cut by position;
212
- * the core-memory slice is one separator-free part. `undefined` keeps a slice,
213
- * an empty string drops it.
210
+ * …, `# Instruction Files`, `# Core Memory`). Core memory is the last,
211
+ * separator-free part; the instruction-files slice before it may itself
212
+ * contain separators, so both are cut by position. A core from before that
213
+ * order (memory, then instruction files) is read too and written in the
214
+ * current order. `undefined` keeps a slice, an empty string drops it.
214
215
  * @param {string} core
215
216
  * @param {{ coreMemoryContext?: string, instructionFilesContext?: string }} slices
216
217
  */
217
218
  export function replaceWorkspaceCoreSlices(core, { coreMemoryContext, instructionFilesContext } = {}) {
219
+ let { head, instructionFiles, memory } = splitWorkspaceCoreSlices(core);
220
+ if (coreMemoryContext !== undefined) {
221
+ const value = trimmedSlice(coreMemoryContext);
222
+ memory = value ? `${CORE_MEMORY_HEADING}${value}` : '';
223
+ }
224
+ if (instructionFilesContext !== undefined) instructionFiles = trimmedSlice(instructionFilesContext);
225
+ return [head, instructionFiles, memory].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
226
+ }
227
+
228
+ /** A BP3 core's `{ head, instructionFiles, memory }` (each '' when absent). */
229
+ function splitWorkspaceCoreSlices(core) {
218
230
  let head = String(core || '');
231
+ let memory = '';
232
+ const memoryAt = bp3SliceAt(head, CORE_MEMORY_HEADING);
233
+ if (memoryAt) {
234
+ memory = head.slice(memoryAt.start);
235
+ head = head.slice(0, memoryAt.cut);
236
+ const legacy = memory.indexOf(`${BP3_PART_SEPARATOR}${INSTRUCTION_FILES_HEADING}`);
237
+ if (legacy >= 0) {
238
+ const instructions = memory.slice(legacy + BP3_PART_SEPARATOR.length);
239
+ memory = memory.slice(0, legacy);
240
+ head = head ? `${head}${BP3_PART_SEPARATOR}${instructions}` : instructions;
241
+ }
242
+ }
219
243
  let instructionFiles = '';
220
244
  const instructionAt = bp3SliceAt(head, INSTRUCTION_FILES_HEADING);
221
245
  if (instructionAt) {
222
246
  instructionFiles = head.slice(instructionAt.start);
223
247
  head = head.slice(0, instructionAt.cut);
224
248
  }
225
- let parts = head ? head.split(BP3_PART_SEPARATOR) : [];
226
- if (coreMemoryContext !== undefined) {
227
- parts = parts.filter((part) => !part.startsWith(CORE_MEMORY_HEADING));
228
- const memory = trimmedSlice(coreMemoryContext);
229
- if (memory) parts.push(`${CORE_MEMORY_HEADING}${memory}`);
230
- }
231
- if (instructionFilesContext !== undefined) instructionFiles = trimmedSlice(instructionFilesContext);
232
- return [...parts, instructionFiles].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
249
+ return { head, instructionFiles, memory };
233
250
  }
234
251
 
235
252
  /**
@@ -286,9 +303,7 @@ function textOf(message) {
286
303
  */
287
304
  function presentedWorkspace(session) {
288
305
  const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
289
- const instructionAt = bp3SliceAt(core, INSTRUCTION_FILES_HEADING);
290
- const head = instructionAt ? core.slice(0, instructionAt.cut) : core;
291
- const memory = (head ? head.split(BP3_PART_SEPARATOR) : []).find((part) => part.startsWith(CORE_MEMORY_HEADING));
306
+ const { instructionFiles, memory } = splitWorkspaceCoreSlices(core);
292
307
  const tail = typeof session.bp3EnvironmentContext === 'string' ? session.bp3EnvironmentContext : '';
293
308
  const facts = tail.split(BP3_PART_SEPARATOR)[0] || '';
294
309
  const promptText = [textOf(bpEnvSystemMessage(session)), textOf(bp3SystemMessage(session))].join('\n');
@@ -297,10 +312,23 @@ function presentedWorkspace(session) {
297
312
  cwd: cwd ? cwd.trim() : null,
298
313
  environmentContext: facts.startsWith('- ') ? facts.trim() : '',
299
314
  coreMemoryContext: memory ? memory.slice(CORE_MEMORY_HEADING.length).trim() : '',
300
- instructionFilesContext: instructionAt ? core.slice(instructionAt.start).trim() : '',
315
+ instructionFilesContext: instructionFiles.trim(),
301
316
  };
302
317
  }
303
318
 
319
+ /**
320
+ * A rebuilt BP3 core head (composed without workspace slices) followed by the
321
+ * instruction-files and core-memory slices the session's prompt presents now,
322
+ * so a rebuild of the rest of the core (a mid-conversation workflow switch)
323
+ * leaves what the model was told about its workspace — and any staged
324
+ * workspaceReroot — as it was.
325
+ */
326
+ export function withPresentedWorkspaceSlices(session, nextHead) {
327
+ const core = session.bp3EnvSplit === true ? textOf(bp3SystemMessage(session)) : String(session.bp3CoreContext || '');
328
+ const { instructionFiles, memory } = splitWorkspaceCoreSlices(core);
329
+ return [nextHead, instructionFiles, memory].filter((part) => part?.trim()).join(BP3_PART_SEPARATOR);
330
+ }
331
+
304
332
  const WORKSPACE_SLICES = ['environmentContext', 'coreMemoryContext', 'instructionFilesContext'];
305
333
 
306
334
  /**
@@ -0,0 +1,50 @@
1
+ // Provider-native replay state on transcript messages: reasoning items,
2
+ // thinking blocks with their signatures, ordered native blocks, Gemini thought
3
+ // signatures on tool calls. It belongs to the provider (and the account) that
4
+ // produced it — another provider rejects it or, worse, a gateway of the same
5
+ // family validates it against its own key. A route change drops it; what the
6
+ // next provider needs (text, tool calls and their results, in order) stays,
7
+ // and each adapter rebuilds its own wire shape from that.
8
+
9
+ // Every message field a provider adapter replays or re-reads as native state.
10
+ const PROVIDER_REPLAY_FIELDS = [
11
+ 'providerReplay',
12
+ 'reasoning',
13
+ 'thinkingBlocks',
14
+ 'assistantBlocks',
15
+ 'reasoningItems',
16
+ 'reasoningContent',
17
+ 'providerMetadata',
18
+ ];
19
+ // Opaque provider tokens a tool call may carry (Gemini thought signatures).
20
+ const TOOL_CALL_SIGNATURE_FIELDS = ['thoughtSignature', 'thought_signature'];
21
+
22
+ const hasAny = (value, fields) => fields.some((field) => field in value);
23
+
24
+ function plainToolCalls(toolCalls) {
25
+ if (!Array.isArray(toolCalls) || !toolCalls.some((call) => call && hasAny(call, TOOL_CALL_SIGNATURE_FIELDS))) {
26
+ return toolCalls;
27
+ }
28
+ return toolCalls.map((call) => {
29
+ if (!call || !hasAny(call, TOOL_CALL_SIGNATURE_FIELDS)) return call;
30
+ const plain = { ...call };
31
+ for (const field of TOOL_CALL_SIGNATURE_FIELDS) delete plain[field];
32
+ return plain;
33
+ });
34
+ }
35
+
36
+ /** `message` without provider-native replay state; the same object when it carries none. */
37
+ export function stripProviderReplayMessage(message) {
38
+ if (!message || typeof message !== 'object') return message;
39
+ const toolCalls = plainToolCalls(message.toolCalls);
40
+ if (!hasAny(message, PROVIDER_REPLAY_FIELDS) && toolCalls === message.toolCalls) return message;
41
+ const plain = { ...message };
42
+ for (const field of PROVIDER_REPLAY_FIELDS) delete plain[field];
43
+ if (toolCalls !== message.toolCalls) plain.toolCalls = toolCalls;
44
+ return plain;
45
+ }
46
+
47
+ /** Replace, never mutate: delta saves see rewritten messages as changed. */
48
+ export function stripProviderReplayMessages(messages) {
49
+ return Array.isArray(messages) ? messages.map(stripProviderReplayMessage) : messages;
50
+ }
@@ -112,20 +112,36 @@ export function _buildAgentRules(profile = 'full') {
112
112
  );
113
113
  }
114
114
 
115
- export function _buildLeadRules({ includeLeadBrief = true } = {}) {
115
+ // One `part` of LEAD.md (rules-builder buildLeadRoleContent): 'common' (the
116
+ // lines every Lead shares), 'coding' / 'agent' (the mode-variant passages),
117
+ // 'identity' (the coding identity sentence).
118
+ export function _buildLeadRules({ includeLeadBrief = true, part = 'common' } = {}) {
116
119
  const PLUGIN_ROOT = mixdogRoot();
117
120
  const DATA_DIR = resolvePluginData();
118
121
  const RULES_DIR = join(PLUGIN_ROOT, 'rules');
122
+ const variant = includeLeadBrief ? 'delegating' : 'delegation-free';
119
123
  return buildCachedRules(
120
124
  'buildLeadRoleContent',
121
125
  'lead role rules',
122
126
  [join(RULES_DIR, 'lead'), join(DATA_DIR, 'mixdog-config.json')],
123
- { PLUGIN_ROOT, DATA_DIR, includeLeadBrief },
124
- includeLeadBrief ? 'delegating' : 'delegation-free'
127
+ { PLUGIN_ROOT, DATA_DIR, includeLeadBrief, part },
128
+ `${variant}\n${part}`
125
129
  );
126
130
  }
127
131
 
128
- export function _buildLeadMetaContext() {
132
+ // One agent-mode rule file (rules/agent-mode): 'conduct', 'mixy' or 'routine'.
133
+ export function _buildAgentModeRules({ part = 'conduct' } = {}) {
134
+ const PLUGIN_ROOT = mixdogRoot();
135
+ return buildCachedRules(
136
+ 'buildAgentModeContent',
137
+ 'agent mode rules',
138
+ [join(PLUGIN_ROOT, 'rules', 'agent-mode')],
139
+ { PLUGIN_ROOT, part },
140
+ part
141
+ );
142
+ }
143
+
144
+ export function _buildLeadMetaContext({ includeOutputStyle = true } = {}) {
129
145
  const PLUGIN_ROOT = mixdogRoot();
130
146
  const DATA_DIR = resolvePluginData();
131
147
  const RULES_DIR = join(PLUGIN_ROOT, 'rules');
@@ -138,7 +154,8 @@ export function _buildLeadMetaContext() {
138
154
  join(PLUGIN_ROOT, 'output-styles'),
139
155
  join(DATA_DIR, 'output-styles'),
140
156
  ],
141
- { PLUGIN_ROOT, DATA_DIR }
157
+ { PLUGIN_ROOT, DATA_DIR, includeOutputStyle },
158
+ includeOutputStyle ? 'style' : 'no-style'
142
159
  );
143
160
  }
144
161