mocode-ai 0.4.4 → 0.4.6
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 +219 -209
- package/README.zh-CN.md +10 -0
- package/dist/agent/index.js +30 -13
- package/dist/config/index.js +13 -11
- package/dist/config/presets.js +177 -0
- package/dist/llm/capabilities.js +4 -0
- package/dist/llm/index.js +5 -1
- package/dist/memory/reflect.js +5 -2
- package/dist/repl/index.js +268 -10
- package/dist/tools/builtins/ask-human.js +71 -43
- package/dist/tools/builtins/codegraph.js +1 -1
- package/dist/ui/batch.js +235 -0
- package/dist/ui/content.js +51 -0
- package/dist/ui/diff.js +41 -4
- package/dist/ui/intervention.js +59 -20
- package/dist/ui/layout.js +449 -66
- package/dist/ui/prompt.js +99 -5
- package/dist/ui/render.js +22 -0
- package/dist/ui/theme.js +22 -0
- package/package.json +1 -1
package/dist/config/index.js
CHANGED
|
@@ -99,7 +99,7 @@ function buildPlanModeSuffix() {
|
|
|
99
99
|
## ⛯ PLAN MODE (active now)
|
|
100
100
|
You are in PLAN mode: investigate and design only — do NOT execute or change anything.
|
|
101
101
|
- Your editing / command tools (write_file, edit_file, run_command) have been REMOVED from your tool list. Use only the read-only tools available to you (read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human) to investigate.
|
|
102
|
-
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt.)
|
|
102
|
+
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt. But first check whether this conversation already covers it — don't re-explore something already retrieved earlier in this session.)
|
|
103
103
|
- Then produce a clear, actionable implementation plan: files to change (with paths), what to change in each and why, the ordered steps, edge cases to handle, and how to verify (typecheck / tests / build). Be specific enough to execute against.
|
|
104
104
|
- When the plan is complete and ready for review, you MUST call the \`ask_human\` tool to surface the plan to the user for approval — do NOT just output the plan as plain text and STOP. ask_human renders a real interactive selection panel inside the TUI; plain-text approval questions in your reply are hard to see and easy to miss.
|
|
105
105
|
- Pass the \`ask_human\` tool a concise plan summary (goal + files/areas to change + key risks + verification) and these three options so the user can decide in one click:
|
|
@@ -115,7 +115,7 @@ You are in PLAN mode: investigate and design only — do NOT execute or change a
|
|
|
115
115
|
## ⛯ PLAN MODE (active now)
|
|
116
116
|
You are in PLAN mode: investigate and design only — do NOT execute or change anything.
|
|
117
117
|
- Your editing / command / memory-write tools (write_file, edit_file, run_command, memory_save, memory_update, memory_forget) have been REMOVED from your tool list. Use only the read-only tools available to you (read_file, glob, grep, codegraph, web_search, web_fetch, use_skill, ask_human, memory_search, memory_list) to investigate.
|
|
118
|
-
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt.)
|
|
118
|
+
- Research thoroughly: locate the relevant code, trace call paths, and understand existing patterns and conventions before designing. (Codegraph is the default first action for code exploration — see Workflow in the base prompt. But first check whether this conversation already covers it — don't re-explore something already retrieved earlier in this session.)
|
|
119
119
|
- Then produce a clear, actionable implementation plan: files to change (with paths), what to change in each and why, the ordered steps, edge cases to handle, and how to verify (typecheck / tests / build). Be specific enough to execute against.
|
|
120
120
|
- When the plan is complete and ready for review, you MUST call the \`ask_human\` tool to surface the plan to the user for approval — do NOT just output the plan as plain text and STOP. ask_human renders a real interactive selection panel inside the TUI; plain-text approval questions in your reply are hard to see and easy to miss.
|
|
121
121
|
- Pass the \`ask_human\` tool a concise plan summary (goal + files/areas to change + key risks + verification) and these three options so the user can decide in one click:
|
|
@@ -145,6 +145,10 @@ ${PLATFORM_NOTE}
|
|
|
145
145
|
|
|
146
146
|
## Step / Turn Economy (read this first — saves LLM calls)
|
|
147
147
|
- **Minimize turns**: each user message costs at least one LLM call, and history grows every step until threshold-triggered compact fires (extra call). If a request contains ≥2 independent sub-goals (e.g. "改 X 然后再优化 Y"), ask the user to split them into separate turns rather than chaining both in one go. State this politely: "这条包含 N 个独立目标,建议拆成 N 次对话,以避免上下文膨胀。"
|
|
148
|
+
- **Answer directly when you already know the answer — do this before the batching rule below**: before planning any tool calls for this turn, first check whether you can answer from the current conversation, an earlier tool result already in context, a file/symbol already read in this session, or general reasoning/knowledge alone. If so, skip tools entirely and answer directly. Only call a tool when the info is genuinely missing, may be stale (the underlying file/state changed since you last read it), or requires verification you cannot do from context. This applies to every tool — codegraph, grep, web_search, run_command — not just read_file.
|
|
149
|
+
- ✅ already have it: user asks "刚才那个函数在哪个文件" after codegraph_explore returned it two turns ago → answer from that result, no new call.
|
|
150
|
+
- ✅ pure reasoning: user asks "这个改动会不会影响性能" and the relevant code/logic is already visible in context → reason and answer directly, no need to re-run a profiler or re-read the file "just to be safe".
|
|
151
|
+
- ❌ wasteful: re-running grep/codegraph for a symbol whose location this same conversation already returned, "just to be sure".
|
|
148
152
|
- **Plan the full turn, then emit it as one batch — this is the single biggest step-saver**: before emitting anything, enumerate every read / edit / command you'll need for this sub-goal, then return them together as one set of tool_calls (reads run in parallel, writes/commands run in the order given). Don't emit one call, observe, then emit the next in a follow-up turn when you could have planned both upfront.
|
|
149
153
|
- ✅ one turn: \`[read_file A, read_file B, edit_file A, run_command 'npm test']\`
|
|
150
154
|
- ❌ four turns: \`[read_file A]\` → \`[read_file B]\` → \`[edit_file A]\` → \`[run_command 'npm test']\`
|
|
@@ -160,18 +164,17 @@ ${PLATFORM_NOTE}
|
|
|
160
164
|
- **Code exploration first action**: before reading files with read_file or searching with grep, check if a .codegraph/ index exists. If it does, use the codegraph tool (explore for questions/features, node for a specific symbol) as your FIRST step — it returns source + call paths in one shot. Only fall back to read_file/grep when codegraph misses, you need just-changed content, or you're editing a known small file. Build the index with \`codegraph init\` if none exists.
|
|
161
165
|
- Small steps: break tasks into verifiable sub-steps. Before each step, think clearly about what to change and why.
|
|
162
166
|
- Verify after change: run typecheck / tests / build via run_command to confirm it works. Never claim done without verification.
|
|
167
|
+
- **Web search when freshness matters**: for tasks involving UI/interaction/copy/visual design, new SDKs or APIs, CVE/version upgrades, or anything likely past your training cutoff, web_search FIRST to ground your work in current material — don't fall back on stale templates (gradient+emoji defaults, "I hope this message finds you well" openers, generic AI-flavored phrasing). Routine coding (bug fixes, refactors, tests, internal docs) doesn't need it.
|
|
163
168
|
|
|
164
169
|
## Tool Guidelines
|
|
165
170
|
- See each tool's own description for parameters and usage; this section covers selection strategy and pitfalls only.
|
|
166
171
|
- **If the user gave a precise path or symbol, go directly**: read_file or codegraph node it — don't pre-validate with glob/grep.
|
|
167
|
-
- Before editing code, read_file to confirm actual content (with line numbers); don't guess from memory.
|
|
172
|
+
- Before editing code, read_file to confirm actual content (with line numbers); don't guess from memory. (Skip if you already read this exact content earlier in this session and nothing has changed it since — see Step Economy above.)
|
|
168
173
|
- For local edits use edit_file: old_string must be unique and match exactly (including indentation/newlines); include surrounding context lines to ensure uniqueness. Use write_file for new files or full rewrites.
|
|
169
|
-
- Use glob to find file paths, grep to search content. **Don't use run_command for file-level checks** (existence / listing / type) — those have no clean cmd.exe equivalent and Windows path escaping fails often. Use \`glob\` to list, and just call \`read_file\` to test existence (returns ENOENT as a clean error string).
|
|
170
|
-
- run_command
|
|
171
|
-
- Use web_search for information beyond training data (new versions, news, real-time data, latest APIs); don't answer potentially outdated info from memory.
|
|
172
|
-
- Use web_fetch to read a specific URL (a link from search results, or a URL given by the user); it only fetches static HTML — if a JS-rendered page yields no body, switch to web_search (its results include cleaned body text).
|
|
174
|
+
- Use glob to find file paths, grep to search content. **Don't use run_command for file-level checks** (existence / listing / type) — those have no clean cmd.exe equivalent and Windows path escaping fails often. Use \`glob\` to list, and just call \`read_file\` to test existence (returns ENOENT as a clean error string).
|
|
175
|
+
- run_command has side effects on the host — state intent before invoking (delete, install, push, reset, etc.).
|
|
173
176
|
- Call ask_human when you hit a decision point requiring user input (multiple implementation approaches, unclear intent, or needing extra info to proceed) — list options for the user to pick (they can also choose "custom input" to answer freely). Don't call it frequently when the task is clear and you can decide yourself; if the user cancels, switch approach or proceed with available info — don't re-ask the same question.
|
|
174
|
-
- **Trim context when stale**: when an old tool result is dead weight (sub-goal done, no downstream consumer, or superseded by a later read), call drop_context to stub it
|
|
177
|
+
- **Trim context when stale**: when an old tool result is dead weight (sub-goal done, no downstream consumer, or superseded by a later read), call drop_context to stub it; otherwise rely on automatic pruning.
|
|
175
178
|
- **Batch writes and commands too, not just reads**: the executor runs ALL returned tool_calls (reads, writes, commands) before the next LLM call. Emit independent edit_file / write_file / run_command in one response when the chain is clear — don't serialize them across turns just because they have side effects. (The read-only batching note in Step Economy applies to writes the same way.)
|
|
176
179
|
- **Chain shell workflows in a single \`run_command\`**: use \`&&\`, \`;\`, \`|\`, \`>\`, heredocs to fold multi-step scripts (\`mkdir -p x && cat > x/file.ts <<'EOF' ... EOF && npm test\`) into one call. Only emit a follow-up turn when the result forces a decision (error, ambiguous output, branching logic).
|
|
177
180
|
|
|
@@ -193,9 +196,8 @@ ${PLATFORM_NOTE}
|
|
|
193
196
|
${memorySection}
|
|
194
197
|
|
|
195
198
|
## Working notepad (todolist) — for multi-step tasks
|
|
196
|
-
- For tasks spanning **≥2 independent modules** OR when the user asks for stepwise progress ("先计划再执行" / "plan then do" / "按步骤来"), call \`todolist create\` first
|
|
197
|
-
-
|
|
198
|
-
- See the \`todolist\` tool description for the full action set (create / read / update / add_step / finish / list / unarchive / delete) and lifecycle.
|
|
199
|
+
- For tasks spanning **≥2 independent modules** OR when the user asks for stepwise progress ("先计划再执行" / "plan then do" / "按步骤来"), call \`todolist create\` first; update as you go. Skip for single-file edits or quick lookups.
|
|
200
|
+
- Plan is file-backed (\`todolist read\` to re-orient). See the tool description for the full action set.
|
|
199
201
|
|
|
200
202
|
## Termination & Reporting
|
|
201
203
|
- Stop immediately when no more tools are needed; give conclusions directly.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* 多模型预设(`/model save <name>` 保存的命名配置)的纯 I/O 叶子。
|
|
6
|
+
*
|
|
7
|
+
* 存储:每个预设一个独立 JSON 文件 `~/.mocode/models/<name>.json`。
|
|
8
|
+
* - 选用 per-file 而非单一 index.json:写不需要读-合并-写整本,天然原子(单文件 rename),
|
|
9
|
+
* 符合 memory/store.ts 的惯例;并发写多个预设互不打架。
|
|
10
|
+
* - 路径前缀与 CONFIG_PATH 共享 `~/.mocode/`,保证权限/位置一致。
|
|
11
|
+
*
|
|
12
|
+
* 不读 process.env、不触发 config 单例初始化——repl(/model 子命令)与 commands/config.ts
|
|
13
|
+
* 之外的脚本都能安全 import。错误一律抛(写失败属异常路径,调用方决定怎么提示用户)。
|
|
14
|
+
*
|
|
15
|
+
* 命名约束:[a-zA-Z0-9_-]{1,32}。理由:
|
|
16
|
+
* - 排除路径分隔符与 .. 防止越权写文件。
|
|
17
|
+
* - 与 dotenv key 风格一致,便于未来扩展到同名 env override。
|
|
18
|
+
* - 32 位上限防极长名撑爆文件名系统。
|
|
19
|
+
*/
|
|
20
|
+
/** 预设目录:`~/.mocode/models/`(按需创建)。 */
|
|
21
|
+
export const MODELS_DIR = path.join(os.homedir(), '.mocode', 'models');
|
|
22
|
+
const NAME_RE = /^[a-zA-Z0-9_-]{1,32}$/;
|
|
23
|
+
/** 名字是否合法(调用方复用,避免在多处重复同一正则)。 */
|
|
24
|
+
export function isValidPresetName(name) {
|
|
25
|
+
return NAME_RE.test(name);
|
|
26
|
+
}
|
|
27
|
+
/** 把合法的 name 拼成文件路径;非法 name 抛错(路径穿越防护的第二道)。 */
|
|
28
|
+
function filePathFor(name) {
|
|
29
|
+
if (!isValidPresetName(name)) {
|
|
30
|
+
throw new Error(`非法预设名: ${JSON.stringify(name)}(仅允许 [a-zA-Z0-9_-]{1,32})`);
|
|
31
|
+
}
|
|
32
|
+
return path.join(MODELS_DIR, `${name}.json`);
|
|
33
|
+
}
|
|
34
|
+
/** 把磁盘上的 raw JSON 解析并校验为 ModelPreset;非法字段抛错。 */
|
|
35
|
+
function parsePreset(raw) {
|
|
36
|
+
const obj = JSON.parse(raw);
|
|
37
|
+
const { name, baseURL, apiKey, model, contextWindow } = obj;
|
|
38
|
+
if (typeof name !== 'string' || !isValidPresetName(name)) {
|
|
39
|
+
throw new Error('预设文件 name 缺失或非法');
|
|
40
|
+
}
|
|
41
|
+
if (typeof baseURL !== 'string' || !baseURL) {
|
|
42
|
+
throw new Error(`预设 ${name}: baseURL 缺失`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof apiKey !== 'string' || !apiKey) {
|
|
45
|
+
throw new Error(`预设 ${name}: apiKey 缺失`);
|
|
46
|
+
}
|
|
47
|
+
if (typeof model !== 'string' || !model) {
|
|
48
|
+
throw new Error(`预设 ${name}: model 缺失`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof contextWindow !== 'number' || !Number.isFinite(contextWindow) || contextWindow <= 0) {
|
|
51
|
+
throw new Error(`预设 ${name}: contextWindow 必须为正数`);
|
|
52
|
+
}
|
|
53
|
+
return { name, baseURL, apiKey, model, contextWindow: Math.floor(contextWindow) };
|
|
54
|
+
}
|
|
55
|
+
/** 读单个预设;不存在抛错。 */
|
|
56
|
+
export function getPreset(name) {
|
|
57
|
+
const p = filePathFor(name);
|
|
58
|
+
return parsePreset(fs.readFileSync(p, 'utf8'));
|
|
59
|
+
}
|
|
60
|
+
/** 读单个预设;不存在返回 null(供列表/可选切换场景)。 */
|
|
61
|
+
export function readPreset(name) {
|
|
62
|
+
try {
|
|
63
|
+
return getPreset(name);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
if (e.code === 'ENOENT')
|
|
67
|
+
return null;
|
|
68
|
+
throw e;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** 写/覆盖一个预设(原子:写 tmp 再 rename)。 */
|
|
72
|
+
export function savePreset(preset) {
|
|
73
|
+
if (!isValidPresetName(preset.name)) {
|
|
74
|
+
throw new Error(`非法预设名: ${JSON.stringify(preset.name)}`);
|
|
75
|
+
}
|
|
76
|
+
fs.mkdirSync(MODELS_DIR, { recursive: true });
|
|
77
|
+
const dest = filePathFor(preset.name);
|
|
78
|
+
const tmp = `${dest}.tmp-${process.pid}-${Date.now()}`;
|
|
79
|
+
fs.writeFileSync(tmp, JSON.stringify(preset, null, 2), 'utf8');
|
|
80
|
+
fs.renameSync(tmp, dest);
|
|
81
|
+
}
|
|
82
|
+
/** 删除一个预设;不存在返回 false,成功返回 true。 */
|
|
83
|
+
export function deletePreset(name) {
|
|
84
|
+
try {
|
|
85
|
+
fs.unlinkSync(filePathFor(name));
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
if (e.code === 'ENOENT')
|
|
90
|
+
return false;
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 重命名一个预设(原子:link+unlink,跨设备时退化为 copy+unlink)。
|
|
96
|
+
* 用于 /model rename <old> <new>;不存在的旧名 / 已存在的新名返回 false,具体由调用方决定提示文案。
|
|
97
|
+
*/
|
|
98
|
+
export function renamePreset(oldName, newName) {
|
|
99
|
+
if (!isValidPresetName(newName)) {
|
|
100
|
+
throw new Error(`非法新名: ${JSON.stringify(newName)}`);
|
|
101
|
+
}
|
|
102
|
+
const oldPath = filePathFor(oldName);
|
|
103
|
+
const newPath = filePathFor(newName);
|
|
104
|
+
if (!fs.existsSync(oldPath))
|
|
105
|
+
return false;
|
|
106
|
+
if (fs.existsSync(newPath))
|
|
107
|
+
return false; // 拒绝覆盖,避免静默吞用户数据
|
|
108
|
+
fs.mkdirSync(MODELS_DIR, { recursive: true });
|
|
109
|
+
try {
|
|
110
|
+
fs.renameSync(oldPath, newPath);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
if (e.code !== 'EXDEV')
|
|
114
|
+
throw e;
|
|
115
|
+
// 跨设备:rename 会抛 EXDEV,改 copy+unlink。
|
|
116
|
+
fs.copyFileSync(oldPath, newPath);
|
|
117
|
+
fs.unlinkSync(oldPath);
|
|
118
|
+
}
|
|
119
|
+
// 重命名后同步更新文件内的 name 字段(我们写出去时总一致,但允许用户手改过 JSON 后不一致)。
|
|
120
|
+
const p = parsePreset(fs.readFileSync(newPath, 'utf8'));
|
|
121
|
+
if (p.name !== newName)
|
|
122
|
+
savePreset({ ...p, name: newName });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
/** 列出全部预设(按 name 升序);目录不存在返回空数组。 */
|
|
126
|
+
export function listPresets() {
|
|
127
|
+
if (!fs.existsSync(MODELS_DIR))
|
|
128
|
+
return [];
|
|
129
|
+
const out = [];
|
|
130
|
+
for (const entry of fs.readdirSync(MODELS_DIR)) {
|
|
131
|
+
if (!entry.endsWith('.json'))
|
|
132
|
+
continue;
|
|
133
|
+
const name = entry.slice(0, -'.json'.length);
|
|
134
|
+
if (!isValidPresetName(name))
|
|
135
|
+
continue; // 跳过非我们写的杂文件
|
|
136
|
+
try {
|
|
137
|
+
out.push(parsePreset(fs.readFileSync(path.join(MODELS_DIR, entry), 'utf8')));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// 单个坏文件不阻断列表;用户用 /model delete 显式清理即可。
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
out.sort((a, b) => a.name.localeCompare(b.name));
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* 把当前 config 的 LLM 四键(baseURL/apiKey/model/contextWindow)迁为命名预设。
|
|
148
|
+
* 用于启动时一次性兜底老用户:加 /model 之前就已经在 ~/.mocode/config 写过的配置,
|
|
149
|
+
* /model list 应该立刻能看到,而不是空。已有同名预设则不重复写。
|
|
150
|
+
*
|
|
151
|
+
* 返回新建的预设名;若未配置完整(baseURL/apiKey 缺失)或已有同名则返回 null。
|
|
152
|
+
* 设计为幂等:重启调用一次也只会生效一次。
|
|
153
|
+
*/
|
|
154
|
+
export function migrateCurrentToPreset(input) {
|
|
155
|
+
if (!input.baseURL || !input.apiKey || !input.model)
|
|
156
|
+
return null;
|
|
157
|
+
if (!Number.isFinite(input.contextWindow) || input.contextWindow <= 0)
|
|
158
|
+
return null;
|
|
159
|
+
const existing = listPresets();
|
|
160
|
+
const dup = existing.find((p) => p.baseURL === input.baseURL &&
|
|
161
|
+
p.apiKey === input.apiKey &&
|
|
162
|
+
p.model === input.model &&
|
|
163
|
+
p.contextWindow === input.contextWindow);
|
|
164
|
+
if (dup)
|
|
165
|
+
return null;
|
|
166
|
+
// 'default' 已被占 → 用户已显式起过预设,无需老数据迁入;返回 null 让调用方跳过即可。
|
|
167
|
+
if (existing.some((p) => p.name === 'default'))
|
|
168
|
+
return null;
|
|
169
|
+
savePreset({
|
|
170
|
+
name: 'default',
|
|
171
|
+
baseURL: input.baseURL,
|
|
172
|
+
apiKey: input.apiKey,
|
|
173
|
+
model: input.model,
|
|
174
|
+
contextWindow: input.contextWindow,
|
|
175
|
+
});
|
|
176
|
+
return 'default';
|
|
177
|
+
}
|
package/dist/llm/capabilities.js
CHANGED
|
@@ -26,6 +26,9 @@ const KNOWN_TEXT_ONLY_PREFIXES = [
|
|
|
26
26
|
'babbage-',
|
|
27
27
|
'davinci-',
|
|
28
28
|
'gpt-4o-mini-search', // 搜索专用,无视觉入口
|
|
29
|
+
// MiniMax M2 系列(M2 / M2.1 / M2.5 / M2.7,含各自 -highspeed 变体):纯文本,无视觉输入。
|
|
30
|
+
// 官方文档明确仅 MiniMax-M3 支持 image/video content parts;M2.x 传 image_url 会被拒。
|
|
31
|
+
'minimax-m2',
|
|
29
32
|
];
|
|
30
33
|
const KNOWN_VISION_FAMILIES = [
|
|
31
34
|
'gpt-4o',
|
|
@@ -54,6 +57,7 @@ const KNOWN_VISION_FAMILIES = [
|
|
|
54
57
|
'minicpm-v',
|
|
55
58
|
'glm-4v',
|
|
56
59
|
'yi-vl',
|
|
60
|
+
'minimax-m3', // 官方文档:仅 M3 支持 image_url/video_url content parts
|
|
57
61
|
];
|
|
58
62
|
/** 归一化:小写、去空白;用于前缀比较。 */
|
|
59
63
|
function normalize(model) {
|
package/dist/llm/index.js
CHANGED
|
@@ -264,11 +264,15 @@ async function chatOnce(messages, handlers, signal, toolsOverride) {
|
|
|
264
264
|
const create = createImplOverride
|
|
265
265
|
? createImplOverride
|
|
266
266
|
: (body, opts) => client.chat.completions.create(body, opts);
|
|
267
|
+
const activeTools = toolsOverride ?? chatTools;
|
|
267
268
|
const stream = await create({
|
|
268
269
|
model: config.model,
|
|
269
270
|
messages,
|
|
270
|
-
tools:
|
|
271
|
+
tools: activeTools,
|
|
271
272
|
stream: true,
|
|
273
|
+
// 显式声明允许一次响应携带多个 tool_call(OpenAI 兼容协议标准字段)。
|
|
274
|
+
// 不设置时依赖各家后端的默认值,某些第三方网关/模型在缺省时会退化为串行单步调用。
|
|
275
|
+
...(activeTools.length > 0 ? { parallel_tool_calls: true } : {}),
|
|
272
276
|
...(config.maxTokens ? { max_tokens: config.maxTokens } : {}),
|
|
273
277
|
...(config.includeUsage ? { stream_options: { include_usage: true } } : {}),
|
|
274
278
|
}, signal ? { signal } : undefined);
|
package/dist/memory/reflect.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import path from 'node:path';
|
|
13
13
|
import { chat } from '../llm/index.js';
|
|
14
|
-
import { config } from '../config/index.js';
|
|
14
|
+
import { config, isMemoryEnabled } from '../config/index.js';
|
|
15
15
|
import { saveEntry, updateEntry, forgetEntry, loadAll, gcMemories, } from './store.js';
|
|
16
16
|
// ── 日志(静默容错,裁尾保最近)─────────────────────────────────────────────
|
|
17
17
|
function logPath() {
|
|
@@ -224,7 +224,8 @@ export function formatReflectResult(r) {
|
|
|
224
224
|
return `记忆反思:${parts.join(' ')}${r.error ? ` [${r.error}]` : ''}`;
|
|
225
225
|
}
|
|
226
226
|
/**
|
|
227
|
-
* fire-and-forget 触发反思。已有在飞任务 / autoReflect 关闭 → 跳过。
|
|
227
|
+
* fire-and-forget 触发反思。已有在飞任务 / autoReflect 关闭 / 记忆子系统总开关关闭 → 跳过。
|
|
228
|
+
* 记忆关闭时反思毫无意义(没有可存的地方),且会误打日志、误弹摘要,故一并短路。
|
|
228
229
|
* repl 轮末调:与下一轮 agent 并发跑,不阻塞。
|
|
229
230
|
*/
|
|
230
231
|
export function kickoffReflection(transcript) {
|
|
@@ -232,6 +233,8 @@ export function kickoffReflection(transcript) {
|
|
|
232
233
|
return;
|
|
233
234
|
if (!config.autoReflect)
|
|
234
235
|
return;
|
|
236
|
+
if (!isMemoryEnabled())
|
|
237
|
+
return;
|
|
235
238
|
inflight = (async () => {
|
|
236
239
|
try {
|
|
237
240
|
const r = await runReflection(transcript);
|