min-agent 0.4.1 → 0.5.1

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 (69) hide show
  1. package/README.md +46 -2
  2. package/dist/agent.js +89 -29
  3. package/dist/cli/commands/chat.js +3 -0
  4. package/dist/cli/commands/ctx.js +7 -0
  5. package/dist/cli/commands/exec.js +3 -0
  6. package/dist/cli/commands/index.js +32 -7
  7. package/dist/cli/commands/memory.js +33 -15
  8. package/dist/cli/commands/setup.js +55 -3
  9. package/dist/cli/commands/shared.js +10 -1
  10. package/dist/cli/commands/think.js +12 -0
  11. package/dist/cli/commands/write-config.js +22 -0
  12. package/dist/cli/option-helpers.js +13 -1
  13. package/dist/cli/program.js +57 -14
  14. package/dist/cli/setup/detect.js +17 -0
  15. package/dist/cli/setup/flags.js +12 -0
  16. package/dist/cli/setup/flow.js +108 -0
  17. package/dist/cli/setup/provider-form.js +102 -0
  18. package/dist/cli/setup/ui.js +534 -0
  19. package/dist/code-mode.js +1 -1
  20. package/dist/config.js +93 -159
  21. package/dist/context-window.js +39 -49
  22. package/dist/ctx-cli.js +30 -0
  23. package/dist/ctx.js +80 -0
  24. package/dist/memory-cli.js +33 -0
  25. package/dist/memory.js +127 -46
  26. package/dist/model-catalog.js +285 -0
  27. package/dist/ollama-model.js +234 -0
  28. package/dist/ollama-openai-bridge.js +383 -0
  29. package/dist/permission-cli.js +1 -4
  30. package/dist/provider.js +4 -1
  31. package/dist/reasoning-stream.js +158 -0
  32. package/dist/sandbox-cli.js +1 -4
  33. package/dist/scope.js +23 -0
  34. package/dist/serve/common.js +22 -1
  35. package/dist/serve/routes-chat.js +21 -1
  36. package/dist/serve/routes-memory.js +31 -2
  37. package/dist/serve/routes-meta.js +69 -6
  38. package/dist/think-cli.js +36 -0
  39. package/dist/thinking-wire.js +239 -0
  40. package/dist/thinking.js +166 -0
  41. package/dist/token-display.js +10 -7
  42. package/dist/tools/todo.js +22 -8
  43. package/dist/tui/App.js +48 -8
  44. package/dist/tui/CtxPicker.js +68 -0
  45. package/dist/tui/InputBar.js +112 -37
  46. package/dist/tui/MessageList.js +53 -22
  47. package/dist/tui/StatusBar.js +7 -3
  48. package/dist/tui/ThinkPicker.js +75 -0
  49. package/dist/tui/bracketed-paste.js +37 -0
  50. package/dist/tui/caret-pos.js +10 -8
  51. package/dist/tui/index.js +13 -1
  52. package/dist/tui/layout.js +17 -0
  53. package/dist/tui/overlay-input.js +12 -0
  54. package/dist/tui/paste-draft.js +173 -0
  55. package/dist/tui/selection.js +8 -2
  56. package/dist/tui/slash-commands.js +24 -1
  57. package/dist/tui/slash-handler.js +88 -18
  58. package/dist/tui/text-width.js +6 -6
  59. package/dist/tui-chat.js +85 -7
  60. package/docs/API.md +69 -6
  61. package/docs/superpowers/plans/2026-08-23-cli-setup.md +501 -0
  62. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +475 -0
  63. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +450 -0
  64. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +282 -0
  65. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +174 -0
  66. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +140 -0
  67. package/package.json +1 -1
  68. package/skills/self-config/SKILL.md +7 -4
  69. package/skills/self-config/reference.md +12 -6
