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
@@ -5,8 +5,15 @@
5
5
 
6
6
  import { parseToolArgs } from '../runtime/shared/tool-surface.mjs';
7
7
 
8
- const HOOK_DENIAL_RE = /denied by hook/i;
9
- const APPROVAL_UNAVAILABLE_RE = /approval required but no approval UI is available/i;
8
+ // Anchor to the orchestrator's actual denial envelope:
9
+ // `Error: tool "<name>" denied by hook[: ...]`
10
+ // `Error: tool "<name>" denied by hook: approval required but no approval UI is available`
11
+ // Matching the bare phrase anywhere in the body produced FALSE POSITIVES when a
12
+ // normal (successful) tool result merely CONTAINED the text — e.g. a read/grep
13
+ // over this very file or the hook source. The denial is always an Error
14
+ // envelope emitted at the START of the result, so anchor on that prefix.
15
+ const HOOK_DENIAL_RE = /^Error:\s*tool\s*"[^"]*"\s*denied by hook\b/im;
16
+ const APPROVAL_UNAVAILABLE_RE = /denied by hook:\s*approval required but no approval UI is available/i;
10
17
 
11
18
  export function toolItemResultText(item) {
12
19
  if (!item) return '';
@@ -26,6 +33,10 @@ export function isHookApprovalDenialToolResult(text) {
26
33
 
27
34
  export function isHookApprovalDenialToolItem(item) {
28
35
  if (!item || item.kind !== 'tool') return false;
36
+ // A genuine hook denial is always surfaced as an ERROR result. Gating on
37
+ // isError stops a successful result whose body happens to contain the denial
38
+ // phrase (e.g. reading the hook source) from rendering a phantom "Denied".
39
+ if (!item.isError) return false;
29
40
  return isHookApprovalDenialToolResult(toolItemResultText(item));
30
41
  }
31
42
 
@@ -137,7 +137,7 @@ function renderLine(line, width) {
137
137
  // Blockquote (possibly nested).
138
138
  const q = /^(\s*>+)\s?(.*)$/.exec(line);
139
139
  if (q) {
140
- return dim(PALETTE.quoteText('│ ')) + italic(PALETTE.quoteText(renderInline(q[2])));
140
+ return dim('│ ') + italic(renderInline(q[2]));
141
141
  }
142
142
 
143
143
  // Bullet list.
@@ -74,13 +74,13 @@ export function canonicalModelDisplay(model, provider) {
74
74
  return `Grok ${grok[1].split('-').map(titleModelPart).filter(Boolean).join(' ')}`;
75
75
  }
76
76
 
77
- const claudeLegacy = raw.match(/^claude-(\d+)(?:-(\d+))?-(opus|sonnet|haiku)(?:-|$)/i);
77
+ const claudeLegacy = raw.match(/^claude-(\d+)(?:-(\d+))?-(opus|sonnet|haiku|fable)(?:-|$)/i);
78
78
  if (claudeLegacy) {
79
79
  const version = `${claudeLegacy[1]}${claudeLegacy[2] ? `.${claudeLegacy[2]}` : ''}`;
80
80
  return `Claude ${titleModelPart(claudeLegacy[3])} ${version}`;
81
81
  }
82
82
 
83
- const claude = raw.match(/^claude-(opus|sonnet|haiku)-(.+)$/i);
83
+ const claude = raw.match(/^claude-(opus|sonnet|haiku|fable)-(.+)$/i);
84
84
  if (claude) {
85
85
  return `Claude ${titleModelPart(claude[1])} ${claude[2].replace(/-/g, '.')}`;
86
86
  }
@@ -16,7 +16,7 @@ import { bold, colorEnabled, rgb } from './ansi.mjs';
16
16
  import { displayModelName, shortenModelName } from './model-display.mjs';
17
17
  import { createSessionStats } from './session-stats.mjs';
18
18
  import { forEachSessionRuntime } from '../runtime/agent/orchestrator/session/manager.mjs';
19
- import { listHiddenRoleNames } from '../runtime/agent/orchestrator/internal-roles.mjs';
19
+ import { listHiddenAgentNames } from '../runtime/agent/orchestrator/internal-agents.mjs';
20
20
  import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
21
21
  import { readCachedOAuthUsageSnapshot } from '../runtime/agent/orchestrator/providers/oauth-usage.mjs';
22
22
  import { readCachedOpenCodeGoUsageSnapshot } from '../runtime/agent/orchestrator/providers/opencode-go-usage.mjs';
@@ -61,11 +61,11 @@ const L2_SPINNER_FRAME_MS = 120;
61
61
  // replaces them. Snapshots from a previous launch stay hidden during boot so the
62
62
  // statusline starts empty until the current session captures usage once.
63
63
  const STATUSLINE_PROCESS_STARTED_AT_MS = Date.now() - Math.floor((Number(process.uptime?.()) || 0) * 1000);
64
- const DEFAULT_HIDDEN_STATUSLINE_ROLES = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
64
+ const DEFAULT_HIDDEN_STATUSLINE_AGENTS = Object.freeze(['explorer', 'cycle1-agent', 'cycle2-agent', 'cycle3-agent', 'scheduler-task', 'webhook-handler']);
65
65
  let _shellJobsSegmentCache = { ownerPid: 0, at: 0, value: { count: 0, elapsedLabel: '' } };
66
66
  let _gatewayQuotaStatusCache = { key: '', at: 0, value: null };
67
67
  let _fallbackQuotaStatusCache = { key: '', at: 0, value: null };
68
- let _hiddenStatuslineRoles = null;
68
+ let _hiddenStatuslineAgents = null;
69
69
  // Option A boot gate: the L1 usage/quota segment stays fully empty until THIS
70
70
  // process has captured its FIRST confirmed (current-process) OAuth usage
71
71
  // snapshot for THAT provider. The latch is monotonic PER PROVIDER — once a
@@ -211,7 +211,7 @@ function resolveContextUsedPct({
211
211
  }
212
212
 
213
213
  function normalizeAgentWorkerForStatusline(worker = {}) {
214
- const tag = String(worker.tag || worker.role || worker.name || '').trim();
214
+ const tag = String(worker.tag || worker.agent || worker.name || '').trim();
215
215
  if (!tag) return null;
216
216
  const statusText = String(worker.stage || worker.status || '').toLowerCase();
217
217
  const status = isTerminalBridgeStatus(statusText) ? 'idle' : 'running';
@@ -219,7 +219,7 @@ function normalizeAgentWorkerForStatusline(worker = {}) {
219
219
  tag,
220
220
  status,
221
221
  startedAtMs: timeMs(worker.startedAt || worker.startTime || worker.createdAt),
222
- role: worker.role || null,
222
+ agent: worker.agent || null,
223
223
  stage: worker.stage || worker.status || null,
224
224
  sessionId: worker.sessionId || null,
225
225
  provider: worker.provider || null,
@@ -231,7 +231,7 @@ function normalizeAgentJobForStatusline(job = {}) {
231
231
  const statusText = String(job.status || job.stage || '').toLowerCase();
232
232
  if (!statusText) return null;
233
233
  const taskId = String(job.task_id || job.taskId || '').trim();
234
- const tag = String(job.tag || job.role || job.type || taskId || '').trim();
234
+ const tag = String(job.tag || job.agent || job.type || taskId || '').trim();
235
235
  if (!tag && !taskId) return null;
236
236
  const startedAtMs = timeMs(job.startedAt);
237
237
  const finishedAtMs = timeMs(job.finishedAt);
@@ -243,7 +243,7 @@ function normalizeAgentJobForStatusline(job = {}) {
243
243
  finalStatus: statusText,
244
244
  startedAtMs,
245
245
  finishedAtMs,
246
- role: job.role || null,
246
+ agent: job.agent || null,
247
247
  stage: job.stage || job.workerStatus || job.status || null,
248
248
  sessionId: job.sessionId || null,
249
249
  provider: job.provider || null,
@@ -256,7 +256,7 @@ function normalizeAgentJobForStatusline(job = {}) {
256
256
  taskId,
257
257
  status: 'running',
258
258
  startedAtMs,
259
- role: job.role || null,
259
+ agent: job.agent || null,
260
260
  stage: job.stage || job.workerStatus || job.status || null,
261
261
  sessionId: job.sessionId || null,
262
262
  provider: job.provider || null,
@@ -387,10 +387,9 @@ function renderNativeStatusline({
387
387
 
388
388
  const agentPayload = agentStatuslinePayload([
389
389
  ...(Array.isArray(agentWorkers) ? agentWorkers : []),
390
- ...activeHiddenRoleWorkers({ sessionId, clientHostPid }),
390
+ ...activeHiddenAgentWorkers({ sessionId, clientHostPid }),
391
391
  ], agentJobs);
392
- const { maintenance, runningWorkers } = classifyAgentWorkers(agentPayload.workers);
393
- if (maintenance.length) addL1(maintenance.join(' '));
392
+ const { runningWorkers } = classifyAgentWorkers(agentPayload.workers);
394
393
  const shellStatus = shellJobsStatus({ clientHostPid });
395
394
 
396
395
  const spinnerNow = Date.now();
@@ -688,30 +687,30 @@ function classifyAgentWorkers(workers = []) {
688
687
  }
689
688
 
690
689
  function hiddenWorkerLabel(worker = {}) {
691
- const role = String(worker?.role || '').trim();
690
+ const agent = String(worker?.agent || '').trim();
692
691
  const tag = String(worker?.tag || '').trim();
693
- return maintenanceLabel(role) || (!role ? maintenanceLabel(tag) : '');
692
+ return maintenanceLabel(agent) || (!agent ? maintenanceLabel(tag) : '');
694
693
  }
695
694
 
696
- function hiddenStatuslineRoles() {
697
- if (_hiddenStatuslineRoles) return _hiddenStatuslineRoles;
698
- const roles = new Set(DEFAULT_HIDDEN_STATUSLINE_ROLES);
695
+ function hiddenStatuslineAgents() {
696
+ if (_hiddenStatuslineAgents) return _hiddenStatuslineAgents;
697
+ const agents = new Set(DEFAULT_HIDDEN_STATUSLINE_AGENTS);
699
698
  try {
700
- for (const role of listHiddenRoleNames()) {
701
- const clean = String(role || '').trim();
702
- if (clean) roles.add(clean);
699
+ for (const agent of listHiddenAgentNames()) {
700
+ const clean = String(agent || '').trim();
701
+ if (clean) agents.add(clean);
703
702
  }
704
703
  } catch {}
705
- _hiddenStatuslineRoles = roles;
706
- return roles;
704
+ _hiddenStatuslineAgents = agents;
705
+ return agents;
707
706
  }
708
707
 
709
708
  function isActiveHiddenStatus(statusText) {
710
709
  return /^(connecting|requesting|streaming|tool_running|running)$/i.test(String(statusText || '').trim());
711
710
  }
712
711
 
713
- function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
714
- const roles = hiddenStatuslineRoles();
712
+ function activeHiddenAgentWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
713
+ const agents = hiddenStatuslineAgents();
715
714
  const ownerPid = positiveInt(clientHostPid);
716
715
  const ownerSessionId = String(sessionId || '').trim();
717
716
  const rows = [];
@@ -720,8 +719,8 @@ function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
720
719
  if (!entry || entry.closed === true) continue;
721
720
  const session = entry.session || null;
722
721
  if (!session || session.closed === true) continue;
723
- const role = String(session?.role || '').trim();
724
- if (!role || !roles.has(role)) continue;
722
+ const agent = String(session?.agent || '').trim();
723
+ if (!agent || !agents.has(agent)) continue;
725
724
  const id = session?.id || runtimeSessionId || null;
726
725
  if (ownerSessionId && id === ownerSessionId) continue;
727
726
  const sessionOwnerId = String(session?.ownerSessionId || '').trim();
@@ -732,8 +731,8 @@ function activeHiddenRoleWorkers({ sessionId = '', clientHostPid = 0 } = {}) {
732
731
  const status = String(session?.status || stage || '').trim().toLowerCase();
733
732
  if (!isActiveHiddenStatus(stage || status)) continue;
734
733
  rows.push({
735
- tag: String(session?.agentTag || `${role}:${id || rows.length}`).trim(),
736
- role,
734
+ tag: String(session?.agentTag || `${agent}:${id || rows.length}`).trim(),
735
+ agent,
737
736
  status: 'running',
738
737
  stage: stage || status || 'running',
739
738
  sessionId: id,
@@ -11,6 +11,7 @@ import {
11
11
  readLatestGatewayHostRoute,
12
12
  readGatewaySessionRoute,
13
13
  } from '../src/gateway/session-routes.mjs';
14
+ import { compactBoundaryDenominator } from '../src/gateway/route-meta.mjs';
14
15
 
15
16
  function positiveInt(value) {
16
17
  const n = parseInt(String(value || ''), 10);
@@ -278,16 +279,8 @@ function resolveStatusAutoCompactTokenLimit(configured, active = {}, lastCompact
278
279
  export const _resolveStatusAutoCompactTokenLimit = resolveStatusAutoCompactTokenLimit;
279
280
 
280
281
  export function compactBoundaryForStatus(routeInfo = {}, compact = null) {
281
- const compactLimit = num(routeInfo?.autoCompactTokenLimit ?? compact?.compactLimitTokens, 0);
282
- const contextWindow = num(routeInfo?.contextWindow ?? compact?.contextWindow, 0);
283
- const budgetWindow = num(compact?.budgetWindow, 0);
284
- const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
285
- if (compactLimit > 0 && contextWindow > 0) return Math.min(compactLimit, contextWindow);
286
- if (compactLimit > 0) return compactLimit;
287
- if (budgetWindow > 0 && contextWindow > 0) return Math.min(budgetWindow, contextWindow);
288
- if (budgetWindow > 0) return budgetWindow;
289
- if (contextWindow > 0) return contextWindow;
290
- return rawContextWindow > 0 ? rawContextWindow : null;
282
+ const n = compactBoundaryDenominator(routeInfo, compact);
283
+ return n > 0 ? n : null;
291
284
  }
292
285
 
293
286
  function sessionIdFromTranscriptPath(transcriptPath) {
@@ -378,8 +371,8 @@ function displayForModel(provider, model, info) {
378
371
  const display = cleanString(info?.display) || cleanString(info?.displayName) || cleanString(info?.name);
379
372
  if (display) return display;
380
373
  if (provider === 'anthropic-oauth') {
381
- const m = String(model || '').match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)/i);
382
- if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}.${m[3]}`;
374
+ const m = String(model || '').match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?/i);
375
+ if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}${m[3] ? `.${m[3]}` : ''}`;
383
376
  }
384
377
  const raw = cleanString(model);
385
378
  const gpt = raw.match(/^gpt[-_](.+)$/i);
@@ -119,7 +119,7 @@ export function resolveClaudeModelAlias(model) {
119
119
  if (!raw) return null;
120
120
  const stripped = stripClaudeContextSuffix(raw);
121
121
  if (/^claude-/i.test(stripped)) return stripped;
122
- const family = stripped.match(/^(opus|sonnet|haiku)(?:[-_].*)?$/i)?.[1]?.toLowerCase();
122
+ const family = stripped.match(/^(opus|sonnet|haiku|fable)(?:[-_].*)?$/i)?.[1]?.toLowerCase();
123
123
  if (family) return familyDefaultModel(family);
124
124
  return null;
125
125
  }
@@ -128,11 +128,11 @@ export function displayClaudeModel(model, raw = '', display = '') {
128
128
  const explicit = cleanString(display);
129
129
  if (explicit) return explicit;
130
130
  const id = cleanString(model) || resolveClaudeModelAlias(raw) || cleanString(raw);
131
- const m = id.match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)(?:-\d{8})?$/i);
131
+ const m = id.match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?(?:-\d{8})?$/i);
132
132
  if (m) {
133
133
  const family = `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()}`;
134
134
  const suffix = /\[1m\]/i.test(raw) || contextWindowFromRawModel(raw, id) === 1000000 ? ' (1M context)' : '';
135
- return `${family} ${m[2]}.${m[3]}${suffix}`;
135
+ return `${family} ${m[2]}${m[3] ? `.${m[3]}` : ''}${suffix}`;
136
136
  }
137
137
  return id || raw || '';
138
138
  }
@@ -254,8 +254,8 @@ function displayForModel(provider, model, info) {
254
254
  const display = cleanString(info?.display) || cleanString(info?.displayName) || cleanString(info?.name);
255
255
  if (display) return display;
256
256
  if (provider === 'anthropic-oauth') {
257
- const m = String(model || '').match(/^claude-(opus|sonnet|haiku)-(\d+)-(\d+)/i);
258
- if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}.${m[3]}`;
257
+ const m = String(model || '').match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?/i);
258
+ if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}${m[3] ? `.${m[3]}` : ''}`;
259
259
  }
260
260
  return model || '';
261
261
  }
@@ -343,25 +343,44 @@ export function readGatewayRouteInfo(seed = {}, providerObj = null) {
343
343
  };
344
344
  }
345
345
 
346
- function compactBoundaryForRoute(routeInfo = {}, compact = null) {
347
- const compactLimit = num(routeInfo?.autoCompactTokenLimit ?? compact?.compactLimitTokens, 0);
346
+ // Boundary denominator for status %, overflow checks, and gateway env window.
347
+ // Order: explicit boundary (routeInfo or compact) → contextWindow → budgetWindow
348
+ // → rawContextWindow. autoCompactTokenLimit / compactLimitTokens are triggers
349
+ // only and are never read here.
350
+ export function compactBoundaryDenominator(routeInfo = {}, compact = null) {
351
+ const fromRoute = num(
352
+ routeInfo?.compactBoundaryTokens
353
+ ?? routeInfo?.compact_boundary_tokens
354
+ ?? routeInfo?.boundaryTokens,
355
+ 0,
356
+ );
357
+ const fromCompact = num(
358
+ compact?.boundaryTokens
359
+ ?? compact?.compactBoundaryTokens
360
+ ?? compact?.compact_boundary_tokens,
361
+ 0,
362
+ );
363
+ const boundaryTokens = fromRoute > 0 ? fromRoute : fromCompact;
348
364
  const contextWindow = num(routeInfo?.contextWindow ?? compact?.contextWindow, 0);
349
365
  const budgetWindow = num(compact?.budgetWindow, 0);
350
366
  const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
351
- if (compactLimit > 0 && contextWindow > 0) return Math.min(compactLimit, contextWindow);
352
- if (compactLimit > 0) return compactLimit;
353
- if (budgetWindow > 0 && contextWindow > 0) return Math.min(budgetWindow, contextWindow);
354
- if (budgetWindow > 0) return budgetWindow;
367
+ if (boundaryTokens > 0) return boundaryTokens;
355
368
  if (contextWindow > 0) return contextWindow;
369
+ if (budgetWindow > 0) return budgetWindow;
356
370
  return rawContextWindow > 0 ? rawContextWindow : 0;
357
371
  }
358
372
 
373
+ function compactBoundaryForRoute(routeInfo = {}, compact = null) {
374
+ return compactBoundaryDenominator(routeInfo, compact);
375
+ }
376
+
359
377
  // Single source of truth for the value synced to host env
360
378
  // CLAUDE_CODE_AUTO_COMPACT_WINDOW. BOTH the --enable-time write
361
379
  // (scripts/gateway-model.mjs) and the runtime sync (src/gateway/server.mjs)
362
380
  // must derive the window the SAME way, or they disagree on the value written to
363
- // settings.json. Prefer the explicit compact limit, bounded by the effective
364
- // context window, then fall back to context/raw metadata. Returns a positive
381
+ // settings.json. Denominator order: compactBoundaryTokens / boundaryTokens (route
382
+ // or compact) contextWindow budgetWindow rawContextWindow. Never
383
+ // autoCompactTokenLimit or compactLimitTokens (trigger-only). Returns a positive
365
384
  // integer, or null when unknown.
366
385
  export function autoCompactWindowForRoute(routeInfo) {
367
386
  const n = compactBoundaryForRoute(routeInfo);
@@ -428,12 +447,7 @@ function usageCostUsd(routeInfo, usage) {
428
447
  }
429
448
 
430
449
  function contextUsageBoundary(routeInfo, compact = null) {
431
- const contextWindow = num(routeInfo?.contextWindow ?? compact?.contextWindow, 0);
432
- const budgetWindow = num(compact?.budgetWindow, 0);
433
- const rawContextWindow = num(routeInfo?.rawContextWindow ?? compact?.rawContextWindow, 0);
434
- if (contextWindow > 0) return contextWindow;
435
- if (budgetWindow > 0) return budgetWindow;
436
- return rawContextWindow > 0 ? rawContextWindow : compactBoundaryForRoute(routeInfo, compact);
450
+ return compactBoundaryDenominator(routeInfo, compact);
437
451
  }
438
452
 
439
453
  export function summarizeGatewayUsage(routeInfo, providerOut, compact = null, durationMs = null) {
@@ -1,14 +1,41 @@
1
+ ---
2
+ id: default
3
+ name: Default
4
+ description: "Default agent workflow — fan-out parallel delegation across independent scopes."
5
+ agents: worker, heavy-worker, reviewer, debugger, maintainer
6
+ ---
7
+
1
8
  # Default Workflow
2
9
 
3
- 1. Plan — Lead discusses the request with the user, forms a plan, and waits for
4
- approval.
5
- 2. Delegate split the work into the maximum number of independent scopes.
6
- Spawn every scope in the same turn. Never merge separable scopes into one
7
- agent. Shared functions or cross-cutting concerns do not justify merging;
8
- split per path and verify the shared parts yourself.
9
- 3. Review fan out one review scope per implementation scope, in the same turn.
10
- Fact-check implementation and review results yourself. Send fixes back to the
11
- original scope and repeat until clean. Skip delegated review only for simple,
12
- low-risk tasks.
13
- 4. Reportintegrate the results, report the final state, and ask the user
14
- whether to ship/deploy when relevant.
10
+ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
11
+ - Lead handles directly: simple 1–2 step work, plus coordination, pre-planning,
12
+ config changes, and final git deployment.
13
+ - Worker: implementation that takes several steps.
14
+ - Heavy Worker: high-complexity, multi-step implementation.
15
+ - Reviewer: verify implementation scopes (diff, regressions, missing checks).
16
+ - Debugger: deploy for very high complexity, or when root-causing has already
17
+ failed at least once.
18
+
19
+ 1. Plan — discuss the request with the user, form a plan, and wait for approval.
20
+ 2. Delegatesplit into the maximum number of independent scopes.
21
+ - PARALLEL across independent scopes by default (implementation, analysis,
22
+ review, debugging split alike). Spawn every scope in the SAME turn. Shared
23
+ or cross-cutting code does NOT justify merging: split per path and verify
24
+ the shared parts yourself. The only single scope is a genuinely
25
+ inseparable dependency — then state it.
26
+ - SEQUENTIAL within a single complex scope — split it into ordered steps
27
+ rather than handing it off in one shot, with a build/test-green gate
28
+ between steps.
29
+ - Reuse the existing agent (same tag) for follow-up on the same scope; spawn
30
+ new only for a separate scope. Write briefs per the lead-tool brief
31
+ contract (token-optimized labeled fragments).
32
+ - After spawning async agent(s), END THE TURN — do not poll, guess, or start
33
+ dependent work until the completion notification resumes you (status/read
34
+ are manual recovery only). Then wait and continue automatically.
35
+ 3. Review — pair one reviewer 1:1 with each implementation scope, spawned in the
36
+ same turn. Cross-check implementation and review results yourself before
37
+ acting on them. Send fixes back to the original scope and repeat until clean.
38
+ Skip review only for simple, low-risk tasks.
39
+ 4. Report — deliver the final report: synthesize results (outcome + key
40
+ evidence; never forward raw agent output), state the final state, and ask the
41
+ user whether to ship/deploy when relevant.
@@ -0,0 +1,46 @@
1
+ ---
2
+ id: sequential
3
+ name: Sequential
4
+ description: "Sequential workflow — delegates 1:1 with no fan-out; one agent at a time."
5
+ agents: worker, heavy-worker, reviewer, debugger, maintainer
6
+ ---
7
+
8
+ # Sequential Workflow
9
+
10
+ Unlike Default, which fans out independent scopes in parallel, this mode runs
11
+ with no fan-out: it delegates one scope at a time, 1:1, and only moves to the
12
+ next after the current one completes.
13
+
14
+ Lead supervises: delegates, coordinates, judges, decides. Route by complexity:
15
+ - Lead handles directly: simple 1–2 step work, plus coordination, pre-planning,
16
+ config changes, and final git deployment.
17
+ - Worker: implementation that takes several steps.
18
+ - Heavy Worker: high-complexity, multi-step implementation.
19
+ - Reviewer: verify implementation scopes (diff, regressions, missing checks).
20
+ - Debugger: deploy for very high complexity, or when root-causing has already
21
+ failed at least once.
22
+
23
+ 1. Plan — discuss the request with the user, form a plan, and wait for approval.
24
+ 2. Delegate — split into ordered scopes and hand them off ONE AT A TIME.
25
+ - NO PARALLEL, NO FAN-OUT. Even when scopes are independent, spawn exactly
26
+ one agent per turn; never spawn multiple agents in the same turn. Wait for
27
+ the current scope's completion notification before spawning the next scope
28
+ (1:1 sequential).
29
+ - SEQUENTIAL within a single complex scope — split it into ordered steps
30
+ rather than handing it off in one shot, with a build/test-green gate
31
+ between steps.
32
+ - Reuse the existing agent (same tag) for follow-up on the same scope; spawn
33
+ new only for a separate scope. Write briefs per the lead-tool brief
34
+ contract (token-optimized labeled fragments).
35
+ - After spawning an async agent, END THE TURN — do not poll, guess, or start
36
+ dependent or subsequent work until the completion notification resumes you
37
+ (status/read are manual recovery only). Then wait and continue
38
+ automatically.
39
+ 3. Review — after each implementation scope completes, pair one reviewer 1:1
40
+ with that scope (spawned on its own turn, not batched). Cross-check
41
+ implementation and review results yourself before acting on them. Send fixes
42
+ back to the original scope and repeat until clean. Skip review only for
43
+ simple, low-risk tasks.
44
+ 4. Report — deliver the final report: synthesize results (outcome + key
45
+ evidence; never forward raw agent output), state the final state, and ask the
46
+ user whether to ship/deploy when relevant.
@@ -1,3 +1,10 @@
1
+ ---
2
+ id: solo
3
+ name: Solo
4
+ description: "Solo workflow — Lead handles everything directly; delegation forbidden."
5
+ agents:
6
+ ---
7
+
1
8
  # Solo Workflow
2
9
 
3
10
  1. Plan — Lead discusses the request with the user, forms a plan, and waits for
@@ -0,0 +1,62 @@
1
+ /**
2
+ * display-width.js — [mixdog fork] vendored copy of the shared display-width
3
+ * policy from src/tui/display-width.mjs.
4
+ *
5
+ * MUST STAY IN SYNC with src/tui/display-width.mjs. ink is aliased as an
6
+ * external bundle (see scripts/build-tui.mjs), so it cannot cleanly import from
7
+ * src; the small policy is replicated here verbatim instead. If the problem
8
+ * ranges or the WT_SESSION/MIXDOG_TUI_AMBIGUOUS_WIDE gate change there, change
9
+ * them here too — ink's MEASUREMENT must agree with OUR wrap/row math or the
10
+ * overlap gets worse, not better.
11
+ *
12
+ * Policy: widen ONLY U+2460–U+24FF (enclosed alphanumerics / circled digits)
13
+ * and U+2190–U+21FF (arrows) to 2 cells when ON. NEVER widen box-drawing
14
+ * (U+2500–U+257F), block elements, or other ambiguous glyphs. ON by default
15
+ * only under Windows Terminal (WT_SESSION); MIXDOG_TUI_AMBIGUOUS_WIDE='1'/'0'
16
+ * overrides and wins. OFF ⇒ identical to plain string-width.
17
+ */
18
+ import stringWidth from 'string-width';
19
+
20
+ function isProblemCodePoint(cp) {
21
+ return (cp >= 0x2460 && cp <= 0x24ff) || (cp >= 0x2190 && cp <= 0x21ff);
22
+ }
23
+
24
+ function resolveAmbiguousWidePolicy(env = process.env) {
25
+ const override = env?.MIXDOG_TUI_AMBIGUOUS_WIDE;
26
+ if (override === '1')
27
+ return true;
28
+ if (override === '0')
29
+ return false;
30
+ return Boolean(env?.WT_SESSION);
31
+ }
32
+
33
+ // Resolved once at module load (matches src/tui/display-width.mjs).
34
+ export const AMBIGUOUS_WIDE = resolveAmbiguousWidePolicy();
35
+
36
+ export function displayWidthWith(str, wide) {
37
+ const s = String(str ?? '');
38
+ const base = stringWidth(s);
39
+ if (!wide)
40
+ return base;
41
+ let extra = 0;
42
+ for (const ch of s) {
43
+ const cp = ch.codePointAt(0);
44
+ if (isProblemCodePoint(cp) && stringWidth(ch) === 1)
45
+ extra += 1;
46
+ }
47
+ return base + extra;
48
+ }
49
+
50
+ /** Visible display width under the resolved policy (ink-side stringWidth). */
51
+ export function displayStringWidth(str) {
52
+ return displayWidthWith(str, AMBIGUOUS_WIDE);
53
+ }
54
+
55
+ /** widest-line equivalent under the policy. */
56
+ export function displayWidestLine(text) {
57
+ let lineWidth = 0;
58
+ for (const line of String(text ?? '').split('\n')) {
59
+ lineWidth = Math.max(lineWidth, displayStringWidth(line));
60
+ }
61
+ return lineWidth;
62
+ }