mocode-ai 0.4.7 → 0.4.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 (118) hide show
  1. package/dist/agent/core.d.ts +112 -0
  2. package/dist/agent/core.js +51 -11
  3. package/dist/agent/index.d.ts +19 -0
  4. package/dist/agent/mode.d.ts +29 -0
  5. package/dist/agent/spawn.d.ts +36 -0
  6. package/dist/attachments/image.d.ts +22 -0
  7. package/dist/commands/config.d.ts +8 -0
  8. package/dist/config/file.d.ts +23 -0
  9. package/dist/config/index.d.ts +114 -0
  10. package/dist/config/index.js +48 -1
  11. package/dist/config/presets.d.ts +57 -0
  12. package/dist/context/budget.d.ts +79 -0
  13. package/dist/context/classifier.d.ts +14 -0
  14. package/dist/context/encoders/_util.d.ts +11 -0
  15. package/dist/context/encoders/code.d.ts +2 -0
  16. package/dist/context/encoders/doc.d.ts +14 -0
  17. package/dist/context/encoders/graph.d.ts +11 -0
  18. package/dist/context/encoders/index.d.ts +2 -0
  19. package/dist/context/encoders/log.d.ts +2 -0
  20. package/dist/context/encoders/memory.d.ts +2 -0
  21. package/dist/context/encoders/passthrough.d.ts +11 -0
  22. package/dist/context/encoders/search.d.ts +2 -0
  23. package/dist/context/encoders/summary.d.ts +12 -0
  24. package/dist/context/encoders/table.d.ts +2 -0
  25. package/dist/context/encoders/tree.d.ts +2 -0
  26. package/dist/context/index.d.ts +6 -0
  27. package/dist/context/lifecycle.d.ts +51 -0
  28. package/dist/context/pipeline.d.ts +9 -0
  29. package/dist/context/registry.d.ts +9 -0
  30. package/dist/context/relevance.d.ts +65 -0
  31. package/dist/context/types.d.ts +36 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/llm/capabilities.d.ts +19 -0
  34. package/dist/llm/index.d.ts +111 -0
  35. package/dist/memory/discover.d.ts +15 -0
  36. package/dist/memory/index.d.ts +13 -0
  37. package/dist/memory/reflect.d.ts +34 -0
  38. package/dist/memory/store.d.ts +124 -0
  39. package/dist/permissions/index.js +138 -0
  40. package/dist/pet/bridge.d.ts +73 -0
  41. package/dist/pet/protocol.d.ts +83 -0
  42. package/dist/pet/state.d.ts +28 -0
  43. package/dist/plan/active.d.ts +23 -0
  44. package/dist/plan/index.d.ts +13 -0
  45. package/dist/plan/state.d.ts +2 -0
  46. package/dist/plan/store.d.ts +78 -0
  47. package/dist/project-skill/index.js +129 -0
  48. package/dist/project-skill/initializer.js +179 -0
  49. package/dist/project-snapshot/index.js +177 -0
  50. package/dist/project-snapshot/static-files.js +87 -0
  51. package/dist/repl/index.d.ts +22 -0
  52. package/dist/repl/index.js +159 -1
  53. package/dist/rollback/index.d.ts +79 -0
  54. package/dist/sandbox/command.d.ts +3 -0
  55. package/dist/sandbox/index.d.ts +4 -0
  56. package/dist/sandbox/jail.d.ts +17 -0
  57. package/dist/sandbox/policy.d.ts +21 -0
  58. package/dist/sandbox/root.d.ts +5 -0
  59. package/dist/session/compact.d.ts +92 -0
  60. package/dist/session/drop.d.ts +19 -0
  61. package/dist/session/index.d.ts +13 -0
  62. package/dist/session/persist.d.ts +30 -0
  63. package/dist/session/scheduler.d.ts +53 -0
  64. package/dist/skills/discover.d.ts +33 -0
  65. package/dist/skills/index.d.ts +13 -0
  66. package/dist/tools/builtins/ask-human.d.ts +9 -0
  67. package/dist/tools/builtins/codegraph.d.ts +2 -0
  68. package/dist/tools/builtins/codegraph.js +17 -4
  69. package/dist/tools/builtins/drop-context.d.ts +18 -0
  70. package/dist/tools/builtins/edit-file.d.ts +2 -0
  71. package/dist/tools/builtins/edit-file.js +1 -0
  72. package/dist/tools/builtins/glob.d.ts +2 -0
  73. package/dist/tools/builtins/grep.d.ts +2 -0
  74. package/dist/tools/builtins/index.d.ts +2 -0
  75. package/dist/tools/builtins/index.js +7 -0
  76. package/dist/tools/builtins/memory-forget.d.ts +2 -0
  77. package/dist/tools/builtins/memory-forget.js +1 -0
  78. package/dist/tools/builtins/memory-list.d.ts +2 -0
  79. package/dist/tools/builtins/memory-save.d.ts +2 -0
  80. package/dist/tools/builtins/memory-save.js +1 -0
  81. package/dist/tools/builtins/memory-search.d.ts +2 -0
  82. package/dist/tools/builtins/memory-update.d.ts +2 -0
  83. package/dist/tools/builtins/memory-update.js +1 -0
  84. package/dist/tools/builtins/project-skill-update.js +73 -0
  85. package/dist/tools/builtins/read-file.d.ts +2 -0
  86. package/dist/tools/builtins/read-file.js +17 -1
  87. package/dist/tools/builtins/run-command.d.ts +2 -0
  88. package/dist/tools/builtins/run-command.js +1 -0
  89. package/dist/tools/builtins/switch-mode.d.ts +2 -0
  90. package/dist/tools/builtins/task.d.ts +2 -0
  91. package/dist/tools/builtins/task.js +1 -0
  92. package/dist/tools/builtins/todolist.d.ts +2 -0
  93. package/dist/tools/builtins/use-skill.d.ts +2 -0
  94. package/dist/tools/builtins/web-fetch.d.ts +2 -0
  95. package/dist/tools/builtins/web-search.d.ts +2 -0
  96. package/dist/tools/builtins/web-search.js +12 -1
  97. package/dist/tools/builtins/write-file.d.ts +2 -0
  98. package/dist/tools/builtins/write-file.js +1 -0
  99. package/dist/tools/constants.d.ts +52 -0
  100. package/dist/tools/registry.d.ts +18 -0
  101. package/dist/tools/types.d.ts +42 -0
  102. package/dist/ui/batch.d.ts +67 -0
  103. package/dist/ui/clipboard.d.ts +9 -0
  104. package/dist/ui/content.d.ts +55 -0
  105. package/dist/ui/content.js +14 -3
  106. package/dist/ui/diff.d.ts +34 -0
  107. package/dist/ui/diff.js +15 -24
  108. package/dist/ui/intervention.d.ts +46 -0
  109. package/dist/ui/layout.d.ts +225 -0
  110. package/dist/ui/layout.js +10 -0
  111. package/dist/ui/markdown.d.ts +46 -0
  112. package/dist/ui/mouse.d.ts +29 -0
  113. package/dist/ui/prompt.d.ts +61 -0
  114. package/dist/ui/render.d.ts +60 -0
  115. package/dist/ui/spinner.d.ts +26 -0
  116. package/dist/ui/theme.d.ts +47 -0
  117. package/dist/updater/index.d.ts +22 -0
  118. package/package.json +1 -1
