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,113 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { Box, Text, useInput, useStdout } from "ink";
4
- import { loadConfig, getActiveProvider, fetchModels } from "../config.js";
5
- import { theme } from "./theme.js";
6
- /**
7
- * Worst-case terminal rows the picker occupies (header/filter + status rows +
8
- * list + footer + borders), used by App so the message area never overlaps it.
9
- */
10
- export function modelPickerRows(terminalRows) {
11
- const maxListRows = Math.max(4, terminalRows - 11);
12
- return Math.min(6 + maxListRows + 3, Math.max(6, terminalRows - 2));
13
- }
14
- /**
15
- * Interactive model picker — replaces the input bar while open.
16
- * - Fetches the provider's model list, falls back to cache
17
- * - Type to filter, ↑↓ to navigate, Enter to select, Esc to cancel
18
- * - With a non-matching filter, Enter applies the typed name directly
19
- * - List is capped so the picker never overflows the terminal
20
- */
21
- export function ModelPicker({ currentModel, onSelect, onCancel }) {
22
- const [models, setModels] = useState(null);
23
- const [query, setQuery] = useState("");
24
- const [index, setIndex] = useState(0);
25
- const { stdout } = useStdout();
26
- useEffect(() => {
27
- let cancelled = false;
28
- const cfg = loadConfig();
29
- const provider = getActiveProvider(cfg);
30
- if (provider?.baseURL && provider?.apiKey) {
31
- fetchModels(provider.baseURL, provider.apiKey)
32
- .then((m) => {
33
- if (!cancelled)
34
- setModels(m);
35
- })
36
- .catch(() => {
37
- if (!cancelled)
38
- setModels([]);
39
- });
40
- }
41
- else {
42
- setModels([]);
43
- }
44
- return () => {
45
- cancelled = true;
46
- };
47
- }, []);
48
- const terminalRows = stdout.rows || 24;
49
- const maxListRows = Math.max(4, terminalRows - 11); // chrome + status bar + safety
50
- const q = query.trim().toLowerCase();
51
- const filtered = (models ?? []).filter((m) => m.toLowerCase().includes(q));
52
- const shownIndex = Math.min(index, Math.max(0, filtered.length - 1));
53
- const windowStart = Math.min(shownIndex, Math.max(0, filtered.length - maxListRows));
54
- const shown = filtered.slice(windowStart, windowStart + maxListRows);
55
- const extraAbove = windowStart;
56
- const extraBelow = filtered.length - (windowStart + shown.length);
57
- useEffect(() => {
58
- setIndex(0);
59
- }, []);
60
- useInput((input, key) => {
61
- const cleanInput = input.replace(/[\r\n]+$/, "");
62
- const isEnter = key.return || /[\r\n]+$/.test(input);
63
- if (key.escape) {
64
- onCancel();
65
- return;
66
- }
67
- if (key.pageDown) {
68
- if (filtered.length > 0)
69
- setIndex((i) => Math.min(filtered.length - 1, i + maxListRows));
70
- return;
71
- }
72
- if (key.pageUp) {
73
- if (filtered.length > 0)
74
- setIndex((i) => Math.max(0, i - maxListRows));
75
- return;
76
- }
77
- if (filtered.length > 0) {
78
- if (key.downArrow) {
79
- setIndex((i) => (i + 1) % filtered.length);
80
- return;
81
- }
82
- if (key.upArrow) {
83
- setIndex((i) => (i - 1 + filtered.length) % filtered.length);
84
- return;
85
- }
86
- }
87
- if (isEnter) {
88
- if (filtered.length > 0)
89
- onSelect(filtered[shownIndex]);
90
- else if (q !== "")
91
- onSelect(q);
92
- return;
93
- }
94
- if (key.backspace || key.delete) {
95
- setQuery((v) => Array.from(v).slice(0, -1).join(""));
96
- return;
97
- }
98
- if (key.ctrl && input === "u") {
99
- setQuery("");
100
- return;
101
- }
102
- if (key.ctrl || key.meta || key.tab)
103
- return;
104
- if (cleanInput)
105
- setQuery((v) => v + cleanInput);
106
- });
107
- const loading = models === null;
108
- const empty = models !== null && filtered.length === 0 && q === "";
109
- // Debounce filter: avoid re-filtering on every keystroke burst
110
- const debouncedQ = q;
111
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: "100%", borderStyle: "round", borderColor: theme.pickerBorder, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.pickerBorder, children: "\u9009\u62E9\u6A21\u578B" }), _jsx(Text, { color: theme.muted, children: currentModel ? ` (当前: ${currentModel})` : "" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "\u7B5B\u9009: " }), _jsx(Text, { color: query ? theme.accent : theme.muted, children: query || "输入字符过滤,↑↓/PgUpDn 导航" })] }), loading && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, children: "\u6B63\u5728\u83B7\u53D6\u6A21\u578B\u5217\u8868..." }) })), empty && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, children: "\u65E0\u6CD5\u83B7\u53D6\u6A21\u578B\u5217\u8868\uFF08\u53EF\u8F93\u5165\u6A21\u578B\u540D\u540E\u6309 Enter\uFF09" }) })), models !== null && extraAbove > 0 && (_jsx(Box, { children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2026 \u4E0A\u65B9\u8FD8\u6709 ", extraAbove, " \u4E2A\u6A21\u578B"] }) })), models !== null &&
112
- shown.map((m, i) => (_jsxs(Box, { children: [_jsx(Text, { color: i === shownIndex - windowStart ? theme.accent : theme.muted, children: i === shownIndex - windowStart ? "❯ " : " " }), _jsx(Text, { bold: i === shownIndex - windowStart, color: i === shownIndex - windowStart ? theme.accent : undefined, children: m }), m === currentModel && _jsx(Text, { color: theme.success, children: " \u2190 \u5F53\u524D" })] }, m))), extraBelow > 0 && (_jsx(Box, { children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2026 \u4E0B\u65B9\u8FD8\u6709 ", extraBelow, " \u4E2A\u6A21\u578B\uFF08\u8F93\u5165\u5B57\u7B26\u8FC7\u6EE4\uFF09"] }) })), models !== null && filtered.length === 0 && debouncedQ !== "" && (_jsxs(Box, { children: [_jsxs(Text, { color: theme.accent, bold: true, children: ["\u276F", " "] }), _jsxs(Text, { color: theme.accent, bold: true, children: ["\u4F7F\u7528\u81EA\u5B9A\u4E49\u6A21\u578B: ", debouncedQ] })] })), _jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, wrap: "truncate-end", children: "\u2191\u2193/PgUpDn \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" }) })] }));
113
- }
@@ -1,136 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useMemo, useState } from "react";
3
- import { Box, Text, useInput } from "ink";
4
- import { wrapByWidth } from "./text-width.js";
5
- import { footerMaxRows, innerTextWidth, QUESTION_CHROME_ROWS, sliceLineWindow } from "./layout.js";
6
- import { theme } from "./theme.js";
7
- import { normalizeQuestionOptions, normalizeQuestionPrompt } from "../question-format.js";
8
- export function questionBodyLines(prompt, options, columns) {
9
- const textWidth = innerTextWidth(columns);
10
- const promptLines = `❓ ${normalizeQuestionPrompt(prompt)}`
11
- .split("\n")
12
- .flatMap((line) => wrapByWidth(line, textWidth));
13
- const optionLines = normalizeQuestionOptions(options).flatMap((o, i) => ` ${i + 1}. ${o}`.split("\n").flatMap((l) => wrapByWidth(l, textWidth)));
14
- return { lines: [...promptLines, ...optionLines], promptLineCount: promptLines.length };
15
- }
16
- /**
17
- * Terminal rows the question bar occupies, mirroring the JSX layout below.
18
- * Capped so a huge prompt cannot push the input row off the screen.
19
- */
20
- export function questionBarRows(prompt, options, columns, frameRowCount = 0) {
21
- const natural = questionBodyLines(prompt, options, columns).lines.length + QUESTION_CHROME_ROWS;
22
- return Math.min(natural, footerMaxRows(frameRowCount, QUESTION_CHROME_ROWS));
23
- }
24
- /** Free-form question prompt: type an answer (or a number to pick an option), Enter to submit, Esc to cancel. */
25
- export function QuestionBar({ prompt, options, onAnswer, columns, maxRows }) {
26
- const [value, setValue] = useState("");
27
- const [caret, setCaret] = useState(0);
28
- const [top, setTop] = useState(0);
29
- const { lines, promptLineCount } = useMemo(() => questionBodyLines(prompt, options, columns), [prompt, options, columns]);
30
- const bodyMax = Math.max(0, maxRows - QUESTION_CHROME_ROWS);
31
- const window = useMemo(() => sliceLineWindow(lines, bodyMax, top), [lines, bodyMax, top]);
32
- const scrollable = window.above > 0 || window.below > 0;
33
- const choices = normalizeQuestionOptions(options);
34
- useInput((input, key) => {
35
- if (key.escape) {
36
- onAnswer(null);
37
- return;
38
- }
39
- if (key.return) {
40
- if (/^\d+$/.test(value.trim()) && choices.length > 0) {
41
- const n = parseInt(value.trim(), 10);
42
- if (n >= 1 && n <= choices.length) {
43
- onAnswer(choices[n - 1]);
44
- return;
45
- }
46
- }
47
- onAnswer(value.trim());
48
- return;
49
- }
50
- if (scrollable) {
51
- const view = Math.max(1, window.lines.length);
52
- if (key.upArrow) {
53
- setTop((t) => sliceLineWindow(lines, bodyMax, t - 1).start);
54
- return;
55
- }
56
- if (key.downArrow) {
57
- setTop((t) => sliceLineWindow(lines, bodyMax, t + 1).start);
58
- return;
59
- }
60
- if (key.pageUp) {
61
- setTop((t) => sliceLineWindow(lines, bodyMax, t - view).start);
62
- return;
63
- }
64
- if (key.pageDown) {
65
- setTop((t) => sliceLineWindow(lines, bodyMax, t + view).start);
66
- return;
67
- }
68
- }
69
- if (key.leftArrow) {
70
- setCaret((c) => Math.max(0, c - 1));
71
- return;
72
- }
73
- if (key.rightArrow) {
74
- setCaret((c) => Math.min(Array.from(value).length, c + 1));
75
- return;
76
- }
77
- if (key.backspace || key.delete) {
78
- if (key.backspace) {
79
- if (caret === 0)
80
- return;
81
- const chars = Array.from(value);
82
- chars.splice(caret - 1, 1);
83
- setValue(chars.join(""));
84
- setCaret((c) => c - 1);
85
- }
86
- else {
87
- const chars = Array.from(value);
88
- if (caret >= chars.length)
89
- return;
90
- chars.splice(caret, 1);
91
- setValue(chars.join(""));
92
- }
93
- return;
94
- }
95
- if (key.ctrl && input === "a") {
96
- setCaret(0);
97
- return;
98
- }
99
- if (key.ctrl && input === "e") {
100
- setCaret(Array.from(value).length);
101
- return;
102
- }
103
- if (key.ctrl && input === "u") {
104
- setValue("");
105
- setCaret(0);
106
- return;
107
- }
108
- if (key.ctrl && input === "k") {
109
- const chars = Array.from(value);
110
- chars.splice(caret);
111
- setValue(chars.join(""));
112
- return;
113
- }
114
- if (key.ctrl || key.meta)
115
- return;
116
- if (key.upArrow || key.downArrow || key.tab)
117
- return;
118
- if (input) {
119
- const clean = input.replace(/[\r\n]+$/, "");
120
- if (!clean)
121
- return;
122
- const chars = Array.from(value);
123
- const ins = Array.from(clean);
124
- chars.splice(caret, 0, ...ins);
125
- setValue(chars.join(""));
126
- setCaret((c) => c + ins.length);
127
- }
128
- });
129
- const hint = scrollable
130
- ? "Enter 提交 · Esc 取消 · ↑↓ 滚动 · 数字选选项"
131
- : "Enter 提交 · Esc 取消 · ←→ 光标 · 数字选选项";
132
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: "100%", borderStyle: "round", borderColor: theme.questionBorder, paddingX: 1, children: [bodyMax > 0 && (_jsxs(Box, { flexDirection: "column", height: bodyMax, overflow: "hidden", flexShrink: 0, width: "100%", children: [window.above > 0 && (_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2026 \u4EE5\u4E0A ", window.above, " \u884C"] }) })), window.lines.map((line, i) => {
133
- const isPrompt = window.start + i < promptLineCount;
134
- return (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { bold: isPrompt, color: isPrompt ? theme.questionBorder : theme.muted, wrap: "truncate-end", children: line }) }, i));
135
- }), window.below > 0 && (_jsx(Box, { flexShrink: 0, children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2193 \u8FD8\u6709 ", window.below, " \u884C"] }) }))] })), _jsxs(Box, { marginTop: 1, flexShrink: 0, height: 1, overflow: "hidden", width: "100%", children: [_jsxs(Text, { bold: true, color: theme.questionBorder, children: ["\u2192", " "] }), _jsx(Text, { children: Array.from(value).slice(0, caret).join("") }), _jsx(Text, { color: theme.muted, inverse: true, children: " " }), _jsx(Text, { children: Array.from(value).slice(caret).join("") })] }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: theme.muted, dimColor: true, wrap: "truncate-end", children: hint }) })] }));
136
- }
@@ -1,79 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from "react";
3
- import { Box, Text, useInput, useStdout } from "ink";
4
- import { listSessions } from "../sessions.js";
5
- import { theme } from "./theme.js";
6
- export function sessionPickerRows(terminalRows) {
7
- const maxListRows = Math.max(4, terminalRows - 11);
8
- return Math.min(6 + maxListRows + 3, Math.max(6, terminalRows - 2));
9
- }
10
- function label(s) {
11
- const date = s.updated.split("T")[0] ?? "";
12
- return `${s.id} ${date} ${s.title} (${s.messageCount})`;
13
- }
14
- export function SessionPicker({ currentId, onSelect, onCancel }) {
15
- const sessions = listSessions();
16
- const [query, setQuery] = useState("");
17
- const [index, setIndex] = useState(0);
18
- const { stdout } = useStdout();
19
- const terminalRows = stdout.rows || 24;
20
- const maxListRows = Math.max(4, terminalRows - 11);
21
- const q = query.trim().toLowerCase();
22
- const filtered = sessions.filter((s) => s.id.toLowerCase().includes(q) || s.title.toLowerCase().includes(q));
23
- const shownIndex = Math.min(index, Math.max(0, filtered.length - 1));
24
- const windowStart = Math.min(shownIndex, Math.max(0, filtered.length - maxListRows));
25
- const shown = filtered.slice(windowStart, windowStart + maxListRows);
26
- const extraAbove = windowStart;
27
- const extraBelow = filtered.length - (windowStart + shown.length);
28
- useEffect(() => {
29
- setIndex(0);
30
- }, []);
31
- useInput((input, key) => {
32
- const cleanInput = input.replace(/[\r\n]+$/, "");
33
- const isEnter = key.return || /[\r\n]+$/.test(input);
34
- if (key.escape) {
35
- onCancel();
36
- return;
37
- }
38
- if (key.pageDown) {
39
- if (filtered.length > 0)
40
- setIndex((i) => Math.min(filtered.length - 1, i + maxListRows));
41
- return;
42
- }
43
- if (key.pageUp) {
44
- if (filtered.length > 0)
45
- setIndex((i) => Math.max(0, i - maxListRows));
46
- return;
47
- }
48
- if (filtered.length > 0) {
49
- if (key.downArrow) {
50
- setIndex((i) => (i + 1) % filtered.length);
51
- return;
52
- }
53
- if (key.upArrow) {
54
- setIndex((i) => (i - 1 + filtered.length) % filtered.length);
55
- return;
56
- }
57
- }
58
- if (isEnter) {
59
- if (filtered.length > 0)
60
- onSelect(filtered[shownIndex].id);
61
- return;
62
- }
63
- if (key.backspace || key.delete) {
64
- setQuery((v) => Array.from(v).slice(0, -1).join(""));
65
- return;
66
- }
67
- if (key.ctrl && input === "u") {
68
- setQuery("");
69
- return;
70
- }
71
- if (key.ctrl || key.meta || key.tab)
72
- return;
73
- if (cleanInput)
74
- setQuery((v) => v + cleanInput);
75
- });
76
- const empty = filtered.length === 0 && q === "";
77
- const noMatch = filtered.length === 0 && q !== "";
78
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: "100%", borderStyle: "round", borderColor: theme.pickerBorder, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.pickerBorder, children: "\u9009\u62E9\u4F1A\u8BDD" }), _jsx(Text, { color: theme.muted, children: currentId ? ` (当前: ${currentId})` : "" })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.muted, children: "\u7B5B\u9009: " }), _jsx(Text, { color: query ? theme.accent : theme.muted, children: query || "输入字符过滤标题或 id" })] }), empty && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, children: "\u65E0\u5DF2\u4FDD\u5B58\u7684\u4F1A\u8BDD" }) })), noMatch && (_jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, children: "\u6CA1\u6709\u5339\u914D\u7684\u4F1A\u8BDD" }) })), extraAbove > 0 && (_jsx(Box, { children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2026 \u4E0A\u65B9\u8FD8\u6709 ", extraAbove, " \u4E2A\u4F1A\u8BDD"] }) })), shown.map((s, i) => (_jsxs(Box, { children: [_jsx(Text, { color: i === shownIndex - windowStart ? theme.accent : theme.muted, children: i === shownIndex - windowStart ? "❯ " : " " }), _jsx(Text, { bold: i === shownIndex - windowStart, color: i === shownIndex - windowStart ? theme.accent : undefined, wrap: "truncate-end", children: label(s) }), s.id === currentId && _jsx(Text, { color: theme.success, children: " \u2190" })] }, s.id))), extraBelow > 0 && (_jsx(Box, { children: _jsxs(Text, { color: theme.muted, dimColor: true, children: ["\u2026 \u4E0B\u65B9\u8FD8\u6709 ", extraBelow, " \u4E2A\u4F1A\u8BDD"] }) })), _jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, wrap: "truncate-end", children: "\u2191\u2193/PgUpDn \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" }) })] }));
79
- }
@@ -1,6 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import InkSpinner from "ink-spinner";
4
- export function Spinner({ label }) {
5
- return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(InkSpinner, { type: "dots" }) }), label && _jsxs(Text, { color: "gray", children: [" ", label] })] }));
6
- }
@@ -1,22 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, Text } from "ink";
3
- import { formatModelStatusMeta, formatTokenStatusLabel } from "../token-display.js";
4
- import { resolveThinkingEffort, thinkingEffortLabel } from "../thinking.js";
5
- import { isMemoryEnabled } from "../memory.js";
6
- import { displayWidth } from "./text-width.js";
7
- export function StatusBar({ state, columns = 80 }) {
8
- const model = state.model || "unknown";
9
- const thinking = thinkingEffortLabel(resolveThinkingEffort(undefined, state.model).thinking);
10
- const modelMeta = formatModelStatusMeta(thinking, state.tokenInfo?.contextWindow, isMemoryEnabled());
11
- const tokensLabel = state.tokenInfo ? formatTokenStatusLabel(state.tokenInfo) : "";
12
- // Transient copy feedback rides on the separator row itself (right end) so
13
- // it never adds a row and the layout does not jump while it is visible.
14
- const sepWidth = Math.max(8, columns - 2);
15
- const notice = state.copyNotice
16
- ? `✓ ${state.copyNotice}`
17
- : state.selectionMode
18
- ? "选择模式 ↑↓←→扩选 Enter复制 Esc退出"
19
- : null;
20
- const noticeWidth = notice ? displayWidth(notice) : 0;
21
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: "100%", children: [_jsx(Text, { dimColor: true, wrap: "truncate-end", children: notice ? `${"─".repeat(Math.max(0, sepWidth - noticeWidth - 2))} ${notice}` : "─".repeat(sepWidth) }), _jsxs(Box, { children: [_jsx(Text, { children: " \uD83E\uDD16 min-agent" }), _jsxs(Text, { color: "gray", wrap: "truncate-end", children: [" ", "(", model, " \u00B7 ", modelMeta, ")", tokensLabel ? ` ${tokensLabel}` : ""] })] })] }));
22
- }
@@ -1,77 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { Box, Text, useInput } from "ink";
4
- import { getActiveProvider, getEffectiveConfig } from "../config.js";
5
- import { getModelCatalog } from "../model-catalog.js";
6
- import { resolveThinkingEffort, thinkingChoiceIndex, thinkingChoicesForModel, thinkingChoicesFromReasoning, thinkingEffortLabel, thinkingSourceLabel, } from "../thinking.js";
7
- import { shouldAcceptOverlayConfirm } from "./overlay-input.js";
8
- import { theme } from "./theme.js";
9
- export function thinkPickerRows() {
10
- return 2 + 1 + 5 + 1 + 2;
11
- }
12
- export function ThinkPicker({ scope, modelId, onSelect, onCancel }) {
13
- const provider = getActiveProvider(getEffectiveConfig());
14
- const id = modelId ?? provider?.defaultModel;
15
- const current = resolveThinkingEffort(undefined, id);
16
- const [choices, setChoices] = useState(() => thinkingChoicesForModel(id));
17
- const [index, setIndex] = useState(() => thinkingChoiceIndex(current.thinking, thinkingChoicesForModel(id)));
18
- const armed = useRef(false);
19
- const moved = useRef(false);
20
- const indexRef = useRef(index);
21
- const choicesRef = useRef(choices);
22
- indexRef.current = index;
23
- choicesRef.current = choices;
24
- useEffect(() => {
25
- const timer = setTimeout(() => {
26
- armed.current = true;
27
- }, 0);
28
- return () => clearTimeout(timer);
29
- }, []);
30
- useEffect(() => {
31
- if (!id)
32
- return;
33
- let cancelled = false;
34
- void getModelCatalog(id, provider?.baseURL).then((entry) => {
35
- if (cancelled || !entry)
36
- return;
37
- const next = thinkingChoicesFromReasoning(entry.reasoning);
38
- setChoices(next);
39
- if (moved.current) {
40
- setIndex((i) => Math.min(i, Math.max(0, next.length - 1)));
41
- return;
42
- }
43
- setIndex(thinkingChoiceIndex(resolveThinkingEffort(undefined, id).thinking, next));
44
- });
45
- return () => {
46
- cancelled = true;
47
- };
48
- }, [id, provider?.baseURL]);
49
- useInput((input, key) => {
50
- if (key.escape) {
51
- onCancel();
52
- return;
53
- }
54
- const list = choicesRef.current;
55
- if (list.length === 0)
56
- return;
57
- if (key.downArrow) {
58
- moved.current = true;
59
- setIndex((i) => (i + 1) % list.length);
60
- return;
61
- }
62
- if (key.upArrow) {
63
- moved.current = true;
64
- setIndex((i) => (i - 1 + list.length) % list.length);
65
- return;
66
- }
67
- if (shouldAcceptOverlayConfirm(armed.current, key, input)) {
68
- const picked = list[indexRef.current];
69
- if (picked)
70
- onSelect(picked);
71
- }
72
- });
73
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, width: "100%", borderStyle: "round", borderColor: theme.pickerBorder, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.pickerBorder, children: "\u9009\u62E9\u601D\u8003\u5F3A\u5EA6" }), scope === "project" && _jsx(Text, { color: theme.muted, children: "\uFF08\u5199\u5165\u5F53\u524D\u9879\u76EE\uFF09" })] }), _jsx(Box, { children: _jsxs(Text, { color: theme.muted, children: ["\u5F53\u524D: ", thinkingEffortLabel(current.thinking), "\uFF08", thinkingSourceLabel(current.source), "\uFF09"] }) }), choices.map((effort, i) => {
74
- const active = i === index;
75
- return (_jsxs(Box, { children: [_jsx(Text, { color: active ? theme.accent : theme.muted, children: active ? "❯ " : " " }), _jsx(Text, { bold: active, color: active ? theme.accent : undefined, children: thinkingEffortLabel(effort) }), effort === current.thinking && _jsx(Text, { color: theme.success, children: " \u2190 \u5F53\u524D" })] }, effort));
76
- }), _jsx(Box, { children: _jsx(Text, { color: theme.muted, dimColor: true, wrap: "truncate-end", children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88" }) })] }));
77
- }
@@ -1,223 +0,0 @@
1
- export function createAgentRunner(deps) {
2
- let abortController = null;
3
- const getController = () => abortController;
4
- const abort = (tui) => {
5
- if (!abortController)
6
- return false;
7
- import("../tools/bash.js").then((m) => m.killActiveProcesses()).catch(() => { });
8
- abortController.abort();
9
- abortController = null;
10
- tui.setRunning(false);
11
- tui.addMessage({ id: `sys-${Date.now()}`, role: "system", content: "(已取消)", timestamp: Date.now() });
12
- return true;
13
- };
14
- const run = async () => {
15
- const turnStart = deps.messages.length;
16
- const uiStart = deps.tui.messageCount();
17
- let compactedDuringRun = false;
18
- abortController = new AbortController();
19
- deps.tui.setRunning(true, "思考中...");
20
- const { summarizeToolCall, summarizeToolResult, toolResultText } = await import("../tool-display.js");
21
- const { getContextWindowInfo } = await import("../context-window.js");
22
- const { getModelPrice, estimateCost, formatCost } = await import("../pricing.js");
23
- const { runOnceWithSystem, emptyResponseMessage, WRAP_UP_MESSAGE } = await import("../agent.js");
24
- const { clearStack, pushTurn } = await import("./undo-stack.js");
25
- const { setSubAgentListener } = await import("../tools/task.js");
26
- const { LOOP_HALT_MESSAGE } = await import("../doom-loop.js");
27
- const { createSaveThrottle } = await import("../save-throttle.js");
28
- const { snapshotTokenInfo } = await import("./token-info.js");
29
- // Persist while the run is in flight so a crash/kill does not lose the turn.
30
- const saveThrottle = createSaveThrottle(() => deps.persist?.());
31
- const billedInAtStart = deps.tracker.totalInputTokens;
32
- const billedOutAtStart = deps.tracker.totalOutputTokens;
33
- let tokenTimer = null;
34
- let tokenPaintSeq = 0;
35
- const applyTokenInfo = async () => {
36
- const seq = ++tokenPaintSeq;
37
- const info = await snapshotTokenInfo(deps.tracker, deps.getModel(), deps.messages);
38
- if (seq !== tokenPaintSeq)
39
- return;
40
- deps.tui.setTokenInfo(info);
41
- };
42
- const scheduleTokenInfo = () => {
43
- if (tokenTimer != null)
44
- return;
45
- tokenTimer = setTimeout(() => {
46
- tokenTimer = null;
47
- void applyTokenInfo();
48
- }, 50);
49
- };
50
- const flushTokenInfo = async () => {
51
- if (tokenTimer != null) {
52
- clearTimeout(tokenTimer);
53
- tokenTimer = null;
54
- }
55
- await applyTokenInfo();
56
- };
57
- const unsubscribeTokens = deps.tracker.subscribe(scheduleTokenInfo);
58
- void applyTokenInfo();
59
- const callbacks = {
60
- onAssistantDisplayDelta: (delta) => deps.tui.appendToLast(delta),
61
- onThinkingDelta: (delta) => {
62
- deps.tui.setRunning(true, "推理中...");
63
- deps.tui.appendToThinking(delta);
64
- },
65
- onToolCall: (toolName, input, toolCallId) => {
66
- deps.tui.setRunning(true, `执行 ${toolName}...`);
67
- deps.tui.addMessage({
68
- id: `tool-${toolCallId || `${Date.now()}-${Math.random().toString(36).slice(2, 6)}`}`,
69
- role: "tool",
70
- content: summarizeToolCall(toolName, input),
71
- toolName,
72
- toolCallId,
73
- timestamp: Date.now(),
74
- });
75
- },
76
- onToolResult: (toolName, output, meta) => {
77
- deps.tui.setRunning(true, "思考中...");
78
- deps.tui.setToolResult(meta.toolCallId, toolName, summarizeToolResult(toolName, output, meta.isError), toolResultText(output), meta.isError);
79
- },
80
- onStreamError: (msg) => {
81
- deps.tui.addMessage({ id: `err-${Date.now()}`, role: "system", content: `错误: ${msg}`, timestamp: Date.now() });
82
- },
83
- onCompaction: (line) => {
84
- compactedDuringRun = true;
85
- deps.setUndoStack(clearStack(deps.getUndoStack()));
86
- if (line === "compacting_start") {
87
- deps.tui.addMessage({
88
- id: `compact-${Date.now()}`,
89
- role: "system",
90
- content: "正在整理过长的对话记录…",
91
- timestamp: Date.now(),
92
- });
93
- }
94
- else if (line.startsWith("compacted_ok")) {
95
- deps.tui.addMessage({
96
- id: `compact-ok-${Date.now()}`,
97
- role: "system",
98
- content: "✓ 已整理对话记录,继续执行",
99
- timestamp: Date.now(),
100
- });
101
- }
102
- },
103
- onRetryNotice: (info) => {
104
- deps.tui.addMessage({
105
- id: `retry-${Date.now()}`,
106
- role: "system",
107
- content: info.kind === "wrap_up"
108
- ? "⟳ 本轮预算用尽,正在收尾…"
109
- : `⟳ 模型返回空响应,重试 ${info.attempt}/${info.maxAttempts}…`,
110
- timestamp: Date.now(),
111
- });
112
- },
113
- onRunFinish: async (info) => {
114
- abortController = null;
115
- deps.tui.setRunning(false);
116
- await flushTokenInfo();
117
- if (info.aborted)
118
- return;
119
- if (info.budgetExceeded)
120
- deps.tui.addMessage({
121
- id: `budget-${Date.now()}`,
122
- role: "system",
123
- content: "⚠ 已达到预算上限。/budget 查看或调高",
124
- timestamp: Date.now(),
125
- });
126
- const runInput = Math.max(0, deps.tracker.totalInputTokens - billedInAtStart);
127
- const runOutput = Math.max(0, deps.tracker.totalOutputTokens - billedOutAtStart);
128
- const occupied = deps.tracker.lastInputTokens;
129
- if (runInput > 0 || runOutput > 0 || occupied > 0) {
130
- const ctx = await getContextWindowInfo(deps.getModel());
131
- const pct = ctx.tokens > 0 && occupied > 0 ? Math.round((occupied / ctx.tokens) * 100) : null;
132
- const price = await getModelPrice(deps.getModel() ?? "");
133
- const cost = estimateCost({ inputTokens: runInput, outputTokens: runOutput }, price);
134
- const parts = [
135
- `Done in ${info.stepCount} step(s)`,
136
- `Tokens: ${runInput} in / ${runOutput} out / ${runInput + runOutput} total`,
137
- ...(pct != null ? [`Context: ${pct}%`] : []),
138
- ...(cost != null ? [`约 ${formatCost(cost)}`] : []),
139
- ];
140
- deps.tui.addMessage({
141
- id: `done-${Date.now()}`,
142
- role: "system",
143
- content: parts.join(" | "),
144
- timestamp: Date.now(),
145
- });
146
- }
147
- if (info.maxStepsReached)
148
- deps.tui.addMessage({
149
- id: `maxsteps-${Date.now()}`,
150
- role: "system",
151
- content: "⚠ 已达到自动续跑上限,发送消息继续",
152
- timestamp: Date.now(),
153
- });
154
- if (info.stopped)
155
- deps.tui.addMessage({
156
- id: `stopped-${Date.now()}`,
157
- role: "system",
158
- content: `⚠ ${LOOP_HALT_MESSAGE}`,
159
- timestamp: Date.now(),
160
- });
161
- else if (info.wrapUp)
162
- deps.tui.addMessage({
163
- id: `wrapup-${Date.now()}`,
164
- role: "system",
165
- content: `⚠ ${WRAP_UP_MESSAGE}`,
166
- timestamp: Date.now(),
167
- });
168
- else if (info.emptyResponse)
169
- deps.tui.addMessage({
170
- id: `empty-${Date.now()}`,
171
- role: "system",
172
- content: `⚠ ${emptyResponseMessage(info.emptyAttempts ?? 0)}`,
173
- timestamp: Date.now(),
174
- });
175
- else if (info.incomplete)
176
- deps.tui.addMessage({
177
- id: `incomplete-${Date.now()}`,
178
- role: "system",
179
- content: "还没有完整回复,发送消息可继续",
180
- timestamp: Date.now(),
181
- });
182
- },
183
- };
184
- try {
185
- setSubAgentListener((ev) => {
186
- if (ev.phase !== "call")
187
- return;
188
- deps.tui.appendToolChild(ev.parentToolCallId, {
189
- name: ev.name,
190
- summary: summarizeToolCall(ev.name, ev.input),
191
- });
192
- });
193
- const runOptions = {
194
- providerName: deps.getProvider(),
195
- taskState: deps.getTaskState(),
196
- onStepPersist: () => saveThrottle.request(),
197
- ...(deps.getPlanMode() ? { planMode: true } : {}),
198
- };
199
- await runOnceWithSystem(deps.messages, deps.getSystemPrompt(), deps.getModel(), abortController?.signal, callbacks, deps.tracker, runOptions);
200
- }
201
- catch (err) {
202
- if (!abortController)
203
- return;
204
- const msg = err instanceof Error ? err.message : String(err);
205
- callbacks.onStreamError(msg);
206
- }
207
- finally {
208
- unsubscribeTokens();
209
- if (tokenTimer != null) {
210
- clearTimeout(tokenTimer);
211
- tokenTimer = null;
212
- }
213
- setSubAgentListener(null);
214
- saveThrottle.cancel();
215
- if (!compactedDuringRun)
216
- deps.setUndoStack(pushTurn(deps.getUndoStack(), turnStart, deps.messages.length, uiStart));
217
- abortController = null;
218
- deps.tui.setRunning(false);
219
- await flushTokenInfo();
220
- }
221
- };
222
- return { run, abort, getController };
223
- }