min-agent 0.5.0 → 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 (156) hide show
  1. package/README.md +32 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +25 -8
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +7 -3
  6. package/skills/self-config/reference.md +8 -4
  7. package/dist/agent.js +0 -1342
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/exec.js +0 -35
  11. package/dist/cli/commands/history.js +0 -58
  12. package/dist/cli/commands/index.js +0 -241
  13. package/dist/cli/commands/init.js +0 -18
  14. package/dist/cli/commands/mcp.js +0 -173
  15. package/dist/cli/commands/memory.js +0 -87
  16. package/dist/cli/commands/models.js +0 -21
  17. package/dist/cli/commands/permission.js +0 -12
  18. package/dist/cli/commands/rules.js +0 -33
  19. package/dist/cli/commands/sandbox.js +0 -13
  20. package/dist/cli/commands/serve.js +0 -9
  21. package/dist/cli/commands/setup.js +0 -4
  22. package/dist/cli/commands/shared.js +0 -16
  23. package/dist/cli/commands/skills.js +0 -119
  24. package/dist/cli/commands/think.js +0 -12
  25. package/dist/cli/commands/update.js +0 -7
  26. package/dist/cli/commands/write-config.js +0 -52
  27. package/dist/cli/errors.js +0 -36
  28. package/dist/cli/exec-prompt.js +0 -26
  29. package/dist/cli/option-helpers.js +0 -65
  30. package/dist/cli/program.js +0 -217
  31. package/dist/clipboard.js +0 -142
  32. package/dist/code-mode.js +0 -184
  33. package/dist/compaction.js +0 -644
  34. package/dist/config.js +0 -470
  35. package/dist/confirm.js +0 -172
  36. package/dist/context-window.js +0 -246
  37. package/dist/doom-loop.js +0 -278
  38. package/dist/fetch-timeout.js +0 -152
  39. package/dist/http-approvals.js +0 -60
  40. package/dist/http.js +0 -119
  41. package/dist/instructions.js +0 -196
  42. package/dist/logger.js +0 -95
  43. package/dist/markdown.js +0 -259
  44. package/dist/mcp.js +0 -1036
  45. package/dist/memory-cli.js +0 -33
  46. package/dist/memory.js +0 -295
  47. package/dist/model-catalog.js +0 -285
  48. package/dist/output.js +0 -76
  49. package/dist/paste-handler.js +0 -41
  50. package/dist/permission-cli.js +0 -40
  51. package/dist/plugins.js +0 -131
  52. package/dist/pricing.js +0 -119
  53. package/dist/provider.js +0 -63
  54. package/dist/question-format.js +0 -60
  55. package/dist/reasoning-stream.js +0 -158
  56. package/dist/sandbox-cli.js +0 -79
  57. package/dist/sandbox.js +0 -403
  58. package/dist/save-throttle.js +0 -45
  59. package/dist/scope.js +0 -23
  60. package/dist/serve/common.js +0 -425
  61. package/dist/serve/routes-chat.js +0 -367
  62. package/dist/serve/routes-mcp.js +0 -212
  63. package/dist/serve/routes-memory.js +0 -95
  64. package/dist/serve/routes-meta.js +0 -233
  65. package/dist/serve/routes-sessions.js +0 -61
  66. package/dist/serve/routes-skills.js +0 -70
  67. package/dist/serve.js +0 -138
  68. package/dist/sessions.js +0 -265
  69. package/dist/skills.js +0 -599
  70. package/dist/structured-output.js +0 -29
  71. package/dist/synthetic.js +0 -7
  72. package/dist/think-cli.js +0 -36
  73. package/dist/thinking-wire.js +0 -228
  74. package/dist/thinking.js +0 -142
  75. package/dist/title-gen.js +0 -55
  76. package/dist/token-display.js +0 -39
  77. package/dist/tool-display.js +0 -178
  78. package/dist/tool-output.js +0 -126
  79. package/dist/tools/apply_patch.js +0 -265
  80. package/dist/tools/atomic-file.js +0 -35
  81. package/dist/tools/backend.js +0 -61
  82. package/dist/tools/bash.js +0 -231
  83. package/dist/tools/code_search.js +0 -98
  84. package/dist/tools/edit.js +0 -68
  85. package/dist/tools/explore.js +0 -232
  86. package/dist/tools/glob.js +0 -40
  87. package/dist/tools/grep.js +0 -174
  88. package/dist/tools/index.js +0 -34
  89. package/dist/tools/question.js +0 -54
  90. package/dist/tools/read.js +0 -109
  91. package/dist/tools/search-searxng.js +0 -223
  92. package/dist/tools/search-serper.js +0 -189
  93. package/dist/tools/task.js +0 -165
  94. package/dist/tools/todo.js +0 -213
  95. package/dist/tools/web_fetch.js +0 -195
  96. package/dist/tools/web_search.js +0 -228
  97. package/dist/tools/write.js +0 -55
  98. package/dist/tui/App.js +0 -126
  99. package/dist/tui/ConfirmBar.js +0 -66
  100. package/dist/tui/InputBar.js +0 -616
  101. package/dist/tui/MessageList.js +0 -718
  102. package/dist/tui/ModelPicker.js +0 -113
  103. package/dist/tui/QuestionBar.js +0 -136
  104. package/dist/tui/SessionPicker.js +0 -79
  105. package/dist/tui/Spinner.js +0 -6
  106. package/dist/tui/StatusBar.js +0 -22
  107. package/dist/tui/ThinkPicker.js +0 -77
  108. package/dist/tui/agent-runner.js +0 -223
  109. package/dist/tui/bracketed-paste.js +0 -37
  110. package/dist/tui/caret-pos.js +0 -179
  111. package/dist/tui/caret.js +0 -69
  112. package/dist/tui/click-count.js +0 -13
  113. package/dist/tui/diff-view.js +0 -61
  114. package/dist/tui/drag-state.js +0 -49
  115. package/dist/tui/hydrate.js +0 -129
  116. package/dist/tui/index.js +0 -258
  117. package/dist/tui/input-history.js +0 -125
  118. package/dist/tui/layout.js +0 -105
  119. package/dist/tui/mouse.js +0 -46
  120. package/dist/tui/overlay-input.js +0 -12
  121. package/dist/tui/paste-draft.js +0 -173
  122. package/dist/tui/prompt-queue.js +0 -24
  123. package/dist/tui/selection.js +0 -232
  124. package/dist/tui/session-switch.js +0 -28
  125. package/dist/tui/slash-commands.js +0 -123
  126. package/dist/tui/slash-handler.js +0 -589
  127. package/dist/tui/text-width.js +0 -113
  128. package/dist/tui/theme.js +0 -12
  129. package/dist/tui/token-info.js +0 -7
  130. package/dist/tui/tool-children.js +0 -19
  131. package/dist/tui/types.js +0 -1
  132. package/dist/tui/undo-stack.js +0 -14
  133. package/dist/tui/use-sgr-mouse.js +0 -29
  134. package/dist/tui-chat.js +0 -456
  135. package/dist/updater.js +0 -116
  136. package/dist/xml-search.js +0 -194
  137. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  138. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  139. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  140. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  141. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  142. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  143. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  144. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  145. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  146. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  147. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  148. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  149. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  150. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  151. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  152. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  153. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  154. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  155. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  156. 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
- }