mixdog 0.9.1 → 0.9.2

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 (214) hide show
  1. package/package.json +8 -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/background-task-meta-smoke.mjs +1 -1
  6. package/scripts/bench-run.mjs +262 -0
  7. package/scripts/compact-smoke.mjs +12 -0
  8. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  9. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  10. package/scripts/internal-comms-bench.mjs +727 -0
  11. package/scripts/internal-comms-smoke.mjs +75 -0
  12. package/scripts/lead-workflow-smoke.mjs +4 -4
  13. package/scripts/live-worker-smoke.mjs +9 -9
  14. package/scripts/output-style-bench.mjs +285 -0
  15. package/scripts/output-style-smoke.mjs +13 -10
  16. package/scripts/patch-replay.mjs +90 -0
  17. package/scripts/provider-stream-stall-test.mjs +276 -0
  18. package/scripts/provider-toolcall-test.mjs +599 -1
  19. package/scripts/routing-corpus.mjs +281 -0
  20. package/scripts/session-bench.mjs +1526 -0
  21. package/scripts/session-diag.mjs +595 -0
  22. package/scripts/task-bench.mjs +207 -0
  23. package/scripts/tool-failures.mjs +6 -6
  24. package/scripts/tool-smoke.mjs +306 -66
  25. package/scripts/toolcall-args-test.mjs +81 -0
  26. package/src/agents/debugger/AGENT.md +4 -4
  27. package/src/agents/heavy-worker/AGENT.md +4 -2
  28. package/src/agents/reviewer/AGENT.md +4 -4
  29. package/src/agents/worker/AGENT.md +4 -2
  30. package/src/app.mjs +10 -6
  31. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  32. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  33. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  34. package/src/headless-role.mjs +14 -14
  35. package/src/help.mjs +1 -0
  36. package/src/lib/rules-builder.cjs +32 -54
  37. package/src/mixdog-session-runtime.mjs +710 -318
  38. package/src/output-styles/default.md +12 -7
  39. package/src/output-styles/minimal.md +25 -0
  40. package/src/output-styles/oneline.md +21 -0
  41. package/src/output-styles/simple.md +10 -9
  42. package/src/repl.mjs +12 -2
  43. package/src/rules/agent/00-common.md +7 -5
  44. package/src/rules/agent/30-explorer.md +7 -8
  45. package/src/rules/lead/01-general.md +3 -1
  46. package/src/rules/lead/lead-tool.md +7 -0
  47. package/src/rules/shared/01-tool.md +17 -12
  48. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  49. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  50. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  51. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  52. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  53. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  54. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  55. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  56. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  57. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  58. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  59. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  62. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
  63. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
  64. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  65. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  66. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  67. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  68. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  69. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
  70. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
  71. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
  72. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  73. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  74. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  75. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
  76. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  77. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  78. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  80. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  81. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  82. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  83. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  84. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  85. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  86. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  87. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  88. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  90. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  91. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  92. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  93. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  94. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  95. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  96. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  97. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  99. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  100. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  101. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  102. package/src/runtime/channels/backends/discord.mjs +99 -9
  103. package/src/runtime/channels/backends/telegram.mjs +501 -0
  104. package/src/runtime/channels/index.mjs +441 -1224
  105. package/src/runtime/channels/lib/config.mjs +54 -2
  106. package/src/runtime/channels/lib/format.mjs +4 -2
  107. package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
  108. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  109. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  110. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  111. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  112. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  113. package/src/runtime/channels/lib/webhook.mjs +59 -31
  114. package/src/runtime/channels/tool-defs.mjs +1 -1
  115. package/src/runtime/memory/index.mjs +184 -19
  116. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  117. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  118. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  119. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  120. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  121. package/src/runtime/memory/lib/memory.mjs +101 -4
  122. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  123. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  124. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  125. package/src/runtime/memory/tool-defs.mjs +6 -3
  126. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  127. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  128. package/src/runtime/shared/config.mjs +9 -0
  129. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  130. package/src/runtime/shared/schedules-store.mjs +21 -19
  131. package/src/runtime/shared/tool-surface.mjs +98 -13
  132. package/src/runtime/shared/transcript-writer.mjs +129 -0
  133. package/src/runtime/shared/update-checker.mjs +214 -0
  134. package/src/standalone/agent-tool.mjs +255 -109
  135. package/src/standalone/channel-admin.mjs +133 -40
  136. package/src/standalone/channel-worker.mjs +8 -291
  137. package/src/standalone/explore-tool.mjs +2 -2
  138. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  139. package/src/standalone/provider-admin.mjs +11 -0
  140. package/src/standalone/usage-dashboard.mjs +1 -1
  141. package/src/tui/App.jsx +2096 -732
  142. package/src/tui/components/ConfirmBar.jsx +47 -0
  143. package/src/tui/components/ContextPanel.jsx +5 -3
  144. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  145. package/src/tui/components/Markdown.jsx +22 -98
  146. package/src/tui/components/Message.jsx +14 -35
  147. package/src/tui/components/Picker.jsx +87 -12
  148. package/src/tui/components/PromptInput.jsx +83 -7
  149. package/src/tui/components/QueuedCommands.jsx +1 -1
  150. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  151. package/src/tui/components/Spinner.jsx +7 -7
  152. package/src/tui/components/StatusLine.jsx +40 -21
  153. package/src/tui/components/TextEntryPanel.jsx +51 -7
  154. package/src/tui/components/ToolExecution.jsx +170 -98
  155. package/src/tui/components/TurnDone.jsx +4 -4
  156. package/src/tui/components/UsagePanel.jsx +1 -1
  157. package/src/tui/components/tool-output-format.mjs +159 -21
  158. package/src/tui/components/tool-output-format.test.mjs +87 -0
  159. package/src/tui/display-width.mjs +69 -0
  160. package/src/tui/display-width.test.mjs +35 -0
  161. package/src/tui/dist/index.mjs +6965 -2391
  162. package/src/tui/engine.mjs +287 -126
  163. package/src/tui/index.jsx +117 -7
  164. package/src/tui/keyboard-protocol.mjs +42 -0
  165. package/src/tui/lib/voice-recorder.mjs +453 -0
  166. package/src/tui/markdown/format-token.mjs +129 -76
  167. package/src/tui/markdown/format-token.test.mjs +61 -19
  168. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  169. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  170. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  171. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  172. package/src/tui/markdown/table-layout.mjs +9 -9
  173. package/src/tui/paste-attachments.mjs +0 -11
  174. package/src/tui/prompt-history-store.mjs +129 -0
  175. package/src/tui/prompt-history-store.test.mjs +52 -0
  176. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  177. package/src/tui/theme.mjs +41 -657
  178. package/src/tui/themes/base.mjs +86 -0
  179. package/src/tui/themes/basic.mjs +85 -0
  180. package/src/tui/themes/catppuccin.mjs +72 -0
  181. package/src/tui/themes/dracula.mjs +70 -0
  182. package/src/tui/themes/everforest.mjs +71 -0
  183. package/src/tui/themes/gruvbox.mjs +71 -0
  184. package/src/tui/themes/index.mjs +71 -0
  185. package/src/tui/themes/indigo.mjs +78 -0
  186. package/src/tui/themes/kanagawa.mjs +80 -0
  187. package/src/tui/themes/light.mjs +81 -0
  188. package/src/tui/themes/nord.mjs +72 -0
  189. package/src/tui/themes/onedark.mjs +16 -0
  190. package/src/tui/themes/rosepine.mjs +70 -0
  191. package/src/tui/themes/teal.mjs +81 -0
  192. package/src/tui/themes/tokyonight.mjs +79 -0
  193. package/src/tui/themes/utils.mjs +106 -0
  194. package/src/tui/themes/warm.mjs +79 -0
  195. package/src/tui/transcript-tool-failures.mjs +13 -2
  196. package/src/ui/markdown.mjs +1 -1
  197. package/src/ui/model-display.mjs +2 -2
  198. package/src/ui/statusline.mjs +26 -27
  199. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  200. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  201. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  202. package/src/workflows/default/WORKFLOW.md +39 -12
  203. package/src/workflows/sequential/WORKFLOW.md +46 -0
  204. package/src/workflows/solo/WORKFLOW.md +7 -0
  205. package/vendor/ink/build/display-width.js +62 -0
  206. package/vendor/ink/build/ink.js +100 -12
  207. package/vendor/ink/build/measure-text.js +4 -1
  208. package/vendor/ink/build/output.js +115 -9
  209. package/vendor/ink/build/render-node-to-output.js +4 -1
  210. package/vendor/ink/build/render.js +4 -0
  211. package/src/output-styles/extreme-simple.md +0 -20
  212. package/src/rules/lead/04-workflow.md +0 -51
  213. package/src/workflows/default/workflow.json +0 -13
  214. 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 {
@@ -121,6 +121,70 @@ export function loadSkillContent(name, cwd) {
121
121
  return readSafe(skill.filePath);
122
122
  }
123
123
 
124
+ /**
125
+ * Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR}).
126
+ */
127
+ export function loadSkillResource(name, cwd) {
128
+ const skills = collectSkillsCached(cwd);
129
+ const skill = skills.find(s => s.name === name);
130
+ if (!skill)
131
+ return null;
132
+ const content = readSafe(skill.filePath);
133
+ if (content == null)
134
+ return null;
135
+ return { content, dir: dirname(skill.filePath), filePath: skill.filePath };
136
+ }
137
+
138
+ function escapeSkillXmlText(value) {
139
+ return String(value || '').replace(/[<>&]/g, (ch) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[ch]));
140
+ }
141
+
142
+ /**
143
+ * Wrap loaded SKILL.md body for Skill tool results (runtime + loop).
144
+ */
145
+ export function buildSkillResultEnvelope(name, content, skillDir) {
146
+ const escapedName = escapeSkillXmlText(name);
147
+ let body = String(content == null ? '' : content);
148
+ let dirLine = '';
149
+ if (skillDir) {
150
+ const normDir = String(skillDir).replace(/\\/g, '/');
151
+ body = body.replace(/\$\{MIXDOG_SKILL_DIR\}/g, normDir);
152
+ const escapedDir = escapeSkillXmlText(normDir);
153
+ dirLine = `<base-dir>${escapedDir}</base-dir>\n`;
154
+ }
155
+ return `<skill>\n<name>${escapedName}</name>\n${dirLine}${body}\n</skill>`;
156
+ }
157
+
158
+ /**
159
+ * Short, model-visible tool_result stub for a loaded skill. The full SKILL.md
160
+ * body is delivered separately as ONE injected user message (newMessages),
161
+ * never in the tool_result — so the body appears exactly once.
162
+ */
163
+ export function buildSkillStub(name) {
164
+ return `Loaded skill: ${String(name || '').trim()}`;
165
+ }
166
+
167
+ /**
168
+ * Build the Skill tool-result envelope used by BOTH the agent-loop viewSkill
169
+ * path and the runtime skillToolContent path so behavior matches across main
170
+ * + agent sessions:
171
+ * - result = short stub (`Loaded skill: <name>`), no body.
172
+ * - newMessages = exactly ONE role:'user' message carrying the full
173
+ * buildSkillResultEnvelope output (<base-dir> + body).
174
+ * The injected user message is flagged `meta:'skill'` so compaction's
175
+ * "latest human prompt" selection does not mistake the skill body for the
176
+ * human's request.
177
+ */
178
+ export function buildSkillToolEnvelope(name, content, skillDir) {
179
+ return {
180
+ __toolEnvelope: true,
181
+ result: buildSkillStub(name),
182
+ newMessages: [
183
+ { role: 'user', content: buildSkillResultEnvelope(name, content, skillDir), meta: 'skill' },
184
+ ],
185
+ };
186
+ }
187
+
124
188
  function compactSkillManifestText(value, max = 180) {
125
189
  const text = String(value || '').replace(/\s+/g, ' ').trim();
126
190
  return text.length > max ? `${text.slice(0, Math.max(1, max - 3))}...` : text;
@@ -194,34 +258,34 @@ export function buildSkillToolDefs(skills, { ownerIsAgentSession = false } = {})
194
258
  ];
