mixdog 0.9.149 → 0.9.151

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 (113) hide show
  1. package/README.md +28 -12
  2. package/package.json +1 -1
  3. package/src/headless-exec.mjs +40 -4
  4. package/src/headless-exec.test.mjs +116 -1
  5. package/src/rules/lead/01-general.md +3 -2
  6. package/src/rules/shared/00-general.md +1 -0
  7. package/src/rules/shared/10-tool-workflow.md +11 -12
  8. package/src/rules/shared/30-exploration.md +4 -3
  9. package/src/rules/shared/60-verification.md +4 -3
  10. package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -2
  11. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +1 -4
  12. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +0 -18
  13. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +0 -3
  14. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1
  15. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +4 -72
  16. package/src/runtime/agent/orchestrator/session/approval-mode.mjs +0 -8
  17. package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +2 -4
  18. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +18 -33
  19. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +21 -42
  20. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +20 -125
  21. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +50 -8
  22. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +31 -1
  23. package/src/runtime/agent/orchestrator/session/store/serialize.mjs +27 -4
  24. package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +60 -0
  25. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +78 -0
  26. package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +78 -0
  27. package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +43 -0
  28. package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +63 -0
  29. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +8 -0
  30. package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +29 -10
  31. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -7
  32. package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +56 -0
  33. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -2
  34. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +50 -23
  35. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +50 -1
  36. package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +31 -0
  37. package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +106 -0
  38. package/src/runtime/agent/orchestrator/tools/builtin/lib/absolute-glob-expand.mjs +129 -0
  39. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +2 -1
  40. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +218 -0
  41. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +9 -2
  42. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +6 -0
  43. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +50 -2
  44. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +7 -1
  45. package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +69 -0
  46. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +33 -6
  47. package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +71 -0
  48. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +6 -1
  49. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +7 -0
  50. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +54 -8
  51. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +90 -51
  52. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +56 -0
  53. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +25 -0
  54. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +16 -0
  55. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +30 -1
  56. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +60 -5
  57. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +54 -1
  58. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +49 -24
  59. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +1 -0
  60. package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +153 -0
  61. package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -1
  62. package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +55 -0
  63. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +54 -3
  64. package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +16 -0
  65. package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +33 -1
  66. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
  67. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +9 -1
  68. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +44 -10
  69. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +57 -5
  70. package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +141 -0
  71. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +7 -3
  72. package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +57 -0
  73. package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +81 -0
  74. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +42 -23
  75. package/src/runtime/media/renditions.mjs +7 -1
  76. package/src/runtime/memory/index.mjs +2 -4
  77. package/src/runtime/memory/lib/pg/process.mjs +29 -2
  78. package/src/runtime/memory/lib/pg/process.test.mjs +36 -0
  79. package/src/runtime/memory/lib/pg/supervisor.mjs +5 -1
  80. package/src/runtime/memory/lib/query-handlers.mjs +13 -19
  81. package/src/runtime/memory/lib/recall-format.mjs +13 -31
  82. package/src/runtime/memory/tool-defs.mjs +2 -2
  83. package/src/runtime/shared/memory-snapshot.mjs +57 -4
  84. package/src/runtime/shared/pristine-execution-contract.json +3 -0
  85. package/src/runtime/shared/pristine-execution.mjs +6 -3
  86. package/src/runtime/shared/tool-surface.mjs +6 -0
  87. package/src/runtime/shared/tool-surface.test.mjs +8 -0
  88. package/src/session-runtime/lifecycle-api.mjs +1 -0
  89. package/src/session-runtime/session-turn-api.mjs +5 -0
  90. package/src/standalone/agent-dispatch-broker.mjs +25 -80
  91. package/src/standalone/channel-worker-heartbeat.test.mjs +30 -0
  92. package/src/standalone/channel-worker.mjs +31 -1
  93. package/src/standalone/daemon.mjs +9 -9
  94. package/src/standalone/memory-runtime-proxy.mjs +5 -2
  95. package/src/standalone/session-client.mjs +23 -3
  96. package/src/standalone/session-runtime-host-health.test.mjs +36 -0
  97. package/src/standalone/session-runtime-host.mjs +26 -76
  98. package/src/standalone/session-runtime-worker.mjs +97 -0
  99. package/src/tui/App.jsx +3 -2
  100. package/src/tui/app/use-global-key-input.mjs +9 -0
  101. package/src/tui/app/use-mouse-input.mjs +77 -14
  102. package/src/tui/components/Spinner.jsx +1 -1
  103. package/src/tui/dist/index.mjs +83 -23
  104. package/src/tui/lib/voice-setup.mjs +3 -2
  105. package/src/tui/session/completion-card-restore.test.mjs +18 -0
  106. package/src/tui/session/session-api-ext.mjs +2 -1
  107. package/src/tui/session/session-api.mjs +8 -1
  108. package/src/tui/session/task-wait-submit.test.mjs +80 -0
  109. package/src/tui/session/turn.mjs +58 -6
  110. package/src/tui/spinner-verbs.mjs +1 -0
  111. package/src/workflows/default/WORKFLOW.md +12 -19
  112. package/src/workflows/headless/WORKFLOW.md +11 -0
  113. package/src/workflows/solo/WORKFLOW.md +6 -11
