min-agent 0.5.1 → 0.6.0

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 (169) hide show
  1. package/README.md +18 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +6 -6
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +4 -2
  6. package/skills/self-config/reference.md +4 -1
  7. package/dist/agent.js +0 -1388
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/ctx.js +0 -7
  11. package/dist/cli/commands/exec.js +0 -35
  12. package/dist/cli/commands/history.js +0 -58
  13. package/dist/cli/commands/index.js +0 -249
  14. package/dist/cli/commands/init.js +0 -18
  15. package/dist/cli/commands/mcp.js +0 -173
  16. package/dist/cli/commands/memory.js +0 -87
  17. package/dist/cli/commands/models.js +0 -21
  18. package/dist/cli/commands/permission.js +0 -12
  19. package/dist/cli/commands/rules.js +0 -33
  20. package/dist/cli/commands/sandbox.js +0 -13
  21. package/dist/cli/commands/serve.js +0 -9
  22. package/dist/cli/commands/setup.js +0 -56
  23. package/dist/cli/commands/shared.js +0 -25
  24. package/dist/cli/commands/skills.js +0 -119
  25. package/dist/cli/commands/think.js +0 -12
  26. package/dist/cli/commands/update.js +0 -7
  27. package/dist/cli/commands/write-config.js +0 -52
  28. package/dist/cli/errors.js +0 -36
  29. package/dist/cli/exec-prompt.js +0 -26
  30. package/dist/cli/option-helpers.js +0 -65
  31. package/dist/cli/program.js +0 -223
  32. package/dist/cli/setup/detect.js +0 -17
  33. package/dist/cli/setup/flags.js +0 -12
  34. package/dist/cli/setup/flow.js +0 -108
  35. package/dist/cli/setup/provider-form.js +0 -102
  36. package/dist/cli/setup/ui.js +0 -534
  37. package/dist/clipboard.js +0 -142
  38. package/dist/code-mode.js +0 -184
  39. package/dist/compaction.js +0 -644
  40. package/dist/config.js +0 -363
  41. package/dist/confirm.js +0 -172
  42. package/dist/context-window.js +0 -256
  43. package/dist/ctx-cli.js +0 -30
  44. package/dist/ctx.js +0 -80
  45. package/dist/doom-loop.js +0 -278
  46. package/dist/fetch-timeout.js +0 -152
  47. package/dist/http-approvals.js +0 -60
  48. package/dist/http.js +0 -119
  49. package/dist/instructions.js +0 -196
  50. package/dist/logger.js +0 -95
  51. package/dist/markdown.js +0 -259
  52. package/dist/mcp.js +0 -1036
  53. package/dist/memory-cli.js +0 -33
  54. package/dist/memory.js +0 -295
  55. package/dist/model-catalog.js +0 -285
  56. package/dist/ollama-model.js +0 -234
  57. package/dist/ollama-openai-bridge.js +0 -383
  58. package/dist/output.js +0 -76
  59. package/dist/paste-handler.js +0 -41
  60. package/dist/permission-cli.js +0 -40
  61. package/dist/plugins.js +0 -131
  62. package/dist/pricing.js +0 -119
  63. package/dist/provider.js +0 -63
  64. package/dist/question-format.js +0 -60
  65. package/dist/reasoning-stream.js +0 -158
  66. package/dist/sandbox-cli.js +0 -79
  67. package/dist/sandbox.js +0 -403
  68. package/dist/save-throttle.js +0 -45
  69. package/dist/scope.js +0 -23
  70. package/dist/serve/common.js +0 -425
  71. package/dist/serve/routes-chat.js +0 -367
  72. package/dist/serve/routes-mcp.js +0 -212
  73. package/dist/serve/routes-memory.js +0 -95
  74. package/dist/serve/routes-meta.js +0 -268
  75. package/dist/serve/routes-sessions.js +0 -61
  76. package/dist/serve/routes-skills.js +0 -70
  77. package/dist/serve.js +0 -138
  78. package/dist/sessions.js +0 -265
  79. package/dist/skills.js +0 -599
  80. package/dist/structured-output.js +0 -29
  81. package/dist/synthetic.js +0 -7
  82. package/dist/think-cli.js +0 -36
  83. package/dist/thinking-wire.js +0 -239
  84. package/dist/thinking.js +0 -166
  85. package/dist/title-gen.js +0 -55
  86. package/dist/token-display.js +0 -39
  87. package/dist/tool-display.js +0 -178
  88. package/dist/tool-output.js +0 -126
  89. package/dist/tools/apply_patch.js +0 -265
  90. package/dist/tools/atomic-file.js +0 -35
  91. package/dist/tools/backend.js +0 -61
  92. package/dist/tools/bash.js +0 -231
  93. package/dist/tools/code_search.js +0 -98
  94. package/dist/tools/edit.js +0 -68
  95. package/dist/tools/explore.js +0 -232
  96. package/dist/tools/glob.js +0 -40
  97. package/dist/tools/grep.js +0 -174
  98. package/dist/tools/index.js +0 -34
  99. package/dist/tools/question.js +0 -54
  100. package/dist/tools/read.js +0 -109
  101. package/dist/tools/search-searxng.js +0 -223
  102. package/dist/tools/search-serper.js +0 -189
  103. package/dist/tools/task.js +0 -165
  104. package/dist/tools/todo.js +0 -213
  105. package/dist/tools/web_fetch.js +0 -195
  106. package/dist/tools/web_search.js +0 -228
  107. package/dist/tools/write.js +0 -55
  108. package/dist/tui/App.js +0 -138
  109. package/dist/tui/ConfirmBar.js +0 -66
  110. package/dist/tui/CtxPicker.js +0 -68
  111. package/dist/tui/InputBar.js +0 -618
  112. package/dist/tui/MessageList.js +0 -718
  113. package/dist/tui/ModelPicker.js +0 -113
  114. package/dist/tui/QuestionBar.js +0 -136
  115. package/dist/tui/SessionPicker.js +0 -79
  116. package/dist/tui/Spinner.js +0 -6
  117. package/dist/tui/StatusBar.js +0 -22
  118. package/dist/tui/ThinkPicker.js +0 -75
  119. package/dist/tui/agent-runner.js +0 -223
  120. package/dist/tui/bracketed-paste.js +0 -37
  121. package/dist/tui/caret-pos.js +0 -179
  122. package/dist/tui/caret.js +0 -69
  123. package/dist/tui/click-count.js +0 -13
  124. package/dist/tui/diff-view.js +0 -61
  125. package/dist/tui/drag-state.js +0 -49
  126. package/dist/tui/hydrate.js +0 -129
  127. package/dist/tui/index.js +0 -264
  128. package/dist/tui/input-history.js +0 -125
  129. package/dist/tui/layout.js +0 -105
  130. package/dist/tui/mouse.js +0 -46
  131. package/dist/tui/overlay-input.js +0 -12
  132. package/dist/tui/paste-draft.js +0 -173
  133. package/dist/tui/prompt-queue.js +0 -24
  134. package/dist/tui/selection.js +0 -232
  135. package/dist/tui/session-switch.js +0 -28
  136. package/dist/tui/slash-commands.js +0 -129
  137. package/dist/tui/slash-handler.js +0 -615
  138. package/dist/tui/text-width.js +0 -113
  139. package/dist/tui/theme.js +0 -12
  140. package/dist/tui/token-info.js +0 -7
  141. package/dist/tui/tool-children.js +0 -19
  142. package/dist/tui/types.js +0 -1
  143. package/dist/tui/undo-stack.js +0 -14
  144. package/dist/tui/use-sgr-mouse.js +0 -29
  145. package/dist/tui-chat.js +0 -478
  146. package/dist/updater.js +0 -116
  147. package/dist/xml-search.js +0 -194
  148. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  149. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  150. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  151. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  152. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  153. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  154. package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
  155. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  156. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  157. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  158. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  159. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  160. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  161. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  162. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  163. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  164. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  165. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  166. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  167. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
  168. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  169. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,37 +0,0 @@
