mocode-ai 1.2.8 → 1.2.9
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/dist/agent/core.js +126 -43
- package/dist/config/index.js +31 -6
- package/dist/config/presets.js +36 -1
- package/dist/context/budget.js +0 -15
- package/dist/llm/index.js +33 -0
- package/dist/repl/index.js +57 -13
- package/dist/session/compact.js +237 -13
- package/dist/session/notes.js +63 -0
- package/dist/session/scheduler.js +5 -9
- package/dist/tools/constants.js +7 -0
- package/package.json +1 -1
- package/dist/__trace_manual_test__.js +0 -1
- package/dist/agent/core.d.ts +0 -112
- package/dist/agent/index.d.ts +0 -19
- package/dist/agent/mode.d.ts +0 -29
- package/dist/agent/retry-classifier.js +0 -64
- package/dist/agent/spawn.d.ts +0 -36
- package/dist/attachments/image.d.ts +0 -22
- package/dist/commands/config.d.ts +0 -8
- package/dist/commands/skill.js +0 -230
- package/dist/config/file.d.ts +0 -23
- package/dist/config/index.d.ts +0 -114
- package/dist/config/presets.d.ts +0 -57
- package/dist/context/budget.d.ts +0 -79
- package/dist/context/classifier.d.ts +0 -14
- package/dist/context/encoders/_util.d.ts +0 -11
- package/dist/context/encoders/code.d.ts +0 -2
- package/dist/context/encoders/doc.d.ts +0 -14
- package/dist/context/encoders/graph.d.ts +0 -11
- package/dist/context/encoders/graph.js +0 -81
- package/dist/context/encoders/index.d.ts +0 -2
- package/dist/context/encoders/log.d.ts +0 -2
- package/dist/context/encoders/log.js +0 -2
- package/dist/context/encoders/memory.d.ts +0 -2
- package/dist/context/encoders/passthrough.d.ts +0 -11
- package/dist/context/encoders/search.d.ts +0 -2
- package/dist/context/encoders/summary.d.ts +0 -12
- package/dist/context/encoders/table.d.ts +0 -2
- package/dist/context/encoders/tree.d.ts +0 -2
- package/dist/context/index.d.ts +0 -6
- package/dist/context/lifecycle.d.ts +0 -51
- package/dist/context/pipeline.d.ts +0 -9
- package/dist/context/registry.d.ts +0 -9
- package/dist/context/relevance.d.ts +0 -65
- package/dist/context/types.d.ts +0 -36
- package/dist/foo.js +0 -28
- package/dist/foo.test.js +0 -12
- package/dist/index.d.ts +0 -1
- package/dist/llm/capabilities.d.ts +0 -19
- package/dist/llm/index.d.ts +0 -111
- package/dist/memory/discover.d.ts +0 -15
- package/dist/memory/index.d.ts +0 -13
- package/dist/memory/reflect.d.ts +0 -34
- package/dist/memory/store.d.ts +0 -124
- package/dist/pet/bridge.d.ts +0 -73
- package/dist/pet/protocol.d.ts +0 -83
- package/dist/pet/state.d.ts +0 -28
- package/dist/plan/active.d.ts +0 -23
- package/dist/plan/active.js +0 -45
- package/dist/plan/index.d.ts +0 -13
- package/dist/plan/index.js +0 -33
- package/dist/plan/state.d.ts +0 -2
- package/dist/plan/state.js +0 -11
- package/dist/plan/store.d.ts +0 -78
- package/dist/plan/store.js +0 -429
- package/dist/project-skill/index.js +0 -95
- package/dist/project-skill/initializer.js +0 -178
- package/dist/project-snapshot/index.js +0 -85
- package/dist/project-snapshot/llm-snapshot.js +0 -150
- package/dist/project-snapshot/static-files.js +0 -87
- package/dist/repl/index.d.ts +0 -22
- package/dist/rollback/index.d.ts +0 -79
- package/dist/sandbox/command.d.ts +0 -3
- package/dist/sandbox/index.d.ts +0 -4
- package/dist/sandbox/jail.d.ts +0 -17
- package/dist/sandbox/policy.d.ts +0 -21
- package/dist/sandbox/root.d.ts +0 -5
- package/dist/session/compact.d.ts +0 -92
- package/dist/session/index.d.ts +0 -13
- package/dist/session/notes-plan.js +0 -41
- package/dist/session/persist.d.ts +0 -30
- package/dist/session/scheduler.d.ts +0 -53
- package/dist/skills/discover.d.ts +0 -33
- package/dist/skills/index.d.ts +0 -13
- package/dist/skills/skill-eval.js +0 -345
- package/dist/skills/skill-improve.js +0 -221
- package/dist/skills/stats.js +0 -102
- package/dist/tools/builtins/apply-patch.js +0 -174
- package/dist/tools/builtins/ask-human.d.ts +0 -9
- package/dist/tools/builtins/codegraph.d.ts +0 -2
- package/dist/tools/builtins/codegraph.js +0 -128
- package/dist/tools/builtins/edit-file.d.ts +0 -2
- package/dist/tools/builtins/glob.d.ts +0 -2
- package/dist/tools/builtins/grep.d.ts +0 -2
- package/dist/tools/builtins/index.d.ts +0 -2
- package/dist/tools/builtins/memory-forget.d.ts +0 -2
- package/dist/tools/builtins/memory-list.d.ts +0 -2
- package/dist/tools/builtins/memory-save.d.ts +0 -2
- package/dist/tools/builtins/memory-search.d.ts +0 -2
- package/dist/tools/builtins/memory-update.d.ts +0 -2
- package/dist/tools/builtins/project-skill-update.js +0 -102
- package/dist/tools/builtins/read-file.d.ts +0 -2
- package/dist/tools/builtins/run-command.d.ts +0 -2
- package/dist/tools/builtins/switch-mode.d.ts +0 -2
- package/dist/tools/builtins/switch-mode.js +0 -44
- package/dist/tools/builtins/task.d.ts +0 -2
- package/dist/tools/builtins/todolist.d.ts +0 -2
- package/dist/tools/builtins/todolist.js +0 -411
- package/dist/tools/builtins/use-skill.d.ts +0 -2
- package/dist/tools/builtins/web-fetch.d.ts +0 -2
- package/dist/tools/builtins/web-search.d.ts +0 -2
- package/dist/tools/builtins/write-file.d.ts +0 -2
- package/dist/tools/constants.d.ts +0 -52
- package/dist/tools/registry.d.ts +0 -18
- package/dist/tools/retry.js +0 -137
- package/dist/tools/types.d.ts +0 -42
- package/dist/ui/batch.d.ts +0 -67
- package/dist/ui/clipboard.d.ts +0 -9
- package/dist/ui/content.d.ts +0 -55
- package/dist/ui/diff.d.ts +0 -34
- package/dist/ui/intervention.d.ts +0 -46
- package/dist/ui/layout.d.ts +0 -225
- package/dist/ui/markdown.d.ts +0 -46
- package/dist/ui/mouse.d.ts +0 -29
- package/dist/ui/prompt.d.ts +0 -61
- package/dist/ui/render.d.ts +0 -60
- package/dist/ui/spinner.d.ts +0 -26
- package/dist/ui/theme.d.ts +0 -47
- package/dist/ui/thinking.js +0 -80
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
// todolist 工具:LLM 的「工作记事本」——把隐式思考外化为持久 checklist,抗压缩、可视化。
|
|
2
|
-
//
|
|
3
|
-
// 落地:plan/store.ts(文件源)+ plan/active.ts(进程级活跃缓存)+ 本工具(写盘 + 通知 listener)。
|
|
4
|
-
//
|
|
5
|
-
// 契约对齐「调度器永不抛错、永远返回字符串」(tools/registry.ts executeTool)。
|
|
6
|
-
// 单 plan/会话:create 前若有 in_progress 活跃 plan → 拒绝(避免误覆盖);finish/abandoned 后可再建。
|
|
7
|
-
//
|
|
8
|
-
// 工具返回:把当前 plan 紧凑渲染给 LLM(不只返操作结果——让 LLM 单次调用后看到完整状态,无需再 read)。
|
|
9
|
-
import { newPlanId, readPlan, writePlan, updatePlan, listPlans, archivePlan, unarchivePlan, deletePlanAnywhere, renderPlanForLLM, localIsoTimestamp, } from '../../plan/store.js';
|
|
10
|
-
import { getActivePlan, setActivePlan, hasActivePlan, clearActivePlan } from '../../plan/active.js';
|
|
11
|
-
import { MAX_OUTPUT } from '../constants.js';
|
|
12
|
-
const VALID_STATUS = new Set([
|
|
13
|
-
'pending', 'in_progress', 'done', 'skipped', 'failed',
|
|
14
|
-
]);
|
|
15
|
-
const VALID_PLAN_STATUS = new Set([
|
|
16
|
-
'in_progress', 'finished', 'abandoned',
|
|
17
|
-
]);
|
|
18
|
-
// ── 工具定义 ──
|
|
19
|
-
export const todolistTool = {
|
|
20
|
-
name: 'todolist',
|
|
21
|
-
description: [
|
|
22
|
-
'Maintain a working "notepad" plan in .mocode/plans/<id>.md (file-based, survives context compression).',
|
|
23
|
-
'',
|
|
24
|
-
'## PREREQUISITES (before create)',
|
|
25
|
-
'Research first: explore codebase (read-only tools) → clarify with user (ask_human) → have concrete steps.',
|
|
26
|
-
'Do NOT create prematurely — if you haven\'t done research and confirmation, stop and do that first.',
|
|
27
|
-
'',
|
|
28
|
-
'## WHEN TO USE / NOT USE',
|
|
29
|
-
'ONLY for genuinely complex tasks: ≥3 file changes, ≥5 tool calls across phases, multi-phase features,',
|
|
30
|
-
'or user explicitly requests ("先计划再执行" / "plan then do").',
|
|
31
|
-
'Skip for: single-file edits, bug fixes, quick lookups, ≤3 focused tool calls. If in doubt, just execute.',
|
|
32
|
-
'',
|
|
33
|
-
'## STEP GRANULARITY (4-5 steps max)',
|
|
34
|
-
'Each step = one meaningful execution unit (a phase, not a single tool call).',
|
|
35
|
-
'GOOD: "调研现有架构 → 设计新接口 → 实现核心逻辑 → 编写测试 → 集成验证"',
|
|
36
|
-
'BAD: "打开文件A → 修改函数X → 保存文件A → 运行测试" (too fine-grained, just do it)',
|
|
37
|
-
'',
|
|
38
|
-
'## UPDATE WORKFLOW',
|
|
39
|
-
'Update in real-time after completing each step (one step = one update, or batch_update for 2-3 at once).',
|
|
40
|
-
'Do NOT batch all updates at the end — update as you go so the chip reflects real progress.',
|
|
41
|
-
'All steps done/skipped → plan auto-finishes, archives, and chip disappears.',
|
|
42
|
-
'',
|
|
43
|
-
'## LIFECYCLE',
|
|
44
|
-
'Single plan per session: create refuses if an in-progress plan already exists — finish or abandon it first.',
|
|
45
|
-
'finish(plan_status="finished") auto-archives to .mocode/plans/archive/; use plan_status="abandoned" to abandon.',
|
|
46
|
-
'To revisit: list with scope=archived, or unarchive to bring back to active. delete permanently removes.',
|
|
47
|
-
].join('\n'),
|
|
48
|
-
parameters: {
|
|
49
|
-
type: 'object',
|
|
50
|
-
properties: {
|
|
51
|
-
action: {
|
|
52
|
-
type: 'string',
|
|
53
|
-
enum: ['create', 'read', 'update', 'batch_update', 'add_step', 'finish', 'list', 'delete', 'unarchive'],
|
|
54
|
-
description: 'create=新计划;read=读当前活跃;update=改步骤状态;batch_update=批量改多个步骤状态;add_step=追加步骤;finish=收尾(自动归档);list=列计划;delete=永久删除;unarchive=从归档还原到活跃',
|
|
55
|
-
},
|
|
56
|
-
title: { type: 'string', description: 'create 必填:计划标题' },
|
|
57
|
-
goal: { type: 'string', description: 'create 可选:目标描述(写进「目标」段)' },
|
|
58
|
-
steps: {
|
|
59
|
-
type: 'array',
|
|
60
|
-
items: { type: 'string' },
|
|
61
|
-
description: 'create 必填:步骤标题数组(从 1 起自动编号)',
|
|
62
|
-
},
|
|
63
|
-
step_id: {
|
|
64
|
-
type: 'number',
|
|
65
|
-
description: 'update 必填:步骤编号(1-based;create 后 read 返回的 id)',
|
|
66
|
-
},
|
|
67
|
-
status: {
|
|
68
|
-
type: 'string',
|
|
69
|
-
enum: ['pending', 'in_progress', 'done', 'skipped', 'failed'],
|
|
70
|
-
description: 'update 必填:目标状态',
|
|
71
|
-
},
|
|
72
|
-
updates: {
|
|
73
|
-
type: 'array',
|
|
74
|
-
items: {
|
|
75
|
-
type: 'object',
|
|
76
|
-
properties: {
|
|
77
|
-
step_id: { type: 'number', description: '步骤编号' },
|
|
78
|
-
status: { type: 'string', enum: ['pending', 'in_progress', 'done', 'skipped', 'failed'], description: '目标状态' },
|
|
79
|
-
},
|
|
80
|
-
required: ['step_id', 'status'],
|
|
81
|
-
},
|
|
82
|
-
description: 'batch_update 必填:批量更新数组,每项含 step_id 和 status',
|
|
83
|
-
},
|
|
84
|
-
note: {
|
|
85
|
-
type: 'string',
|
|
86
|
-
description: 'update / batch_update / finish 可选:追加到进度日志的一行说明(可空)',
|
|
87
|
-
},
|
|
88
|
-
plan_status: {
|
|
89
|
-
type: 'string',
|
|
90
|
-
enum: ['finished', 'abandoned'],
|
|
91
|
-
description: 'finish 必填:finished=完成;abandoned=放弃(中途取消)。finished 触发自动归档。',
|
|
92
|
-
},
|
|
93
|
-
scope: {
|
|
94
|
-
type: 'string',
|
|
95
|
-
enum: ['active', 'archived', 'all'],
|
|
96
|
-
description: 'list 可选:范围(active=默认,仅进行中;archived=仅归档;all=全)。',
|
|
97
|
-
},
|
|
98
|
-
id: {
|
|
99
|
-
type: 'string',
|
|
100
|
-
description: 'delete / unarchive 必填:目标 plan id(list 可拿到)。',
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
required: ['action'],
|
|
104
|
-
},
|
|
105
|
-
async execute(args) {
|
|
106
|
-
const action = String(args.action ?? '');
|
|
107
|
-
try {
|
|
108
|
-
switch (action) {
|
|
109
|
-
case 'create': return doCreate(args);
|
|
110
|
-
case 'read': return doRead();
|
|
111
|
-
case 'update': return doUpdate(args);
|
|
112
|
-
case 'batch_update': return doBatchUpdate(args);
|
|
113
|
-
case 'add_step': return doAddStep(args);
|
|
114
|
-
case 'finish': return doFinish(args);
|
|
115
|
-
case 'list': return doList(args);
|
|
116
|
-
case 'delete': return doDelete(args);
|
|
117
|
-
case 'unarchive': return doUnarchive(args);
|
|
118
|
-
default:
|
|
119
|
-
return `错误:未知 action「${action}」,合法值:create / read / update / batch_update / add_step / finish / list / delete / unarchive。`;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (e) {
|
|
123
|
-
// 兜底:工具内部不抛,但防御性 catch 一道(契约对齐「永不抛」)。
|
|
124
|
-
const why = e instanceof Error ? e.message : String(e);
|
|
125
|
-
return `错误:todolist 内部异常: ${why}`;
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
};
|
|
129
|
-
// ── actions ──
|
|
130
|
-
function doCreate(args) {
|
|
131
|
-
const title = String(args.title ?? '').trim();
|
|
132
|
-
if (!title)
|
|
133
|
-
return '错误:create 必填 title。';
|
|
134
|
-
const goal = String(args.goal ?? '').trim();
|
|
135
|
-
const rawSteps = Array.isArray(args.steps) ? args.steps : null;
|
|
136
|
-
if (!rawSteps || rawSteps.length === 0)
|
|
137
|
-
return '错误:create 必填 steps(非空字符串数组)。';
|
|
138
|
-
const steps = rawSteps
|
|
139
|
-
.map((s) => String(s ?? '').trim())
|
|
140
|
-
.filter((s) => s.length > 0);
|
|
141
|
-
if (steps.length === 0)
|
|
142
|
-
return '错误:create 的 steps 全是空字符串。';
|
|
143
|
-
if (hasActivePlan()) {
|
|
144
|
-
const cur = getActivePlan();
|
|
145
|
-
return `错误:已存在进行中的 plan「${cur?.title ?? ''}」(id=${cur?.id ?? ''}),需先 finish 后再建。`;
|
|
146
|
-
}
|
|
147
|
-
// 旧 plan 残留(finished/abandoned)→ 允许新建,不删历史(用户可后续 read 历史 plan)
|
|
148
|
-
const now = localIsoTimestamp();
|
|
149
|
-
const plan = {
|
|
150
|
-
id: newPlanId(),
|
|
151
|
-
title,
|
|
152
|
-
status: 'in_progress',
|
|
153
|
-
created: now,
|
|
154
|
-
updated: now,
|
|
155
|
-
goal,
|
|
156
|
-
steps: steps.map((t, i) => ({ id: i + 1, title: t, status: 'pending' })),
|
|
157
|
-
log: [{ at: now, text: `创建计划: ${steps.length} 步` }],
|
|
158
|
-
};
|
|
159
|
-
if (!writePlan(plan))
|
|
160
|
-
return '错误:写 plan 文件失败(检查 .mocode/plans 目录权限)。';
|
|
161
|
-
setActivePlan(plan);
|
|
162
|
-
return renderSuccess('create', plan);
|
|
163
|
-
}
|
|
164
|
-
function doRead() {
|
|
165
|
-
const cur = getActivePlan();
|
|
166
|
-
if (!cur) {
|
|
167
|
-
// 兜底:state 缓存空但文件可能存在(sandboxRoot 切了 / 进程重启)→ 列 plans 找一个最新的 in_progress
|
|
168
|
-
const fallback = findInProgressFromDisk();
|
|
169
|
-
if (fallback) {
|
|
170
|
-
setActivePlan(fallback);
|
|
171
|
-
return renderSuccess('read', fallback);
|
|
172
|
-
}
|
|
173
|
-
return '错误:无活跃 plan。先用 action=create 开一个。';
|
|
174
|
-
}
|
|
175
|
-
// 重新读盘以保最新(其他路径可能改了文件)
|
|
176
|
-
const fresh = readPlan(cur.id);
|
|
177
|
-
if (fresh)
|
|
178
|
-
setActivePlan(fresh);
|
|
179
|
-
return renderSuccess('read', fresh ?? cur);
|
|
180
|
-
}
|
|
181
|
-
function doUpdate(args) {
|
|
182
|
-
const cur = getActivePlan();
|
|
183
|
-
if (!cur)
|
|
184
|
-
return '错误:无活跃 plan 可 update。先 create。';
|
|
185
|
-
const stepId = Number(args.step_id);
|
|
186
|
-
if (!Number.isFinite(stepId) || stepId < 1) {
|
|
187
|
-
return `错误:update 必填 step_id(>=1 的整数),收到「${args.step_id}」。`;
|
|
188
|
-
}
|
|
189
|
-
const status = String(args.status ?? '');
|
|
190
|
-
if (!VALID_STATUS.has(status)) {
|
|
191
|
-
return `错误:update 的 status 非法「${status}」,合法:pending / in_progress / done / skipped / failed。`;
|
|
192
|
-
}
|
|
193
|
-
const note = String(args.note ?? '').trim();
|
|
194
|
-
const updated = updatePlan(cur.id, (p) => {
|
|
195
|
-
const step = p.steps.find((s) => s.id === stepId);
|
|
196
|
-
if (!step)
|
|
197
|
-
return false; // 无改动,工具返错
|
|
198
|
-
step.status = status;
|
|
199
|
-
const at = new Date().toISOString();
|
|
200
|
-
const logText = note || `step ${stepId} → ${status}`;
|
|
201
|
-
p.log.push({ at, text: logText });
|
|
202
|
-
return p;
|
|
203
|
-
});
|
|
204
|
-
if (!updated) {
|
|
205
|
-
return `错误:update 失败(找不到 step_id=${stepId},可能 plan 已不存在或 step 编号越界)。`;
|
|
206
|
-
}
|
|
207
|
-
// step 不存在(updatePlan 内 mutator 返 false → updatePlan 返原对象,需二次校验)
|
|
208
|
-
if (!updated.steps.some((s) => s.id === stepId)) {
|
|
209
|
-
return `错误:找不到 step_id=${stepId}(plan 共 ${updated.steps.length} 步)。`;
|
|
210
|
-
}
|
|
211
|
-
// 自动完成:所有步骤都 done/skipped 时自动 finish + 归档 + 清 chip(LLM 常漏调 finish)
|
|
212
|
-
const allDone = updated.steps.length > 0
|
|
213
|
-
&& updated.steps.every((s) => s.status === 'done' || s.status === 'skipped');
|
|
214
|
-
if (allDone && updated.status === 'in_progress') {
|
|
215
|
-
updated.status = 'finished';
|
|
216
|
-
updated.log.push({ at: localIsoTimestamp(), text: '自动完成:所有步骤已完成' });
|
|
217
|
-
if (!writePlan(updated)) {
|
|
218
|
-
setActivePlan(updated);
|
|
219
|
-
return renderSuccess('update', updated) + '\n⚠ 自动 finish 写盘失败';
|
|
220
|
-
}
|
|
221
|
-
const archived = archivePlan(updated.id);
|
|
222
|
-
clearActivePlan();
|
|
223
|
-
const archivedNote = archived
|
|
224
|
-
? '(已自动归档到 plans/archive/)'
|
|
225
|
-
: '(⚠ 归档失败,plan 仍留在 plans/)';
|
|
226
|
-
return renderSuccess('update', updated) + `\n✓ 自动完成:所有步骤已完成 ${archivedNote}`;
|
|
227
|
-
}
|
|
228
|
-
setActivePlan(updated);
|
|
229
|
-
return renderSuccess('update', updated);
|
|
230
|
-
}
|
|
231
|
-
function doBatchUpdate(args) {
|
|
232
|
-
const cur = getActivePlan();
|
|
233
|
-
if (!cur)
|
|
234
|
-
return '错误:无活跃 plan 可 batch_update。先 create。';
|
|
235
|
-
const updates = args.updates;
|
|
236
|
-
if (!Array.isArray(updates) || updates.length === 0) {
|
|
237
|
-
return '错误:batch_update 必填 updates(非空数组,每项含 step_id 和 status)。';
|
|
238
|
-
}
|
|
239
|
-
// 验证所有更新项
|
|
240
|
-
for (const u of updates) {
|
|
241
|
-
const sid = Number(u.step_id);
|
|
242
|
-
const st = String(u.status ?? '');
|
|
243
|
-
if (!Number.isFinite(sid) || sid < 1) {
|
|
244
|
-
return `错误:updates 中某项 step_id 非法「${u.step_id}」,需 >=1 整数。`;
|
|
245
|
-
}
|
|
246
|
-
if (!VALID_STATUS.has(st)) {
|
|
247
|
-
return `错误:updates 中某项 status 非法「${st}」,合法:pending / in_progress / done / skipped / failed。`;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
const note = String(args.note ?? '').trim();
|
|
251
|
-
const updated = updatePlan(cur.id, (p) => {
|
|
252
|
-
for (const u of updates) {
|
|
253
|
-
const sid = Number(u.step_id);
|
|
254
|
-
const st = String(u.status);
|
|
255
|
-
const step = p.steps.find((s) => s.id === sid);
|
|
256
|
-
if (step) {
|
|
257
|
-
step.status = st;
|
|
258
|
-
p.log.push({ at: new Date().toISOString(), text: `step ${sid} → ${st}` });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (note)
|
|
262
|
-
p.log.push({ at: new Date().toISOString(), text: note });
|
|
263
|
-
return p;
|
|
264
|
-
});
|
|
265
|
-
if (!updated)
|
|
266
|
-
return '错误:batch_update 写盘失败。';
|
|
267
|
-
// 检查所有 step_id 是否存在
|
|
268
|
-
const missingIds = [];
|
|
269
|
-
for (const u of updates) {
|
|
270
|
-
const sid = Number(u.step_id);
|
|
271
|
-
if (!updated.steps.some((s) => s.id === sid))
|
|
272
|
-
missingIds.push(sid);
|
|
273
|
-
}
|
|
274
|
-
if (missingIds.length > 0) {
|
|
275
|
-
return `错误:batch_update 找不到 step_id=${missingIds.join(', ')}(plan 共 ${updated.steps.length} 步)。`;
|
|
276
|
-
}
|
|
277
|
-
// 自动完成:所有步骤都 done/skipped 时自动 finish + 归档 + 清 chip(LLM 常漏调 finish)
|
|
278
|
-
const allDone = updated.steps.length > 0
|
|
279
|
-
&& updated.steps.every((s) => s.status === 'done' || s.status === 'skipped');
|
|
280
|
-
if (allDone && updated.status === 'in_progress') {
|
|
281
|
-
updated.status = 'finished';
|
|
282
|
-
updated.log.push({ at: localIsoTimestamp(), text: '自动完成:所有步骤已完成' });
|
|
283
|
-
if (!writePlan(updated)) {
|
|
284
|
-
setActivePlan(updated);
|
|
285
|
-
return renderSuccess('batch_update', updated) + '\n⚠ 自动 finish 写盘失败';
|
|
286
|
-
}
|
|
287
|
-
const archived = archivePlan(updated.id);
|
|
288
|
-
clearActivePlan();
|
|
289
|
-
const archivedNote = archived
|
|
290
|
-
? '(已自动归档到 plans/archive/)'
|
|
291
|
-
: '(⚠ 归档失败,plan 仍留在 plans/)';
|
|
292
|
-
return renderSuccess('batch_update', updated) + `\n✓ 自动完成:所有步骤已完成 ${archivedNote}`;
|
|
293
|
-
}
|
|
294
|
-
setActivePlan(updated);
|
|
295
|
-
return renderSuccess('batch_update', updated);
|
|
296
|
-
}
|
|
297
|
-
function doAddStep(args) {
|
|
298
|
-
const cur = getActivePlan();
|
|
299
|
-
if (!cur)
|
|
300
|
-
return '错误:无活跃 plan 可 add_step。先 create。';
|
|
301
|
-
const text = String(args.title ?? args.note ?? '').trim();
|
|
302
|
-
if (!text)
|
|
303
|
-
return '错误:add_step 必填 title(单条步骤标题)。';
|
|
304
|
-
const updated = updatePlan(cur.id, (p) => {
|
|
305
|
-
const nextId = p.steps.length > 0 ? Math.max(...p.steps.map((s) => s.id)) + 1 : 1;
|
|
306
|
-
p.steps.push({ id: nextId, title: text, status: 'pending' });
|
|
307
|
-
p.log.push({ at: new Date().toISOString(), text: `add step: ${text}` });
|
|
308
|
-
return p;
|
|
309
|
-
});
|
|
310
|
-
if (!updated)
|
|
311
|
-
return '错误:add_step 写盘失败。';
|
|
312
|
-
setActivePlan(updated);
|
|
313
|
-
return renderSuccess('add_step', updated);
|
|
314
|
-
}
|
|
315
|
-
function doFinish(args) {
|
|
316
|
-
const cur = getActivePlan();
|
|
317
|
-
if (!cur)
|
|
318
|
-
return '错误:无活跃 plan 可 finish。';
|
|
319
|
-
const ps = String(args.plan_status ?? 'finished');
|
|
320
|
-
if (!VALID_PLAN_STATUS.has(ps)) {
|
|
321
|
-
return `错误:finish 的 plan_status 非法「${ps}」,合法:finished / abandoned。`;
|
|
322
|
-
}
|
|
323
|
-
const note = String(args.note ?? '').trim();
|
|
324
|
-
const updated = updatePlan(cur.id, (p) => {
|
|
325
|
-
p.status = ps;
|
|
326
|
-
p.log.push({
|
|
327
|
-
at: localIsoTimestamp(),
|
|
328
|
-
text: note || (ps === 'finished' ? '完成' : '放弃'),
|
|
329
|
-
});
|
|
330
|
-
return p;
|
|
331
|
-
});
|
|
332
|
-
if (!updated)
|
|
333
|
-
return '错误:finish 写盘失败。';
|
|
334
|
-
setActivePlan(updated);
|
|
335
|
-
// finished → 自动归档到 plans/archive/(历史完整保留,活跃目录保持干净)。
|
|
336
|
-
// abandoned → 不归档(用户想丢就丢,但仍在 plans/ 下,可显式 delete 删掉)。
|
|
337
|
-
let archived = false;
|
|
338
|
-
if (ps === 'finished') {
|
|
339
|
-
archived = archivePlan(updated.id);
|
|
340
|
-
}
|
|
341
|
-
// finish 后清活跃缓存(下轮 read 自动从 in_progress 列表兜底,本会话不再「活跃」)
|
|
342
|
-
clearActivePlan();
|
|
343
|
-
const archivedNote = ps === 'finished'
|
|
344
|
-
? (archived ? '(已自动归档到 plans/archive/)' : '(⚠ 归档失败,plan 仍留在 plans/,可手动 list 排查)')
|
|
345
|
-
: '';
|
|
346
|
-
return `${renderSuccess('finish', updated)}\n${archivedNote}`.trimEnd();
|
|
347
|
-
}
|
|
348
|
-
function doList(args) {
|
|
349
|
-
const scopeRaw = String(args.scope ?? 'active');
|
|
350
|
-
const scope = scopeRaw === 'archived' || scopeRaw === 'all' ? scopeRaw : 'active';
|
|
351
|
-
const plans = listPlans(scope);
|
|
352
|
-
if (plans.length === 0) {
|
|
353
|
-
return `list: 无 ${scope === 'active' ? '进行中' : scope === 'archived' ? '已归档' : ''}plan。`;
|
|
354
|
-
}
|
|
355
|
-
// 一行一条:id | status | 标题 | 进度
|
|
356
|
-
const lines = [`list (${scope},${plans.length} 条):`];
|
|
357
|
-
for (const p of plans) {
|
|
358
|
-
const done = p.steps.filter((s) => s.status === 'done' || s.status === 'skipped').length;
|
|
359
|
-
const status = p.status === 'in_progress' ? 'live' : p.status === 'finished' ? 'done' : 'gone';
|
|
360
|
-
lines.push(` ${p.id} [${status}] ${p.title} (${done}/${p.steps.length})`);
|
|
361
|
-
}
|
|
362
|
-
return lines.join('\n');
|
|
363
|
-
}
|
|
364
|
-
function doDelete(args) {
|
|
365
|
-
const id = String(args.id ?? '').trim();
|
|
366
|
-
if (!id)
|
|
367
|
-
return '错误:delete 必填 id(从 list 拿)。';
|
|
368
|
-
// 安全护栏:active 状态下不能直接 delete 当前活跃(避免误删正在用的 plan)
|
|
369
|
-
const cur = getActivePlan();
|
|
370
|
-
if (cur && cur.id === id) {
|
|
371
|
-
return `错误:不能 delete 当前活跃 plan「${cur.title}」,需先 finish(自动归档)再 delete 归档副本。`;
|
|
372
|
-
}
|
|
373
|
-
const ok = deletePlanAnywhere(id);
|
|
374
|
-
if (!ok)
|
|
375
|
-
return `错误:delete 失败,id「${id}」在 plans/ 和 plans/archive/ 都找不到。`;
|
|
376
|
-
return `delete ✓: 已永久删除「${id}」(.mocode/plans/ 与 archive/ 都不再存在)。`;
|
|
377
|
-
}
|
|
378
|
-
function doUnarchive(args) {
|
|
379
|
-
const id = String(args.id ?? '').trim();
|
|
380
|
-
if (!id)
|
|
381
|
-
return '错误:unarchive 必填 id(从 list scope=archived 拿)。';
|
|
382
|
-
if (!unarchivePlan(id)) {
|
|
383
|
-
return `错误:unarchive 失败,id「${id}」在 plans/archive/ 找不到(可能已还原,或被 delete)。`;
|
|
384
|
-
}
|
|
385
|
-
// 还原后读一次拿回 plan 对象(若 status=in_progress 还可顺手恢复为活跃)
|
|
386
|
-
const fresh = readPlan(id);
|
|
387
|
-
if (!fresh)
|
|
388
|
-
return `unarchive ✓: 「${id}」已还原到 plans/(读取失败,但文件在)。`;
|
|
389
|
-
if (fresh.status === 'in_progress') {
|
|
390
|
-
setActivePlan(fresh);
|
|
391
|
-
return `${renderSuccess('unarchive', fresh)}\n(已自动设为活跃)。`.trimEnd();
|
|
392
|
-
}
|
|
393
|
-
return `${renderSuccess('unarchive', fresh)}\n(注:该 plan 状态为 ${fresh.status},未自动激活 —— 显式 create 才激活)`;
|
|
394
|
-
}
|
|
395
|
-
// ── helpers ──
|
|
396
|
-
/** 从盘上找一个 in_progress plan(进程级 state 丢失/首次访问时兜底)。无 → null。 */
|
|
397
|
-
function findInProgressFromDisk() {
|
|
398
|
-
const all = listPlans();
|
|
399
|
-
return all.find((p) => p.status === 'in_progress') ?? null;
|
|
400
|
-
}
|
|
401
|
-
/** 工具结果统一格式:`<action>: <一行摘要>\n\n<完整 plan 渲染>`。
|
|
402
|
-
* 超 MAX_OUTPUT 截 plan 渲染尾部(罕见;plan 文件本身就小)。 */
|
|
403
|
-
function renderSuccess(action, p) {
|
|
404
|
-
const done = p.steps.filter((s) => s.status === 'done' || s.status === 'skipped').length;
|
|
405
|
-
const head = `${action} ✓: 「${p.title}」 进度 ${done}/${p.steps.length} (status=${p.status})`;
|
|
406
|
-
const body = renderPlanForLLM(p);
|
|
407
|
-
const full = `${head}\n\n${body}`;
|
|
408
|
-
if (full.length <= MAX_OUTPUT)
|
|
409
|
-
return full;
|
|
410
|
-
return full.slice(0, MAX_OUTPUT) + `\n\n…(plan 渲染已截断 ${full.length - MAX_OUTPUT} 字符)`;
|
|
411
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export declare const MAX_FILE_LINES = 2000;
|
|
2
|
-
export declare const MAX_OUTPUT = 20000;
|
|
3
|
-
export declare const MAX_RESULTS = 100;
|
|
4
|
-
/** 进 history 的单条工具结果上限(字符)。push-time 第一层裁剪,保 head + 标记 + tail。 */
|
|
5
|
-
export declare const MAX_HISTORY_RESULT = 8000;
|
|
6
|
-
/** use_skill 结果(SKILL.md 正文)的放宽上限:指令须完整,中截会破坏语义。 */
|
|
7
|
-
export declare const MAX_SKILL_RESULT = 64000;
|
|
8
|
-
/** 微压缩时旧工具结果截到的存根长度(字符)。 */
|
|
9
|
-
export declare const MAX_OLD_TOOL_STUB = 600;
|
|
10
|
-
/** 单条记忆 body 上限(字符)。进 history 前 memory_search 结果另有 MAX_MEMORY_RESULT 兜底。 */
|
|
11
|
-
export declare const MAX_MEMORY_ENTRY = 4000;
|
|
12
|
-
/** 启动注入 systemPrompt 的索引条目上限(遗忘封顶后 active 条目 ≤ MAX_ACTIVE,索引再封一层)。 */
|
|
13
|
-
export declare const MAX_INDEX_ENTRIES = 50;
|
|
14
|
-
/** active 记忆封顶:超则按 recallCount 低 × lastRecalledAt 老 淘汰到 archived。 */
|
|
15
|
-
export declare const MAX_ACTIVE = 100;
|
|
16
|
-
/** 衰减:active + !pinned + (lastRecalledAt|createdAt) 早于 DECAY_DAYS → archived。 */
|
|
17
|
-
export declare const DECAY_DAYS = 30;
|
|
18
|
-
/** GC:archived 超 GC_DAYS → 硬删。 */
|
|
19
|
-
export declare const GC_DAYS = 90;
|
|
20
|
-
/** memory_search 结果(召回的记忆正文)的放宽上限:指令性内容,中截破坏语义,对齐 use_skill。 */
|
|
21
|
-
export declare const MAX_MEMORY_RESULT = 64000;
|
|
22
|
-
export declare const IGNORE: string[];
|
|
23
|
-
/** Hot/Cold 划分:当前 step 起往前 N 个 user turn 之内的工具结果视为 Hot(绝对不压),
|
|
24
|
-
* 之外的视为 Cold(可调度器压)。默认 4 = 跨过 4 个用户问题仍生效。 */
|
|
25
|
-
export declare const HOT_TURN_WINDOW = 4;
|
|
26
|
-
/** Cold 区内可被就地 stub 的 tool 消息最低 age(经过的消费者 push 数)。默认 2,
|
|
27
|
-
* 与 lifecycle.ts DEFAULT_AGE_THRESHOLD 对齐。 */
|
|
28
|
-
export declare const TOOL_OLD_AGE = 2;
|
|
29
|
-
/** 五区预算占比(总和 0.95,留 5% 给 Reserve)。对齐 user 修正版:
|
|
30
|
-
* System 15 / History 20 / Hot Tool 25 / Cold Tool 25 / Summary 10。 */
|
|
31
|
-
export declare const BUDGET_RATIO: {
|
|
32
|
-
readonly system: 0.15;
|
|
33
|
-
readonly history: 0.2;
|
|
34
|
-
readonly toolRecent: 0.25;
|
|
35
|
-
readonly toolOld: 0.25;
|
|
36
|
-
readonly summary: 0.1;
|
|
37
|
-
readonly reserve: 0.05;
|
|
38
|
-
};
|
|
39
|
-
/**
|
|
40
|
-
* plan 模式下从工具 schema 里剔除的工具(模型根本看不到 → 调不到):
|
|
41
|
-
* 写盘 / 命令 / 记忆写入类 + task(派生子 agent,plan 模式只读不可有副作用)。单一事实源,
|
|
42
|
-
* 被 llm(planChatTools)与 agent(防御 backstop)共用。
|
|
43
|
-
* 只读工具(read_file/glob/grep/codegraph/web_search/web_fetch/use_skill/ask_human/memory_search/memory_list)保留。
|
|
44
|
-
*/
|
|
45
|
-
export declare const PLAN_DISABLED_TOOLS: Set<string>;
|
|
46
|
-
/**
|
|
47
|
-
* 按当前 isMemoryEnabled() 现算 plan 模式应屏蔽的工具。
|
|
48
|
-
* memoryEnabled=false 时记忆工具整体不在 builtinTools 里,plan 屏蔽集里也无须再列 ——
|
|
49
|
-
* 反之留着只是死名字。统一过滤,避免 Set 里残留与已下架工具不一致的概念性冗余。
|
|
50
|
-
* 调用方(agent/core 串行分支、llm/planChatTools)每次 chat 时调本函数拿当前值。
|
|
51
|
-
*/
|
|
52
|
-
export declare function getPlanDisabledTools(): Set<string>;
|
package/dist/tools/registry.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { Tool, DropContextFilter, DropContextResult } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* 工具注册表。当前 = 内置工具;
|
|
4
|
-
* 未来可在此合并 MCP 工具、用户自定义工具等(见 src/mcp/)。
|
|
5
|
-
*/
|
|
6
|
-
export declare const tools: Tool[];
|
|
7
|
-
/**
|
|
8
|
-
* 按名调度工具,统一 try/catch + JSON 解析,返回字符串而非抛错。
|
|
9
|
-
* signal 透传给 tool.execute(经 ctx):长任务工具(run_command/web_fetch)abort 即时取消,
|
|
10
|
-
* 让用户 Ctrl+C 能跟手中断工具执行(而非等命令跑完 / 超时)。
|
|
11
|
-
* opts.skipRollback:子 agent 逻辑隔离用——跳过 recordMutation,子 agent 改动不进主回滚快照链。
|
|
12
|
-
* opts.dropContext:上下文剔除回调(drop_context 工具用),透传给 tool.execute 经 ctx。
|
|
13
|
-
*/
|
|
14
|
-
export declare function executeTool(name: string, argsRaw: string, signal?: AbortSignal, opts?: {
|
|
15
|
-
skipRollback?: boolean;
|
|
16
|
-
dropContext?: (filter: DropContextFilter) => DropContextResult;
|
|
17
|
-
}): Promise<string>;
|
|
18
|
-
export type { Tool, ToolContext, DropContextFilter, DropContextResult } from './types.js';
|
package/dist/tools/retry.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { reflectOn } from '../agent/retry-classifier.js';
|
|
2
|
-
export const TOOL_RETRY_MAX_ATTEMPTS = 3;
|
|
3
|
-
export const TOOL_RETRY_BASE_MS = 250;
|
|
4
|
-
export const TOOL_RETRY_MAX_MS = 1_000;
|
|
5
|
-
export const TOOL_RETRY_TOTAL_BUDGET_MS = 15_000;
|
|
6
|
-
export const TOOL_RETRY_SAME_ARGS_WINDOW_MS = 60_000;
|
|
7
|
-
export const TOOL_RETRY_SAME_ARGS_BUDGET = 2;
|
|
8
|
-
const NEVER_RETRY_CODES = new Set([
|
|
9
|
-
'INVALID_JSON',
|
|
10
|
-
'INVALID_ARGUMENTS',
|
|
11
|
-
'INVALID_TOOL_SCHEMA',
|
|
12
|
-
'UNKNOWN_TOOL',
|
|
13
|
-
'SANDBOX_DENIED',
|
|
14
|
-
'PERMISSION_DENIED',
|
|
15
|
-
'TOOL_DISABLED',
|
|
16
|
-
'MODE_DENIED',
|
|
17
|
-
'ABORTED',
|
|
18
|
-
'EDIT_CONFLICT',
|
|
19
|
-
'POSTCONDITION_FAILED',
|
|
20
|
-
'PROCESS_FAILED',
|
|
21
|
-
'MCP_ERROR',
|
|
22
|
-
]);
|
|
23
|
-
const fingerprintBudgets = new Map();
|
|
24
|
-
const MAX_TRACKED_FINGERPRINTS = 512;
|
|
25
|
-
function reserveFingerprintRetry(fingerprint, now) {
|
|
26
|
-
let budget = fingerprintBudgets.get(fingerprint);
|
|
27
|
-
if (!budget || now - budget.startedAt >= TOOL_RETRY_SAME_ARGS_WINDOW_MS) {
|
|
28
|
-
budget = { startedAt: now, retries: 0 };
|
|
29
|
-
fingerprintBudgets.set(fingerprint, budget);
|
|
30
|
-
}
|
|
31
|
-
if (budget.retries >= TOOL_RETRY_SAME_ARGS_BUDGET)
|
|
32
|
-
return false;
|
|
33
|
-
budget.retries++;
|
|
34
|
-
if (fingerprintBudgets.size > MAX_TRACKED_FINGERPRINTS) {
|
|
35
|
-
const oldest = fingerprintBudgets.keys().next().value;
|
|
36
|
-
if (oldest)
|
|
37
|
-
fingerprintBudgets.delete(oldest);
|
|
38
|
-
}
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
function shouldRetry(outcome, capabilities) {
|
|
42
|
-
return capabilities.retry !== 'never' &&
|
|
43
|
-
outcome.status === 'error' &&
|
|
44
|
-
outcome.retryable === true &&
|
|
45
|
-
!NEVER_RETRY_CODES.has(outcome.code);
|
|
46
|
-
}
|
|
47
|
-
function backoff(attempt) {
|
|
48
|
-
return Math.min(TOOL_RETRY_MAX_MS, TOOL_RETRY_BASE_MS * 2 ** (attempt - 1));
|
|
49
|
-
}
|
|
50
|
-
function abortError() {
|
|
51
|
-
const error = new Error('Tool retry aborted');
|
|
52
|
-
error.name = 'AbortError';
|
|
53
|
-
return error;
|
|
54
|
-
}
|
|
55
|
-
function sleep(ms, signal) {
|
|
56
|
-
if (signal?.aborted)
|
|
57
|
-
return Promise.reject(abortError());
|
|
58
|
-
return new Promise((resolve, reject) => {
|
|
59
|
-
const timer = setTimeout(() => {
|
|
60
|
-
signal?.removeEventListener('abort', onAbort);
|
|
61
|
-
resolve();
|
|
62
|
-
}, ms);
|
|
63
|
-
const onAbort = () => {
|
|
64
|
-
clearTimeout(timer);
|
|
65
|
-
signal?.removeEventListener('abort', onAbort);
|
|
66
|
-
reject(abortError());
|
|
67
|
-
};
|
|
68
|
-
signal?.addEventListener('abort', onAbort, { once: true });
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
/** Retry safe/idempotent transient outcomes; each execute call owns one complete lock attempt. */
|
|
72
|
-
export async function executeWithToolRetry(capabilities, fingerprint, signal, execute, onRetry, onFailedAttempt) {
|
|
73
|
-
const startedAt = Date.now();
|
|
74
|
-
let retryDelayMs = 0;
|
|
75
|
-
let lastOutcome = null;
|
|
76
|
-
for (let attempt = 1; attempt <= TOOL_RETRY_MAX_ATTEMPTS; attempt++) {
|
|
77
|
-
const outcome = await execute(attempt);
|
|
78
|
-
lastOutcome = outcome;
|
|
79
|
-
const elapsed = Date.now() - startedAt;
|
|
80
|
-
const waitMs = backoff(attempt);
|
|
81
|
-
const canRetry = attempt < TOOL_RETRY_MAX_ATTEMPTS &&
|
|
82
|
-
shouldRetry(outcome, capabilities) &&
|
|
83
|
-
elapsed + waitMs <= TOOL_RETRY_TOTAL_BUDGET_MS &&
|
|
84
|
-
!signal?.aborted &&
|
|
85
|
-
reserveFingerprintRetry(fingerprint, Date.now());
|
|
86
|
-
if (!canRetry) {
|
|
87
|
-
// RETRY-02 接缝:所有 attempt 用尽时,如果调用方启用了 onFailedAttempt,
|
|
88
|
-
// 通知一次(用于 history 裁剪 / 等)。只在**真**失败(not success)时通知,
|
|
89
|
-
// success 路径不需要裁剪任何东西。
|
|
90
|
-
if (onFailedAttempt && outcome.status !== 'success') {
|
|
91
|
-
try {
|
|
92
|
-
const reflection = reflectOn(outcome.code);
|
|
93
|
-
onFailedAttempt({
|
|
94
|
-
fingerprint,
|
|
95
|
-
status: outcome.status,
|
|
96
|
-
code: outcome.code,
|
|
97
|
-
category: reflection.category,
|
|
98
|
-
attempts: attempt,
|
|
99
|
-
reflectionHint: reflection.hint,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// 钩子失败不影响主流程。
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
...outcome,
|
|
108
|
-
durationMs: elapsed,
|
|
109
|
-
attempts: attempt,
|
|
110
|
-
retryDelayMs,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
const reflection = reflectOn(outcome.code);
|
|
115
|
-
onRetry?.({
|
|
116
|
-
attempt,
|
|
117
|
-
nextAttempt: attempt + 1,
|
|
118
|
-
waitMs,
|
|
119
|
-
code: outcome.code,
|
|
120
|
-
category: reflection.category,
|
|
121
|
-
reflectionHint: reflection.hint,
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
catch {
|
|
125
|
-
// Retry telemetry is best-effort and must never change tool execution.
|
|
126
|
-
}
|
|
127
|
-
await sleep(waitMs, signal);
|
|
128
|
-
retryDelayMs += waitMs;
|
|
129
|
-
}
|
|
130
|
-
// 不可达路径:循环要么 return,要么最后一次 attempt 也 shouldRetry=false(走 return)。
|
|
131
|
-
// 留 throw 是为了编译期穷尽性检查(TypeScript 期望函数末尾 return)。
|
|
132
|
-
throw new Error('unreachable tool retry state');
|
|
133
|
-
}
|
|
134
|
-
/** Test/session reset seam; production never needs to clear the bounded TTL map. */
|
|
135
|
-
export function resetToolRetryBudgets() {
|
|
136
|
-
fingerprintBudgets.clear();
|
|
137
|
-
}
|