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
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Held requests: unattended runs never auto-approve. In a schedule, webhook or
3
+ * group-room-member run, an approval-needing call is HELD instead of shown as
4
+ * a card: the run pauses on it, a structured held-request row lands in the
5
+ * owning agent's home thread (agent-home-delivery.mjs queue), a phone push
6
+ * says only "<agent>가 승인을 기다립니다" (no tool, arguments or text), and the
7
+ * request expires after HELD_REQUEST_EXPIRY_MS into a deny, so the run goes
8
+ * on with the denial. The user answers from the home thread through the
9
+ * ordinary approval call with the held id (runtime/shared/held-request-host.mjs).
10
+ *
11
+ * Importing this module installs the unattended port of human-gate/gate.mjs.
12
+ * Rooms: a room member session counts as unattended by its id; a surface that
13
+ * hosts its own approval UI (a room) keeps it, and may call holdApprovalRequest
14
+ * itself to hold one request for the member's agent.
15
+ */
16
+ import { randomUUID } from 'node:crypto';
17
+ import { MIXY_AGENT_ID, agentHomeSessionId } from '../runtime/agent/orchestrator/session/agent-home.mjs';
18
+ import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
19
+ import { mixyDisplayName } from '../runtime/agent/orchestrator/session/mixy-name.mjs';
20
+ import {
21
+ recordHumanDecision,
22
+ setUnattendedApprovalPort,
23
+ } from '../runtime/agent/orchestrator/session/human-gate/gate.mjs';
24
+ import { normalizeHumanResponse } from '../runtime/agent/orchestrator/session/human-gate/requests.mjs';
25
+ import { normalizeHeldRequest } from '../runtime/agent/orchestrator/session/human-gate/held-message.mjs';
26
+ import {
27
+ notifyHeldRequest,
28
+ recordRunOwner,
29
+ registerHeldRequestResolver,
30
+ } from '../runtime/shared/held-request-host.mjs';
31
+
32
+ /** Default expiry; MIXDOG_HELD_REQUEST_EXPIRY_MS overrides it. */
33
+ export const HELD_REQUEST_EXPIRY_MS = 5 * 60_000;
34
+
35
+ function configuredExpiryMs() {
36
+ const value = Number(process.env.MIXDOG_HELD_REQUEST_EXPIRY_MS);
37
+ return Number.isFinite(value) && value > 0 ? value : HELD_REQUEST_EXPIRY_MS;
38
+ }
39
+
40
+ const runs = new Map(); // sessionId → { kind, agentId, name }
41
+
42
+ /** Mark `sessionId` as an unattended schedule/webhook run of `agentId`. */
43
+ export function markUnattendedRun(sessionId, { kind, agentId, name = '' }) {
44
+ if (!sessionId) return () => {};
45
+ const run = {
46
+ kind: kind === 'webhook' ? 'webhook' : 'schedule',
47
+ agentId: agentId || MIXY_AGENT_ID,
48
+ name: String(name),
49
+ };
50
+ runs.set(sessionId, run);
51
+ recordRunOwner(sessionId, run.agentId);
52
+ return () => {
53
+ if (runs.get(sessionId) === run) runs.delete(sessionId);
54
+ };
55
+ }
56
+
57
+ /** The unattended run `sessionId` belongs to, or null for an attended session. */
58
+ export function unattendedRunOf(sessionId) {
59
+ const run = runs.get(sessionId);
60
+ if (run) return run;
61
+ const member = roomMemberOfSession(sessionId);
62
+ return member ? { kind: 'room', agentId: member.agentId, name: member.roomId } : null;
63
+ }
64
+
65
+ /**
66
+ * A schedule or webhook turn has nobody to hand a step to: a takeover there
67
+ * (an expired sign-in, 2FA, a CAPTCHA, a confirmation) ends the turn at once
68
+ * instead of waiting, and the routine turn (routine-home-turn.mjs) fails with
69
+ * routineHumanNeededText. `outcome` is login_required, human_action_required
70
+ * or sensitive_confirmation_required. Rooms keep their own UI. Resolves false
71
+ * for a session that is not such a run.
72
+ */
73
+ export async function failRunForHuman(sessionId, outcome, site, abortTurn = defaultAbortTurn) {
74
+ const run = runs.get(sessionId);
75
+ if (!run) return false;
76
+ run.humanNeeded = { outcome, site: String(site || '') };
77
+ await abortTurn(sessionId);
78
+ return true;
79
+ }
80
+
81
+ async function defaultAbortTurn(sessionId) {
82
+ const { abortSessionTurn } = await import('../runtime/agent/orchestrator/session/manager/session-close.mjs');
83
+ abortSessionTurn(sessionId, 'login-required');
84
+ }
85
+
86
+ /** Push name of an agent; the representative's display name for the Lead. */
87
+ function defaultAgentName(agentId) {
88
+ return agentId === MIXY_AGENT_ID ? mixyDisplayName() : agentId;
89
+ }
90
+
91
+ /**
92
+ * The held-request registry. `deliver(agentId, held)` posts a row into the
93
+ * home thread; `push(event)` sends the sanitized phone push; `record(gate,
94
+ * approval)` writes the audit line.
95
+ */
96
+ export function createHeldRequests({
97
+ deliver,
98
+ push = notifyHeldRequest,
99
+ record = recordHumanDecision,
100
+ agentName = defaultAgentName,
101
+ expiryMs = configuredExpiryMs,
102
+ runOf = unattendedRunOf,
103
+ now = Date.now,
104
+ log = (line) => process.stderr.write(`mixdog held-requests: ${line}\n`),
105
+ }) {
106
+ const pending = new Map();
107
+ const post = (agentId, held) =>
108
+ Promise.resolve()
109
+ .then(() => deliver(agentId, held))
110
+ .catch((error) => log(`held request ${held.id} could not be delivered: ${error?.message || error}`));
111
+
112
+ function settle(id, approval, status) {
113
+ const entry = pending.get(id);
114
+ if (!entry) return false;
115
+ pending.delete(id);
116
+ clearTimeout(entry.timer);
117
+ entry.release();
118
+ void post(entry.agentId, { ...entry.held, status, ...(approval.device ? { device: approval.device } : {}) });
119
+ void Promise.resolve()
120
+ .then(() => record(entry.gate, approval))
121
+ .catch((error) => log(`held request ${id} audit failed: ${error?.message || error}`));
122
+ entry.resolve(approval);
123
+ return true;
124
+ }
125
+
126
+ /** Hold `request` (the approval hook's payload) until answered or expired. */
127
+ function hold(request = {}, { signal } = {}) {
128
+ const run = runOf(request.sessionId) || { kind: 'schedule', agentId: MIXY_AGENT_ID, name: '' };
129
+ const id = `held_${randomUUID()}`;
130
+ const ms = typeof expiryMs === 'function' ? expiryMs() : expiryMs;
131
+ const held = normalizeHeldRequest({
132
+ id,
133
+ status: 'pending',
134
+ agentId: run.agentId,
135
+ kind: request.kind || 'approval',
136
+ tool: request.name,
137
+ target: request.outbound?.target || '',
138
+ reason: request.reason || '',
139
+ ...(request.kind === 'outbound'
140
+ ? {
141
+ payload: request.outbound?.payload || '',
142
+ action: request.outbound?.category,
143
+ account: request.outbound?.account,
144
+ editable: request.outbound?.editable,
145
+ details: request.outbound?.details,
146
+ }
147
+ : {}),
148
+ remember: request.remember || ['once'],
149
+ run: { kind: run.kind, name: run.name, sessionId: request.sessionId },
150
+ expiresAt: now() + ms,
151
+ });
152
+ const shape = { kind: held.kind, outbound: request.outbound, remember: request.remember || ['once'] };
153
+ const gate = {
154
+ agentId: run.agentId,
155
+ kind: held.kind,
156
+ ...(held.action ? { action: held.action } : {}),
157
+ tool: held.tool,
158
+ target: held.target,
159
+ };
160
+ return new Promise((resolve) => {
161
+ const onAbort = () => settle(id, { ...normalizeHumanResponse(shape, false), reason: 'run cancelled' }, 'denied');
162
+ signal?.addEventListener?.('abort', onAbort, { once: true });
163
+ const entry = {
164
+ held,
165
+ shape,
166
+ gate,
167
+ agentId: run.agentId,
168
+ resolve,
169
+ release: () => signal?.removeEventListener?.('abort', onAbort),
170
+ timer: setTimeout(
171
+ () =>
172
+ settle(
173
+ id,
174
+ { ...normalizeHumanResponse(shape, false), reason: 'held request expired', timedOut: true },
175
+ 'expired'
176
+ ),
177
+ ms
178
+ ),
179
+ };
180
+ entry.timer.unref?.();
181
+ pending.set(id, entry);
182
+ void post(run.agentId, held);
183
+ try {
184
+ push({
185
+ sessionId: agentHomeSessionId(run.agentId) || agentHomeSessionId(MIXY_AGENT_ID),
186
+ agentId: run.agentId,
187
+ agentName: agentName(run.agentId),
188
+ requestId: id,
189
+ });
190
+ } catch (error) {
191
+ log(`held request ${id} push failed: ${error?.message || error}`);
192
+ }
193
+ });
194
+ }
195
+
196
+ /** Answer held request `id`; false when it is not pending (already settled). */
197
+ function resolve(id, decision = {}) {
198
+ const entry = pending.get(id);
199
+ if (!entry) return false;
200
+ const approved = decision === true || decision?.approved === true;
201
+ const approval = {
202
+ ...normalizeHumanResponse(entry.shape, approved, decision),
203
+ reason: String(decision?.reason || (approved ? 'approved by user' : 'denied by user')),
204
+ ...(typeof decision?.device === 'string' && decision.device ? { device: decision.device } : {}),
205
+ };
206
+ return settle(id, approval, approved ? 'approved' : 'denied');
207
+ }
208
+
209
+ return { hold, resolve, pending: () => [...pending.values()].map((entry) => entry.held) };
210
+ }
211
+
212
+ let processHeld = null;
213
+ async function defaultDeliver(agentId, held) {
214
+ const { deliverHeldRequest } = await import('./agent-home-delivery.mjs');
215
+ return deliverHeldRequest(agentId, held);
216
+ }
217
+ function heldRequests() {
218
+ processHeld ??= createHeldRequests({ deliver: defaultDeliver });
219
+ return processHeld;
220
+ }
221
+
222
+ /** Hold one request for its session's agent (rooms call this from their own hook). */
223
+ export function holdApprovalRequest(request, options) {
224
+ return heldRequests().hold(request, options);
225
+ }
226
+
227
+ setUnattendedApprovalPort({ runOf: unattendedRunOf, hold: holdApprovalRequest, humanNeeded: failRunForHuman });
228
+ registerHeldRequestResolver((id, decision) => heldRequests().resolve(id, decision));
@@ -1,11 +1,123 @@
1
1
  // ask_user: put 1-4 multiple-choice questions to the user through the session's
