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,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,75 +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 { resolveThinkingEffort, thinkingChoiceIndex, thinkingChoicesForModel, thinkingEffortLabel, thinkingSourceLabel, refreshThinkingChoices, } from "../thinking.js";
6
- import { shouldAcceptOverlayConfirm } from "./overlay-input.js";
7
- import { theme } from "./theme.js";
8
- export function thinkPickerRows() {
9
- return 2 + 1 + 5 + 1 + 2;
10
- }
11
- export function ThinkPicker({ scope, modelId, onSelect, onCancel }) {
12
- const provider = getActiveProvider(getEffectiveConfig());
13
- const id = modelId ?? provider?.defaultModel;
14
- const current = resolveThinkingEffort(undefined, id);
15
- const [choices, setChoices] = useState(() => thinkingChoicesForModel(id));
16
- const [index, setIndex] = useState(() => thinkingChoiceIndex(current.thinking, thinkingChoicesForModel(id)));
17
- const armed = useRef(false);
18
- const moved = useRef(false);
19
- const indexRef = useRef(index);
20
- const choicesRef = useRef(choices);
21
- indexRef.current = index;
22
- choicesRef.current = choices;
23
- useEffect(() => {
24
- const timer = setTimeout(() => {
25
- armed.current = true;
26
- }, 0);
27
- return () => clearTimeout(timer);
28
- }, []);
29
- useEffect(() => {
30
- if (!id)
31
- return;
32
- let cancelled = false;
33
- void refreshThinkingChoices(id, { type: provider?.type, baseURL: provider?.baseURL }).then((next) => {
34
- if (cancelled || next.length === 0)
35
- return;
36
- setChoices(next);
37
- if (moved.current) {
38
- setIndex((i) => Math.min(i, Math.max(0, next.length - 1)));
39
- return;
40
- }
41
- setIndex(thinkingChoiceIndex(resolveThinkingEffort(undefined, id).thinking, next));
42
- });
43
- return () => {
44
- cancelled = true;
45
- };
46
- }, [id, provider?.baseURL, provider?.type]);
47
- useInput((input, key) => {
48
- if (key.escape) {
49
- onCancel();
50
- return;
51
- }
52
- const list = choicesRef.current;
53
- if (list.length === 0)
54
- return;
55
- if (key.downArrow) {
56
- moved.current = true;
57
- setIndex((i) => (i + 1) % list.length);
58
- return;
59
- }
60
- if (key.upArrow) {
61
- moved.current = true;
62
- setIndex((i) => (i - 1 + list.length) % list.length);
63
- return;
64
- }
65
- if (shouldAcceptOverlayConfirm(armed.current, key, input)) {
66
- const picked = list[indexRef.current];
67
- if (picked)
68
- onSelect(picked);
69
- }
70
- });
71
- 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) => {
72
- const active = i === index;
73
- 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));
74
- }), _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" }) })] }));
75
- }
@@ -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
- }