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.
- package/README.md +28 -12
- package/package.json +1 -1
- package/src/headless-exec.mjs +40 -4
- package/src/headless-exec.test.mjs +116 -1
- package/src/rules/lead/01-general.md +3 -2
- package/src/rules/shared/00-general.md +1 -0
- package/src/rules/shared/10-tool-workflow.md +11 -12
- package/src/rules/shared/30-exploration.md +4 -3
- package/src/rules/shared/60-verification.md +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +1 -4
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +0 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +4 -72
- package/src/runtime/agent/orchestrator/session/approval-mode.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/compact/handoff.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +18 -33
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +21 -42
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +20 -125
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +50 -8
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +27 -4
- package/src/runtime/agent/orchestrator/session/store/serialize.test.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +78 -0
- package/src/runtime/agent/orchestrator/session/task-wait-control.test.mjs +78 -0
- package/src/runtime/agent/orchestrator/tools/builtin/absence-absorption.test.mjs +43 -0
- package/src/runtime/agent/orchestrator/tools/builtin/absolute-glob-expand.test.mjs +63 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +29 -10
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/enoent-outside-project.test.mjs +56 -0
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +50 -23
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +50 -1
- package/src/runtime/agent/orchestrator/tools/builtin/grep-output-budget.test.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/grep-single-file-rescue.test.mjs +106 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/absolute-glob-expand.mjs +129 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +218 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +50 -2
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/noise-dir-visibility.test.mjs +69 -0
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +33 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-glob-survey.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +54 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +90 -51
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.test.mjs +56 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +25 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.test.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +30 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +60 -5
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +54 -1
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +49 -24
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/write-symlink.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +11 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/aggregate-anchor-relocation.test.mjs +55 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +54 -3
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.test.mjs +33 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +44 -10
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +57 -5
- package/src/runtime/agent/orchestrator/tools/patch/patch-symlink.test.mjs +141 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/patch/v4a-pure-move.test.mjs +57 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-section-coalesce.test.mjs +81 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +42 -23
- package/src/runtime/media/renditions.mjs +7 -1
- package/src/runtime/memory/index.mjs +2 -4
- package/src/runtime/memory/lib/pg/process.mjs +29 -2
- package/src/runtime/memory/lib/pg/process.test.mjs +36 -0
- package/src/runtime/memory/lib/pg/supervisor.mjs +5 -1
- package/src/runtime/memory/lib/query-handlers.mjs +13 -19
- package/src/runtime/memory/lib/recall-format.mjs +13 -31
- package/src/runtime/memory/tool-defs.mjs +2 -2
- package/src/runtime/shared/memory-snapshot.mjs +57 -4
- package/src/runtime/shared/pristine-execution-contract.json +3 -0
- package/src/runtime/shared/pristine-execution.mjs +6 -3
- package/src/runtime/shared/tool-surface.mjs +6 -0
- package/src/runtime/shared/tool-surface.test.mjs +8 -0
- package/src/session-runtime/lifecycle-api.mjs +1 -0
- package/src/session-runtime/session-turn-api.mjs +5 -0
- package/src/standalone/agent-dispatch-broker.mjs +25 -80
- package/src/standalone/channel-worker-heartbeat.test.mjs +30 -0
- package/src/standalone/channel-worker.mjs +31 -1
- package/src/standalone/daemon.mjs +9 -9
- package/src/standalone/memory-runtime-proxy.mjs +5 -2
- package/src/standalone/session-client.mjs +23 -3
- package/src/standalone/session-runtime-host-health.test.mjs +36 -0
- package/src/standalone/session-runtime-host.mjs +26 -76
- package/src/standalone/session-runtime-worker.mjs +97 -0
- package/src/tui/App.jsx +3 -2
- package/src/tui/app/use-global-key-input.mjs +9 -0
- package/src/tui/app/use-mouse-input.mjs +77 -14
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/dist/index.mjs +83 -23
- package/src/tui/lib/voice-setup.mjs +3 -2
- package/src/tui/session/completion-card-restore.test.mjs +18 -0
- package/src/tui/session/session-api-ext.mjs +2 -1
- package/src/tui/session/session-api.mjs +8 -1
- package/src/tui/session/task-wait-submit.test.mjs +80 -0
- package/src/tui/session/turn.mjs +58 -6
- package/src/tui/spinner-verbs.mjs +1 -0
- package/src/workflows/default/WORKFLOW.md +12 -19
- package/src/workflows/headless/WORKFLOW.md +11 -0
- 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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
- **
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
package/src/headless-exec.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
637
|
+
resourceCleanupFailed = true;
|
|
625
638
|
errors.push(error);
|
|
626
639
|
}
|
|
627
640
|
}
|
|
628
641
|
try {
|
|
629
|
-
const cleanupResult = boundary?.cleanup(
|
|
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:
|
|
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:
|
|
14
|
-
the
|
|
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.
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# Tool Workflow
|
|
2
2
|
|
|
3
|
-
- Determine the required outcome and
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
23
|
-
|
|
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
|
-
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
510
|
-
input:
|
|
511
|
-
...(
|
|
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
|
|
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 : [];
|