2
2
  // tool-approval hook (kind: 'question') and report the answers to the model.
3
+ import { randomUUID } from 'node:crypto';
3
4
  import {
5
+ validateAskUserHandoff,
4
6
  validateAskUserQuestions,
5
7
  ASK_USER_MAX_TEXT,
6
8
  } from '../../runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs';
7
9
  import { createCallerContextResolvers } from './caller-context.mjs';
8
10
  import { runAbortable } from '../../runtime/shared/abort-race.mjs';
11
+ import { SECRET_REQUEST_TIMEOUT_MS } from '../../runtime/agent/orchestrator/session/human-gate/requests.mjs';
12
+ import {
13
+ agentIdForSession,
14
+ recordHumanDecision,
15
+ unattendedApprovalPort,
16
+ } from '../../runtime/agent/orchestrator/session/human-gate/gate.mjs';
17
+ import { browserSecretInput } from '../../runtime/browser-bridge/client.mjs';
18
+ import { computerSecretInput } from '../../runtime/computer-bridge/client.mjs';
19
+
20
+ /**
21
+ * A secret or takeover handoff. Secret: the card collects the value on the
22
+ * desktop, which holds it for `requestId` and fills it into the target field
23
+ * (isolated-world fill for the browser, one-shot typed input for computer
24
+ * use); here only the verdict travels, and the result is exactly 'provided',
25
+ * 'skipped' or 'timeout'. Takeover: the run waits until the user says done
26
+ * (computer and browser actions stay blocked meanwhile, desktop
27
+ * main/takeover-gate.ts); the result is 'done' or 'denied'.
28
+ */
29
+ export async function runAskUserHandoff(handoff, { hook, sessionId, toolCallId, signal, inputs = {} }) {
30
+ const secretInput = {
31
+ browser: inputs.browser || browserSecretInput,
32
+ computer: inputs.computer || computerSecretInput,
33
+ };
34
+ const gate = {
35
+ agentId: agentIdForSession(sessionId),
36
+ kind: handoff.kind,
37
+ tool: `ask_user:${handoff.surface}`,
38
+ target: handoff.target || handoff.field || '',
39
+ };
40
+ // A schedule or webhook run has nobody to take over: the step ends it now,
41
+ // reported by what it needs (a sign-in, a step, a confirmation), never a waiting card.
42
+ const outcome = HANDOFF_OUTCOMES[handoff.need] || HANDOFF_OUTCOMES.action;
43
+ if (
44
+ handoff.kind === 'takeover' &&
45
+ (await unattendedApprovalPort()?.humanNeeded?.(sessionId, outcome, handoffSite(handoff)))
46
+ ) {
47
+ return UNATTENDED_TAKEOVER;
48
+ }
49
+ if (typeof hook !== 'function') {
50
+ // Nobody can take over here: blocked, never "proceed".
51
+ if (handoff.kind === 'takeover') return NO_TAKEOVER;
52
+ await recordHumanDecision(gate, { response: 'skipped' });
53
+ return 'skipped';
54
+ }
55
+ if (handoff.kind === 'secret') {
56
+ const requestId = `secret_${randomUUID()}`;
57
+ const result = await runAbortable(signal, () =>
58
+ hook({
59
+ name: 'ask_user',
60
+ kind: 'secret',
61
+ secret: { requestId, surface: handoff.surface, target: handoff.target, field: handoff.field },
62
+ reason: '',
63
+ timeoutMs: SECRET_REQUEST_TIMEOUT_MS,
64
+ sessionId,
65
+ toolCallId,
66
+ })
67
+ );
68
+ let verdict = 'skipped';
69
+ if (result?.timedOut === true) verdict = 'timeout';
70
+ else if (result?.approved === true && result.response === 'provide-secret') {
71
+ verdict = await secretInput[handoff.surface](sessionId, requestId, { signal });
72
+ }
73
+ await recordHumanDecision(gate, { response: verdict, device: result?.device });
74
+ return verdict;
75
+ }
76
+ const result = await runAbortable(signal, () =>
77
+ hook({
78
+ name: 'ask_user',
79
+ kind: 'takeover',
80
+ takeover: {
81
+ surface: handoff.surface,
82
+ instructions: handoff.instructions,
83
+ target: handoff.target,
84
+ ...handoff.identity,
85
+ // A login takeover: the desktop opens its sign-in window on this page.
86
+ ...(handoff.login ? { purpose: 'login', url: handoff.login.url } : {}),
87
+ },
88
+ args: { surface: handoff.surface, instructions: handoff.instructions },
89
+ reason: '',
90
+ timeoutMs: null,
91
+ sessionId,
92
+ toolCallId,
93
+ })
94
+ );
95
+ const answer = result?.approved === true ? 'done' : 'denied';
96
+ await recordHumanDecision(gate, { response: answer, device: result?.device });
97
+ return answer;
98
+ }
99
+
100
+ /** The unattended outcome of a takeover by what it needs (routine-result.mjs). */
101
+ const HANDOFF_OUTCOMES = Object.freeze({
102
+ sign_in: 'login_required',
103
+ action: 'human_action_required',
104
+ confirmation: 'sensitive_confirmation_required',
105
+ });
106
+ const UNATTENDED_TAKEOVER =
107
+ 'Nobody is present in this routine run, so it ends here and the user is told what needs them. Stop; do not retry or work around this step.';
108
+ const NO_TAKEOVER =
109
+ 'Blocked: nobody can take over in this session, so this step was not done. Stop and tell the user what needs them; do not work around it.';
110
+
111
+ /** The site a takeover is about: the first host its target (or instructions)
112
+ * names, else the target as the model wrote it. */
113
+ export function handoffSite(handoff) {
114
+ if (handoff.login) return new URL(handoff.login.url).hostname;
115
+ for (const text of [handoff.target, handoff.instructions]) {
116
+ const host = /\b((?:[a-z0-9-]+\.)+[a-z]{2,})\b/i.exec(String(text || ''))?.[1];
117
+ if (host) return host.toLowerCase();
118
+ }
119
+ return String(handoff.target || '').trim();
120
+ }
9
121
 
