mocode-ai 0.5.4 → 0.5.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/dist/agent/core.js +34 -49
- package/dist/agent/index.js +96 -52
- package/dist/agent/spawn.js +7 -2
- package/dist/config/index.js +0 -1
- package/dist/context/budget.js +22 -33
- package/dist/context/index.js +1 -1
- package/dist/context/lifecycle.js +1 -55
- package/dist/context/relevance.js +1 -62
- package/dist/context/utils.js +68 -0
- package/dist/project-skill/initializer.js +83 -83
- package/dist/project-snapshot/llm-snapshot.js +69 -69
- package/dist/repl/index.js +28 -22
- package/dist/rollback/index.js +1 -13
- package/dist/session/compact.js +20 -24
- package/dist/session/drop.js +1 -43
- package/dist/session/index.js +1 -1
- package/dist/session/scheduler.js +8 -8
- package/dist/tools/builtins/todolist.js +2 -4
- package/dist/ui/batch.js +142 -207
- package/dist/ui/content.js +22 -0
- package/dist/ui/layout.js +55 -37
- package/dist/ui/markdown.js +8 -2
- package/package.json +1 -1
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
// - mutation 分支额外调 pushMutation 通知(也走 observeMutation 同语义)。
|
|
27
27
|
//
|
|
28
28
|
// 开关:`config.contextLifecycle`(默认 true;MOCODE_LIFECYCLE=false 回退)。
|
|
29
|
+
import { extractPath, lastUserIndex, toText, toolNameOf } from './utils.js';
|
|
29
30
|
/** 观察类工具(永远只到 REFERENCED,不参与自动 STUB)。 */
|
|
30
31
|
const OBSERVER_TOOLS = new Set([
|
|
31
32
|
'grep',
|
|
@@ -45,61 +46,6 @@ const STUB_PREFIX_NO_CONSUMER = '⌦[无消费者:观察结果已无引用价值
|
|
|
45
46
|
* ≥ 这个值且仍为 LIVE 且非观察类 → 视为 OBSOLETE → STUB。
|
|
46
47
|
* 默认 2:等价于「跨过两个消费者 push 仍无人引用」= 跨过整轮最末尾的工具调用。 */
|
|
47
48
|
const DEFAULT_AGE_THRESHOLD = 2;
|
|
48
|
-
/** 复用 drop.ts 的取 path 思路,但本层要支持更多字段名(read_file/edit_file/write_file 都有 path;
|
|
49
|
-
* edit_file 还可能有 file_path,但这里只看 path,保持单一)。 */
|
|
50
|
-
function extractPath(argsRaw) {
|
|
51
|
-
if (!argsRaw)
|
|
52
|
-
return null;
|
|
53
|
-
let parsed;
|
|
54
|
-
try {
|
|
55
|
-
parsed = JSON.parse(argsRaw);
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
if (!parsed || typeof parsed !== 'object')
|
|
61
|
-
return null;
|
|
62
|
-
const p = parsed.path;
|
|
63
|
-
return typeof p === 'string' && p ? p : null;
|
|
64
|
-
}
|
|
65
|
-
/** 从 tool 消息往前找匹配的 assistant.tool_calls 拿 tool 名。找不到返 null(保守跳过)。 */
|
|
66
|
-
function toolNameOf(history, idx) {
|
|
67
|
-
const tcId = history[idx].tool_call_id;
|
|
68
|
-
if (!tcId)
|
|
69
|
-
return null;
|
|
70
|
-
for (let j = idx - 1; j >= 1; j--) {
|
|
71
|
-
const m = history[j];
|
|
72
|
-
if (m.role !== 'assistant')
|
|
73
|
-
continue;
|
|
74
|
-
const tcs = m.tool_calls;
|
|
75
|
-
if (!tcs)
|
|
76
|
-
continue;
|
|
77
|
-
const hit = tcs.find((tc) => tc?.id === tcId);
|
|
78
|
-
if (hit)
|
|
79
|
-
return hit.function?.name ?? null;
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
/** 找最后一个 user 消息索引;无 user 返 -1。 */
|
|
84
|
-
function lastUserIndex(history) {
|
|
85
|
-
for (let i = history.length - 1; i >= 1; i--) {
|
|
86
|
-
if (history[i].role === 'user')
|
|
87
|
-
return i;
|
|
88
|
-
}
|
|
89
|
-
return -1;
|
|
90
|
-
}
|
|
91
|
-
function toText(content) {
|
|
92
|
-
if (content == null)
|
|
93
|
-
return '';
|
|
94
|
-
if (typeof content === 'string')
|
|
95
|
-
return content;
|
|
96
|
-
try {
|
|
97
|
-
return JSON.stringify(content);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
return String(content);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
49
|
/** 从 tool 结果的 content 中提取「生产者命中过的 path 列表」。
|
|
104
50
|
* - read_file:没有 path 列表(本身就是单 path 消费者,无需再追生产者)。
|
|
105
51
|
* - grep:content 是 `file:line: ...` 行,提取每行的 file 段(只保留绝对路径形态或与 pattern 匹配的)。
|
|
@@ -22,71 +22,10 @@
|
|
|
22
22
|
// - TUI 渲染(hooks.onToolResult)用原始 output,与本层解耦——屏上看全量,LLM 看裁剪后版。
|
|
23
23
|
//
|
|
24
24
|
// 零行为变化兜底:开关 `config.contextRelprune` 关闭时,pipeline 路径完全不调本模块。
|
|
25
|
+
import { extractPath, lastUserIndex, toText, toolNameOf } from './utils.js';
|
|
25
26
|
/** stub 标记前缀(供幂等判定)。drop_context 用的是「⌦[已剔除:与当前任务无关]」,
|
|
26
27
|
* 本层用「⌦[已过时:同 path 已有新 read / 已被 mutation 覆写]」,区分两类剔除来源。 */
|
|
27
28
|
const STUB_PREFIX = '⌦[已过时:同 path 已有新 read / 已被 mutation 覆写]';
|
|
28
|
-
/** 解析工具 arguments(只关心 path);非法返 null。 */
|
|
29
|
-
function extractPath(argsRaw) {
|
|
30
|
-
if (!argsRaw)
|
|
31
|
-
return null;
|
|
32
|
-
let parsed;
|
|
33
|
-
try {
|
|
34
|
-
parsed = JSON.parse(argsRaw);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
if (!parsed || typeof parsed !== 'object')
|
|
40
|
-
return null;
|
|
41
|
-
const p = parsed.path;
|
|
42
|
-
if (typeof p !== 'string' || !p)
|
|
43
|
-
return null;
|
|
44
|
-
return p;
|
|
45
|
-
}
|
|
46
|
-
/** 从 history 末尾向前找最后一个 user 消息的索引;无 user 返 -1。
|
|
47
|
-
* 复用 drop.ts 的思路:user 及其之后的 tool 结果视为当前轮保护区。
|
|
48
|
-
* 在本层里,read_file tool 消息若落在 user 之后,本轮还在用,不能 stub。
|
|
49
|
-
* 注:history[0] 是 system,user 不会落在 0;若 user 就在末尾(即 0 user 之后),
|
|
50
|
-
* protectedFrom=0 时整段历史都不可 stub(实际不会发生:pushToolResult 必在 user 之后)。 */
|
|
51
|
-
function lastUserIndex(history) {
|
|
52
|
-
for (let i = history.length - 1; i >= 1; i--) {
|
|
53
|
-
if (history[i].role === 'user')
|
|
54
|
-
return i;
|
|
55
|
-
}
|
|
56
|
-
return -1;
|
|
57
|
-
}
|
|
58
|
-
/** 取 tool 消息对应的工具名(从紧邻的前导 assistant.tool_calls 按 tool_call_id 配对找)。
|
|
59
|
-
* 返回 null 表示找不到(孤儿 tool,极少见),本层保守不动。 */
|
|
60
|
-
function toolNameOf(history, idx) {
|
|
61
|
-
const tcId = history[idx].tool_call_id;
|
|
62
|
-
if (!tcId)
|
|
63
|
-
return null;
|
|
64
|
-
for (let j = idx - 1; j >= 1; j--) {
|
|
65
|
-
const m = history[j];
|
|
66
|
-
if (m.role !== 'assistant')
|
|
67
|
-
continue;
|
|
68
|
-
const tcs = m.tool_calls;
|
|
69
|
-
if (!tcs)
|
|
70
|
-
continue;
|
|
71
|
-
const hit = tcs.find((tc) => tc?.id === tcId);
|
|
72
|
-
if (hit)
|
|
73
|
-
return hit.function?.name ?? null;
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
/** 把 content 拍平成字符串(对齐 drop.ts)。 */
|
|
78
|
-
function toText(content) {
|
|
79
|
-
if (content == null)
|
|
80
|
-
return '';
|
|
81
|
-
if (typeof content === 'string')
|
|
82
|
-
return content;
|
|
83
|
-
try {
|
|
84
|
-
return JSON.stringify(content);
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
return String(content);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
29
|
/**
|
|
91
30
|
* 维护「path → 该 path 所有 read_file tool 消息的 history index」映射。
|
|
92
31
|
* - observePush:把刚 push 的 read_file tool 消息登记,并把同 path 的"更早" read 全部 stub。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// 跨上下文模块共享的小工具函数。
|
|
2
|
+
//
|
|
3
|
+
// 目的:消除 `relevance.ts` / `lifecycle.ts` / `budget.ts` / `compact.ts` / `session/drop.ts`
|
|
4
|
+
// 之间的 copy-paste。这些函数逻辑完全一致,统一维护于此,避免行为漂移。
|
|
5
|
+
//
|
|
6
|
+
// 不变量:
|
|
7
|
+
// - 永不抛错(对齐「调度器永不抛错」契约)。
|
|
8
|
+
// - 仅依赖 `ChatMessage` 的最小形状,不反向 import agent / session / tools。
|
|
9
|
+
/** 把消息 content 拍平成字符串(OpenAI 可能 string / null / 多模态数组)。
|
|
10
|
+
* 用于估算 token、内容匹配、stub 拼接等场景。 */
|
|
11
|
+
export function toText(content) {
|
|
12
|
+
if (content == null)
|
|
13
|
+
return '';
|
|
14
|
+
if (typeof content === 'string')
|
|
15
|
+
return content;
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return String(content);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** 从 history 末尾向前找最后一个 user 消息的索引;无 user 返 -1。
|
|
24
|
+
* 用于划定「当前轮保护区」——该 user 及其之后的消息(agent 本轮还在用)不动。
|
|
25
|
+
* 注:history[0] 是 system,故下界为 1。 */
|
|
26
|
+
export function lastUserIndex(history) {
|
|
27
|
+
for (let i = history.length - 1; i >= 1; i--) {
|
|
28
|
+
if (history[i].role === 'user')
|
|
29
|
+
return i;
|
|
30
|
+
}
|
|
31
|
+
return -1;
|
|
32
|
+
}
|
|
33
|
+
/** 取 tool 消息对应的工具名(从紧邻的前导 assistant.tool_calls 按 tool_call_id 配对找)。
|
|
34
|
+
* 返回 null 表示找不到(孤儿 tool 消息,极少见),调用方保守跳过。 */
|
|
35
|
+
export function toolNameOf(history, idx) {
|
|
36
|
+
const tcId = history[idx].tool_call_id;
|
|
37
|
+
if (!tcId)
|
|
38
|
+
return null;
|
|
39
|
+
for (let j = idx - 1; j >= 1; j--) {
|
|
40
|
+
const m = history[j];
|
|
41
|
+
if (m.role !== 'assistant')
|
|
42
|
+
continue;
|
|
43
|
+
const tcs = m.tool_calls;
|
|
44
|
+
if (!tcs)
|
|
45
|
+
continue;
|
|
46
|
+
const hit = tcs.find((tc) => tc?.id === tcId);
|
|
47
|
+
if (hit)
|
|
48
|
+
return hit.function?.name ?? null;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/** 解析工具 arguments JSON,只取 `path` 字段;非法 / 无 path 返 null。
|
|
53
|
+
* 用于 read_file / edit_file / write_file 等以 path 为关键字的工具,供跨消息关联使用。 */
|
|
54
|
+
export function extractPath(argsRaw) {
|
|
55
|
+
if (!argsRaw)
|
|
56
|
+
return null;
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(argsRaw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
if (!parsed || typeof parsed !== 'object')
|
|
65
|
+
return null;
|
|
66
|
+
const p = parsed.path;
|
|
67
|
+
return typeof p === 'string' && p ? p : null;
|
|
68
|
+
}
|
|
@@ -5,63 +5,63 @@ import { spawnAgent } from '../agent/spawn.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* 子 agent 的系统提示后缀:角色与输出格式约束
|
|
7
7
|
*/
|
|
8
|
-
const SKILL_INIT_SUFFIX = `You are a project exploration agent. Your task is to deeply understand this project and generate a concise "Project Skill" document.
|
|
9
|
-
|
|
10
|
-
## ⚠️ Complementary to Snapshot — CRITICAL
|
|
11
|
-
The following information is ALREADY provided by Project Snapshot — **do NOT repeat**:
|
|
12
|
-
- ✗ Project one-liner description (快照已有)
|
|
13
|
-
- ✗ Module names and file locations (快照 srcTree 已有)
|
|
14
|
-
- ✗ Tech stack names and versions (快照 techStack 已有)
|
|
15
|
-
- ✗ Build/test commands (快照 keyCommands 已有)
|
|
16
|
-
- ✗ File lists and directory structure (快照 srcTree 已有)
|
|
17
|
-
|
|
18
|
-
**Your job is to capture INSIGHTS only:**
|
|
19
|
-
- **WHY** — design decisions, trade-offs, reasons for choosing X over Y
|
|
20
|
-
- **HOW** — module behaviors, data flows, call chains, non-obvious interactions
|
|
21
|
-
- **GOTCHAS** — pitfalls, edge cases, non-intuitive behaviors
|
|
22
|
-
- **CONVENTIONS** — naming patterns, code style, unwritten rules
|
|
23
|
-
|
|
24
|
-
## Exploration Strategy
|
|
25
|
-
1. Use \`codegraph\` to trace call chains and understand module interactions
|
|
26
|
-
2. Read core modules to understand **behaviors** (not just names)
|
|
27
|
-
3. Look for complex logic, error handling patterns, unusual configs
|
|
28
|
-
4. Identify naming conventions, common abstractions
|
|
29
|
-
|
|
30
|
-
## Output Format
|
|
31
|
-
Output the skill document between these exact delimiters:
|
|
32
|
-
|
|
33
|
-
\`\`\`skill-start
|
|
34
|
-
## 架构要点
|
|
35
|
-
### 核心模块及职责
|
|
36
|
-
- **\`path/to/module\`** — 行为描述(做什么 + 怎么做)
|
|
37
|
-
...
|
|
38
|
-
|
|
39
|
-
### 关键设计决策
|
|
40
|
-
- 决策:原因
|
|
41
|
-
...
|
|
42
|
-
|
|
43
|
-
## 项目约定
|
|
44
|
-
- 约定(具体的,有例子的)
|
|
45
|
-
...
|
|
46
|
-
|
|
47
|
-
## 开发流程
|
|
48
|
-
- 命令 + 注意事项/坑点(命令本身快照已有,这里只写注意什么)
|
|
49
|
-
...
|
|
50
|
-
|
|
51
|
-
## 常见坑点
|
|
52
|
-
- 坑:解法
|
|
53
|
-
...
|
|
54
|
-
\`\`\`skill-end
|
|
55
|
-
|
|
56
|
-
## Rules — MUST FOLLOW
|
|
57
|
-
1. 禁止列出文件名清单或目录结构
|
|
58
|
-
2. 禁止列出依赖名和版本
|
|
59
|
-
3. 禁止写项目概述/一句话描述
|
|
60
|
-
4. 禁止只写命令本身(快照已有),只写注意事项
|
|
61
|
-
5. 每个模块描述必须包含行为(做什么 + 怎么协作),不只是名称
|
|
62
|
-
6. **总长度目标 3000-4000 字符,硬上限 4000**
|
|
63
|
-
7. 写可操作的内容,用具体路径和例子
|
|
64
|
-
8. 如果某个 section 没有实质内容,省略它
|
|
8
|
+
const SKILL_INIT_SUFFIX = `You are a project exploration agent. Your task is to deeply understand this project and generate a concise "Project Skill" document.
|
|
9
|
+
|
|
10
|
+
## ⚠️ Complementary to Snapshot — CRITICAL
|
|
11
|
+
The following information is ALREADY provided by Project Snapshot — **do NOT repeat**:
|
|
12
|
+
- ✗ Project one-liner description (快照已有)
|
|
13
|
+
- ✗ Module names and file locations (快照 srcTree 已有)
|
|
14
|
+
- ✗ Tech stack names and versions (快照 techStack 已有)
|
|
15
|
+
- ✗ Build/test commands (快照 keyCommands 已有)
|
|
16
|
+
- ✗ File lists and directory structure (快照 srcTree 已有)
|
|
17
|
+
|
|
18
|
+
**Your job is to capture INSIGHTS only:**
|
|
19
|
+
- **WHY** — design decisions, trade-offs, reasons for choosing X over Y
|
|
20
|
+
- **HOW** — module behaviors, data flows, call chains, non-obvious interactions
|
|
21
|
+
- **GOTCHAS** — pitfalls, edge cases, non-intuitive behaviors
|
|
22
|
+
- **CONVENTIONS** — naming patterns, code style, unwritten rules
|
|
23
|
+
|
|
24
|
+
## Exploration Strategy
|
|
25
|
+
1. Use \`codegraph\` to trace call chains and understand module interactions
|
|
26
|
+
2. Read core modules to understand **behaviors** (not just names)
|
|
27
|
+
3. Look for complex logic, error handling patterns, unusual configs
|
|
28
|
+
4. Identify naming conventions, common abstractions
|
|
29
|
+
|
|
30
|
+
## Output Format
|
|
31
|
+
Output the skill document between these exact delimiters:
|
|
32
|
+
|
|
33
|
+
\`\`\`skill-start
|
|
34
|
+
## 架构要点
|
|
35
|
+
### 核心模块及职责
|
|
36
|
+
- **\`path/to/module\`** — 行为描述(做什么 + 怎么做)
|
|
37
|
+
...
|
|
38
|
+
|
|
39
|
+
### 关键设计决策
|
|
40
|
+
- 决策:原因
|
|
41
|
+
...
|
|
42
|
+
|
|
43
|
+
## 项目约定
|
|
44
|
+
- 约定(具体的,有例子的)
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
## 开发流程
|
|
48
|
+
- 命令 + 注意事项/坑点(命令本身快照已有,这里只写注意什么)
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
## 常见坑点
|
|
52
|
+
- 坑:解法
|
|
53
|
+
...
|
|
54
|
+
\`\`\`skill-end
|
|
55
|
+
|
|
56
|
+
## Rules — MUST FOLLOW
|
|
57
|
+
1. 禁止列出文件名清单或目录结构
|
|
58
|
+
2. 禁止列出依赖名和版本
|
|
59
|
+
3. 禁止写项目概述/一句话描述
|
|
60
|
+
4. 禁止只写命令本身(快照已有),只写注意事项
|
|
61
|
+
5. 每个模块描述必须包含行为(做什么 + 怎么协作),不只是名称
|
|
62
|
+
6. **总长度目标 3000-4000 字符,硬上限 4000**
|
|
63
|
+
7. 写可操作的内容,用具体路径和例子
|
|
64
|
+
8. 如果某个 section 没有实质内容,省略它
|
|
65
65
|
`;
|
|
66
66
|
/**
|
|
67
67
|
* 生成初始项目 Skill(使用子 agent 深度探索)
|
|
@@ -72,35 +72,35 @@ export async function generateInitialSkill(existingSkill, signal) {
|
|
|
72
72
|
const hasExisting = !!existingSkill;
|
|
73
73
|
let prompt;
|
|
74
74
|
if (hasExisting) {
|
|
75
|
-
prompt = `以下是当前的项目 Skill 内容,请深度探索项目并优化/完善它:
|
|
76
|
-
|
|
77
|
-
\`\`\`markdown
|
|
78
|
-
${existingSkill}
|
|
79
|
-
\`\`\`
|
|
80
|
-
|
|
81
|
-
## 优化方向
|
|
82
|
-
1. 补充缺失的信息(架构要点、设计决策、调用链等)
|
|
83
|
-
2. 修正过时或不准确的内容
|
|
84
|
-
3. 让描述更具体(用实际路径和例子)
|
|
85
|
-
4. 删除冗余或空泛的描述
|
|
86
|
-
5. 如果发现新的坑点或约定,添加进去
|
|
87
|
-
|
|
75
|
+
prompt = `以下是当前的项目 Skill 内容,请深度探索项目并优化/完善它:
|
|
76
|
+
|
|
77
|
+
\`\`\`markdown
|
|
78
|
+
${existingSkill}
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
## 优化方向
|
|
82
|
+
1. 补充缺失的信息(架构要点、设计决策、调用链等)
|
|
83
|
+
2. 修正过时或不准确的内容
|
|
84
|
+
3. 让描述更具体(用实际路径和例子)
|
|
85
|
+
4. 删除冗余或空泛的描述
|
|
86
|
+
5. 如果发现新的坑点或约定,添加进去
|
|
87
|
+
|
|
88
88
|
记住:保持精简,总长度控制在 4000-5000 字符。输出完整的优化后版本。`;
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
prompt = `请深度探索当前项目,生成一份项目专属 Skill 文档。
|
|
92
|
-
|
|
93
|
-
## 探索步骤
|
|
94
|
-
1. 先用 glob 扫描项目结构(**/*.ts, **/*.json 等)
|
|
95
|
-
2. 读取 package.json、README.md、tsconfig.json 等关键文件
|
|
96
|
-
3. 用 codegraph 或 grep 找到主入口、核心模块、关键接口
|
|
97
|
-
4. 理解架构模式、数据流向、调用关系
|
|
98
|
-
5. 识别命名约定、代码风格、测试策略
|
|
99
|
-
6. 找出可能的坑点(复杂配置、特殊依赖、构建陷阱)
|
|
100
|
-
|
|
101
|
-
## 输出
|
|
102
|
-
探索完成后,将完整的 skill 文档输出在 \`\`\`skill-start 和 \`\`\`skill-end 之间。
|
|
103
|
-
|
|
91
|
+
prompt = `请深度探索当前项目,生成一份项目专属 Skill 文档。
|
|
92
|
+
|
|
93
|
+
## 探索步骤
|
|
94
|
+
1. 先用 glob 扫描项目结构(**/*.ts, **/*.json 等)
|
|
95
|
+
2. 读取 package.json、README.md、tsconfig.json 等关键文件
|
|
96
|
+
3. 用 codegraph 或 grep 找到主入口、核心模块、关键接口
|
|
97
|
+
4. 理解架构模式、数据流向、调用关系
|
|
98
|
+
5. 识别命名约定、代码风格、测试策略
|
|
99
|
+
6. 找出可能的坑点(复杂配置、特殊依赖、构建陷阱)
|
|
100
|
+
|
|
101
|
+
## 输出
|
|
102
|
+
探索完成后,将完整的 skill 文档输出在 \`\`\`skill-start 和 \`\`\`skill-end 之间。
|
|
103
|
+
|
|
104
104
|
记住:内容要精简、可操作、有具体路径和例子。总长度控制在 4000-5000 字符。`;
|
|
105
105
|
}
|
|
106
106
|
try {
|
|
@@ -2,64 +2,64 @@ import { spawnAgent } from '../agent/spawn.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* 子 agent 系统提示:让 agent 自主探索项目并生成 markdown 快照
|
|
4
4
|
*/
|
|
5
|
-
const SNAPSHOT_LLM_SUFFIX = `You are a project analysis agent. Your task is to explore this project and generate a concise markdown snapshot for an AI coding assistant.
|
|
6
|
-
|
|
7
|
-
## Your Mission
|
|
8
|
-
Explore the project using available tools (read_file, glob, grep) and generate a structured markdown snapshot that captures:
|
|
9
|
-
- WHAT exists (project description, tech stack, commands, modules, directory structure)
|
|
10
|
-
- WHERE it is (paths, locations)
|
|
11
|
-
|
|
12
|
-
Do NOT explain WHY or HOW (that's Project Skill's job).
|
|
13
|
-
|
|
14
|
-
## Exploration Strategy
|
|
15
|
-
1. Read package.json, README.md, tsconfig.json (or equivalent for other languages)
|
|
16
|
-
2. List top-level directories to identify modules
|
|
17
|
-
3. Scan src/ directory structure (depth ≤3, directories only)
|
|
18
|
-
4. Extract key commands from package.json scripts
|
|
19
|
-
5. Identify tech stack from dependencies
|
|
20
|
-
|
|
21
|
-
## Output Format
|
|
22
|
-
Output a markdown document between these exact delimiters:
|
|
23
|
-
|
|
24
|
-
\`\`\`snapshot-md
|
|
25
|
-
# Project Snapshot
|
|
26
|
-
|
|
27
|
-
## Description
|
|
28
|
-
(一句话描述项目,≤80字,中文)
|
|
29
|
-
|
|
30
|
-
## Tech Stack
|
|
31
|
-
(关键技术栈,逗号分隔,含版本,≤15项)
|
|
32
|
-
|
|
33
|
-
## Commands
|
|
34
|
-
| Command | Description |
|
|
35
|
-
|---------|-------------|
|
|
36
|
-
| \`npm run build\` | 构建项目 |
|
|
37
|
-
| \`npm test\` | 运行测试 |
|
|
38
|
-
| ... | ... |
|
|
39
|
-
|
|
40
|
-
## Modules
|
|
41
|
-
- **module-name** — 一句话职责描述(≤20字)
|
|
42
|
-
- **module-name** — 一句话职责描述
|
|
43
|
-
- ...
|
|
44
|
-
|
|
45
|
-
## Source Tree
|
|
46
|
-
\`\`\`
|
|
47
|
-
src/
|
|
48
|
-
agent/
|
|
49
|
-
tools/
|
|
50
|
-
ui/
|
|
51
|
-
\`\`\`
|
|
52
|
-
\`\`\`snapshot-md
|
|
53
|
-
|
|
54
|
-
## Rules
|
|
55
|
-
1. **Description**: ≤80字,中文,从 README 和 package.json description 提炼。不要"一个..."开头。
|
|
56
|
-
2. **Tech Stack**: ≤15项,逗号分隔,含版本。格式如 "TypeScript 5.x, Node.js ≥18, openai@^4"。
|
|
57
|
-
3. **Commands**: 5-8个最重要的命令,从 package.json scripts 提取。Description ≤15字。用表格格式。
|
|
58
|
-
4. **Modules**: 每个顶层模块目录一条。职责≤20字,只写 WHAT(做什么)。
|
|
59
|
-
5. **Source Tree**: 只列目录(不列文件),深度≤3,用缩进表示层级,放在代码块中。
|
|
60
|
-
6. 不要包含:设计决策、注意事项、坑点、约定 → 这些是 Skill 的职责。
|
|
61
|
-
7. 总 markdown ≤ 2500 字符。
|
|
62
|
-
8. 使用中文。
|
|
5
|
+
const SNAPSHOT_LLM_SUFFIX = `You are a project analysis agent. Your task is to explore this project and generate a concise markdown snapshot for an AI coding assistant.
|
|
6
|
+
|
|
7
|
+
## Your Mission
|
|
8
|
+
Explore the project using available tools (read_file, glob, grep) and generate a structured markdown snapshot that captures:
|
|
9
|
+
- WHAT exists (project description, tech stack, commands, modules, directory structure)
|
|
10
|
+
- WHERE it is (paths, locations)
|
|
11
|
+
|
|
12
|
+
Do NOT explain WHY or HOW (that's Project Skill's job).
|
|
13
|
+
|
|
14
|
+
## Exploration Strategy
|
|
15
|
+
1. Read package.json, README.md, tsconfig.json (or equivalent for other languages)
|
|
16
|
+
2. List top-level directories to identify modules
|
|
17
|
+
3. Scan src/ directory structure (depth ≤3, directories only)
|
|
18
|
+
4. Extract key commands from package.json scripts
|
|
19
|
+
5. Identify tech stack from dependencies
|
|
20
|
+
|
|
21
|
+
## Output Format
|
|
22
|
+
Output a markdown document between these exact delimiters:
|
|
23
|
+
|
|
24
|
+
\`\`\`snapshot-md
|
|
25
|
+
# Project Snapshot
|
|
26
|
+
|
|
27
|
+
## Description
|
|
28
|
+
(一句话描述项目,≤80字,中文)
|
|
29
|
+
|
|
30
|
+
## Tech Stack
|
|
31
|
+
(关键技术栈,逗号分隔,含版本,≤15项)
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
| Command | Description |
|
|
35
|
+
|---------|-------------|
|
|
36
|
+
| \`npm run build\` | 构建项目 |
|
|
37
|
+
| \`npm test\` | 运行测试 |
|
|
38
|
+
| ... | ... |
|
|
39
|
+
|
|
40
|
+
## Modules
|
|
41
|
+
- **module-name** — 一句话职责描述(≤20字)
|
|
42
|
+
- **module-name** — 一句话职责描述
|
|
43
|
+
- ...
|
|
44
|
+
|
|
45
|
+
## Source Tree
|
|
46
|
+
\`\`\`
|
|
47
|
+
src/
|
|
48
|
+
agent/
|
|
49
|
+
tools/
|
|
50
|
+
ui/
|
|
51
|
+
\`\`\`
|
|
52
|
+
\`\`\`snapshot-md
|
|
53
|
+
|
|
54
|
+
## Rules
|
|
55
|
+
1. **Description**: ≤80字,中文,从 README 和 package.json description 提炼。不要"一个..."开头。
|
|
56
|
+
2. **Tech Stack**: ≤15项,逗号分隔,含版本。格式如 "TypeScript 5.x, Node.js ≥18, openai@^4"。
|
|
57
|
+
3. **Commands**: 5-8个最重要的命令,从 package.json scripts 提取。Description ≤15字。用表格格式。
|
|
58
|
+
4. **Modules**: 每个顶层模块目录一条。职责≤20字,只写 WHAT(做什么)。
|
|
59
|
+
5. **Source Tree**: 只列目录(不列文件),深度≤3,用缩进表示层级,放在代码块中。
|
|
60
|
+
6. 不要包含:设计决策、注意事项、坑点、约定 → 这些是 Skill 的职责。
|
|
61
|
+
7. 总 markdown ≤ 2500 字符。
|
|
62
|
+
8. 使用中文。
|
|
63
63
|
`;
|
|
64
64
|
/**
|
|
65
65
|
* 生成 LLM 快照(markdown 格式)
|
|
@@ -67,17 +67,17 @@ src/
|
|
|
67
67
|
* @param signal AbortSignal
|
|
68
68
|
*/
|
|
69
69
|
export async function generateLLMSnapshot(root, signal) {
|
|
70
|
-
const prompt = `请探索项目 ${root},生成项目快照(markdown 格式)。
|
|
71
|
-
|
|
72
|
-
## 探索步骤
|
|
73
|
-
1. 读取 package.json(或等效配置文件)
|
|
74
|
-
2. 读取 README.md
|
|
75
|
-
3. 列出顶层目录,识别模块
|
|
76
|
-
4. 扫描 src/ 目录结构(深度≤3,只列目录)
|
|
77
|
-
5. 从 package.json scripts 提取关键命令
|
|
78
|
-
6. 从 dependencies 识别技术栈
|
|
79
|
-
|
|
80
|
-
## 输出
|
|
70
|
+
const prompt = `请探索项目 ${root},生成项目快照(markdown 格式)。
|
|
71
|
+
|
|
72
|
+
## 探索步骤
|
|
73
|
+
1. 读取 package.json(或等效配置文件)
|
|
74
|
+
2. 读取 README.md
|
|
75
|
+
3. 列出顶层目录,识别模块
|
|
76
|
+
4. 扫描 src/ 目录结构(深度≤3,只列目录)
|
|
77
|
+
5. 从 package.json scripts 提取关键命令
|
|
78
|
+
6. 从 dependencies 识别技术栈
|
|
79
|
+
|
|
80
|
+
## 输出
|
|
81
81
|
严格按照系统提示的 markdown 格式输出,不要添加额外解释。`;
|
|
82
82
|
try {
|
|
83
83
|
const result = await spawnAgent({
|
package/dist/repl/index.js
CHANGED
|
@@ -169,7 +169,7 @@ function refreshStatusBase(history, lastTurnUsage) {
|
|
|
169
169
|
model: config.model,
|
|
170
170
|
contextBar: renderContextBarInline(history),
|
|
171
171
|
cwd: process.cwd(),
|
|
172
|
-
modeTag: getAgentMode() === 'plan' ? '
|
|
172
|
+
modeTag: getAgentMode() === 'plan' ? 'Plan' : 'Auto',
|
|
173
173
|
planSummary: hasActivePlan() ? getActivePlanSummary(process.stdout.columns ?? 80) : '',
|
|
174
174
|
lastTurnUsage,
|
|
175
175
|
});
|
|
@@ -455,14 +455,15 @@ function textOf(c) {
|
|
|
455
455
|
* 回放默认全折叠;用户可鼠标点击摘要行展开(由 BatchRenderer 接管,见 ui/batch.ts)。
|
|
456
456
|
*/
|
|
457
457
|
export function renderHistory(history) {
|
|
458
|
-
const
|
|
459
|
-
//
|
|
460
|
-
let
|
|
458
|
+
const idToEntry = new Map();
|
|
459
|
+
// 普通工具可跨 assistant 步聚合;mutation 各自占一个 group,并切断前后普通工具。
|
|
460
|
+
let pendingBatches = [];
|
|
461
|
+
let normalBatch = null;
|
|
461
462
|
const flushBatch = () => {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
463
|
+
for (const entries of pendingBatches)
|
|
464
|
+
batch.writeSummaryOnly(entries, layout);
|
|
465
|
+
pendingBatches = [];
|
|
466
|
+
normalBatch = null;
|
|
466
467
|
};
|
|
467
468
|
for (let idx = 0; idx < history.length; idx++) {
|
|
468
469
|
const m = history[idx];
|
|
@@ -505,14 +506,25 @@ export function renderHistory(history) {
|
|
|
505
506
|
for (const tc of tcs) {
|
|
506
507
|
const name = tc?.function?.name ?? '';
|
|
507
508
|
const args = tc?.function?.arguments ?? '';
|
|
508
|
-
|
|
509
|
-
idToName.set(tc.id, name);
|
|
510
|
-
pendingBatch.push({
|
|
509
|
+
const entry = {
|
|
511
510
|
name,
|
|
512
511
|
callSummary: summarizeToolCall(name, args),
|
|
513
512
|
resultSummary: '',
|
|
514
513
|
diffBlock: null,
|
|
515
|
-
}
|
|
514
|
+
};
|
|
515
|
+
if (batch.isMutationToolName(name)) {
|
|
516
|
+
normalBatch = null;
|
|
517
|
+
pendingBatches.push([entry]);
|
|
518
|
+
}
|
|
519
|
+
else {
|
|
520
|
+
if (!normalBatch) {
|
|
521
|
+
normalBatch = [];
|
|
522
|
+
pendingBatches.push(normalBatch);
|
|
523
|
+
}
|
|
524
|
+
normalBatch.push(entry);
|
|
525
|
+
}
|
|
526
|
+
if (tc?.id)
|
|
527
|
+
idToEntry.set(tc.id, entry);
|
|
516
528
|
}
|
|
517
529
|
continue; // 跳过后续 tool 消息处理循环(由下一分支填 result)
|
|
518
530
|
}
|
|
@@ -522,18 +534,10 @@ export function renderHistory(history) {
|
|
|
522
534
|
}
|
|
523
535
|
if (m.role === 'tool') {
|
|
524
536
|
const id = m.tool_call_id ?? '';
|
|
525
|
-
const
|
|
537
|
+
const target = idToEntry.get(id);
|
|
538
|
+
const name = target?.name ?? '';
|
|
526
539
|
const output = textOf(m.content);
|
|
527
540
|
const preview = summarizeToolResult(name, output);
|
|
528
|
-
// 匹配 pendingBatch 中尚未填 result 的同名 entry;同名前缀 tool 较罕见(并行工具同 id 不同名)
|
|
529
|
-
let target;
|
|
530
|
-
for (let i = pendingBatch.length - 1; i >= 0; i--) {
|
|
531
|
-
const e = pendingBatch[i];
|
|
532
|
-
if (e.name === name && !e.resultSummary) {
|
|
533
|
-
target = e;
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
541
|
if (target) {
|
|
538
542
|
target.resultSummary = preview;
|
|
539
543
|
target.fullOutput = output;
|
|
@@ -853,6 +857,7 @@ export async function startRepl(initialHistory, sessionId, updateNotice = null,
|
|
|
853
857
|
if (!loadSnapshots(loaded.id))
|
|
854
858
|
rebuildFromHistory(history);
|
|
855
859
|
contextState.lastUsage = undefined;
|
|
860
|
+
contextState.correction = 1;
|
|
856
861
|
lastTurnUsage = undefined; // 续接:旧会话的 token 累计已无意义,清空等下轮覆写
|
|
857
862
|
layout.clearContent();
|
|
858
863
|
renderHistory(history);
|
|
@@ -985,6 +990,7 @@ export async function startRepl(initialHistory, sessionId, updateNotice = null,
|
|
|
985
990
|
currentSessionId = undefined; // 下轮起新会话文件
|
|
986
991
|
turnCount = 0; // 反思 cadence 重新计数
|
|
987
992
|
contextState.lastUsage = undefined;
|
|
993
|
+
contextState.correction = 1;
|
|
988
994
|
lastTurnUsage = undefined; // 清空旧轮的 token 累计
|
|
989
995
|
pendingAttachments = []; // 一并清空待发图片
|
|
990
996
|
layout.clearContent();
|
package/dist/rollback/index.js
CHANGED
|
@@ -2,24 +2,12 @@ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { config } from '../config/index.js';
|
|
4
4
|
import { truncateDisplay } from '../ui/render.js';
|
|
5
|
+
import { toText } from '../context/utils.js';
|
|
5
6
|
let turnIdCounter = 0;
|
|
6
7
|
let currentTurnId = 0;
|
|
7
8
|
let turns = [];
|
|
8
9
|
let snapshots = [];
|
|
9
10
|
const MUTATION_TOOLS = new Set(['write_file', 'edit_file']);
|
|
10
|
-
/** 把任意 content 拍平成字符串(OpenAI 可能是 string / 多模态数组)。 */
|
|
11
|
-
function toText(content) {
|
|
12
|
-
if (content == null)
|
|
13
|
-
return '';
|
|
14
|
-
if (typeof content === 'string')
|
|
15
|
-
return content;
|
|
16
|
-
try {
|
|
17
|
-
return JSON.stringify(content);
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return String(content);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
11
|
/** 规整成 cwd 相对路径(快照存相对,跨 /resume 同项目可识别;resolve 已归一 ./ 和 ..)。 */
|
|
24
12
|
function toRel(p) {
|
|
25
13
|
try {
|