@@ -0,0 +1,534 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from "react";
3
+ import { Box, Text, render, useInput } from "ink";
4
+ import InkSpinner from "ink-spinner";
5
+ import path from "path";
6
+ import { fetchModelsLive, getActiveProvider, getConfigDir, loadConfig, normalizeOllamaBaseURL, saveConfig, } from "../../config.js";
7
+ import { theme } from "../../tui/theme.js";
8
+ import { detectLocalOllama } from "./detect.js";
9
+ import { applyAddProvider, applyRemoveProvider, applySwitchProvider, applyUpdateProvider, filterChoices, maskApiKey, suggestName, validateProviderName, } from "./provider-form.js";
10
+ const OPENAI_URL = "https://api.openai.com/v1";
11
+ const OLLAMA_URL = "http://localhost:11434/v1";
12
+ export async function renderSetupWizard(mode) {
13
+ return new Promise((resolve) => {
14
+ const instance = render(_jsx(SetupApp, { mode: mode, onDone: (result) => {
15
+ instance.unmount();
16
+ resolve(result);
17
+ } }), { exitOnCtrlC: false });
18
+ });
19
+ }
20
+ function SetupApp({ mode, onDone }) {
21
+ const [screen, setScreen] = useState(() => mode === "session-gate" ? { id: "gate" } : mode === "hub" ? { id: "hub" } : { id: "type" });
22
+ const [draft, setDraft] = useState(null);
23
+ const [detected, setDetected] = useState({ found: false });
24
+ const [error, setError] = useState("");
25
+ const [tick, setTick] = useState(0);
26
+ const reload = () => setTick((n) => n + 1);
27
+ void tick;
28
+ const config = loadConfig();
29
+ const abortFirstRun = () => onDone("aborted");
30
+ const finishHub = () => onDone("saved");
31
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [_jsx(Text, { bold: true, color: theme.accent, children: "\u914D\u7F6E\u670D\u52A1\u5546" }), screen.id === "gate" && (_jsx(ChoiceList, { items: [
32
+ { label: "开始配置", value: "start" },
33
+ { label: "退出", value: "exit" },
34
+ ], onSelect: (value) => {
35
+ if (value === "exit")
36
+ onDone("aborted");
37
+ else
38
+ setScreen({ id: "type" });
39
+ }, onCancel: () => onDone("aborted") })), screen.id === "hub" && (_jsx(HubScreen, { config: config, onAdd: () => {
40
+ setDraft({
41
+ fromHub: true,
42
+ type: "openai-compatible",
43
+ baseURL: OPENAI_URL,
44
+ apiKey: "",
45
+ defaultModel: "",
46
+ name: "",
47
+ });
48
+ setError("");
49
+ setScreen({ id: "type" });
50
+ }, onPick: (action) => {
51
+ const providers = config.providers ?? [];
52
+ if (action === "switch" && providers.length <= 1) {
53
+ setScreen({ id: "message", text: "当前只有一个服务商,无需切换。", back: "hub" });
54
+ return;
55
+ }
56
+ setScreen({ id: "pick", action });
57
+ }, onDone: finishHub })), screen.id === "pick" && (_jsx(ProviderPick, { config: config, onCancel: () => setScreen({ id: "hub" }), onSelect: (provider) => {
58
+ const name = provider.name ?? "";
59
+ if (screen.action === "switch") {
60
+ const next = applySwitchProvider(config, name);
61
+ if (isFormError(next)) {
62
+ setScreen({ id: "message", text: next.error, back: "hub" });
63
+ return;
64
+ }
65
+ saveConfig(next);
66
+ logSaved();
67
+ reload();
68
+ setScreen({ id: "hub" });
69
+ return;
70
+ }
71
+ if (screen.action === "delete") {
72
+ setScreen({ id: "delete-confirm", name });
73
+ return;
74
+ }
75
+ if (screen.action === "test") {
76
+ void (async () => {
77
+ const live = await fetchModelsLive(provider.baseURL, provider.apiKey);
78
+ if (live.ok && live.models.length > 0) {
79
+ setScreen({
80
+ id: "message",
81
+ text: `可访问,${live.models.length} 个模型`,
82
+ back: "hub",
83
+ });
84
+ return;
85
+ }
86
+ const status = live.status != null ? `HTTP ${live.status}。` : "";
87
+ const hint = provider.type === "ollama" ? "请确认 Ollama 已启动。" : "请检查 API 地址和密钥。";
88
+ setScreen({
89
+ id: "message",
90
+ text: redactSecrets(`无法连接到该服务商。${status}${hint}`),
91
+ back: "hub",
92
+ });
93
+ })();
94
+ return;
95
+ }
96
+ setDraft(draftFromProvider(provider, true));
97
+ setError("");
98
+ setScreen({ id: "type" });
99
+ } })), screen.id === "delete-confirm" && (_jsx(ChoiceList, { title: `确定删除服务商 "${screen.name}"?`, items: [
100
+ { label: "删除", value: "yes" },
101
+ { label: "取消", value: "no" },
102
+ ], onSelect: (value) => {
103
+ if (value !== "yes") {
104
+ setScreen({ id: "hub" });
105
+ return;
106
+ }
107
+ const next = applyRemoveProvider(loadConfig(), screen.name);
108
+ if (isFormError(next)) {
109
+ setScreen({ id: "message", text: next.error, back: "hub" });
110
+ return;
111
+ }
112
+ saveConfig(next);
113
+ logSaved();
114
+ reload();
115
+ setScreen({ id: "hub" });
116
+ }, onCancel: () => setScreen({ id: "hub" }) })), screen.id === "message" && (_jsx(MessageScreen, { text: screen.text, onClose: () => {
117
+ if (screen.back === "exit-saved")
118
+ finishHub();
119
+ else
120
+ setScreen({ id: "hub" });
121
+ } })), screen.id === "type" && (_jsx(TypeScreen, { detected: detected, onDetected: setDetected, initial: draft?.type, onCancel: () => {
122
+ if (draft?.fromHub || mode === "hub")
123
+ setScreen({ id: "hub" });
124
+ else
125
+ abortFirstRun();
126
+ }, onSelect: (type) => {
127
+ const next = startDraft(type, draft, detected);
128
+ setDraft(next);
129
+ setError("");
130
+ if (type === "openai")
131
+ setScreen({ id: "key" });
132
+ else
133
+ setScreen({ id: "url" });
134
+ } })), screen.id === "url" && draft && (_jsx(FieldScreen, { label: draft.type === "ollama" ? "Ollama 地址" : "API 地址", value: draft.baseURL, error: error, onChange: (baseURL) => setDraft({ ...draft, baseURL }), onCancel: () => {
135
+ setError("");
136
+ setScreen({ id: "type" });
137
+ }, onSubmit: () => {
138
+ const trimmed = draft.baseURL.trim();
139
+ if (!trimmed) {
140
+ setError("API 地址不能为空。");
141
+ return;
142
+ }
143
+ const baseURL = draft.type === "ollama" ? normalizeOllamaBaseURL(trimmed) : trimmed.replace(/\/$/, "");
144
+ setDraft({ ...draft, baseURL });
145
+ setError("");
146
+ if (draft.type === "ollama")
147
+ setScreen({ id: "fetch" });
148
+ else
149
+ setScreen({ id: "key" });
150
+ } })), screen.id === "key" && draft && (_jsx(FieldScreen, { label: "API \u5BC6\u94A5", value: draft.apiKey, mask: true, placeholder: draft.existingKey ? `已保存 (${maskApiKey(draft.existingKey)}),回车保留` : undefined, error: error, onChange: (apiKey) => setDraft({ ...draft, apiKey }), onCancel: () => {
151
+ setError("");
152
+ if (draft.type === "openai")
153
+ setScreen({ id: "type" });
154
+ else
155
+ setScreen({ id: "url" });
156
+ }, onSubmit: () => {
157
+ if (!draft.apiKey.trim() && draft.existingKey) {
158
+ setDraft({ ...draft, apiKey: draft.existingKey });
159
+ setError("");
160
+ setScreen({ id: "fetch" });
161
+ return;
162
+ }
163
+ if (!draft.apiKey.trim()) {
164
+ setError("API 密钥不能为空。");
165
+ return;
166
+ }
167
+ setError("");
168
+ setScreen({ id: "fetch" });
169
+ } })), screen.id === "fetch" && draft && (_jsx(FetchScreen, { draft: draft, onBack: () => setScreen(draft.type === "ollama" ? { id: "url" } : { id: "key" }), onList: (models) => {
170
+ setDraft({ ...draft, models });
171
+ setError("");
172
+ setScreen({ id: "models", models });
173
+ }, onEmpty: () => {
174
+ setError("");
175
+ setScreen({ id: "model-manual" });
176
+ } })), screen.id === "models" && draft && (_jsx(ModelListScreen, { models: screen.models, current: draft.defaultModel, onCancel: () => setScreen(draft.type === "openai" ? { id: "key" } : { id: "url" }), onSelect: (defaultModel) => {
177
+ setDraft({ ...draft, defaultModel });
178
+ setError("");
179
+ setScreen({ id: "name" });
180
+ } })), screen.id === "model-manual" && draft && (_jsx(FieldScreen, { label: "\u65E0\u6CD5\u83B7\u53D6\u6A21\u578B\u5217\u8868\uFF0C\u8BF7\u624B\u52A8\u8F93\u5165\u9ED8\u8BA4\u6A21\u578B", value: draft.defaultModel, error: error, onChange: (defaultModel) => setDraft({ ...draft, defaultModel }), onCancel: () => setScreen(draft.type === "openai" ? { id: "key" } : { id: "url" }), onSubmit: () => {
181
+ if (!draft.defaultModel.trim()) {
182
+ setError("默认模型不能为空。");
183
+ return;
184
+ }
185
+ setError("");
186
+ if (draft.type === "openai-compatible")
187
+ setScreen({ id: "context" });
188
+ else
189
+ setScreen({ id: "name" });
190
+ } })), screen.id === "context" && draft && (_jsx(FieldScreen, { label: "\u4E0A\u4E0B\u6587\u7A97\u53E3\uFF08token\uFF0C\u56DE\u8F66\u8DF3\u8FC7\uFF09", value: draft.contextWindow != null ? String(draft.contextWindow) : "", error: error, onChange: (raw) => {
191
+ const trimmed = raw.trim();
192
+ if (!trimmed) {
193
+ setDraft({ ...draft, contextWindow: undefined });
194
+ return;
195
+ }
196
+ const n = Number.parseInt(trimmed, 10);
197
+ setDraft({ ...draft, contextWindow: Number.isFinite(n) ? n : undefined });
198
+ }, onCancel: () => setScreen({ id: "model-manual" }), onSubmit: (raw) => {
199
+ const trimmed = raw.trim();
200
+ if (!trimmed) {
201
+ setDraft({ ...draft, contextWindow: undefined });
202
+ setError("");
203
+ setScreen({ id: "name" });
204
+ return;
205
+ }
206
+ const n = Number.parseInt(trimmed, 10);
207
+ if (!Number.isFinite(n) || n <= 0) {
208
+ setError("上下文窗口必须是大于 0 的整数。");
209
+ return;
210
+ }
211
+ setDraft({ ...draft, contextWindow: n });
212
+ setError("");
213
+ setScreen({ id: "name" });
214
+ } })), screen.id === "name" && draft && (_jsx(FieldScreen, { label: "\u540D\u79F0", value: draft.name, error: error, onChange: (name) => setDraft({ ...draft, name }), onCancel: () => {
215
+ if (draft.models && draft.models.length > 0)
216
+ setScreen({ id: "models", models: draft.models });
217
+ else
218
+ setScreen({ id: "model-manual" });
219
+ }, onSubmit: () => {
220
+ const nameError = validateProviderName(draft.name);
221
+ if (nameError) {
222
+ setError(nameError);
223
+ return;
224
+ }
225
+ setError("");
226
+ setScreen({ id: "summary" });
227
+ } })), screen.id === "summary" && draft && (_jsx(SummaryScreen, { draft: draft, error: error, onCancel: () => setScreen({ id: "name" }), onConfirm: () => {
228
+ const result = commitDraft(draft);
229
+ if (isFormError(result)) {
230
+ setError(result.error);
231
+ setScreen({ id: "name" });
232
+ return;
233
+ }
234
+ saveConfig(result);
235
+ logSaved();
236
+ if (draft.fromHub || mode === "hub") {
237
+ reload();
238
+ setDraft(null);
239
+ setScreen({ id: "hub" });
240
+ return;
241
+ }
242
+ onDone("saved");
243
+ } }))] }));
244
+ }
245
+ function HubScreen({ config, onAdd, onPick, onDone, }) {
246
+ const providers = config.providers ?? [];
247
+ const items = [
248
+ { label: "添加", value: "add" },
249
+ { label: "设为当前", value: "switch" },
250
+ { label: "修改", value: "edit" },
251
+ { label: "删除", value: "delete" },
252
+ { label: "测试连接", value: "test" },
253
+ { label: "完成", value: "done" },
254
+ ];
255
+ return (_jsxs(Box, { flexDirection: "column", children: [providers.map((p) => (_jsxs(Text, { color: p.name === config.activeProvider ? theme.accent : theme.muted, children: [p.name === config.activeProvider ? "* " : " ", p.name ?? "(未命名)", " ", p.baseURL, " ", p.defaultModel ?? "未设置模型"] }, p.name ?? p.baseURL))), _jsx(ChoiceList, { items: items, onSelect: (value) => {
256
+ if (value === "add")
257
+ onAdd();
258
+ else if (value === "done")
259
+ onDone();
260
+ else
261
+ onPick(value);
262
+ }, onCancel: onDone })] }));
263
+ }
264
+ function ProviderPick({ config, onSelect, onCancel, }) {
265
+ const providers = config.providers ?? [];
266
+ const active = getActiveProvider(config)?.name;
267
+ return (_jsx(ChoiceList, { items: providers.map((p) => ({
268
+ label: `${p.name ?? "(未命名)"}${p.name === active ? " *" : ""} ${p.baseURL}`,
269
+ value: p.name ?? "",
270
+ })), initial: Math.max(0, providers.findIndex((p) => p.name === active)), onSelect: (name) => {
271
+ const provider = providers.find((p) => p.name === name);
272
+ if (provider)
273
+ onSelect(provider);
274
+ }, onCancel: onCancel }));
275
+ }
276
+ function TypeScreen({ detected, onDetected, initial, onSelect, onCancel, }) {
277
+ useEffect(() => {
278
+ let cancelled = false;
279
+ void detectLocalOllama().then((result) => {
280
+ if (!cancelled)
281
+ onDetected(result);
282
+ });
283
+ return () => {
284
+ cancelled = true;
285
+ };
286
+ }, [onDetected]);
287
+ const items = [
288
+ { label: "OpenAI 兼容接口", hint: "OpenRouter、DeepSeek、Groq 等", value: "openai-compatible" },
289
+ { label: "OpenAI", hint: "官方接口", value: "openai" },
290
+ {
291
+ label: "Ollama(本地)",
292
+ hint: detected.found ? "已检测到" : "本机模型",
293
+ value: "ollama",
294
+ },
295
+ ];
296
+ const initialIndex = initial === "openai" ? 1 : initial === "ollama" ? 2 : detected.found && !initial ? 2 : 0;
297
+ return (_jsx(ChoiceList, { items: items, initial: initialIndex, onSelect: (value) => onSelect(value), onCancel: onCancel }, `${initial ?? "auto"}-${detected.found ? "detected" : "plain"}`));
298
+ }
299
+ function FetchScreen({ draft, onList, onEmpty, onBack, }) {
300
+ const onListRef = useRef(onList);
301
+ const onEmptyRef = useRef(onEmpty);
302
+ onListRef.current = onList;
303
+ onEmptyRef.current = onEmpty;
304
+ useInput((_input, key) => {
305
+ if (key.escape)
306
+ onBack();
307
+ });
308
+ useEffect(() => {
309
+ let cancelled = false;
310
+ void fetchModelsLive(draft.baseURL, draft.apiKey || "ollama").then((live) => {
311
+ if (cancelled)
312
+ return;
313
+ if (live.ok && live.models.length > 0)
314
+ onListRef.current(live.models);
315
+ else
316
+ onEmptyRef.current();
317
+ });
318
+ return () => {
319
+ cancelled = true;
320
+ };
321
+ }, [draft.apiKey, draft.baseURL]);
322
+ return (_jsxs(Box, { children: [_jsx(Text, { color: theme.accent, children: _jsx(InkSpinner, { type: "dots" }) }), _jsx(Text, { color: theme.muted, children: " \u6B63\u5728\u83B7\u53D6\u6A21\u578B\u5217\u8868\u2026" })] }));
323
+ }
324
+ function ModelListScreen({ models, current, onSelect, onCancel, }) {
325
+ const [query, setQuery] = useState("");
326
+ const [index, setIndex] = useState(0);
327
+ const filtered = filterChoices(query, models);
328
+ const q = query.trim();
329
+ useArmEnter((input, key, isEnter) => {
330
+ if (key.escape) {
331
+ onCancel();
332
+ return;
333
+ }
334
+ if (key.downArrow && filtered.length > 0) {
335
+ setIndex((i) => (i + 1) % filtered.length);
336
+ return;
337
+ }
338
+ if (key.upArrow && filtered.length > 0) {
339
+ setIndex((i) => (i - 1 + filtered.length) % filtered.length);
340
+ return;
341
+ }
342
+ if (isEnter) {
343
+ if (filtered.length > 0)
344
+ onSelect(filtered[Math.min(index, filtered.length - 1)]);
345
+ else if (q)
346
+ onSelect(q);
347
+ return;
348
+ }
349
+ if (key.backspace || key.delete) {
350
+ setQuery((v) => Array.from(v).slice(0, -1).join(""));
351
+ setIndex(0);
352
+ return;
353
+ }
354
+ if (key.ctrl || key.meta || key.tab)
355
+ return;
356
+ const clean = input.replace(/[\r\n]+$/, "");
357
+ if (clean) {
358
+ setQuery((v) => v + clean);
359
+ setIndex(0);
360
+ }
361
+ });
362
+ const shownIndex = Math.min(index, Math.max(0, filtered.length - 1));
363
+ const windowSize = 12;
364
+ const windowStart = Math.min(shownIndex, Math.max(0, filtered.length - windowSize));
365
+ const shown = filtered.slice(windowStart, windowStart + windowSize);
366
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.muted, children: "\u8F93\u5165\u53EF\u7B5B\u9009" }), _jsx(Text, { color: query ? theme.accent : theme.muted, children: query || " " }), shown.map((model, i) => {
367
+ const selected = windowStart + i === shownIndex;
368
+ return (_jsxs(Text, { color: selected ? theme.accent : undefined, bold: selected, children: [selected ? "▸ " : " ", model, model === current ? " ← 当前" : ""] }, model));
369
+ }), filtered.length === 0 && q !== "" && _jsxs(Text, { color: theme.accent, children: ["\u25B8 \u4F7F\u7528\u81EA\u5B9A\u4E49\u6A21\u578B: ", q] }), _jsx(Text, { color: theme.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
370
+ }
371
+ function FieldScreen({ label, value, mask, placeholder, error, onChange, onSubmit, onCancel, }) {
372
+ useArmEnter((input, key, isEnter) => {
373
+ if (key.escape) {
374
+ onCancel();
375
+ return;
376
+ }
377
+ if (isEnter) {
378
+ onSubmit(value);
379
+ return;
380
+ }
381
+ if (key.backspace || key.delete) {
382
+ onChange(Array.from(value).slice(0, -1).join(""));
383
+ return;
384
+ }
385
+ if (key.ctrl || key.meta || key.tab)
386
+ return;
387
+ const clean = input.replace(/[\r\n]+$/, "");
388
+ if (clean)
389
+ onChange(value + clean);
390
+ });
391
+ const shown = mask ? "*".repeat(Array.from(value).length) : value;
392
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: label }), _jsx(Text, { color: theme.accent, children: shown || placeholder || " " }), error ? _jsx(Text, { color: theme.error, children: error }) : null, _jsx(Text, { color: theme.muted, children: "Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
393
+ }
394
+ function SummaryScreen({ draft, error, onConfirm, onCancel, }) {
395
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["\u7C7B\u578B ", typeLabel(draft.type)] }), _jsxs(Text, { children: ["\u5730\u5740 ", draft.baseURL] }), _jsxs(Text, { children: ["\u5BC6\u94A5 ", maskApiKey(draft.apiKey)] }), _jsxs(Text, { children: ["\u6A21\u578B ", draft.defaultModel] }), _jsxs(Text, { children: ["\u540D\u79F0 ", draft.name.trim()] }), error ? _jsx(Text, { color: theme.error, children: error }) : null, _jsx(ChoiceList, { items: [
396
+ { label: "保存", value: "save" },
397
+ { label: "返回", value: "back" },
398
+ ], onSelect: (value) => {
399
+ if (value === "save")
400
+ onConfirm();
401
+ else
402
+ onCancel();
403
+ }, onCancel: onCancel })] }));
404
+ }
405
+ function MessageScreen({ text, onClose }) {
406
+ useArmEnter((_input, key, isEnter) => {
407
+ if (key.escape || isEnter)
408
+ onClose();
409
+ });
410
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: text }), _jsx(Text, { color: theme.muted, children: "Enter / Esc \u7EE7\u7EED" })] }));
411
+ }
412
+ function ChoiceList({ title, items, initial = 0, onSelect, onCancel, }) {
413
+ const [index, setIndex] = useState(Math.min(Math.max(0, initial), Math.max(0, items.length - 1)));
414
+ useArmEnter((_input, key, isEnter) => {
415
+ if (key.escape) {
416
+ onCancel();
417
+ return;
418
+ }
419
+ if (items.length === 0)
420
+ return;
421
+ if (key.downArrow) {
422
+ setIndex((i) => (i + 1) % items.length);
423
+ return;
424
+ }
425
+ if (key.upArrow) {
426
+ setIndex((i) => (i - 1 + items.length) % items.length);
427
+ return;
428
+ }
429
+ if (isEnter)
430
+ onSelect(items[index].value);
431
+ });
432
+ return (_jsxs(Box, { flexDirection: "column", children: [title ? _jsx(Text, { children: title }) : null, items.map((item, i) => (_jsxs(Text, { color: i === index ? theme.accent : undefined, bold: i === index, children: [i === index ? "▸ " : " ", item.label, item.hint ? _jsxs(Text, { color: theme.muted, children: [" ", item.hint] }) : null] }, item.value))), _jsx(Text, { color: theme.muted, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u8FD4\u56DE" })] }));
433
+ }
434
+ function useArmEnter(handler) {
435
+ const armed = useRef(false);
436
+ const handlerRef = useRef(handler);
437
+ handlerRef.current = handler;
438
+ useEffect(() => {
439
+ const timer = setTimeout(() => {
440
+ armed.current = true;
441
+ }, 0);
442
+ return () => clearTimeout(timer);
443
+ }, []);
444
+ useInput((input, key) => {
445
+ const isEnter = key.return || /[\r\n]+$/.test(input);
446
+ if (isEnter && !armed.current)
447
+ return;
448
+ handlerRef.current(input, key, isEnter);
449
+ });
450
+ }
451
+ function startDraft(type, previous, detected) {
452
+ const fromHub = previous?.fromHub === true || previous?.editingName != null;
453
+ const editingName = previous?.editingName;
454
+ if (type === "openai") {
455
+ return {
456
+ fromHub,
457
+ editingName,
458
+ type,
459
+ baseURL: OPENAI_URL,
460
+ apiKey: "",
461
+ existingKey: previous?.type === "openai" ? (previous.existingKey ?? previous.apiKey) : previous?.existingKey,
462
+ defaultModel: previous?.type === "openai" ? previous.defaultModel : "",
463
+ name: previous?.name ?? "",
464
+ };
465
+ }
466
+ if (type === "ollama") {
467
+ return {
468
+ fromHub,
469
+ editingName,
470
+ type,
471
+ baseURL: detected.baseURL || previous?.baseURL || OLLAMA_URL,
472
+ apiKey: "ollama",
473
+ defaultModel: previous?.type === "ollama" ? previous.defaultModel : "llama3",
474
+ name: previous?.name ?? "",
475
+ };
476
+ }
477
+ return {
478
+ fromHub,
479
+ editingName,
480
+ type,
481
+ baseURL: previous?.type === "openai-compatible" ? previous.baseURL : OPENAI_URL,
482
+ apiKey: "",
483
+ existingKey: previous?.existingKey,
484
+ defaultModel: previous?.defaultModel ?? "",
485
+ name: previous?.name ?? "",
486
+ };
487
+ }
488
+ function draftFromProvider(provider, fromHub) {
489
+ const type = provider.type ?? "openai-compatible";
490
+ return {
491
+ fromHub,
492
+ editingName: provider.name,
493
+ type,
494
+ baseURL: provider.baseURL,
495
+ apiKey: "",
496
+ existingKey: provider.apiKey,
497
+ defaultModel: provider.defaultModel ?? "",
498
+ contextWindow: provider.contextWindow,
499
+ name: provider.name ?? "",
500
+ };
501
+ }
502
+ function commitDraft(draft) {
503
+ const config = loadConfig();
504
+ const existing = (config.providers ?? []).map((p) => p.name).filter((name) => Boolean(name));
505
+ const name = draft.name.trim() ||
506
+ suggestName(draft.type, draft.baseURL, existing.filter((item) => item !== draft.editingName));
507
+ const payload = {
508
+ name,
509
+ type: draft.type,
510
+ baseURL: draft.baseURL,
511
+ apiKey: draft.apiKey || draft.existingKey || (draft.type === "ollama" ? "ollama" : ""),
512
+ defaultModel: draft.defaultModel.trim(),
513
+ ...(draft.contextWindow != null ? { contextWindow: draft.contextWindow } : {}),
514
+ };
515
+ if (draft.editingName)
516
+ return applyUpdateProvider(config, draft.editingName, payload);
517
+ return applyAddProvider(config, payload, { overwrite: false });
518
+ }
519
+ function typeLabel(type) {
520
+ if (type === "openai")
521
+ return "OpenAI";
522
+ if (type === "ollama")
523
+ return "Ollama";
524
+ return "OpenAI 兼容接口";
525
+ }
526
+ function logSaved() {
527
+ console.log(`已保存到 ${path.join(getConfigDir(), "config.json")}`);
528
+ }
529
+ function redactSecrets(text) {
530
+ return text.replace(/Bearer\s+\S+/gi, "Bearer ****").replace(/\bsk-[A-Za-z0-9_-]+/g, "sk-****");
531
+ }
532
+ function isFormError(result) {
533
+ return Object.keys(result).length === 1 && "error" in result;
534
+ }
package/dist/code-mode.js CHANGED
@@ -149,7 +149,7 @@ export function buildCodeSystemPrompt(project, instructions) {
149
149
  "- When the user asks to configure, install, or manage MCP servers, skills, rules, memory, permission, sandbox, providers, plugins, or other min-agent settings, follow the built-in `self-config` skill (already loaded). If its instructions are missing, load it with the skill tool before making changes.",
150
150
  "",
151
151
  "# Workflow",
152
- "- For greetings, thanks, or general chat with no concrete task, reply directly — do not call any tools. The Project Structure below already covers the basics; do not re-explore the codebase just to answer a greeting.",
152
+ "- For greetings, thanks, or general chat with no concrete task, reply directly — do not call any tools. Memories and session state are not a reason to start work; only the current user message is. The Project Structure below already covers the basics; do not re-explore the codebase just to answer a greeting.",
153
153
  "- Use search tools (grep, glob) to understand the codebase before making changes, only once the user gives a concrete task.",
154
154
  "- Use the search_web tool for live web search. Never emit XML tags such as <web_search>.",
155
155
  "- After a few targeted web searches and fetching the best sources, stop researching and produce the requested output.",