mixdog 0.9.4 → 0.9.6

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 (121) hide show
  1. package/README.md +112 -38
  2. package/package.json +4 -1
  3. package/scripts/bench/r5-orchestrated-task.json +7 -0
  4. package/scripts/bench/round-r13-clientmeta.json +464 -0
  5. package/scripts/bench/round-r14-betafeatures.json +466 -0
  6. package/scripts/bench/round-r15-fulldefault.json +462 -0
  7. package/scripts/bench/round-r16-sessionid.json +466 -0
  8. package/scripts/bench/round-r17-wirebytes.json +456 -0
  9. package/scripts/bench/round-r18-prewarm.json +468 -0
  10. package/scripts/bench/round-r19-clean.json +472 -0
  11. package/scripts/bench/round-r20-prewarm-clean.json +475 -0
  12. package/scripts/bench/round-r21-delta-retry.json +473 -0
  13. package/scripts/bench/round-r22-full-probe.json +693 -0
  14. package/scripts/bench/round-r23-itemprobe.json +701 -0
  15. package/scripts/bench/round-r24-shapefix.json +677 -0
  16. package/scripts/bench/round-r25-serial.json +464 -0
  17. package/scripts/bench/round-r26-parallel3.json +671 -0
  18. package/scripts/bench/round-r27-parallel10.json +894 -0
  19. package/scripts/bench/round-r28-parallel10-stagger.json +882 -0
  20. package/scripts/bench/round-r29-parallel10-stagger166.json +886 -0
  21. package/scripts/bench/round-r30-instid.json +253 -0
  22. package/scripts/bench/round-r31-upgradeprobe.json +256 -0
  23. package/scripts/bench/round-r32-vs-codex-lead.json +254 -0
  24. package/scripts/bench/round-r33-vs-codex-codex.json +115 -0
  25. package/scripts/bench/round-r34-orchestrated.json +120 -0
  26. package/scripts/bench/round-r35-orchestrated-codex.json +61 -0
  27. package/scripts/bench/round-r36-orchestrated-capped.json +128 -0
  28. package/scripts/bench-run.mjs +40 -7
  29. package/scripts/internal-comms-bench.mjs +2 -4
  30. package/scripts/model-catalog-audit.mjs +209 -0
  31. package/scripts/model-list-sanitize-test.mjs +37 -0
  32. package/scripts/recall-bench.mjs +76 -13
  33. package/scripts/recall-quality-cases.json +12 -0
  34. package/scripts/tool-smoke.mjs +2 -2
  35. package/src/agents/heavy-worker/AGENT.md +7 -7
  36. package/src/agents/scheduler-task/AGENT.md +2 -3
  37. package/src/agents/webhook-handler/AGENT.md +2 -3
  38. package/src/agents/worker/AGENT.md +9 -10
  39. package/src/app.mjs +12 -1
  40. package/src/headless-role.mjs +7 -1
  41. package/src/mixdog-session-runtime.mjs +15 -14
  42. package/src/output-styles/default.md +14 -18
  43. package/src/output-styles/minimal.md +4 -5
  44. package/src/output-styles/simple.md +7 -8
  45. package/src/rules/agent/00-common.md +6 -5
  46. package/src/rules/agent/30-explorer.md +16 -5
  47. package/src/rules/lead/01-general.md +5 -5
  48. package/src/rules/lead/lead-brief.md +6 -3
  49. package/src/rules/lead/lead-tool.md +6 -4
  50. package/src/rules/shared/01-tool.md +17 -21
  51. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +8 -3
  52. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +19 -0
  53. package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +11 -3
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +9 -1
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +53 -9
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +23 -10
  57. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -9
  58. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +33 -4
  59. package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +80 -0
  60. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +93 -0
  61. package/src/runtime/agent/orchestrator/session/loop.mjs +27 -5
  62. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +1 -1
  63. package/src/runtime/agent/orchestrator/stall-policy.mjs +20 -1
  64. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -7
  65. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  66. package/src/runtime/channels/backends/discord-gateway.mjs +2 -2
  67. package/src/runtime/channels/backends/discord.mjs +15 -26
  68. package/src/runtime/channels/backends/telegram.mjs +8 -12
  69. package/src/runtime/channels/index.mjs +23 -43
  70. package/src/runtime/channels/lib/backend-dispatch.mjs +5 -3
  71. package/src/runtime/channels/lib/config.mjs +37 -149
  72. package/src/runtime/channels/lib/event-pipeline.mjs +4 -4
  73. package/src/runtime/channels/lib/event-queue.mjs +15 -9
  74. package/src/runtime/channels/lib/inbound-routing.mjs +2 -39
  75. package/src/runtime/channels/lib/interaction-workflows.mjs +5 -113
  76. package/src/runtime/channels/lib/scheduler.mjs +27 -113
  77. package/src/runtime/channels/lib/webhook/deliveries.mjs +3 -2
  78. package/src/runtime/channels/lib/webhook.mjs +8 -47
  79. package/src/runtime/memory/index.mjs +9 -0
  80. package/src/runtime/memory/lib/core-memory-store.mjs +5 -1
  81. package/src/runtime/memory/lib/ko-morph.mjs +195 -0
  82. package/src/runtime/memory/lib/memory-recall-store.mjs +47 -10
  83. package/src/runtime/memory/lib/memory-text-utils.mjs +46 -0
  84. package/src/runtime/memory/lib/query-handlers.mjs +27 -5
  85. package/src/runtime/memory/tool-defs.mjs +2 -2
  86. package/src/runtime/shared/config.mjs +14 -4
  87. package/src/runtime/shared/markdown-frontmatter.mjs +19 -0
  88. package/src/runtime/shared/schedules-store.mjs +13 -3
  89. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  90. package/src/session-runtime/config-helpers.mjs +45 -10
  91. package/src/session-runtime/quick-model-rows.mjs +46 -11
  92. package/src/session-runtime/quick-search-models.mjs +28 -27
  93. package/src/session-runtime/settings-api.mjs +34 -1
  94. package/src/session-runtime/tool-defs.mjs +1 -1
  95. package/src/session-runtime/workflow.mjs +1 -1
  96. package/src/standalone/agent-tool/tool-def.mjs +1 -1
  97. package/src/standalone/agent-tool.mjs +30 -0
  98. package/src/standalone/channel-admin.mjs +102 -90
  99. package/src/standalone/explore-tool.mjs +39 -10
  100. package/src/tui/App.jsx +131 -28
  101. package/src/tui/app/app-format.mjs +11 -4
  102. package/src/tui/app/channel-pickers.mjs +2 -4
  103. package/src/tui/app/maintenance-pickers.mjs +60 -28
  104. package/src/tui/app/model-options.mjs +5 -1
  105. package/src/tui/app/settings-picker.mjs +0 -1
  106. package/src/tui/app/transcript-window.mjs +8 -2
  107. package/src/tui/app/use-transcript-scroll.mjs +3 -1
  108. package/src/tui/app/use-transcript-window.mjs +19 -1
  109. package/src/tui/components/ConfirmBar.jsx +10 -7
  110. package/src/tui/components/Picker.jsx +32 -11
  111. package/src/tui/components/PromptInput.jsx +10 -1
  112. package/src/tui/components/StatusLine.jsx +6 -3
  113. package/src/tui/dist/index.mjs +441 -293
  114. package/src/tui/engine.mjs +48 -12
  115. package/src/tui/index.jsx +11 -52
  116. package/src/ui/statusline.mjs +10 -0
  117. package/src/workflows/default/WORKFLOW.md +15 -17
  118. package/src/workflows/sequential/WORKFLOW.md +15 -17
  119. package/vendor/ink/build/ink.js +9 -1
  120. package/scripts/recall-usecase-probe.json +0 -6
  121. package/src/runtime/channels/lib/holidays.mjs +0 -138
