mixdog 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/README.md +47 -23
  2. package/package.json +33 -27
  3. package/scripts/_test-folder-dialog.mjs +30 -0
  4. package/scripts/agent-parallel-smoke.mjs +388 -0
  5. package/scripts/agent-tag-reuse-smoke.mjs +183 -0
  6. package/scripts/background-task-meta-smoke.mjs +38 -0
  7. package/scripts/boot-smoke.mjs +52 -9
  8. package/scripts/build-runtime-linux.sh +348 -0
  9. package/scripts/build-runtime-macos.sh +217 -0
  10. package/scripts/build-runtime-windows.ps1 +242 -0
  11. package/scripts/compact-active-turn-test.mjs +68 -0
  12. package/scripts/compact-smoke.mjs +859 -129
  13. package/scripts/compact-trigger-migration-smoke.mjs +187 -0
  14. package/scripts/fix-brief-fn.mjs +35 -0
  15. package/scripts/fix-format-tool-surface.mjs +24 -0
  16. package/scripts/fix-tool-exec-visible.mjs +42 -0
  17. package/scripts/generate-runtime-manifest.mjs +166 -0
  18. package/scripts/hook-bus-test.mjs +330 -0
  19. package/scripts/lead-workflow-smoke.mjs +33 -39
  20. package/scripts/live-worker-smoke.mjs +43 -37
  21. package/scripts/llm-trace-summary.mjs +315 -0
  22. package/scripts/memory-meta-concurrency-test.mjs +20 -0
  23. package/scripts/output-style-smoke.mjs +56 -15
  24. package/scripts/parent-abort-link-test.mjs +44 -0
  25. package/scripts/patch-agent-brief.mjs +48 -0
  26. package/scripts/patch-app.mjs +21 -0
  27. package/scripts/patch-app2.mjs +18 -0
  28. package/scripts/patch-dist-brief.mjs +96 -0
  29. package/scripts/patch-tool-exec.mjs +70 -0
  30. package/scripts/pretool-ask-runtime-test.mjs +54 -0
  31. package/scripts/provider-toolcall-test.mjs +376 -0
  32. package/scripts/reactive-compact-persist-smoke.mjs +124 -0
  33. package/scripts/sanitize-tool-pairs-test.mjs +260 -0
  34. package/scripts/session-context-bench.mjs +344 -0
  35. package/scripts/session-ingest-smoke.mjs +177 -0
  36. package/scripts/set-effort-config-test.mjs +41 -0
  37. package/scripts/smoke-runtime-negative.ps1 +106 -0
  38. package/scripts/smoke-runtime-negative.sh +97 -0
  39. package/scripts/smoke.mjs +25 -0
  40. package/scripts/tool-result-hook-test.mjs +48 -0
  41. package/scripts/tool-smoke.mjs +1223 -95
  42. package/scripts/toolcall-args-test.mjs +150 -0
  43. package/scripts/tui-background-failure-smoke.mjs +73 -0
  44. package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
  45. package/src/agents/debugger/AGENT.md +8 -0
  46. package/src/agents/explore/AGENT.md +4 -0
  47. package/src/agents/heavy-worker/AGENT.md +9 -3
  48. package/src/agents/maintainer/AGENT.md +4 -0
  49. package/src/agents/reviewer/AGENT.md +8 -0
  50. package/src/agents/scheduler-task/AGENT.md +12 -0
  51. package/src/agents/scheduler-task/agent.json +6 -0
  52. package/src/agents/webhook-handler/AGENT.md +12 -0
  53. package/src/agents/webhook-handler/agent.json +6 -0
  54. package/src/agents/worker/AGENT.md +9 -3
  55. package/src/app.mjs +77 -3
  56. package/src/defaults/hidden-roles.json +17 -12
  57. package/src/headless-role.mjs +117 -0
  58. package/src/help.mjs +30 -0
  59. package/src/hooks/lib/permission-evaluator.cjs +11 -475
  60. package/src/lib/keychain-cjs.cjs +9 -1
  61. package/src/lib/mixdog-debug.cjs +0 -7
  62. package/src/lib/rules-builder.cjs +240 -96
  63. package/src/lib/text-utils.cjs +1 -1
  64. package/src/mixdog-session-runtime.mjs +2325 -450
  65. package/src/output-styles/default.md +12 -28
  66. package/src/output-styles/extreme-simple.md +9 -6
  67. package/src/output-styles/simple.md +22 -9
  68. package/src/repl.mjs +118 -59
  69. package/src/rules/agent/00-common.md +15 -0
  70. package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
  71. package/src/rules/agent/30-explorer.md +22 -0
  72. package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
  73. package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
  74. package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
  75. package/src/rules/lead/01-general.md +9 -5
  76. package/src/rules/lead/04-workflow.md +51 -12
  77. package/src/rules/lead/lead-tool.md +6 -0
  78. package/src/rules/shared/01-tool.md +12 -1
  79. package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
  80. package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
  81. package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
  82. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
  83. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
  84. package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
  85. package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
  86. package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
  87. package/src/runtime/agent/orchestrator/config.mjs +174 -55
  88. package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
  89. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
  90. package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
  91. package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
  92. package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
  93. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
  94. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
  95. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
  96. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
  97. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
  98. package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
  99. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
  100. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
  101. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
  102. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
  103. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
  104. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
  105. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
  106. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
  107. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
  108. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
  109. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
  110. package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
  111. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
  112. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
  113. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
  114. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
  115. package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
  116. package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
  117. package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
  118. package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
  119. package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
  120. package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
  121. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
  122. package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
  123. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
  124. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
  125. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
  126. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
  127. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
  128. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
  129. package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
  130. package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
  131. package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
  132. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
  133. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
  134. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
  135. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
  136. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
  137. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
  138. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
  139. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
  140. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
  141. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
  142. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
  143. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
  144. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
  145. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
  146. package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
  147. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
  148. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
  149. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
  150. package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
  151. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
  152. package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
  153. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
  154. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
  155. package/src/runtime/channels/index.mjs +152 -24
  156. package/src/runtime/channels/lib/scheduler.mjs +18 -14
  157. package/src/runtime/channels/lib/session-discovery.mjs +3 -2
  158. package/src/runtime/channels/lib/tool-format.mjs +0 -2
  159. package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
  160. package/src/runtime/channels/lib/webhook.mjs +1 -1
  161. package/src/runtime/channels/tool-defs.mjs +29 -29
  162. package/src/runtime/memory/index.mjs +635 -107
  163. package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
  164. package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
  165. package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
  166. package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
  167. package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
  168. package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
  169. package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
  170. package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
  171. package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
  172. package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
  173. package/src/runtime/memory/lib/memory.mjs +88 -9
  174. package/src/runtime/memory/lib/model-profile.mjs +1 -1
  175. package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
  176. package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
  177. package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
  178. package/src/runtime/memory/lib/session-ingest.mjs +194 -0
  179. package/src/runtime/memory/lib/trace-store.mjs +96 -51
  180. package/src/runtime/memory/tool-defs.mjs +46 -37
  181. package/src/runtime/search/index.mjs +102 -466
  182. package/src/runtime/search/lib/web-tools.mjs +45 -25
  183. package/src/runtime/search/tool-defs.mjs +16 -23
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/atomic-file.mjs +4 -3
  186. package/src/runtime/shared/background-tasks.mjs +122 -11
  187. package/src/runtime/shared/child-spawn-gate.mjs +145 -0
  188. package/src/runtime/shared/config.mjs +7 -4
  189. package/src/runtime/shared/err-text.mjs +131 -4
  190. package/src/runtime/shared/llm/cost.mjs +2 -2
  191. package/src/runtime/shared/llm/http-agent.mjs +23 -7
  192. package/src/runtime/shared/llm/index.mjs +34 -11
  193. package/src/runtime/shared/llm/usage-log.mjs +4 -4
  194. package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
  195. package/src/runtime/shared/singleton-owner.mjs +104 -0
  196. package/src/runtime/shared/tool-execution-contract.mjs +199 -20
  197. package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
  198. package/src/runtime/shared/tool-surface.mjs +624 -98
  199. package/src/runtime/shared/user-data-guard.mjs +0 -2
  200. package/src/standalone/agent-task-status.mjs +203 -0
  201. package/src/standalone/agent-task-status.test.mjs +76 -0
  202. package/src/standalone/agent-tool.mjs +1913 -0
  203. package/src/standalone/channel-worker.mjs +370 -14
  204. package/src/standalone/explore-tool.mjs +165 -70
  205. package/src/standalone/folder-dialog.mjs +314 -0
  206. package/src/standalone/hook-bus.mjs +898 -22
  207. package/src/standalone/memory-runtime-proxy.mjs +320 -0
  208. package/src/standalone/projects.mjs +226 -0
  209. package/src/standalone/provider-admin.mjs +41 -24
  210. package/src/standalone/seeds.mjs +2 -69
  211. package/src/standalone/usage-dashboard.mjs +96 -8
  212. package/src/tui/App.jsx +4798 -2153
  213. package/src/tui/components/AnsiText.jsx +39 -28
  214. package/src/tui/components/ContextPanel.jsx +87 -29
  215. package/src/tui/components/Markdown.jsx +43 -77
  216. package/src/tui/components/MarkdownTable.jsx +9 -184
  217. package/src/tui/components/Message.jsx +28 -11
  218. package/src/tui/components/Picker.jsx +95 -56
  219. package/src/tui/components/PromptInput.jsx +367 -239
  220. package/src/tui/components/QueuedCommands.jsx +1 -1
  221. package/src/tui/components/SlashCommandPalette.jsx +27 -21
  222. package/src/tui/components/Spinner.jsx +67 -38
  223. package/src/tui/components/StatusLine.jsx +606 -38
  224. package/src/tui/components/TextEntryPanel.jsx +128 -9
  225. package/src/tui/components/ToolExecution.jsx +617 -368
  226. package/src/tui/components/TurnDone.jsx +3 -3
  227. package/src/tui/components/UsagePanel.jsx +3 -5
  228. package/src/tui/components/tool-output-format.mjs +365 -0
  229. package/src/tui/components/tool-output-format.test.mjs +220 -0
  230. package/src/tui/dist/index.mjs +8915 -2418
  231. package/src/tui/engine-runtime-notification.test.mjs +115 -0
  232. package/src/tui/engine-tool-result-text.test.mjs +75 -0
  233. package/src/tui/engine.mjs +1455 -279
  234. package/src/tui/figures.mjs +21 -40
  235. package/src/tui/index.jsx +75 -31
  236. package/src/tui/input-editing.mjs +25 -0
  237. package/src/tui/markdown/format-token.mjs +511 -68
  238. package/src/tui/markdown/format-token.test.mjs +216 -0
  239. package/src/tui/markdown/render-ansi.mjs +94 -0
  240. package/src/tui/markdown/render-ansi.test.mjs +108 -0
  241. package/src/tui/markdown/stream-fence.mjs +34 -0
  242. package/src/tui/markdown/stream-fence.test.mjs +26 -0
  243. package/src/tui/markdown/table-layout.mjs +250 -0
  244. package/src/tui/paste-attachments.mjs +0 -7
  245. package/src/tui/spinner-verbs.mjs +1 -2
  246. package/src/tui/statusline-ansi-bridge.mjs +172 -0
  247. package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
  248. package/src/tui/theme.mjs +746 -24
  249. package/src/tui/time-format.mjs +1 -1
  250. package/src/tui/transcript-tool-failures.mjs +67 -0
  251. package/src/tui/transcript-tool-failures.test.mjs +111 -0
  252. package/src/ui/ansi.mjs +1 -2
  253. package/src/ui/markdown.mjs +85 -26
  254. package/src/ui/markdown.test.mjs +70 -0
  255. package/src/ui/model-display.mjs +121 -0
  256. package/src/ui/session-stats.mjs +44 -0
  257. package/src/ui/statusline-context-label.test.mjs +15 -0
  258. package/src/ui/statusline.mjs +386 -178
  259. package/src/ui/tool-card.mjs +3 -16
  260. package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
  261. package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
  262. package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
  263. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
  264. package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
  265. package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
  266. package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
  267. package/src/workflows/default/WORKFLOW.md +12 -5
  268. package/src/workflows/default/workflow.json +0 -1
  269. package/src/workflows/solo/WORKFLOW.md +15 -0
  270. package/src/workflows/solo/workflow.json +7 -0
  271. package/vendor/ink/build/output.js +6 -1
  272. package/src/agents/scheduler-task.md +0 -3
  273. package/src/agents/web-researcher/AGENT.md +0 -3
  274. package/src/agents/web-researcher/agent.json +0 -6
  275. package/src/agents/webhook-handler.md +0 -3
  276. package/src/rules/bridge/00-common.md +0 -5
  277. package/src/rules/bridge/30-explorer.md +0 -4
  278. package/src/rules/lead/00-tool-lead.md +0 -5
  279. package/src/rules/shared/00-language.md +0 -3
  280. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  281. package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
  282. package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
  283. package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
  284. package/src/standalone/bridge-tool.mjs +0 -1414
  285. package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env node
