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.
- package/README.md +18 -18
- package/dist/cli.js +538 -12
- package/docs/API.md +6 -6
- package/package.json +6 -6
- package/skills/self-config/SKILL.md +4 -2
- package/skills/self-config/reference.md +4 -1
- package/dist/agent.js +0 -1388
- package/dist/assistant-stream.js +0 -131
- package/dist/cli/commands/chat.js +0 -13
- package/dist/cli/commands/ctx.js +0 -7
- package/dist/cli/commands/exec.js +0 -35
- package/dist/cli/commands/history.js +0 -58
- package/dist/cli/commands/index.js +0 -249
- package/dist/cli/commands/init.js +0 -18
- package/dist/cli/commands/mcp.js +0 -173
- package/dist/cli/commands/memory.js +0 -87
- package/dist/cli/commands/models.js +0 -21
- package/dist/cli/commands/permission.js +0 -12
- package/dist/cli/commands/rules.js +0 -33
- package/dist/cli/commands/sandbox.js +0 -13
- package/dist/cli/commands/serve.js +0 -9
- package/dist/cli/commands/setup.js +0 -56
- package/dist/cli/commands/shared.js +0 -25
- package/dist/cli/commands/skills.js +0 -119
- package/dist/cli/commands/think.js +0 -12
- package/dist/cli/commands/update.js +0 -7
- package/dist/cli/commands/write-config.js +0 -52
- package/dist/cli/errors.js +0 -36
- package/dist/cli/exec-prompt.js +0 -26
- package/dist/cli/option-helpers.js +0 -65
- package/dist/cli/program.js +0 -223
- package/dist/cli/setup/detect.js +0 -17
- package/dist/cli/setup/flags.js +0 -12
- package/dist/cli/setup/flow.js +0 -108
- package/dist/cli/setup/provider-form.js +0 -102
- package/dist/cli/setup/ui.js +0 -534
- package/dist/clipboard.js +0 -142
- package/dist/code-mode.js +0 -184
- package/dist/compaction.js +0 -644
- package/dist/config.js +0 -363
- package/dist/confirm.js +0 -172
- package/dist/context-window.js +0 -256
- package/dist/ctx-cli.js +0 -30
- package/dist/ctx.js +0 -80
- package/dist/doom-loop.js +0 -278
- package/dist/fetch-timeout.js +0 -152
- package/dist/http-approvals.js +0 -60
- package/dist/http.js +0 -119
- package/dist/instructions.js +0 -196
- package/dist/logger.js +0 -95
- package/dist/markdown.js +0 -259
- package/dist/mcp.js +0 -1036
- package/dist/memory-cli.js +0 -33
- package/dist/memory.js +0 -295
- package/dist/model-catalog.js +0 -285
- package/dist/ollama-model.js +0 -234
- package/dist/ollama-openai-bridge.js +0 -383
- package/dist/output.js +0 -76
- package/dist/paste-handler.js +0 -41
- package/dist/permission-cli.js +0 -40
- package/dist/plugins.js +0 -131
- package/dist/pricing.js +0 -119
- package/dist/provider.js +0 -63
- package/dist/question-format.js +0 -60
- package/dist/reasoning-stream.js +0 -158
- package/dist/sandbox-cli.js +0 -79
- package/dist/sandbox.js +0 -403
- package/dist/save-throttle.js +0 -45
- package/dist/scope.js +0 -23
- package/dist/serve/common.js +0 -425
- package/dist/serve/routes-chat.js +0 -367
- package/dist/serve/routes-mcp.js +0 -212
- package/dist/serve/routes-memory.js +0 -95
- package/dist/serve/routes-meta.js +0 -268
- package/dist/serve/routes-sessions.js +0 -61
- package/dist/serve/routes-skills.js +0 -70
- package/dist/serve.js +0 -138
- package/dist/sessions.js +0 -265
- package/dist/skills.js +0 -599
- package/dist/structured-output.js +0 -29
- package/dist/synthetic.js +0 -7
- package/dist/think-cli.js +0 -36
- package/dist/thinking-wire.js +0 -239
- package/dist/thinking.js +0 -166
- package/dist/title-gen.js +0 -55
- package/dist/token-display.js +0 -39
- package/dist/tool-display.js +0 -178
- package/dist/tool-output.js +0 -126
- package/dist/tools/apply_patch.js +0 -265
- package/dist/tools/atomic-file.js +0 -35
- package/dist/tools/backend.js +0 -61
- package/dist/tools/bash.js +0 -231
- package/dist/tools/code_search.js +0 -98
- package/dist/tools/edit.js +0 -68
- package/dist/tools/explore.js +0 -232
- package/dist/tools/glob.js +0 -40
- package/dist/tools/grep.js +0 -174
- package/dist/tools/index.js +0 -34
- package/dist/tools/question.js +0 -54
- package/dist/tools/read.js +0 -109
- package/dist/tools/search-searxng.js +0 -223
- package/dist/tools/search-serper.js +0 -189
- package/dist/tools/task.js +0 -165
- package/dist/tools/todo.js +0 -213
- package/dist/tools/web_fetch.js +0 -195
- package/dist/tools/web_search.js +0 -228
- package/dist/tools/write.js +0 -55
- package/dist/tui/App.js +0 -138
- package/dist/tui/ConfirmBar.js +0 -66
- package/dist/tui/CtxPicker.js +0 -68
- package/dist/tui/InputBar.js +0 -618
- package/dist/tui/MessageList.js +0 -718
- package/dist/tui/ModelPicker.js +0 -113
- package/dist/tui/QuestionBar.js +0 -136
- package/dist/tui/SessionPicker.js +0 -79
- package/dist/tui/Spinner.js +0 -6
- package/dist/tui/StatusBar.js +0 -22
- package/dist/tui/ThinkPicker.js +0 -75
- package/dist/tui/agent-runner.js +0 -223
- package/dist/tui/bracketed-paste.js +0 -37
- package/dist/tui/caret-pos.js +0 -179
- package/dist/tui/caret.js +0 -69
- package/dist/tui/click-count.js +0 -13
- package/dist/tui/diff-view.js +0 -61
- package/dist/tui/drag-state.js +0 -49
- package/dist/tui/hydrate.js +0 -129
- package/dist/tui/index.js +0 -264
- package/dist/tui/input-history.js +0 -125
- package/dist/tui/layout.js +0 -105
- package/dist/tui/mouse.js +0 -46
- package/dist/tui/overlay-input.js +0 -12
- package/dist/tui/paste-draft.js +0 -173
- package/dist/tui/prompt-queue.js +0 -24
- package/dist/tui/selection.js +0 -232
- package/dist/tui/session-switch.js +0 -28
- package/dist/tui/slash-commands.js +0 -129
- package/dist/tui/slash-handler.js +0 -615
- package/dist/tui/text-width.js +0 -113
- package/dist/tui/theme.js +0 -12
- package/dist/tui/token-info.js +0 -7
- package/dist/tui/tool-children.js +0 -19
- package/dist/tui/types.js +0 -1
- package/dist/tui/undo-stack.js +0 -14
- package/dist/tui/use-sgr-mouse.js +0 -29
- package/dist/tui-chat.js +0 -478
- package/dist/updater.js +0 -116
- package/dist/xml-search.js +0 -194
- package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
- package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
- package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
- package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
- package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
- package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
- package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
- package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
- package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
- package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
- package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
- package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
- package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
- package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
- package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
- package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
- package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
- package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
package/dist/tui/text-width.js
DELETED
|
@@ -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
package/dist/tui/token-info.js
DELETED
|
@@ -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 {};
|
package/dist/tui/undo-stack.js
DELETED
|
@@ -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
|
-
}
|