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
@@ -0,0 +1,200 @@
1
+ /**
2
+ * `session create`: an attended agent-mode conversation (an agent's home
3
+ * thread) hands coding to a new, visible coding-mode task session — the
4
+ * user's own Lead, created through the same session service and defaults as a
5
+ * desktop New task — and gets each of its own turns' results back.
6
+ *
7
+ * Only turns the origin agent starts (the brief, a later `send` from it)
8
+ * report: when such a turn ends its result is queued durably for the agent's
9
+ * home (agent-home-delivery.mjs), which posts it and wakes the agent once to
10
+ * relay it. A turn blocked on the user's answer or approval posts one faint
11
+ * line instead; the card stays in the task session for the user.
12
+ */
13
+ import { randomUUID } from 'node:crypto';
14
+ import { realpathSync, statSync } from 'node:fs';
15
+ import { basename, resolve } from 'node:path';
16
+ import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
17
+
18
+ const TITLE_LIMIT = 60;
19
+
20
+ /** `{ cwd, desktopSession, project }` of `project`: a registered Project name
21
+ * (or alias) or an existing directory; none starts a task without a Project
22
+ * in the caller's folder, as a desktop New task does in its task workspace. */
23
+ export function resolveTaskProject(project, { projects = [], cwd = process.cwd() } = {}) {
24
+ const requested = clean(project);
25
+ if (!requested) return { cwd, desktopSession: { classification: 'task', projectPath: null }, project: null };
26
+ const key = requested.toLowerCase();
27
+ const named = projects.filter((entry) => [entry.name, entry.alias].some((name) => clean(name).toLowerCase() === key));
28
+ if (named.length > 1)
29
+ throw new Error(`session: Project "${requested}" is ambiguous; ask the user which one is meant`);
30
+ let path = named[0]?.path || '';
31
+ if (!path) {
32
+ const candidate = resolve(cwd, requested);
33
+ let directory = false;
34
+ try {
35
+ directory = statSync(candidate).isDirectory();
36
+ } catch {}
37
+ if (!directory) {
38
+ throw new Error(
39
+ `session: "${requested}" is neither a registered Project nor an existing directory; run list-projects`
40
+ );
41
+ }
42
+ path = realpathSync(candidate);
43
+ }
44
+ return {
45
+ cwd: path,
46
+ desktopSession: { classification: 'project', projectPath: path },
47
+ project: { name: named[0]?.name || basename(path), path },
48
+ };
49
+ }
50
+
51
+ const turnStatus = (detail) =>
52
+ ({ done: 'completed', completed: 'completed', failed: 'failed' })[detail?.status] || 'cancelled';
53
+
54
+ export function createAgentTaskSessions({
55
+ getSessionService,
56
+ deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
57
+ loadSession = async (sessionId) =>
58
+ (await import('../runtime/agent/orchestrator/session/store.mjs')).loadSession(sessionId),
59
+ listProjects = async () => (await import('../runtime/shared/projects.mjs')).listProjects(),
60
+ newTaskDefault = async () =>
61
+ (await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
62
+ modelRoute = async (modelRef) => {
63
+ const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
64
+ import('../runtime/shared/schedule-model-ref.mjs'),
65
+ import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
66
+ import('../runtime/agent/orchestrator/config.mjs'),
67
+ ]);
68
+ // A provider/model[@effort…] ref (a superseded model runs on its
69
+ // successor) or a preset name.
70
+ const ref = parseScheduleModelRef(modelRef);
71
+ if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
72
+ const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
73
+ if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
74
+ return modelRouteFields(preset);
75
+ },
76
+ log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
77
+ }) {
78
+ const service = () => {
79
+ const value = getSessionService();
80
+ if (!value) throw new Error('session: session service is unavailable');
81
+ return value;
82
+ };
83
+
84
+ /** Submit `prompt` as the origin agent's turn; its end (and a wait for the
85
+ * user in it) reports to the agent's home. Resolves on intake. */
86
+ async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null) {
87
+ const sessions = service();
88
+ const report = (status, text = '') =>
89
+ Promise.resolve(beforeReport)
90
+ .then(() =>
91
+ deliver(origin.agentId, {
92
+ runId: `${id}_${status === 'blocked' ? 'blocked' : 'end'}`,
93
+ taskSessionId: sessionId,
94
+ title: origin.title,
95
+ status,
96
+ text,
97
+ })
98
+ )
99
+ .catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
100
+ const notify = origin.notify !== false;
101
+ let unwatch = () => {};
102
+ if (notify) {
103
+ const runtime = await sessions.materializeSession(sessionId);
104
+ let blocked = false;
105
+ unwatch =
106
+ runtime?.subscribe?.(() => {
107
+ if (blocked || !runtime.getState?.()?.toolApproval) return;
108
+ blocked = true;
109
+ void report('blocked');
110
+ }) || unwatch;
111
+ }
112
+ const result = await sessions.submitSession({
113
+ sessionId,
114
+ prompt,
115
+ options: {
116
+ id,
117
+ mode: 'prompt',
118
+ transcriptMeta: { sender: 'lead' },
119
+ ...(notify
120
+ ? {
121
+ onSettled: (detail) => {
122
+ unwatch();
123
+ void report(turnStatus(detail), detail?.result?.content || detail?.error || '');
124
+ },
125
+ }
126
+ : {}),
127
+ },
128
+ });
129
+ if (result?.accepted !== true) unwatch();
130
+ return result?.accepted === true;
131
+ }
132
+
133
+ /** Create the task session for `brief` and start it; returns at once. */
134
+ async function create(args = {}, { callerSessionId, callerAgentId, callerCwd } = {}) {
135
+ if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
136
+ const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
137
+ if (!brief) throw new Error('session: brief is required for action=create');
138
+ const where = resolveTaskProject(args.project, { projects: await listProjects(), cwd: callerCwd || process.cwd() });
139
+ const requestId = `atask_${randomUUID()}`;
140
+ const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
141
+ const taskOrigin = {
142
+ agentId: callerAgentId,
143
+ sessionId: callerSessionId,
144
+ requestId,
145
+ title,
146
+ notify: args.notify !== false,
147
+ };
148
+ const route = clean(args.model) ? await modelRoute(clean(args.model)) : {};
149
+ const sessions = service();
150
+ const created = await sessions.createSession({
151
+ ...route,
152
+ cwd: where.cwd,
153
+ desktopSession: where.desktopSession,
154
+ sessionProfile: { taskOrigin },
155
+ });
156
+ const sessionId = String(created?.sessionId || '');
157
+ if (!sessionId) throw new Error('session: task session creation returned no session id');
158
+ // The user's New task default: a Project task starts in its own worktree.
159
+ if (where.project && (await newTaskDefault()) === 'worktree') {
160
+ try {
161
+ await sessions.configureSession(
162
+ {
163
+ sessionId,
164
+ action: 'requestWorktree',
165
+ args: [{ provision: true, carryChanges: 'leave', prompt: title, txId: requestId }],
166
+ },
167
+ { revision: 1 }
168
+ );
169
+ } catch (error) {
170
+ if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
171
+ }
172
+ }
173
+ // "Started" lands in the home before any report of the brief's turn.
174
+ let started = () => {};
175
+ const ready = new Promise((resolveReady) => {
176
+ started = resolveReady;
177
+ });
178
+ const accepted = await agentTurn(sessionId, brief, taskOrigin, requestId, ready);
179
+ if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
180
+ if (taskOrigin.notify) {
181
+ await deliver(callerAgentId, {
182
+ runId: `${requestId}_started`,
183
+ taskSessionId: sessionId,
184
+ title,
185
+ status: 'started',
186
+ }).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
187
+ }
188
+ started();
189
+ return { session_id: sessionId, title, project: where.project?.path ?? null };
190
+ }
191
+
192
+ /** The origin of a task session, when `agentId` created it. */
193
+ async function originFor(sessionId, agentId) {
194
+ if (!agentId) return null;
195
+ const origin = (await loadSession(sessionId))?.taskOrigin;
196
+ return origin?.agentId === agentId ? origin : null;
197
+ }
198
+
199
+ return { create, originFor, send: (sessionId, prompt, origin) => agentTurn(sessionId, prompt, origin) };
200
+ }
@@ -54,6 +54,7 @@ import { createResourceApi } from '../resource-api.mjs';
54
54
  import { createModelRouteApi } from '../model-route-api.mjs';
