mocode-ai 1.3.9 → 1.4.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 (134) hide show
  1. package/README.md +78 -70
  2. package/README.zh-CN.md +81 -73
  3. package/bin/mocode-agent-host.js +1 -1
  4. package/dist/agent/core.js +226 -293
  5. package/dist/agent/index.js +14 -10
  6. package/dist/agent/mode.js +3 -3
  7. package/dist/agent/runtime-context.js +45 -0
  8. package/dist/agent/spawn.js +101 -86
  9. package/dist/agent/tool-helpers.js +134 -0
  10. package/dist/agent/trace-state.js +118 -0
  11. package/dist/agents/coordinator.js +6 -1
  12. package/dist/changeset/index.js +9 -8
  13. package/dist/config/index.js +109 -77
  14. package/dist/config/presets.js +3 -1
  15. package/dist/config/profiles.js +125 -0
  16. package/dist/context/age-aware.js +2 -4
  17. package/dist/context/artifacts.js +6 -13
  18. package/dist/context/budget.js +6 -8
  19. package/dist/context/classifier.js +1 -2
  20. package/dist/context/encoders/code.js +2 -6
  21. package/dist/context/encoders/command.js +1 -3
  22. package/dist/context/encoders/search.js +1 -3
  23. package/dist/context/index.js +1 -1
  24. package/dist/context/lifecycle.js +5 -3
  25. package/dist/context/pipeline.js +1 -1
  26. package/dist/context/relevance.js +2 -4
  27. package/dist/context/token-calibration.js +13 -18
  28. package/dist/host/stdio.js +73 -12
  29. package/dist/i18n/index.js +96 -44
  30. package/dist/index.js +1 -1
  31. package/dist/llm/index.js +39 -23
  32. package/dist/llm/provider.js +23 -0
  33. package/dist/llm/providers/anthropic.js +12 -18
  34. package/dist/llm/tool-schema.js +48 -0
  35. package/dist/mcp/client.js +70 -25
  36. package/dist/mcp/config.js +1 -3
  37. package/dist/mcp/index.js +3 -1
  38. package/dist/memory/reflect.js +3 -6
  39. package/dist/memory/store.js +10 -12
  40. package/dist/permissions/index.js +66 -22
  41. package/dist/pet/bridge.js +2 -2
  42. package/dist/repl/commands/appearance.js +72 -0
  43. package/dist/repl/commands/compact.js +114 -0
  44. package/dist/repl/commands/context.js +17 -0
  45. package/dist/repl/commands/image.js +71 -0
  46. package/dist/repl/commands/memory.js +51 -0
  47. package/dist/repl/commands/mode.js +47 -0
  48. package/dist/repl/commands/model.js +435 -0
  49. package/dist/repl/commands/pet.js +70 -0
  50. package/dist/repl/commands/registry.js +46 -0
  51. package/dist/repl/commands/session.js +102 -0
  52. package/dist/repl/commands/skill.js +95 -0
  53. package/dist/repl/commands/system.js +202 -0
  54. package/dist/repl/commands/tool-group.js +233 -0
  55. package/dist/repl/commands/types.js +4 -0
  56. package/dist/repl/commands.js +324 -0
  57. package/dist/repl/index.js +5 -2577
  58. package/dist/repl/message-format.js +151 -0
  59. package/dist/repl/running-input.js +147 -0
  60. package/dist/repl/runtime.js +816 -0
  61. package/dist/repl/status-bar.js +161 -0
  62. package/dist/rollback/index.js +44 -14
  63. package/dist/runtime/browser-manager.js +1 -1
  64. package/dist/runtime/dev-server-manager.js +1 -1
  65. package/dist/runtime/input-injector.js +313 -0
  66. package/dist/runtime/screen-capture.js +89 -0
  67. package/dist/runtime/screen-pipeline.js +223 -0
  68. package/dist/sandbox/index.js +1 -1
  69. package/dist/sandbox/policy.js +8 -2
  70. package/dist/session/compact.js +22 -34
  71. package/dist/session/index.js +2 -2
  72. package/dist/session/notes.js +39 -5
  73. package/dist/session/persist.js +14 -8
  74. package/dist/session/scheduler.js +3 -4
  75. package/dist/session/trace-metrics.js +6 -3
  76. package/dist/skills/activation.js +10 -5
  77. package/dist/skills/discover.js +5 -17
  78. package/dist/skills/index.js +1 -1
  79. package/dist/skills/runner.js +28 -44
  80. package/dist/skills/toolmap.js +5 -2
  81. package/dist/tools/builtins/browser.js +17 -3
  82. package/dist/tools/builtins/computer.js +322 -0
  83. package/dist/tools/builtins/dev-server.js +1 -3
  84. package/dist/tools/builtins/edit-file.js +21 -7
  85. package/dist/tools/builtins/grep.js +1 -1
  86. package/dist/tools/builtins/index.js +28 -25
  87. package/dist/tools/builtins/memory-forget.js +1 -1
  88. package/dist/tools/builtins/memory-graph.js +4 -7
  89. package/dist/tools/builtins/memory-list.js +2 -6
  90. package/dist/tools/builtins/memory-save.js +8 -2
  91. package/dist/tools/builtins/memory-search.js +4 -4
  92. package/dist/tools/builtins/note-append.js +14 -3
  93. package/dist/tools/builtins/plan-update.js +40 -9
  94. package/dist/tools/builtins/run-command.js +8 -8
  95. package/dist/tools/builtins/screenshot.js +11 -87
  96. package/dist/tools/builtins/task.js +29 -40
  97. package/dist/tools/builtins/use-skill.js +1 -1
  98. package/dist/tools/builtins/view-image.js +5 -3
  99. package/dist/tools/builtins/web-fetch.js +1 -3
  100. package/dist/tools/builtins/web-search.js +3 -7
  101. package/dist/tools/builtins/write-file.js +4 -2
  102. package/dist/tools/constants.js +50 -37
  103. package/dist/tools/policy.js +221 -0
  104. package/dist/tools/registry.js +40 -19
  105. package/dist/tools/resource-lock.js +4 -2
  106. package/dist/tools/router.js +143 -0
  107. package/dist/tools/validation.js +6 -5
  108. package/dist/ui/batch.js +63 -23
  109. package/dist/ui/clipboard.js +17 -5
  110. package/dist/ui/content.js +166 -13
  111. package/dist/ui/diff.js +61 -19
  112. package/dist/ui/fuzzy.js +35 -2
  113. package/dist/ui/intervention.js +2 -4
  114. package/dist/ui/layout-internal/content-write.js +587 -0
  115. package/dist/ui/layout-internal/core.js +590 -0
  116. package/dist/ui/layout-internal/input-paint.js +365 -0
  117. package/dist/ui/layout-internal/screen.js +128 -0
  118. package/dist/ui/layout-internal/scroll.js +152 -0
  119. package/dist/ui/layout-internal/selection.js +229 -0
  120. package/dist/ui/layout-internal/state.js +82 -0
  121. package/dist/ui/layout-internal/statusbar.js +351 -0
  122. package/dist/ui/layout-types.js +1 -0
  123. package/dist/ui/layout.js +3 -2288
  124. package/dist/ui/markdown.js +10 -2
  125. package/dist/ui/prompt-internal/editor.js +945 -0
  126. package/dist/ui/prompt-internal/paste.js +52 -0
  127. package/dist/ui/prompt-internal/pickers.js +519 -0
  128. package/dist/ui/prompt-internal/types.js +1 -0
  129. package/dist/ui/prompt.js +4 -1506
  130. package/dist/ui/render.js +87 -2
  131. package/dist/verification/affected.js +11 -5
  132. package/dist/verification/discovery.js +1 -3
  133. package/dist/verification/profile.js +11 -3
  134. package/package.json +11 -2
