minimal-agent 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -72
- package/package.json +18 -13
- package/plugins/ralph-wiggum/plugin.js +205 -0
- package/plugins/ralph-wiggum/src/goalState.js +260 -0
- package/plugins/ralph-wiggum/src/{sentinels.ts → sentinels.js} +4 -7
- package/plugins/ralph-wiggum/src/stopHookRunner.js +104 -0
- package/plugins/ralph-wiggum/src/verificationGate.js +202 -0
- package/plugins/workflow-runner/commands/workflow.md +13 -3
- package/plugins/workflow-runner/{plugin.ts → plugin.js} +20 -26
- package/plugins/workflow-runner/src/expressions.js +369 -0
- package/plugins/workflow-runner/src/index.js +216 -0
- package/plugins/workflow-runner/src/loader.js +183 -0
- package/plugins/workflow-runner/src/runner.js +290 -0
- package/plugins/workflow-runner/src/stepExecutors/assert.js +28 -0
- package/plugins/workflow-runner/src/stepExecutors/llm.js +44 -0
- package/plugins/workflow-runner/src/stepExecutors/skill.js +103 -0
- package/plugins/workflow-runner/src/stepExecutors/{tool.ts → tool.js} +19 -25
- package/plugins/workflow-runner/src/types.js +59 -0
- package/plugins/workflow-runner/src/{workflowState.ts → workflowState.js} +21 -40
- package/src/bootstrap/cwdArg.js +22 -0
- package/src/bootstrap/workingDir.js +31 -0
- package/src/cli/configWizard.js +272 -0
- package/src/cli/print.js +197 -0
- package/src/config/configFile.js +78 -0
- package/src/config.js +118 -0
- package/src/context/compact.js +357 -0
- package/src/context/microCompactLite.js +151 -0
- package/src/context/persistContext.js +109 -0
- package/src/context/reactiveCompact.js +121 -0
- package/src/context/sessionPath.js +58 -0
- package/src/context/snipCompact.js +112 -0
- package/src/context/tokenCounter.js +66 -0
- package/src/llm/client.js +182 -0
- package/src/loop.js +230 -0
- package/src/main.js +116 -0
- package/src/plugin-sdk.js +24 -0
- package/src/plugins/commandRouter.js +169 -0
- package/src/plugins/hookEngine.js +258 -0
- package/src/plugins/pluginApi.js +23 -0
- package/src/plugins/pluginLoader.js +71 -0
- package/src/plugins/pluginRunner.js +65 -0
- package/src/plugins/transcript.js +171 -0
- package/src/prompts/projectInstructions.js +48 -0
- package/src/prompts/skillList.js +126 -0
- package/src/prompts/system.js +155 -0
- package/src/session/runTurn.js +41 -0
- package/src/session/sessionState.js +19 -0
- package/src/tools/bash/bash.js +352 -0
- package/src/tools/bash/semantics.js +85 -0
- package/src/tools/bash/warnings.js +98 -0
- package/src/tools/edit/edit.js +253 -0
- package/src/tools/edit/multi-edit.js +155 -0
- package/src/tools/glob/glob.js +97 -0
- package/src/tools/grep/grep.js +185 -0
- package/src/tools/grep/rgPath.js +173 -0
- package/src/tools/index.js +94 -0
- package/src/tools/read/read.js +209 -0
- package/src/tools/shared/fileState.js +61 -0
- package/src/tools/shared/fileUtils.js +281 -0
- package/src/tools/shared/schemas.js +16 -0
- package/src/tools/types.js +21 -0
- package/src/tools/webbrowser/browser.js +55 -0
- package/src/tools/webbrowser/webbrowser.js +194 -0
- package/src/tools/webfetch/preapproved.js +267 -0
- package/src/tools/webfetch/webfetch.js +317 -0
- package/src/tools/websearch/websearch.js +161 -0
- package/src/tools/write/write.js +125 -0
- package/src/types/turndown.d.ts +23 -0
- package/src/types.js +16 -0
- package/src/ui/App.js +37 -0
- package/src/ui/InputBox.js +240 -0
- package/src/ui/MessageList.js +28 -0
- package/src/ui/Root.js +70 -0
- package/src/ui/StatusLine.js +41 -0
- package/src/ui/ToolStatus.js +11 -0
- package/src/ui/hooks/useChat.js +234 -0
- package/src/ui/hooks/usePasteHandler.js +137 -0
- package/src/ui/hooks/useTextBuffer.js +55 -0
- package/src/ui/hooks/useTokenUsage.js +30 -0
- package/src/ui/textBuffer.js +217 -0
- package/src/utils/packageRoot.js +37 -0
- package/src/utils/resourcePaths.js +49 -0
- package/src/utils/zodToJson.js +29 -0
- package/dist/main.js +0 -5315
- package/plugins/ralph-wiggum/plugin.ts +0 -275
- package/plugins/ralph-wiggum/scripts/setup-ralph-loop.sh +0 -203
- package/plugins/ralph-wiggum/src/goalState.ts +0 -310
- package/plugins/ralph-wiggum/src/stopHookRunner.ts +0 -136
- package/plugins/ralph-wiggum/src/verificationGate.ts +0 -252
- package/plugins/ralph-wiggum/test/goalState.test.ts +0 -410
- package/plugins/ralph-wiggum/test/verificationGate.test.ts +0 -122
- package/plugins/workflow-runner/src/expressions.ts +0 -371
- package/plugins/workflow-runner/src/index.ts +0 -194
- package/plugins/workflow-runner/src/loader.ts +0 -193
- package/plugins/workflow-runner/src/runner.ts +0 -313
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +0 -30
- package/plugins/workflow-runner/src/stepExecutors/llm.ts +0 -54
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +0 -115
- package/plugins/workflow-runner/src/types.ts +0 -183
- package/plugins/workflow-runner/test/cli.e2e.test.ts +0 -114
- package/plugins/workflow-runner/test/e2e.test.ts +0 -268
- package/plugins/workflow-runner/test/expressions.test.ts +0 -140
- package/plugins/workflow-runner/test/fixtures/cli-e2e.yaml +0 -27
- package/plugins/workflow-runner/test/fixtures/hello-workflow.yaml +0 -49
- package/plugins/workflow-runner/test/graceful.test.ts +0 -139
- package/plugins/workflow-runner/test/loader.test.ts +0 -216
- package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +0 -230
- package/plugins/workflow-runner/test/runner.test.ts +0 -511
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/prompts/skillList.ts —— Skill 列表生成(积极使用版)
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 设计原则(v3 重构):
|
|
6
|
+
* 1. System Prompt 里只注入 frontmatter 元数据轻量列表
|
|
7
|
+
* → 让 T 阶段的 LLM 知道"有哪些技能可用 + 何时触发 + 是什么类型"
|
|
8
|
+
* → 不加载完整 SKILL.md(省 token,按需加载)
|
|
9
|
+
* 2. LLM 决定使用某个 skill 时,用 Read 工具读取对应 SKILL.md
|
|
10
|
+
* → 类似 lazy loading:知道存在 → 需要时才加载详情
|
|
11
|
+
* 3. 格式对齐 Tools 的风格:每个 skill 固定 3 行信息
|
|
12
|
+
* 4. 完全依赖 frontmatter 声明(name / description / type / triggers),
|
|
13
|
+
* 不再做任何启发式推断。新增 skill 时必须在 frontmatter 显式声明
|
|
14
|
+
* type 和 triggers 字段(缺省时给出占位提示)。
|
|
15
|
+
* ============================================================
|
|
16
|
+
*/
|
|
17
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { getResourceSearchPaths } from '../utils/resourcePaths.js';
|
|
20
|
+
function stripQuotes(s) {
|
|
21
|
+
const trimmed = s.trim();
|
|
22
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
23
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
24
|
+
return trimmed.slice(1, -1);
|
|
25
|
+
}
|
|
26
|
+
return trimmed;
|
|
27
|
+
}
|
|
28
|
+
function parseFrontmatter(content, sourceDir) {
|
|
29
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
30
|
+
if (!match)
|
|
31
|
+
return null;
|
|
32
|
+
const frontmatter = match[1];
|
|
33
|
+
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
34
|
+
const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
|
|
35
|
+
const typeMatch = frontmatter.match(/^type:\s*(.+)$/m);
|
|
36
|
+
const triggersMatch = frontmatter.match(/^triggers:\s*(.+)$/m);
|
|
37
|
+
if (!nameMatch || !descMatch)
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
name: stripQuotes(nameMatch[1]),
|
|
41
|
+
description: stripQuotes(descMatch[1]),
|
|
42
|
+
type: typeMatch ? stripQuotes(typeMatch[1]) : undefined,
|
|
43
|
+
triggers: triggersMatch ? stripQuotes(triggersMatch[1]) : undefined,
|
|
44
|
+
sourceDir,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function getSkillList() {
|
|
48
|
+
const skills = [];
|
|
49
|
+
const seenNames = new Set();
|
|
50
|
+
// 双源扫描:cwd 优先 + packageRoot fallback;按数组顺序处理,已注册 name 跳过
|
|
51
|
+
const searchPaths = getResourceSearchPaths('skills', import.meta.url);
|
|
52
|
+
for (const root of searchPaths) {
|
|
53
|
+
try {
|
|
54
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (!entry.isDirectory())
|
|
57
|
+
continue;
|
|
58
|
+
const sourceDir = join(root, entry.name);
|
|
59
|
+
const skillPath = join(sourceDir, 'SKILL.md');
|
|
60
|
+
try {
|
|
61
|
+
const content = await readFile(skillPath, 'utf8');
|
|
62
|
+
const meta = parseFrontmatter(content, sourceDir);
|
|
63
|
+
if (meta && !seenNames.has(meta.name)) {
|
|
64
|
+
seenNames.add(meta.name);
|
|
65
|
+
skills.push(meta);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return skills;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 生成积极的 skill 列表提示词。
|
|
79
|
+
*
|
|
80
|
+
* 输出格式(每个 skill 固定 3 行):
|
|
81
|
+
* ## /skill-name —— 一句话标题
|
|
82
|
+
* - **触发时机**: 用户说 "xxx"、"yyy" 时使用
|
|
83
|
+
* - **功能**: 一句话描述这个 skill 做什么
|
|
84
|
+
* - **类型**: prompt / local / local-jsx
|
|
85
|
+
*
|
|
86
|
+
* 关键设计决策:
|
|
87
|
+
* - 不加载 SKILL.md 全文(节省 ~1500+ tokens/skill)
|
|
88
|
+
* - 只输出 name + description(每个 skill ~40 tokens)
|
|
89
|
+
* - 明确告诉 LLM "决定用时再 Read SKILL.md"
|
|
90
|
+
*/
|
|
91
|
+
export function formatSkillHint(skills) {
|
|
92
|
+
if (skills.length === 0) {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
const lines = [];
|
|
96
|
+
// ---- 标题段 ----
|
|
97
|
+
lines.push('# 可用技能(/命令)');
|
|
98
|
+
lines.push('');
|
|
99
|
+
lines.push('以下是当前可用的技能(**双源**:用户项目目录 + minimal-agent 包目录,同名时项目优先)。**当用户意图匹配某个技能的触发时机时,你应该主动使用它**。');
|
|
100
|
+
lines.push('在使用前,先用 Read 工具读取下方每个技能卡片里的 `**技能目录**` 路径 + `/SKILL.md` 了解完整流程。');
|
|
101
|
+
lines.push('');
|
|
102
|
+
// ---- 每个技能的卡片 ----
|
|
103
|
+
for (const skill of skills) {
|
|
104
|
+
const type = skill.type ?? 'prompt(展开为系统提示词,指导模型执行复杂任务)';
|
|
105
|
+
const triggers = skill.triggers ?? '(详见 SKILL.md)';
|
|
106
|
+
lines.push(`## ${skill.name}`);
|
|
107
|
+
lines.push(`- **触发时机**: ${triggers}`);
|
|
108
|
+
lines.push(`- **功能**: ${skill.description}`);
|
|
109
|
+
lines.push(`- **类型**: ${type}`);
|
|
110
|
+
lines.push(`- **技能目录**: \`${skill.sourceDir}\``);
|
|
111
|
+
lines.push('');
|
|
112
|
+
}
|
|
113
|
+
// ---- 使用流程指引 ----
|
|
114
|
+
lines.push('## 技能调用流程');
|
|
115
|
+
lines.push('');
|
|
116
|
+
lines.push('```');
|
|
117
|
+
lines.push('1. 在 T 阶段判断用户意图是否匹配某个技能的触发时机');
|
|
118
|
+
lines.push('2. 匹配 → 在 A 阶段用 Read 工具读取该技能卡片的"技能目录"/SKILL.md');
|
|
119
|
+
lines.push('3. 按照 SKILL.md 的 Quick Reference 和流程执行');
|
|
120
|
+
lines.push('4. 技能脚本(scripts/)与 SKILL.md 位于同一目录下,用绝对路径执行');
|
|
121
|
+
lines.push('5. 如果技能类型是 prompt,将其内容作为额外上下文指导后续操作');
|
|
122
|
+
lines.push('```');
|
|
123
|
+
lines.push('');
|
|
124
|
+
lines.push('> 💡 技能是工具的组合拳。单个工具是基础操作;技能是针对特定场景的标准化工作流。');
|
|
125
|
+
return lines.join('\n');
|
|
126
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/prompts/system.ts —— 系统提示词
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 系统提示词(system message)是放在历史最前面的固定指令,告诉模型:
|
|
6
|
+
* - 自己的身份
|
|
7
|
+
* - 当前工作环境(cwd / OS / 时间)
|
|
8
|
+
* - 可用的工具及使用建议
|
|
9
|
+
* - 可用技能及调用时机
|
|
10
|
+
* - 输出风格偏好
|
|
11
|
+
*
|
|
12
|
+
* 与 kakadeai 的 systemPromptSection() 复杂的分段缓存不同,
|
|
13
|
+
* 最小化版只输出一段纯文本,简单直接。
|
|
14
|
+
* ============================================================
|
|
15
|
+
*/
|
|
16
|
+
import { homedir } from 'node:os';
|
|
17
|
+
import { loadProjectInstructions } from './projectInstructions.js';
|
|
18
|
+
import { getSkillList, formatSkillHint } from './skillList.js';
|
|
19
|
+
/**
|
|
20
|
+
* 生成系统提示词。
|
|
21
|
+
*
|
|
22
|
+
* @param cwd 当前工作目录
|
|
23
|
+
* @param tools 可用工具数组(用于在 prompt 里列出工具名)
|
|
24
|
+
*/
|
|
25
|
+
export async function getSystemPrompt(cwd, tools) {
|
|
26
|
+
const toolList = tools.map((t) => ` - ${t.name}`).join('\n');
|
|
27
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
28
|
+
// 实时扫描 skills 目录(无缓存)—— 每次重建 system prompt 时都读最新的 SKILL.md,
|
|
29
|
+
// 保证用户中途复制新 skill 进去也能立刻识别
|
|
30
|
+
const skills = await getSkillList();
|
|
31
|
+
const skillHint = formatSkillHint(skills);
|
|
32
|
+
return `你是 minimal-agent,一个最小化的 AI 编程助手。
|
|
33
|
+
你运行在用户的本地终端里,可以读取、修改、创建文件,可以搜索文件名和文件内容,可以联网搜索最新信息。
|
|
34
|
+
|
|
35
|
+
# 工作环境
|
|
36
|
+
- 当前工作目录: ${cwd}
|
|
37
|
+
- 用户主目录: ${homedir()}
|
|
38
|
+
- 平台: ${process.platform}
|
|
39
|
+
- 当前日期: ${today}
|
|
40
|
+
|
|
41
|
+
# 时间感知规则(重要!搜索前必读)
|
|
42
|
+
- **每次执行 WebSearch 前,必须先确认当前日期**
|
|
43
|
+
- 当前日期:${today}
|
|
44
|
+
- 搜索时必须在 query 末尾附带日期限制,如 "... as of [日期]" 或 "... ${today}"
|
|
45
|
+
- 例如搜索日本政策时写:"日本入境政策 ${today}"
|
|
46
|
+
- 禁止使用 "现在"、"最近"、"最新" 等模糊时间词——必须用具体日期
|
|
47
|
+
|
|
48
|
+
# 可用工具
|
|
49
|
+
${toolList}
|
|
50
|
+
|
|
51
|
+
# 工具使用规范
|
|
52
|
+
- 修改既有文件之前,必须用 Read 工具读取相关代码的最新内容。
|
|
53
|
+
"我记得"、"我之前看过"、"应该差不多"都不算读过——必须在本轮任务中实际调用 Read。
|
|
54
|
+
尤其当你要参考某个文件的写法来实现类似功能时,必须先重读该文件的关键部分(函数实现、判断逻辑、数据流),理解清楚后再动手。
|
|
55
|
+
- Edit 工具的 old_string 必须在文件中唯一;不唯一时请扩大上下文或显式 replace_all=true。
|
|
56
|
+
- 同一文件需要修改多处(3 处及以上)时优先用 MultiEdit:所有 edit 按顺序在内存中应用,**全部成功才落盘**;任一失败磁盘不动,避免中间状态污染。单点修改继续用 Edit。
|
|
57
|
+
- Write 覆盖既有文件前同样必须先 Read(与 Edit 对称,未先 Read 会被拒绝并提示"请先 Read");写新文件无此要求。
|
|
58
|
+
- 创建新文件用 Write,或 Edit 时 old_string 传空字符串。
|
|
59
|
+
- 找文件用 Glob("**/*.ts"),找文件内容用 Grep(基于 ripgrep)。
|
|
60
|
+
- 当用户问到训练截止后才出现的信息(最新版本号、近期新闻、第三方 API 文档)时用 WebSearch;优先精确的自然语言查询。
|
|
61
|
+
- 当需要执行终端命令(安装依赖、运行测试/构建/lint、git 操作、文件系统操作等)时用 Bash。
|
|
62
|
+
⚠️ **Bash 是万能工具,但必须作为最后手段**:当 Read/Edit/Write/Glob/Grep 等专用工具无法完成任务时,才考虑用 Bash。
|
|
63
|
+
不要用 Bash 执行 find/grep/cat/head/tail/sed/awk/echo 等命令来替代专用工具——专用工具提供更好的用户体验和可审计性。
|
|
64
|
+
Bash 有安全黑名单(rm -rf /、mkfs、shutdown 等危险命令会被拦截),但仍需谨慎:
|
|
65
|
+
先确认命令无害再执行,避免不可逆操作(如 git push --force、git reset --hard)。
|
|
66
|
+
长时间运行的命令(npm install、bun test)注意超时设置;需要交互输入的命令不要用 Bash(用 Write 写脚本代替)。
|
|
67
|
+
Bash 已识别"信息性退出码":grep/rg/find/diff/test 的 exit=1(无匹配 / 部分不可访问 / 有差异 / 条件假)会自动判为成功,不要因 1 而重试。
|
|
68
|
+
破坏性命令(git reset --hard / git push -f / rm -rf 等)会被 Bash 主动在输出头部加 ⚠️ 警告(不拦截),看到时应向用户确认意图。
|
|
69
|
+
- 当需要获取网页静态文本内容(抓取文档、读取文章)时用 WebFetch。
|
|
70
|
+
WebBrowser 依赖可选包(playwright-core + chromium)—— **默认假定未安装**,
|
|
71
|
+
仅在 WebFetch 明确无法满足(如需要 JS 渲染后的内容、点击按钮、填表单、截图)时再尝试。
|
|
72
|
+
如果 WebBrowser 报"无法启动浏览器/缺少依赖",不要重试,改回 WebFetch 或告知用户安装依赖。
|
|
73
|
+
- 一次回答里可以并行调用多个只读工具(Read/Glob/Grep/WebSearch/WebFetch),写工具(Edit/Write/Bash)请逐个执行。
|
|
74
|
+
|
|
75
|
+
# 技能系统(积极使用)
|
|
76
|
+
${skillHint}
|
|
77
|
+
|
|
78
|
+
# 插件系统(被动触发)
|
|
79
|
+
- minimal-agent 在 \`plugins/\` 目录下加载用户安装的插件,每个插件可暴露形如 \`/<cmd>\` 的命令。
|
|
80
|
+
与 skill 不同,**插件命令必须由用户主动输入才触发**——你**不要**主动调用或模拟这些命令,
|
|
81
|
+
也不要在工具调用里假装在跑插件。
|
|
82
|
+
- 当某个插件命令在执行中(你会在当轮用户消息里看到该插件注入的上下文 / 阶段标记 / 哨兵约定),
|
|
83
|
+
请严格按照该插件给出的指引行动,不要绕开它的契约。
|
|
84
|
+
- 在**常规对话**(无插件注入的阶段标记 / 哨兵约定)中,不要凭空使用这些标记或哨兵词。
|
|
85
|
+
|
|
86
|
+
# 文件读取规范(重要!防止上下文污染)
|
|
87
|
+
## 读取策略(按场景选择)
|
|
88
|
+
### 场景 1:不确定文件内容
|
|
89
|
+
→ 先 Grep("关键字", path="文件路径") 定位
|
|
90
|
+
→ 再 Read(file_path, offset=X, limit=Y) 读指定区域
|
|
91
|
+
|
|
92
|
+
### 场景 2:知道要改哪个函数
|
|
93
|
+
→ 直接 Read(file_path, offset=1, limit=400) 先读前 400 行
|
|
94
|
+
→ 或者用 Grep 找到行号后 Read(file_path, offset=行号, limit=200)
|
|
95
|
+
|
|
96
|
+
### 场景 3:大文件(>256KB)
|
|
97
|
+
→ 不要一次性读,用 Grep 定位关键部分
|
|
98
|
+
→ 分多次小范围 Read,每次不超过 200 行
|
|
99
|
+
→ 例如:Read(file, offset=1, limit=100) → Read(file, offset=101, limit=100)
|
|
100
|
+
|
|
101
|
+
### 场景 4:需要看 imports/exports
|
|
102
|
+
→ Read(file, offset=1, limit=50) 只读文件头部
|
|
103
|
+
|
|
104
|
+
## 警惕信号
|
|
105
|
+
- 遇到 "文件过大" 错误 → 说明你读了不该读的大文件,改用分段读
|
|
106
|
+
- 遇到 "输出超过 30000 字符" → 说明单次读太多,改小 limit
|
|
107
|
+
|
|
108
|
+
# 输出风格
|
|
109
|
+
- 用中文回答用户。
|
|
110
|
+
- 简明扼要,避免重复解释代码做了什么 —— 让代码自身说话。
|
|
111
|
+
- 报错时给出根因,不要只复述错误消息。
|
|
112
|
+
- 不主动跑测试或 typecheck,除非用户要求。
|
|
113
|
+
|
|
114
|
+
# 行为约束
|
|
115
|
+
- 没有用户许可不要执行破坏性操作(rm -rf / git reset --hard / git push --force 等)。
|
|
116
|
+
- 如果用户的请求不清晰,先用一句话澄清再动手。
|
|
117
|
+
- 任务完成后用一两句话总结改了什么,不要长篇大论。
|
|
118
|
+
|
|
119
|
+
# 中断支持
|
|
120
|
+
- 用户可以随时按 ESC 或 Ctrl+C 中断你的工作。
|
|
121
|
+
- 中断后等待用户输入新任务,不要自行继续执行被中断的操作。
|
|
122
|
+
- 如果用户输入了新任务,直接响应新任务,不必提及之前被中断的操作。
|
|
123
|
+
|
|
124
|
+
# 任务识别
|
|
125
|
+
- 用户开始新任务时("帮我做 X"、"这次做 YYY"),不要继续之前未完成的旧任务
|
|
126
|
+
- 用户说"继续"、"接着做"、"再加一个"时,保持当前任务上下文
|
|
127
|
+
- 用户说"重新开始"、"不算了,做 X"时,立即放下旧任务
|
|
128
|
+
- 意图模糊时,先用一句话澄清:"你是继续刚才的,还是开始新的?"`;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* 生成完整系统提示词:getSystemPrompt + 项目级 minimal-agent.md 指令。
|
|
132
|
+
*
|
|
133
|
+
* 合并到**单条** system message,避免某些 provider(如 MiniMax)多 system
|
|
134
|
+
* message 时返回 400 "invalid chat setting"。启动时(main.tsx buildInitialHistory)
|
|
135
|
+
* 和 /new(useChat.clearHistory)都用这个 helper,确保两处行为一致。
|
|
136
|
+
*/
|
|
137
|
+
export async function buildFullSystemPrompt(cwd, tools) {
|
|
138
|
+
let content = await getSystemPrompt(cwd, tools);
|
|
139
|
+
const projectInstructions = await loadProjectInstructions(cwd);
|
|
140
|
+
if (projectInstructions) {
|
|
141
|
+
content += [
|
|
142
|
+
'',
|
|
143
|
+
'--- 项目指令(minimal-agent.md) ---',
|
|
144
|
+
'',
|
|
145
|
+
'⚠️ 时效性声明:',
|
|
146
|
+
'- 以下内容来源于项目根目录的 `minimal-agent.md` 文件,具有时效性,可能已过时。',
|
|
147
|
+
'- 它是参考线索,不是绝对真理。当它与实际代码或文件内容冲突时,以代码为准。',
|
|
148
|
+
'- 涉及代码和项目文件的任何操作前,必须先用 Read / Grep 工具确认当前代码的实际状态,再基于最新信息做决策,不要仅凭此文档的描述操作。',
|
|
149
|
+
'- 如果你发现 `minimal-agent.md` 的描述与当前代码不一致,应主动用 Write 工具更新 `minimal-agent.md` 使其与代码保持同步。',
|
|
150
|
+
'',
|
|
151
|
+
projectInstructions,
|
|
152
|
+
].join('\n');
|
|
153
|
+
}
|
|
154
|
+
return content;
|
|
155
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/session/runTurn.ts —— Headless 单 turn 执行器
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 把"读 session 文件 → 跑 runQuery → 写 session 文件"封装成单一入口。
|
|
6
|
+
* PR 4 HTTP server 的每个请求 handler 用它即可,无需自己拼装。
|
|
7
|
+
*
|
|
8
|
+
* 调用方契约:
|
|
9
|
+
* - sessionFile: per-chat 隔离的上下文文件路径(参见 sessionPath.sessionFileFor)
|
|
10
|
+
* - systemPrompt: 当 loadContext 返回 null(首次会话)时用它建初始 history;
|
|
11
|
+
* 未提供则用空数组开局(少数 case 才合理,例如不需要 system prompt)
|
|
12
|
+
* - sessionState: 多并发场景必须 per-request 创建一份,避免不同 chat 串扰
|
|
13
|
+
* - signal: 调用方持有 AbortController,断开/超时时主动 abort
|
|
14
|
+
*
|
|
15
|
+
* 落盘策略:
|
|
16
|
+
* 用 try/finally 包住 runQuery;不管成功、抛错、还是被中断都保存当前
|
|
17
|
+
* history。中断时 loop.ts 内部会先把"用户按下了 ESC/Ctrl+C"标记
|
|
18
|
+
* 消息追加到 history,所以落盘的也是合法且完整的会话状态。
|
|
19
|
+
* ============================================================
|
|
20
|
+
*/
|
|
21
|
+
import { loadContext, saveContext } from '../context/persistContext.js';
|
|
22
|
+
import { runWithPlugins } from '../plugins/pluginRunner.js';
|
|
23
|
+
export async function* runTurn(opts) {
|
|
24
|
+
const loaded = await loadContext(opts.sessionFile);
|
|
25
|
+
const history = loaded ??
|
|
26
|
+
(opts.systemPrompt
|
|
27
|
+
? [{ role: 'system', content: opts.systemPrompt }]
|
|
28
|
+
: []);
|
|
29
|
+
try {
|
|
30
|
+
yield* runWithPlugins(opts.userInput, {
|
|
31
|
+
provider: opts.provider,
|
|
32
|
+
history,
|
|
33
|
+
signal: opts.signal,
|
|
34
|
+
maxTurns: opts.maxTurns,
|
|
35
|
+
sessionState: opts.sessionState,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await saveContext(history, opts.sessionFile);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/session/sessionState.ts —— 会话级隔离状态
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 把 reactiveCompact + microCompactLite 的可注入状态打包。
|
|
6
|
+
*
|
|
7
|
+
* TUI / print 不需要它(不传 → 默认走模块单例,行为不变);
|
|
8
|
+
* HTTP server per-request 调 createSessionState() 拿独立一份,
|
|
9
|
+
* 让每个 chatId 的工具结果去重缓存、反应式自救标记互不串扰。
|
|
10
|
+
* ============================================================
|
|
11
|
+
*/
|
|
12
|
+
import { createMicroCompactState, } from '../context/microCompactLite.js';
|
|
13
|
+
import { createReactiveCompactState, } from '../context/reactiveCompact.js';
|
|
14
|
+
export function createSessionState() {
|
|
15
|
+
return {
|
|
16
|
+
reactive: createReactiveCompactState(),
|
|
17
|
+
microCompact: createMicroCompactState(),
|
|
18
|
+
};
|
|
19
|
+
}
|