package/README.md CHANGED
@@ -150,18 +150,34 @@ npm run dev
150
150
 
151
151
  ## Terminal-Bench 2.1
152
152
 
153
- Controlled single-model runs on the same 89 tasks produced:
154
-
155
- - **78/89** with Claude Opus 5 vs Claude Code's **77/89**
156
- - **75/89** with GPT-5.6 Sol xhigh, matching Codex CLI
157
- - **1.43×** faster vs Claude Code and **1.27×** faster vs Codex CLI
158
- - **40–47%** smaller median final context
159
- - **29%** lower priced cost vs Claude Code and at least **39.7%** lower vs
160
- Codex CLI
161
-
162
- These are self-reported single runs (`k=1`, 2026-08), not leaderboard
163
- submissions. Raw artifacts, commands, comparison charts, and metric scripts
164
- live under [`benchmarks/terminal-bench-2.1/`](benchmarks/terminal-bench-2.1/).
153
+ Head-to-head runs against other agent CLIs on the same 89 tasks, holding the
154
+ model fixed and changing only the harness. Every run uses the official Harbor
155
+ verifier, fast mode off, a 272k context window, and zero retries.
156
+
157
+ **GPT-5.6 Sol xhigh Mixdog vs Codex CLI**
158
+
159
+ - Score: **86.5%** (385/445) vs Codex CLI's **84.3%** (75/89)
160
+ - **1.11×** faster per trial (339s vs 378s)
161
+ - **42%** lower priced cost ($0.641 vs $1.096 per trial)
162
+ - **45%** smaller median final context (18.5k vs 33.5k tokens)
163
+
164
+ **Claude Opus 5 — Mixdog vs Claude Code**
165
+
166
+ - Score: **79/89** vs Claude Code's **77/89**
167
+ - **1.15×** faster and **19%** lower priced cost ($104.29 vs $129.21 per run)
168
+ - **28%** smaller median final context (27.6k vs 38.2k tokens)
169
+
170
+ Measurement: the Mixdog Sol run follows the same protocol the official
171
+ Terminal-Bench leaderboard requires — all 89 tasks repeated five times
172
+ (`k=5`, 445 trials), scored by the official Harbor verifier. The Codex CLI
173
+ baseline and both Opus-side runs are single passes (`k=1`, 89 trials each).
174
+
175
+ The official leaderboard is not accepting community submissions, so every run
176
+ here ships its raw artifacts instead — Harbor verdicts, official verifier
177
+ output, pinned task checksums, and the usage snapshots behind every cost
178
+ figure — alongside the harness, presets, and metric scripts that recompute
179
+ each number above:
180
+ [`benchmarks/terminal-bench-2.1/`](benchmarks/terminal-bench-2.1/).
165
181
 
166
182
  ## Data and configuration
