mixdog 0.9.47 → 0.9.50
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 +6 -6
- package/package.json +19 -9
- package/scripts/agent-parallel-smoke.mjs +4 -1
- package/scripts/agent-route-batch-test.mjs +40 -0
- package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
- package/scripts/code-graph-description-contract.mjs +185 -0
- package/scripts/code-graph-disk-hit-test.mjs +55 -0
- package/scripts/code-graph-dispatch-test.mjs +96 -0
- package/scripts/compact-pressure-test.mjs +40 -0
- package/scripts/deferred-tool-loading-test.mjs +233 -0
- package/scripts/embedding-worker-exit-test.mjs +76 -0
- package/scripts/execution-completion-dedup-test.mjs +48 -0
- package/scripts/explore-prompt-policy-test.mjs +88 -3
- package/scripts/live-worker-smoke.mjs +68 -16
- package/scripts/memory-core-input-test.mjs +33 -13
- package/scripts/native-edit-wire-test.mjs +152 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
- package/scripts/patch-binary-cache-test.mjs +181 -0
- package/scripts/prompt-immediate-render-test.mjs +89 -0
- package/scripts/provider-toolcall-test.mjs +280 -15
- package/scripts/shell-failure-diagnostics-test.mjs +211 -0
- package/scripts/smoke-loop.mjs +9 -3
- package/scripts/smoke.mjs +5 -106
- package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
- package/scripts/streaming-tail-window-test.mjs +29 -0
- package/scripts/tool-failures.mjs +21 -3
- package/scripts/tool-smoke.mjs +263 -38
- package/scripts/tool-tui-presentation-test.mjs +17 -1
- package/scripts/tui-perf-run.ps1 +26 -0
- package/scripts/tui-transcript-perf-test.mjs +7 -1
- package/scripts/verify-release-assets-test.mjs +281 -0
- package/scripts/verify-release-assets.mjs +293 -0
- package/scripts/windows-hide-spawn-options-test.mjs +19 -0
- package/src/cli.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
- package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
- package/src/runtime/memory/lib/embedding-provider.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
- package/src/runtime/memory/tool-defs.mjs +1 -2
- package/src/session-runtime/context-status.mjs +25 -16
- package/src/session-runtime/model-route-api.mjs +2 -0
- package/src/session-runtime/runtime-core.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +4 -1
- package/src/session-runtime/tool-catalog.mjs +113 -19
- package/src/session-runtime/tool-defs.mjs +1 -0
- package/src/standalone/agent-tool/helpers.mjs +25 -6
- package/src/standalone/agent-tool.mjs +75 -41
- package/src/tui/App.jsx +4 -0
- package/src/tui/app/input-parsers.mjs +8 -9
- package/src/tui/components/Markdown.jsx +6 -1
- package/src/tui/components/Message.jsx +11 -2
- package/src/tui/components/PromptInput.jsx +19 -21
- package/src/tui/components/Spinner.jsx +4 -4
- package/src/tui/components/StatusLine.jsx +6 -6
- package/src/tui/components/TranscriptItem.jsx +2 -2
- package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
- package/src/tui/dist/index.mjs +130 -45
- package/src/tui/engine/agent-job-feed.mjs +21 -2
- package/src/tui/engine/turn.mjs +12 -1
- package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +20 -0
- package/src/ui/statusline.mjs +5 -5
- package/src/vendor/statusline/src/gateway/session-routes.mjs +22 -10
package/scripts/smoke.mjs
CHANGED
|
@@ -19,120 +19,19 @@ function runNode(args, label, options = {}) {
|
|
|
19
19
|
return child;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
const isolatedStatuslineEnv = {
|
|
24
|
-
MIXDOG_DATA_DIR: resolve(root, '.mixdog-smoke-data-empty'),
|
|
25
|
-
MIXDOG_HOME: resolve(root, '.mixdog-smoke-home-empty'),
|
|
26
|
-
MIXDOG_CONFIG_DIR: resolve(root, '.mixdog-smoke-config-empty'),
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
if (!child.stdout.includes('standalone mixdog CLI/TUI coding agent')) {
|
|
30
|
-
process.stderr.write(`unexpected help output:\n${child.stdout}`);
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
22
|
+
runNode(['src/cli.mjs', '--help'], 'help smoke');
|
|
33
23
|
|
|
34
24
|
runNode(['--input-type=module', '-e', `
|
|
35
25
|
const mod = await import('./src/tui/dist/index.mjs');
|
|
36
26
|
if (typeof mod.runTui !== 'function') throw new Error('runTui export missing');
|
|
37
27
|
`], 'tui bundle import smoke');
|
|
38
28
|
|
|
39
|
-
runNode(['--
|
|
40
|
-
|
|
41
|
-
const { renderStatusline } = await import('./src/ui/statusline.mjs');
|
|
42
|
-
const line = await renderStatusline({
|
|
43
|
-
provider: 'openai',
|
|
44
|
-
model: 'gpt-5.5',
|
|
45
|
-
contextWindow: 1000000,
|
|
46
|
-
stats: { currentContextTokens: 999 },
|
|
47
|
-
});
|
|
48
|
-
if (/▓/.test(line) || !line.includes('░') || !line.includes('0.1%')) throw new Error('sub-1% context bar should stay empty: ' + JSON.stringify(line));
|
|
49
|
-
`], 'statusline sub-percent context smoke', { env: isolatedStatuslineEnv });
|
|
50
|
-
|
|
51
|
-
runNode(['--input-type=module', '-e', `
|
|
52
|
-
process.stdout.columns = 120;
|
|
53
|
-
const { renderStatusline } = await import('./src/ui/statusline.mjs');
|
|
54
|
-
const line = await renderStatusline({
|
|
55
|
-
provider: 'openai',
|
|
56
|
-
model: 'gpt-5.5',
|
|
57
|
-
contextWindow: 950000,
|
|
58
|
-
rawContextWindow: 1000000,
|
|
59
|
-
stats: { currentContextTokens: 900000 },
|
|
60
|
-
});
|
|
61
|
-
if (!line.includes('94%') || line.includes('100%')) throw new Error('statusline context% should use effective compact capacity, not the 90% auto-compact trigger: ' + JSON.stringify(line));
|
|
62
|
-
`], 'statusline compact-capacity context smoke', { env: isolatedStatuslineEnv });
|
|
63
|
-
|
|
64
|
-
runNode(['--input-type=module', '-e', `
|
|
65
|
-
process.stdout.columns = 120;
|
|
66
|
-
const { renderStatusline } = await import('./src/ui/statusline.mjs');
|
|
67
|
-
const line = await renderStatusline({
|
|
68
|
-
provider: 'openai',
|
|
69
|
-
model: 'gpt-5.5',
|
|
70
|
-
contextWindow: 950000,
|
|
71
|
-
stats: { currentContextTokens: 350000, currentContextSource: 'estimated' },
|
|
72
|
-
});
|
|
73
|
-
if (line.includes('37%') || !line.includes('0%')) throw new Error('statusline must not show local estimated context as session usage: ' + JSON.stringify(line));
|
|
74
|
-
`], 'statusline estimated-context isolation smoke', { env: isolatedStatuslineEnv });
|
|
75
|
-
|
|
76
|
-
runNode(['--input-type=module', '-e', `
|
|
77
|
-
process.stdout.columns = 120;
|
|
78
|
-
const { renderStatusline } = await import('./src/ui/statusline.mjs');
|
|
79
|
-
const line = await renderStatusline({
|
|
80
|
-
provider: 'openai',
|
|
81
|
-
model: 'gpt-5.5',
|
|
82
|
-
contextWindow: 950000,
|
|
83
|
-
stats: { currentContextTokens: 360000, currentContextSource: 'post_compact_estimate' },
|
|
84
|
-
});
|
|
85
|
-
if (!line.includes('37%')) throw new Error('statusline must show post-compact estimated context after stale API usage: ' + JSON.stringify(line));
|
|
86
|
-
`], 'statusline post-compact context smoke', { env: isolatedStatuslineEnv });
|
|
87
|
-
|
|
88
|
-
runNode(['--input-type=module', '-e', `
|
|
89
|
-
process.stdout.columns = 120;
|
|
90
|
-
const { renderStatusline } = await import('./src/ui/statusline.mjs');
|
|
91
|
-
const line = await renderStatusline({
|
|
92
|
-
provider: 'openai',
|
|
93
|
-
model: 'gpt-5.5',
|
|
94
|
-
contextWindow: 950000,
|
|
95
|
-
stats: { currentContextTokens: 0 },
|
|
96
|
-
agentJobs: [{ task_id: 'task_statusline_smoke', status: 'running', tag: 'bench-agent', startedAt: new Date().toISOString() }],
|
|
97
|
-
});
|
|
98
|
-
if (!line.includes('Running 1 Agent') || line.includes('bench-agent')) throw new Error('statusline must render live agent count without task tags: ' + JSON.stringify(line));
|
|
99
|
-
`], 'statusline live agent task smoke', { env: isolatedStatuslineEnv });
|
|
100
|
-
|
|
101
|
-
runNode(['--input-type=module', '-e', `
|
|
102
|
-
const { mkdtempSync, mkdirSync, writeFileSync } = await import('node:fs');
|
|
103
|
-
const { tmpdir } = await import('node:os');
|
|
104
|
-
const { join } = await import('node:path');
|
|
105
|
-
const root = mkdtempSync(join(tmpdir(), 'mixdog-status-smoke-'));
|
|
106
|
-
mkdirSync(join(root, 'runtime'), { recursive: true });
|
|
107
|
-
writeFileSync(join(root, 'runtime', 'active-instance.json'), JSON.stringify({
|
|
108
|
-
gateway_port: 3468,
|
|
109
|
-
gateway_server_pid: process.pid,
|
|
110
|
-
gateway_provider: 'openai',
|
|
111
|
-
gateway_model: 'gpt-5.5',
|
|
112
|
-
gateway_context_window: 950000,
|
|
113
|
-
gateway_raw_context_window: 1000000,
|
|
114
|
-
gateway_auto_compact_token_limit: 950000,
|
|
115
|
-
gateway_context_used_pct: 37,
|
|
116
|
-
gateway_cc_session_id: 'other-session',
|
|
117
|
-
gateway_updated_at: Date.now()
|
|
118
|
-
}));
|
|
119
|
-
process.env.MIXDOG_RUNTIME_ROOT = join(root, 'runtime');
|
|
120
|
-
const { loadGatewayStatus } = await import('./src/vendor/statusline/bin/statusline-route.mjs');
|
|
121
|
-
const status = loadGatewayStatus({ sessionId: 'fresh-session', clientHostPid: process.pid, activeContextTokens: 0 });
|
|
122
|
-
if (status?.contextUsedPct === 37) throw new Error('statusline leaked active-instance context from another session');
|
|
123
|
-
`], 'statusline session metrics isolation smoke', { env: isolatedStatuslineEnv });
|
|
124
|
-
|
|
125
|
-
const boot = spawnSync(process.execPath, ['src/cli.mjs', '--help'], {
|
|
126
|
-
cwd: root,
|
|
127
|
-
env: { ...process.env, MIXDOG_BOOT_PROFILE: '1' },
|
|
128
|
-
encoding: 'utf8',
|
|
129
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
130
|
-
timeout: 10000,
|
|
29
|
+
const boot = runNode(['src/cli.mjs', '--help'], 'boot profile smoke', {
|
|
30
|
+
env: { MIXDOG_BOOT_PROFILE: '1' },
|
|
131
31
|
});
|
|
132
|
-
|
|
133
|
-
if (boot.status !== 0 || !boot.stderr.includes('[mixdog-boot]') || !boot.stderr.includes('app:run:start')) {
|
|
32
|
+
if (!boot.stderr.includes('[mixdog-boot]')) {
|
|
134
33
|
process.stderr.write(boot.stderr || boot.stdout || 'boot profile smoke failed\n');
|
|
135
|
-
process.exit(
|
|
34
|
+
process.exit(1);
|
|
136
35
|
}
|
|
137
36
|
|
|
138
37
|
process.stdout.write('smoke passed ✓\n');
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
acceptQuotaSnapshot,
|
|
5
|
+
fallbackStatusline,
|
|
6
|
+
resolveContextUsedPct,
|
|
7
|
+
} from '../src/ui/statusline.mjs';
|
|
4
8
|
|
|
5
9
|
// Monotonic hysteresis for the 5H/7D usage segment: once a value has rendered,
|
|
6
10
|
// an OLDER shared-cache snapshot (written by another mixdog instance) must not
|
|
@@ -35,3 +39,24 @@ test('own-instance live data always wins; empty timestamps preserve prior behavi
|
|
|
35
39
|
// Shared→shared same asOf → accept (idempotent refresh).
|
|
36
40
|
assert.equal(acceptQuotaSnapshot({ asOf: 2000, owned: false }, { asOf: 2000, owned: false }), true);
|
|
37
41
|
});
|
|
42
|
+
|
|
43
|
+
test('context percentage uses compaction pressure over trigger and ignores gateway boundary percent', () => {
|
|
44
|
+
const args = {
|
|
45
|
+
stats: {
|
|
46
|
+
currentEstimatedContextTokens: 75_000,
|
|
47
|
+
currentContextSource: 'estimated',
|
|
48
|
+
},
|
|
49
|
+
contextWindow: 100_000,
|
|
50
|
+
compactBoundaryTokens: 100_000,
|
|
51
|
+
autoCompactTokenLimit: 75_000,
|
|
52
|
+
};
|
|
53
|
+
assert.equal(resolveContextUsedPct({
|
|
54
|
+
...args,
|
|
55
|
+
gatewayStatus: { contextUsedPct: 75 },
|
|
56
|
+
}), 100);
|
|
57
|
+
assert.match(
|
|
58
|
+
fallbackStatusline({ provider: 'openai', model: 'test', ...args })
|
|
59
|
+
.replace(/\x1b\[[0-9;]*m/g, ''),
|
|
60
|
+
/\b100%/,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
windowPlainStreamingText,
|
|
6
|
+
} from '../src/tui/markdown/streaming-markdown.mjs';
|
|
7
|
+
import {
|
|
8
|
+
measureStreamingMarkdownRenderedRows,
|
|
9
|
+
} from '../src/tui/markdown/measure-rendered-rows.mjs';
|
|
10
|
+
|
|
11
|
+
test('bottom-pinned plain streaming text keeps only the visible suffix', () => {
|
|
12
|
+
const lines = Array.from({ length: 100 }, (_, index) => `plain line ${index}`);
|
|
13
|
+
const full = lines.join('\n');
|
|
14
|
+
const windowed = windowPlainStreamingText(full, 80, 12);
|
|
15
|
+
|
|
16
|
+
assert.equal(windowed, lines.slice(-12).join('\n'));
|
|
17
|
+
assert.ok(measureStreamingMarkdownRenderedRows(full, 83, 'plain-window-full') > 12);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('markdown streaming text is never internally windowed', () => {
|
|
21
|
+
const markdown = `${'plain history\n'.repeat(100)}**live markdown**`;
|
|
22
|
+
assert.equal(windowPlainStreamingText(markdown, 80, 12), markdown.trim());
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('wrapped plain lines consume the streaming row budget', () => {
|
|
26
|
+
const lines = ['old', 'x'.repeat(160), 'new'];
|
|
27
|
+
assert.equal(windowPlainStreamingText(lines.join('\n'), 80, 2), lines.slice(-1).join('\n'));
|
|
28
|
+
assert.equal(windowPlainStreamingText(lines.join('\n'), 80, 3), lines.slice(-2).join('\n'));
|
|
29
|
+
});
|
|
@@ -94,20 +94,33 @@ const rows = files.flatMap(readRows)
|
|
|
94
94
|
.filter((row) => !agentFilter || String(row.agent || '-') === agentFilter)
|
|
95
95
|
.filter((row) => !categoryFilter || rowCategory(row) === categoryFilter)
|
|
96
96
|
.sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
|
|
97
|
-
const
|
|
97
|
+
const isCommandExit = (row) => rowCategory(row) === 'command-exit';
|
|
98
|
+
const actionableRows = rows.filter((row) => !isCommandExit(row));
|
|
99
|
+
const commandExitRows = rows.filter(isCommandExit);
|
|
100
|
+
// Limit each partition independently so a burst of ordinary command exits
|
|
101
|
+
// cannot crowd runtime/actionable failures out of the displayed report.
|
|
102
|
+
const actionableRecent = actionableRows.slice(-limit);
|
|
103
|
+
const commandExitRecent = commandExitRows.slice(-limit);
|
|
104
|
+
const recent = [...actionableRecent, ...commandExitRecent]
|
|
105
|
+
.sort((a, b) => Number(a.ts || 0) - Number(b.ts || 0));
|
|
98
106
|
const byTool = new Map();
|
|
99
107
|
const byCategory = new Map();
|
|
108
|
+
const actionableByTool = new Map();
|
|
109
|
+
const commandExitByTool = new Map();
|
|
100
110
|
for (const row of recent) {
|
|
101
111
|
const tool = rowTool(row);
|
|
102
112
|
const category = rowCategory(row);
|
|
103
113
|
inc(byTool, tool);
|
|
104
114
|
inc(byCategory, `${tool} / ${category}`);
|
|
115
|
+
inc(isCommandExit(row) ? commandExitByTool : actionableByTool, tool);
|
|
105
116
|
}
|
|
106
117
|
|
|
107
118
|
if (jsonMode) {
|
|
108
119
|
console.log(JSON.stringify({
|
|
109
120
|
shown: recent.length,
|
|
110
121
|
matched: rows.length,
|
|
122
|
+
actionable_failures: { shown: actionableRecent.length, matched: actionableRows.length },
|
|
123
|
+
command_exits: { shown: commandExitRecent.length, matched: commandExitRows.length },
|
|
111
124
|
since: sinceTs ? new Date(sinceTs).toISOString() : null,
|
|
112
125
|
filters: {
|
|
113
126
|
tool: toolFilter,
|
|
@@ -116,13 +129,17 @@ if (jsonMode) {
|
|
|
116
129
|
},
|
|
117
130
|
sources: files.filter(existsSync),
|
|
118
131
|
tools: Object.fromEntries([...byTool.entries()].sort((a, b) => b[1] - a[1])),
|
|
132
|
+
actionable_tools: Object.fromEntries([...actionableByTool.entries()].sort((a, b) => b[1] - a[1])),
|
|
133
|
+
command_exit_tools: Object.fromEntries([...commandExitByTool.entries()].sort((a, b) => b[1] - a[1])),
|
|
119
134
|
categories: Object.fromEntries([...byCategory.entries()].sort((a, b) => b[1] - a[1])),
|
|
120
135
|
rows: recent,
|
|
121
136
|
}, null, 2));
|
|
122
137
|
process.exit(0);
|
|
123
138
|
}
|
|
124
139
|
|
|
125
|
-
console.log(`
|
|
140
|
+
console.log(`actionable failures: ${actionableRecent.length}/${actionableRows.length} shown`);
|
|
141
|
+
console.log(`command exits: ${commandExitRecent.length}/${commandExitRows.length} shown (retained)`);
|
|
142
|
+
console.log(`rows: ${recent.length}/${rows.length} shown`);
|
|
126
143
|
if (sinceTs) console.log(`since: ${new Date(sinceTs).toISOString()}`);
|
|
127
144
|
const filterParts = [
|
|
128
145
|
toolFilter ? `tool=${toolFilter}` : '',
|
|
@@ -131,7 +148,8 @@ const filterParts = [
|
|
|
131
148
|
].filter(Boolean);
|
|
132
149
|
if (filterParts.length) console.log(`filters: ${filterParts.join(', ')}`);
|
|
133
150
|
if (files.length > 0) console.log(`sources: ${files.filter(existsSync).join(', ') || '(none)'}`);
|
|
134
|
-
console.log(`tools: ${[...
|
|
151
|
+
console.log(`actionable tools: ${[...actionableByTool.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
|
|
152
|
+
console.log(`command-exit tools: ${[...commandExitByTool.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
|
|
135
153
|
console.log(`categories: ${[...byCategory.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
|
|
136
154
|
for (const row of recent) {
|
|
137
155
|
const tool = rowTool(row);
|