195
259
  return _skillToolDefsCache;
196
260
  }
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,
261
+ // --- Agent-scoped instruction loader ---
262
+ // Emits a BP2 agent/system block scoped to the calling agent:
263
+ // - Public/custom agents: their own agents/<agent>.md when present,
200
264
  // 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
265
+ // - Hidden agents: their own rules/agent/<agent>.md section only.
266
+ // - Null agent: falls back to the full all-in-one block
203
267
  // (explicit-cache unified-shard path).
204
268
  //
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.
269
+ // Agent-specific markdown intentionally rides BP2, behind the shared BP1 tool
270
+ // and skill manifest prefix, so agent changes do not disturb the common layer.
207
271
  //
208
272
  // 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.
273
+ // `kind` field in internal-agents.mjs. Any other non-null agent is public/custom.
210
274
  import {
211
- listHiddenRolesByKind,
212
- isHiddenRole,
213
- getRoleCatalogShareAgents,
214
- isInboundEventRole,
215
- } from '../internal-roles.mjs';
275
+ listHiddenAgentsByKind,
276
+ isHiddenAgent,
277
+ getAgentCatalogShareAgents,
278
+ isInboundEventAgent,
279
+ } from '../internal-agents.mjs';
216
280
 
217
- function loadRoleClassification() {
281
+ function loadAgentClassification() {
218
282
  // 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.
283
+ // listHiddenAgentsByKind now reads from the mtime-aware cache inside
284
+ // internal-agents.mjs so the classification always reflects the current
285
+ // agents.json on disk.
222
286
  return {
223
- retrieval: new Set(listHiddenRolesByKind('retrieval')),
224
- maintenance: new Set(listHiddenRolesByKind('maintenance')),
287
+ retrieval: new Set(listHiddenAgentsByKind('retrieval')),
288
+ maintenance: new Set(listHiddenAgentsByKind('maintenance')),
225
289
  };
226
290
  }
227
291
 
@@ -235,7 +299,7 @@ const _scopedRoleInstructionsCache = new Map();
235
299
  const _scopedRoleInstructionsMtimeCache = new Map();
236
300
  const _ROLE_INSTRUCTIONS_MTIME_TTL_MS = 2000;
237
301
 
238
- function loadHiddenRoleSnippets(pluginRoot) {
302
+ function loadHiddenAgentSnippets(pluginRoot) {
239
303
  try {
240
304
  const agentRulesDir = join(pluginRoot, 'rules', 'agent');
241
305
  if (!existsSync(agentRulesDir)) return [];
@@ -262,11 +326,11 @@ function loadAgentSections(pluginRoot) {
262
326
  const agentSections = [];
263
327
  if (!existsSync(agentsDir)) return agentSections;
264
328
  // 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
329
+ // - flat: agents/<agent>.md (legacy: scheduler-task, webhook-handler)
330
+ // - nested: agents/<agent>/AGENT.md (current: worker, heavy-worker, reviewer, …)
331
+ // The previous flat-only readdir silently dropped every nested agent, so a
332
+ // public agent like heavy-worker produced an EMPTY scoped instruction block
333
+ // (BP2) — the model lost its agent contract and the tool smoke's
270
334
  // "heavy-worker AGENT.md must be included" assertion failed. Walk both.
271
335
  let entries;
272
336
  try {
@@ -295,22 +359,22 @@ function loadAgentSections(pluginRoot) {
295
359
  return agentSections;
296
360
  }
297
361
 
298
- // Empty by design: scoped role markdown already rides BP2 for every provider.
362
+ // Empty by design: scoped agent markdown already rides BP2 for every provider.
299
363
  // Keeping the set in place preserves the old branch point for a future
300
364
  // provider-specific experiment without changing today's cache layout.
301
365
  const EXPLICIT_CACHE_PROVIDERS = new Set();
302
366
 
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
367
+ // Inbound-event maintenance agents that report results back to Lead are
368
+ // declared via `inboundEvent: true` in defaults/agents.json and read
369
+ // through isInboundEventAgent(). Such agents must follow
306
370
  // rules/agent/20-skip-protocol.md so genuine no-ops (label-only events,
307
371
  // 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.
372
+ // dispatch layer suppresses the Lead inject. Other agents (cycle1/cycle2 memory
373
+ // maintenance, retrieval agents) never emit toward Lead.
310
374
 
311
- export function loadScopedRoleInstructions(role, provider = null) {
375
+ export function loadScopedRoleInstructions(agent, provider = null) {
312
376
  const useUnified = !!(provider && EXPLICIT_CACHE_PROVIDERS.has(provider));
313
- const cacheKey = useUnified ? '__unified__' : (role || '__all__');
377
+ const cacheKey = useUnified ? '__unified__' : (agent || '__all__');
314
378
  const cached = _scopedRoleInstructionsCache.get(cacheKey);
315
379
  const pluginRoot = mixdogRoot();
316
380
  // Use maxMtimeRecursive so edits to .md files inside agents/ and
@@ -327,7 +391,7 @@ export function loadScopedRoleInstructions(role, provider = null) {
327
391
  mtime = maxMtimeRecursive([
328
392
  join(pluginRoot, 'agents'),
329
393
  join(pluginRoot, 'rules', 'agent'),
330
- join(pluginRoot, 'defaults', 'hidden-roles.json'),
394
+ join(pluginRoot, 'defaults', 'agents.json'),
331
395
  ]);
332
396
  _scopedRoleInstructionsMtimeCache.set(cacheKey, { mtime, checkedAt: Date.now() });
333
397
  if (_scopedRoleInstructionsMtimeCache.size > 16) {
@@ -338,68 +402,68 @@ export function loadScopedRoleInstructions(role, provider = null) {
338
402
  if (cached && mtime <= cached.mtime) {
339
403
  return cached.value;
340
404
  }
341
- // Compute classification before file loading — internal-roles metadata
342
- // failures (malformed/missing hidden-roles.json) must propagate, not be
405
+ // Compute classification before file loading — internal-agents metadata
406
+ // failures (malformed/missing agents.json) must propagate, not be
343
407
  // 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))
408
+ const classification = loadAgentClassification();
409
+ const agentSharesCatalog = agent && classification.retrieval.has(agent)
410
+ ? new Set(getAgentCatalogShareAgents(agent))
347
411
  : new Set();
348
- const roleIsInboundEvent = role && classification.maintenance.has(role)
349
- ? isInboundEventRole(role)
412
+ const agentIsInboundEvent = agent && classification.maintenance.has(agent)
413
+ ? isInboundEventAgent(agent)
350
414
  : false;
351
415
 
352
416
  try {
353
417
  const agentSections = loadAgentSections(pluginRoot);
354
- const hiddenPairs = loadHiddenRoleSnippets(pluginRoot);
418
+ const hiddenPairs = loadHiddenAgentSnippets(pluginRoot);
355
419
 
356
- // Pick which agent-rule sections + agents/<role>.md sections to emit
357
- // based on role classification. Self-only emit keeps BP2 minimal.
420
+ // Pick which agent-rule sections + agents/<agent>.md sections to emit
421
+ // based on agent classification. Self-only emit keeps BP2 minimal.
358
422
  let agentRuleSectionsToEmit = null; // null -> drop the agent-rule block entirely
359
- let agentSectionsToEmit = agentSections; // default: full (unknown-role fallback)
423
+ let agentSectionsToEmit = agentSections; // default: full (unknown-agent fallback)
360
424
  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
425
+ // Explicit-cache providers — every agent sees the same all-in-one
426
+ // instruction surface. Cross-agent calls hit the same provider-side prefix
427
+ // shard, eliminating the agent-shard miss seen on Pool C
364
428
  // transitions for openai-oauth/openai. This branch is disabled by the
365
- // empty provider set above; BP2 remains the active role surface.
429
+ // empty provider set above; BP2 remains the active agent surface.
366
430
  agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
367
431
  agentSectionsToEmit = agentSections;
368
- } else if (role && classification.retrieval.has(role)) {
369
- // Retrieval roles (explorer) get their own contract section
432
+ } else if (agent && classification.retrieval.has(agent)) {
433
+ // Retrieval agents (explorer) get their own contract section
370
434
  // (rules/agent/30-explorer.md) in BP2.
371
- const self = hiddenPairs.find(p => p.name === role);
435
+ const self = hiddenPairs.find(p => p.name === agent);
372
436
  agentRuleSectionsToEmit = self ? [`## ${self.name}\n\n${self.body}`] : [];
373
437
  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);
438
+ [...agentSharesCatalog].some(name => s.startsWith(`## ${name}\n`)));
439
+ } else if (agent && classification.maintenance.has(agent)) {
440
+ const self = hiddenPairs.find(p => p.name === agent);
377
441
  agentRuleSectionsToEmit = [];
378
442
  if (self) {
379
443
  agentRuleSectionsToEmit.push(`## ${self.name}\n\n${self.body}`);
380
444
  } 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`));
445
+ // Self body from agents/<agent>.md for a maintenance agent
446
+ // without a rules/agent/*.md entry so newly-added hidden agents
447
+ // work without needing a duplicate agent rule file.
448
+ const fromAgent = agentSections.find(s => s.startsWith(`## ${agent}\n`));
385
449
  if (fromAgent) agentRuleSectionsToEmit.push(fromAgent);
386
450
  }
387
- // Inbound-event roles also need the skip-protocol rule so they
451
+ // Inbound-event agents also need the skip-protocol rule so they
388
452
  // can opt their no-op outputs out of the Lead inject (BP1 would
389
453
  // waste the bytes on unrelated agents).
390
- if (roleIsInboundEvent) {
454
+ if (agentIsInboundEvent) {
391
455
  const skip = hiddenPairs.find(p => p.name === 'skip-protocol');
392
456
  if (skip) agentRuleSectionsToEmit.push(`## ${skip.name}\n\n${skip.body}`);
393
457
  }
394
458
  agentSectionsToEmit = [];
395
- } else if (role) {
396
- // Public/custom role — self-only agents/<role>.md when present,
459
+ } else if (agent) {
460
+ // Public/custom agent — self-only agents/<agent>.md when present,
397
461
  // not the full hidden/maintenance bundle. The universal agent
398
462
  // contract rides BP2 (rules/agent/00-common.md).
399
463
  agentRuleSectionsToEmit = [];
400
- agentSectionsToEmit = agentSections.filter(s => s.startsWith(`## ${role}\n`));
464
+ agentSectionsToEmit = agentSections.filter(s => s.startsWith(`## ${agent}\n`));
401
465
  } else {
402
- // Null role — full instruction surface emitted (explicit-cache providers that
466
+ // Null agent — full instruction surface emitted (explicit-cache providers that
403
467
  // shard by __all__ key).
404
468
  agentRuleSectionsToEmit = hiddenPairs.map(p => `## ${p.name}\n\n${p.body}`);
405
469
  // agentSectionsToEmit already set to full agentSections above.
@@ -448,7 +512,7 @@ export function composeSystemPrompt(opts) {
448
512
  // ── BP2: role/system layer ─────────────────────────────────────────
449
513
  const roleInstructionContext = opts.skipRoleCatalog
450
514
  ? ''
451
- : loadScopedRoleInstructions(opts.role || null, opts.provider || null);
515
+ : loadScopedRoleInstructions(opts.agent || null, opts.provider || null);
452
516
  const stableSystemParts = [];
453
517
  if (opts.roleRules) stableSystemParts.push(opts.roleRules);
454
518
  if (opts.userPrompt) stableSystemParts.push(opts.userPrompt);
@@ -1,50 +1,50 @@
1
1
  /**
2
- * Internal hidden roles — Mixdog-managed, user-untouchable.
2
+ * Internal hidden agents — Mixdog-managed, user-untouchable.
3
3
  *
4
- * Unlike public workflow agents, these hidden roles are NEVER exposed to callers of the `agent` tool. They are
4
+ * Unlike public workflow agents, these hidden agents are NEVER exposed to callers of the `agent` tool. They are
5
5
  * invoked only by internal handlers (explore / recall / search) and carry
6
6
  * their own system prompt + tool-set policy.
7
7
  *
8
8
  * Lookup order (agent-dispatch.resolvePresetName):
9
9
  * 1. explicit preset arg
10
10
  * 2. opts.preset
11
- * 3. hidden-role registry (defaults/hidden-roles.json + systemFile frontmatter)
11
+ * 3. hidden-agent registry (defaults/agents.json + systemFile frontmatter)
12
12
  *
13
- * Role definitions live in defaults/hidden-roles.json and the referenced
13
+ * Agent definitions live in defaults/agents.json and the referenced
14
14
  * systemFile markdown frontmatter. Editing those files is a Mixdog source change.
15
15
  *
16
16
  * The preset names refer to entries seeded in mixdog-config.json (agent.presets)
17
17
  * via DEFAULT_PRESETS (see config.mjs). If the user deletes the referenced preset
18
- * from their config the hidden roles degrade gracefully — `resolvePresetName`
18
+ * from their config the hidden agents degrade gracefully — `resolvePresetName`
19
19
  * returns a name, but session creation will fail with a clear "preset not
20
20
  * found" error rather than silently mis-dispatching.
21
21
  *
22
22
  * Kind classification:
23
- * - 'retrieval' : short-lived hidden retrieval roles (explore).
24
- * - 'maintenance' : background-trigger hidden roles (memory cycle, scheduler,
23
+ * - 'retrieval' : short-lived hidden retrieval agents (explore).
24
+ * - 'maintenance' : background-trigger hidden agents (memory cycle, scheduler,
25
25
  * webhook). Receive only their own self section in BP2.
26
26
  *
27
27
  * Permission classification:
28
28
  * - 'read' : read-only — apply_patch/shell blocked at loop.mjs runtime
29
- * guard with the same error string public read-only roles see.
30
- * - 'read-write' : full tool surface — used by hidden roles that legitimately
29
+ * guard with the same error string public read-only agents see.
30
+ * - 'read-write' : full tool surface — used by hidden agents that legitimately
31
31
  * mutate state (scheduler-task launches commands,
32
32
  * webhook-handler persists payloads). agent-dispatch honors this
33
- * declaratively instead of forcing every hidden role to 'read'.
33
+ * declaratively instead of forcing every hidden agent to 'read'.
34
34
  *
35
35
  * Tool schema profile:
36
- * - 'none' : no tools exposed; pure transform/classifier roles.
36
+ * - 'none' : no tools exposed; pure transform/classifier agents.
37
37
  * - 'read' : read/search/code-navigation tools only.
38
38
  * - 'full' : shared agent tool schema for provider cache reuse.
39
39
  *
40
- * Role-specific instruction metadata (consumed by rules-builder.cjs
40
+ * Agent-specific instruction metadata (consumed by rules-builder.cjs
41
41
  * buildAgentRoleSpecificContent + collect.mjs):
42
- * - inboundEvent : role reports results back to Lead and must carry the
42
+ * - inboundEvent : agent reports results back to Lead and must carry the
43
43
  * skip-protocol rule (rules/agent/20-skip-protocol.md)
44
- * in its BP2 role-rule block so no-op outputs opt out of the Lead
44
+ * in its BP2 agent-rule block so no-op outputs opt out of the Lead
45
45
  * inject.
46
46
  * - instructionDir : DATA_DIR subdir whose *.md tree is folded into the
47
- * role's BP4-adjacent user/task data (webhook-handler →
47
+ * agent's BP4-adjacent user/task data (webhook-handler →
48
48
  * webhooks, scheduler-task → schedules).
49
49
  */
50
50
 
@@ -56,24 +56,24 @@ import {
56
56
  parseMarkdownFrontmatter,
57
57
  } from '../../shared/markdown-frontmatter.mjs'
58
58
 
59
- // Resolve the path to defaults/hidden-roles.json once.
59
+ // Resolve the path to defaults/agents.json once.
60
60
  const _MIXDOG_ROOT = mixdogRoot()
61
- const _HIDDEN_ROLES_PATH = (() => {
62
- return join(_MIXDOG_ROOT, 'defaults', 'hidden-roles.json')
61
+ const _AGENTS_PATH = (() => {
62
+ return join(_MIXDOG_ROOT, 'defaults', 'agents.json')
63
63
  })()
64
64
 
65
65
  /** @type {{ mtime: number, map: object } | null} */
66
- let _hiddenRolesCache = null
66
+ let _hiddenAgentsCache = null
67
67
 
68
68
  function _mtimeSafe(file) {
69
69
  try { return statSync(file).mtimeMs } catch { return 0 }
70
70
  }
71
71
 
72
- function _hiddenRolesDependencyMtime() {
73
- let mtime = _mtimeSafe(_HIDDEN_ROLES_PATH)
72
+ function _hiddenAgentsDependencyMtime() {
73
+ let mtime = _mtimeSafe(_AGENTS_PATH)
74
74
  try {
75
- const raw = JSON.parse(readFileSync(_HIDDEN_ROLES_PATH, 'utf8'))
76
- for (const entry of (raw.roles || [])) {
75
+ const raw = JSON.parse(readFileSync(_AGENTS_PATH, 'utf8'))
76
+ for (const entry of (raw.agents || [])) {
77
77
  const systemFile = typeof entry?.systemFile === 'string' ? entry.systemFile.trim() : ''
78
78
  if (!systemFile) continue
79
79
  mtime = Math.max(mtime, _mtimeSafe(join(_MIXDOG_ROOT, systemFile)))
@@ -82,7 +82,7 @@ function _hiddenRolesDependencyMtime() {
82
82
  return mtime
83
83
  }
84
84
 
85
- function _loadHiddenRoleFrontmatter(systemFile) {
85
+ function _loadHiddenAgentFrontmatter(systemFile) {
86
86
  const rel = typeof systemFile === 'string' ? systemFile.trim() : ''
87
87
  if (!rel) return {}
88
88
  try {
@@ -92,8 +92,8 @@ function _loadHiddenRoleFrontmatter(systemFile) {
92
92
  }
93
93
  }
94
94
 
95
- function _mergeHiddenRoleFrontmatter(entry) {
96
- const fm = _loadHiddenRoleFrontmatter(entry.systemFile)
95
+ function _mergeHiddenAgentFrontmatter(entry) {
96
+ const fm = _loadHiddenAgentFrontmatter(entry.systemFile)
97
97
  const merged = { ...entry }
98
98
  const permission = normalizeAgentPermissionOrNone(fm.permission)
99
99
  if (permission) merged.permission = permission
@@ -111,62 +111,62 @@ function _mergeHiddenRoleFrontmatter(entry) {
111
111
  }
112
112
 
113
113
  /**
114
- * Read and parse defaults/hidden-roles.json. Throws on missing/malformed.
114
+ * Read and parse defaults/agents.json. Throws on missing/malformed.
115
115
  */
116
- function _loadHiddenRoles() {
116
+ function _loadHiddenAgents() {
117
117
  try {
118
- const raw = JSON.parse(readFileSync(_HIDDEN_ROLES_PATH, 'utf8'))
118
+ const raw = JSON.parse(readFileSync(_AGENTS_PATH, 'utf8'))
119
119
  const map = Object.create(null)
120
- for (const entry of (raw.roles || [])) {
121
- if (entry && entry.name) map[entry.name] = Object.freeze(_mergeHiddenRoleFrontmatter(entry))
120
+ for (const entry of (raw.agents || [])) {
121
+ if (entry && entry.agent) map[entry.agent] = Object.freeze(_mergeHiddenAgentFrontmatter(entry))
122
122
  }
123
123
  return Object.freeze(map)
124
124
  } catch (err) {
125
- // Fail loudly — a missing or malformed hidden-roles.json breaks dispatch.
126
- throw new Error(`[internal-roles] failed to load defaults/hidden-roles.json: ${err.message}`)
125
+ // Fail loudly — a missing or malformed agents.json breaks dispatch.
126
+ throw new Error(`[internal-agents] failed to load defaults/agents.json: ${err.message}`)
127
127
  }
128
128
  }
129
129
 
130
130
  /**
131
- * Return the hidden-role map, re-reading from disk when the file mtime has
131
+ * Return the hidden-agent map, re-reading from disk when the file mtime has
132
132
  * changed since the last load. This ensures that BP2 cache rebuilds triggered
133
- * by a hidden-roles.json modification (tracked via maxMtimeRecursive in
133
+ * by an agents.json modification (tracked via maxMtimeRecursive in
134
134
  * collect.mjs) consume fresh metadata in the same process.
135
135
  */
136
- function _getHiddenRoles() {
136
+ function _getHiddenAgents() {
137
137
  try {
138
- const mtime = _hiddenRolesDependencyMtime()
139
- if (_hiddenRolesCache && mtime <= _hiddenRolesCache.mtime) {
140
- return _hiddenRolesCache.map
138
+ const mtime = _hiddenAgentsDependencyMtime()
139
+ if (_hiddenAgentsCache && mtime <= _hiddenAgentsCache.mtime) {
140
+ return _hiddenAgentsCache.map
141
141
  }
142
- const map = _loadHiddenRoles()
143
- _hiddenRolesCache = { mtime, map }
142
+ const map = _loadHiddenAgents()
143
+ _hiddenAgentsCache = { mtime, map }
144
144
  return map
145
145
  } catch (err) {
146
146
  // Fail loudly — re-throw with a clear message. A cache hit is never used
147
147
  // when statSync fails because the caller expects current data.
148
- throw new Error(`[internal-roles] failed to load defaults/hidden-roles.json: ${err.message}`)
148
+ throw new Error(`[internal-agents] failed to load defaults/agents.json: ${err.message}`)
149
149
  }
150
150
  }
151
151
 
152
152
  // Eager validate at module init so startup failures are immediate.
153
- _getHiddenRoles()
153
+ _getHiddenAgents()
154
154
 
155
155
  /**
156
- * Return the hidden-role definition, or null if the name is not internal.
156
+ * Return the hidden-agent definition, or null if the name is not internal.
157
157
  */
158
- export function getHiddenRole(name) {
158
+ export function getHiddenAgent(name) {
159
159
  if (!name) return null
160
- return _getHiddenRoles()[name] || null
160
+ return _getHiddenAgents()[name] || null
161
161
  }
162
162
 
163
163
  /**
164
- * Resolve permission stamped on a agent session. Hidden roles declared
164
+ * Resolve permission stamped on a agent session. Hidden agents declared
165
165
  * `permission: 'read'` are read-locked — caller opts cannot upgrade them.
166
166
  * Other built-in permissions include `none`, `read-write`, `mcp`, and `full`.
167
167
  */
168
- export function resolveAgentSessionPermission(role, callerPermission) {
169
- const hidden = getHiddenRole(role)
168
+ export function resolveAgentSessionPermission(agent, callerPermission) {
169
+ const hidden = getHiddenAgent(agent)
170
170
  if (hidden && hidden.permission === 'read') return 'read'
171
171
  if (callerPermission != null && callerPermission !== '') return callerPermission
172
172
  if (hidden?.permission) return hidden.permission
@@ -176,61 +176,61 @@ export function resolveAgentSessionPermission(role, callerPermission) {
176
176
  /**
177
177
  * Boolean check — useful for branching inside agent-dispatch / session-manager.
178
178
  */
179
- export function isHiddenRole(name) {
179
+ export function isHiddenAgent(name) {
180
180
  if (!name) return false
181
- return Object.prototype.hasOwnProperty.call(_getHiddenRoles(), name)
181
+ return Object.prototype.hasOwnProperty.call(_getHiddenAgents(), name)
182
182
  }
183
183
 
184
184
  /**
185
- * List all hidden role names. Used by diagnostics / setup UI guards to ensure
186
- * a user-defined role doesn't collide with an internal one.
185
+ * List all hidden agent names. Used by diagnostics / setup UI guards to ensure
186
+ * a user-defined agent doesn't collide with an internal one.
187
187
  */
188
- export function listHiddenRoleNames() {
189
- return Object.keys(_getHiddenRoles())
188
+ export function listHiddenAgentNames() {
189
+ return Object.keys(_getHiddenAgents())
190
190
  }
191
191
 
192
192
  /**
193
- * List hidden role names matching a given kind ('retrieval' | 'maintenance').
194
- * Consumed by collect.mjs to drive BP2 role-shard classification dynamically
195
- * instead of hard-coding role-name sets.
193
+ * List hidden agent names matching a given kind ('retrieval' | 'maintenance').
194
+ * Consumed by collect.mjs to drive BP2 agent-shard classification dynamically
195
+ * instead of hard-coding agent-name sets.
196
196
  */
197
- export function listHiddenRolesByKind(kind) {
197
+ export function listHiddenAgentsByKind(kind) {
198
198
  const out = []
199
- for (const [name, def] of Object.entries(_getHiddenRoles())) {
199
+ for (const [name, def] of Object.entries(_getHiddenAgents())) {
200
200
  if (def.kind === kind) out.push(name)
201
201
  }
202
202
  return out
203
203
  }
204
204
 
205
205
  /**
206
- * Return the agents/<name>.md sections a hidden role shares in its BP2 catalog
206
+ * Return the agents/<name>.md sections a hidden agent shares in its BP2 catalog
207
207
  * (in addition to its own self section). Drives the explorer→worker cache
208
- * alignment declaratively instead of a hard-coded role-name branch in
209
- * collect.mjs. Returns [] when the role declares none.
208
+ * alignment declaratively instead of a hard-coded agent-name branch in
209
+ * collect.mjs. Returns [] when the agent declares none.
210
210
  */
211
- export function getRoleCatalogShareAgents(name) {
212
- const hidden = getHiddenRole(name)
211
+ export function getAgentCatalogShareAgents(name) {
212
+ const hidden = getHiddenAgent(name)
213
213
  if (!hidden || !Array.isArray(hidden.catalogShareAgents)) return []
214
214
  return hidden.catalogShareAgents.map((n) => String(n || '').trim()).filter(Boolean)
215
215
  }
216
216
 
217
217
  /**
218
- * True when a hidden role reports results back to Lead and must carry the
219
- * skip-protocol rule in its BP2 role-rule block. Replaces the hard-coded
218
+ * True when a hidden agent reports results back to Lead and must carry the
219
+ * skip-protocol rule in its BP2 agent-rule block. Replaces the hard-coded
220
220
  * INBOUND_EVENT_ROLES set in collect.mjs.
221
221
  */
222
- export function isInboundEventRole(name) {
223
- const hidden = getHiddenRole(name)
222
+ export function isInboundEventAgent(name) {
223
+ const hidden = getHiddenAgent(name)
224
224
  return !!(hidden && hidden.inboundEvent === true)
225
225
  }
226
226
 
227
227
  /**
228
228
  * Return the DATA_DIR subdir whose *.md tree rides as BP4-adjacent
229
- * user/task data, or null when the role declares none. Replaces the
229
+ * user/task data, or null when the agent declares none. Replaces the
230
230
  * webhook/scheduler ternary in rules-builder.cjs buildAgentRoleSpecificContent.
231
231
  */
232
- export function getRoleInstructionDir(name) {
233
- const hidden = getHiddenRole(name)
232
+ export function getAgentInstructionDir(name) {
233
+ const hidden = getHiddenAgent(name)
234
234
  const dir = hidden && typeof hidden.instructionDir === 'string' ? hidden.instructionDir.trim() : ''
235
235
  return dir || null
236
236
  }