@@ -0,0 +1,129 @@
1
+ /**
2
+ * 项目专属 Skill — 跨 session 持久化的项目开发知识。
3
+ *
4
+ * 每个项目可维护一个专属 skill 文件(.mocode/project-skill.md),
5
+ * 记录开发约定、架构决策、常见坑点等。开关启用后:
6
+ * 1. 每次启动时内容注入系统提示词(buildProjectSkillSection)
7
+ * 2. Agent 可通过 project_skill_update 工具动态更新
8
+ * 3. 随着项目开发,skill 越来越了解项目,开发效率越来越高
9
+ *
10
+ * 存储位置: <cwd>/.mocode/project-skill.md
11
+ * 开关: MOCODE_PROJECT_SKILL 环境变量,默认 false(零侵入)
12
+ *
13
+ * 与现有功能的关系:
14
+ * - 通用 skill(SKILLS_DIRS): 按需加载(use_skill);项目 skill: 始终注入
15
+ * - memory: 碎片化 fact/decision; 项目 skill: 结构化知识总结
16
+ * - snapshot: 静态文件缓存(只读); 项目 skill: 动态知识(可写)
17
+ */
18
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'node:fs';
19
+ import path from 'node:path';
20
+ /** 项目 skill 文件路径 */
21
+ function skillPath() {
22
+ return path.join(process.cwd(), '.mocode', 'project-skill.md');
23
+ }
24
+ /** 备份文件路径 */
25
+ function backupPath() {
26
+ return path.join(process.cwd(), '.mocode', 'project-skill.md.bak');
27
+ }
28
+ /** 读取项目 skill 内容。不存在/读失败 → null */
29
+ export function readProjectSkill() {
30
+ const p = skillPath();
31
+ if (!existsSync(p))
32
+ return null;
33
+ try {
34
+ const content = readFileSync(p, 'utf8').trim();
35
+ return content || null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /** 内容硬上限(字符数)。超限拒绝写入,防止系统提示词膨胀 */
42
+ const MAX_SKILL_CHARS = 6000;
43
+ /**
44
+ * 写入/更新项目 skill。先备份旧内容再写新内容。
45
+ * 返回 { ok, error? }: ok=false 时 error 说明原因(超限/IO 失败)。
46
+ */
47
+ export function writeProjectSkill(content) {
48
+ const trimmed = content.trim();
49
+ if (trimmed.length > MAX_SKILL_CHARS) {
50
+ return {
51
+ ok: false,
52
+ error: `内容超过上限(${trimmed.length}/${MAX_SKILL_CHARS} 字符)。请精简后重试。`,
53
+ };
54
+ }
55
+ const p = skillPath();
56
+ const dir = path.dirname(p);
57
+ try {
58
+ // 备份旧内容(如果有)
59
+ if (existsSync(p)) {
60
+ copyFileSync(p, backupPath());
61
+ }
62
+ mkdirSync(dir, { recursive: true });
63
+ writeFileSync(p, trimmed + '\n', 'utf8');
64
+ return { ok: true };
65
+ }
66
+ catch (e) {
67
+ return { ok: false, error: `写入失败: ${e.message}` };
68
+ }
69
+ }
70
+ /**
71
+ * 追加内容到项目 skill 末尾(以 \n\n 分隔)。
72
+ * 同样受 MAX_SKILL_CHARS 限制。
73
+ */
74
+ export function appendProjectSkill(addition) {
75
+ const existing = readProjectSkill() ?? '';
76
+ const trimmed = addition.trim();
77
+ if (!trimmed)
78
+ return { ok: false, error: '追加内容为空' };
79
+ const separator = existing ? '\n\n' : '';
80
+ const merged = existing + separator + trimmed;
81
+ return writeProjectSkill(merged);
82
+ }
83
+ /**
84
+ * 生成系统提示词注入段。
85
+ * 开关关闭或文件不存在 → 空串(零行为变化)。
86
+ */
87
+ export function buildProjectSkillSection() {
88
+ const content = readProjectSkill();
89
+ if (!content)
90
+ return '';
91
+ return [
92
+ '',
93
+ '',
94
+ '## Project Skill (project-specific knowledge)',
95
+ 'The following is project-specific knowledge maintained across sessions. Use it as context for all tasks in this project.',
96
+ '',
97
+ '<project-skill>',
98
+ content,
99
+ '</project-skill>',
100
+ '',
101
+ '### Project Skill 维护指南',
102
+ '你可以(也应该)在开发过程中持续更新这个 skill,让它越来越了解项目:',
103
+ '',
104
+ '**何时更新**:',
105
+ '- 发现项目特有的架构模式、设计决策或数据流',
106
+ '- 踩坑后总结出避坑指南(命名冲突、API 行为、构建陷阱等)',
107
+ '- 学到新的命名约定、测试规范、代码风格',
108
+ '- 完成重要重构或引入新模块后',
109
+ '- 用户纠正了你对项目的错误理解',
110
+ '',
111
+ '**更新什么**:',
112
+ '- 项目概述、技术栈、核心模块职责',
113
+ '- 常见坑点和解决方案',
114
+ '- 开发流程(构建、测试、部署命令)',
115
+ '- 关键 API 的使用方式和限制',
116
+ '- 设计决策的 why(不只是 what)',
117
+ '',
118
+ '**如何更新**:',
119
+ '- `project_skill_update(action="read")` — 查看当前内容',
120
+ '- `project_skill_update(action="update", content="...")` — 全量替换(适合大改)',
121
+ '- `project_skill_update(action="append", content="...")` — 追加到末尾(适合加新发现)',
122
+ '',
123
+ '**注意事项**:',
124
+ '- 保持精简,硬上限 6000 字符(约 1500 token)',
125
+ '- 写可操作的内容,避免空泛描述',
126
+ '- 定期整理,删除过时信息',
127
+ '- 更新前建议先 `read` 看一下现有内容,避免重复',
128
+ ].join('\n');
129
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * 项目 Skill 初始化器 - 派生子 agent 带工具深度探索项目,生成高质量的初始 skill
3
+ */
4
+ import { spawnAgent } from '../agent/spawn.js';
5
+ /**
6
+ * 子 agent 的系统提示后缀:角色与输出格式约束
7
+ */
8
+ const SKILL_INIT_SUFFIX = `You are a project exploration agent. Your task is to deeply understand this project and generate a concise, actionable "Project Skill" document.
9
+
10
+ ## Your Mission
11
+ Explore the project thoroughly using available tools, then produce a structured skill document that will help future AI agents work efficiently on this project.
12
+
13
+ ## Exploration Strategy
14
+ 1. **Start broad**: Use \`glob\` to understand the project structure
15
+ 2. **Read key files**: package.json, README.md, tsconfig.json, any config files
16
+ 3. **Understand architecture**: Use \`codegraph\` or \`grep\` to find main entry points, core modules, key interfaces
17
+ 4. **Identify patterns**: Look for naming conventions, directory structure patterns, common abstractions
18
+ 5. **Find pitfalls**: Check for complex configs, unusual dependencies, build quirks
19
+
20
+ ## Output Format
21
+ When you're done exploring, output the skill document between these exact delimiters:
22
+
23
+ \`\`\`skill-start
24
+ (your skill content here)
25
+ \`\`\`skill-end
26
+
27
+ The skill content should follow this structure (use Chinese for headings, adapt sections based on what you find):
28
+
29
+ ## 项目概述
30
+ - 一句话描述项目是什么、做什么
31
+ - 核心目标用户/场景
32
+
33
+ ## 技术栈
34
+ - 主要语言、框架、工具(附版本号)
35
+ - 关键技术选型理由(如果能从文档/代码推断)
36
+
37
+ ## 架构要点
38
+ - 核心模块及其职责(用 path/to/module 格式)
39
+ - 数据流向或调用链(如果有明显模式)
40
+ - 设计模式或架构模式(如果有)
41
+
42
+ ## 项目结构
43
+ - 关键目录及其用途
44
+ - 重要文件的位置
45
+
46
+ ## 开发流程
47
+ - 构建、测试、lint、部署命令
48
+ - 开发环境配置要点
49
+
50
+ ## 项目约定
51
+ - 命名规范(如果有明显模式)
52
+ - 代码风格(如果有 .eslintrc, .prettierrc 等)
53
+ - 测试策略(如果有测试)
54
+
55
+ ## 常见坑点
56
+ - 复杂的配置或构建步骤
57
+ - 容易踩坑的 API 或行为
58
+ - 需要特别注意的依赖版本问题
59
+
60
+ ## Guidelines
61
+ - 保持精简,总长度控制在 4000-5000 字符(硬上限 6000)
62
+ - 写可操作的内容,避免空泛描述(不要写"代码质量高"这种废话)
63
+ - 用具体路径和例子(不要写"有多个模块",要写"src/agent 负责 agent 循环")
64
+ - 如果某个 section 没有明显内容,可以省略或写"待补充"
65
+ - 重点是:让未来的 agent 能立即理解项目并开始有效工作
66
+ `;
67
+ /**
68
+ * 生成初始项目 Skill(使用子 agent 深度探索)
69
+ * @param existingSkill 已有的 skill 内容(如果有,子 agent 会在其基础上优化)
70
+ * @param signal AbortSignal
71
+ */
72
+ export async function generateInitialSkill(existingSkill, signal) {
73
+ const hasExisting = !!existingSkill;
74
+ let prompt;
75
+ if (hasExisting) {
76
+ prompt = `以下是当前的项目 Skill 内容,请深度探索项目并优化/完善它:
77
+
78
+ \`\`\`markdown
79
+ ${existingSkill}
80
+ \`\`\`
81
+
82
+ ## 优化方向
83
+ 1. 补充缺失的信息(架构要点、设计决策、调用链等)
84
+ 2. 修正过时或不准确的内容
85
+ 3. 让描述更具体(用实际路径和例子)
86
+ 4. 删除冗余或空泛的描述
87
+ 5. 如果发现新的坑点或约定,添加进去
88
+
89
+ 记住:保持精简,总长度控制在 4000-5000 字符。输出完整的优化后版本。`;
90
+ }
91
+ else {
92
+ prompt = `请深度探索当前项目,生成一份项目专属 Skill 文档。
93
+
94
+ ## 探索步骤
95
+ 1. 先用 glob 扫描项目结构(**/*.ts, **/*.json 等)
96
+ 2. 读取 package.json、README.md、tsconfig.json 等关键文件
97
+ 3. 用 codegraph 或 grep 找到主入口、核心模块、关键接口
98
+ 4. 理解架构模式、数据流向、调用关系
99
+ 5. 识别命名约定、代码风格、测试策略
100
+ 6. 找出可能的坑点(复杂配置、特殊依赖、构建陷阱)
101
+
102
+ ## 输出
103
+ 探索完成后,将完整的 skill 文档输出在 \`\`\`skill-start 和 \`\`\`skill-end 之间。
104
+
105
+ 记住:内容要精简、可操作、有具体路径和例子。总长度控制在 4000-5000 字符。`;
106
+ }
107
+ try {
108
+ const result = await spawnAgent({
109
+ prompt,
110
+ systemPromptSuffix: SKILL_INIT_SUFFIX,
111
+ tools: ['read_file', 'glob', 'grep', 'codegraph'],
112
+ maxSteps: 30,
113
+ signal,
114
+ });
115
+ if (!result.completed) {
116
+ return {
117
+ ok: false,
118
+ error: '子 agent 未完成(可能中断或超时)',
119
+ transcript: result.transcript,
120
+ };
121
+ }
122
+ if (!result.summary) {
123
+ return {
124
+ ok: false,
125
+ error: '子 agent 未返回内容',
126
+ transcript: result.transcript,
127
+ };
128
+ }
129
+ // 从 summary 中提取 skill 内容
130
+ const skillMatch = result.summary.match(/```skill-start\n([\s\S]*?)\n```skill-end/);
131
+ if (!skillMatch || !skillMatch[1]) {
132
+ // 如果没有找到 delimiters,尝试从最后一段 markdown 代码块提取
133
+ const codeBlockMatch = result.summary.match(/```(?:markdown|md)?\n([\s\S]*?)\n```/);
134
+ if (codeBlockMatch && codeBlockMatch[1]) {
135
+ return {
136
+ ok: true,
137
+ content: codeBlockMatch[1].trim(),
138
+ transcript: result.transcript,
139
+ };
140
+ }
141
+ // 如果连代码块都没有,直接用 summary(但去掉开头的探索日志)
142
+ const lines = result.summary.split('\n');
143
+ const skillStartIdx = lines.findIndex((l) => l.startsWith('## '));
144
+ if (skillStartIdx > 0) {
145
+ return {
146
+ ok: true,
147
+ content: lines.slice(skillStartIdx).join('\n').trim(),
148
+ transcript: result.transcript,
149
+ };
150
+ }
151
+ return {
152
+ ok: false,
153
+ error: '无法从子 agent 输出中提取 skill 内容',
154
+ transcript: result.transcript,
155
+ };
156
+ }
157
+ const content = skillMatch[1].trim();
158
+ // 检查长度
159
+ if (content.length > 6000) {
160
+ return {
161
+ ok: false,
162
+ error: `生成内容过长 (${content.length} 字符),请手动精简后重试`,
163
+ transcript: result.transcript,
164
+ };
165
+ }
166
+ return {
167
+ ok: true,
168
+ content,
169
+ transcript: result.transcript,
170
+ };
171
+ }
172
+ catch (e) {
173
+ return {
174
+ ok: false,
175
+ error: e instanceof Error ? e.message : '未知错误',
176
+ transcript: '',
177
+ };
178
+ }
179
+ }
@@ -0,0 +1,177 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { getSandboxRoot } from '../sandbox/root.js';
5
+ import { scanStaticFiles } from './static-files.js';
6
+ /** 内存缓存:当前 session 的快照(避免重复 IO) */
7
+ let currentSnapshot = null;
8
+ /** 计算 sandboxRoot 的 hash,用作目录名(避免路径特殊字符) */
9
+ export function hashRoot(root) {
10
+ return createHash('sha256').update(root).digest('hex').slice(0, 16);
11
+ }
12
+ /** 快照存储目录 */
13
+ function snapshotDir() {
14
+ const root = getSandboxRoot() ?? process.cwd();
15
+ return path.join(process.cwd(), '.mocode', 'projects', hashRoot(root));
16
+ }
17
+ /** 快照文件路径 */
18
+ function snapshotPath() {
19
+ return path.join(snapshotDir(), 'snapshot.json');
20
+ }
21
+ /** 从磁盘加载快照(不存在/损坏返 null) */
22
+ export function loadSnapshot() {
23
+ if (currentSnapshot)
24
+ return currentSnapshot;
25
+ const p = snapshotPath();
26
+ if (!existsSync(p))
27
+ return null;
28
+ try {
29
+ const raw = readFileSync(p, 'utf8');
30
+ const snap = JSON.parse(raw);
31
+ if (!snap || snap.version !== 1 || !snap.files)
32
+ return null;
33
+ currentSnapshot = snap;
34
+ return snap;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ /** 构建/刷新快照:扫描静态文件 + 提取结构摘要 */
41
+ export function buildSnapshot() {
42
+ const root = getSandboxRoot() ?? process.cwd();
43
+ const files = scanStaticFiles(root);
44
+ const structure = extractStructure(root, files);
45
+ const snap = {
46
+ version: 1,
47
+ root,
48
+ builtAt: new Date().toISOString(),
49
+ files,
50
+ structure,
51
+ };
52
+ // 落盘
53
+ const dir = snapshotDir();
54
+ mkdirSync(dir, { recursive: true });
55
+ writeFileSync(snapshotPath(), JSON.stringify(snap, null, 2), 'utf8');
56
+ currentSnapshot = snap;
57
+ return snap;
58
+ }
59
+ /** 获取当前快照(优先内存 → 磁盘 → 构建) */
60
+ export function getSnapshot() {
61
+ return loadSnapshot() ?? buildSnapshot();
62
+ }
63
+ /** 清空内存缓存(调试/测试用) */
64
+ export function clearSnapshotCache() {
65
+ currentSnapshot = null;
66
+ }
67
+ /**
68
+ * 从快照中查找文件(带 mtime 校验)。
69
+ * 返回 null 表示:文件不在快照中 / mtime 已变 / 快照不存在。
70
+ * 调用方应 fallback 到真实 readFile。
71
+ */
72
+ export function lookupSnapshotFile(absPath) {
73
+ const snap = loadSnapshot();
74
+ if (!snap)
75
+ return null;
76
+ // 转成相对路径(快照 key 是相对路径)
77
+ const root = snap.root;
78
+ if (!absPath.startsWith(root))
79
+ return null;
80
+ const relPath = path.relative(root, absPath).replace(/\\/g, '/');
81
+ const entry = snap.files[relPath];
82
+ if (!entry)
83
+ return null;
84
+ // mtime 校验:磁盘上的 mtime 必须与快照一致
85
+ try {
86
+ const st = statSync(absPath);
87
+ if (st.mtimeMs !== entry.mtime)
88
+ return null;
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ return { content: entry.content, mtime: entry.mtime };
94
+ }
95
+ /** 提取项目结构摘要(从静态文件 + 目录扫描) */
96
+ function extractStructure(root, files) {
97
+ const modules = [];
98
+ const entries = [];
99
+ const configFiles = [];
100
+ // 配置文件:直接看 files keys
101
+ for (const relPath of Object.keys(files)) {
102
+ if (isConfigFile(relPath)) {
103
+ configFiles.push(relPath);
104
+ }
105
+ }
106
+ // 入口文件:从 package.json 提取
107
+ const pkgEntry = files['package.json'];
108
+ if (pkgEntry) {
109
+ try {
110
+ const pkg = JSON.parse(pkgEntry.content);
111
+ if (typeof pkg.main === 'string')
112
+ entries.push(pkg.main);
113
+ if (typeof pkg.module === 'string')
114
+ entries.push(pkg.module);
115
+ if (pkg.bin) {
116
+ if (typeof pkg.bin === 'string')
117
+ entries.push(pkg.bin);
118
+ else if (typeof pkg.bin === 'object') {
119
+ for (const v of Object.values(pkg.bin)) {
120
+ if (typeof v === 'string')
121
+ entries.push(v);
122
+ }
123
+ }
124
+ }
125
+ }
126
+ catch {
127
+ // package.json 解析失败,跳过
128
+ }
129
+ }
130
+ // 模块:扫描顶层目录(排除 node_modules, .git 等)
131
+ try {
132
+ const items = readdirSync(root, { withFileTypes: true });
133
+ for (const item of items) {
134
+ if (!item.isDirectory())
135
+ continue;
136
+ if (item.name.startsWith('.'))
137
+ continue;
138
+ if (item.name === 'node_modules')
139
+ continue;
140
+ if (item.name === 'dist' || item.name === 'build')
141
+ continue;
142
+ modules.push(item.name);
143
+ }
144
+ }
145
+ catch {
146
+ // 目录扫描失败,留空
147
+ }
148
+ return { modules, entries, configFiles };
149
+ }
150
+ /** 判断是否为配置文件 */
151
+ function isConfigFile(relPath) {
152
+ const configPatterns = [
153
+ 'tsconfig.json',
154
+ 'jsconfig.json',
155
+ '.eslintrc',
156
+ '.eslintrc.js',
157
+ '.eslintrc.json',
158
+ '.eslintrc.yml',
159
+ '.prettierrc',
160
+ '.prettierrc.js',
161
+ '.prettierrc.json',
162
+ '.env.example',
163
+ 'jest.config.js',
164
+ 'jest.config.ts',
165
+ 'vitest.config.ts',
166
+ 'vite.config.ts',
167
+ 'next.config.js',
168
+ 'next.config.ts',
169
+ 'webpack.config.js',
170
+ 'rollup.config.js',
171
+ 'pyproject.toml',
172
+ 'setup.py',
173
+ 'go.mod',
174
+ 'Cargo.toml',
175
+ ];
176
+ return configPatterns.some((p) => relPath === p || relPath.endsWith('/' + p));
177
+ }
@@ -0,0 +1,87 @@
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * 静态项目文件白名单 + 扫描逻辑。
5
+ * 这些文件通常是项目元信息,变动频率低、体积小、跨 session 稳定。
6
+ */
7
+ /** 静态文件白名单(文件名或模式) */
8
+ const STATIC_FILE_PATTERNS = [
9
+ // 文档
10
+ /^README(\.[a-z]+)?(\.[a-z]+)?$/i, // README, README.md, README.zh-CN.md
11
+ 'CHANGELOG.md',
12
+ 'CONTRIBUTING.md',
13
+ 'LICENSE',
14
+ 'LICENSE.md',
15
+ 'LICENSE.txt',
16
+ // Node.js
17
+ 'package.json',
18
+ 'tsconfig.json',
19
+ 'jsconfig.json',
20
+ // Python
21
+ 'pyproject.toml',
22
+ 'setup.py',
23
+ 'setup.cfg',
24
+ 'requirements.txt',
25
+ 'Pipfile',
26
+ // Go
27
+ 'go.mod',
28
+ // Rust
29
+ 'Cargo.toml',
30
+ // 环境变量模板
31
+ '.env.example',
32
+ '.env.template',
33
+ ];
34
+ /**
35
+ * 判断文件名是否匹配白名单
36
+ */
37
+ function matchesPattern(filename) {
38
+ for (const p of STATIC_FILE_PATTERNS) {
39
+ if (typeof p === 'string') {
40
+ if (filename === p)
41
+ return true;
42
+ }
43
+ else {
44
+ if (p.test(filename))
45
+ return true;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ /**
51
+ * 扫描项目根目录,收集匹配白名单的静态文件。
52
+ * 只扫顶层(不递归),避免 node_modules 等大目录。
53
+ * @param root 项目根目录(绝对路径)
54
+ * @returns key = 相对路径(正斜杠),val = { mtime, content }
55
+ */
56
+ export function scanStaticFiles(root) {
57
+ const result = {};
58
+ let items;
59
+ try {
60
+ items = readdirSync(root, { withFileTypes: true });
61
+ }
62
+ catch {
63
+ return result;
64
+ }
65
+ for (const item of items) {
66
+ if (!item.isFile())
67
+ continue;
68
+ if (!matchesPattern(item.name))
69
+ continue;
70
+ const absPath = path.join(root, item.name);
71
+ try {
72
+ const stat = statSync(absPath);
73
+ // 体积限制:单文件 > 64KB 跳过(避免 README 巨长拖慢快照)
74
+ if (stat.size > 64 * 1024)
75
+ continue;
76
+ const content = readFileSync(absPath, 'utf8');
77
+ result[item.name] = {
78
+ mtime: stat.mtimeMs,
79
+ content,
80
+ };
81
+ }
82
+ catch {
83
+ // 读取失败:跳过
84
+ }
85
+ }
86
+ return result;
87
+ }
@@ -0,0 +1,22 @@
1
+ import { type ChatMessage } from '../llm/index.js';
2
+ /**
3
+ * 把会话历史渲染成静态文本进内容区(回滚 / 续接 / --resume 后复显上下文,仿 Claude Code):
4
+ * user→❯ 回显、assistant→正文(+ tool_calls 折叠成 ● 摘要行)、tool→↳ 结果预览;system 跳过。
5
+ * 思考段不持久(history 只存正文),故无思考折叠。渲染后续写位在末尾,紧接 enterInputMode 画输入框。
6
+ * 内容长于屏时 viewport 显尾(最近轮次),PgUp 可看更早——与流式态一致。
7
+ * user 多模态:用 textOf 取 text parts;若侧 channel messageAttachments 有原文件名则追加 chip 行
8
+ * (避免 base64 解码不可逆,旧 session 没侧 channel 时只显文本,文件名 fallback 到 image/* mime)。
9
+ *
10
+ * 折叠策略:遇到 assistant + tool_calls 不立即打 ● 行,而是累积到 batchEntries;
11
+ * 跟随的连续 tool 消息按 tool_call_id 反查填 resultSummary;遇下一个非 tool 消息(或末尾)时,
12
+ * 用 batch.writeSummaryOnly 出单行摘要(与实时 runAgent 同一渲染器,UI 一致)。
13
+ * 回放默认全折叠;用户可鼠标点击摘要行展开(由 BatchRenderer 接管,见 ui/batch.ts)。
14
+ */
15
+ export declare function renderHistory(history: ChatMessage[]): void;
16
+ /**
17
+ * 交互式 REPL:全屏 TUI(alt screen + 固定底栏)。INPUT 态底栏=状态行+输入框(raw mode 等按键);
18
+ * 提交后 enterRunningMode(底栏改 dim 占位、光标回内容续写位),命令分发与 runAgent 的流式输出经
19
+ * contentWrite 落入内容区(滚动区域内自动滚动,底栏不动)。history 由本模块持有,在轮次间持久;
20
+ * agent 只读取并追加(+ 经 session/ 压缩)。每轮成功结束后自动落盘,退出后可用 --resume / /resume 续接。
21
+ */
22
+ export declare function startRepl(initialHistory?: ChatMessage[], sessionId?: string, updateNotice?: string | null, sandboxRootOverride?: string): Promise<void>;