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
- /**
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,478 +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 { setThinkingEffort, setThinkingOverride, thinkingChoicesForModel, thinkingEffortLabel, refreshThinkingChoices, } from "./thinking.js";
21
- import { CTX_CHOICES, ctxChoiceLabel, setOllamaContextChoice } from "./ctx.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 registerCtxCompletion() {
32
- setSlashArgProvider("ctx", (tokens) => {
33
- if (tokens.length > 0)
34
- return [];
35
- return CTX_CHOICES.map((value) => (value === "auto" ? { value, description: "按模型探测" } : { value }));
36
- });
37
- }
38
- function registerThinkCompletion() {
39
- const provider = (tokens) => {
40
- const flags = [
41
- { value: "--project", description: "写入当前项目" },
42
- { value: "--global", description: "写入全局配置" },
43
- ];
44
- const used = new Set(tokens);
45
- const remainingFlags = flags.filter((f) => !used.has(f.value));
46
- if (tokens.length === 0) {
47
- const model = getActiveProvider(getEffectiveConfig())?.defaultModel;
48
- return [...thinkingChoicesForModel(model).map((value) => ({ value })), ...remainingFlags];
49
- }
50
- return remainingFlags;
51
- };
52
- setSlashArgProvider("think", provider);
53
- setSlashArgProvider("thinking", provider);
54
- }
55
- function registerMemoryCompletion() {
56
- setSlashArgProvider("memory", (tokens) => {
57
- const flags = [
58
- { value: "--project", description: "写入当前项目" },
59
- { value: "--global", description: "写入全局配置" },
60
- ];
61
- const used = new Set(tokens);
62
- const remainingFlags = flags.filter((f) => !used.has(f.value));
63
- if (tokens.length === 0) {
64
- return [
65
- { value: "on", description: "开启记忆(跨会话保留)" },
66
- { value: "off", description: "关闭记忆(默认)" },
67
- ...remainingFlags,
68
- ];
69
- }
70
- return remainingFlags;
71
- });
72
- }
73
- function registerPermissionCompletion() {
74
- setSlashArgProvider("permission", (tokens) => {
75
- const flags = [
76
- { value: "--project", description: "写入当前项目" },
77
- { value: "--global", description: "写入全局配置" },
78
- ];
79
- const used = new Set(tokens);
80
- const remainingFlags = flags.filter((f) => !used.has(f.value));
81
- if (tokens.length === 0) {
82
- return [
83
- { value: "ask", description: "危险操作前询问(默认)" },
84
- { value: "accept-edits", description: "自动允许改文件,危险命令仍询问" },
85
- { value: "allow-all", description: "全部自动允许" },
86
- ...remainingFlags,
87
- ];
88
- }
89
- return remainingFlags;
90
- });
91
- }
92
- function registerSandboxCompletion() {
93
- setSlashArgProvider("sandbox", (tokens) => {
94
- const flags = [
95
- { value: "--project", description: "写入当前项目" },
96
- { value: "--global", description: "写入全局配置" },
97
- ];
98
- const used = new Set(tokens);
99
- const remainingFlags = flags.filter((f) => !used.has(f.value));
100
- if (tokens.length === 0) {
101
- return [
102
- { value: "off", description: "不隔离(默认)" },
103
- { value: "workspace", description: "只允许写入工作目录" },
104
- { value: "strict", description: "读写都限制在工作目录" },
105
- { value: "network", description: "设置是否允许联网" },
106
- ...remainingFlags,
107
- ];
108
- }
109
- if (tokens[0] === "network") {
110
- const nets = [
111
- { value: "allow", description: "允许联网" },
112
- { value: "deny", description: "禁止联网" },
113
- ];
114
- return [...nets.filter((n) => !used.has(n.value)), ...remainingFlags];
115
- }
116
- return remainingFlags;
117
- });
118
- }
119
- function registerSkillCompletion() {
120
- setSlashArgProvider("skills", (tokens) => {
121
- const action = tokens[0];
122
- if (action === undefined) {
123
- return [
124
- { value: "list", description: "列出全部 skills" },
125
- { value: "info", description: "查看 skill 详情" },
126
- { value: "enable", description: "启用 skill" },
127
- { value: "disable", description: "禁用 skill" },
128
- ];
129
- }
130
- if (action === "info")
131
- return getAllSkills().map((s) => ({ value: s.name, description: s.description }));
132
- if (action !== "enable" && action !== "disable")
133
- return [];
134
- const all = getAllSkills();
135
- const pool = action === "enable"
136
- ? [
137
- ...all.filter((s) => !s.enabled).map((s) => ({ value: s.name, description: s.description })),
138
- ...getStaleDisabledSkills().map((name) => ({ value: name, description: "(配置中的孤儿条目)" })),
139
- ]
140
- : all.filter((s) => s.enabled).map((s) => ({ value: s.name, description: s.description }));
141
- return [
142
- ...pool.filter((o) => !tokens.includes(o.value)),
143
- ...(tokens.includes("--project") ? [] : [{ value: "--project", description: "仅对当前项目生效" }]),
144
- ];
145
- });
146
- }
147
- export async function runTui(opts) {
148
- const { modelId, resumeSessionId, images, providerName, initialPrompt } = opts;
149
- await initMcp();
150
- discoverSkills();
151
- registerSkillCompletion();
152
- registerPermissionCompletion();
153
- registerThinkCompletion();
154
- registerCtxCompletion();
155
- registerMemoryCompletion();
156
- registerSandboxCompletion();
157
- registerSessionCompletion();
158
- const instructions = await loadInstructions();
159
- const config = loadConfig();
160
- const project = scanProject();
161
- let systemPrompt = buildCodeSystemPrompt(project, instructions);
162
- let messages = [];
163
- let sessionId = resumeSessionId;
164
- let currentModel = modelId ?? getActiveProvider(config)?.defaultModel;
165
- const currentProvider = providerName;
166
- if (currentModel)
167
- void refreshThinkingChoices(currentModel);
168
- const tracker = new TokenTracker();
169
- const taskState = emptyTaskState();
170
- let undoStack = createUndoStack();
171
- let lastUserMessage = null;
172
- let planMode = false;
173
- if (resumeSessionId) {
174
- const session = loadSession(resumeSessionId);
175
- if (session) {
176
- messages = session.messages;
177
- sessionId = resumeSessionId;
178
- copyTaskState(session.taskState, taskState);
179
- }
180
- }
181
- let runner = null;
182
- const tui = new TuiRenderer({
183
- onSubmit: async (prompt) => {
184
- const text = prompt.display;
185
- if (text.trim().startsWith("/")) {
186
- const result = await handleSlashCommand(text, tui, messages, currentModel, tracker, (model) => {
187
- currentModel = model;
188
- tui.setModel(model);
189
- void refreshTokenBar();
190
- }, requestExit, {
191
- undoStack,
192
- setUndoStack: (s) => {
193
- undoStack = s;
194
- },
195
- lastUserMessage,
196
- setLastUserMessage: (t) => {
197
- lastUserMessage = t;
198
- },
199
- sessionId,
200
- setSessionId: (id) => {
201
- sessionId = id;
202
- },
203
- planMode,
204
- togglePlanMode: () => {
205
- planMode = !planMode;
206
- },
207
- reloadSystemPrompt: async () => {
208
- const next = await loadInstructions();
209
- systemPrompt = buildCodeSystemPrompt(scanProject(), next);
210
- sysMsg(tui, `✓ 已重载规则(${next.join("").length} 字符)`);
211
- },
212
- applySession,
213
- startNewSession,
214
- openSessionPicker,
215
- taskState,
216
- refreshTokenBar,
217
- });
218
- if (result === "run" && runner) {
219
- await runner.run();
220
- persistSession();
221
- }
222
- return;
223
- }
224
- lastUserMessage = text;
225
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: text, timestamp: Date.now() });
226
- messages.push({ role: "user", content: prompt.content });
227
- if (runner) {
228
- await runner.run();
229
- persistSession();
230
- }
231
- },
232
- onNotice: (notice) => {
233
- sysMsg(tui, notice);
234
- },
235
- onConfirm: (accepted) => {
236
- tui.hideConfirm();
237
- if (confirmResolver) {
238
- confirmResolver(accepted);
239
- confirmResolver = null;
240
- }
241
- },
242
- onQuestionAnswer: (answer) => {
243
- tui.hideQuestion();
244
- if (questionResolver) {
245
- questionResolver(answer);
246
- questionResolver = null;
247
- }
248
- },
249
- onModelPick: (model) => {
250
- tui.hideModelPicker();
251
- const cfg = loadConfig();
252
- const provider = getActiveProvider(cfg);
253
- if (provider) {
254
- provider.defaultModel = model;
255
- import("./config.js").then((m) => m.saveConfig(cfg));
256
- }
257
- currentModel = model;
258
- tui.setModel(model);
259
- void refreshTokenBar();
260
- sysMsg(tui, `✓ 默认模型已设为: ${model}`);
261
- },
262
- onModelCancel: () => {
263
- tui.hideModelPicker();
264
- },
265
- onThinkPick: (effort, scope) => {
266
- setThinkingEffort(effort, scope);
267
- setThinkingOverride(effort);
268
- tui.hideThinkPicker();
269
- sysMsg(tui, `✓ 思考强度已设为 ${thinkingEffortLabel(effort)}(${scope === "project" ? "项目" : "全局"})`);
270
- },
271
- onThinkCancel: () => {
272
- tui.hideThinkPicker();
273
- },
274
- onCtxPick: (choice) => {
275
- const result = setOllamaContextChoice(choice);
276
- tui.hideCtxPicker();
277
- if (!result.ok) {
278
- sysMsg(tui, "窗口档位仅本地模型可用");
279
- return;
280
- }
281
- void refreshTokenBar();
282
- sysMsg(tui, `✓ 上下文窗口已设为${ctxChoiceLabel(choice)}`);
283
- },
284
- onCtxCancel: () => {
285
- tui.hideCtxPicker();
286
- },
287
- onSessionPick: (id) => {
288
- tui.hideSessionPicker();
289
- applySession(id);
290
- },
291
- onSessionCancel: () => {
292
- tui.hideSessionPicker();
293
- },
294
- onToggleSelectionMode: () => {
295
- tui.toggleSelectionMode();
296
- },
297
- onExitSelectionMode: () => {
298
- tui.setSelectionMode(false);
299
- },
300
- onExit: () => {
301
- requestExit();
302
- },
303
- }, modelId ?? getActiveProvider(config)?.defaultModel, sessionId);
304
- runner = createAgentRunner({
305
- tui,
306
- messages,
307
- tracker,
308
- getModel: () => currentModel,
309
- getProvider: () => currentProvider,
310
- getPlanMode: () => planMode,
311
- getUndoStack: () => undoStack,
312
- setUndoStack: (s) => {
313
- undoStack = s;
314
- },
315
- getSystemPrompt: () => systemPrompt,
316
- getTaskState: () => taskState,
317
- persist: () => persistSession(),
318
- });
319
- function persistSession() {
320
- if (messages.length === 0)
321
- return;
322
- try {
323
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
324
- }
325
- catch { }
326
- }
327
- async function refreshTokenBar() {
328
- const { snapshotTokenInfo } = await import("./tui/token-info.js");
329
- tui.setTokenInfo(await snapshotTokenInfo(tracker, currentModel, messages));
330
- }
331
- function applySession(id) {
332
- const ready = prepareSwitch({
333
- running: Boolean(runner?.getController()),
334
- currentId: sessionId,
335
- targetId: id,
336
- exists: Boolean(loadSession(id)),
337
- });
338
- if (!ready.ok) {
339
- sysMsg(tui, switchFailMessage(ready.reason));
340
- return;
341
- }
342
- if (messages.length > 0)
343
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
344
- const session = loadSession(id);
345
- if (!session) {
346
- sysMsg(tui, switchFailMessage("missing"));
347
- return;
348
- }
349
- messages.length = 0;
350
- messages.push(...session.messages);
351
- copyTaskState(session.taskState, taskState);
352
- sessionId = id;
353
- undoStack = clearStack(undoStack);
354
- tracker.reset();
355
- lastUserMessage = lastUserText(session.messages);
356
- tui.setSessionId(id);
357
- tui.update({ messages: hydrateMessages(session.messages) });
358
- void refreshTokenBar();
359
- sysMsg(tui, `✓ 已切换到: ${session.meta.title}`);
360
- }
361
- function startNewSession() {
362
- const ready = prepareNew({
363
- running: Boolean(runner?.getController()),
364
- sessionId,
365
- messageCount: messages.length,
366
- });
367
- if (!ready.ok) {
368
- sysMsg(tui, newFailMessage(ready.reason));
369
- return;
370
- }
371
- if (messages.length > 0)
372
- sessionId = saveSession(messages, sessionId, undefined, undefined, taskState);
373
- messages.length = 0;
374
- resetTaskState(taskState);
375
- sessionId = undefined;
376
- undoStack = clearStack(undoStack);
377
- tracker.reset();
378
- lastUserMessage = null;
379
- tui.setSessionId(undefined);
380
- tui.update({ messages: [] });
381
- void refreshTokenBar();
382
- sysMsg(tui, "✓ 已新建会话");
383
- }
384
- function openSessionPicker() {
385
- if (listSessions().length === 0) {
386
- sysMsg(tui, "无已保存的会话");
387
- return;
388
- }
389
- tui.showSessionPicker();
390
- }
391
- setTuiConfirm((message) => promptQueue.run(() => new Promise((resolve) => {
392
- confirmResolver = resolve;
393
- tui.showConfirm(message);
394
- }), () => false));
395
- setTuiQuestion((prompt, options) => promptQueue.run(() => new Promise((resolve) => {
396
- questionResolver = resolve;
397
- tui.showQuestion(prompt, options);
398
- }), () => null));
399
- let exitResolve;
400
- const exitPromise = new Promise((resolve) => {
401
- exitResolve = resolve;
402
- });
403
- const requestExit = () => {
404
- tui.setSelectionMode(false);
405
- persistSession();
406
- if (runner?.getController()) {
407
- killActiveProcesses();
408
- promptQueue.cancelAll();
409
- if (confirmResolver) {
410
- confirmResolver(false);
411
- confirmResolver = null;
412
- tui.hideConfirm();
413
- }
414
- if (questionResolver) {
415
- questionResolver(null);
416
- questionResolver = null;
417
- tui.hideQuestion();
418
- }
419
- runner.getController().abort();
420
- tui.setRunning(false);
421
- tui.addMessage({ id: `sys-${Date.now()}`, role: "system", content: "(已取消)", timestamp: Date.now() });
422
- }
423
- else
424
- exitResolve?.();
425
- };
426
- tui.start();
427
- if (messages.length > 0)
428
- tui.update({ messages: hydrateMessages(messages) });
429
- await refreshTokenBar();
430
- const prompt = initialPrompt?.trim() ?? "";
431
- const notify = (kind, imgPath) => {
432
- if (kind === "not_found")
433
- sysMsg(tui, `⚠ 图片不存在: ${imgPath}`);
434
- else if (kind === "too_large")
435
- sysMsg(tui, `⚠ 图片超过 5 MB 限制: ${imgPath}`);
436
- else
437
- sysMsg(tui, `📎 ${imgPath}`);
438
- };
439
- if (images && images.length > 0) {
440
- const parts = [{ type: "text", text: prompt }, ...loadImageParts(images, notify)];
441
- if (parts.length > 1 || prompt) {
442
- messages.push({ role: "user", content: parts });
443
- tui.addMessage({
444
- id: `user-${Date.now()}`,
445
- role: "user",
446
- content: prompt ? prompt : `[📎 图片] ${images.join(", ")}`,
447
- timestamp: Date.now(),
448
- });
449
- if (prompt)
450
- lastUserMessage = prompt;
451
- await runner.run();
452
- persistSession();
453
- }
454
- }
455
- else if (prompt) {
456
- lastUserMessage = prompt;
457
- tui.addMessage({ id: `user-${Date.now()}`, role: "user", content: prompt, timestamp: Date.now() });
458
- messages.push({ role: "user", content: prompt });
459
- await runner.run();
460
- persistSession();
461
- }
462
- const onSigint = () => {
463
- if (runner?.getController())
464
- requestExit();
465
- };
466
- process.on("SIGINT", onSigint);
467
- await exitPromise;
468
- process.off("SIGINT", onSigint);
469
- try {
470
- persistSession();
471
- }
472
- finally {
473
- setTuiConfirm(null);
474
- setTuiQuestion(null);
475
- tui.destroy();
476
- await shutdownMcp();
477
- }
478
- }