167
183
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mixdog",
3
- "version": "0.9.149",
3
+ "version": "0.9.151",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Standalone mixdog coding-agent CLI/TUI workspace.",
@@ -14,6 +14,7 @@ import {
14
14
  import { hasActiveBackgroundTasks } from './runtime/shared/background-tasks.mjs';
15
15
  import { installProcessSignalCleanup } from './runtime/shared/process-shutdown.mjs';
16
16
  import { stopStandaloneMemoryRuntimesForProcess } from './standalone/memory-runtime-proxy.mjs';
17
+ import { shutdownDaemonForRuntimeRoot } from './standalone/session-client.mjs';
17
18
  import { applyUsageDelta, createSessionStats } from './ui/session-stats.mjs';
18
19
 
19
20
  function clean(value) {
@@ -558,6 +559,7 @@ export async function runHeadlessExec({
558
559
  boundaryFactory = createPristineExecutionBoundary,
559
560
  runtimeFactory = null,
560
561
  memoryRuntimeCleanup = stopStandaloneMemoryRuntimesForProcess,
562
+ daemonRuntimeCleanup = shutdownDaemonForRuntimeRoot,
561
563
  hasActiveTasks = hasActiveBackgroundTasks,
562
564
  installSignalCleanupFn = installProcessSignalCleanup,
563
565
  } = {}) {
@@ -616,17 +618,28 @@ export async function runHeadlessExec({
616
618
  } catch (error) {
617
619
  errors.push(error);
618
620
  }
619
- let memoryCleanupFailed = false;
621
+ let resourceCleanupFailed = false;
622
+ if (boundary?.runtimeRoot) {
623
+ try {
624
+ await daemonRuntimeCleanup(boundary.runtimeRoot, {
625
+ waitForExit: true,
626
+ timeoutMs: 8_000,
627
+ });
628
+ } catch (error) {
629
+ resourceCleanupFailed = true;
630
+ errors.push(error);
631
+ }
632
+ }
620
633
  if (boundary) {
621
634
  try {
622
635
  await memoryRuntimeCleanup({ waitForExit: true, timeoutMs: 10_000 });
623
636
  } catch (error) {
624
- memoryCleanupFailed = true;
637
+ resourceCleanupFailed = true;
625
638
  errors.push(error);
626
639
  }
627
640
  }
628
641
  try {
629
- const cleanupResult = boundary?.cleanup(memoryCleanupFailed
642
+ const cleanupResult = boundary?.cleanup(resourceCleanupFailed
630
643
  ? { preserveRoot: true }
631
644
  : { tolerateRootRemovalFailure: true });
632
645
  if (cleanupResult?.rootRemovalError) {
@@ -672,7 +685,10 @@ export async function runHeadlessExec({
672
685
  approvalMode: 'implicit',
673
686
  disallowDelegation: true,
674
687
  autoWakeCompletions: false,
675
- initialConfig: boundary.loadConfig(),
688
+ initialConfig: {
689
+ ...boundary.loadConfig(),
690
+ workflow: { active: 'headless' },
691
+ },
676
692
  });
677
693
  if (lifecycle && !clean(runtime?.id) && typeof runtime?.reserveSessionId === 'function') {
678
694
  runtime.reserveSessionId(lifecycle.threadId);
@@ -689,6 +705,25 @@ export async function runHeadlessExec({
689
705
  },
690
706
  );
691
707
  }
708
+ // Rewrite the usage snapshot after every model response, not only on the
709
+ // way out. A session killed mid-run — agent timeout, SIGKILL — never
710
+ // reaches the exit path, and used to leave no usage document at all while
711
+ // its token spend was already real. The file is a few hundred bytes and
712
+ // the write is atomic, so the cost is negligible and a live run stays
713
+ // readable from outside.
714
+ const flushUsageDocument = () => {
715
+ try {
716
+ writeUsageDocument(
717
+ usageLogPath,
718
+ stats,
719
+ runtime,
720
+ lifecycle?.toolCallCount || 0,
721
+ observedModels,
722
+ );
723
+ } catch {
724
+ // Telemetry must never break the session; the exit path reports.
725
+ }
726
+ };
692
727
  const askOptions = {
693
728
  onTextReset: () => true,
694
729
  onUsageDelta: (delta) => {
@@ -696,6 +731,7 @@ export async function runHeadlessExec({
696
731
  if (observedModel) observedModels.add(observedModel);
697
732
  applyUsageDelta(stats, delta);
698
733
  lifecycle?.onUsageDelta(delta);
734
+ flushUsageDocument();
699
735
  },
700
736
  ...(lifecycle ? {
701
737
  onProviderSendStarted: () => lifecycle.onProviderSendStarted(),
@@ -62,6 +62,7 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
62
62
  let activeChecks = 0;
63
63
  let boundaryCleaned = false;
64
64
  let runtimeClosed = false;
65
+ const daemonCleanupCalls = [];
65
66
  const cleanupOrder = [];
66
67
  try {
67
68
  const code = await runHeadlessExec({
@@ -75,6 +76,7 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
75
76
  write: (text) => output.push(text),
76
77
  writeErr: (text) => errors.push(text),
77
78
  boundaryFactory: () => ({
79
+ runtimeRoot: join(root, 'runtime-root'),
78
80
  loadConfig: () => ({ providers: { 'openai-oauth': { enabled: true } } }),
79
81
  cleanup: () => {
80
82
  boundaryCleaned = true;
@@ -106,6 +108,10 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
106
108
  memoryRuntimeCleanup: async () => {
107
109
  cleanupOrder.push('memory');
108
110
  },
111
+ daemonRuntimeCleanup: async (runtimeRoot, options) => {
112
+ daemonCleanupCalls.push({ runtimeRoot, options });
113
+ cleanupOrder.push('daemon');
114
+ },
109
115
  hasActiveTasks: (scope) => {
110
116
  activeScopes.push(scope);
111
117
  activeChecks += 1;
@@ -119,6 +125,7 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
119
125
  assert.deepEqual(errors, []);
120
126
  assert.equal(runtimeOptions[0].approvalMode, 'implicit');
121
127
  assert.equal(runtimeOptions[0].disallowDelegation, true);
128
+ assert.deepEqual(runtimeOptions[0].initialConfig.workflow, { active: 'headless' });
122
129
  assert.equal(runtimeOptions[0].autoWakeCompletions, false);
123
130
  assert.equal(runtimeOptions[0].toolMode, 'full');
124
131
  assert.deepEqual(activeScopes[0], {
@@ -127,7 +134,11 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
127
134
  });
128
135
  assert.equal(boundaryCleaned, true);
129
136
  assert.equal(runtimeClosed, true);
130
- assert.deepEqual(cleanupOrder, ['runtime', 'memory', 'boundary']);
137
+ assert.deepEqual(cleanupOrder, ['runtime', 'daemon', 'memory', 'boundary']);
138
+ assert.deepEqual(daemonCleanupCalls, [{
139
+ runtimeRoot: join(root, 'runtime-root'),
140
+ options: { waitForExit: true, timeoutMs: 8_000 },
141
+ }]);
131
142
  const usage = JSON.parse(readFileSync(usageLogPath, 'utf8'));
132
143
  assert.deepEqual(usage.sessions[0].models, ['gpt-test', 'gpt-fallback']);
133
144
  assert.deepEqual(usage.totals, {
@@ -142,6 +153,98 @@ test('headless exec runs one implicit-approval session and waits for tracked tas
142
153
  }
143
154
  });
144
155
 
156
+ test('headless exec preserves the pristine root when isolated daemon shutdown fails', async () => {
157
+ const errors = [];
158
+ let cleanupOptions = null;
159
+ const code = await runHeadlessExec({
160
+ message: 'done',
161
+ provider: 'openai-oauth',
162
+ model: 'gpt-test',
163
+ usageLogPath: '',
164
+ write() {},
165
+ writeErr: (text) => errors.push(text),
166
+ boundaryFactory: () => ({
167
+ runtimeRoot: '/isolated/runtime',
168
+ loadConfig: () => ({ providers: { 'openai-oauth': { enabled: true } } }),
169
+ cleanup: (options) => { cleanupOptions = options; },
170
+ }),
171
+ runtimeFactory: async () => ({
172
+ id: 'sess_cleanup_failure',
173
+ model: 'gpt-test',
174
+ clientHostPid: 123,
175
+ async ask() { return { result: { content: 'done' } }; },
176
+ async close() {},
177
+ }),
178
+ daemonRuntimeCleanup: async () => { throw new Error('daemon stuck'); },
179
+ memoryRuntimeCleanup: async () => {},
180
+ hasActiveTasks: () => false,
181
+ installSignalCleanupFn: () => ({ uninstall() {} }),
182
+ });
183
+
184
+ assert.equal(code, 1);
185
+ assert.deepEqual(cleanupOptions, { preserveRoot: true });
186
+ assert.ok(errors.some((line) => line.includes('shutdown failed: daemon stuck')));
187
+ });
188
+
189
+ test('headless exec flushes the usage snapshot mid-session, before any exit path', async () => {
190
+ const root = mkdtempSync(join(tmpdir(), 'mixdog-headless-usage-flush-test-'));
191
+ const usageLogPath = join(root, 'usage.json');
192
+ // What a killed run would have left behind: read the file while `ask` is
193
+ // still running. Before the per-response flush this was always absent, so
194
+ // an agent-timeout trial reported no token spend at all.
195
+ let midSession = null;
196
+ try {
197
+ const code = await runHeadlessExec({
198
+ message: 'fix it',
199
+ provider: 'openai-oauth',
200
+ model: 'gpt-test',
201
+ effort: 'high',
202
+ fast: true,
203
+ usageLogPath,
204
+ idlePollMs: 1,
205
+ write: () => {},
206
+ writeErr: () => {},
207
+ boundaryFactory: () => ({
208
+ loadConfig: () => ({ providers: { 'openai-oauth': { enabled: true } } }),
209
+ cleanup: () => {},
210
+ }),
211
+ runtimeFactory: async () => ({
212
+ id: 'sess_flush_test',
213
+ model: 'gpt-test',
214
+ clientHostPid: 123,
215
+ ask: async (_prompt, options) => {
216
+ options.onUsageDelta({
217
+ deltaInput: 9,
218
+ deltaCachedRead: 4,
219
+ deltaCacheWrite: 2,
220
+ deltaOutput: 6,
221
+ });
222
+ try {
223
+ midSession = JSON.parse(readFileSync(usageLogPath, 'utf8'));
224
+ } catch (error) {
225
+ midSession = { error: error?.message ?? String(error) };
226
+ }
227
+ return { result: { content: 'done' } };
228
+ },
229
+ close: async () => {},
230
+ }),
231
+ hasActiveTasks: () => false,
232
+ installSignalCleanupFn: () => ({ uninstall() {} }),
233
+ });
234
+
235
+ assert.equal(code, 0);
236
+ assert.deepEqual(midSession?.totals, {
237
+ inputTokens: 9,
238
+ cacheTokens: 4,
239
+ cacheWriteTokens: 2,
240
+ outputTokens: 6,
241
+ toolCallCountApprox: 0,
242
+ });
243
+ } finally {
244
+ rmSync(root, { recursive: true, force: true });
245
+ }
246
+ });
247
+
145
248
  test('headless exec answers an arrived completion and exits without waiting on live work', async () => {
146
249
  const output = [];
147
250
  const errors = [];
@@ -427,3 +530,15 @@ test('--json is accepted for exec and rejected for the interactive command', ()
427
530
  assert.equal(interactive.kind, 'error');
428
531
  assert.equal(interactive.error, 'option --json is only supported for mixdog exec');
429
532
  });
533
+
534
+ test('headless exec rejects workflow selection', () => {
535
+ const invocation = classifyCliInvocation([
536
+ 'exec',
537
+ '--provider', 'openai-oauth',
538
+ '--model', 'gpt-test',
539
+ '--workflow', 'solo',
540
+ 'fix it',
541
+ ]);
542
+ assert.equal(invocation.kind, 'error');
543
+ assert.equal(invocation.error, 'option --workflow is not supported for mixdog exec');
544
+ });
@@ -10,8 +10,9 @@
10
10
  recoverability.
11
11
  - Mid-task: replacement supersedes; addition folds in; status gets a brief
12
12
  answer while work continues. After compaction, resume the summary.
13
- - Periodic task reports stay in-turn: after each update, call `task wait` for
14
- the requested interval again until the task settles or the request changes.
13
+ - Periodic task reports stay in-turn: when `task wait` returns still-running,
14
+ write the user-facing report first, then call `task wait` for the next
15
+ interval; repeat until the task settles or the request changes.
15
16
  - Auto-compact owns context management: never propose stopping work to the
16
17
  user and never stop on your own judgment; resume and continue the work
17
18
  after every compaction.
@@ -2,3 +2,4 @@
2
2
 
3
3
  - When an internal Mixdog rule conflicts with the user's latest explicit
4
4
  request, follow the user's request.
5
+ - Drive the user's request yourself.
@@ -1,16 +1,16 @@
1
1
  # Tool Workflow
2
2
 
3
- - Determine the required outcome and its gaps requirements are not evidence —
4
- gather only what is missing, act, then verify the affected facets.
5
- - Investigate, build, and verify only what the requested outcome requires, at
6
- the level it requires; internal and framework guarantees are trusted.
7
- - Minimize tool turns through maximal useful parallelism. Cost is counted in
8
- rounds, not calls: a batch is one round, so a call-count saving never
9
- justifies a worse-routed call.
10
- - In each round, issue every necessary non-overlapping call whose inputs are
11
- already known; defer a call only when its target or arguments require an
12
- earlier result. Respect tool/schema limits, never omit required fanout, and
13
- apply one analysis to many targets as one parameterized call when supported.
3
+ - Determine the required outcome and missing evidence; requirements are not
4
+ evidence. Trust internal and framework guarantees.
5
+ - Before exploration or implementation, consult prior work, current external
6
+ information, or repository state only when needed to choose the next action.
7
+ Start with the source most likely to decide it; consult another only if the
8
+ result leaves the decision unresolved.
9
+ - Minimize tool turns by batching only calls that are independently necessary
10
+ before the batch begins. A call whose necessity or scope can change after
11
+ another result waits for that result.
12
+ - Respect tool/schema limits, never omit required fanout, and apply one analysis
13
+ to many targets as one parameterized call when supported.
14
14
  - Route each evidence facet once to its primary owner, preferring the operation
15
15
  that directly returns the evidence needed for the next decision. A summary,
16
16
  overview, or enumeration is not a prerequisite to an operation whose complete
@@ -26,7 +26,6 @@
26
26
  - Mine each returned result fully before opening the next round; a follow-up is
27
27
  valid only for evidence a result omitted, invalidated, or newly made
28
28
  necessary.
29
- - Evidence that determines the answer, edit, or deliverable ends retrieval.
30
29
  - Treat failure as new evidence and re-enter that loop only for the affected
31
30
  facets. Do not abandon a viable approach after one failure or leave the
32
31
  required deliverable half-finished. Report a blocker when no deterministic
@@ -2,6 +2,8 @@
2
2
 
3
3
  - Use read-only means for inspection; never mutate to clear an obstacle or
4
4
  unexpected state. Preserve evidence before a required mutation can destroy it.
5
+ - Stop exploring once sufficient evidence determines the next action required
6
+ by the request.
5
7
  - Ownership is exclusive: each evidence type has one owner;
6
8
  a successful owner result closes that facet.
7
9
  - Route the missing evidence to its primary owner:
@@ -19,9 +21,8 @@
19
21
  - Use a path locator only when the owner's required target is unknown. Paths
20
22
  reachable by expanding an environment variable or the home directory are
21
23
  resolved locations, not unknowns.
22
- - Enumerate sibling directories or same-kind files with one wildcard call
23
- (`glob`, or `read` with a glob for content sampling), never a
24
- directory-by-directory `list` walk or one `read` per file.
24
+ - Enumerate sibling directories or same-kind files with one `glob` call, never
25
+ a directory-by-directory `list` walk or one `read` per file.
25
26
  - Treat supplied target locations as resolved; access them directly without
26
27
  locator searches. Within the current project, pass project-relative paths and
27
28
  omit optional scopes equal to its root; explicit paths may be outside cwd
@@ -1,9 +1,10 @@
1
1
  # Verification
2
2
 
3
3
  - Enter Verification only after all planned work is complete.
4
- - Run only checks that directly cover the changed behavior or essential
5
- invariants; use an umbrella suite only when the user explicitly requests it
6
- or a documented project or release process requires it.
4
+ - Verify the essential behaviors and invariants required to complete the
5
+ requested work.
6
+ - Use an umbrella suite only when explicitly requested or required by the
7
+ documented project or release process.
7
8
  - Blocking checks cover only essential integrity, security, compatibility, and
8
9
  buildability invariants. Treat mutable behavior, UX, exact text, snapshots,
9
10
  and implementation shape as advisory specifications; update them when the
@@ -90,7 +90,6 @@ test('Codex cache key and every wire session identity use the same UUIDv7', () =
90
90
  poolKey: session.id,
91
91
  model: 'gpt-5.6-sol',
92
92
  handshake: true,
93
- useResponsesLite: true,
94
93
  sendOpts: {
95
94
  codexSessionId: sessionId,
96
95
  codexThreadId: sessionId,
@@ -102,7 +101,6 @@ test('Codex cache key and every wire session identity use the same UUIDv7', () =
102
101
  assert.equal(prewarmHeaders['thread-id'], sessionId);
103
102
  assert.equal(prewarmHeaders['x-client-request-id'], sessionId);
104
103
  assert.equal('x-codex-installation-id' in prewarmHeaders, false);
105
- assert.equal('x-openai-internal-codex-responses-lite' in prewarmHeaders, false);
106
104
  const prewarmMetadata = JSON.parse(prewarmHeaders['x-codex-turn-metadata']);
107
105
  assert.equal(prewarmMetadata.request_kind, 'prewarm');
108
106
  assert.equal(prewarmMetadata.turn_id, '');
@@ -172,7 +172,7 @@ export function _metadataTrace(metadata) {
172
172
  }
173
173
 
174
174
  // The WebSocket handshake carries compatibility identity and routing fields.
175
- // Installation and Responses Lite data stay in per-request client_metadata.
175
+ // Installation data stays in per-request client_metadata.
176
176
  export function _codexWsCompatibilityHeaders(context = {}) {
177
177
  const metadata = _codexMetadataBase(null, context);
178
178
  const headers = {};
@@ -211,9 +211,6 @@ export function _withCodexWsClientMetadata(frame, entry, enabled, context = {})
211
211
  const metadata = {
212
212
  ...base,
213
213
  ...(frame.client_metadata && typeof frame.client_metadata === 'object' ? frame.client_metadata : {}),
214
- ...(context?.useResponsesLite === true
215
- ? { ws_request_header_x_openai_internal_codex_responses_lite: 'true' }
216
- : {}),
217
214
  'x-codex-ws-stream-request-start-ms': String(Date.now()),
218
215
  };
219
216
  if (entry && typeof entry === 'object') {
@@ -7,23 +7,6 @@
7
7
  * _displayCodexModel for existing importers.
8
8
  */
9
9
 
10
- const CODEX_RESPONSES_LITE_CAPABILITY = Object.freeze({
11
- 'gpt-5.6-sol': true,
12
- 'gpt-5.6-terra': true,
13
- 'gpt-5.6-luna': true,
14
- 'gpt-5.5': false,
15
- 'gpt-5.4': false,
16
- 'gpt-5.4-mini': false,
17
- 'gpt-5.2': false,
18
- 'codex-auto-review': false,
19
- });
20
-
21
- export function _codexUsesResponsesLite(id, modelInfo = null) {
22
- const explicit = modelInfo?.useResponsesLite ?? modelInfo?.use_responses_lite;
23
- if (typeof explicit === 'boolean') return explicit;
24
- return CODEX_RESPONSES_LITE_CAPABILITY[String(id || '').trim()] === true;
25
- }
26
-
27
10
  // OAuth catalog returns dated ids (gpt-5.4-mini-2026-03-17). Strip the trailing
28
11
  // -YYYY-MM-DD to get the version alias (gpt-5.4-mini). Unknown shapes pass
29
12
  // through unchanged.
@@ -101,7 +84,6 @@ export function _normalizeCodexModel(m) {
101
84
  reasoningLevels: (m?.supported_reasoning_levels || []).map(r => r.effort),
102
85
  supportVerbosity: m?.support_verbosity === true,
103
86
  defaultVerbosity: m?.default_verbosity || null,
104
- useResponsesLite: m?.use_responses_lite === true,
105
87
  supportsReasoningSummaries: m?.supports_reasoning_summaries === true,
106
88
  serviceTiers,
107
89
  defaultServiceTier: m?.default_service_tier || null,
@@ -251,9 +251,6 @@ export async function sendViaHttpSse({
251
251
  const statelessConversation = opts?.statelessConversation === true
252
252
  || _envFlag('MIXDOG_OAI_STATELESS_HTTP', false);
253
253
  const headers = _buildOpenAIHttpFallbackHeaders({ auth, cacheKey, statelessConversation });
254
- if (auth?.type !== 'openai-direct' && body?.input?.[0]?.type === 'additional_tools') {
255
- headers['x-openai-internal-codex-responses-lite'] = 'true';
256
- }
257
254
  const fetchStartedAt = Date.now();
258
255
  const responsesUrl = auth?.type === 'openai-direct'
259
256
  ? OPENAI_DIRECT_RESPONSES_URL
@@ -584,7 +584,6 @@ export async function sendViaWebSocket({
584
584
  sendOpts,
585
585
  model: useModel,
586
586
  serviceTier: body?.service_tier || '',
587
- useResponsesLite: body?.input?.[0]?.type === 'additional_tools',
588
587
  };
589
588
  const codexHandshakeHeaders = useCodexWsClientMetadata
590
589
  ? _codexWsCompatibilityHeaders({ ...codexMetadataContext, handshake: true })
@@ -71,7 +71,6 @@ import {
71
71
  _compareVersion,
72
72
  _isMainCodexFamily,
73
73
  _markLatestCodex,
74
- _codexUsesResponsesLite,
75
74
  } from './openai-codex-model.mjs';
76
75
 
77
76
  // Public test/integration entry retained alongside the transport module export.
@@ -253,53 +252,8 @@ function _codexModelSupportsReasoningSummaries(id) {
253
252
  return true;
254
253
  }
255
254
 
256
- function _codexModelUsesResponsesLite(id, opts = {}) {
257
- if (typeof opts.useResponsesLite === 'boolean') return opts.useResponsesLite;
258
- const override = String(process.env.MIXDOG_OAI_RESPONSES_LITE || '').trim().toLowerCase();
259
- if (['1', 'true', 'yes', 'on'].includes(override)) return true;
260
- if (['0', 'false', 'no', 'off'].includes(override)) return false;
261
- const info = _findCachedCodexModel(id);
262
- return _codexUsesResponsesLite(id, info);
263
- }
264
-
265
- function _responsesLiteTools(tools) {
266
- const out = [];
267
- const functions = [];
268
- let functionsIndex = null;
269
- for (const tool of Array.isArray(tools) ? tools : []) {
270
- if (tool?.type === 'function' || tool?.type === 'custom') {
271
- if (functionsIndex == null) functionsIndex = out.length;
272
- functions.push(tool);
273
- continue;
274
- }
275
- if (tool?.type === 'namespace' && tool?.name === 'functions') {
276
- if (functionsIndex == null) functionsIndex = out.length;
277
- if (Array.isArray(tool.tools)) functions.push(...tool.tools);
278
- continue;
279
- }
280
- out.push(tool);
281
- }
282
- if (functions.length) {
283
- out.splice(functionsIndex, 0, {
284
- type: 'namespace',
285
- name: 'functions',
286
- description: '',
287
- tools: functions,
288
- });
289
- }
290
- return out;
291
- }
292
-
293
255
  export function buildCodexStartupPrewarmBody(body) {
294
- const input = Array.isArray(body?.input) ? body.input : [];
295
- const stableInput = [];
296
- if (input[0]?.type === 'additional_tools' && input[0]?.role === 'developer') {
297
- stableInput.push(input[0]);
298
- if (input[1]?.type === 'message' && input[1]?.role === 'developer') {
299
- stableInput.push(input[1]);
300
- }
301
- }
302
- return { ...body, input: stableInput, generate: false };
256
+ return { ...body, input: [], generate: false };
303
257
  }
304
258
 
305
259
  // Effort normalization: `ultra` collapses to
@@ -340,8 +294,6 @@ export function buildRequestBody(messages, model, tools, sendOpts) {
340
294
  .join('\n\n---\n\n');
341
295
  const opts = sendOpts || {};
342
296
  const promptCacheProvider = opts.promptCacheProvider || 'openai-oauth';
343
- const useResponsesLite = promptCacheProvider === 'openai-oauth'
344
- && _codexModelUsesResponsesLite(model, opts);
345
297
  // Recovery-only encrypted-reasoning replay is DEFAULT ON for the OAuth
346
298
  // backend (validated 2026-08-11: smoke wire parity on normal chains +
347
299
  // live full-frame acceptance + full-run A/B). The per-socket policy in
@@ -421,7 +373,6 @@ export function buildRequestBody(messages, model, tools, sendOpts) {
421
373
  reasoning: {
422
374
  effort: _normalizeReasoningEffort(opts.effort),
423
375
  ...(supportsReasoningSummary ? { summary: 'auto' } : {}),
424
- ...(useResponsesLite ? { context: 'all_turns' } : {}),
425
376
  },
426
377
  store: process.env.MIXDOG_OAI_STORE === 'true' ? true : false,
427
378
  stream: true,
@@ -461,25 +412,6 @@ export function buildRequestBody(messages, model, tools, sendOpts) {
461
412
  const toolsList = (functionTools.length || nativeTools.length)
462
413
  ? [...nativeTools, ...functionTools]
463
414
  : null;
464
- const liteTools = useResponsesLite ? _responsesLiteTools(toolsList || []) : null;
465
- const wireInput = useResponsesLite
466
- ? [
467
- {
468
- type: 'additional_tools',
469
- role: 'developer',
470
- tools: liteTools,
471
- },
472
- ...(instructions
473
- ? [{
474
- type: 'message',
475
- role: 'developer',
476
- content: [{ type: 'input_text', text: instructions }],
477
- }]
478
- : []),
479
- ...input,
480
- ]
481
- : input;
482
- if (useResponsesLite) body.parallel_tool_calls = false;
483
415
  const promptCacheLane = opts.promptCacheLane || resolveProviderPromptCacheLane(promptCacheProvider, opts);
484
416
  const promptCacheKey = buildStableProviderPromptCacheKey(promptCacheProvider, opts, {
485
417
  model,
@@ -506,9 +438,9 @@ export function buildRequestBody(messages, model, tools, sendOpts) {
506
438
  // fast set it.
507
439
  const ordered = {
508
440
  model: body.model,
509
- ...(!useResponsesLite ? { instructions: body.instructions } : {}),
510
- input: wireInput,
511
- ...(!useResponsesLite && toolsList ? { tools: toolsList } : {}),
441
+ instructions: body.instructions,
442
+ input: body.input,
443
+ ...(toolsList ? { tools: toolsList } : {}),
512
444
  tool_choice: body.tool_choice,
513
445
  parallel_tool_calls: body.parallel_tool_calls,
514
446
  reasoning: body.reasoning,
@@ -1,9 +1 @@
1
1
  export const IMPLICIT_APPROVAL_MODE = 'implicit';
2
- export const IMPLICIT_APPROVAL_CONTEXT =
3
- 'Non-interactive execution: treat the initial user request as the approved plan and proceed without requesting approval.';
4
-
5
- export function workflowContextForApprovalMode(workflowContext, approvalMode) {
6
- if (approvalMode !== IMPLICIT_APPROVAL_MODE) return workflowContext || null;
7
- const workflow = typeof workflowContext === 'string' ? workflowContext.trim() : '';
8
- return [workflow, IMPLICIT_APPROVAL_CONTEXT].filter(Boolean).join('\n\n');
9
- }
@@ -1,6 +1,5 @@
1
1
  import { estimateTokens } from '../context-utils.mjs';
2
2
 
3
- const CONVERSATION_LINE_CHARS = 800;
4
3
  const TOOL_OUTCOME_CHARS = 80;
5
4
 
6
5
  function textOf(m) {
@@ -339,7 +338,7 @@ export function conversationLinesFromMemoryText(text) {
339
338
  .replace(/\s+#\d+\s*$/, '')
340
339
  .trim();
341
340
  if (body && body !== '.' && body !== '…') {
342
- rows.push(`${current.role}: ${body.slice(0, CONVERSATION_LINE_CHARS)}`);
341
+ rows.push(`${current.role}: ${body}`);
343
342
  }
344
343
  current = null;
345
344
  };
@@ -372,8 +371,7 @@ export function excludeTailFromConversation(lines, tailMessages) {
372
371
  if (m?.role !== 'user' && m?.role !== 'assistant') continue;
373
372
  const body = String(typeof m.content === 'string' ? m.content : '')
374
373
  .replace(/\s+/g, ' ')
375
- .trim()
376
- .slice(0, CONVERSATION_LINE_CHARS);
374
+ .trim();
377
375
  if (body) tails.push(body);
378
376
  }
379
377
  if (!tails.length) return Array.isArray(lines) ? lines : [];