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.
Files changed (88) hide show
  1. package/README.md +6 -6
  2. package/package.json +19 -9
  3. package/scripts/agent-parallel-smoke.mjs +4 -1
  4. package/scripts/agent-route-batch-test.mjs +40 -0
  5. package/scripts/code-graph-aggregate-cwd-test.mjs +154 -0
  6. package/scripts/code-graph-description-contract.mjs +185 -0
  7. package/scripts/code-graph-disk-hit-test.mjs +55 -0
  8. package/scripts/code-graph-dispatch-test.mjs +96 -0
  9. package/scripts/compact-pressure-test.mjs +40 -0
  10. package/scripts/deferred-tool-loading-test.mjs +233 -0
  11. package/scripts/embedding-worker-exit-test.mjs +76 -0
  12. package/scripts/execution-completion-dedup-test.mjs +48 -0
  13. package/scripts/explore-prompt-policy-test.mjs +88 -3
  14. package/scripts/live-worker-smoke.mjs +68 -16
  15. package/scripts/memory-core-input-test.mjs +33 -13
  16. package/scripts/native-edit-wire-test.mjs +152 -0
  17. package/scripts/openai-oauth-ws-1006-retry-test.mjs +294 -16
  18. package/scripts/patch-binary-cache-test.mjs +181 -0
  19. package/scripts/prompt-immediate-render-test.mjs +89 -0
  20. package/scripts/provider-toolcall-test.mjs +280 -15
  21. package/scripts/shell-failure-diagnostics-test.mjs +211 -0
  22. package/scripts/smoke-loop.mjs +9 -3
  23. package/scripts/smoke.mjs +5 -106
  24. package/scripts/statusline-quota-hysteresis-test.mjs +26 -1
  25. package/scripts/streaming-tail-window-test.mjs +29 -0
  26. package/scripts/tool-failures.mjs +21 -3
  27. package/scripts/tool-smoke.mjs +263 -38
  28. package/scripts/tool-tui-presentation-test.mjs +17 -1
  29. package/scripts/tui-perf-run.ps1 +26 -0
  30. package/scripts/tui-transcript-perf-test.mjs +7 -1
  31. package/scripts/verify-release-assets-test.mjs +281 -0
  32. package/scripts/verify-release-assets.mjs +293 -0
  33. package/scripts/windows-hide-spawn-options-test.mjs +19 -0
  34. package/src/cli.mjs +1 -0
  35. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +16 -5
  36. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +35 -11
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -11
  38. package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +28 -0
  39. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +1 -1
  40. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +34 -34
  41. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +45 -38
  42. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +36 -11
  43. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +4 -4
  44. package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +10 -6
  45. package/src/runtime/agent/orchestrator/session/loop/deferred-call-through.mjs +4 -3
  46. package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +4 -3
  47. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +16 -1
  48. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +2 -2
  49. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +0 -1
  50. package/src/runtime/agent/orchestrator/session/tool-batch.mjs +13 -8
  51. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +30 -16
  52. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +25 -8
  53. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +69 -4
  54. package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +5 -4
  55. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +12 -12
  57. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +12 -7
  58. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +77 -18
  59. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  60. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +99 -24
  61. package/src/runtime/memory/lib/embedding-provider.mjs +3 -2
  62. package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -11
  63. package/src/runtime/memory/tool-defs.mjs +1 -2
  64. package/src/session-runtime/context-status.mjs +25 -16
  65. package/src/session-runtime/model-route-api.mjs +2 -0
  66. package/src/session-runtime/runtime-core.mjs +2 -2
  67. package/src/session-runtime/session-turn-api.mjs +4 -1
  68. package/src/session-runtime/tool-catalog.mjs +113 -19
  69. package/src/session-runtime/tool-defs.mjs +1 -0
  70. package/src/standalone/agent-tool/helpers.mjs +25 -6
  71. package/src/standalone/agent-tool.mjs +75 -41
  72. package/src/tui/App.jsx +4 -0
  73. package/src/tui/app/input-parsers.mjs +8 -9
  74. package/src/tui/components/Markdown.jsx +6 -1
  75. package/src/tui/components/Message.jsx +11 -2
  76. package/src/tui/components/PromptInput.jsx +19 -21
  77. package/src/tui/components/Spinner.jsx +4 -4
  78. package/src/tui/components/StatusLine.jsx +6 -6
  79. package/src/tui/components/TranscriptItem.jsx +2 -2
  80. package/src/tui/components/prompt-input/immediate-render.mjs +47 -0
  81. package/src/tui/components/tool-execution/surface-detail.mjs +14 -9
  82. package/src/tui/dist/index.mjs +130 -45
  83. package/src/tui/engine/agent-job-feed.mjs +21 -2
  84. package/src/tui/engine/turn.mjs +12 -1
  85. package/src/tui/markdown/measure-rendered-rows.mjs +1 -1
  86. package/src/tui/markdown/streaming-markdown.mjs +20 -0
  87. package/src/ui/statusline.mjs +5 -5
  88. 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
- const child = runNode(['src/cli.mjs', '--help'], 'help smoke');
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(['--input-type=module', '-e', `
40
- process.stdout.columns = 120;
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(boot.status || 1);
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 { acceptQuotaSnapshot } from '../src/ui/statusline.mjs';
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 recent = rows.slice(-limit);
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(`tool failures: ${recent.length}/${rows.length} shown`);
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: ${[...byTool.entries()].sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k}:${v}`).join(', ') || '(none)'}`);
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);