mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, existsSync, readdirSync } from 'fs';
2
2
  import { homedir } from 'os';
3
- import { join } from 'path';
3
+ import { dirname, join } from 'path';
4
4
  import { maxMtimeRecursive } from '../cache-mtime.mjs';
5
5
  import { resolvePluginData, mixdogRoot } from '../../../shared/plugin-paths.mjs';
6
6
  import {
@@ -33,6 +33,47 @@ function mixdogAssetDirs(projectDir, kind) {
33
33
  dirs.push(mixdogGlobalDir(kind));
34
34
  return dirs;
35
35
  }
36
+ /**
37
+ * Absolute path to the plugin registry file, or null when the data dir is
38
+ * unresolvable. Included in the skills mtime gate so plugin add/remove
39
+ * (which rewrites registry.json) invalidates the cached skill list even
40
+ * when no surviving skills dir got a newer mtime.
41
+ */
42
+ function pluginRegistryPath() {
43
+ try {
44
+ return join(resolvePluginData(), 'plugins', 'registry.json');
45
+ } catch {
46
+ return null;
47
+ }
48
+ }
49
+ /**
50
+ * Read `<resolvePluginData()>/plugins/registry.json` (safe JSON parse, ignore
51
+ * errors) and yield `<root>/skills` for each registered plugin whose `root`
52
+ * exists on disk and has a `skills` subdirectory.
53
+ */
54
+ function pluginSkillDirs() {
55
+ const registryPath = pluginRegistryPath();
56
+ if (!registryPath)
57
+ return [];
58
+ let registry;
59
+ try {
60
+ registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
61
+ } catch {
62
+ return [];
63
+ }
64
+ if (!registry || !Array.isArray(registry.plugins))
65
+ return [];
66
+ const dirs = [];
67
+ for (const entry of registry.plugins) {
68
+ const root = entry && typeof entry.root === 'string' ? entry.root : null;
69
+ if (!root || !existsSync(root))
70
+ continue;
71
+ const skillsDir = join(root, 'skills');
72
+ if (existsSync(skillsDir))
73
+ dirs.push(skillsDir);
74
+ }
75
+ return dirs;
76
+ }
36
77
  /**
37
78
  * Collect available skills (frontmatter only — token efficient).
38
79
  * Full content loaded on demand via loadSkillContent().
@@ -49,6 +90,9 @@ export function collectSkills(cwd) {
49
90
  // first, then user-global. When cwd is missing, only the global dir is
50
91
  // searched.
51
92
  const dirs = mixdogAssetDirs(projectDir, 'skills');
93
+ // Plugin-provided skills load last so project-local and global mixdog
94
+ // skill dirs keep precedence; `seen` below dedupes by frontmatter name.
95
+ dirs.push(...pluginSkillDirs());
52
96
  const seen = new Set();
53
97
  for (const dir of dirs) {
54
98
  if (!existsSync(dir))
@@ -88,6 +132,13 @@ export function collectSkillsCached(cwd) {
88
132
  const projectDir = (typeof cwd === 'string' && cwd.length > 0) ? cwd : null;
89
133
  // Same mixdog-owned dirs collectSkills() reads, used as the freshness gate.
90
134
  const skillsDirs = mixdogAssetDirs(projectDir, 'skills');
135
+ skillsDirs.push(...pluginSkillDirs());
136
+ // registry.json itself gates plugin add/remove: removal deletes the
137
+ // plugin's skills dir (so no dir mtime advances), but saveRegistry()
138
+ // always rewrites this file. maxMtimeRecursive stats plain files directly.
139
+ const registryPath = pluginRegistryPath();
140
+ if (registryPath)
141
+ skillsDirs.push(registryPath);
91
142
  let mtime;
92
143
  const mtimeCached = _mtimeCache.get(key);
93
144
  if (mtimeCached && Date.now() - mtimeCached.checkedAt < _MTIME_TTL_MS) {
@@ -121,6 +172,70 @@ export function loadSkillContent(name, cwd) {
121
172
  return readSafe(skill.filePath);
122
173
  }
123
174
 
175
+ /**
176
+ * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR}).
177
+ */
178
+ export function loadSkillResource(name, cwd) {
179
+ const skills = collectSkillsCached(cwd);
180
+ const skill = skills.find(s => s.name === name);
181
+ if (!skill)
182
+ return null;
183
+ const content = readSafe(skill.filePath);
184
+ if (content == null)
185
+ return null;
186
+ return { content, dir: dirname(skill.filePath), filePath: skill.filePath };
187
+ }
188
+
189
+ function escapeSkillXmlText(value) {
190
+ return String(value || '').replace(/[<>&]/g, (ch) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[ch]));
191
+ }
192
+
193
+ /**
194
+ * Wrap loaded SKILL.md body for Skill tool results (runtime + loop).
195
+ */
196
+ export function buildSkillResultEnvelope(name, content, skillDir) {
197
+ const escapedName = escapeSkillXmlText(name);
198
+ let body = String(content == null ? '' : content);
199
+ let dirLine = '';
200
+ if (skillDir) {
201
+ const normDir = String(skillDir).replace(/\\/g, '/');
202
+ body = body.replace(/\$\{MIXDOG_SKILL_DIR\}/g, normDir);
203
+ const escapedDir = escapeSkillXmlText(normDir);
204
+ dirLine = `<base-dir>${escapedDir}</base-dir>\n`;
205
+ }
206
+ return `<skill>\n<name>${escapedName}</name>\n${dirLine}${body}\n</skill>`;
207
+ }
208
+
209
+ /**
210
+ * Short, model-visible tool_result stub for a loaded skill. The full SKILL.md
211
+ * body is delivered separately as ONE injected user message (newMessages),
212
+ * never in the tool_result — so the body appears exactly once.
213
+ */
214
+ export function buildSkillStub(name) {
215
+ return `Loaded skill: ${String(name || '').trim()}`;
216
+ }
217
+
218
+ /**
219
+ * Build the Skill tool-result envelope used by BOTH the agent-loop viewSkill
220
+ * path and the runtime skillToolContent path so behavior matches across main
221
+ * + agent sessions:
222
+ * - result = short stub (`Loaded skill: <name>`), no body.
223
+ * - newMessages = exactly ONE role:'user' message carrying the full
224
+ * buildSkillResultEnvelope output (<base-dir> + body).
225
+ * The injected user message is flagged `meta:'skill'` so compaction's
226
+ * "latest human prompt" selection does not mistake the skill body for the
227
+ * human's request.
228
+ */
229
+ export function buildSkillToolEnvelope(name, content, skillDir) {
230
+ return {
231
+ __toolEnvelope: true,
232
+ result: buildSkillStub(name),
233
+ newMessages: [
234
+ { role: 'user', content: buildSkillResultEnvelope(name, content, skillDir), meta: 'skill' },
235
+ ],
236
+ };
237
+ }
238
+
124
239
  function compactSkillManifestText(value, max = 180) {
125
240
  const text = String(value || '').replace(/\s+/g, ' ').trim();
126
241
  return text.length > max ? `${text.slice(0, Math.max(1, max - 3))}...` : text;
@@ -194,34 +309,34 @@ export function buildSkillToolDefs(skills, { ownerIsAgentSession = false } = {})
194
309
  ];
