mixdog 1.2.6 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  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 +7 -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 +11 -4
  13. package/src/defaults/skills/setup-guide/SKILL.md +175 -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/rules/agent/AGENT.md +4 -0
  17. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  18. package/src/rules/agent-mode/MIXY.md +21 -0
  19. package/src/rules/agent-mode/ROUTINE.md +6 -0
  20. package/src/rules/lead/LEAD.md +7 -3
  21. package/src/rules/shared/70-delivery.md +3 -0
  22. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  23. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  24. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  25. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
  26. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  27. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
  28. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  29. package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
  30. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  31. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  32. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  33. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  34. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  35. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  36. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  37. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  38. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
  39. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
  40. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  41. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  42. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  43. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  44. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
  45. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  46. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  47. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +12 -0
  48. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  49. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  50. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  51. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  52. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  53. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  54. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  55. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  56. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  57. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  58. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  59. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  60. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  61. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  62. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  63. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  64. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  65. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  66. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  67. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  68. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  69. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  70. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  71. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  72. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  73. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
  74. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
  75. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  76. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  77. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  78. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  79. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
  80. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  81. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  83. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  84. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  85. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  86. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  87. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  88. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  89. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  90. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  91. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  92. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  93. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  94. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  95. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  96. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  97. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
  98. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
  99. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  100. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  101. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  102. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  103. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  105. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
  106. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
  107. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
  108. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
  109. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
  110. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
  111. package/src/runtime/browser-bridge/client.mjs +24 -0
  112. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  113. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  114. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  115. package/src/runtime/channels/lib/webhook.mjs +0 -1
  116. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  117. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  118. package/src/runtime/computer-bridge/client.mjs +28 -1
  119. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  120. package/src/runtime/connectors/google-mcp.mjs +446 -0
  121. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  122. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  123. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  124. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  125. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  126. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  127. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  128. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  129. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  130. package/src/runtime/memory/lib/memory.mjs +7 -1
  131. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  132. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  133. package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
  134. package/src/runtime/memory/tool-defs.mjs +5 -0
  135. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  136. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  137. package/src/runtime/shared/agent-avatar.mjs +320 -0
  138. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  139. package/src/runtime/shared/agent-route-config.mjs +16 -0
  140. package/src/runtime/shared/atomic-file.mjs +14 -1
  141. package/src/runtime/shared/config.mjs +8 -1
  142. package/src/runtime/shared/connector-accounts.mjs +15 -0
  143. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  144. package/src/runtime/shared/connector-catalog.mjs +230 -0
  145. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  146. package/src/runtime/shared/connector-triggers.mjs +43 -0
  147. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  148. package/src/runtime/shared/held-request-host.d.mts +12 -0
  149. package/src/runtime/shared/held-request-host.mjs +71 -0
  150. package/src/runtime/shared/plain-preview.d.mts +1 -0
  151. package/src/runtime/shared/plain-preview.mjs +28 -0
  152. package/src/runtime/shared/routine-limits.d.mts +11 -0
  153. package/src/runtime/shared/routine-limits.mjs +21 -0
  154. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  155. package/src/runtime/shared/routine-trigger.mjs +44 -0
  156. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  157. package/src/runtime/shared/slow-span.mjs +4 -12
  158. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  159. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  160. package/src/runtime/shared/turn-changes.mjs +0 -8
  161. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  162. package/src/runtime/shared/user-data-guard.mjs +114 -36
  163. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  164. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  165. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  166. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  167. package/src/session-runtime/boot/apis.mjs +50 -18
  168. package/src/session-runtime/boot/facade.mjs +5 -8
  169. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  170. package/src/session-runtime/boot/tools.mjs +3 -0
  171. package/src/session-runtime/channel-config-api.mjs +53 -5
  172. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  173. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  174. package/src/session-runtime/held-requests.mjs +228 -0
  175. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  176. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  177. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  178. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  179. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  180. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  181. package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
  182. package/src/session-runtime/lifecycle/shared.mjs +6 -0
  183. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  184. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  185. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  186. package/src/session-runtime/model-route/set-route.mjs +14 -3
  187. package/src/session-runtime/owned-automation-run.mjs +229 -0
  188. package/src/session-runtime/resource-api.mjs +5 -0
  189. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  190. package/src/session-runtime/resource-skills-api.mjs +12 -0
  191. package/src/session-runtime/room-member-gate.mjs +48 -0
  192. package/src/session-runtime/routine-home-turn.mjs +156 -0
  193. package/src/session-runtime/routine-owner.mjs +48 -0
  194. package/src/session-runtime/schedule-session-run.mjs +30 -64
  195. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  196. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  197. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  198. package/src/session-runtime/services/channel-admin.mjs +93 -24
  199. package/src/session-runtime/services/memory-access.mjs +37 -0
  200. package/src/session-runtime/services/provider-admin.mjs +74 -39
  201. package/src/session-runtime/services/schedule-access.mjs +91 -0
  202. package/src/session-runtime/session-create/session-options.mjs +34 -7
  203. package/src/session-runtime/session-hooks.mjs +16 -8
  204. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  205. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  206. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  207. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  208. package/src/session-runtime/skills-api.mjs +16 -1
  209. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  210. package/src/session-runtime/turn/session-ops.mjs +8 -1
  211. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
  212. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  213. package/src/session-runtime/webhook-session-run.mjs +49 -67
  214. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  215. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  216. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  217. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  218. package/src/standalone/daemon-agent-control.mjs +1 -8
  219. package/src/standalone/daemon-session-control.mjs +11 -2
  220. package/src/standalone/daemon.mjs +18 -4
  221. package/src/standalone/session-protocol.mjs +27 -0
  222. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  223. package/src/standalone/session-service.mjs +19 -0
  224. package/src/tui/app/slash-commands.mjs +20 -0
  225. package/src/tui/dist/index.mjs +164 -86
  226. package/src/tui/session/context-state.mjs +6 -1
  227. package/src/tui/session/notice-surface.mjs +35 -4
  228. package/src/tui/session/queue-helpers.mjs +2 -1
  229. package/src/tui/session/session-api/extensions.mjs +4 -0
  230. package/src/tui/session/session-api/intake/submission.mjs +5 -0
  231. package/src/tui/session/session-api/integrations.mjs +8 -0
  232. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  233. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  234. package/src/tui/session/session-api/routes.mjs +48 -3
  235. package/src/tui/session/session-flow/drain.mjs +12 -3
  236. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  237. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  238. package/src/tui/session/session-flow/steering.mjs +58 -24
  239. package/src/tui/session/tool-approval.mjs +26 -5
  240. package/src/tui/session/transcript-intake.mjs +6 -2
  241. package/src/tui/session/transcript-spill.mjs +5 -1
  242. package/src/tui/session/tui-steering-persist.mjs +65 -42
  243. package/src/tui/session/turn-live-call.mjs +56 -0
  244. package/src/tui/session/turn.mjs +22 -6
  245. package/src/session-runtime/automation-agents.mjs +0 -94
  246. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -23,13 +23,14 @@ import {
23
23
  setEndpointEnabled as dbSetEndpointEnabled,
24
24
  } from '../../runtime/channels/lib/webhooks-db.mjs';
