mocode-ai 1.2.7 → 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.
Files changed (129) hide show
  1. package/dist/agent/core.js +126 -43
  2. package/dist/config/index.js +31 -6
  3. package/dist/config/presets.js +36 -1
  4. package/dist/context/budget.js +5 -16
  5. package/dist/llm/index.js +42 -4
  6. package/dist/repl/index.js +57 -13
  7. package/dist/session/compact.js +329 -40
  8. package/dist/session/notes.js +63 -0
  9. package/dist/session/scheduler.js +5 -9
  10. package/dist/tools/constants.js +21 -0
  11. package/package.json +1 -1
  12. package/dist/__trace_manual_test__.js +0 -1
  13. package/dist/agent/core.d.ts +0 -112
  14. package/dist/agent/index.d.ts +0 -19
  15. package/dist/agent/mode.d.ts +0 -29
  16. package/dist/agent/retry-classifier.js +0 -64
  17. package/dist/agent/spawn.d.ts +0 -36
  18. package/dist/attachments/image.d.ts +0 -22
  19. package/dist/commands/config.d.ts +0 -8
  20. package/dist/commands/skill.js +0 -230
  21. package/dist/config/file.d.ts +0 -23
  22. package/dist/config/index.d.ts +0 -114
  23. package/dist/config/presets.d.ts +0 -57
  24. package/dist/context/budget.d.ts +0 -79
  25. package/dist/context/classifier.d.ts +0 -14
  26. package/dist/context/encoders/_util.d.ts +0 -11
  27. package/dist/context/encoders/code.d.ts +0 -2
  28. package/dist/context/encoders/doc.d.ts +0 -14
  29. package/dist/context/encoders/graph.d.ts +0 -11
  30. package/dist/context/encoders/graph.js +0 -81
  31. package/dist/context/encoders/index.d.ts +0 -2
  32. package/dist/context/encoders/log.d.ts +0 -2
  33. package/dist/context/encoders/log.js +0 -2
  34. package/dist/context/encoders/memory.d.ts +0 -2
  35. package/dist/context/encoders/passthrough.d.ts +0 -11
  36. package/dist/context/encoders/search.d.ts +0 -2
  37. package/dist/context/encoders/summary.d.ts +0 -12
  38. package/dist/context/encoders/table.d.ts +0 -2
  39. package/dist/context/encoders/tree.d.ts +0 -2
  40. package/dist/context/index.d.ts +0 -6
  41. package/dist/context/lifecycle.d.ts +0 -51
  42. package/dist/context/pipeline.d.ts +0 -9
  43. package/dist/context/registry.d.ts +0 -9
  44. package/dist/context/relevance.d.ts +0 -65
  45. package/dist/context/types.d.ts +0 -36
  46. package/dist/foo.js +0 -28
  47. package/dist/foo.test.js +0 -12
  48. package/dist/index.d.ts +0 -1
  49. package/dist/llm/capabilities.d.ts +0 -19
  50. package/dist/llm/index.d.ts +0 -111
  51. package/dist/memory/discover.d.ts +0 -15
  52. package/dist/memory/index.d.ts +0 -13
  53. package/dist/memory/reflect.d.ts +0 -34
  54. package/dist/memory/store.d.ts +0 -124
  55. package/dist/pet/bridge.d.ts +0 -73
  56. package/dist/pet/protocol.d.ts +0 -83
  57. package/dist/pet/state.d.ts +0 -28
  58. package/dist/plan/active.d.ts +0 -23
  59. package/dist/plan/active.js +0 -45
  60. package/dist/plan/index.d.ts +0 -13
  61. package/dist/plan/index.js +0 -33
  62. package/dist/plan/state.d.ts +0 -2
  63. package/dist/plan/state.js +0 -11
  64. package/dist/plan/store.d.ts +0 -78
  65. package/dist/plan/store.js +0 -429
  66. package/dist/project-skill/index.js +0 -95
  67. package/dist/project-skill/initializer.js +0 -178
  68. package/dist/project-snapshot/index.js +0 -85
  69. package/dist/project-snapshot/llm-snapshot.js +0 -150
  70. package/dist/project-snapshot/static-files.js +0 -87
  71. package/dist/repl/index.d.ts +0 -22
  72. package/dist/rollback/index.d.ts +0 -79
  73. package/dist/sandbox/command.d.ts +0 -3
  74. package/dist/sandbox/index.d.ts +0 -4
  75. package/dist/sandbox/jail.d.ts +0 -17
  76. package/dist/sandbox/policy.d.ts +0 -21
  77. package/dist/sandbox/root.d.ts +0 -5
  78. package/dist/session/compact.d.ts +0 -92
  79. package/dist/session/index.d.ts +0 -13
  80. package/dist/session/notes-plan.js +0 -41
  81. package/dist/session/persist.d.ts +0 -30
  82. package/dist/session/scheduler.d.ts +0 -53
  83. package/dist/skills/discover.d.ts +0 -33
  84. package/dist/skills/index.d.ts +0 -13
  85. package/dist/skills/skill-eval.js +0 -345
  86. package/dist/skills/skill-improve.js +0 -221
  87. package/dist/skills/stats.js +0 -102
  88. package/dist/tools/builtins/apply-patch.js +0 -174
  89. package/dist/tools/builtins/ask-human.d.ts +0 -9
  90. package/dist/tools/builtins/codegraph.d.ts +0 -2
  91. package/dist/tools/builtins/codegraph.js +0 -128
  92. package/dist/tools/builtins/edit-file.d.ts +0 -2
  93. package/dist/tools/builtins/glob.d.ts +0 -2
  94. package/dist/tools/builtins/grep.d.ts +0 -2
  95. package/dist/tools/builtins/index.d.ts +0 -2
  96. package/dist/tools/builtins/memory-forget.d.ts +0 -2
  97. package/dist/tools/builtins/memory-list.d.ts +0 -2
  98. package/dist/tools/builtins/memory-save.d.ts +0 -2
  99. package/dist/tools/builtins/memory-search.d.ts +0 -2
  100. package/dist/tools/builtins/memory-update.d.ts +0 -2
  101. package/dist/tools/builtins/project-skill-update.js +0 -102
  102. package/dist/tools/builtins/read-file.d.ts +0 -2
  103. package/dist/tools/builtins/run-command.d.ts +0 -2
  104. package/dist/tools/builtins/switch-mode.d.ts +0 -2
  105. package/dist/tools/builtins/switch-mode.js +0 -44
  106. package/dist/tools/builtins/task.d.ts +0 -2
  107. package/dist/tools/builtins/todolist.d.ts +0 -2
  108. package/dist/tools/builtins/todolist.js +0 -411
  109. package/dist/tools/builtins/use-skill.d.ts +0 -2
  110. package/dist/tools/builtins/web-fetch.d.ts +0 -2
  111. package/dist/tools/builtins/web-search.d.ts +0 -2
  112. package/dist/tools/builtins/write-file.d.ts +0 -2
  113. package/dist/tools/constants.d.ts +0 -52
  114. package/dist/tools/registry.d.ts +0 -18
  115. package/dist/tools/retry.js +0 -137
  116. package/dist/tools/types.d.ts +0 -42
  117. package/dist/ui/batch.d.ts +0 -67
  118. package/dist/ui/clipboard.d.ts +0 -9
  119. package/dist/ui/content.d.ts +0 -55
  120. package/dist/ui/diff.d.ts +0 -34
  121. package/dist/ui/intervention.d.ts +0 -46
  122. package/dist/ui/layout.d.ts +0 -225
  123. package/dist/ui/markdown.d.ts +0 -46
  124. package/dist/ui/mouse.d.ts +0 -29
  125. package/dist/ui/prompt.d.ts +0 -61
  126. package/dist/ui/render.d.ts +0 -60
  127. package/dist/ui/spinner.d.ts +0 -26
  128. package/dist/ui/theme.d.ts +0 -47
  129. package/dist/ui/thinking.js +0 -80
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const memoryUpdateTool: Tool;
@@ -1,102 +0,0 @@
1
- import { readProjectSkill, writeProjectSkillWithCompression, appendProjectSkillWithCompression, } from '../../project-skill/index.js';
2
- /**
3
- * 项目专属 Skill 更新工具。
4
- * 支持三种操作:
5
- * - read: 读取当前 skill 内容
6
- * - write: 全量覆盖(适合重构整个 skill)
7
- * - append: 追加到末尾(适合发现新坑点/约定时增量更新)
8
- *
9
- * 使用场景:
10
- * - 开发过程中发现项目特有的架构模式、命名约定
11
- * - 踩坑后记录避坑指南
12
- * - 总结项目关键决策和设计理由
13
- * - 随着项目开发,skill 越来越了解项目,开发效率越来越高
14
- */
15
- export const projectSkillUpdateTool = {
16
- name: 'project_skill_update',
17
- description: 'Update the project-specific skill file (.mocode/project-skill.md). ' +
18
- 'Use this to record architectural patterns, naming conventions, common pitfalls, ' +
19
- 'and key decisions specific to this project. The skill persists across sessions and ' +
20
- 'is injected into the system prompt when MOCODE_PROJECT_SKILL=true. ' +
21
- 'Actions: "read" (view current content), "write" (replace all), "append" (add to end). ' +
22
- '\n\n' +
23
- '## Skill 维护指南\n' +
24
- 'Skill 专注 Snapshot 无法自动提供的洞察知识(why/how/gotchas)。Snapshot 已提供文件结构和静态文件内容——Skill 不要重复这些信息。\n' +
25
- '\n' +
26
- '**Skill 该写什么(Snapshot 不能替代的)**:\n' +
27
- '- 设计决策的 why(为什么这样设计、取舍是什么)\n' +
28
- '- 模块行为和职责描述(怎么工作、数据流、调用链)\n' +
29
- '- 常见坑点和解决方案(踩过的坑、非直觉行为、边界条件)\n' +
30
- '- 项目约定(命名规范、代码风格、测试策略)\n' +
31
- '- 开发流程(构建/测试/部署命令及注意事项)\n' +
32
- '- 关键 API 的使用限制和特殊行为\n' +
33
- '\n' +
34
- '**Skill 不该写什么(交给 Snapshot)**:\n' +
35
- '- 文件列表和目录结构(Snapshot 自动扫描 src 树)\n' +
36
- '- 静态文件内容摘要(依赖、编译器选项等 → package.json / tsconfig.json)\n' +
37
- '\n' +
38
- '**何时更新**:发现新架构模式、踩坑后总结、学到新约定、完成重要重构、用户纠正理解时。\n' +
39
- '\n' +
40
- '**注意事项**:\n' +
41
- '- 内容长度不设硬上限,按项目实际复杂度完整记录有价值的信息\n' +
42
- '- 写可操作的内容,避免空泛描述\n' +
43
- '- 用具体路径和例子(不要写"有多个模块",要写"src/agent 负责 agent 循环")\n' +
44
- '- 定期整理,删除过时和重复信息\n' +
45
- '- 更新前建议先 `read` 看一下现有内容,避免重复。',
46
- risk: 'safe',
47
- parameters: {
48
- type: 'object',
49
- properties: {
50
- action: {
51
- type: 'string',
52
- enum: ['read', 'write', 'append'],
53
- description: 'Action to perform: "read" to view current content, "write" to replace all, "append" to add to end',
54
- },
55
- content: {
56
- type: 'string',
57
- description: 'Content to write or append (required for "write" and "append" actions, ignored for "read")',
58
- },
59
- },
60
- required: ['action'],
61
- },
62
- execute: async (args, ctx) => {
63
- const action = String(args.action ?? '').trim();
64
- const content = String(args.content ?? '');
65
- const signal = ctx?.signal;
66
- switch (action) {
67
- case 'read': {
68
- const current = readProjectSkill();
69
- if (!current) {
70
- return 'Project skill is empty or does not exist yet.';
71
- }
72
- return `Current project skill:\n\n${current}`;
73
- }
74
- case 'write': {
75
- if (!content.trim()) {
76
- return 'Error: "write" action requires non-empty content parameter.';
77
- }
78
- const result = await writeProjectSkillWithCompression(content, 3, signal);
79
- if (!result.ok) {
80
- return `Error: ${result.error}`;
81
- }
82
- return result.compressed
83
- ? 'Project skill updated successfully (full replacement, auto-compressed to fit limit).'
84
- : 'Project skill updated successfully (full replacement).';
85
- }
86
- case 'append': {
87
- if (!content.trim()) {
88
- return 'Error: "append" action requires non-empty content parameter.';
89
- }
90
- const result = await appendProjectSkillWithCompression(content, 3, signal);
91
- if (!result.ok) {
92
- return `Error: ${result.error}`;
93
- }
94
- return result.compressed
95
- ? 'Project skill updated successfully (appended, auto-compressed to fit limit).'
96
- : 'Project skill updated successfully (appended).';
97
- }
98
- default:
99
- return `Error: Unknown action "${action}". Use "read", "write", or "append".`;
100
- }
101
- },
102
- };
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const readFileTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const runCommandTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const switchModeTool: Tool;
@@ -1,44 +0,0 @@
1
- import { setAgentMode, getAgentMode } from '../../agent/mode.js';
2
- // ---------- switch_mode ----------
3
- export const switchModeTool = {
4
- name: 'switch_mode',
5
- description: [
6
- 'Switch agent mode between "plan" (read-only investigation) and "auto" (full tool execution).',
7
- 'Use to transition from planning to execution WITHIN THE SAME TURN: in plan, after presenting a plan, call switch_mode("auto") and continue — ONLY when the user asked for autonomous execution ("先 plan 再 auto" etc.).',
8
- 'If the user entered plan mode manually (/plan or Shift+Tab) for review, do NOT switch to auto — present the plan and STOP for approval.',
9
- ].join(' '),
10
- parameters: {
11
- type: 'object',
12
- properties: {
13
- mode: {
14
- type: 'string',
15
- enum: ['auto', 'plan'],
16
- description: 'Target mode: "plan" (read-only) or "auto" (full tools)',
17
- },
18
- },
19
- required: ['mode'],
20
- },
21
- async execute(args) {
22
- const target = String(args.mode);
23
- if (target !== 'auto' && target !== 'plan') {
24
- return `错误:mode 必须是 "auto" 或 "plan",收到 "${target}"`;
25
- }
26
- const prev = getAgentMode();
27
- if (prev === target) {
28
- return `已在 ${target} 模式,无需切换。`;
29
- }
30
- setAgentMode(target); // 触发 listener:repl 重写 history[0] 系统提示 + 刷状态行 modeTag
31
- if (target === 'auto') {
32
- return [
33
- `已从 plan 切换到 auto 模式。`,
34
- `写盘 / 命令 / 记忆写入工具(write_file / edit_file / run_command / memory_save / memory_update / memory_forget)现已恢复可用。`,
35
- `请按你刚才产出的计划继续执行(同一轮内,无需重述计划)。`,
36
- ].join('\n');
37
- }
38
- return [
39
- `已从 auto 切换到 plan 模式(只读探查)。`,
40
- `写盘 / 命令 / 记忆写入工具已禁用;只用 read_file / glob / grep / codegraph / web_search / web_fetch / use_skill / ask_human / memory_search / memory_list 探查。`,
41
- `产出计划后 STOP 等用户审批,或(若用户要求自主执行)再调 switch_mode("auto") 续跑。`,
42
- ].join('\n');
43
- },
44
- };
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const taskTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const todolistTool: Tool;
@@ -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,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const useSkillTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const webFetchTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const webSearchTool: Tool;
@@ -1,2 +0,0 @@
1
- import type { Tool } from '../types.js';
2
- export declare const writeFileTool: Tool;
@@ -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>;