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
- /**
2
- * Terminal display-width helpers.
3
- *
4
- * CJK / fullwidth characters occupy two terminal cells, combining marks and
5
- * variation selectors occupy none. Width is measured per grapheme cluster so
6
- * ZWJ emoji (and Ink's own layout, which uses the same cluster rules) stay
7
- * aligned with the mouse column.
8
- */
9
- const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
10
- const EXT_PICT = /\p{Extended_Pictographic}/u;
11
- /** Zero-width: combining marks, variation selectors, zero-width spaces. */
12
- function isZeroWidth(cp) {
13
- return ((cp >= 0x0300 && cp <= 0x036f) ||
14
- (cp >= 0x0483 && cp <= 0x0489) ||
15
- (cp >= 0x0591 && cp <= 0x05bd) ||
16
- (cp >= 0x0610 && cp <= 0x061a) ||
17
- (cp >= 0x064b && cp <= 0x065f) ||
18
- (cp >= 0x0e31 && cp <= 0x0e3a) ||
19
- (cp >= 0x0e47 && cp <= 0x0e4e) ||
20
- (cp >= 0x1ab0 && cp <= 0x1aff) ||
21
- (cp >= 0x1dc0 && cp <= 0x1dff) ||
22
- (cp >= 0x200b && cp <= 0x200f) ||
23
- (cp >= 0x2060 && cp <= 0x2064) ||
24
- (cp >= 0x20d0 && cp <= 0x20ff) ||
25
- (cp >= 0xfe00 && cp <= 0xfe0f) ||
26
- (cp >= 0xfe20 && cp <= 0xfe2f) ||
27
- cp === 0xfeff ||
28
- (cp >= 0xe0100 && cp <= 0xe01ef));
29
- }
30
- /** East Asian Wide / Fullwidth, plus the emoji blocks terminals render double. */
31
- function isFullWidth(cp) {
32
- return ((cp >= 0x1100 && cp <= 0x115f) ||
33
- (cp >= 0x2e80 && cp <= 0x303e) ||
34
- (cp >= 0x3041 && cp <= 0x33ff) ||
35
- (cp >= 0x3400 && cp <= 0x4dbf) ||
36
- (cp >= 0x4e00 && cp <= 0x9fff) ||
37
- (cp >= 0xa000 && cp <= 0xa4cf) ||
38
- (cp >= 0xa960 && cp <= 0xa97f) ||
39
- (cp >= 0xac00 && cp <= 0xd7a3) ||
40
- (cp >= 0xf900 && cp <= 0xfaff) ||
41
- (cp >= 0xfe10 && cp <= 0xfe19) ||
42
- (cp >= 0xfe30 && cp <= 0xfe6f) ||
43
- (cp >= 0xff00 && cp <= 0xff60) ||
44
- (cp >= 0xffe0 && cp <= 0xffe6) ||
45
- (cp >= 0x1f300 && cp <= 0x1f64f) ||
46
- (cp >= 0x1f680 && cp <= 0x1f6ff) ||
47
- (cp >= 0x1f900 && cp <= 0x1f9ff) ||
48
- (cp >= 0x20000 && cp <= 0x2fffd) ||
49
- (cp >= 0x30000 && cp <= 0x3fffd));
50
- }
51
- function codePointWidth(char) {
52
- const cp = char.codePointAt(0);
53
- if (cp === undefined)
54
- return 0;
55
- if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0))
56
- return 0;
57
- if (isZeroWidth(cp))
58
- return 0;
59
- if (isFullWidth(cp) || EXT_PICT.test(char))
60
- return 2;
61
- return 1;
62
- }
63
- /** Display width of a grapheme cluster (or a single code point), in terminal cells. */
64
- export function clusterWidth(cluster) {
65
- if (cluster.length === 0)
66
- return 0;
67
- if (EXT_PICT.test(cluster) || cluster.includes("\u200D"))
68
- return 2;
69
- let width = 0;
70
- for (const ch of cluster)
71
- width += codePointWidth(ch);
72
- return cluster.includes("\uFE0F") ? Math.max(width, 2) : width;
73
- }
74
- /** Display width of a single code point, in terminal cells. */
75
- export function charWidth(char) {
76
- return clusterWidth(char);
77
- }
78
- export function* clustersOf(text, lookup) {
79
- for (const { segment } of segmenter.segment(text)) {
80
- yield { text: segment, width: lookup?.(segment) ?? clusterWidth(segment) };
81
- }
82
- }
83
- /** Display width of a string (single logical line, no ANSI escapes). */
84
- export function displayWidth(text, lookup) {
85
- let width = 0;
86
- for (const { width: w } of clustersOf(text, lookup))
87
- width += w;
88
- return width;
89
- }
90
- /**
91
- * Break a single logical line into visual rows of at most `maxWidth` cells.
92
- * Breaks on cluster boundaries (not word boundaries) so the caret column can
93
- * be derived exactly — a fullwidth character or emoji is never split across rows.
94
- */
95
- export function wrapByWidth(line, maxWidth, lookup) {
96
- const text = typeof line === "string" ? line : line == null ? "" : String(line);
97
- if (maxWidth < 1)
98
- return [text];
99
- const rows = [];
100
- let current = "";
101
- let width = 0;
102
- for (const { text: g, width: w } of clustersOf(text, lookup)) {
103
- if (width + w > maxWidth && current.length > 0) {
104
- rows.push(current);
105
- current = "";
106
- width = 0;
107
- }
108
- current += g;
109
- width += w;
110
- }
111
- rows.push(current);
112
- return rows;
113
- }
package/dist/tui/theme.js DELETED
@@ -1,12 +0,0 @@
1
- export const theme = {
2
- inputBorder: "cyan",
3
- inputBorderDisabled: "gray",
4
- confirmBorder: "blue",
5
- pickerBorder: "blue",
6
- questionBorder: "blue",
7
- accent: "cyan",
8
- muted: "gray",
9
- tool: "yellow",
10
- error: "red",
11
- success: "green",
12
- };
@@ -1,7 +0,0 @@
1
- import { estimateTokens } from "../compaction.js";
2
- import { getContextWindowInfo } from "../context-window.js";
3
- import { tokenInfoFromTracker } from "../token-display.js";
4
- export async function snapshotTokenInfo(tracker, modelId, messages) {
5
- const ctx = await getContextWindowInfo(modelId);
6
- return tokenInfoFromTracker(tracker, ctx.tokens, estimateTokens(messages));
7
- }
@@ -1,19 +0,0 @@
1
- export function mergeToolChild(messages, parentToolCallId, child) {
2
- let idx = -1;
3
- if (parentToolCallId)
4
- idx = messages.findIndex((m) => m.role === "tool" && m.toolCallId === parentToolCallId);
5
- if (idx === -1) {
6
- for (let i = messages.length - 1; i >= 0; i--) {
7
- if (messages[i].role === "tool" && messages[i].toolName === "task") {
8
- idx = i;
9
- break;
10
- }
11
- }
12
- }
13
- if (idx === -1)
14
- return messages;
15
- const target = messages[idx];
16
- const next = messages.slice();
17
- next[idx] = { ...target, toolChildren: [...(target.toolChildren ?? []), child] };
18
- return next;
19
- }
package/dist/tui/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
@@ -1,14 +0,0 @@
1
- export function createUndoStack() {
2
- return [];
3
- }
4
- export function pushTurn(stack, start, end, uiEnd) {
5
- return [...stack, { start, end, uiEnd }];
6
- }
7
- export function popTurn(stack) {
8
- if (stack.length === 0)
9
- return null;
10
- return { turn: stack[stack.length - 1], stack: stack.slice(0, -1) };
11
- }
12
- export function clearStack(_stack) {
13
- return [];
14
- }
@@ -1,29 +0,0 @@
1
- import { useEffect, useRef } from "react";
2
- import { useStdin } from "ink";
3
- import { scanSgrMouse } from "./mouse.js";
4
- export function useSgrMouse(enabled, onEvent) {
5
- const { stdin } = useStdin();
6
- const onEventRef = useRef(onEvent);
7
- onEventRef.current = onEvent;
8
- const bufRef = useRef("");
9
- useEffect(() => {
10
- if (!enabled || !stdin)
11
- return;
12
- const handle = (data) => {
13
- bufRef.current += data.toString("utf-8");
14
- let scanned;
15
- while ((scanned = scanSgrMouse(bufRef.current))) {
16
- bufRef.current = scanned.rest;
17
- onEventRef.current(scanned.event);
18
- }
19
- if (bufRef.current.length > 64) {
20
- const esc = bufRef.current.lastIndexOf("\x1b");
21
- bufRef.current = esc === -1 ? "" : bufRef.current.slice(esc);
22
- }
23
- };
24
- stdin.on("data", handle);
25
- return () => {
26
- stdin.off("data", handle);
27
- };
28
- }, [enabled, stdin]);
29
- }
package/dist/tui-chat.js DELETED
@@ -1,456 +0,0 @@
1
- import { TuiRenderer } from "./tui/index.js";
2
- import { initMcp, shutdownMcp } from "./mcp.js";
3
- import { discoverSkills, getAllSkills, getStaleDisabledSkills } from "./skills.js";
4
- import { setSlashArgProvider } from "./tui/slash-commands.js";
5
- import { loadInstructions } from "./instructions.js";
6
- import { TokenTracker } from "./compaction.js";
7
- import { emptyTaskState, copyTaskState, resetTaskState } from "./tools/todo.js";
8
- import { loadImageParts } from "./agent.js";
9
- import { createUndoStack, clearStack } from "./tui/undo-stack.js";
10
- import { killActiveProcesses } from "./tools/bash.js";
11
- import { setTuiConfirm, setTuiQuestion } from "./confirm.js";
12
- import { loadConfig, getActiveProvider, getEffectiveConfig } from "./config.js";
13
- import { scanProject, buildCodeSystemPrompt } from "./code-mode.js";
14
- import { handleSlashCommand, sysMsg } from "./tui/slash-handler.js";
15
- import { createAgentRunner } from "./tui/agent-runner.js";
16
- import { listSessions, loadSession, saveSession } from "./sessions.js";
17
- import { hydrateMessages, lastUserText } from "./tui/hydrate.js";
18
- import { createPromptQueue } from "./tui/prompt-queue.js";
19
- import { prepareSwitch, switchFailMessage, prepareNew, newFailMessage } from "./tui/session-switch.js";
20
- import { getModelCatalog } from "./model-catalog.js";
21
- import { setThinkingEffort, setThinkingOverride, thinkingChoicesForModel, thinkingEffortLabel } from "./thinking.js";
22
- let confirmResolver = null;
23
- let questionResolver = null;
24
- /** Only one confirm/question overlay can be shown at a time; parallel tool calls queue up. */
25
- const promptQueue = createPromptQueue();
26
- function registerSessionCompletion() {
27
- const provider = () => listSessions().map((s) => ({ value: s.id, description: s.title }));
28
- setSlashArgProvider("sessions", provider);
29
- setSlashArgProvider("resume", provider);
30
- }
31
- function registerThinkCompletion() {
32
- const provider = (tokens) => {
33
- const flags = [
34
- { value: "--project", description: "写入当前项目" },
35
- { value: "--global", description: "写入全局配置" },
36
- ];
37
- const used = new Set(tokens);
38
- const remainingFlags = flags.filter((f) => !used.has(f.value));
39
- if (tokens.length === 0) {
40
- const model = getActiveProvider(getEffectiveConfig())?.defaultModel;
41
- return [...thinkingChoicesForModel(model).map((value) => ({ value })), ...remainingFlags];
42
- }
43
- return remainingFlags;
44
- };
45
- setSlashArgProvider("think", provider);
46
- setSlashArgProvider("thinking", provider);
47
- }
48
- function registerMemoryCompletion() {
49
- setSlashArgProvider("memory", (tokens) => {
50
- const flags = [
51
- { value: "--project", description: "写入当前项目" },
52
- { value: "--global", description: "写入全局配置" },
53
- ];
54
- const used = new Set(tokens);
55
- const remainingFlags = flags.filter((f) => !used.has(f.value));
56
- if (tokens.length === 0) {
57
- return [
58
- { value: "on", description: "开启记忆(跨会话保留)" },
59
- { value: "off", description: "关闭记忆(默认)" },
60
- ...remainingFlags,
61
- ];
62
- }
63
- return remainingFlags;
64
- });
65
- }
66
- function registerPermissionCompletion() {
67
- setSlashArgProvider("permission", (tokens) => {
68
- const flags = [
69
- { value: "--project", description: "写入当前项目" },
70
- { value: "--global", description: "写入全局配置" },
71
- ];
72
- const used = new Set(tokens);
73
- const remainingFlags = flags.filter((f) => !used.has(f.value));
74
- if (tokens.length === 0) {
75
- return [
76
- { value: "ask", description: "危险操作前询问(默认)" },
77
- { value: "accept-edits", description: "自动允许改文件,危险命令仍询问" },
78
- { value: "allow-all", description: "全部自动允许" },
79
- ...remainingFlags,
80
- ];
81
- }
82
- return remainingFlags;
83
- });
84
- }
85
- function registerSandboxCompletion() {
86
- setSlashArgProvider("sandbox", (tokens) => {
87
- const flags = [
88
- { value: "--project", description: "写入当前项目" },
89
- { value: "--global", description: "写入全局配置" },
90
- ];
91
- const used = new Set(tokens);
92
- const remainingFlags = flags.filter((f) => !used.has(f.value));
93
- if (tokens.length === 0) {
94
- return [
95
- { value: "off", description: "不隔离(默认)" },
96
- { value: "workspace", description: "只允许写入工作目录" },
97
- { value: "strict", description: "读写都限制在工作目录" },
98
- { value: "network", description: "设置是否允许联网" },
99
- ...remainingFlags,
100
- ];
101
- }
102
- if (tokens[0] === "network") {
103
- const nets = [
104
- { value: "allow", description: "允许联网" },
105
- { value: "deny", description: "禁止联网" },
106
- ];
107
- return [...nets.filter((n) => !used.has(n.value)), ...remainingFlags];
108
- }
109
- return remainingFlags;
110
- });
111
- }
112
- function registerSkillCompletion() {
113
- setSlashArgProvider("skills", (tokens) => {
114
- const action = tokens[0];
115
- if (action === undefined) {
116
- return [
117
- { value: "list", description: "列出全部 skills" },
118
- { value: "info", description: "查看 skill 详情" },
119
- { value: "enable", description: "启用 skill" },
120
- { value: "disable", description: "禁用 skill" },
121
- ];
122
- }
123
- if (action === "info")
124
- return getAllSkills().map((s) => ({ value: s.name, description: s.description }));
125
- if (action !== "enable" && action !== "disable")
126
- return [];
127
- const all = getAllSkills();
128
- const pool = action === "enable"
129
- ? [
130
- ...all.filter((s) => !s.enabled).map((s) => ({ value: s.name, description: s.description })),
131
- ...getStaleDisabledSkills().map((name) => ({ value: name, description: "(配置中的孤儿条目)" })),
132
- ]
133
- : all.filter((s) => s.enabled).map((s) => ({ value: s.name, description: s.description }));
134
- return [
135
- ...pool.filter((o) => !tokens.includes(o.value)),
136
- ...(tokens.includes("--project") ? [] : [{ value: "--project", description: "仅对当前项目生效" }]),
137
- ];
138
- });
139
- }
140
- export async function runTui(opts) {
141
- const { modelId, resumeSessionId, images, providerName, initialPrompt } = opts;
142
- await initMcp();
143
- discoverSkills();
144
- registerSkillCompletion();
145
- registerPermissionCompletion();
146
- registerThinkCompletion();
147
- registerMemoryCompletion();
148
- registerSandboxCompletion();
149
- registerSessionCompletion();
150
- const instructions = await loadInstructions();
151
- const config = loadConfig();
152
- const project = scanProject();
153
- let systemPrompt = buildCodeSystemPrompt(project, instructions);
154
- let messages = [];
155
- let sessionId = resumeSessionId;
156
- let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
157
- const currentProvider = providerName;
158
- if (currentModel)
159
- void getModelCatalog(currentModel, getActiveProvider(config)?.baseURL);
160
- const tracker = new TokenTracker();
161
- const taskState = emptyTaskState();
162
- let undoStack = createUndoStack();
163
- let lastUserMessage = null;
164
- let planMode = false;
165
- if (resumeSessionId) {
166
- const session = loadSession(resumeSessionId);
167
- if (session) {
168
- messages = session.messages;
169
- sessionId = resumeSessionId;
170
- copyTaskState(session.taskState, taskState);
171
- }
172
- }
173
- let runner = null;
174
- const tui = new TuiRenderer({
175
- onSubmit: async (prompt) => {
176
- const text = prompt.display;
177
- if (text.trim().startsWith("/")) {
178
- const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => {
179
- currentModel = model;
180
- tui.setModel(model);
181
- void refreshTokenBar();
182
- }, requestExit, {
183
- undoStack,
184
- setUndoStack: (s) => {
185
- undoStack = s;
186
- },
187
- lastUserMessage,
188
- setLastUserMessage: (t) => {
189
- lastUserMessage = t;
190
- },
191
- sessionId,
192
- setSessionId: (id) => {
193
- sessionId = id;
194
- },
195
- planMode,
196
- togglePlanMode: () => {
197
- planMode = !planMode;
198
- },
199
- reloadSystemPrompt: async () => {
200
- const next = await loadInstructions();
201
- systemPrompt = buildCodeSystemPrompt(scanProject(), next);
202
- sysMsg(tui, `✓ 已重载规则(${next.join("").length} 字符)`);
203
- },
204
- applySession,
205
- startNewSession,
206
- openSessionPicker,
207
- taskState,
208
- });
209
- if (result === "run" && runner) {
210
- await runner.run();
211
- persistSession();
212
- }
213
- return;
214
- }
215
- lastUserMessage = text;
216
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
217
- messages.push({ role: "user", content: prompt.content });
218
- if (runner) {
219
- await runner.run();
220
- persistSession();
221
- }
222
- },
223
- onNotice: (notice) => {
224
- sysMsg(tui, notice);
225
- },
226
- onConfirm: (accepted) => {
227
- tui.hideConfirm();
228
- if (confirmResolver) {
229
- confirmResolver(accepted);
230
- confirmResolver = null;
231
- }
232
- },
233
- onQuestionAnswer: (answer) => {
234
- tui.hideQuestion();
235
- if (questionResolver) {
236
- questionResolver(answer);
237
- questionResolver = null;
238
- }
239
- },
240
- onModelPick: (model) => {
241
- tui.hideModelPicker();
242
- const cfg = loadConfig();
243
- const provider = getActiveProvider(cfg);
244
- if (provider) {
245
- provider.defaultModel = model;
246
- import("./config.js").then((m) => m.saveConfig(cfg));
247
- }
248
- currentModel = model;
249
- tui.setModel(model);
250
- void refreshTokenBar();
251
- sysMsg(tui, `✓ 默认模型已设为: ${model}`);
252
- },
253
- onModelCancel: () => {
254
- tui.hideModelPicker();
255
- },
256
- onThinkPick: (effort, scope) => {
257
- setThinkingEffort(effort, scope);
258
- setThinkingOverride(effort);
259
- tui.hideThinkPicker();
260
- sysMsg(tui, `✓ 思考强度已设为 ${thinkingEffortLabel(effort)}(${scope === "project" ? "项目" : "全局"})`);
261
- },
262
- onThinkCancel: () => {
263
- tui.hideThinkPicker();
264
- },
265
- onSessionPick: (id) => {
266
- tui.hideSessionPicker();
267
- applySession(id);
268
- },
269
- onSessionCancel: () => {
270
- tui.hideSessionPicker();
271
- },
272
- onToggleSelectionMode: () => {
273
- tui.toggleSelectionMode();
274
- },
275
- onExitSelectionMode: () => {
276
- tui.setSelectionMode(false);
277
- },
278
- onExit: () => {
279
- requestExit();
280
- },
281
- }, modelId ?? getActiveProvider(config)?.defaultModel, sessionId);
282
- runner = createAgentRunner({
283
- tui,
284
- messages,
285
- tracker,
286
- getModel: () => currentModel,
287
- getProvider: () => currentProvider,
288
- getPlanMode: () => planMode,
289
- getUndoStack: () => undoStack,
290
- setUndoStack: (s) => {
291
- undoStack = s;
292
- },
293
- getSystemPrompt: () => systemPrompt,
294
- getTaskState: () => taskState,
295
- persist: () => persistSession(),
296
- });
297
- function persistSession() {
298
- if (messages.length === 0)
299
- return;
300
- try {
301
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
302
- }
303
- catch { }
304
- }
305
- async function refreshTokenBar() {
306
- const { snapshotTokenInfo } = await import("./tui/token-info.js");
307
- tui.setTokenInfo(await snapshotTokenInfo(tracker, currentModel, messages));
308
- }
309
- function applySession(id) {
310
- const ready = prepareSwitch({
311
- running: Boolean(runner?.getController()),
312
- currentId: sessionId,
313
- targetId: id,
314
- exists: Boolean(loadSession(id)),
315
- });
316
- if (!ready.ok) {
317
- sysMsg(tui, switchFailMessage(ready.reason));
318
- return;
319
- }
320
- if (messages.length > 0)
321
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
322
- const session = loadSession(id);
323
- if (!session) {
324
- sysMsg(tui, switchFailMessage("missing"));
325
- return;
326
- }
327
- messages.length = 0;
328
- messages.push(...session.messages);
329
- copyTaskState(session.taskState, taskState);
330
- sessionId = id;
331
- undoStack = clearStack(undoStack);
332
- tracker.reset();
333
- lastUserMessage = lastUserText(session.messages);
334
- tui.setSessionId(id);
335
- tui.update({ messages: hydrateMessages(session.messages) });
336
- void refreshTokenBar();
337
- sysMsg(tui, `✓ 已切换到: ${session.meta.title}`);
338
- }
339
- function startNewSession() {
340
- const ready = prepareNew({
341
- running: Boolean(runner?.getController()),
342
- sessionId,
343
- messageCount: messages.length,
344
- });
345
- if (!ready.ok) {
346
- sysMsg(tui, newFailMessage(ready.reason));
347
- return;
348
- }
349
- if (messages.length > 0)
350
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
351
- messages.length = 0;
352
- resetTaskState(taskState);
353
- sessionId = undefined;
354
- undoStack = clearStack(undoStack);
355
- tracker.reset();
356
- lastUserMessage = null;
357
- tui.setSessionId(undefined);
358
- tui.update({ messages: [] });
359
- void refreshTokenBar();
360
- sysMsg(tui, "✓ 已新建会话");
361
- }
362
- function openSessionPicker() {
363
- if (listSessions().length === 0) {
364
- sysMsg(tui, "无已保存的会话");
365
- return;
366
- }
367
- tui.showSessionPicker();
368
- }
369
- setTuiConfirm((message) => promptQueue.run(() => new Promise((resolve) => {
370
- confirmResolver = resolve;
371
- tui.showConfirm(message);
372
- }), () => false));
373
- setTuiQuestion((prompt, options) => promptQueue.run(() => new Promise((resolve) => {
374
- questionResolver = resolve;
375
- tui.showQuestion(prompt, options);
376
- }), () => null));
377
- let exitResolve;
378
- const exitPromise = new Promise((resolve) => {
379
- exitResolve = resolve;
380
- });
381
- const requestExit = () => {
382
- tui.setSelectionMode(false);
383
- persistSession();
384
- if (runner?.getController()) {
385
- killActiveProcesses();
386
- promptQueue.cancelAll();
387
- if (confirmResolver) {
388
- confirmResolver(false);
389
- confirmResolver = null;
390
- tui.hideConfirm();
391
- }
392
- if (questionResolver) {
393
- questionResolver(null);
394
- questionResolver = null;
395
- tui.hideQuestion();
396
- }
397
- runner.getController().abort();
398
- tui.setRunning(false);
399
- tui.addMessage({ id: `sys-${Date.now()}`, role: "system", content: "(已取消)", timestamp: Date.now() });
400
- }
401
- else
402
- exitResolve?.();
403
- };
404
- tui.start();
405
- if (messages.length > 0)
406
- tui.update({ messages: hydrateMessages(messages) });
407
- await refreshTokenBar();
408
- const prompt = initialPrompt?.trim() ?? "";
409
- const notify = (kind, imgPath) => {
410
- if (kind === "not_found")
411
- sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
412
- else if (kind === "too_large")
413
- sysMsg(tui, `⚠ 图片超过 5 MB 限制: ${imgPath}`);
414
- else
415
- sysMsg(tui, `📎 ${imgPath}`);
416
- };
417
- if (images && images.length > 0) {
418
- const parts = [{ type: "text", text: prompt }, ...loadImageParts(images, notify)];
419
- if (parts.length > 1 || prompt) {
420
- messages.push({ role: "user", content: parts });
421
- tui.addMessage({
422
- id: `user-${Date.now()}`,
423
- role: "user",
424
- content: prompt ? prompt : `[📎 图片] ${images.join(", ")}`,
425
- timestamp: Date.now(),
426
- });
427
- if (prompt)
428
- lastUserMessage = prompt;
429
- await runner.run();
430
- persistSession();
431
- }
432
- }
433
- else if (prompt) {
434
- lastUserMessage = prompt;
435
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: prompt, timestamp: Date.now() });
436
- messages.push({ role: "user", content: prompt });
437
- await runner.run();
438
- persistSession();
439
- }
440
- const onSigint = () => {
441
- if (runner?.getController())
442
- requestExit();
443
- };
444
- process.on("SIGINT", onSigint);
445
- await exitPromise;
446
- process.off("SIGINT", onSigint);
447
- try {
448
- persistSession();
449
- }
450
- finally {
451
- setTuiConfirm(null);
452
- setTuiQuestion(null);
453
- tui.destroy();
454
- await shutdownMcp();
455
- }
456
- }