2
+ // Regression tests pinning the native-provider (OpenAI-compat / claude-code / opencode)
3
+ // tool_call arguments contract: completed-but-malformed arguments JSON is NEVER
4
+ // thrown (which would kill the turn) NOR silently swallowed to {}. With a finish
5
+ // signal observed it is surfaced as an invalid-args MARKER carried on the
6
+ // arguments slot, so the dispatch loop feeds the parse error back to the model
7
+ // as an is_error tool_result and the model self-corrects in the SAME turn.
8
+ // Without a finish signal (mid-stream truncation) it remains a retryable
9
+ // TruncatedStreamError — that transient behavior is deliberately preserved.
10
+ // Unit-test style: synthetic inputs fed
11
+ // directly to the exported parser, asserting the resulting outcome — no
12
+ // network, no model.
13
+ import test from 'node:test';
14
+ import assert from 'node:assert/strict';
15
+ import { readFileSync } from 'node:fs';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { dirname, resolve } from 'node:path';
18
+ import {
19
+ parseCompletedToolCallArgumentsJson,
20
+ toolCallsFromStreamAcc,
21
+ isInvalidToolArgsMarker,
22
+ formatInvalidToolArgsResult,
23
+ } from '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs';
24
+ import { classifyError } from '../src/runtime/agent/orchestrator/providers/retry-classifier.mjs';
25
+
26
+ const __dirname = dirname(fileURLToPath(import.meta.url));
27
+ const STREAM_SRC = resolve(__dirname, '../src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs');
28
+
29
+ // Captures the synchronous throw of `fn` and returns the Error, failing if none.
30
+ function captureThrow(fn) {
31
+ try {
32
+ const v = fn();
33
+ assert.fail(`expected throw, got resolved value: ${JSON.stringify(v)}`);
34
+ } catch (err) {
35
+ assert.ok(err instanceof Error, 'thrown value must be an Error');
36
+ return err;
37
+ }
38
+ }
39
+
40
+ test('A: valid arguments JSON parses to the intended object', () => {
41
+ const out = parseCompletedToolCallArgumentsJson(
42
+ '{"pattern":"x","path":"src"}', 'test', { finishReason: 'stop' });
43
+ assert.deepEqual(out, { pattern: 'x', path: 'src' });
44
+ });
45
+
46
+ test('B: empty/missing arguments default to {}', () => {
47
+ assert.deepEqual(parseCompletedToolCallArgumentsJson('', 'test', { finishReason: 'stop' }), {});
48
+ assert.deepEqual(parseCompletedToolCallArgumentsJson(null, 'test', { finishReason: 'stop' }), {});
49
+ assert.deepEqual(parseCompletedToolCallArgumentsJson(undefined, 'test'), {});
50
+ });
51
+
52
+ test('C: bareword JSON with finishReason → invalid-args marker (no throw, no salvage)', () => {
53
+ const bareword = '{"pattern": dispatchAiWrapped, "path": "src/agent"}';
54
+ const out = parseCompletedToolCallArgumentsJson(bareword, 'test', { finishReason: 'stop' });
55
+ assert.equal(isInvalidToolArgsMarker(out), true);
56
+ assert.equal(out.__invalidToolArgs, true);
57
+ assert.equal(out.__rawArguments, bareword);
58
+ assert.equal(typeof out.__parseError, 'string');
59
+ assert.ok(out.__parseError.length > 0, 'parse error message must be carried');
60
+ // Model-facing tool_result text uses the shared invalid-args template.
61
+ const msg = formatInvalidToolArgsResult({ name: 'grep', arguments: out });
62
+ assert.match(msg, /invalid JSON/);
63
+ assert.match(msg, /Re-issue this tool call with valid JSON arguments/);
64
+ });
65
+
66
+ test('C: bareword JSON without finishReason → retryable TruncatedStreamError', () => {
67
+ const bareword = '{"pattern": dispatchAiWrapped, "path": "src/agent"}';
68
+ // meta omitted entirely
69
+ const err1 = captureThrow(() => parseCompletedToolCallArgumentsJson(bareword, 'test'));
70
+ assert.equal(err1.name, 'TruncatedStreamError');
71
+ assert.equal(err1.code, 'TRUNCATED_STREAM');
72
+ assert.equal(err1.truncatedStream, true);
73
+ // meta present but finishReason unset
74
+ const err2 = captureThrow(() =>
75
+ parseCompletedToolCallArgumentsJson(bareword, 'test', { id: 'call_1', name: 'grep' }));
76
+ assert.equal(err2.name, 'TruncatedStreamError');
77
+ assert.equal(err2.code, 'TRUNCATED_STREAM');
78
+ assert.equal(err2.truncatedStream, true);
79
+ });
80
+
81
+ test('D: fully malformed "{" splits on finishReason just like bareword', () => {
82
+ const marker = parseCompletedToolCallArgumentsJson('{', 'test', { finishReason: 'stop' });
83
+ assert.equal(isInvalidToolArgsMarker(marker), true);
84
+ assert.equal(marker.__rawArguments, '{');
85
+
86
+ const transient = captureThrow(() =>
87
+ parseCompletedToolCallArgumentsJson('{', 'test'));
88
+ assert.equal(transient.name, 'TruncatedStreamError');
89
+ assert.equal(transient.code, 'TRUNCATED_STREAM');
90
+ assert.equal(transient.truncatedStream, true);
91
+ });
92
+
93
+ test('E: source no longer contains any salvageBarewordJson reference', () => {
94
+ const src = readFileSync(STREAM_SRC, 'utf8');
95
+ assert.equal(src.includes('salvageBarewordJson'), false,
96
+ 'salvage helper must be fully removed');
97
+ });
98
+
99
+ // Stream-accumulation path: synthetic per-index accumulator + a parseToolCalls
100
+ // shim that delegates to the real parser (same wiring as openai-compat.mjs).
101
+ function parseToolCallsShim(choice, label) {
102
+ const calls = choice.message?.tool_calls;
103
+ if (!calls?.length) return undefined;
104
+ const finishReason = choice.finish_reason || null;
105
+ return calls
106
+ .filter((tc) => tc.type === 'function')
107
+ .map((tc) => ({
108
+ id: tc.id,
109
+ name: tc.function.name,
110
+ arguments: parseCompletedToolCallArgumentsJson(
111
+ tc.function.arguments, label,
112
+ { id: tc.id, name: tc.function.name, finishReason }),
113
+ }));
114
+ }
115
+
116
+ test('stream acc: valid accumulated arguments parse through to a tool call', () => {
117
+ const acc = new Map();
118
+ acc.set('n:0', {
119
+ id: 'call_a', type: 'function',
120
+ function: { name: 'grep', arguments: '{"pattern":"x","path":"src"}' },
121
+ _order: 1,
122
+ });
123
+ const out = toolCallsFromStreamAcc(acc, parseToolCallsShim, 'test', 'stop');
124
+ assert.deepEqual(out, [{ id: 'call_a', name: 'grep', arguments: { pattern: 'x', path: 'src' } }]);
125
+ });
126
+
127
+ test('stream acc: bareword with finish_reason → invalid-args marker on the call (no throw)', () => {
128
+ const acc = new Map();
129
+ acc.set('n:0', {
130
+ id: 'call_b', type: 'function',
131
+ function: { name: 'grep', arguments: '{"pattern": dispatchAiWrapped, "path": "src/agent"}' },
132
+ _order: 1,
133
+ });
134
+ const out = toolCallsFromStreamAcc(acc, parseToolCallsShim, 'test', 'stop');
135
+ assert.equal(Array.isArray(out), true);
136
+ assert.equal(out.length, 1);
137
+ assert.equal(out[0].id, 'call_b');
138
+ assert.equal(out[0].name, 'grep');
139
+ assert.equal(isInvalidToolArgsMarker(out[0].arguments), true);
140
+ });
141
+
142
+ test('retry-classifier: truncated stream (no finish) → transient (preserved behavior)', () => {
143
+ // The invalid-args marker is no longer a thrown Error, so there is nothing
144
+ // for classifyError to route — the dispatch loop handles it as a
145
+ // tool_result. Only the mid-stream truncation path still throws, and it
146
+ // must remain transient/retryable.
147
+ const transient = captureThrow(() =>
148
+ parseCompletedToolCallArgumentsJson('{', 'test'));
149
+ assert.equal(classifyError(transient), 'transient');
150
+ });
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import { backgroundTaskFailureStatusLabel, isBackgroundErrorOnlyBody } from '../src/runtime/shared/err-text.mjs';
3
+ import { parseBackgroundTaskEnvelope } from '../src/tui/engine.mjs';
4
+
5
+ function assertEq(label, got, want) {
6
+ if (got !== want) throw new Error(`${label}: expected ${JSON.stringify(want)}, got ${JSON.stringify(got)}`);
7
+ }
8
+
9
+ assertEq('context overflow', backgroundTaskFailureStatusLabel('failed', 'AGENT_CONTEXT_OVERFLOW', { surface: 'agent' }), 'Failed · Context too large');
10
+ assertEq('first response timeout', backgroundTaskFailureStatusLabel('failed', 'agent first response stale (120000ms)', { surface: 'agent' }), 'Timeout · No first response 120s');
11
+ assertEq('task stale', backgroundTaskFailureStatusLabel('failed', 'agent task stale (1800000ms)', { surface: 'agent' }), 'Stale · No progress 30m');
12
+ assertEq('tool running stale', backgroundTaskFailureStatusLabel('failed', 'agent tool running stale (180000ms)', { surface: 'agent' }), 'Stale · No progress 3m');
13
+ assertEq(
14
+ 'normalized first response',
15
+ backgroundTaskFailureStatusLabel('failed', 'No first response from the agent within 120s.', { surface: 'agent' }),
16
+ 'Timeout · No first response 120s',
17
+ );
18
+ assertEq(
19
+ 'normalized stale',
20
+ backgroundTaskFailureStatusLabel('failed', 'The agent went stale after 30m without new stream/tool progress.', { surface: 'agent' }),
21
+ 'Stale · No progress 30m',
22
+ );
23
+
24
+ assertEq('error-only body', isBackgroundErrorOnlyBody('Error: Context too large.', 'Context too large.'), true);
25
+ assertEq('real body', isBackgroundErrorOnlyBody('found 3 files\nsrc/a.mjs', ''), false);
26
+
27
+ function showRawResultForBackgroundMetadata(expanded, isBackgroundMetadataResult, hasDisplayResult, hasRawResult) {
28
+ return expanded && (hasDisplayResult || hasRawResult) && (!isBackgroundMetadataResult || hasRawResult);
29
+ }
30
+
31
+ function collapsedFailureUsesHeaderNotDetail(headerFailureStatus, showRawResult) {
32
+ return Boolean(headerFailureStatus) && !showRawResult;
33
+ }
34
+
35
+ assertEq('metadata expand with raw', showRawResultForBackgroundMetadata(true, true, false, true), true);
36
+ assertEq('metadata blocked without raw', showRawResultForBackgroundMetadata(true, true, false, false), false);
37
+
38
+ const headerFailure = backgroundTaskFailureStatusLabel(
39
+ 'failed',
40
+ 'No first response from the agent within 120s.',
41
+ { surface: 'agent' },
42
+ );
43
+ assertEq('header failure label', headerFailure, 'Timeout · No first response 120s');
44
+ assertEq('failure in header not detail', collapsedFailureUsesHeaderNotDetail(headerFailure, false), true);
45
+ assertEq('expanded raw keeps detail path', collapsedFailureUsesHeaderNotDetail(headerFailure, true), false);
46
+
47
+ const failedEnvelope = [
48
+ 'background task',
49
+ 'task_id: task_fail_smoke',
50
+ 'surface: explore',
51
+ 'operation: explore',
52
+ 'status: failed',
53
+ 'error: No first response from the agent within 120s.',
54
+ ].join('\n');
55
+ const parsed = parseBackgroundTaskEnvelope(failedEnvelope);
56
+ if (!parsed?.rawResult || String(parsed.result || '').trim()) {
57
+ throw new Error(`failed envelope should keep rawResult and empty display result: ${JSON.stringify(parsed)}`);
58
+ }
59
+ if (!parsed.args?.error) {
60
+ throw new Error('failed envelope should preserve args.error');
61
+ }
62
+
63
+ const failedEnvelopeWithErrorBody = [
64
+ failedEnvelope,
65
+ '',
66
+ 'Error: No first response from the agent within 120s.',
67
+ ].join('\n');
68
+ const parsedWithErrorBody = parseBackgroundTaskEnvelope(failedEnvelopeWithErrorBody);
69
+ if (!parsedWithErrorBody?.rawResult || String(parsedWithErrorBody.result || '').trim()) {
70
+ throw new Error(`failed envelope with error-only body should keep rawResult and empty display result: ${JSON.stringify(parsedWithErrorBody)}`);
71
+ }
72
+
73
+ console.log('tui-background-failure-smoke: ok');
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ bumpUsageMetricsEpoch,
4
+ bumpUsageMetricsTurnId,
5
+ resolveUsageMetricsEpoch,
6
+ usageMetricsIdempotencyKey,
7
+ applyAskTerminalUsageTotals,
8
+ } from '../src/runtime/agent/orchestrator/session/manager.mjs';
9
+
10
+ function assert(condition, message) {
11
+ if (!condition) throw new Error(message);
12
+ }
13
+
14
+ function applyDelta(sessionId, session, delta, seen) {
15
+ const epoch = resolveUsageMetricsEpoch(session, delta);
16
+ session.usageMetricsEpoch = epoch;
17
+ const ikey = usageMetricsIdempotencyKey(sessionId, session, delta);
18
+ const isReplay = seen.has(ikey);
19
+ seen.add(ikey);
20
+ if (!isReplay) {
21
+ session.totalInputTokens = (session.totalInputTokens || 0) + (delta.deltaInput || 0);
22
+ }
23
+ session.lastIterationIndex = delta.iterationIndex;
24
+ return isReplay;
25
+ }
26
+
27
+ const sessionId = 'smoke-sess';
28
+ const session = { usageMetricsEpoch: 0, lastIterationIndex: null, totalInputTokens: 0 };
29
+ const seen = new Set();
30
+
31
+ assert(
32
+ applyDelta(sessionId, session, { iterationIndex: 1, deltaInput: 100 }, seen) === false,
33
+ 'first iteration should count',
34
+ );
35
+ assert(session.totalInputTokens === 100, 'first iteration tokens');
36
+
37
+ bumpUsageMetricsEpoch(session);
38
+ assert(session.usageMetricsEpoch === 1, 'compact reset should bump epoch');
39
+
40
+ const replay = applyDelta(sessionId, session, {
41
+ iterationIndex: 1,
42
+ usageMetricsEpoch: session.usageMetricsEpoch,
43
+ deltaInput: 50,
44
+ }, seen);
45
+ assert(replay === false, 'equal index after compact reset must not be treated as replay');
46
+ assert(session.totalInputTokens === 150, 'post-compact iteration must add tokens');
47
+
48
+ assert(
49
+ applyDelta(sessionId, session, {
50
+ iterationIndex: 1,
51
+ usageMetricsEpoch: 1,
52
+ deltaInput: 999,
53
+ }, seen) === true,
54
+ 'retry of same epoch+index should replay',
55
+ );
56
+ assert(session.totalInputTokens === 150, 'replay must not double-count');
57
+
58
+ // Semantic compact + provider send at same iteration/epoch (no_change compact path)
59
+ const collisionSeen = new Set();
60
+ const collisionSession = { usageMetricsEpoch: 0, lastIterationIndex: null, totalInputTokens: 0 };
61
+ assert(
62
+ applyDelta(sessionId, collisionSession, {
63
+ iterationIndex: 1,
64
+ source: 'semantic_compact',
65
+ deltaInput: 20,
66
+ }, collisionSeen) === false,
67
+ 'semantic compact usage should count',
68
+ );
69
+ assert(
70
+ applyDelta(sessionId, collisionSession, {
71
+ iterationIndex: 1,
72
+ source: 'provider_send',
73
+ deltaInput: 80,
74
+ }, collisionSeen) === false,
75
+ 'provider send at same iteration must not replay semantic compact key',
76
+ );
77
+ assert(collisionSession.totalInputTokens === 100, 'semantic + provider usage both counted');
78
+
79
+ // Integrated incremental + terminal: totals applied once when incremental flag set
80
+ const integrated = { totalInputTokens: 1000, totalOutputTokens: 0, tokensCumulative: 1000 };
81
+ integrated.totalInputTokens += 40;
82
+ integrated.totalOutputTokens += 6;
83
+ integrated.tokensCumulative += 46;
84
+ applyAskTerminalUsageTotals(integrated, {
85
+ usage: { inputTokens: 40, outputTokens: 6 },
86
+ lastTurnUsage: { inputTokens: 40, outputTokens: 6 },
87
+ }, { skipTotalsIfIncremental: true });
88
+ assert(integrated.totalInputTokens === 1040, 'terminal must not double-count incremental turn totals');
89
+ assert(integrated.totalOutputTokens === 6, 'terminal must not double-count output');
90
+ assert(integrated.lastInputTokens === 40, 'terminal still updates last-turn window snapshot');
91
+
92
+ // Cross-ask collision (reviewer07): ask #2 reuses iteration indices 1-2
93
+ const crossSeen = new Set();
94
+ const crossSession = { usageMetricsEpoch: 0, usageMetricsTurnId: 0, lastIterationIndex: null, totalInputTokens: 0 };
95
+ bumpUsageMetricsTurnId(crossSession);
96
+ assert(crossSession.usageMetricsTurnId === 1, 'ask #1 turn id');
97
+ assert(applyDelta(sessionId, crossSession, { iterationIndex: 1, source: 'provider_send', deltaInput: 100 }, crossSeen) === false);
98
+ assert(applyDelta(sessionId, crossSession, { iterationIndex: 2, source: 'provider_send', deltaInput: 200 }, crossSeen) === false);
99
+ assert(crossSession.totalInputTokens === 300, 'ask #1 iterations counted');
100
+ bumpUsageMetricsTurnId(crossSession);
101
+ assert(crossSession.usageMetricsTurnId === 2, 'ask #2 turn id');
102
+ assert(
103
+ applyDelta(sessionId, crossSession, { iterationIndex: 1, source: 'provider_send', deltaInput: 10 }, crossSeen) === false,
104
+ 'ask #2 iteration 1 must not replay ask #1 key',
105
+ );
106
+ assert(
107
+ applyDelta(sessionId, crossSession, { iterationIndex: 2, source: 'provider_send', deltaInput: 20 }, crossSeen) === false,
108
+ 'ask #2 iteration 2 must not replay ask #1 key',
109
+ );
110
+ assert(applyDelta(sessionId, crossSession, { iterationIndex: 3, source: 'provider_send', deltaInput: 30 }, crossSeen) === false);
111
+ assert(crossSession.totalInputTokens === 360, 'ask #2 tokens must include iter 1-3 without undercount');
112
+
113
+ process.stdout.write('usage-metrics-epoch-smoke: ok\n');
114
+
@@ -1,3 +1,11 @@
1
+ ---
2
+ permission: read
3
+ ---
4
+
1
5
  # Debugger
