mixdog 0.9.140 → 0.9.142

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 (42) hide show
  1. package/LICENSES/MIT.txt +4 -0
  2. package/NOTICE.md +12 -46
  3. package/package.json +1 -1
  4. package/scripts/manifest-cache-key.mjs +44 -0
  5. package/scripts/native-binary-arch.mjs +93 -0
  6. package/scripts/native-binary-arch.test.mjs +75 -0
  7. package/scripts/native-tool-download.mjs +118 -0
  8. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1 -1
  9. package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +2 -2
  10. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -2
  11. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +1 -1
  12. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1 -1
  13. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +12 -12
  14. package/src/runtime/agent/orchestrator/session/compact/file-reattach.mjs +2 -2
  15. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +1 -1
  16. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +1 -1
  17. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -1
  18. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -3
  19. package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +6 -7
  20. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +2 -3
  21. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +1 -1
  22. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -3
  23. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +2 -2
  24. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
  25. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +1 -1
  26. package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
  27. package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
  28. package/src/standalone/agent-tool/shard-spread.mjs +5 -3
  29. package/src/standalone/agent-tool/shard-spread.test.mjs +16 -1
  30. package/src/tui/App.jsx +3 -3
  31. package/src/tui/app/transcript-window.mjs +4 -5
  32. package/src/tui/app/use-mouse-input.mjs +2 -2
  33. package/src/tui/app/use-transcript-scroll.mjs +37 -29
  34. package/src/tui/components/tool-output-format.mjs +2 -2
  35. package/src/tui/dist/index.mjs +21 -31
  36. package/src/tui/themes/teal.mjs +1 -1
  37. package/vendor/ink/build/ink.js +7 -85
  38. package/vendor/ink/build/output.js +7 -9
  39. package/vendor/ink/build/selection-rect.js +80 -0
  40. package/vendor/ink/build/termio-keypress.js +110 -79
  41. package/vendor/ink/build/_baseline-termio-keypress.js +0 -240
  42. package/vendor/ink/build/_baseline-termio-tokenize.js +0 -253
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * termio-keypress.js — chunked keypress pipeline built on the termio tokenizer.
3
3
  *
4
- * Keypress parsing (parseMultipleKeypresses +
5
- * paste IN_PASTE buffering + orphaned mouse-tail resynthesis + terminal-response
6
- * / SGR-mouse recognition) to plain ESM JS.
4
+ * Tokens arrive as text runs and raw escape sequences; this layer decides what
5
+ * each one MEANS: bracketed-paste accumulation, terminal-response replies,
6
+ * SGR/X10 mouse events, orphaned mouse tails, and ordinary keypresses.
7
7
  *
8
8
  * It emits a flat list of typed events:
9
9
  * { kind: 'key', sequence, isPasted, ... } normal keypress or paste
@@ -148,93 +148,124 @@ export const INITIAL_STATE = {
148
148
  pasteBuffer: '',
149
149
  };
150
150
 
