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
@@ -21,10 +21,12 @@ import {
21
21
  isInvalidToolArgsMarker,
22
22
  formatInvalidToolArgsResult,
23
23
  } from '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs';
24
+ import { parseSSEStream } from '../src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs';
24
25
  import { classifyError } from '../src/runtime/agent/orchestrator/providers/retry-classifier.mjs';
25
26
 
26
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
27
28
  const STREAM_SRC = resolve(__dirname, '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs');
29
+ const UTF8 = new TextEncoder();
28
30
 
29
31
  // Captures the synchronous throw of `fn` and returns the Error, failing if none.
30
32
  function captureThrow(fn) {
@@ -148,3 +150,82 @@ test('retry-classifier: truncated stream (no finish) → transient (preserved be
148
150
  parseCompletedToolCallArgumentsJson('{', 'test'));
149
151
  assert.equal(classifyError(transient), 'transient');
150
152
  });
153
+
154
+ function responseFromSseText(text) {
155
+ return {
156
+ body: new ReadableStream({
157
+ start(controller) {
158
+ controller.enqueue(UTF8.encode(text));
159
+ controller.close();
160
+ },
161
+ }),
162
+ };
163
+ }
164
+
165
+ function livePingOnlyResponse(intervalMs = 5) {
166
+ let timer = null;
167
+ return {
168
+ body: new ReadableStream({
169
+ start(controller) {
170
+ timer = setInterval(() => {
171
+ try { controller.enqueue(UTF8.encode(':ping\n\n')); }
172
+ catch { if (timer) clearInterval(timer); }
173
+ }, intervalMs);
174
+ },
175
+ cancel() {
176
+ if (timer) clearInterval(timer);
177
+ },
178
+ }),
179
+ };
180
+ }
181
+
182
+ test('anthropic SSE: comments do not count as stream progress', async () => {
183
+ const state = { firstMessageTimeoutMs: 100 };
184
+ let deltas = 0;
185
+ const result = await parseSSEStream(
186
+ responseFromSseText([
187
+ ':ping',
188
+ '',
189
+ 'event: message_start',
190
+ 'data: {"type":"message_start","message":{"model":"claude-test","usage":{"input_tokens":1}}}',
191
+ '',
192
+ 'event: content_block_delta',
193
+ 'data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hi"}}',
194
+ '',
195
+ 'event: message_stop',
196
+ 'data: {"type":"message_stop"}',
197
+ '',
198
+ ].join('\n')),
199
+ null,
200
+ null,
201
+ () => { deltas += 1; },
202
+ null,
203
+ state,
204
+ null,
205
+ );
206
+ assert.equal(result.content, 'hi');
207
+ assert.equal(deltas, 1, 'only the text delta should count as semantic progress');
208
+ });
209
+
210
+ test('anthropic SSE: ping-only stream times out before empty-stream guard can hang', async () => {
211
+ const controller = new AbortController();
212
+ let deltas = 0;
213
+ await assert.rejects(
214
+ () => parseSSEStream(
215
+ livePingOnlyResponse(),
216
+ controller.signal,
217
+ (reason) => controller.abort(reason),
218
+ () => { deltas += 1; },
219
+ null,
220
+ { firstMessageTimeoutMs: 30 },
221
+ null,
222
+ ),
223
+ (err) => {
224
+ assert.equal(err.code, 'EEMPTYSTREAM');
225
+ assert.equal(err.isEmptyStream, true);
226
+ assert.equal(err.firstByteTimeout, true);
227
+ return true;
228
+ },
229
+ );
230
+ assert.equal(deltas, 0, 'SSE comments must not refresh semantic stream progress');
231
+ });
@@ -4,8 +4,8 @@ permission: read
4
4
 
5
5
  # Debugger
6
6
 
7
- Bug tracking and analysis agent.
7
+ Root-cause analysis agent.
8
8
 
