mixdog 0.9.43 → 0.9.44
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/package.json +3 -2
- package/scripts/ansi-color-capability-test.mjs +90 -0
- package/scripts/generate-runtime-manifest.mjs +39 -22
- package/scripts/grok-oauth-refresh-race-test.mjs +198 -0
- package/scripts/internal-comms-smoke.mjs +24 -7
- package/scripts/maintenance-default-routes-test.mjs +164 -0
- package/scripts/openai-oauth-ws-1006-retry-test.mjs +123 -0
- package/scripts/routing-corpus.mjs +1 -1
- package/scripts/shell-jobs-windows-hide-test.mjs +164 -0
- package/scripts/tui-transcript-jitter-harness-entry.jsx +302 -0
- package/scripts/tui-transcript-jitter-harness.mjs +58 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/rules/lead/lead-brief.md +5 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +40 -3
- package/src/runtime/agent/orchestrator/config.mjs +29 -14
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +46 -21
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -5
- package/src/runtime/memory/data/runtime-manifest.json +11 -11
- package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -2
- package/src/standalone/agent-tool.mjs +65 -50
- package/src/standalone/explore-tool.mjs +17 -16
- package/src/tui/app/provider-setup-picker.mjs +1 -0
- package/src/tui/app/use-transcript-window.mjs +5 -1
- package/src/tui/components/StatusLine.jsx +7 -6
- package/src/tui/dist/index.mjs +213 -77
- package/src/tui/engine/turn.mjs +1 -1
- package/src/tui/index.jsx +3 -2
- package/src/tui/markdown/streaming-markdown.mjs +35 -9
- package/src/tui/statusline-ansi-bridge.mjs +17 -7
- package/src/ui/ansi.mjs +85 -5
- package/src/ui/statusline-format.mjs +7 -7
package/src/tui/index.jsx
CHANGED
|
@@ -15,6 +15,7 @@ import { App } from './App.jsx';
|
|
|
15
15
|
import { createEngineSession } from './engine.mjs';
|
|
16
16
|
import { scheduleRenderFrameAck } from './engine/render-timing.mjs';
|
|
17
17
|
import { installProcessSignalCleanup } from '../runtime/shared/process-shutdown.mjs';
|
|
18
|
+
import { rgbSgr } from '../ui/ansi.mjs';
|
|
18
19
|
import { emitTerminalBackground, loadThemeSettingFromConfig, theme } from './theme.mjs';
|
|
19
20
|
import { POP_KITTY, DISABLE_MODIFY_OTHER_KEYS } from './keyboard-protocol.mjs';
|
|
20
21
|
import { displayWidth } from './display-width.mjs';
|
|
@@ -259,10 +260,10 @@ function resolveTuiStderrLogPath() {
|
|
|
259
260
|
|| join(process.env.MIXDOG_RUNTIME_ROOT || join(tmpdir(), 'mixdog'), 'mixdog-tui.stderr.log');
|
|
260
261
|
}
|
|
261
262
|
|
|
262
|
-
/** `rgb(r,g,b)` →
|
|
263
|
+
/** `rgb(r,g,b)` → supported RGB SGR prefix ('' when unparsable). */
|
|
263
264
|
function ansiFg(rgb) {
|
|
264
265
|
const m = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/.exec(String(rgb || ''));
|
|
265
|
-
return m ?
|
|
266
|
+
return m ? rgbSgr(m[1], m[2], m[3]) : '';
|
|
266
267
|
}
|
|
267
268
|
|
|
268
269
|
/**
|
|
@@ -7,6 +7,8 @@ import { configureMarked, hasMarkdownSyntax } from './render-ansi.mjs';
|
|
|
7
7
|
import { trimPartialClosingFences, findOpenFenceStart } from './stream-fence.mjs';
|
|
8
8
|
|
|
9
9
|
const stablePrefixByStreamKey = new Map();
|
|
10
|
+
// Reuse the current normalized-text split across measure → render → harvest.
|
|
11
|
+
const resolvedPartsByStreamKey = new Map();
|
|
10
12
|
const STABLE_PREFIX_LRU_MAX = 32;
|
|
11
13
|
|
|
12
14
|
/** Lockstep with App streaming row estimate (leading/trailing newline trim). */
|
|
@@ -37,6 +39,25 @@ function getStablePrefixKey(key) {
|
|
|
37
39
|
return value;
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
function getResolvedPartsKey(key, text) {
|
|
43
|
+
if (!key) return null;
|
|
44
|
+
const entry = resolvedPartsByStreamKey.get(key);
|
|
45
|
+
if (!entry || entry.text !== text) return null;
|
|
46
|
+
return entry.parts;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function cacheResolvedPartsKey(key, text, parts) {
|
|
50
|
+
if (!key) return parts;
|
|
51
|
+
if (resolvedPartsByStreamKey.has(key)) resolvedPartsByStreamKey.delete(key);
|
|
52
|
+
resolvedPartsByStreamKey.set(key, { text, parts });
|
|
53
|
+
while (resolvedPartsByStreamKey.size > STABLE_PREFIX_LRU_MAX) {
|
|
54
|
+
const oldest = resolvedPartsByStreamKey.keys().next().value;
|
|
55
|
+
if (oldest === undefined) break;
|
|
56
|
+
resolvedPartsByStreamKey.delete(oldest);
|
|
57
|
+
}
|
|
58
|
+
return parts;
|
|
59
|
+
}
|
|
60
|
+
|
|
40
61
|
function hasOpenFence(text) {
|
|
41
62
|
let ticks = 0;
|
|
42
63
|
let tildes = 0;
|
|
@@ -93,35 +114,40 @@ export function balanceStreamingMarkdown(text) {
|
|
|
93
114
|
|
|
94
115
|
export function resetStreamingMarkdownStablePrefix(streamKey) {
|
|
95
116
|
if (streamKey == null || streamKey === '') return;
|
|
96
|
-
|
|
117
|
+
const key = String(streamKey);
|
|
118
|
+
stablePrefixByStreamKey.delete(key);
|
|
119
|
+
resolvedPartsByStreamKey.delete(key);
|
|
97
120
|
}
|
|
98
121
|
|
|
99
122
|
export function resetAllStreamingMarkdownStablePrefixes() {
|
|
100
123
|
stablePrefixByStreamKey.clear();
|
|
124
|
+
resolvedPartsByStreamKey.clear();
|
|
101
125
|
}
|
|
102
126
|
|
|
103
127
|
export function resolveStreamingMarkdownParts(text, streamKey) {
|
|
104
128
|
const t = streamingLayoutText(text);
|
|
105
129
|
const key = streamKey == null || streamKey === '' ? null : String(streamKey);
|
|
130
|
+
const cachedParts = getResolvedPartsKey(key, t);
|
|
131
|
+
if (cachedParts) return cachedParts;
|
|
106
132
|
|
|
107
133
|
if (!t) {
|
|
108
134
|
if (key) stablePrefixByStreamKey.delete(key);
|
|
109
|
-
return {
|
|
135
|
+
return cacheResolvedPartsKey(key, t, {
|
|
110
136
|
plain: true,
|
|
111
137
|
stablePrefix: '',
|
|
112
138
|
unstableSuffix: '',
|
|
113
139
|
unstableForRender: '',
|
|
114
|
-
};
|
|
140
|
+
});
|
|
115
141
|
}
|
|
116
142
|
|
|
117
143
|
if (!hasMarkdownSyntax(t)) {
|
|
118
144
|
if (key) stablePrefixByStreamKey.delete(key);
|
|
119
|
-
return {
|
|
145
|
+
return cacheResolvedPartsKey(key, t, {
|
|
120
146
|
plain: true,
|
|
121
147
|
stablePrefix: '',
|
|
122
148
|
unstableSuffix: t,
|
|
123
149
|
unstableForRender: t,
|
|
124
|
-
};
|
|
150
|
+
});
|
|
125
151
|
}
|
|
126
152
|
|
|
127
153
|
let stablePrefix = key ? getStablePrefixKey(key) : '';
|
|
@@ -141,13 +167,13 @@ export function resolveStreamingMarkdownParts(text, streamKey) {
|
|
|
141
167
|
if (key && openPrefix) touchStablePrefixKey(key, openPrefix);
|
|
142
168
|
else if (key) stablePrefixByStreamKey.delete(key);
|
|
143
169
|
const unstableSuffix = t.substring(openPrefix.length);
|
|
144
|
-
return {
|
|
170
|
+
return cacheResolvedPartsKey(key, t, {
|
|
145
171
|
plain: false,
|
|
146
172
|
openFence: true,
|
|
147
173
|
stablePrefix: openPrefix,
|
|
148
174
|
unstableSuffix,
|
|
149
175
|
unstableForRender: unstableSuffix,
|
|
150
|
-
};
|
|
176
|
+
});
|
|
151
177
|
}
|
|
152
178
|
|
|
153
179
|
try {
|
|
@@ -179,10 +205,10 @@ export function resolveStreamingMarkdownParts(text, streamKey) {
|
|
|
179
205
|
if (isWhitespaceOnlyText(stablePrefix)) stablePrefix = '';
|
|
180
206
|
|
|
181
207
|
const unstableSuffix = t.substring(stablePrefix.length);
|
|
182
|
-
return {
|
|
208
|
+
return cacheResolvedPartsKey(key, t, {
|
|
183
209
|
plain: false,
|
|
184
210
|
stablePrefix,
|
|
185
211
|
unstableSuffix,
|
|
186
212
|
unstableForRender: balanceStreamingMarkdown(unstableSuffix),
|
|
187
|
-
};
|
|
213
|
+
});
|
|
188
214
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* normalizes that at display time so `/theme` can re-tone without touching ui/.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { rgbToAnsi256 } from '../ui/ansi.mjs';
|
|
10
|
+
|
|
9
11
|
const RESET = '\x1b[0m';
|
|
10
12
|
|
|
11
13
|
/** Truecolor SGR sequences emitted by the shared statusline stack (not theme). */
|
|
@@ -23,6 +25,14 @@ function truecolorSgr(r, g, b) {
|
|
|
23
25
|
return `\x1b[38;2;${r};${g};${b}m`;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
function ansi256Sgr(r, g, b) {
|
|
29
|
+
return `\x1b[38;5;${rgbToAnsi256(r, g, b)}m`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function canonicalSgrVariants(rgb) {
|
|
33
|
+
return [truecolorSgr(...rgb), ansi256Sgr(...rgb)];
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
function footerLocalNum(value) {
|
|
27
37
|
const n = Number(value);
|
|
28
38
|
return Number.isFinite(n) ? n : 0;
|
|
@@ -139,13 +149,13 @@ export function applyDefaultStatusForegroundAfterReset(text, STATUS, reset = RES
|
|
|
139
149
|
export function remapCanonicalStatuslineTruecolor(text, colors) {
|
|
140
150
|
const c = STATUSLINE_CANONICAL_TRUECOLOR;
|
|
141
151
|
const pairs = [
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
...canonicalSgrVariants(c.statusText).map((from) => [from, colors.STATUS]),
|
|
153
|
+
...canonicalSgrVariants(c.subtle).map((from) => [from, colors.SUBTLE]),
|
|
154
|
+
...canonicalSgrVariants(c.success).map((from) => [from, colors.SUCCESS]),
|
|
155
|
+
...canonicalSgrVariants(c.successBright).map((from) => [from, colors.SUCCESS]),
|
|
156
|
+
...canonicalSgrVariants(c.warning).map((from) => [from, colors.WARNING]),
|
|
157
|
+
...canonicalSgrVariants(c.warningBright).map((from) => [from, colors.WARNING]),
|
|
158
|
+
...canonicalSgrVariants(c.error).map((from) => [from, colors.ERROR]),
|
|
149
159
|
];
|
|
150
160
|
let out = String(text || '');
|
|
151
161
|
for (const [from, to] of pairs) {
|
package/src/ui/ansi.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* The decision is computed once at import time but can be recomputed via
|
|
13
13
|
* `refreshColorSupport()` (used by tests / when output is rebound).
|
|
14
14
|
*/
|
|
15
|
-
import { stdout, env } from 'node:process';
|
|
15
|
+
import { stdout, env, platform } from 'node:process';
|
|
16
16
|
|
|
17
17
|
let COLOR_ENABLED = computeColorEnabled();
|
|
18
18
|
|
|
@@ -39,12 +39,92 @@ export function colorEnabled() {
|
|
|
39
39
|
const ESC = '\x1b[';
|
|
40
40
|
const RESET = `${ESC}0m`;
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Whether the terminal can render 24-bit SGR colors.
|
|
44
|
+
*
|
|
45
|
+
* Apple Terminal is the one explicit negative because it advertises a normal
|
|
46
|
+
* xterm TERM while not implementing truecolor. Unknown terminals retain the
|
|
47
|
+
* historical truecolor default.
|
|
48
|
+
*/
|
|
49
|
+
export function supportsTruecolor(environment = env, platformName = platform) {
|
|
50
|
+
const termProgram = String(environment?.TERM_PROGRAM || '').trim().toLowerCase();
|
|
51
|
+
if (termProgram === 'apple_terminal') return false;
|
|
52
|
+
|
|
53
|
+
const colorTerm = String(environment?.COLORTERM || '').trim().toLowerCase();
|
|
54
|
+
if (colorTerm === 'truecolor' || colorTerm === '24bit') return true;
|
|
55
|
+
if (environment?.WT_SESSION !== undefined && environment.WT_SESSION !== '') return true;
|
|
56
|
+
if (['iterm.app', 'wezterm', 'ghostty', 'vscode'].includes(termProgram)) return true;
|
|
57
|
+
if (/(?:direct|truecolor)/i.test(String(environment?.TERM || ''))) return true;
|
|
58
|
+
if (platformName === 'win32') return true;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const ANSI_256_CUBE_LEVELS = Object.freeze([0, 95, 135, 175, 215, 255]);
|
|
63
|
+
|
|
64
|
+
function colorByte(value) {
|
|
65
|
+
const n = Number(value);
|
|
66
|
+
return Number.isFinite(n) ? Math.max(0, Math.min(255, Math.round(n))) : 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function nearestCubeLevel(value) {
|
|
70
|
+
let best = 0;
|
|
71
|
+
let bestDistance = Infinity;
|
|
72
|
+
for (let i = 0; i < ANSI_256_CUBE_LEVELS.length; i++) {
|
|
73
|
+
const distance = Math.abs(value - ANSI_256_CUBE_LEVELS[i]);
|
|
74
|
+
if (distance < bestDistance) {
|
|
75
|
+
best = i;
|
|
76
|
+
bestDistance = distance;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return best;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Convert an RGB triplet to the nearest stable xterm 256-color palette index. */
|
|
83
|
+
export function rgbToAnsi256(r, g, b) {
|
|
84
|
+
const red = colorByte(r);
|
|
85
|
+
const green = colorByte(g);
|
|
86
|
+
const blue = colorByte(b);
|
|
87
|
+
|
|
88
|
+
const redLevel = nearestCubeLevel(red);
|
|
89
|
+
const greenLevel = nearestCubeLevel(green);
|
|
90
|
+
const blueLevel = nearestCubeLevel(blue);
|
|
91
|
+
const cubeRed = ANSI_256_CUBE_LEVELS[redLevel];
|
|
92
|
+
const cubeGreen = ANSI_256_CUBE_LEVELS[greenLevel];
|
|
93
|
+
const cubeBlue = ANSI_256_CUBE_LEVELS[blueLevel];
|
|
94
|
+
const cubeDistance = ((red - cubeRed) ** 2)
|
|
95
|
+
+ ((green - cubeGreen) ** 2)
|
|
96
|
+
+ ((blue - cubeBlue) ** 2);
|
|
97
|
+
|
|
98
|
+
const average = (red + green + blue) / 3;
|
|
99
|
+
const grayLevel = Math.max(0, Math.min(23, Math.round((average - 8) / 10)));
|
|
100
|
+
const grayValue = 8 + (grayLevel * 10);
|
|
101
|
+
const grayDistance = ((red - grayValue) ** 2)
|
|
102
|
+
+ ((green - grayValue) ** 2)
|
|
103
|
+
+ ((blue - grayValue) ** 2);
|
|
104
|
+
|
|
105
|
+
if (grayDistance < cubeDistance) return 232 + grayLevel;
|
|
106
|
+
return 16 + (36 * redLevel) + (6 * greenLevel) + blueLevel;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Raw RGB SGR prefix, downsampled to 256 colors when truecolor is unavailable. */
|
|
110
|
+
export function rgbSgr(r, g, b, background = false) {
|
|
111
|
+
const channel = background ? 48 : 38;
|
|
112
|
+
if (supportsTruecolor()) return `${ESC}${channel};2;${r};${g};${b}m`;
|
|
113
|
+
return `${ESC}${channel};5;${rgbToAnsi256(r, g, b)}m`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function supportedSgrOpen(open) {
|
|
117
|
+
const match = /^(38|48);2;([^;]+);([^;]+);([^;]+)$/.exec(String(open));
|
|
118
|
+
if (!match || supportsTruecolor()) return open;
|
|
119
|
+
return `${match[1]};5;${rgbToAnsi256(match[2], match[3], match[4])}`;
|
|
120
|
+
}
|
|
121
|
+
|
|
42
122
|
/** Build a `(text) => string` wrapper for the given SGR open code(s). */
|
|
43
123
|
function sgr(open) {
|
|
44
|
-
const openSeq = `${ESC}${open}m`;
|
|
45
124
|
return (text) => {
|
|
46
125
|
if (!COLOR_ENABLED) return String(text ?? '');
|
|
47
126
|
const s = String(text ?? '');
|
|
127
|
+
const openSeq = `${ESC}${supportedSgrOpen(open)}m`;
|
|
48
128
|
// Re-open after any nested reset so composed styles survive concatenation.
|
|
49
129
|
return openSeq + s.replace(/\x1b\[0m/g, RESET + openSeq) + RESET;
|
|
50
130
|
};
|
|
@@ -84,9 +164,9 @@ export const bgGray = sgr('100');
|
|
|
84
164
|
export const bgBlack = sgr('40');
|
|
85
165
|
export const bgBlue = sgr('44');
|
|
86
166
|
|
|
87
|
-
// ---
|
|
88
|
-
//
|
|
89
|
-
//
|
|
167
|
+
// --- RGB colors --------------------------------------------------------------
|
|
168
|
+
// Emit 24-bit SGR where supported, otherwise use the nearest 256-color entry.
|
|
169
|
+
// Honors NO_COLOR / TTY exactly like the named helpers above.
|
|
90
170
|
|
|
91
171
|
/** Foreground truecolor wrapper: `rgb(215,119,87)('x')`. */
|
|
92
172
|
export function rgb(r, g, b) {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* bar/segment formatters, small numeric helpers, and the TUI-independent
|
|
6
6
|
* formatElapsed() replica. No behavior change — statusline.mjs re-imports these.
|
|
7
7
|
*/
|
|
8
|
-
import { colorEnabled, rgb } from './ansi.mjs';
|
|
8
|
+
import { colorEnabled, rgb, rgbSgr } from './ansi.mjs';
|
|
9
9
|
import { displayModelName, shortenModelName } from './model-display.mjs';
|
|
10
10
|
import { getModelMetadataSync } from '../runtime/agent/orchestrator/providers/model-catalog.mjs';
|
|
11
11
|
|
|
@@ -23,12 +23,12 @@ function sgr(code) {
|
|
|
23
23
|
|
|
24
24
|
export const R = sgr('0');
|
|
25
25
|
export const B = sgr('1');
|
|
26
|
-
export const D =
|
|
27
|
-
export const GRN =
|
|
28
|
-
export const YLW =
|
|
29
|
-
export const RED =
|
|
30
|
-
export const CYN =
|
|
31
|
-
export const GREY =
|
|
26
|
+
export const D = colorEnabled() ? rgbSgr(136, 136, 136) : '';
|
|
27
|
+
export const GRN = colorEnabled() ? rgbSgr(0, 170, 75) : '';
|
|
28
|
+
export const YLW = colorEnabled() ? rgbSgr(255, 193, 7) : '';
|
|
29
|
+
export const RED = colorEnabled() ? rgbSgr(220, 70, 88) : '';
|
|
30
|
+
export const CYN = colorEnabled() ? rgbSgr(136, 136, 136) : '';
|
|
31
|
+
export const GREY = colorEnabled() ? rgbSgr(136, 136, 136) : '';
|
|
32
32
|
|
|
33
33
|
export function terminalColumns() {
|
|
34
34
|
const cols = Number(process.stdout?.columns);
|