1
- export const BRACKETED_PASTE_ENABLE = "\x1b[?2004h";
2
- export const BRACKETED_PASTE_DISABLE = "\x1b[?2004l";
3
- const PASTE_START = "\x1b[200~";
4
- const PASTE_END = "\x1b[201~";
5
- export function scanBracketedPaste(buffer, chunk) {
6
- const pastes = [];
7
- let buf = buffer + chunk;
8
- while (true) {
9
- const start = buf.indexOf(PASTE_START);
10
- if (start === -1) {
11
- return {
12
- buffer: pastes.length > 0 ? buf : incompleteStartPrefix(buf),
13
- pastes,
14
- active: false,
15
- };
16
- }
17
- const body = buf.slice(start + PASTE_START.length);
18
- const end = body.indexOf(PASTE_END);
19
- if (end === -1) {
20
- return { buffer: PASTE_START + body, pastes, active: true };
21
- }
22
- pastes.push(body.slice(0, end));
23
- buf = body.slice(end + PASTE_END.length);
24
- }
25
- }
26
- export function shouldIgnorePasteInput(input, active) {
27
- if (active)
28
- return true;
29
- return input.includes(PASTE_START) || input.includes(PASTE_END) || input.includes("[200~") || input.includes("[201~");
30
- }
31
- function incompleteStartPrefix(buf) {
32
- const esc = buf.lastIndexOf("\x1b");
33
- if (esc === -1)
34
- return "";
35
- const prefix = buf.slice(esc);
36
- return PASTE_START.startsWith(prefix) ? prefix : "";
37
- }
@@ -1,179 +0,0 @@
1
- import { displayWidth, wrapByWidth, clustersOf } from "./text-width.js";
2
- /** Split the value into visual rows, tracking each row's character start index (newlines count as one). */
3
- export function visualRows(value, maxWidth, lookup) {
4
- const rows = [];
5
- let start = 0;
6
- const lines = value.split("\n");
7
- for (let i = 0; i < lines.length; i++) {
8
- for (const row of wrapByWidth(lines[i], maxWidth, lookup)) {
9
- rows.push({ text: row, startIndex: start });
10
- start += Array.from(row).length;
11
- }
12
- if (i < lines.length - 1)
13
- start += 1;
14
- }
15
- return rows;
16
- }
17
- /** Move the caret by one code point; clamps at the start/end of the value. */
18
- export function moveCaretHorizontal(value, caretIndex, direction) {
19
- const len = Array.from(value).length;
20
- return Math.min(Math.max(caretIndex + direction, 0), len);
21
- }
22
- /**
23
- * Map a click column (1-based terminal column) on a visual row to a character
24
- * index in the value. Fullwidth characters snap to the nearer boundary.
25
- */
26
- export function caretIndexFromClick(rows, rowIndex, clickColumn, textStartColumn, lookup) {
27
- const row = rows[rowIndex];
28
- if (!row)
29
- return 0;
30
- const target = clickColumn - textStartColumn;
31
- if (target <= 0)
32
- return row.startIndex;
33
- let width = 0;
34
- let idx = 0;
35
- for (const { text: g, width: w } of clustersOf(row.text, lookup)) {
36
- if (width + w > target) {
37
- if (lookup?.(g) !== undefined)
38
- return row.startIndex + idx;
39
- return row.startIndex + idx + (target - width < w / 2 ? 0 : Array.from(g).length);
40
- }
41
- width += w;
42
- idx += Array.from(g).length;
43
- }
44
- return row.startIndex + idx;
45
- }
46
- /** Insert text at the caret; returns the new value and caret index. */
47
- export function insertAt(value, caretIndex, text) {
48
- const chars = Array.from(value);
49
- chars.splice(caretIndex, 0, ...Array.from(text));
50
- return { value: chars.join(""), caretIndex: caretIndex + Array.from(text).length };
51
- }
52
- /** Remove the code point before the caret. */
53
- export function backspaceAt(value, caretIndex) {
54
- if (caretIndex <= 0)
55
- return { value, caretIndex };
56
- const chars = Array.from(value);
57
- chars.splice(caretIndex - 1, 1);
58
- return { value: chars.join(""), caretIndex: caretIndex - 1 };
59
- }
60
- /** Remove the code point at the caret. */
61
- export function deleteAt(value, caretIndex) {
62
- const chars = Array.from(value);
63
- if (caretIndex >= chars.length)
64
- return { value, caretIndex };
65
- chars.splice(caretIndex, 1);
66
- return { value: chars.join(""), caretIndex };
67
- }
68
- /** Move the caret one visual row up (`-1`) or down (`1`), keeping the display column. */
69
- export function moveCaretVertical(value, caretIndex, direction, maxWidth, lookup) {
70
- const rows = visualRows(value, maxWidth, lookup);
71
- let rowIndex = 0;
72
- let offset = 0;
73
- for (let i = 0; i < rows.length; i++) {
74
- const row = rows[i];
75
- const end = row.startIndex + Array.from(row.text).length;
76
- if (caretIndex >= row.startIndex && caretIndex <= end) {
77
- rowIndex = i;
78
- offset = caretIndex - row.startIndex;
79
- break;
80
- }
81
- if (i === rows.length - 1) {
82
- rowIndex = i;
83
- offset = Array.from(row.text).length;
84
- }
85
- }
86
- const next = rowIndex + direction;
87
- if (next < 0 || next >= rows.length)
88
- return caretIndex;
89
- const col = displayWidth(Array.from(rows[rowIndex].text).slice(0, offset).join(""), lookup);
90
- return caretIndexFromClick(rows, next, col, 0, lookup);
91
- }
92
- export function moveToLineStart(value, caretIndex) {
93
- const chars = Array.from(value);
94
- let i = caretIndex - 1;
95
- while (i >= 0 && chars[i] !== "\n")
96
- i--;
97
- return i + 1;
98
- }
99
- export function moveToLineEnd(value, caretIndex) {
100
- const chars = Array.from(value);
101
- let i = caretIndex;
102
- while (i < chars.length && chars[i] !== "\n")
103
- i++;
104
- return i;
105
- }
106
- export function deleteToLineStart(value, caretIndex) {
107
- const start = moveToLineStart(value, caretIndex);
108
- if (start === caretIndex)
109
- return { value, caretIndex };
110
- const chars = Array.from(value);
111
- chars.splice(start, caretIndex - start);
112
- return { value: chars.join(""), caretIndex: start };
113
- }
114
- export function deleteToLineEnd(value, caretIndex) {
115
- const end = moveToLineEnd(value, caretIndex);
116
- if (end === caretIndex)
117
- return { value, caretIndex };
118
- const chars = Array.from(value);
119
- chars.splice(caretIndex, end - caretIndex);
120
- return { value: chars.join(""), caretIndex };
121
- }
122
- export function deleteWordBefore(value, caretIndex) {
123
- if (caretIndex <= 0)
124
- return { value, caretIndex };
125
- const chars = Array.from(value);
126
- let i = caretIndex - 1;
127
- while (i >= 0 && /\s/.test(chars[i]))
128
- i--;
129
- while (i >= 0 && !/\s/.test(chars[i]))
130
- i--;
131
- const start = i + 1;
132
- chars.splice(start, caretIndex - start);
133
- return { value: chars.join(""), caretIndex: start };
134
- }
135
- /** The longest escape sequence this scanner recognises, kept for the cap. */
136
- const MAX_DELETE_PREFIX = 32;
137
- /**
138
- * Scan raw stdin bytes for Backspace (DEL, 0x7F — what most terminals send
139
- * for the Backspace key) and Delete sequences (CSI 3 ~ and its modifier
140
- * variants like CSI 3;5 ~ for Ctrl+Delete), which may span chunks. Ink
141
- * reports all of them as `key.delete`, so they are distinguished here.
142
- */
143
- export function scanDeleteKeys(buffer, chunk) {
144
- const buf = buffer + chunk;
145
- const actions = [];
146
- const re = /\x1b\[3(?:;\d+)?[~$^]|\x7f/g;
147
- let lastConsumedEnd = 0;
148
- let m;
149
- while ((m = re.exec(buf))) {
150
- lastConsumedEnd = m.index + m[0].length;
151
- actions.push(m[0] === "\x7f" ? { kind: "backspace" } : { kind: "delete" });
152
- }
153
- const tail = buf.slice(lastConsumedEnd);
154
- const esc = tail.lastIndexOf("\x1b");
155
- if (esc === -1)
156
- return { buffer: "", actions };
157
- const prefix = tail.slice(esc);
158
- return { buffer: prefix.length > MAX_DELETE_PREFIX ? "\x1b" : prefix, actions };
159
- }
160
- const HOME_END_RE = /\x1b\[(?:[HF]|[1478]~)/g;
161
- const MAX_HOME_END_PREFIX = 8;
162
- export function scanHomeEndKeys(buffer, chunk) {
163
- const buf = buffer + chunk;
164
- const actions = [];
165
- let lastConsumedEnd = 0;
166
- HOME_END_RE.lastIndex = 0;
167
- let m;
168
- while ((m = HOME_END_RE.exec(buf))) {
169
- lastConsumedEnd = m.index + m[0].length;
170
- const seq = m[0];
171
- actions.push(seq.endsWith("H") || seq.endsWith("1~") || seq.endsWith("7~") ? { kind: "home" } : { kind: "end" });
172
- }
173
- const tail = buf.slice(lastConsumedEnd);
174
- const esc = tail.lastIndexOf("\x1b");
175
- if (esc === -1)
176
- return { buffer: "", actions };
177
- const prefix = tail.slice(esc);
178
- return { buffer: prefix.length > MAX_HOME_END_PREFIX ? "\x1b" : prefix, actions };
179
- }
package/dist/tui/caret.js DELETED
@@ -1,69 +0,0 @@
1
- /**
2
- * Real terminal caret tracking for the TUI.
3
- *
4
- * Ink draws frames with `log-update`, which hides the hardware cursor and
5
- * always leaves it at the end of the frame. That is fine for ASCII typing
6
- * (the input bar paints its own `█` block), but an IME draws its preedit text
7
- * and candidate window at the *hardware* cursor. With the cursor parked below
8
- * the input box, Chinese input appears in the wrong place.
9
- *
10
- * The fix: after every frame Ink writes, move the hardware cursor onto the cell
11
- * where the input bar painted its caret, and undo that move right before the
12
- * next frame so Ink's own relative erase bookkeeping stays intact.
13
- */
14
- let caret = null;
15
- /** Called by the input bar on every render; `null` disables caret tracking. */
16
- export function setCaretPosition(position) {
17
- caret = position;
18
- }
19
- /**
20
- * Wrap a TTY stream so Ink's writes are followed by a caret move.
21
- *
22
- * Only the row offset needs undoing: Ink's frames always end at column 1
23
- * (either after a trailing newline, or after `clearTerminal` repositions the
24
- * cursor absolutely), so `\r` is enough to restore the column.
25
- */
26
- export function createCaretAwareStdout(base) {
27
- let pendingRowsUp = 0;
28
- const write = (chunk, encoding, callback) => {
29
- const isBuf = Buffer?.isBuffer(chunk);
30
- let chunkStr;
31
- if (typeof chunk === "string")
32
- chunkStr = chunk;
33
- else if (isBuf)
34
- chunkStr = chunk.toString(encoding || "utf-8");
35
- else if (chunk != null)
36
- chunkStr = String(chunk);
37
- else
38
- return base.write(chunk, encoding, callback);
39
- let out = "";
40
- if (pendingRowsUp > 0) {
41
- out += `\x1b[${pendingRowsUp}B\r`;
42
- pendingRowsUp = 0;
43
- }
44
- out += chunkStr;
45
- const position = caret;
46
- if (position) {
47
- const rowsUp = position.rowsAbove + (chunkStr.endsWith("\n") ? 1 : 0);
48
- if (rowsUp > 0) {
49
- out += `\x1b[${rowsUp}A\x1b[${position.column}G`;
50
- pendingRowsUp = rowsUp;
51
- }
52
- }
53
- const finalChunk = typeof chunk === "string" ? out : Buffer.from(out, encoding || "utf-8");
54
- return base.write(finalChunk, encoding, callback);
55
- };
56
- return new Proxy(base, {
57
- get(target, prop) {
58
- if (prop === "write")
59
- return write;
60
- // Read through to the real stream: getters like `columns`/`rows` and
61
- // methods like `on`/`off` must run with the stream as `this`.
62
- const value = Reflect.get(target, prop, target);
63
- return typeof value === "function" ? value.bind(target) : value;
64
- },
65
- set(target, prop, value) {
66
- return Reflect.set(target, prop, value, target);
67
- },
68
- });
69
- }
@@ -1,13 +0,0 @@
1
- export const DOUBLE_CLICK_MS = 400;
2
- export const DOUBLE_CLICK_COL_SLOP = 2;
3
- export function createClickCounter() {
4
- return { row: -1, col: -1, at: Number.NEGATIVE_INFINITY, count: 0 };
5
- }
6
- export function noteClick(c, ev) {
7
- const same = ev.row === c.row && Math.abs(ev.col - c.col) <= DOUBLE_CLICK_COL_SLOP && ev.at - c.at <= DOUBLE_CLICK_MS;
8
- c.count = same ? c.count + 1 : 1;
9
- c.row = ev.row;
10
- c.col = ev.col;
11
- c.at = ev.at;
12
- return c.count;
13
- }
@@ -1,61 +0,0 @@
1
- import { execFile } from "child_process";
2
- import { promisify } from "util";
3
- const MAX_DIFF_CHARS = 8000;
4
- const execFileAsync = promisify(execFile);
5
- export async function getWorkingTreeDiff(cwd = process.cwd(), run = defaultRun) {
6
- try {
7
- await run("git rev-parse --is-inside-work-tree", cwd);
8
- }
9
- catch {
10
- return { ok: false, reason: "not_git", message: "当前目录不是 git 仓库" };
11
- }
12
- let stat = "";
13
- let full = "";
14
- try {
15
- stat = await run("git diff --stat", cwd);
16
- full = await run("git diff", cwd);
17
- }
18
- catch (err) {
19
- return { ok: false, reason: "error", message: err instanceof Error ? err.message : String(err) };
20
- }
21
- if (!full.trim())
22
- return { ok: false, reason: "no_changes", message: "工作区无未提交变更" };
23
- const body = full.length > MAX_DIFF_CHARS ? full.slice(0, MAX_DIFF_CHARS) + "\n… [diff 过长,已截断]" : full;
24
- return { ok: true, diff: stat ? `${stat}\n\n${body}` : body };
25
- }
26
- async function defaultRun(cmd, cwd) {
27
- const parts = parseCmd(cmd);
28
- const { stdout } = await execFileAsync(parts[0], parts.slice(1), {
29
- cwd,
30
- encoding: "utf-8",
31
- timeout: 10000,
32
- maxBuffer: 10 * 1024 * 1024,
33
- });
34
- return stdout;
35
- }
36
- function parseCmd(cmd) {
37
- const out = [];
38
- let cur = "";
39
- let q = null;
40
- for (const ch of cmd) {
41
- if (q) {
42
- if (ch === q)
43
- q = null;
44
- else
45
- cur += ch;
46
- }
47
- else if (ch === '"' || ch === "'")
48
- q = ch;
49
- else if (ch === " ") {
50
- if (cur) {
51
- out.push(cur);
52
- cur = "";
53
- }
54
- }
55
- else
56
- cur += ch;
57
- }
58
- if (cur)
59
- out.push(cur);
60
- return out;
61
- }
@@ -1,49 +0,0 @@
1
- import { MOTION, MOD_MASK } from "./mouse.js";
2
- export function createDragMachine() {
3
- return { pending: null, selection: null };
4
- }
5
- export function clearDrag(machine) {
6
- machine.pending = null;
7
- machine.selection = null;
8
- }
9
- /**
10
- * Advance the drag state machine on one SGR mouse event (wheel events are
11
- * filtered by the caller before this runs). `maxHeight` is the full region
12
- * allocated to the message list, not just its rendered rows — a press on the
13
- * blank gap below a short conversation still starts a drag there, and the
14
- * selection math clamps out-of-range rows.
15
- *
16
- * Terminals disagree on drag motion: xterm sends button + 32 (MOTION), but
17
- * Apple Terminal reports it as a plain button-0 press. While a drag is in
18
- * progress either form extends the selection instead of resetting it —
19
- * otherwise every move would re-anchor and clear the highlight.
20
- */
21
- export function stepDrag(machine, event, maxHeight) {
22
- const { button, row, col, press } = event;
23
- const plain = (button & MOD_MASK) === 0;
24
- const dragging = machine.pending != null;
25
- if (plain && dragging && ((button & MOTION) !== 0 || (button === 0 && press))) {
26
- const origin = machine.pending;
27
- if (row === origin.row && col === origin.col && machine.selection == null)
28
- return null;
29
- machine.selection = { anchor: origin, cur: { row, col } };
30
- return { type: "selection", selection: machine.selection };
31
- }
32
- if (button === 0 && press) {
33
- machine.selection = null;
34
- machine.pending = row >= 1 && row <= maxHeight ? { row, col } : null;
35
- return { type: "selection", selection: null };
36
- }
37
- if (button === 0 && !press) {
38
- const active = machine.selection;
39
- const pending = machine.pending;
40
- machine.pending = null;
41
- machine.selection = null;
42
- if (active && (active.anchor.row !== active.cur.row || active.anchor.col !== active.cur.col)) {
43
- return { type: "copy", selection: active };
44
- }
45
- if (pending)
46
- return { type: "click", row: pending.row, col: pending.col };
47
- }
48
- return null;
49
- }
@@ -1,129 +0,0 @@
1
- import { summarizeToolCall, summarizeToolResult, toolResultText } from "../tool-display.js";
2
- import { extractXmlSearch } from "../xml-search.js";
3
- function isRecord(v) {
4
- return typeof v === "object" && v !== null;
5
- }
6
- function textFromContent(content) {
7
- if (typeof content === "string")
8
- return content;
9
- if (!Array.isArray(content))
10
- return "";
11
- const parts = [];
12
- for (const part of content) {
13
- if (isRecord(part) && part.type === "text" && typeof part.text === "string")
14
- parts.push(part.text);
15
- }
16
- return parts.join("");
17
- }
18
- function hasMediaPart(content) {
19
- if (!Array.isArray(content))
20
- return false;
21
- return content.some((part) => isRecord(part) && (part.type === "image" || part.type === "file"));
22
- }
23
- function unwrapToolOutput(output) {
24
- if (!isRecord(output))
25
- return output;
26
- if ((output.type === "text" || output.type === "json") && "value" in output)
27
- return output.value;
28
- return output;
29
- }
30
- function userText(content) {
31
- const text = textFromContent(content).trim();
32
- if (text)
33
- return textFromContent(content);
34
- if (hasMediaPart(content))
35
- return "[📎 图片]";
36
- return null;
37
- }
38
- export function lastUserText(messages) {
39
- for (let i = messages.length - 1; i >= 0; i--) {
40
- const msg = messages[i];
41
- if (msg?.role !== "user")
42
- continue;
43
- const text = textFromContent(msg.content).trim();
44
- if (text)
45
- return textFromContent(msg.content);
46
- }
47
- return null;
48
- }
49
- export function hydrateMessages(messages) {
50
- const out = [];
51
- for (let i = 0; i < messages.length; i++) {
52
- const msg = messages[i];
53
- if (msg.role === "assistant") {
54
- out.push(...hydrateAssistant(msg, i));
55
- continue;
56
- }
57
- const mapped = mapMessage(msg, i);
58
- if (mapped)
59
- out.push(mapped);
60
- }
61
- return out;
62
- }
63
- function hydrateAssistant(msg, index) {
64
- const content = textFromContent(msg.content);
65
- if (!content)
66
- return [];
67
- const { slices } = extractXmlSearch(content);
68
- if (slices.every((sl) => sl.type === "display")) {
69
- return [{ id: `hyd-${index}-assistant`, role: "assistant", content, timestamp: 0 }];
70
- }
71
- const out = [];
72
- let part = 0;
73
- for (const sl of slices) {
74
- if (sl.type === "display") {
75
- if (!sl.text.trim())
76
- continue;
77
- out.push({ id: `hyd-${index}-assistant-${part++}`, role: "assistant", content: sl.text, timestamp: 0 });
78
- continue;
79
- }
80
- const query = sl.block.query;
81
- const result = sl.block.kind === "results" ? sl.block.body : sl.block.query;
82
- const input = { query };
83
- out.push({
84
- id: `hyd-${index}-search-${part++}`,
85
- role: "tool",
86
- content: summarizeToolCall("search_web", input),
87
- toolName: "search_web",
88
- toolCallId: `xml-search-hyd-${index}-${part}`,
89
- toolResult: result,
90
- toolResultSummary: summarizeToolResult("search_web", result),
91
- timestamp: 0,
92
- });
93
- }
94
- return out;
95
- }
96
- function mapMessage(msg, index) {
97
- if (msg.role === "user") {
98
- const content = userText(msg.content);
99
- if (content == null)
100
- return null;
101
- return { id: `hyd-${index}-user`, role: "user", content, timestamp: 0 };
102
- }
103
- if (msg.role === "tool") {
104
- if (!Array.isArray(msg.content))
105
- return null;
106
- for (const part of msg.content) {
107
- if (!isRecord(part) || part.type !== "tool-result")
108
- continue;
109
- const toolName = typeof part.toolName === "string" ? part.toolName : "";
110
- if (!toolName)
111
- continue;
112
- const output = unwrapToolOutput(part.output);
113
- const result = toolResultText(output);
114
- const toolCallId = typeof part.toolCallId === "string" ? part.toolCallId : undefined;
115
- return {
116
- id: `hyd-${index}-tool`,
117
- role: "tool",
118
- content: toolName,
119
- toolName,
120
- toolCallId,
121
- toolResult: result,
122
- toolResultSummary: summarizeToolResult(toolName, output),
123
- timestamp: 0,
124
- };
125
- }
126
- return null;
127
- }
128
- return null;
129
- }