10
122
  const NO_USER =
11
123
  'No user is available to answer in this session. Proceed with your best judgment and state the choices you made.';
@@ -63,6 +175,15 @@ export function createAskUserToolHandlers({ rt }) {
63
175
  const { sessionIdFor, signalFor } = createCallerContextResolvers(rt);
64
176
  return {
65
177
  ask_user: async (args, { callerCtx }) => {
178
+ const handoff = validateAskUserHandoff(args);
179
+ if (handoff) {
180
+ return runAskUserHandoff(handoff, {
181
+ hook: callerCtx?.toolApprovalHook,
182
+ sessionId: sessionIdFor(callerCtx),
183
+ toolCallId: callerCtx?.toolCallId || null,
184
+ signal: signalFor(callerCtx),
185
+ });
186
+ }
66
187
  const questions = validateAskUserQuestions(args);
67
188
  const hook = callerCtx?.toolApprovalHook;
68
189
  if (typeof hook !== 'function') return NO_USER;
@@ -46,6 +46,10 @@ export function createFeatureToolHandlers({ rt, setupTool, officeToolsEnabled, m
46
46
  signal: signalFor(callerCtx),
47
47
  });
48
48
  },
49
- setup: async (args, { callerCtx }) => await setupTool.execute(args || {}, { signal: signalFor(callerCtx) }),
49
+ setup: async (args, { callerCtx }) =>
50
+ await setupTool.execute(args || {}, {
51
+ signal: signalFor(callerCtx),
52
+ callerSessionId: callerCtx?.callerSessionId || null,
53
+ }),
50
54
  };
51
55
  }