9
- Return the most likely cause, supporting evidence, and the smallest useful next
10
- check or fix path. Avoid broad speculation; distinguish confirmed facts from
11
- inferences.
9
+ Smallest confirmed cause chain before fixes. Return likely cause, evidence
10
+ (`file:line`), smallest next check/fix. Fragments only, no narration. Mark
11
+ confirmed facts vs inferences; avoid broad speculation.
@@ -3,7 +3,9 @@ permission: read-write
3
3
  ---
4
4
 
5
5
  # Heavy Worker
6
- Complex implementation agent.
6
+ Broad implementation agent.
7
7
 
8
- Reviewer/Lead verification follows your delivery; avoid broad build/test loops and focus on minimal checks plus how to verify. Scope confirmation, obvious-error checks, and static review are fine.
8
+ Bounded slices; smallest coherent change, not rewrite. Stop: unclear scope,
9
+ growing blast radius, or Lead-only verification. Minimal checks + how-to-verify.
10
+ Handoff fragments only, no report bloat: changed `file:line`, verification, risks.
9
11
 
@@ -4,8 +4,8 @@ permission: read
4
4
 
5
5
  # Reviewer
6
6
 
7
- Cross-checking review agent.
7
+ Regression/risk review agent.
8
8
 
9
- Return findings first, ordered by severity, with precise file references when
10
- available. If no actionable issues are found, say so clearly and note only
11
- material residual risk or missing verification.
9
+ Find actionable correctness/regression/security/verification risks. Findings
10
+ first, severity-ordered, one line with `file:line`; skip non-risky nits. No
11
+ preamble/prose padding. If clean, one line + only material residual risk.
@@ -3,7 +3,9 @@ permission: read-write
3
3
  ---
4
4
 
5
5
  # Worker
6
- Basic implementation agent.
6
+ Scoped implementation agent.
7
7
 
8
- Reviewer/Lead verification follows your delivery; keep self-checks minimal (scope confirmation and obvious-error checks). Static review, change summaries, and manual verification notes are fine.
8
+ Smallest scoped change; no drive-by cleanup. Stop when done/blocked.
9
+ Self-check scope+obvious errors; Lead/Reviewer verify. Handoff fragments only,
10
+ no report bloat: changed `file:line`, verification, risks.
9
11
 