195
310
  return _skillToolDefsCache;
196
311
  }
197
- // --- Role-scoped instruction loader ---
198
- // Emits a BP2 role/system block scoped to the calling role:
199
- // - Public/custom agents: their own agents/<role>.md when present,
312
+ // --- Agent-scoped instruction loader ---
313
+ // Emits a BP2 agent/system block scoped to the calling agent:
314
+ // - Public/custom agents: their own agents/<agent>.md when present,
200
315
  // plus the public agent-worker contract.
201
- // - Hidden roles: their own rules/agent/<role>.md section only.
202
- // - Null role: falls back to the full all-in-one block
316
+ // - Hidden agents: their own rules/agent/<agent>.md section only.
317
+ // - Null agent: falls back to the full all-in-one block
203
318
  // (explicit-cache unified-shard path).
204
319
  //
205
- // Role-specific markdown intentionally rides BP2, behind the shared BP1 tool
206
- // and skill manifest prefix, so role changes do not disturb the common layer.
320
+ // Agent-specific markdown intentionally rides BP2, behind the shared BP1 tool
321
+ // and skill manifest prefix, so agent changes do not disturb the common layer.
207
322
  //
208
323
  // Classification is dynamic — hidden retrieval/maintenance sets come from the
209
- // `kind` field in internal-roles.mjs. Any other non-null role is public/custom.
324
+ // `kind` field in internal-agents.mjs. Any other non-null agent is public/custom.
210
325
  import {
211
- listHiddenRolesByKind,
212
- isHiddenRole,
213
- getRoleCatalogShareAgents,
214
- isInboundEventRole,
215
- } from '../internal-roles.mjs';
326
+ listHiddenAgentsByKind,
327
+ isHiddenAgent,
328
+ getAgentCatalogShareAgents,
329
+ isInboundEventAgent,
330
+ } from '../internal-agents.mjs';
216
331
 