@@ -1,12 +1,15 @@
1
1
  // Tools that read rather than act: web search/fetch, memory, the code graph,
2
2
  // the deferred tool catalog, and skill bodies.
3
3
  import {
4
+ deferredCatalogUnion,
4
5
  refreshDeferredMcpToolCatalog,
5
6
  renderToolSearch,
6
7
  } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog.mjs';
8
+ import { parseToolSelection } from '../../runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs';
7
9
  import { resolveDeferredSelectMode } from '../../runtime/agent/orchestrator/session/loop/deferred-call-through.mjs';
8
10
  import { dispatchWebSearchRuntimeTool, memoryToolArgsForCaller } from '../runtime-tool-routing.mjs';
9
11
  import { createCallerContextResolvers } from './caller-context.mjs';
12
+ import { memoryCallerScope } from '../services/memory-access.mjs';
10
13
 
11
14
  export function createKnowledgeToolHandlers({
12
15
  rt,
@@ -17,6 +20,7 @@ export function createKnowledgeToolHandlers({
17
20
  runNativeWebSearch,
18
21
  activeToolSurface,
19
22
  mcpStatus,
23
+ awaitMcpConnect,
20
24
  skillToolContent,
21
25
  }) {
22
26
  const { signalFor } = createCallerContextResolvers(rt);
@@ -29,10 +33,23 @@ export function createKnowledgeToolHandlers({
29
33
  runNativeWebSearch,
30
34
  });
31
35
 
36
+ // The calling session's memory scope always replaces any model-supplied
37
+ // one: the memory runtime authorizes every pool read and write by it. A
38
+ // caller named but not resolvable here counts as an unattended run.
39
+ const memoryCallerSession = (callerCtx) => {
40
+ if (callerCtx?.callerSession) return callerCtx.callerSession;
41
+ const id = callerCtx?.callerSessionId;
42
+ return !id || id === rt.session?.id ? rt.session || null : null;
43
+ };
32
44
  const memory = async (args, { name, callerCtx, callerCwd }) => {
33
45
  const memoryMod = await getMemoryModule();
34
46
  if (!memoryMod?.handleToolCall) throw new Error('memory runtime is not available');
35
- return await memoryMod.handleToolCall(name, memoryToolArgsForCaller(args, callerCwd), signalFor(callerCtx));
47
+ const memoryCaller = memoryCallerScope(memoryCallerSession(callerCtx));
48
+ return await memoryMod.handleToolCall(
49
+ name,
50
+ { ...memoryToolArgsForCaller(args, callerCwd), memory_caller: memoryCaller },
51
+ signalFor(callerCtx)
52
+ );
36
53
  };
37
54
 
38
55
  // Catalog tools answer for the calling session's own scope, bridge or
@@ -41,9 +58,21 @@ export function createKnowledgeToolHandlers({
41
58
  const scopeSession = (callerCtx) => callerCtx?.callerSession || activeToolSurface();
42
59
  const scopeMode = (session) => session?.deferredSurfaceMode || resolveDeferredSelectMode(session) || rt.mode;
43
60
 
44
- const toolSearch = (args, { callerCtx } = {}) => {
61
+ const catalogLacks = (session, names) => {
62
+ const known = new Set(deferredCatalogUnion(session).map((tool) => String(tool?.name || '').toLowerCase()));
63
+ return parseToolSelection(names).some((name) => !known.has(String(name).toLowerCase()));
64
+ };
65
+ // A name the catalog does not hold yet may belong to a server still in its
66
+ // handshake (a fresh session's first turn, or the reconnect after a
67
+ // sign-in): wait for that connect before reporting it missing. Returns at
68
+ // once when no connect is in flight.
69
+ const toolSearch = async (args, { callerCtx } = {}) => {
45
70
  const session = scopeSession(callerCtx);
46
71
  refreshDeferredMcpToolCatalog(session, rt.config);
72
+ if (awaitMcpConnect && catalogLacks(session, args?.names)) {
73
+ await awaitMcpConnect();
74
+ refreshDeferredMcpToolCatalog(session, rt.config);
75
+ }
47
76
  return renderToolSearch(args, session, scopeMode(session), { mcpStatus });
48
77
  };
49
78
 
@@ -5,9 +5,33 @@ import { resolve } from 'node:path';
5
5
  import { listProjects } from '../../runtime/shared/projects.mjs';
6
6
  import { applySessionSidebarChange } from '../../runtime/shared/session-sidebar-host.mjs';
7
7
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
8
+ import { AGENT_MODE, resolveSessionMode } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
9
+ import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
10
+ import { scheduleCallerScope } from '../services/schedule-access.mjs';
11
+ import { resolveHandoffAttachments, sessionRoots } from '../handoff-attachments.mjs';
8
12
 
9
13
  const USER_TITLE_ERROR = 'session: the user already titled this session; the title was not changed';
10
14
 
15
+ /** The agent an attended agent conversation (an agent's home thread) acts as;
16
+ * '' for every other caller: delegated workers, coding sessions (task
17
+ * sessions included) and unattended schedule/webhook runs. */
18
+ function attendedAgentOf(session) {
19
+ const { mode, agentId } = resolveSessionMode({ session });
20
+ const { kind } = scheduleCallerScope(session);
21
+ return mode === AGENT_MODE && (kind === 'human' || kind === 'agent') ? agentId : '';
22
+ }
23
+
24
+ /** The caller's `attachments` as stored prompt parts, resolved against its own
25
+ * conversation and folders (handoff-attachments.mjs). */
26
+ function callerAttachmentParts(rt, args, callerCwd, surface) {
27
+ return resolveHandoffAttachments(args?.attachments, {
28
+ messages: rt.session?.messages,
29
+ roots: sessionRoots(rt.session, callerCwd),
30
+ cwd: callerCwd,
31
+ surface,
32
+ }).parts;
33
+ }
34
+
11
35
  export function createWorkspaceToolHandlers({
12
36
  rt,
13
37
  goalRuntime,
@@ -65,12 +89,37 @@ export function createWorkspaceToolHandlers({
65
89
  const session = async (args, { callerCtx, callerCwd }) => {
66
90
  const action = clean(args?.action || (args?.path ? 'set-project' : 'status')).toLowerCase();
67
91
  const target = clean(args?.session_id);
68
- if (['list', 'read', 'send', 'cancel', 'delete'].includes(action) || (action === 'status' && target)) {
92
+ if (['list', 'read', 'send', 'cancel', 'delete', 'create'].includes(action) || (action === 'status' && target)) {
69
93
  const callerSessionId = assertLead(action, callerCtx);
94
+ const callerAgentId = attendedAgentOf(rt.session);
95
+ if (action === 'create' && !callerAgentId) {
96
+ throw new Error(
97
+ 'session: create belongs to a bot conversation; delegated workers, agent task sessions and schedule/webhook runs cannot create agent task sessions'
98
+ );
99
+ }
100
+ // A task session reaches its agent only through its reported result.
101
+ const origin = rt.session?.taskOrigin;
102
+ if (
103
+ action === 'send' &&
104
+ origin &&
105
+ (target === origin.sessionId || agentIdOfHomeSession(target) === origin.agentId)
106
+ ) {
107
+ throw new Error('session: a task session reports to its agent only through its result');
108
+ }
70
109
  if (typeof executeSessionControl !== 'function') {
71
110
  throw new Error('session: cross-session control is unavailable in this runtime');
72
111
  }
73
- return JSON.stringify(await executeSessionControl({ ...args, action }, { callerSessionId }));
112
+ const { attachments, ...rest } = args || {};
113
+ if (attachments !== undefined && action !== 'send' && action !== 'create') {
114
+ throw new Error(`session: attachments apply to send and create, not action=${action}`);
115
+ }
116
+ const attachmentParts = callerAttachmentParts(rt, args, callerCwd, 'session');
117
+ return JSON.stringify(
118
+ await executeSessionControl(
119
+ { ...rest, action },
120
+ { callerSessionId, callerAgentId, callerCwd, ...(attachmentParts.length ? { attachmentParts } : {}) }
121
+ )
122
+ );
74
123
  }
75
124
  if (target && target !== assertLead(action, callerCtx)) {
76
125
  throw new Error(`session: action=${action} only applies to the current session`);
@@ -192,7 +241,11 @@ export function createWorkspaceToolHandlers({
192
241
  }),
193
242
  agent: async (args, { callerCtx, callerCwd }) => {
194
243
  const callerSessionId = callerCtx?.callerSessionId || rt.session?.id || null;
195
- return await agentTool.execute(args, {
244
+ // `attachmentParts` is never the model's to set: only the resolved
245
+ // `attachments` reach the agent's turn.
246
+ const { attachments, attachmentParts: _ignored, ...rest } = args || {};
247
+ const attachmentParts = callerAttachmentParts(rt, { attachments }, callerCwd, 'agent');
248
+ return await agentTool.execute(attachmentParts.length ? { ...rest, attachmentParts } : rest, {
196
249
  callerCwd,
197
250
  invocationSource: 'model-tool',
198
251
  callerSessionId,
@@ -30,6 +30,7 @@ export function createInternalToolExecutor({
30
30
  runNativeWebSearch,
31
31
  activeToolSurface,
32
32
  mcpStatus,
33
+ awaitMcpConnect,
33
34
  skillToolContent,
34
35
  getWorkspaceApi,
35
36
  getSessionManager,
@@ -49,6 +50,7 @@ export function createInternalToolExecutor({
49
50
  runNativeWebSearch,
50
51
  activeToolSurface,
51
52
  mcpStatus,
53
+ awaitMcpConnect,
52
54
  skillToolContent,
53
55
  }),
54
56
  ...createWorkspaceToolHandlers({
@@ -3,6 +3,8 @@
3
3
  * catalog: durable visibility filter, lead-shape heuristics and preview rows.
4
4
  */
5
5
  import { isAgentOwner } from '../../runtime/agent/orchestrator/agent-owner.mjs';
6
+ import { normalizeRoutineProvenance } from '../../runtime/agent/orchestrator/session/routine-result.mjs';
7
+ import { sessionModeOf } from '../../runtime/agent/orchestrator/session/agent-mode.mjs';
6
8
  import {
7
9
  isAgentOnlySession,
8
10
  isRootLeadSession,
@@ -70,9 +72,12 @@ export function leadSessionRow(s, working = false) {
70
72
  if (!agentSession && !isLeadShaped(s, owner, sourceType)) return null;
71
73
  const { preview, messageCount, realTurnCount } = previewOf(s);
72
74
  if (!preview && messageCount === 0) return null;
75
+ const lastMessageRoutine = normalizeRoutineProvenance(s.lastMessageRoutine);
73
76
  return {
74
77
  id: s.id,
75
78
  agent: s.agent || null,
79
+ // 'agent' | 'coding' (agent-mode.mjs): the desktop renders agent-mode chats.
80
+ sessionMode: sessionModeOf(s),
76
81
  owner,
77
82
  visibility: isAgentOnlySession(s) ? 'agent-only' : 'ordinary',
78
83
  ownerSessionId: s.ownerSessionId || s.parentSessionId || null,
@@ -92,6 +97,10 @@ export function leadSessionRow(s, working = false) {
92
97
  messageCount,
93
98
  title: cleanSessionPreview(s.title || '', 100),
94
99
  preview,
100
+ // The latest user or assistant line (an agent contact's row shows it).
101
+ ...(s.lastMessagePreview ? { lastMessagePreview: String(s.lastMessagePreview) } : {}),
102
+ // That line's routine provenance: clients label a routine result by it.
103
+ ...(lastMessageRoutine ? { lastMessageRoutine } : {}),
95
104
  // Working indicator: the in-memory runtime state ALONE is the liveness
96
105
  // signal. The persisted lastHeartbeatAt JSON field (refreshed by the final
97
106
  // save) must not be folded in — it outlives the turn it describes.
@@ -60,6 +60,7 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
60
60
  getStandaloneTools,
61
61
  beginRoutePreparation,
62
62
  clearRoutePreparation,
63
+ refreshAgentProfile,
63
64
  } = deps;
64
65
 
65
66
  function restoreResumedToolSurface(target, targetRoute) {
@@ -165,6 +166,9 @@ export function createSessionResume(deps, { ingestSessionIntoMemory, closeSurfac
165
166
  // preparation temporarily dropped loaded tools and invalidated the
166
167
  // provider-aligned context baseline.
167
168
  restoreResumedToolSurface(resumed, resumedRoute);
169
+ // A thread running as an agent whose profile was edited while no runtime
170
+ // held it takes the saved profile now (tool-policy-refresh.mjs).
171
+ await refreshAgentProfile?.({ trigger: 'resume' });
168
172
  const finishRoutePreparation = routePreparation(resumed, resumedRoute);
169
173
  if (typeof beginRoutePreparation === 'function') {
170
174
  beginRoutePreparation(finishRoutePreparation);
@@ -3,6 +3,8 @@
3
3
  * lifecycle groups (teardown, session switching, inheritance).
4
4
  */
5
5
  import { hasUserConversationMessage } from '../../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
6
+ import { isAgentHomeSessionId } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
7
+ import { isRoomThreadSessionId } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
6
8
 
7
9
  export { SESSION_ID_PATTERN } from '../../runtime/agent/orchestrator/runtime-core/session-id.mjs';
8
10
 
@@ -19,7 +21,11 @@ export function isScratchConversation(messages, liveTurnMessages) {
19
21
  );
20
22
  }
21
23
 
24
+ /** An agent's home thread is never scratch: a blank one is tombstoned on no
25
+ * close, so its fixed id stays open for the next visit. Neither is a group
26
+ * room's log or member session (agent-room.mjs), whose ids are fixed too. */
22
27
  export function isScratchSession(session) {
28
+ if (isAgentHomeSessionId(session?.id) || isRoomThreadSessionId(session?.id)) return false;
23
29
  return isScratchConversation(session?.messages, session?.liveTurnMessages);
24
30
  }
25
31
 
@@ -2,6 +2,7 @@
2
2
  // servers plus the servers registered plugins declare (resolved live, with
3
3
  // explicit owner metadata), per-server config reads, and the live/failed rollup.
4
4
  import { clean } from '../../runtime/agent/orchestrator/runtime-core/session-text.mjs';
5
+ import { connectorOfServer, gmailAppPasswordAccount } from '../../runtime/shared/connector-catalog.mjs';
5
6
  import { envFlag } from '../../runtime/shared/env.mjs';
6
7
  import { resolveMcpServerCatalog } from './plugin-servers.mjs';
7
8
 
@@ -142,9 +143,16 @@ export function createMcpServerCatalog({
142
143
  source: sources[name] || 'config',
143
144
  };
144
145
  if (authRequired) row.authRequired = true;
146
+ // The catalog service this server connects (connector-catalog.mjs):
147
+ // its marker.
148
+ const connector = entry.owner ? '' : connectorOfServer(cfg);
149
+ if (connector) row.connector = connector;
145
150
  const oauth = mcpClient.getMcpServerOAuthState?.(cfg);
146
151
  // Why the last sign-in did not complete (already credential-scrubbed).
147
152
  if (oauth) row.oauth = authRequired && fail?.msg ? { ...oauth, lastError: fail.msg } : oauth;
153
+ // Gmail connected with an app password: the account it runs as.
154
+ const appPasswordAccount = gmailAppPasswordAccount(cfg);
155
+ if (appPasswordAccount) row.appPassword = { account: appPasswordAccount };
148
156
  if (entry.owner) {
149
157
  row.owner = entry.owner;
150
158
  row.pluginEnabled = entry.pluginEnabled;
@@ -70,6 +70,9 @@ export function createMcpServerInput({ mcpClient, getCurrentCwd }) {
70
70
  if (bearerTokenEnvVar) config.bearer_token_env_var = bearerTokenEnvVar;
71
71
  const envHeaders = coerceStringRecord(input.env_http_headers || input.envHttpHeaders);
72
72
  if (envHeaders) config.env_http_headers = envHeaders;
73
+ // The user's own OAuth client (앱 번호); its secret never lands here.
74
+ const oauthClientId = clean(input.oauthClientId);
75
+ if (oauthClientId) config.oauthClientId = oauthClientId;
73
76
  return config;
74
77
  };
75
78
  const url = clean(input.url);