@@ -0,0 +1,233 @@
1
+ /**
2
+ * 自动工具路由的 capability gate 命令组:/subagent · /fe(/frontend) · /cu(/computer) ·
3
+ * /mcp · /memory_switch · /memory_status。
4
+ *
5
+ * `/subagent`、`/fe`、`/cu` 不再把工具常驻加入 schema:on/unset 仅允许下一真实用户轮的
6
+ * LLM router 按需选择对应簇,off 写入 env=false 并形成硬否决。refreshChatTools 只为没有
7
+ * ToolPolicy 的 legacy 嵌入路径保留。MCP 仍负责真实连接生命周期;memory 开关还控制索引。
8
+ */
9
+ import * as layout from '../../ui/layout.js';
10
+ import { ui } from '../../ui/theme.js';
11
+ import { bannerLines } from '../../ui/render.js';
12
+ import { t } from '../../i18n/index.js';
13
+ import { isSubAgentRouteAllowed, updateSubAgentConfig, isFrontendRouteAllowed, updateFrontendToolsConfig, isComputerUseRouteAllowed, updateComputerUseConfig, isMcpEnabled, updateMcpConfig, isMemoryEnabled, isMemoryRouteAllowed, updateMemoryConfig, } from '../../config/index.js';
14
+ import { updateConfigKey, CONFIG_PATH } from '../../config/file.js';
15
+ import { getAgentMode } from '../../agent/mode.js';
16
+ import { refreshChatTools } from '../../llm/index.js';
17
+ import { registerToolsExtension } from '../../tools/registry.js';
18
+ import { initializeAllMcp, getMcpTools, closeAllMcp } from '../../mcp/index.js';
19
+ import { unhandled, next } from './types.js';
20
+ export const toolGroupCommands = [
21
+ // /subagent [status|on|off] — orchestration 簇的兼容 capability gate。
22
+ (ctx) => {
23
+ const { line } = ctx;
24
+ if (line !== '/subagent' && !line.startsWith('/subagent '))
25
+ return unhandled();
26
+ const arg = line.startsWith('/subagent ') ? line.slice('/subagent '.length).trim().toLowerCase() : 'status';
27
+ if (arg === '' || arg === 'status') {
28
+ const enabled = isSubAgentRouteAllowed();
29
+ const state = t(enabled ? 'subagent.stateOn' : 'subagent.stateOff');
30
+ layout.contentWrite(`${ui.accent}${t('subagent.status', { state })}${ui.reset}\n` +
31
+ `${ui.dim}MOCODE_SUBAGENT_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n`);
32
+ return next();
33
+ }
34
+ if (arg !== 'on' && arg !== 'off') {
35
+ layout.contentWrite(`${ui.yellow}${t('subagent.usage')}${ui.reset}\n`);
36
+ return next();
37
+ }
38
+ const enabled = arg === 'on';
39
+ if (enabled !== isSubAgentRouteAllowed()) {
40
+ updateSubAgentConfig(enabled);
41
+ updateConfigKey('MOCODE_SUBAGENT_ENABLED', enabled ? 'true' : 'false');
42
+ refreshChatTools();
43
+ ctx.history[0] = { role: 'system', content: ctx.buildSystemMessage(getAgentMode() === 'plan') };
44
+ layout.rewriteBanner(bannerLines(ctx.banner()));
45
+ }
46
+ layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'subagent.changedOn' : 'subagent.changedOff')}${ui.reset}\n`);
47
+ return next();
48
+ },
49
+ // /fe|/frontend [status|on|off] — browser-debug + desktop-observe 的兼容 gate。
50
+ // view_image 是 common 工具,不受此开关影响。
51
+ (ctx) => {
52
+ const { line } = ctx;
53
+ const isFe = line === '/fe' || line.startsWith('/fe ') || line === '/frontend' || line.startsWith('/frontend ');
54
+ if (!isFe)
55
+ return unhandled();
56
+ const raw = line.startsWith('/fe ')
57
+ ? line.slice('/fe '.length)
58
+ : line.startsWith('/frontend ')
59
+ ? line.slice('/frontend '.length)
60
+ : line === '/frontend'
61
+ ? ''
62
+ : line.slice('/fe'.length);
63
+ const arg = raw.trim().toLowerCase();
64
+ if (arg === '' || arg === 'status') {
65
+ const enabled = isFrontendRouteAllowed();
66
+ const state = t(enabled ? 'fe.stateOn' : 'fe.stateOff');
67
+ layout.contentWrite(`${ui.accent}${t('fe.status', { state })}${ui.reset}\n` +
68
+ `${ui.dim}MOCODE_FRONTEND_TOOLS_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n`);
69
+ return next();
70
+ }
71
+ if (arg !== 'on' && arg !== 'off') {
72
+ layout.contentWrite(`${ui.yellow}${t('fe.usage')}${ui.reset}\n`);
73
+ return next();
74
+ }
75
+ const enabled = arg === 'on';
76
+ if (enabled !== isFrontendRouteAllowed()) {
77
+ updateFrontendToolsConfig(enabled);
78
+ updateConfigKey('MOCODE_FRONTEND_TOOLS_ENABLED', enabled ? 'true' : 'false');
79
+ refreshChatTools();
80
+ ctx.history[0] = { role: 'system', content: ctx.buildSystemMessage(getAgentMode() === 'plan') };
81
+ layout.rewriteBanner(bannerLines(ctx.banner()));
82
+ }
83
+ layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'fe.changedOn' : 'fe.changedOff')}${ui.reset}\n`);
84
+ return next();
85
+ },
86
+ // /cu|/computer [status|on|off] — computer-control 的高危 capability gate。
87
+ // on 仅允许 router 在用户明确要求 GUI 操作时选择;每个动作仍经过权限门,plan 永远过滤。
88
+ (ctx) => {
89
+ const { line } = ctx;
90
+ const isCu = line === '/cu' || line.startsWith('/cu ') || line === '/computer' || line.startsWith('/computer ');
91
+ if (!isCu)
92
+ return unhandled();
93
+ const raw = line.startsWith('/cu ')
94
+ ? line.slice('/cu '.length)
95
+ : line.startsWith('/computer ')
96
+ ? line.slice('/computer '.length)
97
+ : line === '/computer'
98
+ ? ''
99
+ : line.slice('/cu'.length);
100
+ const arg = raw.trim().toLowerCase();
101
+ if (arg === '' || arg === 'status') {
102
+ const enabled = isComputerUseRouteAllowed();
103
+ const state = t(enabled ? 'cu.stateOn' : 'cu.stateOff');
104
+ layout.contentWrite(`${ui.accent}${t('cu.status', { state })}${ui.reset}\n` +
105
+ `${ui.dim}MOCODE_COMPUTER_USE_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n`);
106
+ return next();
107
+ }
108
+ if (arg !== 'on' && arg !== 'off') {
109
+ layout.contentWrite(`${ui.yellow}${t('cu.usage')}${ui.reset}\n`);
110
+ return next();
111
+ }
112
+ const enabled = arg === 'on';
113
+ if (enabled !== isComputerUseRouteAllowed()) {
114
+ updateComputerUseConfig(enabled);
115
+ updateConfigKey('MOCODE_COMPUTER_USE_ENABLED', enabled ? 'true' : 'false');
116
+ refreshChatTools();
117
+ ctx.history[0] = { role: 'system', content: ctx.buildSystemMessage(getAgentMode() === 'plan') };
118
+ layout.rewriteBanner(bannerLines(ctx.banner()));
119
+ }
120
+ layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'cu.changedOn' : 'cu.changedOff')}${ui.reset}\n`);
121
+ return next();
122
+ },
123
+ // /mcp [status|on|off] — 唯一带 async 副作用的开关(on 连 MCP,off 断)。
124
+ async (ctx) => {
125
+ const { line } = ctx;
126
+ if (line !== '/mcp' && !line.startsWith('/mcp '))
127
+ return unhandled();
128
+ const arg = line.startsWith('/mcp ') ? line.slice('/mcp '.length).trim().toLowerCase() : 'status';
129
+ if (arg === '' || arg === 'status') {
130
+ const enabled = isMcpEnabled();
131
+ const state = t(enabled ? 'mcp.stateOn' : 'mcp.stateOff');
132
+ layout.contentWrite(`${ui.accent}${t('mcp.status', { state })}${ui.reset}\n` +
133
+ `${ui.dim}MOCODE_MCP_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n` +
134
+ `${ui.dim}${t('mcp.restartHint')}${ui.reset}\n`);
135
+ return next();
136
+ }
137
+ if (arg !== 'on' && arg !== 'off') {
138
+ layout.contentWrite(`${ui.yellow}${t('mcp.usage')}${ui.reset}\n`);
139
+ return next();
140
+ }
141
+ const enabled = arg === 'on';
142
+ if (enabled === isMcpEnabled()) {
143
+ layout.contentWrite(`${ui.dim}${t('mcp.unchanged', { state: t(enabled ? 'mcp.stateOn' : 'mcp.stateOff') })}${ui.reset}\n`);
144
+ return next();
145
+ }
146
+ updateMcpConfig(enabled);
147
+ updateConfigKey('MOCODE_MCP_ENABLED', enabled ? 'true' : 'false');
148
+ if (enabled) {
149
+ await initializeAllMcp();
150
+ registerToolsExtension('mcp', getMcpTools());
151
+ }
152
+ else {
153
+ await closeAllMcp();
154
+ registerToolsExtension('mcp', []);
155
+ }
156
+ refreshChatTools();
157
+ ctx.history[0] = { role: 'system', content: ctx.buildSystemMessage(getAgentMode() === 'plan') };
158
+ layout.rewriteBanner(bannerLines(ctx.banner()));
159
+ layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'mcp.changedOn' : 'mcp.changedOff')}${ui.reset}\n`);
160
+ return next();
161
+ },
162
+ // /memory_switch [on|off|status] · /memory_status — 同时管理 memory route gate 与 Memory Index。
163
+ // 无参切换;status 明确区分“可被路由”和“索引/反思已启用”,避免 unset 状态被误报为禁用。
164
+ (ctx) => {
165
+ const { line } = ctx;
166
+ const isMemSwitch = line === '/memory_switch' ||
167
+ line.startsWith('/memory_switch ') ||
168
+ line === '/memory_status' ||
169
+ line.startsWith('/memory_status ');
170
+ if (!isMemSwitch)
171
+ return unhandled();
172
+ try {
173
+ const switchArg = line.startsWith('/memory_switch ')
174
+ ? line.slice('/memory_switch '.length).trim().toLowerCase()
175
+ : '';
176
+ const showStatus = line === '/memory_status' || line.startsWith('/memory_status ') || switchArg === 'status';
177
+ if (showStatus) {
178
+ const routeAllowed = isMemoryRouteAllowed();
179
+ const indexEnabled = isMemoryEnabled();
180
+ layout.contentWrite(`${ui.accent}${t('memory.routeStatus', {
181
+ state: t(routeAllowed ? 'memory.stateAllowed' : 'memory.stateBlocked'),
182
+ })}${ui.reset}\n` +
183
+ `${ui.accent}${t('memory.indexStatus', {
184
+ state: t(indexEnabled ? 'memory.stateEnabled' : 'memory.stateDisabled'),
185
+ })}${ui.reset}\n`);
186
+ layout.contentWrite(`${ui.dim}${t('memory.source', {
187
+ value: process.env.MEMORY_ENABLED ?? '<unset>',
188
+ path: CONFIG_PATH,
189
+ })}${ui.reset}\n` + `${ui.dim}${t('memory.statusHint')}${ui.reset}\n`);
190
+ return next();
191
+ }
192
+ let nextEnabled;
193
+ if (switchArg === '') {
194
+ nextEnabled = !isMemoryEnabled();
195
+ }
196
+ else if (['on', 'true', '1', 'yes', 'y', 'enable', 'enabled'].includes(switchArg)) {
197
+ nextEnabled = true;
198
+ }
199
+ else if (['off', 'false', '0', 'no', 'n', 'disable', 'disabled'].includes(switchArg)) {
200
+ nextEnabled = false;
201
+ }
202
+ else {
203
+ layout.contentWrite(`${ui.yellow}${t('memory.usage')}${ui.reset}\n`);
204
+ return next();
205
+ }
206
+ const indexEnabled = isMemoryEnabled();
207
+ const routeAllowed = isMemoryRouteAllowed();
208
+ const alreadyAligned = nextEnabled ? indexEnabled && routeAllowed : !indexEnabled && !routeAllowed;
209
+ if (alreadyAligned) {
210
+ layout.contentWrite(`${ui.dim}${t('memory.unchanged', {
211
+ state: t(nextEnabled ? 'memory.stateEnabled' : 'memory.stateDisabled'),
212
+ })}${ui.reset}\n`);
213
+ return next();
214
+ }
215
+ updateMemoryConfig(nextEnabled);
216
+ updateConfigKey('MEMORY_ENABLED', nextEnabled ? 'true' : 'false');
217
+ // 自动路由读取 env gate;legacy chatTools 同步刷新。
218
+ refreshChatTools();
219
+ ctx.history[0] = { role: 'system', content: ctx.buildSystemMessage(getAgentMode() === 'plan') };
220
+ layout.contentWrite(`${nextEnabled ? ui.green : ui.yellow}${t(nextEnabled ? 'memory.changedOn' : 'memory.changedOff')}${ui.reset}\n`);
221
+ layout.contentWrite(`${ui.dim}${t('memory.saved', {
222
+ path: CONFIG_PATH,
223
+ value: nextEnabled ? 'true' : 'false',
224
+ })}${ui.reset}\n`);
225
+ // 即时刷新 banner;当前已发出的 immutable policy snapshot 不受影响。
226
+ layout.rewriteBanner(bannerLines(ctx.banner()));
227
+ }
228
+ catch (error) {
229
+ layout.contentWrite(`${ui.red}${t('memory.failed', { error: error instanceof Error ? error.message : String(error) })}${ui.reset}\n`);
230
+ }
231
+ return next();
232
+ },
233
+ ];
@@ -0,0 +1,4 @@
1
+ export const unhandled = () => ({ kind: 'unhandled' });
2
+ export const next = () => ({ kind: 'next' });
3
+ export const exit = () => ({ kind: 'exit' });
4
+ export const forward = (input) => ({ kind: 'forward', input });
@@ -0,0 +1,324 @@
1
+ import { ui } from '../ui/theme.js';
2
+ import { t } from '../i18n/index.js';
3
+ import { DEFAULT_CONTEXT_WINDOW_TOKENS } from '../config/index.js';
4
+ export const PROMPT = '❯ ';
5
+ /**
6
+ * 斜杠命令树(仅用于输入菜单;分发仍走下方 if 链)。
7
+ * 分支节点只负责导航,叶子的 value 保持现有命令文本,因此不破坏命令兼容性。
8
+ */
9
+ export function buildSlashCommands() {
10
+ const d = (key) => t(key);
11
+ return [
12
+ { name: '/help', desc: d('commands.help') },
13
+ { name: '/exit', desc: d('commands.exit') },
14
+ { name: '/clear', desc: d('commands.clear') },
15
+ { name: '/context', desc: d('commands.context') },
16
+ { name: '/skills', desc: d('commands.skills') },
17
+ { name: '/skill', desc: '执行某个 skill(/skill <name> [args-json])' },
18
+ { name: '/compact', desc: d('commands.compact') },
19
+ { name: '/resume', desc: d('commands.sessionResume') },
20
+ { name: '/sessions', desc: d('commands.sessionBrowse') },
21
+ { name: '/rollback', desc: d('commands.sessionRollback') },
22
+ {
23
+ name: '/memory',
24
+ desc: d('commands.memory'),
25
+ children: [
26
+ { name: 'overview', value: '/memory', desc: d('commands.memoryOverview') },
27
+ { name: 'toggle', value: '/memory_switch', desc: d('commands.memoryToggle') },
28
+ { name: 'on', value: '/memory_switch on', desc: d('commands.memoryOn') },
29
+ { name: 'off', value: '/memory_switch off', desc: d('commands.memoryOff') },
30
+ { name: 'status', value: '/memory_status', desc: d('commands.memoryStatus') },
31
+ { name: 'reflect', value: '/reflect', desc: d('commands.memoryReflect') },
32
+ ],
33
+ },
34
+ { name: '/init', desc: d('commands.memoryInit') },
35
+ {
36
+ name: '/subagent',
37
+ desc: d('commands.subagent'),
38
+ children: [
39
+ { name: 'on', value: '/subagent on', desc: d('commands.subagentOn') },
40
+ { name: 'off', value: '/subagent off', desc: d('commands.subagentOff') },
41
+ { name: 'status', value: '/subagent status', desc: d('commands.subagentStatus') },
42
+ ],
43
+ },
44
+ {
45
+ name: '/fe',
46
+ desc: d('commands.fe'),
47
+ children: [
48
+ { name: 'on', value: '/fe on', desc: d('commands.feOn') },
49
+ { name: 'off', value: '/fe off', desc: d('commands.feOff') },
50
+ { name: 'status', value: '/fe status', desc: d('commands.feStatus') },
51
+ ],
52
+ },
53
+ {
54
+ name: '/cu',
55
+ desc: d('commands.cu'),
56
+ children: [
57
+ { name: 'on', value: '/cu on', desc: d('commands.cuOn') },
58
+ { name: 'off', value: '/cu off', desc: d('commands.cuOff') },
59
+ { name: 'status', value: '/cu status', desc: d('commands.cuStatus') },
60
+ ],
61
+ },
62
+ {
63
+ name: '/mcp',
64
+ desc: d('commands.mcp'),
65
+ children: [
66
+ { name: 'on', value: '/mcp on', desc: d('commands.mcpOn') },
67
+ { name: 'off', value: '/mcp off', desc: d('commands.mcpOff') },
68
+ { name: 'status', value: '/mcp status', desc: d('commands.mcpStatus') },
69
+ ],
70
+ },
71
+ { name: '/theme', desc: d('commands.theme') },
72
+ {
73
+ name: '/model',
74
+ desc: d('commands.model'),
75
+ children: [
76
+ { name: 'configure', value: '/model', desc: d('commands.modelConfigure') },
77
+ { name: 'switch', value: '/model switch', desc: d('commands.modelSwitch') },
78
+ { name: 'list', value: '/model list', desc: d('commands.modelList') },
79
+ { name: 'show', value: '/model show', desc: d('commands.modelShow') },
80
+ { name: 'use <name>', value: '/model use ', submit: false, desc: d('commands.modelUse') },
81
+ { name: 'delete <name>', value: '/model delete ', submit: false, desc: d('commands.modelDelete') },
82
+ ],
83
+ },
84
+ {
85
+ name: '/mode',
86
+ desc: d('commands.mode'),
87
+ children: [
88
+ { name: 'plan', value: '/plan', desc: d('commands.modePlan') },
89
+ { name: 'auto', value: '/auto', desc: d('commands.modeAuto') },
90
+ ],
91
+ },
92
+ {
93
+ name: '/pet',
94
+ desc: d('commands.pet'),
95
+ children: [
96
+ { name: 'toggle', value: '/pet', desc: d('commands.petToggle') },
97
+ { name: 'skin', value: '/pet skin', desc: d('commands.petSkin') },
98
+ { name: 'quit', value: '/pet quit', desc: d('commands.petQuit') },
99
+ ],
100
+ },
101
+ {
102
+ name: '/image',
103
+ desc: d('commands.image'),
104
+ children: [
105
+ { name: 'attach <path>', value: '/image ', submit: false, desc: d('commands.imageAttach') },
106
+ { name: 'list', value: '/image list', desc: d('commands.imageList') },
107
+ { name: 'clear', value: '/image clear', desc: d('commands.imageClear') },
108
+ ],
109
+ },
110
+ {
111
+ name: '/language',
112
+ desc: d('commands.language'),
113
+ children: [
114
+ { name: 'zh-CN', value: '/language zh-CN', desc: d('commands.languageZh') },
115
+ { name: 'en', value: '/language en', desc: d('commands.languageEn') },
116
+ ],
117
+ },
118
+ {
119
+ name: '/upgrade',
120
+ desc: d('commands.upgrade'),
121
+ children: [
122
+ { name: 'now', value: '/upgrade', desc: d('commands.upgradeNow') },
123
+ { name: 'check', value: '/upgrade check', desc: d('commands.upgradeCheck') },
124
+ { name: 'status', value: '/upgrade status', desc: d('commands.upgradeStatus') },
125
+ ],
126
+ },
127
+ ];
128
+ }
129
+ /** 从菜单树递归生成 /help 内容;叶子 value 与菜单路径不同则同时展示真实命令。 */
130
+ export function slashHelpLines(nodes = buildSlashCommands(), parentPath = '', depth = 0) {
131
+ const lines = [];
132
+ for (const node of nodes) {
133
+ const menuPath = parentPath ? `${parentPath} ${node.name}` : node.name;
134
+ const isBranch = Boolean(node.children?.length);
135
+ const actual = node.value?.trimEnd();
136
+ const mapping = actual && actual !== menuPath ? ` ${ui.dim}→ ${actual}${ui.reset}` : '';
137
+ const marker = isBranch ? ` ${ui.dim}›${ui.reset}` : '';
138
+ lines.push(`${' '.repeat(depth)}${ui.accent}${menuPath}${ui.reset}${marker}${mapping} ${ui.dim}${node.desc}${ui.reset}`);
139
+ if (node.children?.length)
140
+ lines.push(...slashHelpLines(node.children, menuPath, depth + 1));
141
+ }
142
+ return lines;
143
+ }
144
+ /** chat 常见错误类别 → 中文引导文案的 i18n 键(识别不出的错误不走这张表,保留原始诊断)。 */
145
+ export const LLM_ERROR_HINT_KEYS = {
146
+ auth: 'repl.llmAuthError',
147
+ quota: 'repl.llmQuotaError',
148
+ timeout: 'repl.llmTimeoutError',
149
+ network: 'repl.llmNetworkError',
150
+ context: 'repl.llmContextError',
151
+ };
152
+ /** /help 的分组:按使用场景归组,组内保持菜单树顺序。未列入的顶层命令兜底进「其他」。 */
153
+ export const HELP_GROUPS = [
154
+ { key: 'help.groupFrequent', names: ['/help', '/clear', '/context', '/compact', '/resume', '/model'] },
155
+ { key: 'help.groupSession', names: ['/sessions', '/rollback', '/memory', '/skills', '/skill', '/init'] },
156
+ {
157
+ key: 'help.groupConfig',
158
+ names: ['/mode', '/subagent', '/fe', '/mcp', '/theme', '/pet', '/image', '/language', '/upgrade'],
159
+ },
160
+ ];
161
+ /**
162
+ * 菜单树里全部可用命令名(含分支路径与叶子的真实命令),供未知命令纠错建议用。
163
+ * /quit、/frontend 确实可用但没登记进菜单树,单独补上——否则用户照提示打了却仍被判未知。
164
+ */
165
+ export function knownCommandNames() {
166
+ const out = [];
167
+ const walk = (nodes, parent) => {
168
+ for (const node of nodes) {
169
+ const path = parent ? `${parent} ${node.name}` : node.name;
170
+ out.push(path);
171
+ const actual = node.value?.trim();
172
+ if (actual)
173
+ out.push(actual.split(/\s+/)[0]);
174
+ if (node.children?.length)
175
+ walk(node.children, path);
176
+ }
177
+ };
178
+ walk(buildSlashCommands(), '');
179
+ out.push('/quit', '/frontend');
180
+ return [...new Set(out)];
181
+ }
182
+ /**
183
+ * 是否"长得像一条斜杠命令":单个前导 / + 词字符(字母开头,可含数字/下划线/连字符)。
184
+ * 用来把 /hepl 这类拼写错误与 /tmp/foo、/src/index.ts:12:5 这类路径区分开——
185
+ * 后者在编码 agent 里很常见,同样以 / 开头,绝不能被当成命令拦下。
186
+ */
187
+ export function isCommandShape(cmd) {
188
+ return /^\/[A-Za-z][\w-]*$/.test(cmd);
189
+ }
190
+ /** Levenshtein 距离(标准 DP)。短命令字符串,直接两行数组滚动即可。 */
191
+ export function editDistance(a, b) {
192
+ if (a === b)
193
+ return 0;
194
+ if (!a.length)
195
+ return b.length;
196
+ if (!b.length)
197
+ return a.length;
198
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
199
+ for (let i = 1; i <= a.length; i++) {
200
+ const cur = [i];
201
+ for (let j = 1; j <= b.length; j++) {
202
+ cur[j] = Math.min(prev[j] + 1, // 删
203
+ cur[j - 1] + 1, // 插
204
+ prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
205
+ }
206
+ prev = cur;
207
+ }
208
+ return prev[b.length];
209
+ }
210
+ /** 未知命令的纠错建议:返回编辑距离最近且在阈值内的命令名,没有则 null(不要瞎猜)。 */
211
+ export function suggestCommand(input) {
212
+ // 阈值随长度放宽,但对短命令保持严格:/hepl(5) vs /help 距离 2 → 命中;
213
+ // /x(2) 不该被建议成任何东西。
214
+ const threshold = Math.max(1, Math.ceil(input.length / 3));
215
+ let best = null;
216
+ let bestDist = Infinity;
217
+ for (const name of knownCommandNames()) {
218
+ if (name === input)
219
+ continue; // 别建议"你自己"(分支节点如 /mode 也在候选里)
220
+ const d = editDistance(input, name);
221
+ if (d < bestDist) {
222
+ bestDist = d;
223
+ best = name;
224
+ }
225
+ }
226
+ return bestDist <= threshold ? best : null;
227
+ }
228
+ /** 主题名 → 本地化描述。 */
229
+ export function themeDescription(name) {
230
+ const key = `theme.${name}`;
231
+ return name in
232
+ {
233
+ default: 1,
234
+ light: 1,
235
+ solarized: 1,
236
+ gruvbox: 1,
237
+ nord: 1,
238
+ orange: 1,
239
+ rose: 1,
240
+ emerald: 1,
241
+ amber: 1,
242
+ lavender: 1,
243
+ sunset: 1,
244
+ }
245
+ ? t(key)
246
+ : '';
247
+ }
248
+ /** /model 预设后端:同时声明原生协议;旧服务继续走 OpenAI-compatible。 */
249
+ export const MODEL_PRESETS = [
250
+ {
251
+ label: 'Anthropic Claude',
252
+ provider: 'anthropic',
253
+ baseURL: 'https://api.anthropic.com',
254
+ model: 'claude-sonnet-4-5',
255
+ window: 200000,
256
+ anthropicPromptCache: true,
257
+ },
258
+ {
259
+ label: 'GLM(智谱)',
260
+ provider: 'openai',
261
+ baseURL: 'https://open.bigmodel.cn/api/v3',
262
+ model: 'glm-4.6',
263
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
264
+ anthropicPromptCache: false,
265
+ },
266
+ {
267
+ label: 'DeepSeek',
268
+ provider: 'openai',
269
+ baseURL: 'https://api.deepseek.com',
270
+ model: 'deepseek-chat',
271
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
272
+ anthropicPromptCache: false,
273
+ },
274
+ {
275
+ label: 'Qwen(阿里)',
276
+ provider: 'openai',
277
+ baseURL: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
278
+ model: 'qwen-plus',
279
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
280
+ anthropicPromptCache: false,
281
+ },
282
+ // MiniMax OpenAI 兼容端点(https://platform.minimax.io/docs/api-reference/text-openai-api)。
283
+ // MiniMax-M3 为唯一支持图片/视频输入的模型;M2 系列纯文本(见 llm/capabilities.ts KNOWN_TEXT_ONLY_PREFIXES)。
284
+ {
285
+ label: 'MiniMax',
286
+ provider: 'openai',
287
+ baseURL: 'https://api.minimax.io/v1',
288
+ model: 'MiniMax-M3',
289
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
290
+ anthropicPromptCache: false,
291
+ },
292
+ {
293
+ label: '本地 Ollama',
294
+ provider: 'openai',
295
+ baseURL: 'http://localhost:11434/v1',
296
+ model: 'qwen2.5:7b',
297
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
298
+ anthropicPromptCache: false,
299
+ },
300
+ {
301
+ label: '本地 vLLM',
302
+ provider: 'openai',
303
+ baseURL: 'http://localhost:8000/v1',
304
+ model: 'default',
305
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
306
+ anthropicPromptCache: false,
307
+ },
308
+ {
309
+ label: '自定义 base_url',
310
+ provider: 'openai',
311
+ baseURL: '',
312
+ model: '',
313
+ window: DEFAULT_CONTEXT_WINDOW_TOKENS,
314
+ anthropicPromptCache: false,
315
+ },
316
+ ];
317
+ /** apiKey 脱敏:只露末 4 位,前面打星号(显示用,绝不把明文 key 写进内容区)。 */
318
+ export function maskKey(k) {
319
+ if (!k)
320
+ return '(未设置)';
321
+ if (k.length <= 8)
322
+ return '****';
323
+ return `${'='.repeat(Math.min(k.length - 4, 20))}${k.slice(-4)}`;
324
+ }