55
55
  import { createWorkflowAgentsApi } from '../workflow-agents-api.mjs';
56
56
  import { createToolPolicyRefresh } from '../tool-policy-refresh.mjs';
57
+ import { subscribeAgentProfileChanges } from '../agent-profile-changes.mjs';
57
58
  import { loadInstructionFilesContext } from '../../runtime/agent/orchestrator/context/instruction-files.mjs';
58
59
  import { createSessionTurnApi } from '../session-turn-api.mjs';
59
60
  import { createGoalFacadeApi } from '../goal-facade-api.mjs';
@@ -110,10 +111,20 @@ export function wireApis(boot) {
110
111
  }
111
112
  boot.disposeGlobalExtensionSubscription = () => {
112
113
  boot.disposeConfigSync?.();
114
+ boot.disposeAgentProfileChanges();
113
115
  boot.resourceApi.disposeGlobalExtensionSubscription?.();
114
116
  };
115
117
  boot.modelRouteApi = modelRouteApiFor(boot);
116
118
  boot.workflowAgentsApi = workflowAgentsApiFor(boot);
119
+ // Connector allow lists (agent lists, MCP project scopes) become the
120
+ // equivalent "switched off" lists once; a later connector is on everywhere.
121
+ if (!boot.params.initialConfig || typeof boot.params.initialConfig !== 'object') {
122
+ try {
123
+ boot.workflowAgentsApi.migrateConnectorPolicies();
124
+ } catch (error) {
125
+ bootProfile('connector-policy:migrate-failed', { error: error?.message || String(error) });
126
+ }
127
+ }
117
128
  boot.workspaceApi = workspaceApiFor(boot);