217
- function loadRoleClassification() {
332
+ function loadAgentClassification() {
218
333
  // Not cached — called only on instruction rebuild (mtime-busted), and
219
- // listHiddenRolesByKind now reads from the mtime-aware cache inside
220
- // internal-roles.mjs so the classification always reflects the current
221
- // hidden-roles.json on disk.
334
+ // listHiddenAgentsByKind now reads from the mtime-aware cache inside
335
+ // internal-agents.mjs so the classification always reflects the current
336
+ // agents.json on disk.
222
337
  return {
223
- retrieval: new Set(listHiddenRolesByKind('retrieval')),
224
- maintenance: new Set(listHiddenRolesByKind('maintenance')),
338
+ retrieval: new Set(listHiddenAgentsByKind('retrieval')),
339
+ maintenance: new Set(listHiddenAgentsByKind('maintenance')),
225
340
  };
226
341
  }
227
342
 
@@ -235,7 +350,7 @@ const _scopedRoleInstructionsCache = new Map();
235
350
  const _scopedRoleInstructionsMtimeCache = new Map();
236
351
  const _ROLE_INSTRUCTIONS_MTIME_TTL_MS = 2000;
237
352
 
238
- function loadHiddenRoleSnippets(pluginRoot) {
353
+ function loadHiddenAgentSnippets(pluginRoot) {
239
354
  try {
240
355
  const agentRulesDir = join(pluginRoot, 'rules', 'agent');
241
356
  if (!existsSync(agentRulesDir)) return [];
@@ -262,11 +377,11 @@ function loadAgentSections(pluginRoot) {
262
377
  const agentSections = [];
263
378
  if (!existsSync(agentsDir)) return agentSections;
264
379
  // agents/ holds two layouts that must BOTH be loaded:
265
- // - flat: agents/<role>.md (legacy: scheduler-task, webhook-handler)
266
- // - nested: agents/<role>/AGENT.md (current: worker, heavy-worker, reviewer, …)
267
- // The previous flat-only readdir silently dropped every nested role, so a
268
- // public role like heavy-worker produced an EMPTY scoped instruction block
269
- // (BP2) — the model lost its role contract and the tool smoke's
380
+ // - flat: agents/<agent>.md (legacy: scheduler-task, webhook-handler)
381
+ // - nested: agents/<agent>/AGENT.md (current: worker, heavy-worker, reviewer, …)
382
+ // The previous flat-only readdir silently dropped every nested agent, so a
383
+ // public agent like heavy-worker produced an EMPTY scoped instruction block
384
+ // (BP2) — the model lost its agent contract and the tool smoke's
270
385
  // "heavy-worker AGENT.md must be included" assertion failed. Walk both.
271
386
  let entries;
272
387
  try {
@@ -295,22 +410,22 @@ function loadAgentSections(pluginRoot) {
295
410
  return agentSections;
296
411
  }
297
412
 
298
- // Empty by design: scoped role markdown already rides BP2 for every provider.
413
+ // Empty by design: scoped agent markdown already rides BP2 for every provider.
299
414
  // Keeping the set in place preserves the old branch point for a future
300
415
  // provider-specific experiment without changing today's cache layout.
301
416
  const EXPLICIT_CACHE_PROVIDERS = new Set();
302
417
 
303
- // Inbound-event maintenance roles that report results back to Lead are
304
- // declared via `inboundEvent: true` in defaults/hidden-roles.json and read
305
- // through isInboundEventRole(). Such roles must follow
418
+ // Inbound-event maintenance agents that report results back to Lead are
419
+ // declared via `inboundEvent: true` in defaults/agents.json and read
420
+ // through isInboundEventAgent(). Such agents must follow
306
421
  // rules/agent/20-skip-protocol.md so genuine no-ops (label-only events,
307
422
  // dedup, "nothing to report") prefix their output with `[meta:silent]` and the
308
- // dispatch layer suppresses the Lead inject. Other roles (cycle1/cycle2 memory
309
- // maintenance, retrieval roles) never emit toward Lead.
423
+ // dispatch layer suppresses the Lead inject. Other agents (cycle1/cycle2 memory
424
+ // maintenance, retrieval agents) never emit toward Lead.
310
425
 
311
- export function loadScopedRoleInstructions(role, provider = null) {
426
+ export function loadScopedRoleInstructions(agent, provider = null) {
312
427
  const useUnified = !!(provider && EXPLICIT_CACHE_PROVIDERS.has(provider));
313
- const cacheKey = useUnified ? '__unified__' : (role || '__all__');
428
+ const cacheKey = useUnified ? '__unified__' : (agent || '__all__');
314
429
  const cached = _scopedRoleInstructionsCache.get(cacheKey);
315
430
  const pluginRoot = mixdogRoot();
316
431
  // Use maxMtimeRecursive so edits to .md files inside agents/ and
@@ -327,7 +442,7 @@ export function loadScopedRoleInstructions(role, provider = null) {
327
442
  mtime = maxMtimeRecursive([
328
443
  join(pluginRoot, 'agents'),
329
444
  join(pluginRoot, 'rules', 'agent'),
330
- join(pluginRoot, 'defaults', 'hidden-roles.json'),
445
+ join(pluginRoot, 'defaults', 'agents.json'),
331
446
  ]);
332
447
  _scopedRoleInstructionsMtimeCache.set(cacheKey, { mtime, checkedAt: Date.now() });
333
448
  if (_scopedRoleInstructionsMtimeCache.size > 16) {
@@ -338,68 +453,68 @@ export function loadScopedRoleInstructions(role, provider = null) {
338
453
  if (cached && mtime <= cached.mtime) {
339
454
  return cached.value;
340
455
  }
341
- // Compute classification before file loading — internal-roles metadata
342
- // failures (malformed/missing hidden-roles.json) must propagate, not be
456
+ // Compute classification before file loading — internal-agents metadata
457
+ // failures (malformed/missing agents.json) must propagate, not be
343
458
  // silently swallowed by the file-IO catch below.
344
- const classification = loadRoleClassification();
345
- const roleSharesCatalog = role && classification.retrieval.has(role)
346
- ? new Set(getRoleCatalogShareAgents(role))
459
+ const classification = loadAgentClassification();
460
+ const agentSharesCatalog = agent && classification.retrieval.has(agent)
461
+ ? new Set(getAgentCatalogShareAgents(agent))
347
462
  : new Set();
348
- const roleIsInboundEvent = role && classification.maintenance.has(role)
349
- ? isInboundEventRole(role)
463
+ const agentIsInboundEvent = agent && classification.maintenance.has(agent)
464
+ ? isInboundEventAgent(agent)
350
465
  : false;
351
466
 
352
467
  try {
353
468
  const agentSections = loadAgentSections(pluginRoot);
354
- const hiddenPairs = loadHiddenRoleSnippets(pluginRoot);
469
+ const hiddenPairs = loadHiddenAgentSnippets(pluginRoot);
355
470
 
356
- // Pick which agent-rule sections + agents/<role>.md sections to emit
357
- // based on role classification. Self-only emit keeps BP2 minimal.
471
+ // Pick which agent-rule sections + agents/<agent>.md sections to emit
472
+ // based on agent classification. Self-only emit keeps BP2 minimal.
358
473
  let agentRuleSectionsToEmit = null; // null -> drop the agent-rule block entirely
359
- let agentSectionsToEmit = agentSections; // default: full (unknown-role fallback)
474
+ let agentSectionsToEmit = agentSections; // default: full (unknown-agent fallback)
360
475
  if (useUnified) {
361
- // Explicit-cache providers — every role sees the same all-in-one
362
- // instruction surface. Cross-role calls hit the same provider-side prefix
363
- // shard, eliminating the role-shard miss seen on Pool C
476
+ // Explicit-cache providers — every agent sees the same all-in-one
477
+ // instruction surface. Cross-agent calls hit the same provider-side prefix
478
+ // shard, eliminating the agent-shard miss seen on Pool C
364
479
  // transitions for openai-oauth/openai. This branch is disabled by the
365
- // empty provider set above; BP2 remains the active role surface.
480
+ // empty provider set above; BP2 remains the active agent surface.
366
481
  agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
367
482
  agentSectionsToEmit = agentSections;
368
- } else if (role && classification.retrieval.has(role)) {
369
- // Retrieval roles (explorer) get their own contract section
483
+ } else if (agent && classification.retrieval.has(agent)) {
484
+ // Retrieval agents (explorer) get their own contract section
370
485
  // (rules/agent/30-explorer.md) in BP2.
371
- const self = hiddenPairs.find(p => p.name === role);
486
+ const self = hiddenPairs.find(p => p.name === agent);
372
487
  agentRuleSectionsToEmit = self ? [`## ${self.name}\n\n${self.body}`] : [];
373
488
  agentSectionsToEmit = agentSections.filter(s =>
374
- [...roleSharesCatalog].some(name => s.startsWith(`## ${name}\n`)));
375
- } else if (role && classification.maintenance.has(role)) {
376
- const self = hiddenPairs.find(p => p.name === role);
489
+ [...agentSharesCatalog].some(name => s.startsWith(`## ${name}\n`)));
490
+ } else if (agent && classification.maintenance.has(agent)) {
491
+ const self = hiddenPairs.find(p => p.name === agent);
377
492
  agentRuleSectionsToEmit = [];
378
493
  if (self) {
379
494
  agentRuleSectionsToEmit.push(`## ${self.name}\n\n${self.body}`);
380
495
  } else {
381
- // Fallback: maintenance role without rules/agent/*.md entry
382
- // pull self body from agents/<role>.md instead so newly-added
383
- // hidden roles work without needing a duplicate agent rule file.
384
- const fromAgent = agentSections.find(s => s.startsWith(`## ${role}\n`));
496
+ // Self body from agents/<agent>.md for a maintenance agent
497
+ // without a rules/agent/*.md entry so newly-added hidden agents
498
+ // work without needing a duplicate agent rule file.
499
+ const fromAgent = agentSections.find(s => s.startsWith(`## ${agent}\n`));
385
500
  if (fromAgent) agentRuleSectionsToEmit.push(fromAgent);
386
501
  }
387
- // Inbound-event roles also need the skip-protocol rule so they
502
+ // Inbound-event agents also need the skip-protocol rule so they
388
503
  // can opt their no-op outputs out of the Lead inject (BP1 would
389
504
  // waste the bytes on unrelated agents).
390
- if (roleIsInboundEvent) {
505
+ if (agentIsInboundEvent) {
391
506
  const skip = hiddenPairs.find(p => p.name === 'skip-protocol');
392
507
  if (skip) agentRuleSectionsToEmit.push(`## ${skip.name}\n\n${skip.body}`);
393
508
  }
394
509
  agentSectionsToEmit = [];
395
- } else if (role) {
396
- // Public/custom role — self-only agents/<role>.md when present,
510
+ } else if (agent) {
511
+ // Public/custom agent — self-only agents/<agent>.md when present,
397
512
  // not the full hidden/maintenance bundle. The universal agent
398
513
  // contract rides BP2 (rules/agent/00-common.md).
399
514
  agentRuleSectionsToEmit = [];
400
- agentSectionsToEmit = agentSections.filter(s => s.startsWith(`## ${role}\n`));
515
+ agentSectionsToEmit = agentSections.filter(s => s.startsWith(`## ${agent}\n`));
401
516
  } else {
402
- // Null role — full instruction surface emitted (explicit-cache providers that
517
+ // Null agent — full instruction surface emitted (explicit-cache providers that
403
518
  // shard by __all__ key).
404
519
  agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
405
520
  // agentSectionsToEmit already set to full agentSections above.
@@ -448,7 +563,7 @@ export function composeSystemPrompt(opts) {
448
563
  // ── BP2: role/system layer ─────────────────────────────────────────
449
564
  const roleInstructionContext = opts.skipRoleCatalog
450
565
  ? ''
451
- : loadScopedRoleInstructions(opts.role || null, opts.provider || null);
566
+ : loadScopedRoleInstructions(opts.agent || null, opts.provider || null);
452
567
  const stableSystemParts = [];
453
568
  if (opts.roleRules) stableSystemParts.push(opts.roleRules);
454
569
  if (opts.userPrompt) stableSystemParts.push(opts.userPrompt);