151
+ // An orphaned mouse tail: the ESC was flushed on its own as a lone Escape (a
152
+ // heavy render blocked the event loop past the pending-ESC timer), so the
153
+ // continuation reached us as plain text. The X10 Cb slot is narrowed to the
154
+ // wheel range so ordinary typing like `[MAX]` batched into one read is not
155
+ // mistaken for a click.
156
+ // eslint-disable-next-line no-control-regex
157
+ const ORPHAN_SGR_TAIL_RE = /^\[<\d+;\d+;\d+[Mm]$/;
158
+ // eslint-disable-next-line no-control-regex
159
+ const ORPHAN_X10_TAIL_RE = /^\[M[\x60-\x7f][\x20-\uffff]{2}$/;
160
+
161
+ /** A non-paste sequence token becomes a response, a mouse event, or a key. */
162
+ function eventForSequence(sequence) {
163
+ const response = parseTerminalResponse(sequence);
164
+ if (response) return { kind: 'response', sequence, response };
165
+ return parseMouseEvent(sequence) ?? asKey(sequence);
166
+ }
167
+
168
+ /** Restore the ESC an orphaned mouse tail lost, or null when it is real text. */
169
+ function eventForOrphanTail(text) {
170
+ if (!ORPHAN_SGR_TAIL_RE.test(text) && !ORPHAN_X10_TAIL_RE.test(text)) return null;
171
+ const restored = `\x1b${text}`;
172
+ return parseMouseEvent(restored) ?? asKey(restored);
173
+ }
174
+
151
175
  /**
152
- * Feed a chunk (or null to flush) through the tokenizer + paste/mouse/response
153
- * classification. Returns [events, newState]. The tokenizer instance lives on
154
- * the returned state so incomplete sequences buffer across calls.
176
+ * Cut a text run at every DEL/BS byte. A held backspace arrives as one chunk
177
+ * (`\x7f\x7f\x7f`) and must land as three key events, not one garbage key.
178
+ * Carriage returns and tabs stay inside their run.
155
179
  */
156
- export function parseMultipleKeypresses(prevState, input = '') {
157
- const isFlush = input === null;
158
- const inputString = isFlush ? '' : typeof input === 'string' ? input : String(input ?? '');
180
+ function splitEditingBytes(text) {
181
+ const pieces = [];
182
+ let run = '';
183
+ for (const character of text) {
184
+ if (character !== '\x7f' && character !== '\x08') {
185
+ run += character;
186
+ continue;
187
+ }
188
+ if (run) pieces.push(run);
189
+ pieces.push(character);
190
+ run = '';
191
+ }
192
+ if (run) pieces.push(run);
193
+ return pieces;
194
+ }
159
195
 
196
+ /** Bracketed-paste accumulator: everything between the markers is literal. */
197
+ function pasteAccumulator(open, buffer) {
198
+ return {
199
+ open,
200
+ buffer,
201
+ start() {
202
+ this.open = true;
203
+ this.buffer = '';
204
+ },
205
+ absorb(text) {
206
+ this.buffer += text;
207
+ },
208
+ take() {
209
+ const content = this.buffer;
210
+ this.open = false;
211
+ this.buffer = '';
212
+ return content;
213
+ },
214
+ };
215
+ }
216
+
217
+ function asInputString(input) {
218
+ if (typeof input === 'string') return input;
219
+ return String(input ?? '');
220
+ }
221
+
222
+ /**
223
+ * Feed a chunk (or null to flush) through the tokenizer and turn the resulting
224
+ * tokens into typed events. Returns [events, newState]; the tokenizer instance
225
+ * rides on the returned state so incomplete sequences buffer across calls.
226
+ */
227
+ export function parseMultipleKeypresses(prevState, input = '') {
228
+ const flushing = input === null;
160
229
  const tokenizer = prevState._tokenizer ?? createTokenizer({ x10Mouse: true });
161
- const tokens = isFlush ? tokenizer.flush() : tokenizer.feed(inputString);
230
+ const tokens = flushing ? tokenizer.flush() : tokenizer.feed(asInputString(input));
162
231
 
163
- const keys = [];
164
- let inPaste = prevState.mode === 'IN_PASTE';
165
- let pasteBuffer = prevState.pasteBuffer;
232
+ const events = [];
233
+ const paste = pasteAccumulator(prevState.mode === 'IN_PASTE', prevState.pasteBuffer);
166
234
 
167
235
  for (const token of tokens) {
168
- if (token.type === 'sequence') {
169
- if (token.value === PASTE_START) {
170
- inPaste = true;
171
- pasteBuffer = '';
172
- } else if (token.value === PASTE_END) {
173
- // Always emit a paste key, even for empty pastes.
174
- keys.push(createPasteKey(pasteBuffer));
175
- inPaste = false;
176
- pasteBuffer = '';
177
- } else if (inPaste) {
178
- // Sequences inside paste are literal text.
179
- pasteBuffer += token.value;
180
- } else {
181
- const response = parseTerminalResponse(token.value);
182
- if (response) {
183
- keys.push({ kind: 'response', sequence: token.value, response });
184
- } else {
185
- const mouse = parseMouseEvent(token.value);
186
- keys.push(mouse ?? asKey(token.value));
187
- }
188
- }
189
- } else if (token.type === 'text') {
190
- if (inPaste) {
191
- pasteBuffer += token.value;
192
- } else if (
193
- /^\[<\d+;\d+;\d+[Mm]$/.test(token.value) ||
194
- /^\[M[\x60-\x7f][\x20-\uffff]{2}$/.test(token.value)
195
- ) {
196
- // Orphaned SGR/X10 mouse tail (fullscreen only). A heavy render blocked
197
- // the event loop past the pending-ESC flush timer, so the buffered ESC
198
- // flushed as a lone Escape and the continuation `[<btn;col;rowM` arrived
199
- // as text. Re-synthesize with the ESC prefix so the scroll/mouse event
200
- // still fires. X10 Cb slot narrowed to wheel range [\x60-\x7f] so typed
201
- // input like `[MAX]` batched into one read isn't dropped as a click.
202
- const resynthesized = '\x1b' + token.value;
203
- const mouse = parseMouseEvent(resynthesized);
204
- keys.push(mouse ?? asKey(resynthesized));
205
- } else {
206
- // Split 0x7F (DEL) and 0x08 (BS) bytes into individual key events so
207
- // held-backspace chunks (`\x7f\x7f\x7f`) don't parse as one garbage
208
- // key. \r and \t are left intact — only backspace bytes are split.
209
- let run = '';
210
- for (const ch of token.value) {
211
- if (ch === '\x7f' || ch === '\x08') {
212
- if (run) {
213
- keys.push(asKey(run));
214
- run = '';
215
- }
216
- keys.push(asKey(ch));
217
- } else {
218
- run += ch;
219
- }
220
- }
221
- if (run) keys.push(asKey(run));
222
- }
236
+ const isText = token.type === 'text';
237
+ if (!isText && token.value === PASTE_START) {
238
+ paste.start();
239
+ continue;
240
+ }
241
+ if (!isText && token.value === PASTE_END) {
242
+ // An empty paste still emits a key so callers can react to it.
243
+ events.push(createPasteKey(paste.take()));
244
+ continue;
223
245
  }
246
+ if (paste.open) {
247
+ paste.absorb(token.value);
248
+ continue;
249
+ }
250
+ if (!isText) {
251
+ events.push(eventForSequence(token.value));
252
+ continue;
253
+ }
254
+ const orphan = eventForOrphanTail(token.value);
255
+ if (orphan) {
256
+ events.push(orphan);
257
+ continue;
258
+ }
259
+ for (const piece of splitEditingBytes(token.value)) events.push(asKey(piece));
224
260
  }
225
261
 
226
- if (isFlush && inPaste && pasteBuffer) {
227
- keys.push(createPasteKey(pasteBuffer));
228
- inPaste = false;
229
- pasteBuffer = '';
230
- }
262
+ // A paste left open at end-of-stream is delivered rather than dropped.
263
+ if (flushing && paste.open && paste.buffer) events.push(createPasteKey(paste.take()));
231
264
 
232
- const newState = {
233
- mode: inPaste ? 'IN_PASTE' : 'NORMAL',
265
+ return [events, {
266
+ mode: paste.open ? 'IN_PASTE' : 'NORMAL',
234
267
  incomplete: tokenizer.buffer(),
235
- pasteBuffer,
268
+ pasteBuffer: paste.buffer,
236
269
  _tokenizer: tokenizer,
237
- };
238
-
239
- return [keys, newState];
270
+ }];
240
271
  }
@@ -1,240 +0,0 @@
1
- /**
2
- * termio-keypress.js — chunked keypress pipeline built on the termio tokenizer.
3
- *
4
- * Keypress parsing (parseMultipleKeypresses +
5
- * paste IN_PASTE buffering + orphaned mouse-tail resynthesis + terminal-response
6
- * / SGR-mouse recognition) to plain ESM JS.
7
- *
8
- * It emits a flat list of typed events:
9
- * { kind: 'key', sequence, isPasted, ... } normal keypress or paste
10
- * { kind: 'mouse', button, action, col, row, sequence } SGR click/drag
11
- * { kind: 'response', sequence, response } terminal query reply
12
- *
13
- * Single-sequence keypress decoding is delegated to the existing kitty-aware
14
- * parseKeypress (parse-keypress.js) so kitty/modifyOtherKeys handling is shared.
15
- */
16
- import { createTokenizer, PASTE_START, PASTE_END } from './_baseline-termio-tokenize.js';
17
- import parseKeypress from './parse-keypress.js';
18
-
19
- // SGR mouse event: CSI < button ; col ; row M (press) or m (release).
20
- // Button bit 0x40 = wheel, bit 0x20 = drag/motion.
21
- // eslint-disable-next-line no-control-regex
22
- const SGR_MOUSE_RE = /^\x1b\[<(\d+);(\d+);(\d+)([Mm])$/;
23
-
24
- // Wheel button classification shared by SGR + X10 decoding. Button bit 0x40 =
25
- // wheel; low bit selects direction (0=up, 1=down). Mask 0x43 checks wheel-flag
26
- // + direction while ignoring modifier bits so modified wheel events still
27
- // classify. The vendored parse-keypress.js (enquirer-based) has NO mouse
28
- // branches, so wheel naming lives here.
29
- function wheelName(s) {
30
- let button;
31
- const m = SGR_MOUSE_RE.exec(s);
32
- if (m) {
33
- button = parseInt(m[1], 10);
34
- } else if (s.length === 6 && s.startsWith('\x1b[M')) {
35
- // X10: CSI M + 3 bytes (Cb+32, Cx+32, Cy+32).
36
- button = s.charCodeAt(3) - 32;
37
- } else {
38
- return null;
39
- }
40
- if ((button & 0x43) === 0x40) return 'wheelup';
41
- if ((button & 0x43) === 0x41) return 'wheeldown';
42
- return null;
43
- }
44
-
45
- // -- Terminal response patterns (inbound sequences from the terminal itself) --
46
- // eslint-disable-next-line no-control-regex
47
- const DECRPM_RE = /^\x1b\[\?(\d+);(\d+)\$y$/;
48
- // eslint-disable-next-line no-control-regex
49
- const DA1_RE = /^\x1b\[\?([\d;]*)c$/;
50
- // eslint-disable-next-line no-control-regex
51
- const DA2_RE = /^\x1b\[>([\d;]*)c$/;
52
- // eslint-disable-next-line no-control-regex
53
- const KITTY_FLAGS_RE = /^\x1b\[\?(\d+)u$/;
54
- // eslint-disable-next-line no-control-regex
55
- const CURSOR_POSITION_RE = /^\x1b\[\?(\d+);(\d+)R$/;
56
- // eslint-disable-next-line no-control-regex
57
- const OSC_RESPONSE_RE = /^\x1b\](\d+);(.*?)(?:\x07|\x1b\\)$/s;
58
- // eslint-disable-next-line no-control-regex
59
- const XTVERSION_RE = /^\x1bP>\|(.*?)(?:\x07|\x1b\\)$/s;
60
-
61
- /** DECRPM status values (response to DECRQM). */
62
- export const DECRPM_STATUS = {
63
- NOT_RECOGNIZED: 0,
64
- SET: 1,
65
- RESET: 2,
66
- PERMANENTLY_SET: 3,
67
- PERMANENTLY_RESET: 4,
68
- };
69
-
70
- function splitNumericParams(params) {
71
- if (!params) return [];
72
- return params.split(';').map((p) => parseInt(p, 10));
73
- }
74
-
75
- /**
76
- * Recognize a sequence token as a terminal response, or null if it should be
77
- * treated as a keypress. These patterns are syntactically distinguishable from
78
- * keyboard input (no physical key produces CSI ? ... c etc.).
79
- */
80
- function parseTerminalResponse(s) {
81
- if (s.startsWith('\x1b[')) {
82
- let m;
83
- if ((m = DECRPM_RE.exec(s))) {
84
- return { type: 'decrpm', mode: parseInt(m[1], 10), status: parseInt(m[2], 10) };
85
- }
86
- if ((m = DA1_RE.exec(s))) return { type: 'da1', params: splitNumericParams(m[1]) };
87
- if ((m = DA2_RE.exec(s))) return { type: 'da2', params: splitNumericParams(m[1]) };
88
- if ((m = KITTY_FLAGS_RE.exec(s))) return { type: 'kittyKeyboard', flags: parseInt(m[1], 10) };
89
- if ((m = CURSOR_POSITION_RE.exec(s))) {
90
- return { type: 'cursorPosition', row: parseInt(m[1], 10), col: parseInt(m[2], 10) };
91
- }
92
- return null;
93
- }
94
- if (s.startsWith('\x1b]')) {
95
- const m = OSC_RESPONSE_RE.exec(s);
96
- if (m) return { type: 'osc', code: parseInt(m[1], 10), data: m[2] };
97
- }
98
- if (s.startsWith('\x1bP')) {
99
- const m = XTVERSION_RE.exec(s);
100
- if (m) return { type: 'xtversion', name: m[1] };
101
- }
102
- return null;
103
- }
104
-
105
- /**
106
- * Parse an SGR mouse sequence into a ParsedMouse, or null if not a mouse event
107
- * or if it's a wheel event (wheel stays a keypress for scroll routing).
108
- */
109
- function parseMouseEvent(s) {
110
- const match = SGR_MOUSE_RE.exec(s);
111
- if (!match) return null;
112
- const button = parseInt(match[1], 10);
113
- // Wheel events (bit 6 set) stay as keys so the scroll path can route them.
114
- if ((button & 0x40) !== 0) return null;
115
- return {
116
- kind: 'mouse',
117
- button,
118
- action: match[4] === 'M' ? 'press' : 'release',
119
- col: parseInt(match[2], 10),
120
- row: parseInt(match[3], 10),
121
- sequence: s,
122
- };
123
- }
124
-
125
- function createPasteKey(content) {
126
- const key = parseKeypress('');
127
- key.kind = 'key';
128
- key.sequence = content;
129
- key.raw = content;
130
- key.isPasted = true;
131
- return key;
132
- }
133
-
134
- function asKey(s) {
135
- const key = parseKeypress(s);
136
- key.kind = 'key';
137
- if (key.isPasted === undefined) key.isPasted = false;
138
- // Vendored parse-keypress.js has no mouse branches; classify wheel here so
139
- // App.js can route wheel keys to the 'mouse' channel.
140
- const wheel = wheelName(s);
141
- if (wheel) key.name = wheel;
142
- return key;
143
- }
144
-
145
- export const INITIAL_STATE = {
146
- mode: 'NORMAL',
147
- incomplete: '',
148
- pasteBuffer: '',
149
- };
150
-
151
- /**
152
- * Feed a chunk (or null to flush) through the tokenizer + paste/mouse/response
153
- * classification. Returns [events, newState]. The tokenizer instance lives on
154
- * the returned state so incomplete sequences buffer across calls.
155
- */
156
- export function parseMultipleKeypresses(prevState, input = '') {
157
- const isFlush = input === null;
158
- const inputString = isFlush ? '' : typeof input === 'string' ? input : String(input ?? '');
159
-
160
- const tokenizer = prevState._tokenizer ?? createTokenizer({ x10Mouse: true });
161
- const tokens = isFlush ? tokenizer.flush() : tokenizer.feed(inputString);
162
-
163
- const keys = [];
164
- let inPaste = prevState.mode === 'IN_PASTE';
165
- let pasteBuffer = prevState.pasteBuffer;
166
-
167
- for (const token of tokens) {
168
- if (token.type === 'sequence') {
169
- if (token.value === PASTE_START) {
170
- inPaste = true;
171
- pasteBuffer = '';
172
- } else if (token.value === PASTE_END) {
173
- // Always emit a paste key, even for empty pastes.
174
- keys.push(createPasteKey(pasteBuffer));
175
- inPaste = false;
176
- pasteBuffer = '';
177
- } else if (inPaste) {
178
- // Sequences inside paste are literal text.
179
- pasteBuffer += token.value;
180
- } else {
181
- const response = parseTerminalResponse(token.value);
182
- if (response) {
183
- keys.push({ kind: 'response', sequence: token.value, response });
184
- } else {
185
- const mouse = parseMouseEvent(token.value);
186
- keys.push(mouse ?? asKey(token.value));
187
- }
188
- }
189
- } else if (token.type === 'text') {
190
- if (inPaste) {
191
- pasteBuffer += token.value;
192
- } else if (
193
- /^\[<\d+;\d+;\d+[Mm]$/.test(token.value) ||
194
- /^\[M[\x60-\x7f][\x20-\uffff]{2}$/.test(token.value)
195
- ) {
196
- // Orphaned SGR/X10 mouse tail (fullscreen only). A heavy render blocked
197
- // the event loop past the pending-ESC flush timer, so the buffered ESC
198
- // flushed as a lone Escape and the continuation `[<btn;col;rowM` arrived
199
- // as text. Re-synthesize with the ESC prefix so the scroll/mouse event
200
- // still fires. X10 Cb slot narrowed to wheel range [\x60-\x7f] so typed
201
- // input like `[MAX]` batched into one read isn't dropped as a click.
202
- const resynthesized = '\x1b' + token.value;
203
- const mouse = parseMouseEvent(resynthesized);
204
- keys.push(mouse ?? asKey(resynthesized));
205
- } else {
206
- // Split 0x7F (DEL) and 0x08 (BS) bytes into individual key events so
207
- // held-backspace chunks (`\x7f\x7f\x7f`) don't parse as one garbage
208
- // key. \r and \t are left intact — only backspace bytes are split.
209
- let run = '';
210
- for (const ch of token.value) {
211
- if (ch === '\x7f' || ch === '\x08') {
212
- if (run) {
213
- keys.push(asKey(run));
214
- run = '';
215
- }
216
- keys.push(asKey(ch));
217
- } else {
218
- run += ch;
219
- }
220
- }
221
- if (run) keys.push(asKey(run));
222
- }
223
- }
224
- }
225
-
226
- if (isFlush && inPaste && pasteBuffer) {
227
- keys.push(createPasteKey(pasteBuffer));
228
- inPaste = false;
229
- pasteBuffer = '';
230
- }
231
-
232
- const newState = {
233
- mode: inPaste ? 'IN_PASTE' : 'NORMAL',
234
- incomplete: tokenizer.buffer(),
235
- pasteBuffer,
236
- _tokenizer: tokenizer,
237
- };
238
-
239
- return [keys, newState];
240
- }