118
129
  boot.sessionTurnApi = sessionTurnApiFor(boot);
119
130
  const getFacadeSessionId = () => boot.rt.session?.id || boot.rt.reservedSessionId || null;
@@ -132,24 +143,36 @@ export function wireApis(boot) {
132
143
 
133
144
  function wireToolPolicyRefresh(boot) {
134
145
  const { rt, cfgMod } = boot;
135
- const { refreshEmptySessionToolPolicy } = createToolPolicyRefresh({
136
- getSession: () => rt.session,
137
- getRoute: () => rt.route,
138
- getMode: () => rt.mode,
139
- getConfig: () => rt.config,
140
- getDataDir: () => dataDirOf(cfgMod),
141
- getActiveTurnCount: () => rt.activeTurnCount || 0,
142
- modelStandaloneTools: boot.modelStandaloneTools,
143
- featureDisallowedTools: boot.featureDisallowedTools,
144
- loadCoreMemoryContext: boot.loadCoreMemoryContext,
145
- loadInstructionFilesContext: (session) =>
146
- session.skipInstructionFiles
147
- ? ''
148
- : loadInstructionFilesContext({ mode: rt.config?.instructionFiles, cwd: session.cwd }),
149
- activeWorkflowContext: workflowHelpers.activeWorkflowContext,
150
- invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
151
- });
146
+ const { refreshEmptySessionToolPolicy, switchSessionWorkflow, refreshAgentProfile, applyPendingAgentProfile } =
147
+ createToolPolicyRefresh({
148
+ getSession: () => rt.session,
149
+ getRoute: () => rt.route,
150
+ getMode: () => rt.mode,
151
+ getConfig: () => rt.config,
152
+ getDataDir: () => dataDirOf(cfgMod),
153
+ getActiveTurnCount: () => rt.activeTurnCount || 0,
154
+ modelStandaloneTools: boot.modelStandaloneTools,
155
+ featureDisallowedTools: boot.featureDisallowedTools,
156
+ loadCoreMemoryContext: boot.loadCoreMemoryContext,
157
+ loadInstructionFilesContext: (session) =>
158
+ session.skipInstructionFiles
159
+ ? ''
160
+ : loadInstructionFilesContext({ mode: rt.config?.instructionFiles, cwd: session.cwd }),
161
+ activeWorkflowContext: workflowHelpers.activeWorkflowContext,
162
+ invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
163
+ });
152
164
  boot.refreshEmptySessionToolPolicy = refreshEmptySessionToolPolicy;
165
+ boot.switchSessionWorkflow = switchSessionWorkflow;
166
+ boot.refreshAgentProfile = refreshAgentProfile;
167
+ boot.applyPendingAgentProfile = applyPendingAgentProfile;
168
+ // A saved bot reaches this runtime's thread when its prompt embeds it (it
169
+ // runs as that bot, or lists it); the rebuilt prompt is saved with the session.
170
+ const profileChanges = subscribeAgentProfileChanges(async () => {
171
+ if (!(await refreshAgentProfile())) return;
172
+ saveSession(rt.session);
173
+ boot.invalidateContextStatusCache();
174
+ });
175
+ boot.disposeAgentProfileChanges = profileChanges.unsubscribe;
153
176
  }
154
177
 
155
178
  // Built-in install adapters. Memory downloads and loads the embedding model at
@@ -425,6 +448,7 @@ function lifecycleApiFor(boot) {
425
448
  invalidateContextStatusCache: boot.invalidateContextStatusCache,
426
449
  invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
427
450
  applyResolvedCwd: boot.applyResolvedCwd,
451
+ refreshAgentProfile: boot.refreshAgentProfile,
428
452
  // Created after the lifecycle API, so resolved at call time.
429
453
  cleanupDeletedSessionWorktree: (sessionId) => boot.workspaceApi?.cleanupDeletedSession?.(sessionId),
430
454
  resolveRoute,
@@ -458,7 +482,9 @@ function resourceApiFor(boot) {
458
482
  connectConfiguredMcp: boot.connectConfiguredMcp,
459
483
  cancelMcpLogin: boot.cancelMcpLogin,
460
484
  completeMcpLogin: boot.completeMcpLogin,
461
- clearMcpServerOAuth: (cfg) => boot.mcpClient.clearMcpServerOAuth(cfg),
485
+ clearMcpServerOAuth: (cfg, options) => boot.mcpClient.clearMcpServerOAuth(cfg, options),
486
+ startMcpServerOAuthSwitch: (cfg, launch, name) => boot.mcpClient.startMcpServerOAuthSwitch(cfg, launch, name),
487
+ commitMcpServerOAuthSwitch: (cfg, signIn) => boot.mcpClient.commitMcpServerOAuthSwitch(cfg, signIn),
462
488
  getMcpServerOAuthState: (cfg) => boot.mcpClient.getMcpServerOAuthState(cfg),
463
489
  probeMcpServerAuth: (cfg) => boot.mcpClient.probeMcpServerAuth(cfg),
464
490
  invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
@@ -473,6 +499,7 @@ function resourceApiFor(boot) {
473
499
  skillContent: boot.skillContent,
474
500
  addGlobalSkill: boot.addGlobalSkill,
475
501
  saveSkillDocument: boot.saveSkillDocument,
502
+ removeGlobalSkill: boot.removeGlobalSkill,
476
503
  invalidateSkills: boot.invalidateSkills,
477
504
  getDisabledSkills: () => settingsApi.getDisabledSkills(),
478
505
  setDisabledSkills: (names) => settingsApi.setDisabledSkills(names),
@@ -542,6 +569,9 @@ function workflowAgentsApiFor(boot) {
542
569
  saveConfigAndAdopt: boot.saveConfigAndAdopt,
543
570
  ensureProvidersReady: boot.ensureProvidersReady,
544
571
  displayConfig: boot.displayConfig,
572
+ mcpStatus: () => boot.mcpStatus(),
573
+ resolveConfiguredMcpCatalog: () => boot.resolveConfiguredMcpCatalog(),
574
+ getMcpTools: () => boot.mcpClient.getMcpTools(rt.mcpScopeId),
545
575
  agentRouteFromConfig,
546
576
  loadAgentDefinition: workflowHelpers.loadAgentDefinition,
547
577
  listCustomAgentIds: workflowHelpers.listCustomAgentIds,
@@ -555,6 +585,7 @@ function workflowAgentsApiFor(boot) {
555
585
  invalidateContextStatusCache: boot.invalidateContextStatusCache,
556
586
  invalidatePreSessionToolSurface: boot.invalidatePreSessionToolSurface,
557
587
  refreshEmptySessionToolPolicy: boot.refreshEmptySessionToolPolicy,
588
+ switchSessionWorkflow: boot.switchSessionWorkflow,
558
589
  });
559
590
  }
560
591
 
@@ -596,6 +627,7 @@ function sessionTurnApiFor(boot) {
596
627
  const { rt, tunables, remoteTranscript, routePreparation } = boot;
597
628
  return createSessionTurnApi({
598
629
  applyPendingRoute: () => boot.modelRouteApi.applyPendingRoute(),
630
+ applyPendingAgentProfile: boot.applyPendingAgentProfile,
599
631
  prepareTurnWorkspace: boot.workspaceApi.prepareTurnWorkspace,
600
632
  admitTurnWorkspace: boot.workspaceApi.admitTurnWorkspace,
601
633
  getSession: () => rt.session,
@@ -85,17 +85,14 @@ function reserveSessionId(boot, id) {
85
85
  }
86
86
 
87
87
  // Every session's 2 s status pulse lands here; activeWorkflowSummary reads
88
- // through the process-wide shared caches, so no per-session file I/O.
88
+ // through the process-wide shared caches, so no per-session file I/O. A
89
+ // session presents its own workflow (set at creation, or switched in it);
90
+ // the configured one is the default for new sessions.
89
91
  function currentWorkflow(boot) {
90
92
  const { rt, cfgMod } = boot;
91
93
  const active = workflowHelpers.activeWorkflowSummary(rt.config, dataDirOf(cfgMod));
92
- if (rt.session?.workflow && typeof rt.session.workflow === 'object') {
93
- const current = rt.session.workflow;
94
- return current?.id && active?.id && current.id !== active.id
95
- ? { ...active, currentSession: current, appliedToCurrentSession: false }
96
- : active;
97
- }
98
- return active;
94
+ const current = rt.session?.workflow;
95
+ return current?.id && active?.id && current.id !== active.id ? current : active;
99
96
  }
100
97
 
101
98
  function facadeGetters(boot) {
@@ -14,6 +14,7 @@ import { createSelfUpdateController } from '../self-update.mjs';
14
14
  import { createCompletionWakeScheduler, createNotificationBus } from '../notification-bus.mjs';
15
15
  import { createSkillsApi } from '../skills-api.mjs';
16
16
  import { createCwdPlugins } from '../cwd-plugins.mjs';
17
+ import { injectedMemoryAgentId } from '../services/memory-access.mjs';
17
18
  import { listProjectsStrict } from '../../runtime/shared/projects.mjs';
18
19
  import { isWithin } from '../../runtime/shared/worktrees/paths.mjs';
19
20
  import { STANDALONE_DATA_DIR } from '../runtime-paths.mjs';
@@ -212,29 +213,37 @@ export function sessionAdopter(boot) {
212
213
  // the mutable cwd, the context module and the live tool surface.
213
214
  function wireSkills(boot) {
214
215
  const { rt, contextMod } = boot;
215
- const { skillsStatus, skillContent, skillToolContent, addGlobalSkill, saveSkillDocument, invalidateSkills } =
216
- createSkillsApi({
217
- contextMod,
218
- getCwd: () => rt.currentCwd,
219
- getTools: () => {
220
- const surface = boot.activeToolSurface();
221
- return [
222
- ...new Map(
223
- [
224
- ...(surface?.tools || []),
225
- ...(surface?.deferredToolCatalog || []),
226
- ...(surface?.deferredLateToolCatalog || []),
227
- ].map((tool) => [tool.name, tool])
228
- ).values(),
229
- ];
230
- },
231
- });
216
+ const {
217
+ skillsStatus,
218
+ skillContent,
219
+ skillToolContent,
220
+ addGlobalSkill,
221
+ saveSkillDocument,
222
+ removeGlobalSkill,
223
+ invalidateSkills,
224
+ } = createSkillsApi({
225
+ contextMod,
226
+ getCwd: () => rt.currentCwd,
227
+ getTools: () => {
228
+ const surface = boot.activeToolSurface();
229
+ return [
230
+ ...new Map(
231
+ [
232
+ ...(surface?.tools || []),
233
+ ...(surface?.deferredToolCatalog || []),
234
+ ...(surface?.deferredLateToolCatalog || []),
235
+ ].map((tool) => [tool.name, tool])
236
+ ).values(),
237
+ ];
238
+ },
239
+ });
232
240
  Object.assign(boot, {
233
241
  skillsStatus,
234
242
  skillContent,
235
243
  skillToolContent,
236
244
  addGlobalSkill,
237
245
  saveSkillDocument,
246
+ removeGlobalSkill,
238
247
  invalidateSkills,
239
248
  });
240
249
  }
@@ -247,6 +256,8 @@ function wireCwdPlugins(boot) {
247
256
  const { rt, cfgMod, hooks, hookCommonPayload, tunables } = boot;
248
257
  const { resolveCwdPath, applyResolvedCwd, pluginsStatus, loadCoreMemoryContext } = createCwdPlugins({
249
258
  getCurrentCwd: () => rt.currentCwd,
259
+ getMemoryAgentId: () =>
260
+ injectedMemoryAgentId({ session: rt.session, sessionId: rt.reservedSessionId, profile: rt.sessionProfile }),
250
261
  setCurrentCwd: (next) => {
251
262
  rt.currentCwd = next;
252
263
  },
@@ -225,6 +225,8 @@ function wireInternalTools(boot) {
225
225
  getConfig: () => rt.config,
226
226
  notifySessionUi,
227
227
  getSessionId: () => rt.session?.id || rt.reservedSessionId || null,
228
+ getCallerSession: (sessionId) =>
229
+ sessionId === rt.session?.id ? rt.session : boot.mgr?.getSession?.(sessionId) || null,
228
230
  flushSettings: () => boot.flushAllConfigSavesAsync({ requireSaved: true }),
229
231
  });
230
232
  const disposeInternalTools = internalTools.setInternalToolsProvider({
@@ -250,6 +252,7 @@ function wireInternalTools(boot) {
250
252
  runNativeWebSearch: (...a) => boot.runNativeWebSearch(...a),
251
253
  activeToolSurface: boot.activeToolSurface,
252
254
  mcpStatus: boot.mcpStatus,
255
+ awaitMcpConnect: () => boot.awaitInitialMcpConnect?.(),
253
256
  skillToolContent: boot.skillToolContent,
254
257
  // Late-bound: the workspace authority is built with the API slices.
255
258
  getWorkspaceApi: () => boot.workspaceApi || null,
@@ -10,8 +10,18 @@ import {
10
10
  setWebhookConfigAsync,
11
11
  testWebhook,
12
12
  } from './services/channel-admin.mjs';
13
- import { getSchedule } from '../runtime/channels/lib/schedules-db.mjs';
13
+ import { getSchedule, markScheduleFailure, markScheduleSuccess } from '../runtime/channels/lib/schedules-db.mjs';
14
+ import { loadEndpointConfig, readEndpointLastPayload } from '../runtime/channels/lib/webhooks-db.mjs';
15
+ import { webhookEventSummary, webhookRunPrompt } from '../runtime/channels/lib/webhook/session-dispatch.mjs';
14
16
  import { runScheduleSession } from './schedule-session-run.mjs';
17
+ import { runWebhookSession } from './webhook-session-run.mjs';
18
+ import { retryRoutineDelivery } from './owned-automation-run.mjs';
19
+
20
+ /** Why a "Run now" did not run: its owner bot is missing, or a run is going. */
21
+ function skippedRunMessage(name, outcome) {
22
+ if (outcome.reason === 'owner') return `"${name}" is paused: ${outcome.message}`;
23
+ return `"${name}" is still running; this run was skipped`;
24
+ }
15
25
 
16
26
  // Webhook/schedule config surface. The mutating admin helpers are imported
17
27
  // directly here and the runtime injects only the closure-owned callbacks
@@ -78,10 +88,48 @@ export function createChannelConfigApi({ channels, reloadChannelsSoon, ensureAut
78
88
  const id = String(name || '').trim();
79
89
  const schedule = await getSchedule(id);
80
90
  if (!schedule) throw new Error(`schedule "${id}" does not exist`);
81
- // Runs in the engine process as a VISIBLE schedule session, so it works
82
- // even while the channels worker is off and the run lands in Recent.
83
- const { sessionId, result } = await runScheduleSession(schedule);
84
- return { name: schedule.name, ok: true, sessionId, result };
91
+ // Runs in the engine process as a turn in the owner bot's chat, so it
92
+ // works even while the channels worker is off.
93
+ let outcome;
94
+ try {
95
+ // Asked for by a person: no failure backoff.
96
+ outcome = await runScheduleSession(schedule, { attended: true });
97
+ } catch (error) {
98
+ await markScheduleFailure(schedule.name, new Date()).catch(() => {});
99
+ throw error;
100
+ }
101
+ if (outcome.skipped) throw new Error(skippedRunMessage(schedule.name, outcome));
102
+ // The run's outcome shows as the schedule's last run, like a timed fire.
103
+ await markScheduleSuccess(schedule.name, new Date()).catch(() => {});
104
+ return { name: schedule.name, ok: true, sessionId: outcome.sessionId, result: outcome.result };
105
+ },
106
+ // Re-runs the endpoint on its latest received payload, with its current
107
+ // instructions, behind the same untrusted-data fence as a live delivery.
108
+ async runWebhookNow(name) {
109
+ const id = String(name || '').trim();
110
+ const endpoint = await loadEndpointConfig(id);
111
+ if (!endpoint) throw new Error(`webhook "${id}" does not exist`);
112
+ const payload = await readEndpointLastPayload(id);
113
+ if (!payload) throw new Error(`webhook "${id}" has not received a payload yet`);
114
+ const run = webhookRunPrompt(endpoint.instructions, payload.body ?? {}, payload.headers ?? {});
115
+ const outcome = await runWebhookSession(
116
+ {
117
+ name: endpoint.name,
118
+ ownerAgentId: endpoint.ownerAgentId,
119
+ cwd: endpoint.cwd || null,
120
+ attachments: endpoint.attachments || null,
121
+ event: webhookEventSummary(payload.headers ?? {}, payload.body ?? {}),
122
+ prompt: run.prompt,
123
+ skillSelection: run.skillSelection,
124
+ },
125
+ { attended: true }
126
+ );
127
+ if (outcome.skipped) throw new Error(skippedRunMessage(endpoint.name, outcome));
128
+ return { name: endpoint.name, ok: true, sessionId: outcome.sessionId, result: outcome.result };
129
+ },
130
+ // Queues again the result a finished run could not deliver; never reruns it.
131
+ async retryRoutineDelivery(kind, name) {
132
+ return retryRoutineDelivery(kind, String(name || '').trim());
85
133
  },
86
134
  };
87
135
  }
@@ -1,6 +1,10 @@
1
1
  // cwd-plugins/core-memory-context.mjs — the user-curated core-memory block
2
- // injected into new sessions, read from the memory runtime's atomic snapshot.
2
+ // injected into new sessions, read from the memory runtime's atomic snapshot:
3
+ // common, then the project's, then the session's own agent memory
4
+ // (services/memory-access.mjs injectedMemoryAgentId). The same snapshot gives
5
+ // the same bytes, so an unchanged pool never invalidates the prompt cache.
3
6
  import { featureEnvOverride } from '../../runtime/agent/orchestrator/runtime-core/config-helpers.mjs';
7
+ import { clean as cleanText } from '../../runtime/shared/clean.mjs';
4
8
  import { readSessionCoreMemoryPayload } from '../../runtime/memory/lib/core-memory-file.mjs';
5
9
 
6
10
  // Budget for the whole injected block in UTF-8 bytes: bytes track token cost
@@ -8,25 +12,37 @@ import { readSessionCoreMemoryPayload } from '../../runtime/memory/lib/core-memo
8
12
  const CORE_MEMORY_MAX_BYTES = 32 * 1024;
9
13
  const TRUNCATED_MARK = '\n- ...';
10
14
 
11
- export function createCoreMemoryContext({ getCurrentCwd, bootProfile, clean, cfgMod, STANDALONE_DATA_DIR }) {
12
- function formatCoreMemoryLines(payload = {}) {
13
- const seen = new Set();
14
- const lines = [];
15
- for (const value of Array.isArray(payload.userLines) ? payload.userLines : []) {
16
- const text = clean(value).replace(/\s+/g, ' ');
17
- if (!text) continue;
18
- const key = text.toLowerCase();
19
- if (seen.has(key)) continue;
20
- seen.add(key);
21
- lines.push(`- ${text}`);
22
- }
23
- const out = lines.join('\n');
24
- if (Buffer.byteLength(out, 'utf8') <= CORE_MEMORY_MAX_BYTES) return out;
25
- const budget = CORE_MEMORY_MAX_BYTES - Buffer.byteLength(TRUNCATED_MARK, 'utf8');
26
- // A byte cut can split a multi-byte character; drop its replacement chars.
27
- const head = Buffer.from(out, 'utf8').subarray(0, budget).toString('utf8').replace(/\uFFFD+$/, '');
28
- return `${head.replace(/\s+\S*$/, '')}${TRUNCATED_MARK}`;
15
+ function formatCoreMemoryBlock(payload = {}, clean = cleanText) {
16
+ const seen = new Set();
17
+ const lines = [];
18
+ for (const value of Array.isArray(payload.userLines) ? payload.userLines : []) {
19
+ const text = clean(value).replace(/\s+/g, ' ');
20
+ if (!text) continue;
21
+ const key = text.toLowerCase();
22
+ if (seen.has(key)) continue;
23
+ seen.add(key);
24
+ lines.push(`- ${text}`);
29
25
  }
26
+ const out = lines.join('\n');
27
+ if (Buffer.byteLength(out, 'utf8') <= CORE_MEMORY_MAX_BYTES) return out;
28
+ const budget = CORE_MEMORY_MAX_BYTES - Buffer.byteLength(TRUNCATED_MARK, 'utf8');
29
+ // A byte cut can split a multi-byte character; drop its replacement chars.
30
+ const head = Buffer.from(out, 'utf8')
31
+ .subarray(0, budget)
32
+ .toString('utf8')
33
+ .replace(/\uFFFD+$/, '');
34
+ return `${head.replace(/\s+\S*$/, '')}${TRUNCATED_MARK}`;
35
+ }
36
+
37
+ export function createCoreMemoryContext({
38
+ getCurrentCwd,
39
+ getMemoryAgentId = () => '',
40
+ bootProfile,
41
+ clean,
42
+ cfgMod,
43
+ STANDALONE_DATA_DIR,
44
+ }) {
45
+ const formatCoreMemoryLines = (payload) => formatCoreMemoryBlock(payload, clean);
30
46
 
31
47
  async function loadCoreMemoryContext() {
32
48
  // User-curated core memory always injects into new sessions: the Memory
@@ -42,7 +58,7 @@ export function createCoreMemoryContext({ getCurrentCwd, bootProfile, clean, cfg
42
58
  // The prompt path never starts or waits for PG/embedding/IPC. Memory
43
59
  // runtime maintains this atomic snapshot independently.
44
60
  const dataDir = process.env.MIXDOG_DATA_DIR || cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
45
- const payload = readSessionCoreMemoryPayload(dataDir, getCurrentCwd());
61
+ const payload = readSessionCoreMemoryPayload(dataDir, getCurrentCwd(), getMemoryAgentId());
46
62
  if (!payload) {
47
63
  bootProfile('core-memory:file-missing');
48
64
  return '';
@@ -68,6 +68,10 @@ export function createPluginStatus({
68
68
  const name = clean(manifest.name) || clean(manifest.id) || clean(entry.name) || root.split(/[\\/]/).pop() || root;
69
69
  const { mcpScript, mcpInline } = discoverPluginMcp(root);
70
70
  const skillCount = countSkillFiles(root);
71
+ // The hook files the hook bus loads for a plugin (hook-bus/config.mjs).
72
+ const hooks = [resolve(root, 'hooks', 'hooks.json'), resolve(root, '.mixdog', 'hooks.json')].some((file) =>
73
+ existsSync(file)
74
+ );
71
75
  const plugin = {
72
76
  id: clean(entry.id) || name,
73
77
  name,
@@ -85,11 +89,12 @@ export function createPluginStatus({
85
89
  installedAt: entry.installedAt || null,
86
90
  updatedAt: entry.updatedAt || null,
87
91
  skillCount,
92
+ hooks,
88
93
  mcpScript,
89
94
  mcpInline,
90
95
  };
91
96
  if (invalid) plugin.invalid = true;
92
- if (plugin.enabled && skillCount === 0 && !mcpScript && !mcpInline) {
97
+ if (plugin.enabled && skillCount === 0 && !mcpScript && !mcpInline && !hooks) {
93
98
  plugin.searchedSkillRoots = pluginSkillsRoots(root);
94
99
  plugin.warning = `No skills or MCP server found. Searched skill roots: ${plugin.searchedSkillRoots.join(', ')}`;
95
100
  }