2
6
 
3
7
  Bug tracking and analysis agent.
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.
@@ -1,3 +1,7 @@
1
+ ---
2
+ permission: read
3
+ ---
4
+
1
5
  # Explore
2
6
 
3
7
  Repository exploration agent that finds relevant surfaces and summarizes them
@@ -1,3 +1,9 @@
1
- # Heavy Worker
2
-
3
- Complex implementation agent.
1
+ ---
2
+ permission: read-write
3
+ ---
4
+
5
+ # Heavy Worker
6
+ Complex implementation agent.
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.
9
+
@@ -1,3 +1,7 @@
1
+ ---
2
+ permission: read-write
3
+ ---
4
+
1
5
  # Maintainer
2
6
 
3
7
  Maintenance and cleanup agent.
@@ -1,3 +1,11 @@
1
+ ---
2
+ permission: read
3
+ ---
4
+
1
5
  # Reviewer
2
6
 
3
7
  Cross-checking review agent.
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.
@@ -0,0 +1,12 @@
1
+ ---
2
+ permission: read-write
3
+ toolSchemaProfile: read-write-search
4
+ ---
5
+
6
+ # Scheduler Task
7
+
8
+ Scheduled task execution agent.
9
+
10
+ Output a concise English handoff for Lead: outcome, action taken or skipped,
11
+ notable evidence, and any remaining risk. If there is nothing actionable, use
12
+ the skip protocol.
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "scheduler-task",
3
+ "name": "Scheduler Task",
4
+ "description": "Scheduled-task executor invoked by scheduler tick.",
5
+ "entry": "AGENT.md"
6
+ }
@@ -0,0 +1,12 @@
1
+ ---
2
+ permission: read-write
3
+ toolSchemaProfile: read-write-search
4
+ ---
5
+
6
+ # Webhook Handler
7
+
8
+ Inbound webhook event analysis agent.
9
+
10
+ Output a concise English handoff for Lead: event outcome, action taken or
11
+ skipped, notable evidence, and any remaining risk. If there is nothing
12
+ actionable, use the skip protocol.
@@ -0,0 +1,6 @@
1
+ {
2
+ "id": "webhook-handler",
3
+ "name": "Webhook Handler",
4
+ "description": "Webhook payload handler invoked by inbound webhook events.",
5
+ "entry": "AGENT.md"
6
+ }
@@ -1,3 +1,9 @@
1
- # Worker
2
-
3
- Basic implementation agent.
1
+ ---
2
+ permission: read-write
3
+ ---
4
+
5
+ # Worker
6
+ Basic implementation agent.
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.
9
+
package/src/app.mjs CHANGED
@@ -6,9 +6,33 @@ import { performance } from 'node:perf_hooks';
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
7
  const VALUE_OPTIONS = new Set(['--provider', '--model']);
