mixdog 0.9.0 → 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 (240) hide show
  1. package/package.json +10 -3
  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/session-ingest-smoke.mjs +2 -2
  23. package/scripts/task-bench.mjs +207 -0
  24. package/scripts/tool-failures.mjs +6 -6
  25. package/scripts/tool-smoke.mjs +306 -66
  26. package/scripts/toolcall-args-test.mjs +81 -0
  27. package/src/agents/debugger/AGENT.md +4 -4
  28. package/src/agents/heavy-worker/AGENT.md +4 -2
  29. package/src/agents/reviewer/AGENT.md +4 -4
  30. package/src/agents/worker/AGENT.md +4 -2
  31. package/src/app.mjs +10 -6
  32. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  33. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  34. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  35. package/src/headless-role.mjs +14 -14
  36. package/src/help.mjs +1 -0
  37. package/src/lib/mixdog-debug.cjs +0 -22
  38. package/src/lib/plugin-paths.cjs +1 -7
  39. package/src/lib/rules-builder.cjs +34 -56
  40. package/src/mixdog-session-runtime.mjs +710 -319
  41. package/src/output-styles/default.md +12 -7
  42. package/src/output-styles/minimal.md +25 -0
  43. package/src/output-styles/oneline.md +21 -0
  44. package/src/output-styles/simple.md +10 -9
  45. package/src/repl.mjs +12 -4
  46. package/src/rules/agent/00-common.md +7 -5
  47. package/src/rules/agent/30-explorer.md +7 -8
  48. package/src/rules/lead/01-general.md +3 -1
  49. package/src/rules/lead/lead-tool.md +7 -0
  50. package/src/rules/shared/01-tool.md +17 -12
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  52. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  53. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  54. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  55. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  56. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  57. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  58. package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
  59. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  60. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  61. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
  62. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  63. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
  66. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
  67. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
  68. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  69. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  70. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  71. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
  72. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
  73. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
  74. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
  75. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  76. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
  77. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  78. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
  79. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  80. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  81. package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
  82. package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
  83. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  84. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  85. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  86. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  87. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
  88. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
  89. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
  91. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  92. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  94. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  95. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
  99. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  100. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
  101. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  102. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
  104. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  105. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
  106. package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
  107. package/src/runtime/channels/backends/discord.mjs +99 -9
  108. package/src/runtime/channels/backends/telegram.mjs +501 -0
  109. package/src/runtime/channels/index.mjs +441 -1254
  110. package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
  111. package/src/runtime/channels/lib/config.mjs +54 -3
  112. package/src/runtime/channels/lib/drop-trace.mjs +1 -1
  113. package/src/runtime/channels/lib/executor.mjs +0 -3
  114. package/src/runtime/channels/lib/format.mjs +4 -2
  115. package/src/runtime/channels/lib/memory-client.mjs +0 -38
  116. package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
  117. package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
  118. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  119. package/src/runtime/channels/lib/session-discovery.mjs +0 -4
  120. package/src/runtime/channels/lib/telegram-format.mjs +283 -0
  121. package/src/runtime/channels/lib/tool-format.mjs +1 -2
  122. package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
  123. package/src/runtime/channels/lib/webhook.mjs +59 -31
  124. package/src/runtime/channels/tool-defs.mjs +1 -1
  125. package/src/runtime/lib/keychain-cjs.cjs +0 -1
  126. package/src/runtime/memory/data/runtime-manifest.json +6 -7
  127. package/src/runtime/memory/index.mjs +187 -43
  128. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  129. package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
  130. package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
  131. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  132. package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
  133. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  134. package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
  135. package/src/runtime/memory/lib/memory.mjs +101 -4
  136. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  137. package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
  138. package/src/runtime/memory/lib/session-ingest.mjs +116 -7
  139. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  140. package/src/runtime/memory/tool-defs.mjs +6 -3
  141. package/src/runtime/search/index.mjs +2 -7
  142. package/src/runtime/search/lib/config.mjs +0 -4
  143. package/src/runtime/search/lib/state.mjs +1 -15
  144. package/src/runtime/search/lib/web-tools.mjs +0 -1
  145. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  146. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  147. package/src/runtime/shared/child-spawn-gate.mjs +0 -6
  148. package/src/runtime/shared/config.mjs +9 -0
  149. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  150. package/src/runtime/shared/schedules-store.mjs +21 -19
  151. package/src/runtime/shared/tool-surface.mjs +98 -13
  152. package/src/runtime/shared/transcript-writer.mjs +129 -0
  153. package/src/runtime/shared/update-checker.mjs +214 -0
  154. package/src/standalone/agent-tool.mjs +255 -109
  155. package/src/standalone/channel-admin.mjs +133 -40
  156. package/src/standalone/channel-worker.mjs +8 -291
  157. package/src/standalone/explore-tool.mjs +2 -2
  158. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  159. package/src/standalone/provider-admin.mjs +11 -0
  160. package/src/standalone/seeds.mjs +1 -11
  161. package/src/standalone/usage-dashboard.mjs +1 -1
  162. package/src/tui/App.jsx +2137 -750
  163. package/src/tui/components/ConfirmBar.jsx +47 -0
  164. package/src/tui/components/ContextPanel.jsx +5 -3
  165. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  166. package/src/tui/components/Markdown.jsx +22 -98
  167. package/src/tui/components/Message.jsx +14 -35
  168. package/src/tui/components/Picker.jsx +87 -12
  169. package/src/tui/components/PromptInput.jsx +146 -9
  170. package/src/tui/components/QueuedCommands.jsx +1 -1
  171. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  172. package/src/tui/components/Spinner.jsx +7 -7
  173. package/src/tui/components/StatusLine.jsx +40 -21
  174. package/src/tui/components/TextEntryPanel.jsx +51 -7
  175. package/src/tui/components/ToolExecution.jsx +177 -100
  176. package/src/tui/components/TurnDone.jsx +4 -4
  177. package/src/tui/components/UsagePanel.jsx +1 -1
  178. package/src/tui/components/tool-output-format.mjs +312 -40
  179. package/src/tui/components/tool-output-format.test.mjs +180 -1
  180. package/src/tui/display-width.mjs +69 -0
  181. package/src/tui/display-width.test.mjs +35 -0
  182. package/src/tui/dist/index.mjs +7324 -2393
  183. package/src/tui/engine.mjs +287 -126
  184. package/src/tui/index.jsx +117 -7
  185. package/src/tui/keyboard-protocol.mjs +42 -0
  186. package/src/tui/lib/voice-recorder.mjs +453 -0
  187. package/src/tui/markdown/format-token.mjs +354 -142
  188. package/src/tui/markdown/format-token.test.mjs +155 -17
  189. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  190. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  191. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  192. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  193. package/src/tui/markdown/table-layout.mjs +9 -9
  194. package/src/tui/paste-attachments.mjs +0 -11
  195. package/src/tui/prompt-history-store.mjs +129 -0
  196. package/src/tui/prompt-history-store.test.mjs +52 -0
  197. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  198. package/src/tui/theme.mjs +41 -647
  199. package/src/tui/themes/base.mjs +86 -0
  200. package/src/tui/themes/basic.mjs +85 -0
  201. package/src/tui/themes/catppuccin.mjs +72 -0
  202. package/src/tui/themes/dracula.mjs +70 -0
  203. package/src/tui/themes/everforest.mjs +71 -0
  204. package/src/tui/themes/gruvbox.mjs +71 -0
  205. package/src/tui/themes/index.mjs +71 -0
  206. package/src/tui/themes/indigo.mjs +78 -0
  207. package/src/tui/themes/kanagawa.mjs +80 -0
  208. package/src/tui/themes/light.mjs +81 -0
  209. package/src/tui/themes/nord.mjs +72 -0
  210. package/src/tui/themes/onedark.mjs +16 -0
  211. package/src/tui/themes/rosepine.mjs +70 -0
  212. package/src/tui/themes/teal.mjs +81 -0
  213. package/src/tui/themes/tokyonight.mjs +79 -0
  214. package/src/tui/themes/utils.mjs +106 -0
  215. package/src/tui/themes/warm.mjs +79 -0
  216. package/src/tui/transcript-tool-failures.mjs +13 -2
  217. package/src/ui/markdown.mjs +1 -1
  218. package/src/ui/model-display.mjs +2 -2
  219. package/src/ui/statusline.mjs +26 -27
  220. package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
  221. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  222. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  223. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  224. package/src/workflows/default/WORKFLOW.md +39 -12
  225. package/src/workflows/sequential/WORKFLOW.md +46 -0
  226. package/src/workflows/solo/WORKFLOW.md +7 -0
  227. package/vendor/ink/build/display-width.js +62 -0
  228. package/vendor/ink/build/ink.js +154 -20
  229. package/vendor/ink/build/measure-text.js +4 -1
  230. package/vendor/ink/build/output.js +115 -9
  231. package/vendor/ink/build/render-node-to-output.js +4 -1
  232. package/vendor/ink/build/render.js +4 -0
  233. package/src/hooks/lib/permission-rules.cjs +0 -170
  234. package/src/hooks/lib/settings-loader.cjs +0 -112
  235. package/src/lib/hook-pipe-path.cjs +0 -10
  236. package/src/output-styles/extreme-simple.md +0 -20
  237. package/src/rules/lead/04-workflow.md +0 -51
  238. package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
  239. package/src/workflows/default/workflow.json +0 -13
  240. package/src/workflows/solo/workflow.json +0 -7