@@ -68,27 +68,67 @@ function textOfResult(result) {
68
68
  }
69
69
 
70
70
  function countEntryLines(text) {
71
- const t = String(text || '').trim();
72
- if (!t || t === '(no results)' || t === '(no valid ids)') return 0;
73
- return t.split('\n').filter((line) => line.trim() && !line.startsWith('[recall truncated') && !line.startsWith('note:')).length;
71
+ return resultLines(text).length;
74
72
  }
75
73
 
76
74
  function topN(text, n = 3, maxLen = 140) {
77
- const t = String(text || '').trim();
78
- if (!t || t === '(no results)') return [];
79
- return t.split('\n')
80
- .filter((line) => line.trim() && !line.startsWith('[recall truncated') && !line.startsWith('note:'))
75
+ return resultLines(text)
81
76
  .slice(0, n)
82
77
  .map((line) => (line.length > maxLen ? `${line.slice(0, maxLen - 1)}…` : line));
83
78
  }
84
79
 
85
- function evaluateCase(kase, { count, ms, isError }) {
80
+ // Full ordered list of result lines (no slice), used for topNContains rank
81
+ // scoring. Same filtering rule as countEntryLines/topN.
82
+ function resultLines(text) {
83
+ const t = String(text || '').trim();
84
+ if (!t || t === '(no results)' || t === '(no valid ids)') return [];
85
+ return t.split('\n').filter((line) => {
86
+ const trimmed = line.trim();
87
+ return trimmed
88
+ && !trimmed.startsWith('[recall truncated')
89
+ && !trimmed.startsWith('note:')
90
+ && !/^\[[^\]]+\]$/.test(trimmed);
91
+ });
92
+ }
93
+
94
+ // Score expect.topNContains: for each expected substring, find the 1-indexed
95
+ // rank of the first result line containing it (case-insensitive), over the
96
+ // FULL result list (not just the topN cutoff) so we can tell "missed
97
+ // entirely" from "present but ranked below cutoff". hit@N/MRR are then
98
+ // computed against the topN cutoff.
99
+ function scoreTopNContains(lines, substrings, n) {
100
+ const lower = lines.map((l) => l.toLowerCase());
101
+ const perSubstring = substrings.map((needle) => {
102
+ const hay = String(needle || '').toLowerCase();
103
+ let rank = null;
104
+ for (let i = 0; i < lower.length; i++) {
105
+ if (hay && lower[i].includes(hay)) { rank = i + 1; break; }
106
+ }
107
+ const hit = rank !== null && rank <= n;
108
+ return { needle, rank, hit, rr: hit ? 1 / rank : 0 };
109
+ });
110
+ const total = perSubstring.length || 1;
111
+ const hitAtN = perSubstring.reduce((s, p) => s + (p.hit ? 1 : 0), 0) / total;
112
+ const mrr = perSubstring.reduce((s, p) => s + p.rr, 0) / total;
113
+ return { perSubstring, hitAtN, mrr, n };
114
+ }
115
+
116
+ function evaluateCase(kase, { count, ms, isError }, quality) {
86
117
  const warnings = [];
87
118
  if (isError) warnings.push('error result');
88
119
  if (ms > WARN_LATENCY_MS) warnings.push(`latency ${ms}ms > ${WARN_LATENCY_MS}ms`);
89
120
  if ((kase.expect === 'browse' || kase.expect === 'idlookup') && count === 0) {
90
121
  warnings.push('0 results for a browse/id-lookup case (expected data present)');
91
122
  }
123
+ if (quality) {
124
+ for (const p of quality.perSubstring) {
125
+ if (!p.hit) {
126
+ warnings.push(p.rank === null
127
+ ? `topNContains miss: "${p.needle}" not found in results`
128
+ : `topNContains miss: "${p.needle}" found at rank ${p.rank} > topN ${quality.n}`);
129
+ }
130
+ }
131
+ }
92
132
  const status = warnings.length ? 'WARN' : 'PASS';
93
133
  return { status, warnings };
94
134
  }
