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
@@ -1,6 +1,10 @@
1
1
  import { SETUP_EXTENDED_PROPERTIES, SETUP_DESKTOP_ACTIONS } from './settings-contract.mjs';
2
2
  import { MEDIA_KINDS } from '../../runtime/media/tool-defs.mjs';
3
3
  import { OPENAI_COMPAT_PRESETS } from '../../runtime/agent/orchestrator/providers/openai-compat-presets.mjs';
4
+ import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
5
+ import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
6
+ import { BOT_AGENT_KIND, WORKFLOW_AGENT_KIND } from '../../runtime/shared/agent-route-config.mjs';
7
+ import { assertAutomationAccess, scheduleCallerScope } from '../services/schedule-access.mjs';
4
8
 
5
9
  const own = (value, key) => Object.hasOwn(value || {}, key);
6
10
  const pick = (value, keys) => Object.fromEntries(keys.filter((key) => own(value, key)).map((key) => [key, value[key]]));
@@ -162,13 +166,20 @@ async function readDefinition(rt, kind, name) {
162
166
  };
163
167
  }
164
168
 
165
- async function saveDefinition(rt, args) {
169
+ // A created agent's explicit marker: the one given, else a bot when Mixy's
170
+ // agent-mode chat creates it (Mixy creates bots), else a workflow agent.
171
+ function createdAgentKind(input, caller) {
172
+ if (own(input, 'kind')) return input.kind;
173
+ const { mode, agentId } = resolveSessionMode({ session: caller });
174
+ return mode === AGENT_MODE && agentId === MIXY_AGENT_ID ? BOT_AGENT_KIND : WORKFLOW_AGENT_KIND;
175
+ }
176
+
177
+ async function saveDefinition(rt, args, context = {}) {
166
178
  const kind = args.definitionKind;
167
179
  const input = args.definition;
168
- const allowed =
169
- kind === 'skill'
170
- ? ['originalName', 'name', 'description', 'body', 'whenToUse', 'toolDependencies']
171
- : ['id', 'name', 'description', 'body'];
180
+ let allowed = ['id', 'name', 'description', 'body'];
181
+ if (kind === 'skill') allowed = ['originalName', 'name', 'description', 'body', 'whenToUse', 'toolDependencies'];
182
+ else if (kind === 'agent') allowed = [...allowed, 'kind'];
172
183
  const unexpected = Object.keys(input).find((key) => !allowed.includes(key));
173
184
  if (unexpected) throw new Error(`${unexpected} is not a ${kind} definition field`);
174
185
  const creating = args.action === 'create_definition';
@@ -198,11 +209,34 @@ async function saveDefinition(rt, args) {
198
209
  const agents = await rt.listAgents();
199
210
  if (agents.some((agent) => agent.id === input.id)) throw new Error(`agent "${input.id}" already exists`);
200
211
  }
201
- return rt.saveAgentDefinition(creating ? next : { ...next, id: current.id });
212
+ if (creating) return rt.saveAgentDefinition({ ...next, kind: createdAgentKind(input, context.caller) });
213
+ // The stored kind is kept as it is; an edit naming one is refused by the editor.
214
+ const { kind: _storedKind, ...edit } = { ...pick(current, definitionKeys), ...input };
215
+ return rt.saveAgentDefinition({ ...edit, ...(own(input, 'kind') ? { kind: input.kind } : {}), id: current.id });
202
216
  }
203
217
  return creating ? rt.addSkill(next) : rt.saveSkill({ ...next, originalName: current.originalName });
204
218
  }
205
219
 