25
25
  import { normalizeAutomationAttachments } from '../../runtime/shared/automation-attachments.mjs';
26
- import { migrateCodexModelRef } from '../../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs';
26
+ import { assertRoutineOwner, routineOwnerProblem } from '../routine-owner.mjs';
27
27
  import { hasEnabledAutomation } from '../../runtime/shared/automation-presence.mjs';
28
28
  import { resolveScheduleTimezone, validateScheduleCron } from '../../runtime/shared/schedule-time.mjs';
29
29
  import { pgClusterExists } from '../../runtime/memory/lib/pg/adapter.mjs';
30
30
  import { resolvePluginData } from '../../runtime/shared/plugin-paths.mjs';
31
31
  import { readHookPublicBase } from '../../runtime/channels/lib/webhook/relay-tunnel.mjs';
32
32
  import { WEBHOOK_TEST_FIELD, WEBHOOK_TEST_HEADER } from '../../runtime/channels/lib/webhook/request-handler.mjs';
33
+ import { MIXY_AGENT_ID, agentHomeSessionId } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
33
34
 
34
35
  const NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
35
36
 
@@ -187,7 +188,7 @@ function parseAtDatetime(at) {
187
188
  // Map the store's def shape onto the flat display shape every schedule reader
188
189
  // (channelSetup, renderChannelStatus, TUI pickers) consumes: `.time` renders
189
190
  // the cron or the one-shot datetime, `.route` the channel/session target.
190
- function scheduleToDisplay(s) {
191
+ function scheduleToDisplay(s, ownerProblem = '') {
191
192
  return {
192
193
  name: s.name,
193
194
  description: s.description || '',
@@ -195,26 +196,85 @@ function scheduleToDisplay(s) {
195
196
  whenAt: s.whenAt || undefined,
196
197
  whenCron: s.whenCron || undefined,
197
198
  timezone: s.timezone || undefined,
198
- // Shown (and re-saved by the editor) on the catalog successor of a superseded model.
199
- model: s.model ? migrateCodexModelRef(s.model) : undefined,
200
199
  cwd: s.cwd || undefined,
201
- workflow: s.workflow || undefined,
202
200
  attachments: s.attachments || undefined,
203
201
  enabled: s.enabled !== false,
204
202
  instructions: s.prompt,
205
203
  route: 'session',
204
+ // The bot it belongs to: each run is a turn in that bot's chat.
205
+ owner: s.ownerAgentId || MIXY_AGENT_ID,
206
+ // Why it is paused for its owner (routine-owner.mjs); absent when it runs.
207
+ ...(ownerProblem ? { ownerProblem } : {}),
208
+ nextFireAt: isoOrUndefined(s.nextFireAt),
209
+ lastSuccessAt: isoOrUndefined(s.lastSuccessAt),
210
+ lastFailedAt: isoOrUndefined(s.lastFailedAt),
211
+ lastSkippedAt: isoOrUndefined(s.lastSkippedAt),
212
+ ...routineHealth(s),
213
+ };
214
+ }
215
+
216
+ // Failures in a row (the routine pauses itself at the limit) and the results
217
+ // of finished runs still not queued for the owner: `{ count, failed, error }`,
218
+ // `failed` when the queue refused one (the app offers a retry).
219
+ function routineHealth(row) {
220
+ const entries = Object.values(row.resultDelivery || {}).filter(Boolean);
221
+ const refused = entries.filter((entry) => entry.state === 'failed').at(-1);
222
+ return {
223
+ consecutiveFailures: Number(row.consecutiveFailures) || 0,
224
+ ...(entries.length
225
+ ? {
226
+ undelivered: {
227
+ count: entries.length,
228
+ failed: Boolean(refused),
229
+ ...(refused?.error ? { error: String(refused.error) } : {}),
230
+ },
231
+ }
232
+ : {}),
233
+ };
234
+ }
235
+
236
+ function isoOrUndefined(value) {
237
+ if (!value) return undefined;
238
+ const at = new Date(value);
239
+ return Number.isNaN(at.getTime()) ? undefined : at.toISOString();
240
+ }
241
+
242
+ // A schedule's or webhook's owner: an explicit agent id, else the saved owner
243
+ // on an overwrite, else Mixy.
244
+ function scheduleOwner(owner, existing) {
245
+ const value = String(owner ?? '').trim();
246
+ if (value) {
247
+ if (!agentHomeSessionId(value)) throw new Error(`schedule owner "${value}" is not a valid agent id`);
248
+ return value;
249
+ }
250
+ return existing?.ownerAgentId || MIXY_AGENT_ID;
251
+ }
252
+
253
+ /** Each owner's problem (routine-owner.mjs), probed once per owner. */
254
+ function ownerProblems() {
255
+ const seen = new Map();
256
+ return (ownerAgentId) => {
257
+ const owner = ownerAgentId || MIXY_AGENT_ID;
258
+ if (!seen.has(owner))
259
+ seen.set(
260
+ owner,
261
+ routineOwnerProblem(owner).catch(() => '')
262
+ );
263
+ return seen.get(owner);
206
264
  };
207
265
  }
208
266
 
209
267
  export async function listSchedules() {
210
268
  const rows = await dbListSchedules();
211
- return rows.map(scheduleToDisplay);
269
+ const problemOf = ownerProblems();
270
+ return Promise.all(rows.map(async (row) => scheduleToDisplay(row, await problemOf(row.ownerAgentId))));
212
271
  }
213
272
 
214
273
  // Register or update a schedule in the PG store. Recurring input maps `time`
215
274
  // (+ optional `days`) to a cron; one-shot input maps an `at` datetime; the two
216
275
  // are mutually exclusive (also enforced by the store's when_at/when_cron XOR).
217
- // Every schedule runs as a visible app session and requires a model.
276
+ // Every schedule runs as a turn in its owner bot's chat; a bot that does not
277
+ // exist (no representative bot yet, an archived bot) cannot own one.
218
278
  export async function saveSchedule({
219
279
  name,
220
280
  description = '',
@@ -222,26 +282,26 @@ export async function saveSchedule({
222
282
  at,
223
283
  timezone,
224
284
  days,
225
- model,
226
285
  cwd,
227
- workflow,
228
286
  attachments,
229
287
  enabled,
230
288
  instructions,
289
+ owner,
231
290
  overwrite = false,
232
291
  } = {}) {
233
292
  const id = assertScheduleName(name);
234
293
  const body = String(instructions || '').trim();
235
294
  if (!body) throw new Error('schedule instructions are required');
236
- const scheduleModel = String(model || '').trim();
237
- if (!scheduleModel) throw new Error('schedule model is required — choose a model before saving');
238
295
  const hasTime = time != null && String(time).trim() !== '';
239
296
  const hasAt = at != null && String(at).trim() !== '';
240
297
  if (hasTime && hasAt) throw new Error('provide either `time` (recurring) or `at` (one-shot), not both');
241
298
  if (!hasTime && !hasAt) throw new Error('either `time` (recurring cron) or `at` (one-shot datetime) is required');
242
- if (overwrite !== true && (await dbGetSchedule(id))) {
299
+ const existing = await dbGetSchedule(id);
300
+ if (overwrite !== true && existing) {
243
301
  throw new Error(`schedule "${id}" already exists`);
244
302
  }
303
+ const ownerAgentId = scheduleOwner(owner, existing);
304
+ await assertRoutineOwner(ownerAgentId);
245
305
  const whenCron = hasTime ? validateScheduleCron(foldDaysIntoCron(normalizeCron(time), days)) : null;
246
306
  const whenAt = hasAt ? parseAtDatetime(at) : null;
247
307
  const scheduleTimezone = hasTime ? resolveScheduleTimezone(timezone) : null;
@@ -253,14 +313,13 @@ export async function saveSchedule({
253
313
  timezone: scheduleTimezone,
254
314
  target: 'session',
255
315
  channelId: null,
256
- model: scheduleModel,
257
316
  cwd: cwd ? String(cwd).trim() : null,
258
- workflow: workflow ? String(workflow).trim() : null,
259
317
  attachments: normalizeAutomationAttachments(attachments),
260
318
  delivery: 'app',
261
319
  prompt: body,
262
320
  enabled: enabled !== false,
263
321
  nextFireAt: whenAt,
322
+ ownerAgentId,
264
323
  });
265
324
  automationProbe.invalidate();
266
325
  return scheduleToDisplay(saved);
@@ -287,13 +346,13 @@ export async function setScheduleEnabled(name, enabled) {
287
346
  // store's migration hook.
288
347
  async function listWebhooks() {
289
348
  const endpoints = await dbListEndpoints();
290
- return endpoints.map((ep) => ({
349
+ const problemOf = ownerProblems();
350
+ const problems = await Promise.all(endpoints.map((ep) => problemOf(ep.ownerAgentId)));
351
+ return endpoints.map((ep, index) => ({
291
352
  name: ep.name,
292
353
  description: ep.description || '',
293
354
  parser: ep.parser || 'github',
294
- ...(ep.model ? { model: migrateCodexModelRef(ep.model) } : {}),
295
355
  ...(ep.cwd ? { cwd: ep.cwd } : {}),
296
- ...(ep.workflow ? { workflow: ep.workflow } : {}),
297
356
  ...(ep.attachments ? { attachments: ep.attachments } : {}),
298
357
  enabled: ep.enabled,
299
358
  // The store never projects the plaintext secret through list paths; it
@@ -302,6 +361,16 @@ async function listWebhooks() {
302
361
  secret: undefined,
303
362
  instructions: ep.instructions,
304
363
  route: 'session',
364
+ // The bot it belongs to: each run is a turn in that bot's chat.
365
+ owner: ep.ownerAgentId || MIXY_AGENT_ID,
366
+ ...(problems[index] ? { ownerProblem: problems[index] } : {}),
367
+ lastEventAt: isoOrUndefined(ep.lastEventAt),
368
+ lastSuccessAt: isoOrUndefined(ep.lastSuccessAt),
369
+ lastFailedAt: isoOrUndefined(ep.lastFailedAt),
370
+ lastSkippedAt: isoOrUndefined(ep.lastSkippedAt),
371
+ // Whether "Run now" has a received payload to re-run (never the payload).
372
+ hasLastPayload: ep.hasLastPayload === true,
373
+ ...routineHealth(ep),
305
374
  }));
306
375
  }
307
376
 
@@ -310,12 +379,11 @@ export async function saveWebhook({
310
379
  description = '',
311
380
  parser = 'github',
312
381
  secret,
313
- model,
314
382
  cwd,
315
- workflow,
316
383
  attachments,
317
384
  enabled,
318
385
  instructions,
386
+ owner,
319
387
  overwrite = false,
320
388
  } = {}) {
321
389
  const id = assertName(name, 'webhook name');
@@ -327,9 +395,12 @@ export async function saveWebhook({
327
395
  }
328
396
  const body = String(instructions || '').trim();
329
397
  if (!body) throw new Error('webhook instructions are required');
330
- if (overwrite !== true && (await dbLoadEndpoint(id))) {
398
+ const existing = await dbLoadEndpoint(id);
399
+ if (overwrite !== true && existing) {
331
400
  throw new Error(`webhook "${id}" already exists`);
332
401
  }
402
+ const ownerAgentId = scheduleOwner(owner, existing);
403
+ await assertRoutineOwner(ownerAgentId);
333
404
  // Secret semantics: an explicit value always wins; an EMPTY value on an
334
405
  // overwrite PRESERVES the stored secret (editing instructions must not
335
406
  // silently rotate the key the external service was configured with); only
@@ -343,23 +414,21 @@ export async function saveWebhook({
343
414
  description: String(description || '').trim(),
344
415
  parser: nextParser,
345
416
  channelId: null,
346
- model: model ? String(model).trim() : null,
347
417
  cwd: cwd ? String(cwd).trim() : null,
348
- workflow: workflow ? String(workflow).trim() : null,
349
418
  attachments: normalizeAutomationAttachments(attachments),
350
419
  delivery: 'app',
351
420
  secret: secretValue,
352
421
  instructions: body,
353
422
  enabled: enabled !== false,
423
+ ownerAgentId,
354
424
  });
355
425
  automationProbe.invalidate();
356
426
  return {
357
427
  name: id,
428
+ owner: saved.ownerAgentId || ownerAgentId,
358
429
  description: saved.description,
359
430
  parser: saved.parser,
360
- ...(saved.model ? { model: saved.model } : {}),
361
431
  ...(saved.cwd ? { cwd: saved.cwd } : {}),
362
- ...(saved.workflow ? { workflow: saved.workflow } : {}),
363
432
  ...(saved.attachments ? { attachments: saved.attachments } : {}),
364
433
  ...(enabled === false ? { enabled: false } : {}),
365
434
  secret: secretValue,
@@ -0,0 +1,37 @@
1
+ // Whose memory a session reads and writes (runtime/memory/lib/
2
+ // core-memory-access.mjs enforces it). Mixy is the Lead configuration, so the
3
+ // user's own sessions (an ordinary Lead session, Mixy's home) act as Mixy; an
4
+ // agent's home thread and its room member sessions act as that agent; a
5
+ // schedule or webhook run acts for its owning agent (`ownerAgentId`, stamped
6
+ // at creation); a delegated agent as its own agent id.
7
+ import { MIXY_AGENT_ID, agentHomeSessionId } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
8
+ import { resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
9
+ import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
10
+ import { scheduleCallerScope } from './schedule-access.mjs';
11
+
12
+ const validAgentId = (value) => {
13
+ const id = typeof value === 'string' ? value.trim() : '';
14
+ return id && agentHomeSessionId(id) ? id : '';
15
+ };
16
+
17
+ /** The `memory_caller` `{ kind, agentId }` of a calling session record:
18
+ * 'human' and 'agent' are conversations with the user, 'room-member' and
19
+ * 'headless' unattended runs. */
20
+ export function memoryCallerScope(session) {
21
+ const scope = scheduleCallerScope(session);
22
+ if (scope.kind === 'human') return { kind: 'human', agentId: MIXY_AGENT_ID };
23
+ if (scope.kind !== 'headless') return scope;
24
+ const delegated = isAgentOwner(session) || Boolean(session?.parentSessionId);
25
+ return {
26
+ kind: 'headless',
27
+ agentId: validAgentId(session?.ownerAgentId) || (delegated ? validAgentId(session?.agent) : ''),
28
+ };
29
+ }
30
+
31
+ /** The agent whose own memory a session's prompt carries: its home thread's,
32
+ * room member's or run owner's agent, Mixy for the user's Lead sessions, and
33
+ * none for a delegated agent. `session` is the live record when there is
34
+ * one, else the reserved id and creation profile. */
35
+ export function injectedMemoryAgentId({ session = null, sessionId = '', profile = null } = {}) {
36
+ return resolveSessionMode({ session, sessionId, profile }).agentId;
37
+ }
@@ -367,6 +367,7 @@ function oauthAuthFields(p, auth) {
367
367
  status: auth.status || (authenticated ? 'Set' : 'Not Set'),
368
368
  detail: auth.detail || p.desc,
369
369
  expiresAt: auth.expiresAt || null,
370
+ plan: auth.plan || null,
370
371
  usable: auth.usable === true || (auth.usable == null && authenticated),
371
372
  refreshable: auth.refreshable === true,
372
373
  reauthRequired: auth.reauthRequired === true,
@@ -958,8 +959,8 @@ async function withTimeout(run, ms, label) {
958
959
  }
959
960
  }
960
961
 
961
- function existingCustomEntry(cfgMod, id) {
962
- const entry = cfgMod.loadConfig().providers?.[id];
962
+ function existingCustomEntry(config, id) {
963
+ const entry = config.providers?.[id];
963
964
  if (!isCustomProviderId(id) || !isCustomProviderConfig(entry)) throw new Error(`unknown custom provider "${id}"`);
964
965
  return entry;
965
966
  }
@@ -1021,16 +1022,15 @@ function withStoredSettings(input, entry) {
1021
1022
  return out;
1022
1023
  }
1023
1024
 
1024
- // Validated, secret-free config for `input`; the key (typed, else stored for an
1025
- // existing provider) is resolved separately and never enters the config.
1026
- async function resolveCustomInput(cfgMod, rawInput, options) {
1027
- let input = inputObject(rawInput);
1028
- validateCustomTransportFields(input);
1025
+ // Validated, secret-free config for `input` over one config snapshot (the
1026
+ // stored entry supplies an edit's omitted settings); the key (typed, else
1027
+ // stored for an existing provider) is resolved separately and never enters
1028
+ // the config. Synchronous, so a caller can merge and commit one snapshot.
1029
+ function customInputAt(snapshot, input, normalizeCustomProviderConfig, options) {
1029
1030
  const id = input.id === undefined ? null : input.id;
1030
- if (id) input = withStoredSettings(input, existingCustomEntry(cfgMod, id));
1031
- const { normalizeCustomProviderConfig } = await loadCustomProviderModule();
1032
- const config = normalizeCustomProviderConfig(input, options);
1033
- const typed = typeof input.apiKey === 'string' ? input.apiKey.trim() : '';
1031
+ const merged = id ? withStoredSettings(input, existingCustomEntry(snapshot, id)) : input;
1032
+ const config = normalizeCustomProviderConfig(merged, options);
1033
+ const typed = typeof merged.apiKey === 'string' ? merged.apiKey.trim() : '';
1034
1034
  const keyless = config.auth === 'none';
1035
1035
  const envName = keyless ? '' : config.apiKeyEnv || '';
1036
1036
  let apiKey = '';
@@ -1039,36 +1039,71 @@ async function resolveCustomInput(cfgMod, rawInput, options) {
1039
1039
  return { id, config, typed: keyless || envName ? '' : typed, apiKey, keyless, envName };
1040
1040
  }
1041
1041
 
1042
+ async function resolveCustomInput(cfgMod, rawInput, options) {
1043
+ const input = inputObject(rawInput);
1044
+ validateCustomTransportFields(input);
1045
+ const { normalizeCustomProviderConfig } = await loadCustomProviderModule();
1046
+ return customInputAt(cfgMod.loadConfig(), input, normalizeCustomProviderConfig, options);
1047
+ }
1048
+
1049
+ function requireCustomKey({ apiKey, keyless, envName }) {
1050
+ if (!keyless && !envName && !apiKey) throw new Error('API key is required');
1051
+ }
1052
+
1053
+ // One key transaction per provider, from reading the rollback key to the
1054
+ // config commit or rollback: a failed save restores the key the preceding
1055
+ // save committed and never one a newer save wrote. Settings-only saves and
1056
+ // other providers never wait.
1057
+ const customKeySaves = new Map();
1058
+
1059
+ function serializeCustomKeySave(id, run) {
1060
+ const result = (customKeySaves.get(id) || Promise.resolve()).then(run);
1061
+ const settled = result.then(
1062
+ () => {},
1063
+ () => {}
1064
+ );
1065
+ customKeySaves.set(id, settled);
1066
+ settled.then(() => {
1067
+ if (customKeySaves.get(id) === settled) customKeySaves.delete(id);
1068
+ });
1069
+ return result;
1070
+ }
1071
+
1042
1072
  export async function saveCustomProvider(cfgMod, rawInput) {
1043
1073
  requireLocalProviderManagement();
1044
- const {
1045
- id: existingId,
1046
- config,
1047
- typed,
1048
- apiKey,
1049
- keyless,
1050
- envName,
1051
- } = await resolveCustomInput(cfgMod, rawInput, { requireModels: false });
1052
- if (!keyless && !envName && !apiKey) throw new Error('API key is required');
1074
+ // Early verdict: an invalid save never touches the keychain.
1075
+ const resolved = await resolveCustomInput(cfgMod, rawInput, { requireModels: false });
1076
+ requireCustomKey(resolved);
1077
+ const { id: existingId, typed } = resolved;
1053
1078
  const id = existingId || `custom-${randomUUID()}`;
1054
- const entry = { ...config, type: 'custom', enabled: config.enabled !== false };
1055
- const account = SECRET_ACCOUNTS.agentApiKey(id);
1056
- const previousKey = typed && existingId ? getAgentApiKey(id) : null;
1057
- if (typed) await saveSecretAsync(account, typed);
1058
- try {
1059
- const current = cfgMod.loadConfig();
1060
- cfgMod.saveConfig(
1061
- { ...current, providers: { ...(current.providers || {}), [id]: entry } },
1062
- { baseConfig: current }
1063
- );
1064
- } catch (error) {
1065
- if (typed) {
1066
- if (previousKey) await saveSecretAsync(account, previousKey);
1067
- else await deleteSecretAsync(account);
1079
+ const { normalizeCustomProviderConfig } = await loadCustomProviderModule();
1080
+ const commit = async () => {
1081
+ const account = SECRET_ACCOUNTS.agentApiKey(id);
1082
+ const previousKey = typed && existingId ? getAgentApiKey(id) : null;
1083
+ if (typed) await saveSecretAsync(account, typed);
1084
+ let entry;
1085
+ try {
1086
+ // Synchronous from here: omitted settings come from the config this
1087
+ // save commits over, so an edit that landed during the awaits above (a
1088
+ // model's visibility, a peer save, a removal) is never reverted.
1089
+ const current = cfgMod.loadConfig();
1090
+ const latest = customInputAt(current, rawInput, normalizeCustomProviderConfig, { requireModels: false });
1091
+ requireCustomKey(latest);
1092
+ entry = { ...latest.config, type: 'custom', enabled: latest.config.enabled !== false };
1093
+ cfgMod.saveConfig(
1094
+ { ...current, providers: { ...(current.providers || {}), [id]: entry } },
1095
+ { baseConfig: current }
1096
+ );
1097
+ } catch (error) {
1098
+ if (typed) {
1099
+ if (previousKey) await saveSecretAsync(account, previousKey);
1100
+ else await deleteSecretAsync(account);
1101
+ }
1102
+ throw error;
1068
1103
  }
1069
- throw error;
1070
- }
1071
- return customProviderRow(id, entry);
1104
+ return customProviderRow(id, entry);
1105
+ };
1106
+ return typed ? serializeCustomKeySave(id, commit) : commit();
1072
1107
  }
1073
1108
 
1074
1109
  const MODEL_PATCH_KEYS = new Set(['manual', 'hidden', 'name', 'contextWindow', 'maxOutputTokens', 'reasoning']);
@@ -1104,7 +1139,7 @@ export async function updateCustomProviderModel(cfgMod, rawId, rawModelId, patch
1104
1139
  // Everything below is synchronous: the provider is read, patched and saved
1105
1140
  // without yielding, so a concurrent update can never be overwritten by a
1106
1141
  // provider object read before the await above.
1107
- const entry = existingCustomEntry(cfgMod, id);
1142
+ const entry = existingCustomEntry(cfgMod.loadConfig(), id);
1108
1143
  const current = normalizeCustomProviderConfig(entry, { requireModels: false });
1109
1144
  const overrides = new Map(Object.entries(current.modelOverrides || {}));
1110
1145
  if (patch === null) {
@@ -1241,7 +1276,7 @@ function withoutProviderReferences(config, id) {
1241
1276
  export async function removeCustomProvider(cfgMod, rawId) {
1242
1277
  requireLocalProviderManagement();
1243
1278
  const id = String(rawId || '').trim();
1244
- existingCustomEntry(cfgMod, id);
1279
+ existingCustomEntry(cfgMod.loadConfig(), id);
1245
1280
  // Secret first: a real keychain failure (locked, permission) leaves the
1246
1281
  // provider registered so the removal can be retried. A missing secret
1247
1282
  // (keyless provider) is not an error.
@@ -0,0 +1,91 @@
1
+ // Who may manage automations (schedules and webhooks) from a chat, by the
2
+ // calling session's provenance. Enforced by every setup automation action
3
+ // (setup-tool/extended-actions.mjs), the chat-facing door of channel-admin:
4
+ // - the human's own sessions (an ordinary Lead session, Mixy's home) manage
5
+ // every automation and may move one to an agent that exists;
6
+ // - another agent's home thread manages only the automations it owns and can
7
+ // never give one away;
8
+ // - a headless run (schedule, webhook, delegated agent, or a session that
9
+ // cannot be resolved) manages none — nor does it change any other setting
10
+ // (setup-tool/executor.mjs refuses every mutating setup action for it).
11
+ // - a group-room member session is no background run, but it speaks for the
12
+ // room, not for the user: it creates or changes no agent, automation or
13
+ // other setting either.
14
+ // The app's own panels call channel-admin directly and act for the human.
15
+ import { MIXY_AGENT_ID, agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
16
+ import { roomMemberOfSession } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
17
+ import { mixyDisplayName } from '../../runtime/agent/orchestrator/session/mixy-name.mjs';
18
+ import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
19
+
20
+ const INTERACTIVE_SOURCE_TYPES = new Set(['', 'lead', 'cli']);
21
+
22
+ /** `{ kind: 'human' | 'agent' | 'room-member' | 'headless', agentId? }` of a session record. */
23
+ export function scheduleCallerScope(session) {
24
+ const id = String(session?.id || '');
25
+ const member = roomMemberOfSession(id);
26
+ if (member) return { kind: 'room-member', agentId: member.agentId };
27
+ const homeAgent = agentIdOfHomeSession(id);
28
+ if (homeAgent) return homeAgent === MIXY_AGENT_ID ? { kind: 'human' } : { kind: 'agent', agentId: homeAgent };
29
+ const headless =
30
+ !id ||
31
+ isAgentOwner(session) ||
32
+ Boolean(session.parentSessionId) ||
33
+ Boolean(session.ownerSessionId && session.ownerSessionId !== id) ||
34
+ !INTERACTIVE_SOURCE_TYPES.has(String(session.sourceType || '').toLowerCase());
35
+ return headless ? { kind: 'headless' } : { kind: 'human' };
36
+ }
37
+
38
+ /** Throws unless `scope` may `action` the `kind` ('schedule' | 'webhook')
39
+ * automation `current` (null when creating) with `owner` the requested owner
40
+ * ('' keeps the saved one). `agentIds` are the agents a transfer may name;
41
+ * Mixy always counts. */
42
+ export function assertAutomationAccess(
43
+ scope,
44
+ { kind = 'schedule', action, current = null, owner = '', agentIds = [] }
45
+ ) {
46
+ if (scope?.kind === 'room-member') throw new Error(`A group-room member cannot ${action} ${kind}s`);
47
+ if (scope?.kind !== 'human' && scope?.kind !== 'agent') {
48
+ throw new Error(`A background run cannot ${action} ${kind}s`);
49
+ }
50
+ const requested = String(owner ?? '').trim();
51
+ const saved = current ? current.owner || MIXY_AGENT_ID : '';
52
+ if (scope.kind === 'agent') {
53
+ if (current && saved !== scope.agentId) {
54
+ throw new Error(
55
+ `${kind === 'webhook' ? 'Webhook' : 'Schedule'} "${current.name}" belongs to another agent; this thread manages only its own ${kind}s`
56
+ );
57
+ }
58
+ if (requested && requested !== scope.agentId) throw new Error(`This thread cannot give a ${kind} to another agent`);
59
+ return;
60
+ }
61
+ if (requested && requested !== saved && requested !== MIXY_AGENT_ID && !agentIds.includes(requested)) {
62
+ throw new Error(`Agent "${requested}" does not exist`);
63
+ }
64
+ }
65
+
66
+ /** Throws unless `caller` (scope `scope`) is Mixy — its home thread or an
67
+ * ordinary Lead session acting as 'lead': only Mixy creates, edits or
68
+ * deletes agent definitions from a chat. The app's own panels call the agent
69
+ * editor directly and act for the human. */
70
+ export function assertAgentEditorAccess(scope, caller, action) {
71
+ const acting = String(caller?.agent || '').trim();
72
+ if (scope?.kind === 'human' && (!acting || acting === MIXY_AGENT_ID)) return;
73
+ const name = mixyDisplayName();
74
+ throw new Error(
75
+ `Only ${name} (the representative agent) can create, edit or delete agents (setup ${action}); ask the user to do it with ${name} or in the Agents settings`
76
+ );
77
+ }
78
+
79
+ /** Throws when an agent's home thread changes another agent's settings. */
80
+ export function assertOwnAgentSettings(scope, agentId, action) {
81
+ if (scope?.kind === 'agent' && String(agentId || '').trim() !== scope.agentId) {
82
+ throw new Error(`This thread manages only its own agent's settings (setup ${action})`);
83
+ }
84
+ }
85
+
86
+ /** Throws when `scope` is a headless run: it reads settings, never changes them. */
87
+ export function assertInteractiveCaller(scope, action) {
88
+ if (scope?.kind === 'human' || scope?.kind === 'agent') return;
89
+ if (scope?.kind === 'room-member') throw new Error(`A group-room member cannot change settings (setup ${action})`);
90
+ throw new Error(`A background run cannot change settings (setup ${action})`);
91
+ }
@@ -2,11 +2,14 @@
2
2
  // the current runtime state: route, tool spec, ownership/lane, workflow pack,
3
3
  // memory context and compaction.
4
4
  import { normalizeCompactionConfig } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
5
- import { hasOwn } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
+ import { clean, hasOwn } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
6
6
  import { toolSpecForMode } from '../../runtime/agent/orchestrator/runtime-core/effort.mjs';
7
- import { pluginDataDir } from '../runtime-paths.mjs';
7
+ import { STANDALONE_ROOT, pluginDataDir } from '../runtime-paths.mjs';
8
+ import { readAgentFrontmatterPermission } from '../services/agent-tool/helpers.mjs';
8
9
  import { LEAD_DISALLOWED_TOOLS } from '../../runtime/agent/orchestrator/runtime-core/tool-defs.mjs';
9
10
  import { HEADLESS_TOOL_PROFILE } from '../../runtime/agent/orchestrator/runtime-core/tool-profile.mjs';
11
+ import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
12
+ import { MIXY_AGENT_ID } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
10
13
 
11
14
  // Ownership and permission fields an agent-owned session inherits from its profile.
12
15
  function agentOwnedSessionFields(sessionProfile) {
@@ -31,14 +34,30 @@ export function sessionOptions(deps, coreMemoryContext) {
31
34
  // Load the active WORKFLOW.md pack once for both summary + context block.
32
35
  const {
33
36
  summary: workflow,
34
- context: leadWorkflowContext,
37
+ leadPromptParts,
35
38
  packContext,
36
39
  orchestrationMode,
37
40
  } = activeWorkflowContext(rt.config, dataDir);
38
41
  const sessionProfile = rt.sessionProfile && typeof rt.sessionProfile === 'object' ? rt.sessionProfile : null;
39
42
  const agentOwned = sessionProfile?.owner === 'agent' || sessionProfile?.visibility === 'agent-only';
40
- // Agent-owned sessions keep the Active Workflow block only.
41
- const workflowContext = agentOwned ? packContext : leadWorkflowContext;
43
+ // Agent or coding mode, from the one resolver (agent-mode.mjs).
44
+ const { mode: sessionMode, agentId: actingAgent } = resolveSessionMode({
45
+ sessionId: rt.reservedSessionId,
46
+ profile: sessionProfile,
47
+ });
48
+ // An agent's home thread: a user conversation that runs AS that agent (its
49
+ // AGENT.md role and permission); route comes with the create call.
50
+ const homeAgent = agentOwned ? '' : clean(sessionProfile?.homeAgent);
51
+ const homePermission = homeAgent ? readAgentFrontmatterPermission(homeAgent, dataDir, STANDALONE_ROOT) : null;
52
+ // A coding task session an agent created (session create): the user's own
53
+ // Lead, tagged with its origin; `sourceName` lets the Agents list file it.
54
+ const taskOrigin = agentOwned ? null : sessionProfile?.taskOrigin || null;
55
+ // Every session leads BP3 with the Active Workflow block; a Lead adds its
56
+ // orchestration and catalog slices (coding, agent mode or Mixy's).
57
+ const workflowContext = packContext;
58
+ const leadWorkflowParts = agentOwned
59
+ ? undefined
60
+ : leadPromptParts({ agentMode: sessionMode === AGENT_MODE, mixy: actingAgent === MIXY_AGENT_ID });
42
61
  const callerDisallowedTools = [
43
62
  ...(agentOwned ? [] : LEAD_DISALLOWED_TOOLS),
44
63
  ...(!agentOwned && rt.disallowDelegation ? ['agent'] : []),
@@ -51,7 +70,8 @@ export function sessionOptions(deps, coreMemoryContext) {
51
70
  tools: toolSpecForMode(rt.mode),
52
71
  ...(Array.isArray(schemaAllowedTools) ? { schemaAllowedTools } : {}),
53
72
  owner: agentOwned ? 'agent' : 'cli',
54
- agent: agentOwned ? sessionProfile?.agent || 'worker' : 'lead',
73
+ agent: agentOwned ? sessionProfile?.agent || 'worker' : homeAgent || 'lead',
74
+ ...(homePermission ? { permission: homePermission } : {}),
55
75
  lane: agentOwned ? 'agent' : 'cli',
56
76
  // One-shot `mixdog exec` runs carry their own source so session catalogs
57
77
  // can keep them out of the user's resumable list.
@@ -60,7 +80,12 @@ export function sessionOptions(deps, coreMemoryContext) {
60
80
  : rt.toolProfile === HEADLESS_TOOL_PROFILE
61
81
  ? 'exec'
62
82
  : 'lead',
63
- sourceName: agentOwned ? sessionProfile?.sourceName || sessionProfile?.agent || 'agent' : 'main',
83
+ sourceName: agentOwned
84
+ ? sessionProfile?.sourceName || sessionProfile?.agent || 'agent'
85
+ : taskOrigin
86
+ ? `agent:${taskOrigin.agentId}`
87
+ : 'main',
88
+ ...(taskOrigin ? { taskOrigin } : {}),
64
89
  ...(rt.approvalMode ? { approvalMode: rt.approvalMode } : {}),
65
90
  clientHostPid: sessionProfile?.clientHostPid || process.pid,
66
91
  mcpScopeId: rt.mcpScopeId,
@@ -71,6 +96,8 @@ export function sessionOptions(deps, coreMemoryContext) {
71
96
  coreMemoryContext,
72
97
  workflow,
73
98
  workflowContext,
99
+ ...(leadWorkflowParts ? { leadWorkflowParts } : {}),
100
+ sessionMode,
74
101
  orchestrationMode,
75
102
  fast: rt.route.fast === true,
76
103
  autoEffort: rt.route.autoEffort !== false,