package/src/app.mjs CHANGED
@@ -5,7 +5,7 @@ import { performance } from 'node:perf_hooks';
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const VALUE_OPTIONS = new Set(['--provider', '--model']);
8
- const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react']);
8
+ const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react', '--remote', '--onboarding']);
9
9
  const HEADLESS_ROLE_ALIASES = new Map([
10
10
  ['explorer', 'explore'],
11
11
  ['explore', 'explore'],
@@ -72,7 +72,7 @@ function positionalArgs(argv) {
72
72
  return out;
73
73
  }
74
74
 
75
- function normalizeHeadlessRole(value) {
75
+ function normalizeHeadlessAgent(value) {
76
76
  const key = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
77
77
  return HEADLESS_ROLE_ALIASES.get(key) || null;
78
78
  }
@@ -85,11 +85,11 @@ export function parseHeadlessRoleCommand(argv = []) {
85
85
  return { error: 'usage: mixdog <role> <message...>' };
86
86
  }
87
87
 
88
- const role = normalizeHeadlessRole(args[0]);
89
- if (!role) return null;
88
+ const agent = normalizeHeadlessAgent(args[0]);
89
+ if (!agent) return null;
90
90
  const message = args.slice(1).join(' ').trim();
91
91
  if (!message) return { error: `usage: mixdog ${args[0]} <message...>` };
92
- return { role, message };
92
+ return { agent, message };
93
93
  }
94
94
 
95
95
  /**
@@ -110,10 +110,14 @@ export async function run(argv = []) {
110
110
  const provIdx = argv.indexOf('--provider');
111
111
  const modelIdx = argv.indexOf('--model');
112
112
  const toolMode = argv.includes('--readonly') ? 'readonly' : 'full';
113
+ const remote = argv.includes('--remote');
114
+ const forceOnboarding = argv.includes('--onboarding');
113
115
  const opts = {
114
116
  provider: provIdx >= 0 ? argv[provIdx + 1] : undefined,
115
117
  model: modelIdx >= 0 ? argv[modelIdx + 1] : undefined,
116
118
  toolMode,
119
+ remote,
120
+ forceOnboarding,
117
121
  };
118
122
 
119
123
  // `--help` / `-h`: keep this path tiny; do not import the REPL/runtime stack.
@@ -142,7 +146,7 @@ export async function run(argv = []) {
142
146
  if (headless) {
143
147
  const { runHeadlessRole } = await import('./headless-role.mjs');
144
148
  return await runHeadlessRole({
145
- role: headless.role,
149
+ agent: headless.agent,
146
150
  message: headless.message,
147
151
  provider: opts.provider,
148
152
  model: opts.model,
@@ -1,7 +1,7 @@
1
1
  {
2
- "roles": [
2
+ "agents": [
3
3
  {
4
- "name": "explorer",
4
+ "agent": "explorer",
5
5
  "slot": "explore",
6
6
  "systemFile": "rules/agent/30-explorer.md",
7
7
  "description": "Filesystem navigation agent invoked by the `explore` MCP tool",
@@ -12,7 +12,7 @@
12
12
  "stallCap": { "idleSeconds": 240, "toolRunningSeconds": 180 }
13
13
  },
14
14
  {
15
- "name": "cycle1-agent",
15
+ "agent": "cycle1-agent",
16
16
  "slot": "cycle1",
17
17
  "systemFile": "rules/agent/40-cycle1-agent.md",
18
18
  "description": "Chunker/classifier invoked by memory-cycle runCycle1",
@@ -24,7 +24,7 @@
24
24
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
25
25
  },
26
26
  {
27
- "name": "cycle2-agent",
27
+ "agent": "cycle2-agent",
28
28
  "slot": "cycle2",
29
29
  "systemFile": "rules/agent/41-cycle2-agent.md",
30
30
  "description": "Root re-scorer invoked by memory-cycle runCycle2",
@@ -36,7 +36,7 @@
36
36
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
37
37
  },
38
38
  {
39
- "name": "cycle3-agent",
39
+ "agent": "cycle3-agent",
40
40
  "slot": "cycle3",
41
41
  "systemFile": "rules/agent/42-cycle3-agent.md",
42
42
  "description": "Core memory reviewer invoked by memory-cycle runCycle3",
@@ -48,7 +48,7 @@
48
48
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
49
49
  },
50
50
  {
51
- "name": "scheduler-task",
51
+ "agent": "scheduler-task",
52
52
  "slot": "scheduler",
53
53
  "systemFile": "agents/scheduler-task/AGENT.md",
54
54
  "description": "Scheduled-task executor invoked by scheduler tick",
@@ -61,7 +61,7 @@
61
61
  "stallCap": { "idleSeconds": 600, "toolRunningSeconds": 600 }
62
62
  },
63
63
  {
64
- "name": "webhook-handler",
64
+ "agent": "webhook-handler",
65
65
  "slot": "webhook",
66
66
  "systemFile": "agents/webhook-handler/AGENT.md",
67
67
  "description": "Webhook payload handler invoked by inbound webhook events",
@@ -0,0 +1,32 @@
1
+ ---
2
+ time: 0 9 * * *
3
+ timezone: Asia/Seoul
4
+ days: weekday
5
+ channel: main
6
+ model: gpt-5
7
+ enabled: true
8
+ ---
9
+
10
+ # Schedule instructions (prompt body)
11
+
12
+ This whole markdown body is the prompt the scheduler runs when the cron fires.
13
+ Everything above the second `---` is frontmatter metadata; everything below is
14
+ the instructions.
15
+
16
+ Frontmatter keys:
17
+
18
+ - `time` — required. 5- or 6-field cron expression (e.g. `0 9 * * *`).
19
+ - `timezone` — optional IANA tz (e.g. `Asia/Seoul`); host-local when omitted.
20
+ - `days` — optional; omit for `daily`. Allowed values: `weekday`, `weekend`, or
21
+ a comma list of day abbreviations (`mon,tue,wed,thu,fri,sat,sun`). Written
22
+ only when not `daily`.
23
+ - `channel` — optional channel label. WITH a channel the run dispatches to that
24
+ channel (non-interactive) and REQUIRES `model`; WITHOUT it the run injects
25
+ into the current session (interactive).
26
+ - `model` — required when `channel` is set; the preset id used for dispatch.
27
+ - `enabled` — optional; written `false` to disable. Missing/anything-else means
28
+ enabled. The reader casts this string to a boolean.
29
+
30
+ Storage layout: `<data>/schedules/<name>/SCHEDULE.md` — one file per schedule.
31
+ This `.example.md` is documentation only; the readers match `SCHEDULE.md`
32
+ exactly, so this file is never loaded as a real schedule.
@@ -0,0 +1,40 @@
1
+ ---
2
+ parser: github
3
+ channel: main
4
+ model: gpt-5
5
+ role: webhook-handler
6
+ enabled: true
7
+ ---
8
+
9
+ # Webhook instructions (prompt body)
10
+
11
+ This markdown body is the handler prompt. On each verified delivery the webhook
12
+ payload is fenced as untrusted data and appended after these instructions.
13
+ Everything above the second `---` is frontmatter metadata; everything below is
14
+ the instructions.
15
+
16
+ Frontmatter keys:
17
+
18
+ - `parser` — required. One of `github`, `generic`, `stripe`, `sentry`. Selects
19
+ the signature header + verification scheme.
20
+ - `channel` — optional channel label. WITH a channel the delivery is dispatched
21
+ as a delegate (REQUIRES `model` + `role`) and reported to that channel;
22
+ WITHOUT it the payload is enqueued into the current session (interactive).
23
+ - `model` — required when `channel` is set; the preset id used for delegate
24
+ dispatch.
25
+ - `role` — user-workflow role for delegate mode; defaults to `webhook-handler`.
26
+ - `enabled` — optional; written `false` to disable (delivery rejected with 404).
27
+ Missing/anything-else means enabled. The reader casts this string to a boolean.
28
+
29
+ Secret storage: the HMAC secret is NOT a frontmatter key. It lives in a side
30
+ file `<data>/webhooks/<name>/secret` (plaintext, one line), autogenerated
31
+ (24 random bytes hex) when omitted at save time. Keeping it out of the lossy
32
+ `key: value` frontmatter lets an arbitrary user secret (quotes/colons/newlines)
33
+ round-trip losslessly and prevents enable/disable rewrites from corrupting it.
34
+ The admin list API never exposes the value — only `secretSet: true/false`.
35
+
36
+ Storage layout: `<data>/webhooks/<name>/WEBHOOK.md` — one file per endpoint.
37
+ `secret` and `deliveries.jsonl` live alongside it and are managed separately.
38
+ This
39
+ `.example.md` is documentation only; the readers match `WEBHOOK.md` exactly, so
40
+ this file is never loaded as a real endpoint.
@@ -19,16 +19,16 @@ function taskIdFromOutput(text) {
19
19
  return clean(text).match(/agent task:\s*(\S+)/i)?.[1] || null;
20
20
  }
21
21
 
22
- function makeTag(role) {
23
- return `headless-${role}-${process.pid}-${Date.now()}`
22
+ function makeTag(agent) {
23
+ return `headless-${agent}-${process.pid}-${Date.now()}`
24
24
  .replace(/[^A-Za-z0-9_.-]+/g, '-')
25
25
  .replace(/^-+|-+$/g, '');
26
26
  }
27
27
 
28
- function buildHeadlessSpawnArgs({ role, tag, cwd, message, provider, model } = {}) {
28
+ export function buildHeadlessSpawnArgs({ agent, tag, cwd, message, provider, model } = {}) {
29
29
  const spawnArgs = {
30
30
  type: 'spawn',
31
- role: clean(role),
31
+ agent: clean(agent),
32
32
  tag,
33
33
  cwd,
34
34
  prompt: clean(message),
@@ -52,7 +52,7 @@ function buildAgentRunner(cwd) {
52
52
  }
53
53
 
54
54
  export async function runHeadlessRole({
55
- role,
55
+ agent,
56
56
  message,
57
57
  provider,
58
58
  model,
@@ -60,10 +60,10 @@ export async function runHeadlessRole({
60
60
  write = (text) => stdout.write(text),
61
61
  writeErr = (text) => stderr.write(text),
62
62
  } = {}) {
63
- const cleanRole = clean(role);
63
+ const cleanAgent = clean(agent);
64
64
  const cleanMessage = clean(message);
65
- if (!cleanRole) {
66
- writeErr('mixdog: role is required\n');
65
+ if (!cleanAgent) {
66
+ writeErr('mixdog: agent is required\n');
67
67
  return 1;
68
68
  }
69
69
  if (!cleanMessage) {
@@ -71,8 +71,8 @@ export async function runHeadlessRole({
71
71
  return 1;
72
72
  }
73
73
 
74
- const agent = buildAgentRunner(cwd);
75
- const tag = makeTag(cleanRole);
74
+ const agentRunner = buildAgentRunner(cwd);
75
+ const tag = makeTag(cleanAgent);
76
76
  const context = {
77
77
  invocationSource: 'headless',
78
78
  cwd,
@@ -80,7 +80,7 @@ export async function runHeadlessRole({
80
80
  clientHostPid: process.pid,
81
81
  };
82
82
  const spawnArgs = buildHeadlessSpawnArgs({
83
- role: cleanRole,
83
+ agent: cleanAgent,
84
84
  tag,
85
85
  cwd,
86
86
  message: cleanMessage,
@@ -91,7 +91,7 @@ export async function runHeadlessRole({
91
91
  let taskId = null;
92
92
  let lastOutput = '';
93
93
  try {
94
- const started = await agent.execute(spawnArgs, context);
94
+ const started = await agentRunner.execute(spawnArgs, context);
95
95
  lastOutput = clean(started);
96
96
  taskId = taskIdFromOutput(started);
97
97
  if (!taskId) {
@@ -100,7 +100,7 @@ export async function runHeadlessRole({
100
100
  }
101
101
 
102
102
  for (;;) {
103
- lastOutput = clean(await agent.execute({ type: 'read', task_id: taskId }, context));
103
+ lastOutput = clean(await agentRunner.execute({ type: 'read', task_id: taskId }, context));
104
104
  if (TERMINAL_STATUS_RE.test(lastOutput)) break;
105
105
  await sleep(500);
106
106
  }
@@ -109,7 +109,7 @@ export async function runHeadlessRole({
109
109
  return FAILURE_STATUS_RE.test(lastOutput) ? 1 : 0;
110
110
  } finally {
111
111
  try {
112
- await agent.execute({ type: 'close', tag }, context);
112
+ await agentRunner.execute({ type: 'close', tag }, context);
113
113
  } catch {
114
114
  // Best-effort cleanup only; the command result above is the useful signal.
115
115
  }
package/src/help.mjs CHANGED
@@ -6,6 +6,7 @@ export const HELP_LINES = [
6
6
  '',
7
7
  'Usage:',
8
8
  ' mixdog [--provider <name>] [--model <name>] [--readonly]',
9
+ ' mixdog [--onboarding] re-run the first-run setup wizard',
9
10
  ' mixdog <role> <message...>',
10
11
  ' mixdog --help',
11
12
  '',
@@ -21,10 +21,10 @@
21
21
  * Source files (rules/):
22
22
  * - shared/01-tool.md — universal tool policy (Lead + agent BP1, identical full set)
23
23
  * - lead/lead-tool.md — Lead-specific control-tower / delegation / ToolSearch guidance
24
- * - lead/01-04 — Lead workflow / channels / team / general
24
+ * - lead/01-02 — Lead general / channels
25
25
  * - output-styles/<name>.md — Lead output style, selected by config outputStyle
26
26
  * - agent/00-common.md — agent common behavior + universal worker contract (BP2)
27
- * - agent/10..50-*.md — per-hidden-role bodies (consumed by loadScopedRoleInstructions)
27
+ * - agent/10..50-*.md — per-hidden-agent bodies (consumed by loadScopedRoleInstructions)
28
28
  *
29
29
  * Core memory snapshot is injected separately from the memory worker (pgdata)
30
30
  * (Lead only).
@@ -184,29 +184,29 @@ function loadOutputStyle({ PLUGIN_ROOT, DATA_DIR }) {
184
184
 
185
185
  /**
186
186
  * Resolve the DATA_DIR subdir whose *.md instruction tree is sent as
187
- * BP4-adjacent user/task data, from the role's `instructionDir` metadata in
188
- * defaults/hidden-roles.json. Returns null when the role declares none.
187
+ * BP4-adjacent user/task data, from the agent's `instructionDir` metadata in
188
+ * defaults/agents.json. Returns null when the agent declares none.
189
189
  *
190
- * Mirrors internal-roles.mjs getRoleInstructionDir — rules-builder is CommonJS
190
+ * Mirrors internal-agents.mjs getAgentInstructionDir — rules-builder is CommonJS
191
191
  * and cannot import the ESM module, so it reads the same source of truth
192
192
  * directly. Keeps the webhook-handler→webhooks / scheduler-task→schedules
193
- * mapping declarative instead of a hard-coded role-name ternary.
193
+ * mapping declarative instead of a hard-coded agent-name ternary.
194
194
  *
195
195
  * @param {string} mixdogRoot
196
- * @param {string} role
196
+ * @param {string} agent
197
197
  * @returns {string|null}
198
198
  */
199
- function resolveRoleInstructionDir(mixdogRoot, role) {
200
- if (!mixdogRoot || !role) return null;
201
- const metaPath = path.join(mixdogRoot, 'defaults', 'hidden-roles.json');
199
+ function resolveAgentInstructionDir(mixdogRoot, agent) {
200
+ if (!mixdogRoot || !agent) return null;
201
+ const metaPath = path.join(mixdogRoot, 'defaults', 'agents.json');
202
202
  let raw;
203
203
  try {
204
204
  raw = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
205
205
  } catch (e) {
206
- throw new Error(`[rules-builder] failed to read/parse hidden-roles.json at ${metaPath}: ${e.message}`);
206
+ throw new Error(`[rules-builder] failed to read/parse agents.json at ${metaPath}: ${e.message}`);
207
207
  }
208
- for (const entry of (raw.roles || [])) {
209
- if (entry && entry.name === role) {
208
+ for (const entry of (raw.agents || [])) {
209
+ if (entry && entry.agent === agent) {
210
210
  const dir = typeof entry.instructionDir === 'string' ? entry.instructionDir.trim() : '';
211
211
  return dir || null;
212
212
  }
@@ -247,34 +247,15 @@ function composeUserAddressBullet(memoryConfig) {
247
247
  }
248
248
 
249
249
  function splitLeadGeneral(general, addressBullet = '') {
250
- const lines = String(general || '').split(/\r?\n/);
251
- const roleLines = [];
252
- const metaLines = [];
253
- let inMeta = false;
254
- for (const line of lines) {
255
- if (/language used by the user/i.test(line)) {
256
- if (!inMeta) {
257
- metaLines.push('# General');
258
- metaLines.push('');
259
- inMeta = true;
260
- }
261
- metaLines.push(line);
262
- } else {
263
- roleLines.push(line);
264
- }
265
- }
266
- if (addressBullet) {
267
- if (!inMeta) {
268
- metaLines.push('# General');
269
- metaLines.push('');
270
- inMeta = true;
271
- }
272
- metaLines.push(addressBullet);
273
- }
274
- return {
275
- role: roleLines.join('\n').trim(),
276
- meta: metaLines.join('\n').trim(),
277
- };
250
+ // Language is owned solely by the `# Language` section (buildLanguageSection);
251
+ // general.md never carries a language line, so there is no language content to
252
+ // promote into the meta layer here. The only meta-tier content this split
253
+ // emits is the optional user address-form bullet.
254
+ const role = String(general || '').trim();
255
+ const meta = addressBullet
256
+ ? ['# General', '', addressBullet].join('\n').trim()
257
+ : '';
258
+ return { role, meta };
278
259
  }
279
260
 
280
261
  function buildSharedToolContent({ PLUGIN_ROOT }) {
@@ -299,9 +280,6 @@ function buildLeadRoleContent({ PLUGIN_ROOT, DATA_DIR }) {
299
280
  const channels = readOptional(path.join(LEAD_DIR, '02-channels.md'));
300
281
  if (channels) parts.push(channels);
301
282
 
302
- const workflow = readOptional(path.join(LEAD_DIR, '04-workflow.md'));
303
- if (workflow) parts.push(workflow);
304
-
305
283
  return parts.join('\n\n');
306
284
  }
307
285
 
@@ -420,26 +398,26 @@ function buildAgentRetrievalInjectionContent() {
420
398
  * @param {object} opts
421
399
  * @param {string} opts.PLUGIN_ROOT
422
400
  * @param {string} opts.DATA_DIR
423
- * @param {string|null} opts.currentRole
401
+ * @param {string|null} opts.currentAgent
424
402
  * @returns {string}
425
403
  */
426
- function buildAgentRoleSpecificContent({ PLUGIN_ROOT, DATA_DIR, currentRole }) {
427
- if (!currentRole) return '';
404
+ function buildAgentRoleSpecificContent({ PLUGIN_ROOT, DATA_DIR, currentAgent }) {
405
+ if (!currentAgent) return '';
428
406
  const parts = [];
429
407
 
430
- // The role's instruction subdir (webhook-handler → webhooks, scheduler-task
431
- // → schedules) is declared via `instructionDir` in defaults/hidden-roles.json
432
- // rather than hard-coded here, so adding a new inbound-event role needs only
408
+ // The agent's instruction subdir (webhook-handler → webhooks, scheduler-task
409
+ // → schedules) is declared via `instructionDir` in defaults/agents.json
410
+ // rather than hard-coded here, so adding a new inbound-event agent needs only
433
411
  // a metadata entry.
434
- const subdirForRole = resolveRoleInstructionDir(PLUGIN_ROOT, currentRole);
435
- if (subdirForRole) {
436
- const dir = path.join(DATA_DIR, subdirForRole);
412
+ const subdirForAgent = resolveAgentInstructionDir(PLUGIN_ROOT, currentAgent);
413
+ if (subdirForAgent) {
414
+ const dir = path.join(DATA_DIR, subdirForAgent);
437
415
  const collected = collectMarkdownFilesRecursive(dir);
438
416
  if (collected.length > 0) {
439
417
  collected.sort();
440
418
  const blocks = collected.map(f => stripFrontmatter(readOptional(f))).filter(Boolean);
441
419
  if (blocks.length > 0) {
442
- parts.push([`# Agent ${subdirForRole}`, '', blocks.join('\n\n')].join('\n'));
420
+ parts.push([`# Agent ${subdirForAgent}`, '', blocks.join('\n\n')].join('\n'));
443
421
  }
444
422
  }
445
423
  }