220
+ async function automationRow(rt, kind, name) {
221
+ const status = await rt.getChannelSetup();
222
+ return status[kind === 'schedule' ? 'schedules' : 'webhooks'].find((row) => row.name === name) || null;
223
+ }
224
+
225
+ /** Authorize one automation action for the calling session (`context.caller`,
226
+ * its session record; null when it could not be resolved) — schedule-access.mjs. */
227
+ async function authorizeAutomation(rt, context, { kind, action, current, owner = '' }) {
228
+ const scope = scheduleCallerScope(context.caller);
229
+ const transfer = String(owner ?? '').trim();
230
+ const agents = transfer && scope.kind === 'human' ? await rt.listAgents() : [];
231
+ assertAutomationAccess(scope, {
232
+ kind,
233
+ action,
234
+ current,
235
+ owner: transfer,
236
+ agentIds: (Array.isArray(agents) ? agents : []).map((agent) => agent?.id).filter(Boolean),
237
+ });
238
+ }
239
+
206
240
  export const EXTENDED_SETUP_HANDLERS = Object.freeze({
207
241
  set_local_context: (rt, args) => rt.setLocalProviderContext(args.modelId, args.localContextWindow),
208
242
  async list_models(rt, args) {
@@ -238,22 +272,29 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
238
272
  return {
239
273
  ...result,
240
274
  recovery:
241
- 'User-authored definition files are deleted. A built-in override may revert to its built-in definition.',
275
+ args.definitionKind === 'workflow'
276
+ ? 'User-authored definition files are deleted. A built-in override may revert to its built-in definition.'
277
+ : 'A custom agent or bot is archived with its chat and its routines are switched off; the user can restore it from Sessions → Archived, or delete it there for good. A built-in override reverts to its built-in definition.',
242
278
  };
243
279
  },
244
- async save_automation(rt, args) {
280
+ async save_automation(rt, args, context = {}) {
245
281
  const kind = args.automationKind;
246
282
  const input = args.entry;
247
283
  if (kind === 'webhook' && ['time', 'at', 'timezone', 'days'].some((key) => own(input, key))) {
248
- throw new Error('Schedule timing fields are not accepted for webhooks');
284
+ throw new Error('Schedule fields (timing) are not accepted for webhooks');
249
285
  }
250
286
  if (kind === 'schedule' && own(input, 'parser')) throw new Error('parser is a webhook-only field');
251
287
  let previous = {};
252
288
  if (input.overwrite) {
253
- const status = await rt.getChannelSetup();
254
- previous = status[kind === 'schedule' ? 'schedules' : 'webhooks'].find((row) => row.name === input.name);
289
+ previous = await automationRow(rt, kind, input.name);
255
290
  if (!previous) throw new Error(`${kind} "${input.name}" does not exist`);
256
291
  }
292
+ await authorizeAutomation(rt, context, {
293
+ kind,
294
+ action: input.overwrite ? 'edit' : 'create',
295
+ current: input.overwrite ? previous : null,
296
+ owner: input.owner,
297
+ });
257
298
  const next = { ...pick(previous, entryKeys), ...input };
258
299
  if (kind === 'schedule') {
259
300
  if (previous.whenAt && !own(input, 'time') && !own(input, 'at')) {
@@ -263,6 +304,11 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
263
304
  if (own(input, 'time') && !own(input, 'at')) delete next.at;
264
305
  if (own(input, 'at') && !own(input, 'time')) delete next.time;
265
306
  }
307
+ // An automation created from an agent's home thread belongs to that agent.
308
+ if (!input.overwrite && !own(input, 'owner')) {
309
+ const homeAgent = agentIdOfHomeSession(context.sessionId);
310
+ if (homeAgent) next.owner = homeAgent;
311
+ }
266
312
  const result = kind === 'schedule' ? await rt.saveSchedule(next) : await rt.saveWebhook(next);
267
313
  return {
268
314
  ...publicAutomation(result),
@@ -275,18 +321,42 @@ export const EXTENDED_SETUP_HANDLERS = Object.freeze({
275
321
  : {}),
276
322
  };
277
323
  },
278
- async delete_automation(rt, args) {
279
- const status = await rt.getChannelSetup();
280
- const rows = status[args.automationKind === 'schedule' ? 'schedules' : 'webhooks'];
281
- if (!rows.some((row) => row.name === args.name)) throw new Error('Automation entry not found');
324
+ async delete_automation(rt, args, context = {}) {
325
+ const current = await automationRow(rt, args.automationKind, args.name);
326
+ if (!current) throw new Error('Automation entry not found');
327
+ await authorizeAutomation(rt, context, { kind: args.automationKind, action: 'delete', current });
282
328
  const result =
283
329
  args.automationKind === 'schedule' ? await rt.deleteSchedule(args.name) : await rt.deleteWebhook(args.name);
284
330
  return { ...result, recovery: 'The automation record is deleted; recreating it requires its definition.' };
285
331
  },
286
- set_automation_enabled: (rt, args) =>
287
- args.automationKind === 'schedule'
332
+ async set_automation_enabled(rt, args, context = {}) {
333
+ const current = await automationRow(rt, args.automationKind, args.name);
334
+ if (!current) throw new Error('Automation entry not found');
335
+ const action = args.enabled === false ? 'pause' : 'resume';
336
+ await authorizeAutomation(rt, context, { kind: args.automationKind, action, current });
337
+ return args.automationKind === 'schedule'
288
338
  ? rt.setScheduleEnabled(args.name, args.enabled)
289
- : rt.setWebhookEnabled(args.name, args.enabled),
339
+ : rt.setWebhookEnabled(args.name, args.enabled);
340
+ },
341
+ // Starts the run and returns at once: the run is its own session, and its
342
+ // result posts into the owning agent's thread when it ends. A webhook
343
+ // re-runs on its latest received payload.
344
+ async run_automation(rt, args, context = {}) {
345
+ const kind = args.automationKind;
346
+ const current = await automationRow(rt, kind, args.name);
347
+ if (!current) throw new Error('Automation entry not found');
348
+ await authorizeAutomation(rt, context, { kind, action: 'run', current });
349
+ if (kind === 'webhook' && current.hasLastPayload !== true) {
350
+ throw new Error(`webhook "${args.name}" has not received a payload yet`);
351
+ }
352
+ const run = kind === 'webhook' ? rt.runWebhookNow(args.name) : rt.runScheduleNow(args.name);
353
+ void Promise.resolve(run).catch(() => {});
354
+ return {
355
+ name: args.name,
356
+ started: true,
357
+ note: "The run is its own session; its result posts into the owning agent's thread when it finishes.",
358
+ };
359
+ },
290
360
  async set_developer_option(rt, args) {
291
361
  const option = rt
292
362
  .getDeveloperSettings()
@@ -360,9 +430,9 @@ function hookRuleAt(rt, index) {
360
430
  return publicHookRule(rule);
361
431
  }
362
432
 
363
- export async function executeExtendedSetupAction(rt, args, requestDesktop) {
433
+ export async function executeExtendedSetupAction(rt, args, requestDesktop, context = {}) {
364
434
  if (SETUP_DESKTOP_ACTIONS.includes(args.action)) return requestDesktop(args);
365
435
  const handler = EXTENDED_SETUP_HANDLERS[args.action];
366
436
  if (!handler) throw new Error(`setup: unhandled action "${args.action}"`);
367
- return handler(rt, args);
437
+ return handler(rt, args, context);
368
438
  }
@@ -26,6 +26,7 @@ export const SETUP_EXTENDED_ACTION_FIELDS = Object.freeze({
26
26
  save_automation: 'automationKind entry',
27
27
  delete_automation: 'automationKind name',
28
28
  set_automation_enabled: 'automationKind name enabled',
29
+ run_automation: 'automationKind name',
29
30
  set_webhook_config: 'webhook',
30
31
  set_desktop_settings: 'desktop',
31
32
  set_appearance: 'appearance',
@@ -51,6 +52,7 @@ export const SETUP_ACTIVITY_RAIL_PIN_IDS = Object.freeze([
51
52
  'workflows',
52
53
  'projects',
53
54
  'extensions',
55
+ 'skills',
54
56
  'source-control',
55
57
  'search',
56
58
  'webhooks',
@@ -155,6 +157,12 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
155
157
  name: text,
156
158
  description: text,
157
159
  body: text,
160
+ kind: {
161
+ type: 'string',
162
+ enum: ['bot', 'agent'],
163
+ description:
164
+ 'Agent create only: "bot" (a contact the user chats with) or "agent" (a workflow sub-agent). Omitted: a bot when created from the representative bot\'s chat, else a workflow agent. Fixed after creation.',
165
+ },
158
166
  whenToUse: text,
159
167
  toolDependencies: {
160
168
  type: ['array', 'null'],
@@ -172,13 +180,7 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
172
180
  timezone: text,
173
181
  days: { ...text, description: 'Schedule day selector, e.g. weekdays or mon,wed,fri.' },
174
182
  parser: { type: 'string', enum: ['github', 'generic', 'stripe', 'sentry'] },
175
- model: {
176
- ...text,
177
- description:
178
- 'Required for new schedules; no default-model fallback. Use provider/model[@effort][+fast][+noauto][?parameter=value] (+noauto: Auto reasoning off; absent = on) or an existing preset name. On overwrite, omission preserves the saved model, which must be non-empty.',
179
- },
180
183
  cwd: text,
181
- workflow: text,
182
184
  attachments: {
183
185
  type: 'array',
184
186
  maxItems: 8,
@@ -197,6 +199,11 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
197
199
  ),
198
200
  },
199
201
  enabled: boolean,
202
+ owner: {
203
+ ...text,
204
+ description:
205
+ "The bot id that owns the schedule or webhook; each run is a turn in that bot's chat, where it replies with the result. The bot must exist (no representative bot yet = refused). New entries default to the bot of the calling thread (the representative bot = lead); on overwrite, omission keeps the owner.",
206
+ },
200
207
  overwrite: { ...boolean, description: 'Explicitly update an existing entry; omitted fields are preserved.' },
201
208
  },
202
209
  ['name']
@@ -213,6 +220,7 @@ export const SETUP_EXTENDED_PROPERTIES = Object.freeze({
213
220
  usagePinned: boolean,
214
221
  usageRailShortLong: boolean,
215
222
  computerObserveOnly: boolean,
223
+ browserAutoSignIn: boolean,
216
224
  notificationVolume: {
217
225
  type: 'integer',
218
226
  minimum: 0,
@@ -7,6 +7,7 @@ import {
7
7
  import { MEDIA_KINDS } from '../../runtime/media/tool-defs.mjs';
8
8
  import { INSTRUCTION_FILES_MODES } from '../../runtime/shared/instruction-files-mode.mjs';
9
9
  import { WORKTREE_NEW_TASK_DEFAULTS } from '../../runtime/shared/worktrees/settings.mjs';
10
+ import { CONNECTOR_IDS } from '../../runtime/shared/connector-catalog.mjs';
10
11
 
11
12
  /** Built-in `setup` tool: the model's handle on a Mixdog user's persisted
12
13
  * configuration. The description is a contract only; how to use each action
@@ -110,6 +111,7 @@ export const SETUP_ACTION_FIELDS = Object.freeze({
110
111
  set_plugin_enabled: 'name enabled',
111
112
  remove_plugin: 'name',
112
113
  enable_plugin_mcp: 'name',
114
+ connect_connector: 'connector',
113
115
  set_worktree_settings: 'worktree',
114
116
  ...SETUP_EXTENDED_ACTION_FIELDS,
115
117
  });
@@ -345,6 +347,12 @@ export const SETUP_TOOL_DEFS = Object.freeze([
345
347
  description: 'set_disabled_skills: full list of disabled skill names.',
346
348
  },
347
349
  source: { type: 'string', description: 'add_plugin: Git URL, owner/repo, or local path.' },
350
+ connector: {
351
+ type: 'string',
352
+ enum: [...CONNECTOR_IDS],
353
+ description:
354
+ 'connect_connector: catalog service to connect. Shows the user a connect card in this conversation (one-click sign-in or a key sheet in Desktop); it never connects by itself.',
355
+ },
348
356
  kind: {
349
357
  type: 'string',
350
358
  enum: ['skills', 'mcp', 'plugins', ...MEDIA_KINDS],
@@ -1,7 +1,7 @@
1
1
  // Skill surface (status listing, resource load, tool envelope, global skill
2
2
  // creation/editing). The runtime facade only wires the mutable cwd and the
3
3
  // context module into it.
4
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
4
+ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
5
5
  import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
6
6
  import { resolvePluginData } from '../runtime/shared/plugin-paths.mjs';
7
7
  import {
@@ -193,6 +193,20 @@ export function createSkillsApi({ contextMod, getCwd, getTools = () => [] }) {
193
193
  return { originalName, name, filePath };
194
194
  }
195
195
 
196
+ function removeGlobalSkill(input = {}) {
197
+ const name = validateSkillName(input.name);
198
+ const resource = contextMod.loadSkillResource?.(name, getCwd());
199
+ if (!resource?.filePath) throw new Error(`skill not found: ${name}`);
200
+ const dir = dirname(resolve(resource.filePath));
201
+ const rel = relative(globalSkillsRoot(), dir);
202
+ if (!rel || rel.startsWith('..') || isAbsolute(rel) || basename(dir) !== name) {
203
+ throw new Error(`only your own skills can be removed: ${name}`);
204
+ }
205
+ rmSync(dir, { recursive: true, force: true });
206
+ invalidateSkills();
207
+ return { name };
208
+ }
209
+
196
210
  function invalidateSkills() {
197
211
  contextMod.invalidateSkillsCache?.(getCwd());
198
212
  }
@@ -203,6 +217,7 @@ export function createSkillsApi({ contextMod, getCwd, getTools = () => [] }) {
203
217
  skillToolContent,
204
218
  addGlobalSkill,
205
219
  saveSkillDocument,
220
+ removeGlobalSkill,
206
221
  invalidateSkills,
207
222
  };
208
223
  }
@@ -1,7 +1,8 @@
1
1
  // Refresh the empty Lead session's model-facing tool surface and BP1/BP3
2
2
  // layers after workflow or search/memory settings change. Conversation
3
3
  // sessions keep their frozen schema; the next new session picks up the
4
- // updated policy.
4
+ // updated policy. The one exception is a workflow switch made in the session
5
+ // itself (switchSessionWorkflow), which rebuilds that conversation's policy.
5
6
  import { sessionHasConversationMessages } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
6
7
  import { toSessionWorkflowMeta } from '../runtime/agent/orchestrator/runtime-core/workflow.mjs';
7
8
  import {
@@ -21,7 +22,25 @@ import {
21
22
  } from '../runtime/agent/orchestrator/session/manager/session-tool-surface.mjs';
22
23
  import { finalizeSessionToolList } from '../runtime/agent/orchestrator/session/manager/tool-resolution.mjs';
23
24
  import { isAgentOwner } from '../runtime/agent/orchestrator/agent-owner.mjs';
25
+ import {
26
+ AGENT_MODE,
27
+ isRoutineSource,
28
+ resolveSessionMode,
29
+ sessionModeOf,
30
+ } from '../runtime/agent/orchestrator/session/agent-mode.mjs';
24
31
  import { unusedModelEditToolName } from '../runtime/shared/edit-tool-dialect.mjs';
32
+ import { MIXY_AGENT_ID } from '../runtime/agent/orchestrator/session/agent-home.mjs';
33
+ import {
34
+ refreshSessionBp3Environment,
35
+ withPresentedWorkspaceSlices,
36
+ } from '../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
37
+ import { stageWorkflowSwitchNotice } from '../runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs';
38
+ import { markToolSurfaceMigrated } from '../runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs';
39
+ import { agentProfileRevision, leadRosterRevision } from '../runtime/agent/orchestrator/context/role-instructions.mjs';
40
+ import { recordTranscriptBoundary } from './model-route/set-route.mjs';
41
+
42
+ const WORKFLOW_SWITCH_BUSY = 'The session is running a turn; wait for it to finish.';
43
+ const AGENT_PROFILE_INTENT = 'agent_profile';
25
44
 
26
45
  function toolNames(list) {
27
46
  return (Array.isArray(list) ? list : [])
@@ -46,16 +65,28 @@ function rewriteSystemHeading(session, heading, nextContent) {
46
65
  if (target) replaceMessageContent(messages, target, nextContent);
47
66
  }
48
67
 
49
- function rewriteBp3Core(session, nextCore) {
68
+ function bp3Message(session) {
50
69
  const messages = Array.isArray(session.messages) ? session.messages : [];
51
- const bp3 =
70
+ return (
52
71
  messages.find((message) => message?.role === 'system' && message.cacheTier === 'tier3') ||
53
72
  messages.find(
54
73
  (message) =>
55
74
  message?.role === 'system' &&
56
75
  typeof message.content === 'string' &&
57
76
  message.content.startsWith('# Active Workflow:')
58
- );
77
+ )
78
+ );
79
+ }
80
+
81
+ // The BP3 core the session's prompt carries now (split layout: the whole
82
+ // tier3 block; combined: the stored core beside the environment).
83
+ function presentedBp3Core(session) {
84
+ return session.bp3EnvSplit === true ? bp3Message(session)?.content : session.bp3CoreContext;
85
+ }
86
+
87
+ function rewriteBp3Core(session, nextCore) {
88
+ const messages = Array.isArray(session.messages) ? session.messages : [];
89
+ const bp3 = bp3Message(session);
59
90
  if (!bp3 || typeof bp3.content !== 'string') return;
60
91
  if (session.bp3EnvSplit === true) {
61
92
  // Split layout: the environment lives in its own cacheTier:'env' system
@@ -69,6 +100,27 @@ function rewriteBp3Core(session, nextCore) {
69
100
  session.bp3CoreContext = nextCore || '';
70
101
  }
71
102
 
103
+ // A Lead's workflow slices (orchestration, agent and contact catalogs) for its
104
+ // mode, Mixy's with the contacts.
105
+ function leadPartsFor(session, leadPromptParts) {
106
+ return leadPromptParts({
107
+ agentMode: sessionModeOf(session) === AGENT_MODE,
108
+ mixy: resolveSessionMode({ session }).agentId === MIXY_AGENT_ID,
109
+ });
110
+ }
111
+
112
+ // A conversation keeps its `# Session` block: the legacy combined layout
113
+ // rebuilds the tier3 block from the new core (rooted where it was).
114
+ function rewriteConversationBp3Core(session, nextCore) {
115
+ if (session.bp3EnvSplit !== true && typeof session.bp3CoreContext === 'string') {
116
+ const previous = session.bp3CoreContext;
117
+ session.bp3CoreContext = nextCore;
118
+ if (refreshSessionBp3Environment(session, session.cwd)) return;
119
+ session.bp3CoreContext = previous;
120
+ }
121
+ rewriteBp3Core(session, nextCore);
122
+ }
123
+
72
124
  export function createToolPolicyRefresh({
73
125
  getSession,
74
126
  getRoute,
@@ -137,15 +189,11 @@ export function createToolPolicyRefresh({
137
189
  );
138
190
  }
139
191
 
140
- async function refreshEmptySessionToolPolicy() {
141
- invalidatePreSessionToolSurface?.();
142
- const session = getSession?.();
143
- if (!session?.id) return { appliedToCurrentSession: true };
144
- if (!admitsRefresh(session)) return { appliedToCurrentSession: false };
145
- // Older records contain only compiled prompt text; rebuilding would guess
146
- // away custom instructions. Keep that immutable material for every owner.
147
- const identity = session.promptIdentity;
148
- if (!identity) return { appliedToCurrentSession: false, reason: 'missing-prompt-identity' };
192
+ // The policy `config` gives `session`, computed without touching the
193
+ // session, so a turn that starts during the async rebuild cannot lose tools
194
+ // to it. `conversation`: the rebuilt BP3 core keeps the instruction files
195
+ // and core memory the prompt presents now (withPresentedWorkspaceSlices).
196
+ async function stagePolicy(session, identity, config, { conversation = false } = {}) {
149
197
  const ownerIsAgent = isAgentOwner(session);
150
198
  const schemaAllowedTools = honoredSchemaAllowlist(session.schemaAllowedTools, {
151
199
  ownerIsAgent,
@@ -155,11 +203,13 @@ export function createToolPolicyRefresh({
155
203
 
156
204
  const {
157
205
  summary: workflow,
158
- context: leadWorkflowContext,
159
- packContext,
206
+ leadPromptParts,
207
+ packContext: workflowContext,
160
208
  orchestrationMode,
161
- } = activeWorkflowContext(getConfig(), getDataDir());
162
- const workflowContext = ownerIsAgent ? packContext : leadWorkflowContext;
209
+ } = activeWorkflowContext(config, getDataDir());
210
+ const sessionMode = sessionModeOf(session);
211
+ const agentMode = sessionMode === AGENT_MODE;
212
+ const leadWorkflowParts = ownerIsAgent ? {} : leadPartsFor(session, leadPromptParts);
163
213
  const denied = [
164
214
  ...session.callerDisallowedTools,
165
215
  ...featureDisallowedTools(),
@@ -168,8 +218,6 @@ export function createToolPolicyRefresh({
168
218
  .map((name) => String(name || ''))
169
219
  .filter(Boolean);
170
220
 
171
- // Stage: everything below is computed without touching the session, so a
172
- // turn that starts during the async rebuild cannot lose tools to it.
173
221
  const baseRules =
174
222
  identity.skipAgentRules || toolFreeRole
175
223
  ? ''
@@ -179,29 +227,201 @@ export function createToolPolicyRefresh({
179
227
  provider: getRoute()?.provider,
180
228
  model: getRoute()?.model,
181
229
  });
182
- const coreMemoryContext = await leadCoreMemoryContext();
230
+ const coreMemoryContext = conversation ? '' : await leadCoreMemoryContext();
183
231
  const { sessionMarkerCore } = composeSystemPrompt({
184
232
  ...sessionRolePromptFields(identity, {
185
233
  ownerIsAgent,
186
234
  toolFreeRole,
187
235
  delegationFree: delegationDisabled(session, ownerIsAgent),
236
+ sessionMode,
237
+ routine: isRoutineSource(session.sourceType),
188
238
  }),
189
239
  provider: getRoute()?.provider,
190
240
  workflowContext,
241
+ ...leadWorkflowParts,
191
242
  coreMemoryContext,
192
- // The rebuilt core keeps (or newly applies) the instruction-files setting.
193
- instructionFilesContext: toolFreeRole ? null : loadInstructionFilesContext?.(session) || null,
243
+ // The rebuilt core keeps (or newly applies) the instruction-files
244
+ // setting; agent mode carries none.
245
+ instructionFilesContext:
246
+ conversation || toolFreeRole || agentMode ? null : loadInstructionFilesContext?.(session) || null,
194
247
  });
248
+ return {
249
+ workflow,
250
+ orchestrationMode,
251
+ sessionMode,
252
+ denied,
253
+ schemaAllowedTools,
254
+ ownerIsAgent,
255
+ baseRules,
256
+ core: conversation ? withPresentedWorkspaceSlices(session, sessionMarkerCore) : sessionMarkerCore,
257
+ };
258
+ }
259
+
260
+ // Apply a staged policy synchronously; the BP3 core is the caller's.
261
+ function applyPolicy(session, staged) {
262
+ session.workflow = toSessionWorkflowMeta(staged.workflow);
263
+ session.orchestrationMode = staged.orchestrationMode;
264
+ session.sessionMode = staged.sessionMode;
265
+ applyToolDenials(session, staged.denied, staged.schemaAllowedTools, staged.ownerIsAgent);
266
+ rewriteSystemHeading(session, '# Tool Use', staged.baseRules);
267
+ session.updatedAt = Date.now();
268
+ }
269
+
270
+ async function refreshEmptySessionToolPolicy() {
271
+ invalidatePreSessionToolSurface?.();
272
+ const session = getSession?.();
273
+ if (!session?.id) return { appliedToCurrentSession: true };
274
+ if (!admitsRefresh(session)) return { appliedToCurrentSession: false };
275
+ // Older records contain only compiled prompt text; rebuilding would guess
276
+ // away custom instructions. Keep that immutable material for every owner.
277
+ const identity = session.promptIdentity;
278
+ if (!identity) return { appliedToCurrentSession: false, reason: 'missing-prompt-identity' };
279
+ const staged = await stagePolicy(session, identity, getConfig());
195
280
  // Apply: re-check admission after the await, then mutate synchronously.
196
281
  if (!admitsRefresh(session)) return { appliedToCurrentSession: false };
197
- session.workflow = toSessionWorkflowMeta(workflow);
198
- session.orchestrationMode = orchestrationMode;
199
- applyToolDenials(session, denied, schemaAllowedTools, ownerIsAgent);
200
- rewriteSystemHeading(session, '# Tool Use', baseRules);
201
- rewriteBp3Core(session, sessionMarkerCore);
202
- session.updatedAt = Date.now();
282
+ applyPolicy(session, staged);
283
+ rewriteBp3Core(session, staged.core);
203
284
  return { appliedToCurrentSession: true };
204
285
  }
205
286
 
206
- return { refreshEmptySessionToolPolicy };
287
+ /**
288
+ * Switch the current coding session's workflow mid-conversation (the
289
+ * composer's workflow selector): its prompt (BP3 core and `# Tool Use`) and
290
+ * tool/delegation surface are rebuilt now for `workflowId` with the current
291
+ * orchestration mode and agents, so its next turn runs on them, and that
292
+ * turn carries a one-time notice (manager/workflow-switch-notice.mjs). The
293
+ * provider prompt cache is rebuilt once. Refused while a turn runs, like a
294
+ * working-folder change. The configured workflow (the default for new
295
+ * sessions) is the caller's and stays as it is.
296
+ *
297
+ * Running work is untouched: agents and workers the session started under
298
+ * the old workflow are separate sessions with their own prompts and tools,
299
+ * so they keep running as they are (unlike a working-folder change, running
300
+ * agents never refuse this); only delegation made from the next turn on
301
+ * uses the new surface.
302
+ *
303
+ * Returns null when the session has no conversation (the caller applies the
304
+ * configured workflow to an empty session as before), else
305
+ * `{ appliedToCurrentSession: true, workflow, workflowSwitch }` where
306
+ * `workflowSwitch` is the transcript boundary (null when unchanged).
307
+ */
308
+ async function switchSessionWorkflow(workflowId) {
309
+ const session = getSession?.();
310
+ if (!session?.id) return null;
311
+ const conversation = () =>
312
+ sessionHasConversationMessages(session) || sessionHasConversationMessages({ messages: session.liveTurnMessages });
313
+ if (!conversation()) return null;
314
+ if (isAgentOwner(session) || sessionModeOf(session) === AGENT_MODE) {
315
+ throw new Error('Only a coding session has a workflow.');
316
+ }
317
+ const identity = session.promptIdentity;
318
+ if (!identity) throw new Error('This session is too old to switch its workflow; start a new task.');
319
+ const busy = () => getSession?.() !== session || turnInFlight(session);
320
+ if (busy()) throw new Error(WORKFLOW_SWITCH_BUSY);
321
+ const config = getConfig();
322
+ const staged = await stagePolicy(
323
+ session,
324
+ identity,
325
+ { ...config, workflow: { ...(config?.workflow || {}), active: workflowId } },
326
+ { conversation: true }
327
+ );
328
+ if (busy()) throw new Error(WORKFLOW_SWITCH_BUSY);
329
+ const before = session.workflow;
330
+ const from = before?.id ? before : null;
331
+ applyPolicy(session, staged);
332
+ rewriteConversationBp3Core(session, staged.core);
333
+ // The request prefix changed: drop provider replay state and the context
334
+ // baseline, and tag the next send's expected cache break.
335
+ markToolSurfaceMigrated(session, 'workflow_switch');
336
+ stageWorkflowSwitchNotice(session, from, session.workflow);
337
+ let workflowSwitch = null;
338
+ if (from?.id !== session.workflow.id) {
339
+ workflowSwitch = {
340
+ fromId: from?.id || '',
341
+ from: from?.name || from?.id || '',
342
+ toId: session.workflow.id,
343
+ to: session.workflow.name,
344
+ at: Date.now(),
345
+ };
346
+ // Two switches can land in the same millisecond (A→B→A): the same
347
+ // switch is a repeat only when its direction matches too.
348
+ recordTranscriptBoundary(
349
+ session,
350
+ 'workflowSwitches',
351
+ workflowSwitch,
352
+ (entry) =>
353
+ entry?.at === workflowSwitch.at &&
354
+ entry?.fromId === workflowSwitch.fromId &&
355
+ entry?.toId === workflowSwitch.toId
356
+ );
357
+ }
358
+ return { appliedToCurrentSession: true, workflow: session.workflow, workflowSwitch };
359
+ }
360
+
361
+ // An agent profile edit that met a running turn: the next turn applies it.
362
+ let agentProfilePending = false;
363
+
364
+ /**
365
+ * Bring a Lead session's prompt to the saved bots — its own agent's
366
+ * persona, AGENT.md and name, and the bot roster its catalogs list — after
367
+ * a bot was saved, created, renamed, archived or restored
368
+ * (agent-profile-changes.mjs), on resume, or at the start of the turn after
369
+ * one that ran through the change (`trigger`: 'save' | 'resume' | 'turn').
370
+ * Two stamps (agentProfileRevision, agentRosterRevision) tell whether
371
+ * anything it embeds changed; a delegated worker keeps its prompt. The core
372
+ * is rebuilt like a workflow switch's (the session's own workflow, the
373
+ * workspace slices the model was told) and replaces the stored one, so the
374
+ * next turn sends it with one provider cache break; nothing is re-read per
375
+ * turn. Tools are untouched. Returns true when the prompt changed.
376
+ */
377
+ async function refreshAgentProfile({ trigger = 'save' } = {}) {
378
+ const session = getSession?.();
379
+ const identity = session?.promptIdentity;
380
+ const agent = identity?.agent;
381
+ if (!session?.id || !agent || isAgentOwner(session)) return false;
382
+ // Only Mixy's own (agent-mode) thread embeds Mixy's profile.
383
+ const revision = agentProfileRevision(agent, { mixy: sessionModeOf(session) === AGENT_MODE });
384
+ const config = getConfig();
385
+ const workflowId = session.workflow?.id;
386
+ const workflowConfig = workflowId
387
+ ? { ...config, workflow: { ...(config?.workflow || {}), active: workflowId } }
388
+ : config;
389
+ const roster = leadRosterRevision(
390
+ leadPartsFor(session, activeWorkflowContext(workflowConfig, getDataDir()).leadPromptParts)
391
+ );
392
+ if (session.agentProfileRevision === revision && session.agentRosterRevision === roster) return false;
393
+ // A coding session from before profile stamps keeps its prompt when it
394
+ // is reopened; a saved edit still reaches it.
395
+ if (session.agentProfileRevision === undefined && trigger === 'resume' && sessionModeOf(session) !== AGENT_MODE) {
396
+ return false;
397
+ }
398
+ // At a turn's start its own turn is the one counted; nothing runs yet.
399
+ const busy = () => getSession?.() !== session || (trigger !== 'turn' && turnInFlight(session));
400
+ if (busy()) {
401
+ agentProfilePending = true;
402
+ return false;
403
+ }
404
+ const staged = await stagePolicy(session, identity, workflowConfig, { conversation: true });
405
+ if (busy()) {
406
+ agentProfilePending = true;
407
+ return false;
408
+ }
409
+ session.agentProfileRevision = revision;
410
+ session.agentRosterRevision = roster;
411
+ session.updatedAt = Date.now();
412
+ if (staged.core === presentedBp3Core(session)) return false;
413
+ rewriteConversationBp3Core(session, staged.core);
414
+ // The request prefix changed: one cache break, tagged on the next send.
415
+ markToolSurfaceMigrated(session, AGENT_PROFILE_INTENT);
416
+ return true;
417
+ }
418
+
419
+ /** Turn start: apply an edit that arrived while the previous turn ran. */
420
+ async function applyPendingAgentProfile() {
421
+ if (!agentProfilePending) return false;
422
+ agentProfilePending = false;
423
+ return refreshAgentProfile({ trigger: 'turn' });
424
+ }
425
+
426
+ return { refreshEmptySessionToolPolicy, switchSessionWorkflow, refreshAgentProfile, applyPendingAgentProfile };
207
427
  }