8
8
  const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react']);
9
+ const HEADLESS_ROLE_ALIASES = new Map([
10
+ ['explorer', 'explore'],
11
+ ['explore', 'explore'],
12
+ ['maint', 'maintainer'],
13
+ ['maintenance', 'maintainer'],
14
+ ['maintainer', 'maintainer'],
15
+ ['worker', 'worker'],
16
+ ['heavy', 'heavy-worker'],
17
+ ['heavyworker', 'heavy-worker'],
18
+ ['heavy-worker', 'heavy-worker'],
19
+ ['review', 'reviewer'],
20
+ ['reviewer', 'reviewer'],
21
+ ['debug', 'debugger'],
22
+ ['debugger', 'debugger'],
23
+ ['web', 'web-researcher'],
24
+ ['web-researcher', 'web-researcher'],
25
+ ]);
9
26
  const BOOT_PROFILE_ENABLED = /^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_BOOT_PROFILE || ''));
10
27
  const BOOT_PROFILE_START = globalThis.__mixdogBootProfileStart || (globalThis.__mixdogBootProfileStart = performance.now());
11
28
 
29
+ // Many independent singletons self-register a process 'exit' drain (session
30
+ // store, bash sessions, search/memory state, bridge trace, channel worker, …),
31
+ // which legitimately exceeds Node's default 10-listener warning threshold in a
32
+ // fully-loaded runtime. Raise the cap once at the CLI entry so a benign
33
+ // MaxListenersExceededWarning never leaks into the user's terminal.
34
+ try { process.setMaxListeners(Math.max(process.getMaxListeners(), 64)); } catch { /* ignore */ }
35
+
12
36
  function bootProfile(event, fields = {}) {
13
37
  if (!BOOT_PROFILE_ENABLED) return;
14
38
  const elapsedMs = performance.now() - BOOT_PROFILE_START;
@@ -33,6 +57,41 @@ function unknownOption(argv) {
33
57
  return null;
34
58
  }
35
59
 
60
+ function positionalArgs(argv) {
61
+ const out = [];
62
+ for (let i = 0; i < argv.length; i += 1) {
63
+ const arg = argv[i];
64
+ if (VALUE_OPTIONS.has(arg)) {
65
+ i += 1;
66
+ continue;
67
+ }
68
+ if (FLAG_OPTIONS.has(arg)) continue;
69
+ if (String(arg || '').startsWith('-')) continue;
70
+ out.push(arg);
71
+ }
72
+ return out;
73
+ }
74
+
75
+ function normalizeHeadlessRole(value) {
76
+ const key = String(value || '').trim().toLowerCase().replace(/[\s_]+/g, '-');
77
+ return HEADLESS_ROLE_ALIASES.get(key) || null;
78
+ }
79
+
80
+ export function parseHeadlessRoleCommand(argv = []) {
81
+ const args = positionalArgs(argv);
82
+ if (args.length === 0) return null;
83
+
84
+ if (String(args[0] || '').toLowerCase() === 'role') {
85
+ return { error: 'usage: mixdog <role> <message...>' };
86
+ }
87
+
88
+ const role = normalizeHeadlessRole(args[0]);
89
+ if (!role) return null;
90
+ const message = args.slice(1).join(' ').trim();
91
+ if (!message) return { error: `usage: mixdog ${args[0]} <message...>` };
92
+ return { role, message };
93
+ }
94
+
36
95
  /**
37
96
  * mixdog launcher.
38
97
  *
@@ -57,10 +116,9 @@ export async function run(argv = []) {
57
116
  toolMode,
58
117
  };
59
118
 
60
- // `--help` / `-h`: print the help text and exit 0. The help source lives in
61
- // repl.mjs (single source of truth) and is exported as printHelp().
119
+ // `--help` / `-h`: keep this path tiny; do not import the REPL/runtime stack.
62
120
  if (argv.includes('--help') || argv.includes('-h')) {
63
- const { printHelp } = await import('./repl.mjs');
121
+ const { printHelp } = await import('./help.mjs');
64
122
  printHelp();
65
123
  return 0;
66
124
  }
@@ -76,6 +134,22 @@ export async function run(argv = []) {
76
134
  return 1;
77
135
  }
78
136
 
137
+ const headless = parseHeadlessRoleCommand(argv);
138
+ if (headless?.error) {
139
+ process.stderr.write(`mixdog: ${headless.error}\n`);
140
+ return 1;
141
+ }
142
+ if (headless) {
143
+ const { runHeadlessRole } = await import('./headless-role.mjs');
144
+ return await runHeadlessRole({
145
+ role: headless.role,
146
+ message: headless.message,
147
+ provider: opts.provider,
148
+ model: opts.model,
149
+ cwd: process.cwd(),
150
+ });
151
+ }
152
+
79
153
  // Default: the canonical React/Ink TUI over the mixdog session runtime.
80
154
  const bundle = join(__dirname, 'tui', 'dist', 'index.mjs');
81
155
  if (!existsSync(bundle)) {
@@ -3,10 +3,10 @@
3
3
  {
4
4
  "name": "explorer",
5
5
  "slot": "explore",
6
- "systemFile": "rules/bridge/30-explorer.md",
6
+ "systemFile": "rules/agent/30-explorer.md",
7
7
  "description": "Filesystem navigation agent invoked by the `explore` MCP tool",
8
8
  "invokedBy": "explore",
9
- "toolSchemaProfile": "filesystem-read",
9
+ "toolSchemaProfile": "read",
10
10
  "kind": "retrieval",
11
11
  "permission": "read",
12
12
  "stallCap": { "idleSeconds": 240, "toolRunningSeconds": 180 }
@@ -14,43 +14,47 @@
14
14
  {
15
15
  "name": "cycle1-agent",
16
16
  "slot": "cycle1",
17
- "systemFile": "rules/bridge/40-cycle1-agent.md",
17
+ "systemFile": "rules/agent/40-cycle1-agent.md",
18
18
  "description": "Chunker/classifier invoked by memory-cycle runCycle1",
19
19
  "invokedBy": "cycle1",
20
20
  "maintKey": "memory",
21
- "toolSchemaProfile": "llm-only", "kind": "maintenance",
21
+ "toolSchemaProfile": "none",
22
+ "kind": "maintenance",
22
23
  "permission": "read",
23
24
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
24
25
  },
25
26
  {
26
27
  "name": "cycle2-agent",
27
28
  "slot": "cycle2",
28
- "systemFile": "rules/bridge/41-cycle2-agent.md",
29
+ "systemFile": "rules/agent/41-cycle2-agent.md",
29
30
  "description": "Root re-scorer invoked by memory-cycle runCycle2",
30
31
  "invokedBy": "cycle2",
31
32
  "maintKey": "memory",
32
- "toolSchemaProfile": "llm-only", "kind": "maintenance",
33
+ "toolSchemaProfile": "none",
34
+ "kind": "maintenance",
33
35
  "permission": "read",
34
36
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
35
37
  },
36
38
  {
37
39
  "name": "cycle3-agent",
38
40
  "slot": "cycle3",
39
- "systemFile": "rules/bridge/42-cycle3-agent.md",
41
+ "systemFile": "rules/agent/42-cycle3-agent.md",
40
42
  "description": "Core memory reviewer invoked by memory-cycle runCycle3",
41
43
  "invokedBy": "cycle3",
42
44
  "maintKey": "memory",
43
- "toolSchemaProfile": "llm-only", "kind": "maintenance",
45
+ "toolSchemaProfile": "none",
46
+ "kind": "maintenance",
44
47
  "permission": "read",
45
48
  "stallCap": { "idleSeconds": 300, "toolRunningSeconds": 300 }
46
49
  },
47
50
  {
48
51
  "name": "scheduler-task",
49
52
  "slot": "scheduler",
50
- "systemFile": "agents/scheduler-task.md",
53
+ "systemFile": "agents/scheduler-task/AGENT.md",
51
54
  "description": "Scheduled-task executor invoked by scheduler tick",
52
55
  "invokedBy": "scheduler",
53
- "toolSchemaProfile": "unified", "kind": "maintenance",
56
+ "toolSchemaProfile": "read-write-search",
57
+ "kind": "maintenance",
54
58
  "permission": "read-write",
55
59
  "inboundEvent": true,
56
60
  "instructionDir": "schedules",
@@ -59,10 +63,11 @@
59
63
  {
60
64
  "name": "webhook-handler",
61
65
  "slot": "webhook",
62
- "systemFile": "agents/webhook-handler.md",
66
+ "systemFile": "agents/webhook-handler/AGENT.md",
63
67
  "description": "Webhook payload handler invoked by inbound webhook events",
64
68
  "invokedBy": "webhook",
65
- "toolSchemaProfile": "unified", "kind": "maintenance",
69
+ "toolSchemaProfile": "read-write-search",
70
+ "kind": "maintenance",
66
71
  "permission": "read-write",
67
72
  "inboundEvent": true,
68
73
  "instructionDir": "webhooks",