@@ -109,7 +149,14 @@ async function runCase(memoryModule, kase) {
109
149
  const ms = Date.now() - started;
110
150
  const text = textOfResult(result);
111
151
  const count = countEntryLines(text);
112
- const evalResult = evaluateCase(kase, { count, ms, isError });
152
+ // expect stays a plain string for legacy cases ('results'/'browse'/'idlookup').
153
+ // New quality cases use an object: { kind?, topNContains: [...], topN? }.
154
+ const expectObj = kase.expect && typeof kase.expect === 'object' ? kase.expect : null;
155
+ const expectKind = expectObj ? expectObj.kind : kase.expect;
156
+ const topNContains = expectObj && Array.isArray(expectObj.topNContains) ? expectObj.topNContains : null;
157
+ const cutoffN = expectObj && Number.isInteger(expectObj.topN) ? expectObj.topN : 5;
158
+ const quality = topNContains ? scoreTopNContains(resultLines(text), topNContains, cutoffN) : null;
159
+ const evalResult = evaluateCase({ ...kase, expect: expectKind }, { count, ms, isError }, quality);
113
160
  return {
114
161
  id: kase.id,
115
162
  label: kase.label,
@@ -119,6 +166,7 @@ async function runCase(memoryModule, kase) {
119
166
  isError,
120
167
  errMsg,
121
168
  top3: topN(text, 3),
169
+ quality,
122
170
  status: evalResult.status,
123
171
  warnings: evalResult.warnings,
124
172
  };
@@ -128,6 +176,12 @@ function printCase(row) {
128
176
  process.stdout.write(`\n[${row.status}] ${row.id} — ${row.label}\n`);
129
177
  process.stdout.write(` params: ${JSON.stringify(row.args)}\n`);
130
178
  process.stdout.write(` results: ${row.count} latency: ${row.ms}ms${row.isError ? ` ERROR: ${row.errMsg}` : ''}\n`);
179
+ if (row.quality) {
180
+ process.stdout.write(` hit@${row.quality.n}: ${row.quality.hitAtN.toFixed(2)} MRR: ${row.quality.mrr.toFixed(2)}\n`);
181
+ for (const p of row.quality.perSubstring) {
182
+ process.stdout.write(` substr "${p.needle}" -> rank ${p.rank ?? 'none'}${p.hit ? '' : ' (miss)'}\n`);
183
+ }
184
+ }
131
185
  if (row.top3.length) {
132
186
  for (const line of row.top3) process.stdout.write(` - ${line}\n`);
133
187
  } else {
@@ -138,18 +192,27 @@ function printCase(row) {
138
192
 
139
193
  function printSummary(rows) {
140
194
  process.stdout.write('\n=== recall-bench summary ===\n');
141
- const widths = { id: 18, results: 8, ms: 8, status: 6 };
195
+ const widths = { id: 18, results: 8, ms: 8, status: 6, hit: 9, mrr: 7 };
142
196
  process.stdout.write(
143
- `${'case'.padEnd(widths.id)}${'results'.padEnd(widths.results)}${'ms'.padEnd(widths.ms)}${'status'.padEnd(widths.status)}notes\n`,
197
+ `${'case'.padEnd(widths.id)}${'results'.padEnd(widths.results)}${'ms'.padEnd(widths.ms)}${'status'.padEnd(widths.status)}${'hit@N'.padEnd(widths.hit)}${'MRR'.padEnd(widths.mrr)}notes\n`,
144
198
  );
145
199
  for (const r of rows) {
200
+ const hitStr = r.quality ? r.quality.hitAtN.toFixed(2) : '-';
201
+ const mrrStr = r.quality ? r.quality.mrr.toFixed(2) : '-';
146
202
  process.stdout.write(
147
- `${String(r.id).padEnd(widths.id)}${String(r.count).padEnd(widths.results)}${String(r.ms).padEnd(widths.ms)}${String(r.status).padEnd(widths.status)}${r.warnings.join('; ')}\n`,
203
+ `${String(r.id).padEnd(widths.id)}${String(r.count).padEnd(widths.results)}${String(r.ms).padEnd(widths.ms)}${String(r.status).padEnd(widths.status)}${hitStr.padEnd(widths.hit)}${mrrStr.padEnd(widths.mrr)}${r.warnings.join('; ')}\n`,
148
204
  );
149
205
  }
150
206
  const warnCount = rows.filter((r) => r.status === 'WARN').length;
151
207
  const totalMs = rows.reduce((s, r) => s + r.ms, 0);
152
- process.stdout.write(`\ncases=${rows.length} pass=${rows.length - warnCount} warn=${warnCount} total_latency=${totalMs}ms\n`);
208
+ const qualityRows = rows.filter((r) => r.quality);
209
+ let aggLine = '';
210
+ if (qualityRows.length) {
211
+ const aggHit = qualityRows.reduce((s, r) => s + r.quality.hitAtN, 0) / qualityRows.length;
212
+ const aggMrr = qualityRows.reduce((s, r) => s + r.quality.mrr, 0) / qualityRows.length;
213
+ aggLine = ` agg_hit@N=${aggHit.toFixed(3)} agg_MRR=${aggMrr.toFixed(3)} (${qualityRows.length} scored cases)`;
214
+ }
215
+ process.stdout.write(`\ncases=${rows.length} pass=${rows.length - warnCount} warn=${warnCount} total_latency=${totalMs}ms${aggLine}\n`);
153
216
  }
154
217
 
155
218
  async function main() {
@@ -0,0 +1,12 @@
1
+ [
2
+ { "id": "q-kr-recall-phrase", "label": "Korean distinctive phrase: persistent memory", "args": { "query": "영속 메모리" }, "expect": { "kind": "results", "topNContains": ["영속 메모리"], "topN": 5 } },
3
+ { "id": "q-en-recall-pipeline", "label": "English distinctive phrase: recall fast-track builder", "args": { "query": "buildRecallFastTrackQuery" }, "expect": { "kind": "results", "topNContains": ["buildRecallFastTrackQuery"], "topN": 5 } },
4
+ { "id": "q-cycle1-drain", "label": "cycle1 ECONNRESET topic", "args": { "query": "cycle1 ECONNRESET" }, "expect": { "kind": "results", "topNContains": ["cycle1", "ECONNRESET"], "topN": 5 } },
5
+ { "id": "q-recall-general", "label": "bare recall keyword", "args": { "query": "recall" }, "expect": { "kind": "results", "topNContains": ["recall"], "topN": 5 } },
6
+ { "id": "q-uc4-topic-kr", "label": "UC4 topic query with Korean qualifier", "args": { "query": "recall period last 개선", "limit": 8 }, "expect": { "kind": "results", "topNContains": ["개선"], "topN": 5 } },
7
+ { "id": "q-recall-improve-kr", "label": "cache improvement topic", "args": { "query": "캐시 개선" }, "expect": { "kind": "results", "topNContains": ["개선"], "topN": 5 } },
8
+ { "id": "q-battle-balance", "label": "ProjectAA topic", "args": { "query": "ProjectAA" }, "expect": { "kind": "results", "topNContains": ["ProjectAA"], "topN": 5 } },
9
+ { "id": "q-fanout-recall-battle", "label": "fan-out array query cache+ProjectAA", "args": { "query": ["캐시 개선", "ProjectAA"], "limit": 5 }, "expect": { "kind": "results", "topNContains": ["개선", "ProjectAA"], "topN": 10 } },
10
+ { "id": "q-short-2tok-cycle", "label": "short 2-token cycle1 query", "args": { "query": "cycle1 drain", "limit": 8 }, "expect": { "kind": "results", "topNContains": ["cycle1"], "topN": 3 } },
11
+ { "id": "q-recall-scope-project", "label": "project-scoped recall query", "args": { "query": "recall", "cwd": "C:\\Project\\mixdog" , "limit": 10 }, "expect": { "kind": "results", "topNContains": ["recall"], "topN": 5 } }
12
+ ]
@@ -1482,8 +1482,8 @@ if (!/repo-local|not web/i.test(codeGraphProps.mode?.description || '') || !/sou
1482
1482
  }
1483
1483
  const recallTool = MEMORY_TOOL_DEFS.find((tool) => tool.name === 'recall');
1484
1484
  const recallProps = recallTool?.inputSchema?.properties || {};
1485
- if (!/when in doubt, recall first/i.test(recallTool?.description || '') || !recallProps.id?.anyOf || !/Do not invent ids/i.test(recallProps.id?.description || '')) {
1486
- throw new Error('recall schema must preserve prior-context guidance and id lookup shape');
1485
+ if (!/Use only to check prior conversation or past events/i.test(recallTool?.description || '') || !recallProps.id?.anyOf || !/Do not invent ids/i.test(recallProps.id?.description || '')) {
1486
+ throw new Error('recall schema must preserve scoped prior-context guidance and id lookup shape');
1487
1487
  }
1488
1488
  if (!/array for independent fan-out/i.test(recallProps.query?.description || '') || !/Project pool selector/i.test(recallProps.projectScope?.description || '')) {
1489
1489
  throw new Error('recall schema must explain fan-out query and project scope filters');
@@ -8,13 +8,13 @@ Broad implementation agent.
8
8
  Bounded slices; smallest coherent change, not rewrite. Stop: unclear scope,
9
9
  growing blast radius, or Lead-only verification.
10
10
 
11
- EDIT-FIRST DISCIPLINE. Survey the slice with ONE batched read/grep round,
12
- then start patching the first bounded piece — broad scope means edit
13
- incrementally, not read exhaustively. NEVER "one more confirming read": if
14
- an anchor is plausible, the next call is `apply_patch`. Repeated read-only
15
- turns without an edit = stalling; any runtime reminder is your cue to patch
16
- the piece you understand or report blocked — a blocked report is a valid
17
- completion. Self-check comes AFTER edits; deep verification is Lead's.
11
+ EDIT-FIRST DISCIPLINE. Survey the slice with ONE batched read/grep round, then
12
+ patch the first bounded piece — broad scope means edit incrementally, not read
13
+ exhaustively. NEVER "one more confirming read": if an anchor is plausible, the
14
+ next call is `apply_patch`. Repeated read-only turns without an edit = stalling;
15
+ any runtime reminder is your cue to patch the piece you understand or report
16
+ blocked — a blocked report is a valid completion. Self-check comes AFTER edits;
17
+ deep verification is Lead's.
18
18
 
19
19
  Minimal checks + how-to-verify. Hand off outcome as fragments anchored to
20
20
  `file:line`; no narration, no report bloat.
@@ -7,6 +7,5 @@ toolSchemaProfile: read-write-search
7
7
 
8
8
  Scheduled task execution agent.
9
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.
10
+ Concise English handoff for Lead: outcome, action taken or skipped, notable
11
+ evidence, remaining risk. Nothing actionable use the skip protocol.
@@ -7,6 +7,5 @@ toolSchemaProfile: read-write-search
7
7
 
8
8
  Inbound webhook event analysis agent.
9
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.
10
+ Concise English handoff for Lead: event outcome, action taken or skipped,
11
+ notable evidence, remaining risk. Nothing actionable use the skip protocol.
@@ -7,15 +7,14 @@ Scoped implementation agent.
7
7
 
8
8
  Smallest scoped change; no drive-by cleanup. Stop when done/blocked.
9
9
 
10
- EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust
11
- them and patch. No anchor: locate with AT MOST 1-2 reads, then edit. NEVER
12
- "one more confirming read": if you know the file and the change, the next
13
- call is `apply_patch`. Repeated read-only turns without an edit = stalling;
14
- any runtime reminder is your cue to patch now or return blocked with what's
15
- missing — a blocked report is a valid completion. Confidence threshold is
16
- "plausible", not "proven"; self-check comes AFTER the edit, and Lead/Reviewer
17
- own final verification.
10
+ EDIT-FIRST DISCIPLINE. Brief anchors (`file:line`) are pre-verified — trust and
11
+ patch. No anchor: locate with AT MOST 1-2 reads, then edit. NEVER "one more
12
+ confirming read": if you know the file and the change, the next call is
13
+ `apply_patch`. Repeated read-only turns without an edit = stalling; any runtime
14
+ reminder is your cue to patch now or return blocked with what's missing — a
15
+ blocked report is a valid completion. Threshold is "plausible", not "proven";
16
+ self-check comes AFTER the edit, and Lead/Reviewer own final verification.
18
17
 
19
- Hand off outcome as fragments anchored to `file:line`; no narration, no
20
- report bloat.
18
+ Hand off outcome as fragments anchored to `file:line`; no narration, no report
19
+ bloat.
21
20
 
package/src/app.mjs CHANGED
@@ -4,8 +4,12 @@ import { fileURLToPath } from 'node:url';
4
4
  import { performance } from 'node:perf_hooks';
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const VALUE_OPTIONS = new Set(['--provider', '--model']);
7
+ // --workflow is consumed (with its value) but ignored on the headless role
8
+ // path: workflow selection is Lead-session-scoped. Accepting it keeps a
9
+ // mistakenly passed `--workflow X` from leaking "X" into the role message.
10
+ const VALUE_OPTIONS = new Set(['--provider', '--model', '--effort', '--workflow']);
8
11
  const FLAG_OPTIONS = new Set(['--readonly', '--help', '-h', '--plain', '--react', '--remote', '--onboarding']);
12
+ const HEADLESS_FLAG_OPTIONS = new Set(['--fast']);
9
13
  const HEADLESS_ROLE_ALIASES = new Map([
10
14
  ['explorer', 'explore'],
11
15
  ['explore', 'explore'],
@@ -52,6 +56,7 @@ function unknownOption(argv) {
52
56
  continue;
53
57
  }
54
58
  if (FLAG_OPTIONS.has(arg)) continue;
59
+ if (HEADLESS_FLAG_OPTIONS.has(arg)) continue;
55
60
  if (String(arg || '').startsWith('-')) return arg;
56
61
  }
57
62
  return null;
@@ -66,6 +71,7 @@ function positionalArgs(argv) {
66
71
  continue;
67
72
  }
68
73
  if (FLAG_OPTIONS.has(arg)) continue;
74
+ if (HEADLESS_FLAG_OPTIONS.has(arg)) continue;
69
75
  if (String(arg || '').startsWith('-')) continue;
70
76
  out.push(arg);
71
77
  }
@@ -109,12 +115,15 @@ export async function run(argv = []) {
109
115
 
110
116
  const provIdx = argv.indexOf('--provider');
111
117
  const modelIdx = argv.indexOf('--model');
118
+ const effortIdx = argv.indexOf('--effort');
112
119
  const toolMode = argv.includes('--readonly') ? 'readonly' : 'full';
113
120
  const remote = argv.includes('--remote');
114
121
  const forceOnboarding = argv.includes('--onboarding');
115
122
  const opts = {
116
123
  provider: provIdx >= 0 ? argv[provIdx + 1] : undefined,
117
124
  model: modelIdx >= 0 ? argv[modelIdx + 1] : undefined,
125
+ effort: effortIdx >= 0 ? argv[effortIdx + 1] : undefined,
126
+ fast: argv.includes('--fast'),
118
127
  toolMode,
119
128
  remote,
120
129
  forceOnboarding,
@@ -150,6 +159,8 @@ export async function run(argv = []) {
150
159
  message: headless.message,
151
160
  provider: opts.provider,
152
161
  model: opts.model,
162
+ effort: opts.effort,
163
+ fast: opts.fast,
153
164
  cwd: process.cwd(),
154
165
  });
155
166
  }
@@ -25,7 +25,7 @@ function makeTag(agent) {
25
25
  .replace(/^-+|-+$/g, '');
26
26
  }
27
27
 
28
- export function buildHeadlessSpawnArgs({ agent, tag, cwd, message, provider, model } = {}) {
28
+ export function buildHeadlessSpawnArgs({ agent, tag, cwd, message, provider, model, effort, fast } = {}) {
29
29
  const spawnArgs = {
30
30
  type: 'spawn',
31
31
  agent: clean(agent),
@@ -35,6 +35,8 @@ export function buildHeadlessSpawnArgs({ agent, tag, cwd, message, provider, mod
35
35
  };
36
36
  if (clean(provider)) spawnArgs.provider = clean(provider);
37
37
  if (clean(model)) spawnArgs.model = clean(model);
38
+ if (clean(effort)) spawnArgs.effort = clean(effort);
39
+ if (fast === true) spawnArgs.fast = true;
38
40
  return spawnArgs;
39
41
  }
40
42
 
@@ -56,6 +58,8 @@ export async function runHeadlessRole({
56
58
  message,
57
59
  provider,
58
60
  model,
61
+ effort,
62
+ fast,
59
63
  cwd = process.cwd(),
60
64
  write = (text) => stdout.write(text),
61
65
  writeErr = (text) => stderr.write(text),
@@ -86,6 +90,8 @@ export async function runHeadlessRole({
86
90
  message: cleanMessage,
87
91
  provider,
88
92
  model,
93
+ effort,
94
+ fast,
89
95
  });
90
96
 
91
97
  let taskId = null;
@@ -54,13 +54,12 @@ import {
54
54
  } from './runtime/agent/orchestrator/providers/custom-tool-wire.mjs';
55
55
  import {
56
56
  channelSetup,
57
- deleteChannel,
58
57
  deleteSchedule,
59
58
  deleteWebhook,
60
59
  forgetDiscordToken,
61
60
  forgetTelegramToken,
62
61
  forgetWebhookAuthtoken,
63
- saveChannel,
62
+ setChannel,
64
63
  saveDiscordToken,
65
64
  saveTelegramToken,
66
65
  saveSchedule,
@@ -138,6 +137,7 @@ import {
138
137
  normalizeAutoClearConfig,
139
138
  resolveAutoClearIdleMs,
140
139
  autoClearIdleMsForProvider,
140
+ autoClearProviderDefaults,
141
141
  normalizeCompactionConfig,
142
142
  moduleEnabled,
143
143
  setModuleEnabledInConfig,
@@ -177,6 +177,7 @@ import {
177
177
  agentPresetSlot,
178
178
  normalizeAgentId,
179
179
  normalizeWorkflowId,
180
+ DEFAULT_WORKFLOW_ID,
180
181
  createWorkflowHelpers,
181
182
  normalizeSearchProviderId,
182
183
  isDefaultSearchRouteConfig,
@@ -638,7 +639,7 @@ export async function createMixdogSessionRuntime({
638
639
  let updateProcessState = { phase: 'idle', version: null, error: null };
639
640
 
640
641
  function autoUpdateEnabled() {
641
- return config?.update?.auto === true;
642
+ return config?.update?.auto !== false;
642
643
  }
643
644
 
644
645
  async function checkForUpdateInternal({ force = false } = {}) {
@@ -681,8 +682,8 @@ export async function createMixdogSessionRuntime({
681
682
  // Non-blocking boot hook: fires after the runtime object below is fully
682
683
  // constructed (setTimeout(0) defers past the synchronous return), so a
683
684
  // slow/hanging registry request or npm install can never delay session
684
- // boot. Auto-update defaults to OFF (config.update.auto is undefined until
685
- // the user opts in via setAutoUpdate(true)); a failed check or install is
685
+ // boot. Auto-update defaults to ON unless config.update.auto is explicitly
686
+ // false; a failed check or install is
686
687
  // swallowed — getUpdateStatus()/getUpdateSettings() are the only surfaces,
687
688
  // there is no push notice channel from runtime -> TUI today.
688
689
  // force:true — always hit the registry at boot (the 24h disk cache went
@@ -1662,6 +1663,7 @@ export async function createMixdogSessionRuntime({
1662
1663
  hasOwn,
1663
1664
  normalizeAutoClearConfig,
1664
1665
  autoClearIdleMsForProvider,
1666
+ autoClearProviderDefaults,
1665
1667
  normalizeCompactionConfig,
1666
1668
  normalizeCompactTypeSetting,
1667
1669
  normalizeSystemShellConfig,
@@ -1749,10 +1751,14 @@ export async function createMixdogSessionRuntime({
1749
1751
  },
1750
1752
  get workflow() {
1751
1753
  const dataDir = cfgMod.getPluginData?.() || STANDALONE_DATA_DIR;
1754
+ const active = activeWorkflowSummary(config, dataDir);
1752
1755
  if (session?.workflow && typeof session.workflow === 'object') {
1753
- return workflowSummary(session.workflow);
1756
+ const current = workflowSummary(session.workflow);
1757
+ return current?.id && active?.id && current.id !== active.id
1758
+ ? { ...active, currentSession: current, appliedToCurrentSession: false }
1759
+ : active;
1754
1760
  }
1755
- return activeWorkflowSummary(config, dataDir);
1761
+ return active;
1756
1762
  },
1757
1763
  get outputStyle() {
1758
1764
  return getOutputStyleStatusCached().current;
@@ -2003,13 +2009,8 @@ export async function createMixdogSessionRuntime({
2003
2009
  remoteStateListeners.add(listener);
2004
2010
  return () => remoteStateListeners.delete(listener);
2005
2011
  },
2006
- saveChannel(entry) {
2007
- const result = saveChannel(entry);
2008
- reloadChannelsSoon();
2009
- return result;
2010
- },
2011
- deleteChannel(name) {
2012
- const result = deleteChannel(name);
2012
+ setChannel(entry) {
2013
+ const result = setChannel(entry);
2013
2014
  reloadChannelsSoon();
2014
2015
  return result;
2015
2016
  },
@@ -8,24 +8,21 @@ keep-coding-instructions: true
8
8
  # Output Style
9
9
 
10
10
  Mixdog default — the most detailed style, but always summary-form, never
11
- essay-form. Depth comes from picking the right facts, not from explaining more.
11
+ essay-form. Depth comes from picking the right facts, not explaining more.
12
12
 
13
13
  Content
14
14
  - Lead with the outcome in one short sentence, then only the detail that
15
15
  matters: what changed, key evidence (paths, commands, errors, verification).
16
- - Summarize at the concept level: name the problem/behavior and the direction,
17
- not the code path. Cite a symbol or path only as an anchor, never as the
18
- explanation.
16
+ - Summarize at the concept level: name the problem/behavior and direction, not
17
+ the code path. Cite a symbol/path only as an anchor, never as the explanation.
19
18
  - Compress by cutting content (filler, hedging, connective padding, restated
20
19
  facts), not by clipping grammar: keep natural, complete sentences in the
21
20
  user's language — never telegraph-style stub endings. Technical terms and
22
21
  code stay exact.
23
22
  - State conclusions, not reasoning: no mechanism walkthroughs, background, or
24
- chained qualifiers unless the user asks. One decisive fact beats three
25
- hedges.
26
- - Say each point once: problem and fix in ONE compact statement, not a
27
- restated pair. Prefer fewer, denser items over full coverage of every
28
- nuance.
23
+ chained qualifiers unless asked. One decisive fact beats three hedges.
24
+ - Say each point once: problem and fix in ONE compact statement, not a restated
25
+ pair. Prefer fewer, denser items over covering every nuance.
29
26
  - Size budget: roughly TWICE the Simple style — per point about 2 rendered
30
27
  lines, whole report ~10–15 lines. Spend the extra room on evidence and
31
28
  context Simple would drop, not on longer sentences.
@@ -39,14 +36,13 @@ Content
39
36
  - Keep paths, commands, symbols, API names, code, and exact errors verbatim.
40
37
 
41
38
  Layout (hard rules)
42
- - One bullet or numbered item = one idea, at most 2 rendered lines including
43
- its sub-bullet. If it needs more, cut the detail — do not add lines.
44
- - Open each list item with a short **bold key point**, then the brief
45
- elaboration — never bury the point mid-sentence.
46
- - Insert a blank line between numbered items, and between any list items that
47
- run past one line (loose list). Never emit a wall of consecutive multi-line
48
- items.
49
- - Keep paragraphs to ~3 lines max, with a blank line between paragraphs,
50
- lists, and code blocks.
39
+ - One bullet or numbered item = one idea, at most 2 rendered lines including its
40
+ sub-bullet. If it needs more, cut the detail — do not add lines.
41
+ - Open each item with a short **bold key point**, then the brief elaboration —
42
+ never bury the point mid-sentence.
43
+ - Insert a blank line between numbered items, and between any list items running
44
+ past one line (loose list). Never emit a wall of consecutive multi-line items.
45
+ - Keep paragraphs to ~3 lines max, with a blank line between paragraphs, lists,
46
+ and code blocks.
51
47
  - Nest at most one sub-level; deeper detail means you are over-explaining.
52
48
  - Never name this style unless asked.
@@ -11,16 +11,15 @@ Minimal — a very short summary: one or two sentences, nothing more.
11
11
 
12
12
  - Summarize only the net result in one short sentence; add a second short
13
13
  sentence only if a second fact (verification, blocker) genuinely needs it.
14
- Never cram unrelated facts into one run-on line just to stay at one sentence.
14
+ Never cram unrelated facts into a run-on just to stay at one sentence.
15
15
  - Size budget: roughly HALF the Simple style — 1–2 plain sentences, ~2–3
16
16
  rendered lines at most, however large the task was.
17
17
  - Compress by cutting content, not grammar: natural, complete sentences only.
18
18
  Concept-level only — never walk through code or mechanisms.
19
- - Summarize, never itemize: do not describe which files changed or how they were
20
- edited. State only what the change accomplishes.
19
+ - Summarize, never itemize: do not describe which files changed or how. State
20
+ only what the change accomplishes.
21
21
  - No headings, bullets, numbered lists, labels, or sections — plain sentences
22
- only. This holds even when the request says "report" or "summary"; keep it to
23
- one or two sentences regardless.
22
+ only, even when the request says "report" or "summary".
24
23
  - Preferred pattern: `<target> 변경되었습니다. <verification> 통과 완료입니다.`
25
24
  - If verification was not run, say the change is done and verification was not
26
25
  run.
@@ -12,8 +12,8 @@ Practical concise — outcome-first handoffs for coding work: summarize the
12
12
  result, do not narrate or explain the change.
13
13
 
14
14
  - Open with the outcome in one sentence: done, blocked, or awaiting a decision.
15
- - Summarize at the concept level: name the behavior and the direction, not the
16
- code path. Cite a symbol/path only as an anchor, never as the explanation.
15
+ - Summarize at the concept level: name the behavior and direction, not the code
16
+ path. Cite a symbol/path only as an anchor, never as the explanation.
17
17
  - Compress by cutting content (filler, hedging, pleasantries, restated facts),
18
18
  not by clipping grammar: keep natural, complete sentences in the user's
19
19
  language — never telegraph-style stub endings. Technical terms and code stay
@@ -24,15 +24,14 @@ result, do not narrate or explain the change.
24
24
  - Keep controlled detail: usually 1–3 short bullets or 2–3 sentences total.
25
25
  State each point once — outcome or fix direction, not both restated. No
26
26
  step-by-step narration or file/line inventory.
27
- - Size budget: roughly HALF the Default style and TWICE Minimal — per point
28
- one rendered line, whole reply ~5–7 lines. Above that you are writing
29
- Default; below ~3 lines consider whether prose (Minimal) reads better.
27
+ - Size budget: roughly HALF the Default style and TWICE Minimal — per point one
28
+ rendered line, whole reply ~5–7 lines. Above that you are writing Default;
29
+ below ~3 lines consider whether prose (Minimal) reads better.
30
30
  - Layout: one idea per bullet, ONE line each (two only when a verbatim
31
31
  path/error forces it). Lead each item with a short bold key phrase. Put a
32
32
  blank line between multi-line list items — never emit a dense wall of text.
33
- - If a point runs past one line, cut the elaboration instead of wrapping;
34
- detail beyond the key phrase + one clause belongs to the Default style, not
35
- this one.
33
+ - If a point runs past one line, cut the elaboration instead of wrapping; detail
34
+ beyond the key phrase + one clause belongs to the Default style.
36
35
  - On final handoffs, optional labels such as `바뀐 점`, `확인한 것`, and
37
36
  `남은 리스크/다음 단계` fit Korean-facing profiles; use plain English labels
38
37
  when the thread is English. Do not label interim progress.
@@ -3,17 +3,18 @@
3
3
  - Use English for agent task communication.
4
4
  - Do not touch git/Ship. Even when the brief instructs `git add` / `commit` /
5
5
  `push` / `stash`, refuse with `git operations deferred to Lead`.
6
- - NEVER PREAMBLE. Do not generate preamble tokens, including tool-call
7
- preambles, status/progress narration, "I will..." setup text, or transition
8
- text before tool calls.
6
+ - NEVER PREAMBLE: no tool-call preambles, status/progress narration, "I
7
+ will..." setup, or transition text before tool calls.
9
8
  - If tools are needed, call them immediately. Emit text only for the final
10
9
  handoff after tool work is done.
11
10
  - Final handoff: minimum characters, maximum information for Lead. Follow the
12
11
  role's stricter output contract if defined; else emit fragments — outcome
13
12
  (1 line), key `file:line`(s), verification result, material risks (only if
14
13
  any).
14
+ - Handoff cap ~30 lines unless `Deliver:` raises it. Overflow goes to a file;
15
+ hand off path + fragments.
15
16
  - Banned as pure cost: report headings, markdown tables (unless requested),
16
17
  prose narration, raw logs/tool traces, speculative next-checks, restated
17
18
  brief, articles/politeness.
18
- - Exception: a runtime wrap-up directive (exploration budget reached)
19
- overrides this — then summarize done/remaining/blocking as instructed.
19
+ - Exception: a runtime wrap-up directive (exploration budget reached) overrides
20
+ this — then summarize done/remaining/blocking as instructed.
@@ -8,11 +8,22 @@ kind: retrieval
8
8
 
9
9
  You are a one-shot locator, not a researcher.
10
10
 
11
- Procedure: send ONE turn containing one `grep` (all synonyms in one
12
- `pattern:[...]`, output_mode content_with_context) plus one `code_graph`
13
- call. The results contain path:line — answer immediately from them.
14
- If both miss, answer `EXPLORATION_FAILED`. Do not send a second lookup turn.
11
+ Find code anchors fast. Batch independent lookups in turn 1: one `grep`
12
+ with all literal strings/errors/identifier guesses, plus `code_graph`/`find`
13
+ or `glob` when useful.
14
+
15
+ Prefer user-visible strings and identifiers. `dist`/generated hits are leads,
16
+ not answers: trace them back to source. On miss, derive new tokens from
17
+ returned paths/names or widen scope; do not repeat synonyms. NEVER issue
18
+ serial single-tool turns: every turn after the first must batch ALL remaining
19
+ candidate lookups in one shot.
20
+
21
+ Budget: 4 tool turns. The FIRST credible `path:line` ends the search — answer
22
+ with what you have; more anchors are not more value. Spend remaining turns
23
+ only while you have zero anchors, and buy coverage with wider batches, never
24
+ with extra serial turns. On ANY iteration warning, answer immediately with
25
+ best anchors or `EXPLORATION_FAILED`.
15
26
 
16
27
  Answer format, nothing else:
17
- - up to 5 lines of `path:line — symbol/name — short reason` (append `?` if weak)
28
+ - up to 5 lines: `path:line — symbol/name — short reason` (append `?` if weak)
18
29
  - or `EXPLORATION_FAILED`
@@ -1,11 +1,11 @@
1
1
  # General
2
2
 
3
3
  - Omit direct names, honorifics, headings, and labels in preambles.
4
- - Preambles are optional: use them only when they add user-visible value, keep
5
- them to one short sentence, and skip routine lookup narration.
4
+ - Preambles are optional: use only when they add user-visible value, keep to
5
+ one short sentence, skip routine lookup narration.
6
6
  - Destructive/hard-to-reverse actions require explicit confirmation.
7
7
  - Never push, build, or deploy without an explicit user request.
8
8
  Implementation approval is not deploy approval.
9
- - Rather than deferring checks or work to the user, proactively handle whatever
10
- you can first, and propose only the parts that need a decision in a
11
- consultative tone ("Shall we proceed this way?").
9
+ - Proactively handle what you can first rather than deferring to the user;
10
+ propose only the parts needing a decision, consultatively ("Shall we
11
+ proceed this way?").
@@ -3,9 +3,12 @@
3
3
  - Brief = one-line fragments `Goal:` `Anchors:` `Allow/Forbid:` `Deliver:`
4
4
  `Verify:` (+`Stop:` heavy-worker). No role-known rules, background, or
5
5
  motivation — minimum characters, maximum information.
6
- - Full brief only on fresh spawn or `respawned: true` (dead-tag send =
7
- cold session; re-supply anchors). Live-session follow-ups = delta only;
8
- never restate Goal/rules.
6
+ - `Deliver:` states output size/shape (e.g. "fragments <=15 lines", "verdict
7
+ + top-3 risks", "detail to file, path only"). Never request a long report
8
+ in the handoff itself.
9
+ - Full brief only on fresh spawn or `respawned: true` (dead-tag send = cold
10
+ session; re-supply anchors). Live-session follow-ups = delta only; never
11
+ restate Goal/rules.
9
12
  - Never `send` mid-run; batch all adjustments into ONE follow-up after
10
13
  completion. Interrupt only to cancel.
11
14
  - All agent communication in English.
@@ -1,6 +1,8 @@
1
1
  # Lead Tool Use
2
2
 
3
- - Lead owns repo-local shell work: run git/build/test/verification commands via
4
- `shell` directly; do not delegate them to agents.
5
- - Use the current project/workspace selected by the session. Only change the work project when the user asks for a different project or a tool call explicitly needs another project root.
6
- - Use `agent` for scoped implementation, research, review, and debugging, not for git commit/push/stash or Ship.
3
+ - Lead owns repo-local shell work: run git/build/test/verification via `shell`
4
+ directly; do not delegate to agents.
5
+ - Use the session's current project/workspace. Change the work project only
6
+ when the user asks for another project or a tool call needs another root.
7
+ - Use `agent` for scoped implementation, research, review, and debugging — not
8
+ for git commit/push/stash or Ship.