@@ -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
+ }
@@ -85,7 +85,7 @@ const stripKittyQueryResponsesAndTrailingPartial = (buffer) => {
85
85
  // desynchronizes the incremental erase used for frames that exactly fill the
86
86
  // viewport, leaving stale copies of previous frames behind (#969). Keep the
87
87
  // pre-7.0 behavior of fully clearing between fullscreen frames there.
88
- const isWindowsConsole = process.platform === 'win32';
88
+ const isWindowsConsole = process.platform === 'win32' || Boolean(process.env.WT_SESSION);
89
89
  const shouldClearTerminalForFrame = ({ isTty, viewportRows, previousOutputHeight, nextOutputHeight, isUnmounting, }) => {
90
90
  if (!isTty) {
91
91
  return false;
@@ -181,6 +181,11 @@ export default class Ink {
181
181
  // cells ({ x1, y1, x2, y2 } inclusive, normalized) or null. The App sets it
182
182
  // via setSelection(); onRender forwards it to the renderer for highlighting.
183
183
  selectionRect = null;
184
+ // [mixdog fork] Coalesce drag-selection repaints when a frame is pending.
185
+ isRendering = false;
186
+ selectionRepaintQueued = false;
187
+ selectionRepaintFlushPending = false;
188
+ selectionRepaintEpoch = 0;
184
189
  // [mixdog fork] text under the current selection rect, refreshed every render
185
190
  // from the output grid. Read back via getSelectionText() on drag-release.
186
191
  selectedText = null;
@@ -318,9 +323,9 @@ export default class Ink {
318
323
  this.log.setCursorPosition(position);
319
324
  };
320
325
  // [mixdog fork] Update the mouse drag-selection rectangle and repaint so the
321
- // inverse highlight tracks the drag live. Called by the App's mouse handler.
326
+ // inverse highlight tracks the drag. Called by the App's mouse handler.
322
327
  // A no-op-equal update is skipped to avoid redundant frames during motion.
323
- setSelection = (rect) => {
328
+ setSelection = (rect, options = {}) => {
324
329
  const a = this.selectionRect;
325
330
  const same = a === rect ||
326
331
  (a && rect &&
@@ -331,22 +336,68 @@ export default class Ink {
331
336
  a.y2 === rect.y2 &&
332
337
  a.clipY1 === rect.clipY1 &&
333
338
  a.clipY2 === rect.clipY2 &&
334
- a.captureText === rect.captureText);
339
+ a.captureText === rect.captureText &&
340
+ a.selectionForeground === rect.selectionForeground &&
341
+ a.selectionBackground === rect.selectionBackground);
335
342
  if (same) {
336
- return;
343
+ if (!options.immediate) {
344
+ return;
345
+ }
346
+ }
347
+ else {
348
+ this.selectionRect = rect ?? null;
337
349
  }
338
- this.selectionRect = rect ?? null;
339
350
  if (!this.isUnmounted) {
340
- // Drag selection is a direct manipulation gesture. Route it through
341
- // the unthrottled renderer so the highlight tracks the pointer
342
- // instead of trailing behind at maxFps.
343
- this.rootNode.onImmediateRender();
351
+ if (options.immediate) {
352
+ this.selectionRepaintEpoch++;
353
+ this.selectionRepaintFlushPending = false;
354
+ this.rootNode.onImmediateRender();
355
+ return;
356
+ }
357
+ this.scheduleSelectionRepaint();
358
+ }
359
+ };
360
+ scheduleSelectionRepaint = () => {
361
+ if (this.hasPendingThrottledRender) {
362
+ return;
344
363
  }
364
+ if (this.isRendering) {
365
+ this.selectionRepaintQueued = true;
366
+ return;
367
+ }
368
+ if (this.selectionRepaintFlushPending) {
369
+ return;
370
+ }
371
+ this.selectionRepaintFlushPending = true;
372
+ const epoch = ++this.selectionRepaintEpoch;
373
+ queueMicrotask(() => {
374
+ this.selectionRepaintFlushPending = false;
375
+ if (epoch !== this.selectionRepaintEpoch || this.isUnmounted) {
376
+ return;
377
+ }
378
+ if (this.hasPendingThrottledRender) {
379
+ return;
380
+ }
381
+ this.rootNode.onImmediateRender();
382
+ });
345
383
  };
346
384
  // [mixdog fork] Given a 0-based cell (x, y), return the inclusive rect of the
347
- // word (maximal run of non-whitespace cells) on that single row, or null if
348
- // the cell is whitespace/empty or out of range. Reuses the cached cell-value
349
- // rows from the last render so it works without retaining the Output instance.
385
+ // word at that cell on that single row, or null if the cell is whitespace/empty
386
+ // or out of range. Reuses the cached cell-value rows from the last render so it
387
+ // works without retaining the Output instance.
388
+ //
389
+ // Ported from claude-code's selection.ts charClass/wordBoundsAt (3-class word
390
+ // model) onto mixdog's rect(linear) infra. Instead of a naive "non-space run",
391
+ // expansion stops at a CHARACTER-CLASS change:
392
+ // class 1 = WORD_CHAR — letters (any script), digits, and the punctuation
393
+ // iTerm2 treats as word-part by default (/-+~_.\), so a path like
394
+ // `/usr/bin/bash` or `~/.claude/config.json` selects whole.
395
+ // class 2 = other punctuation — so `->` selects just `->`, not the words
396
+ // on either side.
397
+ // class 0 = space/empty. claude-code treats a space run as selectable
398
+ // (class 0), but mixdog intentionally returns null on empty/space
399
+ // so a double-click on blank does nothing (safer for our transcript
400
+ // where most alt-screen cells are padding).
350
401
  getWordRectAt = (x, y) => {
351
402
  const rows = this.lastPlainRows;
352
403
  if (!rows)
@@ -354,16 +405,89 @@ export default class Ink {
354
405
  const cells = rows[y];
355
406
  if (!Array.isArray(cells))
356
407
  return null;
357
- const isWordChar = (v) => !!v && !/^\s$/u.test(v);
358
- if (!isWordChar(cells[x]))
408
+ // Unicode-aware word-char set (matches selection.ts WORD_CHAR).
409
+ const WORD_CHAR = /[\p{L}\p{N}_/.\-+~\\]/u;
410
+ const charClass = (v) => {
411
+ if (!v || v === ' ')
412
+ return 0;
413
+ return WORD_CHAR.test(v) ? 1 : 2;
414
+ };
415
+ // [mixdog fork] Wide/CJK glyphs occupy 2+ grid cells: the HEAD cell
416
+ // holds the glyph and each TRAILING cell is stored as '' (spacer tail)
417
+ // carrying the glyph's styles — see output.js ~L237-243 for how wide
418
+ // chars are laid into the grid. A '' cell is a wide-char TAIL only when
419
+ // it directly follows a non-empty non-space glyph (class !== 0); a ''
420
+ // after '' or after a space is genuine blank padding. This mirrors
421
+ // selection.ts wordBoundsAt's SpacerTail step-back (L172-178) and
422
+ // expansion step-over (L206-221) on mixdog's string-cell grid.
423
+ const isWideTail = (i) => i > 0 && cells[i] === '' && charClass(cells[i - 1]) !== 0;
424
+ // On entry: if the click landed on a spacer tail, step back to the head
425
+ // so charClass sees the actual glyph. Genuine blank padding is left
426
+ // alone, preserving the null-on-blank behavior below.
427
+ let sx = x;
428
+ if (isWideTail(sx))
429
+ sx = sx - 1;
430
+ const cls = charClass(cells[sx]);
431
+ // Preserve mixdog's null-on-space/empty behavior (class 0).
432
+ if (cls === 0)
359
433
  return null;
360
- let x1 = x, x2 = x;
361
- while (x1 - 1 >= 0 && isWordChar(cells[x1 - 1]))
362
- x1--;
363
- while (x2 + 1 < cells.length && isWordChar(cells[x2 + 1]))
364
- x2++;
434
+ let x1 = sx, x2 = sx;
435
+ // Expand left: step OVER a spacer tail to the wide-char head and include
436
+ // both columns when the head matches the class; otherwise stop at a
437
+ // class change.
438
+ while (x1 - 1 >= 0) {
439
+ const p = x1 - 1;
440
+ if (isWideTail(p)) {
441
+ if (p - 1 >= 0 && charClass(cells[p - 1]) === cls) {
442
+ x1 = p - 1;
443
+ continue;
444
+ }
445
+ break;
446
+ }
447
+ if (charClass(cells[p]) === cls) {
448
+ x1 = p;
449
+ continue;
450
+ }
451
+ break;
452
+ }
453
+ // Expand right: INCLUDE a spacer tail that follows an in-run glyph so x2
454
+ // covers the wide glyph's full width; otherwise stop at a class change.
455
+ while (x2 + 1 < cells.length) {
456
+ const n = x2 + 1;
457
+ if (isWideTail(n)) {
458
+ x2 = n;
459
+ continue;
460
+ }
461
+ if (charClass(cells[n]) === cls) {
462
+ x2 = n;
463
+ continue;
464
+ }
465
+ break;
466
+ }
365
467
  return { x1, y1: y, x2, y2: y };
366
468
  };
469
+ // [mixdog fork] Given a 0-based row y, return the inclusive rect of the whole
470
+ // logical line at that row (mirrors claude-code's selectLineAt intent on
471
+ // mixdog's rect infra). x1 is always 0; x2 is the last non-space content cell
472
+ // so trailing padding isn't selected. Returns null for an empty/blank row.
473
+ // Exposed symmetrically to getWordRectAt (index.jsx wires it into the store).
474
+ getLineRectAt = (y) => {
475
+ const rows = this.lastPlainRows;
476
+ if (!rows)
477
+ return null;
478
+ const cells = rows[y];
479
+ if (!Array.isArray(cells))
480
+ return null;
481
+ let x2 = -1;
482
+ for (let x = 0; x < cells.length; x++) {
483
+ const v = cells[x];
484
+ if (v && !/^\s$/u.test(v))
485
+ x2 = x;
486
+ }
487
+ if (x2 < 0)
488
+ return null;
489
+ return { x1: 0, y1: y, x2, y2: y };
490
+ };
367
491
  restoreLastOutput = () => {
368
492
  if (!this.interactive) {
369
493
  return;
@@ -401,6 +525,8 @@ export default class Ink {
401
525
  this.nextRenderCommit.resolve();
402
526
  this.nextRenderCommit = undefined;
403
527
  }
528
+ this.isRendering = true;
529
+ try {
404
530
  const startTime = performance.now();
405
531
  const { output, outputHeight, staticOutput, cursor, selectedText, plainRows } = render(this.rootNode, this.isScreenReaderEnabled, this.selectionRect);
406
532
  // [mixdog fork] Cache the text under the current selection rect so the App
@@ -488,6 +614,14 @@ export default class Ink {
488
614
  this.fullStaticOutput += staticOutput;
489
615
  }
490
616
  this.renderInteractiveFrame(output, outputHeight, hasStaticOutput ? staticOutput : '');
617
+ }
618
+ finally {
619
+ this.isRendering = false;
620
+ if (this.selectionRepaintQueued) {
621
+ this.selectionRepaintQueued = false;
622
+ this.scheduleSelectionRepaint();
623
+ }
624
+ }
491
625
  };
492
626
  render(node) {
493
627
  const tree = (React.createElement(AccessibilityContext.Provider, { value: { isScreenReaderEnabled: this.isScreenReaderEnabled } },
@@ -1,4 +1,7 @@
1
- import widestLine from 'widest-line';
1
+ // [mixdog fork] use the shared display-width policy so ink's text measurement
2
+ // matches OUR wrap/row math for circled digits / arrows. Kept in sync with
3
+ // src/tui/display-width.mjs.
4
+ import { displayWidestLine as widestLine } from './display-width.js';
2
5
  const cache = new Map();
3
6
  const measureText = (text) => {
4
7
  if (text.length === 0) {