minimal-agent 0.11.0 → 0.13.0
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/package.json +5 -1
- package/plugins/workflow-runner/plugin.js +27 -15
- package/plugins/workflow-runner/plugin.ts +34 -20
- package/plugins/workflow-runner/src/agentCommon.js +13 -12
- package/plugins/workflow-runner/src/agentCommon.ts +22 -13
- package/plugins/workflow-runner/src/expressions.js +29 -8
- package/plugins/workflow-runner/src/expressions.ts +28 -10
- package/plugins/workflow-runner/src/index.js +50 -20
- package/plugins/workflow-runner/src/index.ts +75 -31
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +2 -1
- package/plugins/workflow-runner/src/recovery.js +9 -2
- package/plugins/workflow-runner/src/recovery.ts +9 -2
- package/plugins/workflow-runner/src/runner.js +31 -5
- package/plugins/workflow-runner/src/runner.ts +33 -5
- package/plugins/workflow-runner/src/stepExecutors/assert.js +2 -1
- package/plugins/workflow-runner/src/stepExecutors/assert.ts +3 -1
- package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
- package/plugins/workflow-runner/src/stepExecutors/skill.ts +12 -7
- package/plugins/workflow-runner/src/stepExecutors/tool.js +91 -61
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +111 -71
- package/plugins/workflow-runner/src/stepExecutors/verify.js +6 -3
- package/plugins/workflow-runner/src/stepExecutors/verify.ts +7 -3
- package/plugins/workflow-runner/src/taskIdentity.js +197 -21
- package/plugins/workflow-runner/src/taskIdentity.ts +201 -20
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.js +113 -7
- package/plugins/workflow-runner/src/types.ts +137 -14
- package/plugins/workflow-runner/src/workflowState.js +455 -54
- package/plugins/workflow-runner/src/workflowState.ts +508 -63
- package/plugins/workflow-runner/test/cli.e2e.test.ts +20 -0
- package/plugins/workflow-runner/test/context-files.test.ts +47 -16
- package/plugins/workflow-runner/test/e2eNewLayout.test.ts +314 -0
- package/plugins/workflow-runner/test/engineRobustness.test.ts +9 -1
- package/plugins/workflow-runner/test/expressions.test.ts +42 -0
- package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
- package/plugins/workflow-runner/test/illustrate.load.test.ts +2 -2
- package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
- package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +19 -0
- package/plugins/workflow-runner/test/ppt.load.test.ts +15 -15
- package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +3 -2
- package/plugins/workflow-runner/test/research.load.test.ts +328 -0
- package/plugins/workflow-runner/test/runnerPreflight.test.ts +68 -11
- package/plugins/workflow-runner/test/stateLifecycle.test.ts +60 -12
- package/plugins/workflow-runner/test/taskArbitration.test.ts +19 -3
- package/plugins/workflow-runner/test/taskLifecycle.test.ts +61 -7
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/plugins/workflow-runner/test/webppt.load.test.ts +4 -4
- package/plugins/workflow-runner/test/word.load.test.ts +9 -9
- package/skills/skill-creator/scripts/run_eval.py +3 -2
- package/src/cli/print.js +36 -4
- package/src/cli/print.ts +42 -4
- package/src/cli/streamJson.js +19 -4
- package/src/cli/streamJson.ts +17 -4
- package/src/config/configDefaults.js +25 -0
- package/src/config/configDefaults.ts +33 -0
- package/src/config/configFile.js +2 -21
- package/src/config/configFile.ts +11 -24
- package/src/config/configManagement.js +1 -1
- package/src/config/configManagement.ts +2 -2
- package/src/config/configService.js +24 -0
- package/src/config/configService.ts +23 -0
- package/src/context/compact.js +4 -0
- package/src/context/compact.ts +4 -0
- package/src/context/contextBundle.js +38 -13
- package/src/context/contextBundle.ts +47 -13
- package/src/harness/stallGuard.js +19 -4
- package/src/harness/stallGuard.ts +30 -7
- package/src/infra/verification.js +25 -18
- package/src/infra/verification.ts +50 -18
- package/src/loop.js +95 -11
- package/src/loop.ts +125 -16
- package/src/main.js +8 -1
- package/src/main.tsx +7 -1
- package/src/memory/constants.js +10 -0
- package/src/memory/constants.ts +13 -0
- package/src/memory/episodicMemory.js +274 -2
- package/src/memory/episodicMemory.ts +306 -1
- package/src/memory/fileStore.js +24 -4
- package/src/memory/fileStore.ts +22 -2
- package/src/memory/memoryService.js +79 -12
- package/src/memory/memoryService.ts +108 -9
- package/src/memory/repository.js +415 -85
- package/src/memory/repository.ts +437 -86
- package/src/memory/turnJournal.js +95 -0
- package/src/memory/turnJournal.ts +120 -0
- package/src/memory/types.ts +16 -0
- package/src/plugins/pluginApi.ts +30 -6
- package/src/plugins/pluginRunner.js +23 -8
- package/src/plugins/pluginRunner.ts +32 -13
- package/src/prompts/policyRegistry.js +55 -1
- package/src/prompts/policyRegistry.ts +55 -1
- package/src/prompts/promptAssembly.js +39 -7
- package/src/prompts/promptAssembly.ts +145 -59
- package/src/session/runAgentTurn.js +43 -21
- package/src/session/runAgentTurn.ts +68 -26
- package/src/session/runTurn.js +11 -46
- package/src/session/runTurn.ts +12 -46
- package/src/session/runtimeStore.js +149 -0
- package/src/session/runtimeStore.ts +197 -0
- package/src/session/sessionState.js +5 -0
- package/src/session/sessionState.ts +14 -0
- package/src/tools/advisor/advisor.js +76 -9
- package/src/tools/advisor/advisor.ts +79 -9
- package/src/tools/agent/agent.js +143 -14
- package/src/tools/agent/agent.ts +149 -14
- package/src/tools/agent/agentTaskService.js +60 -0
- package/src/tools/agent/agentTaskService.ts +113 -0
- package/src/tools/agent/agentTypes.js +5 -0
- package/src/tools/agent/agentTypes.ts +5 -0
- package/src/tools/bash/bash.js +80 -15
- package/src/tools/bash/bash.ts +83 -15
- package/src/tools/capabilities.js +5 -0
- package/src/tools/capabilities.ts +11 -1
- package/src/tools/edit/edit.js +7 -4
- package/src/tools/edit/edit.ts +7 -4
- package/src/tools/edit/multi-edit.js +4 -2
- package/src/tools/edit/multi-edit.ts +4 -2
- package/src/tools/glob/glob.js +156 -40
- package/src/tools/glob/glob.ts +185 -46
- package/src/tools/index.js +35 -17
- package/src/tools/index.ts +81 -20
- package/src/tools/lsp/lsp.js +165 -22
- package/src/tools/lsp/lsp.ts +206 -36
- package/src/tools/powershell/powershell.js +134 -27
- package/src/tools/powershell/powershell.ts +171 -30
- package/src/tools/presentresult/presentresult.js +10 -0
- package/src/tools/presentresult/presentresult.ts +10 -0
- package/src/tools/read/read.js +249 -29
- package/src/tools/read/read.ts +396 -59
- package/src/tools/shared/diffSummary.js +44 -0
- package/src/tools/shared/diffSummary.ts +63 -0
- package/src/tools/shared/fileActivity.js +2 -1
- package/src/tools/shared/fileActivity.ts +3 -0
- package/src/tools/shared/permanentDeleteGuard.js +57 -0
- package/src/tools/shared/permanentDeleteGuard.ts +71 -0
- package/src/tools/shared/toolOutputStore.js +97 -0
- package/src/tools/shared/toolOutputStore.ts +110 -0
- package/src/tools/skill/skill.js +86 -0
- package/src/tools/skill/skill.ts +94 -0
- package/src/tools/snip/snip.js +67 -0
- package/src/tools/snip/snip.ts +80 -0
- package/src/tools/tasks/backgroundTaskService.js +79 -28
- package/src/tools/tasks/backgroundTaskService.ts +165 -49
- package/src/tools/tasks/taskManagement.js +163 -0
- package/src/tools/tasks/taskManagement.ts +180 -0
- package/src/tools/tasks/taskOutput.js +64 -14
- package/src/tools/tasks/taskOutput.ts +102 -23
- package/src/tools/tasks/taskStop.js +48 -8
- package/src/tools/tasks/taskStop.ts +69 -9
- package/src/tools/tasks/taskStore.js +46 -0
- package/src/tools/tasks/taskStore.ts +77 -0
- package/src/tools/terminal/terminalCapture.js +167 -0
- package/src/tools/terminal/terminalCapture.ts +179 -0
- package/src/tools/trash/trash.js +233 -0
- package/src/tools/trash/trash.ts +283 -0
- package/src/tools/webbrowser/browserControl.js +5 -1
- package/src/tools/webbrowser/browserControl.ts +7 -1
- package/src/tools/webbrowser/webbrowser.js +8 -4
- package/src/tools/webbrowser/webbrowser.ts +8 -4
- package/src/tools/worktree/sessionWorktree.js +128 -0
- package/src/tools/worktree/sessionWorktree.ts +158 -0
- package/src/tools/write/write.js +6 -2
- package/src/tools/write/write.ts +6 -2
- package/src/types.ts +22 -4
- package/src/ui/hooks/useChat.js +31 -11
- package/src/ui/hooks/useChat.ts +31 -13
- package/src/web/protocol.ts +78 -3
- package/workflows/adaptive-research.yaml +5 -5
- package/workflows/book-review-short.yaml +5 -5
- package/workflows/e2e-write-greet.yaml +2 -2
- package/workflows/illustrate.yaml +12 -12
- package/workflows/plan.yaml +18 -18
- package/workflows/ppt.yaml +201 -201
- package/workflows/ralph-loop.yaml +3 -1
- package/workflows/research.md +449 -0
- package/workflows/research.yaml +873 -0
- package/workflows/webppt.yaml +47 -47
- package/workflows/word.yaml +80 -80
- package/workflows/youtube-shorts.yaml +25 -25
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/memory/turnJournal.ts —— 「一个用户回合」的会话原文记账
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 把 user 输入与本回合最终 assistant 回答写进 `episodes/*.jsonl`,
|
|
6
|
+
* 给 `SearchConversations` 提供可检索的历史原文。
|
|
7
|
+
*
|
|
8
|
+
* ★ 为什么是独立模块、而不是塞进 loop.ts:
|
|
9
|
+
* `runQuery` 不等于「一个用户回合」——子 Agent(tools/agent/agent.ts 直接调
|
|
10
|
+
* runQuery,且**携带同一份 workspaceId/sessionId**)和 workflow 的每个 llm/agent 步
|
|
11
|
+
* 都会各调一次 runQuery。记在那里会把子任务提示词当成「用户说的话」灌进历史检索,
|
|
12
|
+
* 污染的正是这个功能要保护的东西。
|
|
13
|
+
*
|
|
14
|
+
* 也不能只留在 `session/runTurn.ts`:TUI 走的是 `ui/hooks/useChat.ts` → runWithPlugins,
|
|
15
|
+
* **根本不经过 runTurn**。此前的后果是 TUI 对话永远不入库,而一次性的
|
|
16
|
+
* `.backfill-<ws>.json` 标记又让 SearchConversations 在首次搜索后永久停更 ——
|
|
17
|
+
* 工具宣称"搜索本机历史会话原文",实际对 TUI 用户静默失效。
|
|
18
|
+
*
|
|
19
|
+
* 所以:实现只有这一份,调用点恰好落在两个真正的「用户回合」边界上
|
|
20
|
+
* (runTurn 与 useChat.submit),子 Agent 与 workflow 步一概不记。
|
|
21
|
+
* ============================================================
|
|
22
|
+
*/
|
|
23
|
+
import { applyEpisodeRetention, episodicMemoryService, finishRetentionSweep, shouldSweepRetention, } from './episodicMemory.js';
|
|
24
|
+
import { memoryService } from './memoryService.js';
|
|
25
|
+
function messageText(message) {
|
|
26
|
+
if (typeof message.content === 'string')
|
|
27
|
+
return message.content;
|
|
28
|
+
if (!Array.isArray(message.content))
|
|
29
|
+
return '';
|
|
30
|
+
return message.content
|
|
31
|
+
.filter((part) => part.type === 'text')
|
|
32
|
+
.map((part) => part.text)
|
|
33
|
+
.join('\n');
|
|
34
|
+
}
|
|
35
|
+
function lastAssistantMessage(messages, start) {
|
|
36
|
+
for (let index = messages.length - 1; index >= start; index -= 1) {
|
|
37
|
+
if (messages[index]?.role === 'assistant')
|
|
38
|
+
return messages[index];
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 会话原文的保留清理:节流后台执行,绝不阻塞本回合。
|
|
44
|
+
* 与 `clipboardImage::pruneOldTempImages` 同款 fire-and-forget,不需要常驻定时器。
|
|
45
|
+
*/
|
|
46
|
+
function sweepRetentionInBackground(workspaceId) {
|
|
47
|
+
if (!shouldSweepRetention(workspaceId))
|
|
48
|
+
return;
|
|
49
|
+
void (async () => {
|
|
50
|
+
try {
|
|
51
|
+
const settings = await memoryService.settings();
|
|
52
|
+
if (!settings.retention)
|
|
53
|
+
return;
|
|
54
|
+
await applyEpisodeRetention(workspaceId, settings.retention);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
finishRetentionSweep(workspaceId);
|
|
58
|
+
}
|
|
59
|
+
})().catch(() => undefined);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 开一次回合记账。缺权限 / 缺 workspaceId / 缺 sessionId 都返回 null(不记,也不报错)——
|
|
63
|
+
* 记账是附加能力,绝不能反过来阻断对话。
|
|
64
|
+
*/
|
|
65
|
+
export async function startTurnJournal(input) {
|
|
66
|
+
const context = input.memoryContext;
|
|
67
|
+
// 判据是 `!== 'denied'`:只要宿主没有明确拒绝记忆访问就记账,`read-only` 也照记。
|
|
68
|
+
// 这是**有意放松**的(2026-08-13 明确决定:优先保证检索效果与性能)——收紧成"只有可写
|
|
69
|
+
// 权限才记"会让 `-p`(默认 read-only)和部分 Desktop 场景的历史检索大面积失效。
|
|
70
|
+
// 真正的隐私边界由 `denied` 与保留窗口(MemorySettings.retention)负责,不靠这里收窄。
|
|
71
|
+
if (!context || context.access === 'denied' || !context.workspaceId || !context.sessionId) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const target = { workspaceId: context.workspaceId, sessionId: context.sessionId };
|
|
75
|
+
await episodicMemoryService.append({
|
|
76
|
+
...target,
|
|
77
|
+
role: 'user',
|
|
78
|
+
content: input.userInput,
|
|
79
|
+
...(input.userMessageId ? { messageId: input.userMessageId } : {}),
|
|
80
|
+
}).catch(() => undefined);
|
|
81
|
+
sweepRetentionInBackground(target.workspaceId);
|
|
82
|
+
return {
|
|
83
|
+
async finish(history, startIndex) {
|
|
84
|
+
const assistant = lastAssistantMessage(history, startIndex);
|
|
85
|
+
const content = assistant ? messageText(assistant) : '';
|
|
86
|
+
if (!content)
|
|
87
|
+
return;
|
|
88
|
+
await episodicMemoryService.append({
|
|
89
|
+
...target,
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
content,
|
|
92
|
+
}).catch(() => undefined);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================
|
|
3
|
+
* src/memory/turnJournal.ts —— 「一个用户回合」的会话原文记账
|
|
4
|
+
* ------------------------------------------------------------
|
|
5
|
+
* 把 user 输入与本回合最终 assistant 回答写进 `episodes/*.jsonl`,
|
|
6
|
+
* 给 `SearchConversations` 提供可检索的历史原文。
|
|
7
|
+
*
|
|
8
|
+
* ★ 为什么是独立模块、而不是塞进 loop.ts:
|
|
9
|
+
* `runQuery` 不等于「一个用户回合」——子 Agent(tools/agent/agent.ts 直接调
|
|
10
|
+
* runQuery,且**携带同一份 workspaceId/sessionId**)和 workflow 的每个 llm/agent 步
|
|
11
|
+
* 都会各调一次 runQuery。记在那里会把子任务提示词当成「用户说的话」灌进历史检索,
|
|
12
|
+
* 污染的正是这个功能要保护的东西。
|
|
13
|
+
*
|
|
14
|
+
* 也不能只留在 `session/runTurn.ts`:TUI 走的是 `ui/hooks/useChat.ts` → runWithPlugins,
|
|
15
|
+
* **根本不经过 runTurn**。此前的后果是 TUI 对话永远不入库,而一次性的
|
|
16
|
+
* `.backfill-<ws>.json` 标记又让 SearchConversations 在首次搜索后永久停更 ——
|
|
17
|
+
* 工具宣称"搜索本机历史会话原文",实际对 TUI 用户静默失效。
|
|
18
|
+
*
|
|
19
|
+
* 所以:实现只有这一份,调用点恰好落在两个真正的「用户回合」边界上
|
|
20
|
+
* (runTurn 与 useChat.submit),子 Agent 与 workflow 步一概不记。
|
|
21
|
+
* ============================================================
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
applyEpisodeRetention,
|
|
26
|
+
episodicMemoryService,
|
|
27
|
+
finishRetentionSweep,
|
|
28
|
+
shouldSweepRetention,
|
|
29
|
+
} from './episodicMemory.js';
|
|
30
|
+
import { memoryService } from './memoryService.js';
|
|
31
|
+
import type { Message } from '../types.js';
|
|
32
|
+
|
|
33
|
+
export interface TurnJournalInput {
|
|
34
|
+
memoryContext?: {
|
|
35
|
+
access?: string;
|
|
36
|
+
workspaceId?: string;
|
|
37
|
+
sessionId?: string;
|
|
38
|
+
};
|
|
39
|
+
userInput: string;
|
|
40
|
+
userMessageId?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface TurnJournal {
|
|
44
|
+
/** 回合收尾时调用:从 history 里取本回合最后一条 assistant 消息入库。 */
|
|
45
|
+
finish(history: readonly Message[], startIndex: number): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function messageText(message: Message): string {
|
|
49
|
+
if (typeof message.content === 'string') return message.content;
|
|
50
|
+
if (!Array.isArray(message.content)) return '';
|
|
51
|
+
return message.content
|
|
52
|
+
.filter((part): part is Extract<typeof part, { type: 'text' }> => part.type === 'text')
|
|
53
|
+
.map((part) => part.text)
|
|
54
|
+
.join('\n');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function lastAssistantMessage(
|
|
58
|
+
messages: readonly Message[],
|
|
59
|
+
start: number,
|
|
60
|
+
): Message | undefined {
|
|
61
|
+
for (let index = messages.length - 1; index >= start; index -= 1) {
|
|
62
|
+
if (messages[index]?.role === 'assistant') return messages[index];
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 会话原文的保留清理:节流后台执行,绝不阻塞本回合。
|
|
69
|
+
* 与 `clipboardImage::pruneOldTempImages` 同款 fire-and-forget,不需要常驻定时器。
|
|
70
|
+
*/
|
|
71
|
+
function sweepRetentionInBackground(workspaceId: string): void {
|
|
72
|
+
if (!shouldSweepRetention(workspaceId)) return;
|
|
73
|
+
void (async () => {
|
|
74
|
+
try {
|
|
75
|
+
const settings = await memoryService.settings();
|
|
76
|
+
if (!settings.retention) return;
|
|
77
|
+
await applyEpisodeRetention(workspaceId, settings.retention);
|
|
78
|
+
} finally {
|
|
79
|
+
finishRetentionSweep(workspaceId);
|
|
80
|
+
}
|
|
81
|
+
})().catch(() => undefined);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 开一次回合记账。缺权限 / 缺 workspaceId / 缺 sessionId 都返回 null(不记,也不报错)——
|
|
86
|
+
* 记账是附加能力,绝不能反过来阻断对话。
|
|
87
|
+
*/
|
|
88
|
+
export async function startTurnJournal(
|
|
89
|
+
input: TurnJournalInput,
|
|
90
|
+
): Promise<TurnJournal | null> {
|
|
91
|
+
const context = input.memoryContext;
|
|
92
|
+
// 判据是 `!== 'denied'`:只要宿主没有明确拒绝记忆访问就记账,`read-only` 也照记。
|
|
93
|
+
// 这是**有意放松**的(2026-08-13 明确决定:优先保证检索效果与性能)——收紧成"只有可写
|
|
94
|
+
// 权限才记"会让 `-p`(默认 read-only)和部分 Desktop 场景的历史检索大面积失效。
|
|
95
|
+
// 真正的隐私边界由 `denied` 与保留窗口(MemorySettings.retention)负责,不靠这里收窄。
|
|
96
|
+
if (!context || context.access === 'denied' || !context.workspaceId || !context.sessionId) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const target = { workspaceId: context.workspaceId, sessionId: context.sessionId };
|
|
100
|
+
await episodicMemoryService.append({
|
|
101
|
+
...target,
|
|
102
|
+
role: 'user',
|
|
103
|
+
content: input.userInput,
|
|
104
|
+
...(input.userMessageId ? { messageId: input.userMessageId } : {}),
|
|
105
|
+
}).catch(() => undefined);
|
|
106
|
+
sweepRetentionInBackground(target.workspaceId);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
async finish(history, startIndex) {
|
|
110
|
+
const assistant = lastAssistantMessage(history, startIndex);
|
|
111
|
+
const content = assistant ? messageText(assistant) : '';
|
|
112
|
+
if (!content) return;
|
|
113
|
+
await episodicMemoryService.append({
|
|
114
|
+
...target,
|
|
115
|
+
role: 'assistant',
|
|
116
|
+
content,
|
|
117
|
+
}).catch(() => undefined);
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
package/src/memory/types.ts
CHANGED
|
@@ -71,10 +71,26 @@ export interface MemoryReviewWindows {
|
|
|
71
71
|
decision: number;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* 会话原文日志(`episodes/*.jsonl`)的保留窗口。
|
|
76
|
+
*
|
|
77
|
+
* 记忆条目(records)是用户明确授权保存的稳定事实,只有用户能删;会话原文日志是为了
|
|
78
|
+
* `SearchConversations` 而留的**全量对话副本**,性质完全不同——它必须有保留上限,否则
|
|
79
|
+
* 明文对话会无限累积(仓库里其它 append-only 存储都有清理,只有它没有)。
|
|
80
|
+
*/
|
|
81
|
+
export interface MemoryRetention {
|
|
82
|
+
/** 超过这个天数的会话原文自动清理。 */
|
|
83
|
+
episodeDays: number;
|
|
84
|
+
/** 单个工作区的会话原文总体积上限(MB),先到者先触发。 */
|
|
85
|
+
episodeMaxMegabytes: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
74
88
|
export interface MemorySettings {
|
|
75
89
|
autoRemember: boolean;
|
|
76
90
|
/** Optional on the public wire type for pre-v2 desktop clients. */
|
|
77
91
|
reviewAfterDays?: MemoryReviewWindows;
|
|
92
|
+
/** Optional on the wire so pre-retention clients keep deserializing. */
|
|
93
|
+
retention?: MemoryRetention;
|
|
78
94
|
}
|
|
79
95
|
|
|
80
96
|
export interface MemoryDiagnostic {
|
package/src/plugins/pluginApi.ts
CHANGED
|
@@ -21,9 +21,21 @@
|
|
|
21
21
|
* ============================================================
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import type {
|
|
24
|
+
import type { ContextBundle } from '../context/contextBundle.js';
|
|
25
|
+
import type { AgentRuntimeContext } from '../runtime/agentRuntimeContext.js';
|
|
26
|
+
import type {
|
|
27
|
+
RunAgentTurnOptions,
|
|
28
|
+
RunAgentTurnResult,
|
|
29
|
+
} from '../session/runAgentTurn.js';
|
|
25
30
|
import type { SessionState } from '../session/sessionState.js';
|
|
26
|
-
import type {
|
|
31
|
+
import type {
|
|
32
|
+
LoopEvent,
|
|
33
|
+
Message,
|
|
34
|
+
PromptCacheContext,
|
|
35
|
+
Provider,
|
|
36
|
+
ToolCallContext,
|
|
37
|
+
ToolResult,
|
|
38
|
+
} from '../types.js';
|
|
27
39
|
|
|
28
40
|
/** 框架传给插件的运行时上下文(仅暴露执行 LLM/工具 所需的最小集合)。 */
|
|
29
41
|
export interface PluginContext {
|
|
@@ -38,12 +50,23 @@ export interface PluginContext {
|
|
|
38
50
|
maxTurns?: number;
|
|
39
51
|
/** 当前回合的瞬时外部上下文;插件不得把它持久化到 history。 */
|
|
40
52
|
transientContext?: string;
|
|
41
|
-
transientBundle?:
|
|
42
|
-
promptCache?:
|
|
53
|
+
transientBundle?: ContextBundle;
|
|
54
|
+
promptCache?: PromptCacheContext;
|
|
43
55
|
/** Canonical host-issued trust context; plugins may narrow it but never elevate it. */
|
|
44
|
-
runtimeContext?:
|
|
56
|
+
runtimeContext?: AgentRuntimeContext;
|
|
45
57
|
/** Host tool names are an upper bound. Plugins may narrow, never expand. */
|
|
46
58
|
allowedTools?: readonly string[];
|
|
59
|
+
/**
|
|
60
|
+
* Host tool dispatcher. Rich plugins must use the same injected executor as
|
|
61
|
+
* the parent run so tests, embedders and host-specific dispatch remain on one
|
|
62
|
+
* execution path.
|
|
63
|
+
*/
|
|
64
|
+
executeTool?: (
|
|
65
|
+
name: string,
|
|
66
|
+
rawArgsJson: string,
|
|
67
|
+
signal?: AbortSignal,
|
|
68
|
+
context?: ToolCallContext,
|
|
69
|
+
) => Promise<ToolResult>;
|
|
47
70
|
/**
|
|
48
71
|
* 宿主的**授权与可见性上下文**,原样转交给工具层(`executeTool` 第四参)。
|
|
49
72
|
*
|
|
@@ -100,7 +123,8 @@ export interface PluginEvent {
|
|
|
100
123
|
export interface PluginApi {
|
|
101
124
|
activate?(
|
|
102
125
|
ctx: PluginRuntimeContext,
|
|
103
|
-
):
|
|
126
|
+
): // biome-ignore lint/suspicious/noConfusingVoidType: lifecycle hooks intentionally accept synchronous void or an async disposer.
|
|
127
|
+
Promise<void | (() => void)> | void | (() => void);
|
|
104
128
|
runCommand?(
|
|
105
129
|
commandName: string,
|
|
106
130
|
args: string,
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
* 2) / 输入 → discoverPlugins → resolveCommand
|
|
10
10
|
* a) 命中插件且其 plugin.ts 暴露 runCommand → 让 plugin.ts 接管
|
|
11
11
|
* b) 命中插件但只有声明式 commands/*.md → 把 prompt 拼好喂给 runQuery
|
|
12
|
-
* c) 未命中
|
|
13
|
-
*
|
|
12
|
+
* c) 未命中 -> warning 提示未知命令后透传 runQuery(/ 只是激活前缀,
|
|
13
|
+
* 不是严格命令系统;未知的 /xxx 当普通用户输入处理,不报错)
|
|
14
14
|
* ============================================================
|
|
15
15
|
*/
|
|
16
|
-
import { discoverPlugins, resolveCommand, buildCommandInput, } from './commandRouter.js';
|
|
17
|
-
import { loadPluginApi } from './pluginLoader.js';
|
|
18
16
|
import { runQuery } from '../loop.js';
|
|
17
|
+
import { buildCommandInput, discoverPlugins, listAvailableCommands, resolveCommand, } from './commandRouter.js';
|
|
18
|
+
import { loadPluginApi } from './pluginLoader.js';
|
|
19
19
|
export async function* runWithPlugins(userInput, options) {
|
|
20
20
|
const { provider, history, signal } = options;
|
|
21
21
|
const isSlashCommand = userInput.trimStart().startsWith('/');
|
|
@@ -30,6 +30,7 @@ export async function* runWithPlugins(userInput, options) {
|
|
|
30
30
|
sessionState: options.sessionState,
|
|
31
31
|
chat: options.chat,
|
|
32
32
|
executeTool: options.executeTool,
|
|
33
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
33
34
|
toolGate: options.toolGate,
|
|
34
35
|
transientContext: options.transientContext,
|
|
35
36
|
transientBundle: options.transientBundle,
|
|
@@ -55,10 +56,21 @@ export async function* runWithPlugins(userInput, options) {
|
|
|
55
56
|
await discoverPlugins();
|
|
56
57
|
const matched = resolveCommand(userInput);
|
|
57
58
|
if (!matched) {
|
|
58
|
-
// 未命中任何插件命令
|
|
59
|
-
// / 只是激活前缀(类似 Claude Code 的 /
|
|
60
|
-
//
|
|
61
|
-
//
|
|
59
|
+
// 未命中任何插件命令 -> 提示后透传 runQuery。
|
|
60
|
+
// / 只是激活前缀(类似 Claude Code 的 / 模式),不是严格命令系统:
|
|
61
|
+
// 不匹配仍当普通对话处理,让 LLM 自行理解意图。但 -p 等无人值守场景里
|
|
62
|
+
// 拼错的 /reseach 会被静默当文本烧一轮 LLM(实战:用户以为在跑工作流,
|
|
63
|
+
// 实际什么都没发生)--先 warning 列出已注册命令,零成本纠正拼写。
|
|
64
|
+
const trimmedUnknown = userInput.trimStart();
|
|
65
|
+
const spaceUnknown = trimmedUnknown.indexOf(' ', 1);
|
|
66
|
+
const unknownCmd = (spaceUnknown >= 0 ? trimmedUnknown.slice(1, spaceUnknown) : trimmedUnknown.slice(1)).trim();
|
|
67
|
+
if (unknownCmd) {
|
|
68
|
+
const available = listAvailableCommands().map((c) => c.command);
|
|
69
|
+
yield {
|
|
70
|
+
type: 'warning',
|
|
71
|
+
message: `未知命令 /${unknownCmd}(已注册:${available.length ? available.join('、') : '无'}),已按普通对话处理。`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
62
74
|
yield* runQuery(userInput, {
|
|
63
75
|
provider,
|
|
64
76
|
history,
|
|
@@ -69,6 +81,7 @@ export async function* runWithPlugins(userInput, options) {
|
|
|
69
81
|
sessionState: options.sessionState,
|
|
70
82
|
chat: options.chat,
|
|
71
83
|
executeTool: options.executeTool,
|
|
84
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
72
85
|
toolGate: options.toolGate,
|
|
73
86
|
transientContext: options.transientContext,
|
|
74
87
|
transientBundle: options.transientBundle,
|
|
@@ -104,6 +117,7 @@ export async function* runWithPlugins(userInput, options) {
|
|
|
104
117
|
promptCache: options.promptCache,
|
|
105
118
|
runtimeContext: options.runtimeContext,
|
|
106
119
|
allowedTools: options.allowedTools,
|
|
120
|
+
executeTool: options.executeTool,
|
|
107
121
|
// ⚠️ 宿主授权上下文**必须**跟着走。这一处曾经漏掉,而下面两条 runQuery 路径
|
|
108
122
|
// 都带着它 —— 于是富插件成了三条路里唯一拿不到 browserRun/computerRun 的那条。
|
|
109
123
|
// 后果不是"少个字段":workflow 里调 ComputerUse 恒返回「当前宿主未启用
|
|
@@ -129,6 +143,7 @@ export async function* runWithPlugins(userInput, options) {
|
|
|
129
143
|
sessionState: options.sessionState,
|
|
130
144
|
chat: options.chat,
|
|
131
145
|
executeTool: options.executeTool,
|
|
146
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
132
147
|
toolGate: options.toolGate,
|
|
133
148
|
transientContext: options.transientContext,
|
|
134
149
|
transientBundle: options.transientBundle,
|
|
@@ -9,20 +9,21 @@
|
|
|
9
9
|
* 2) / 输入 → discoverPlugins → resolveCommand
|
|
10
10
|
* a) 命中插件且其 plugin.ts 暴露 runCommand → 让 plugin.ts 接管
|
|
11
11
|
* b) 命中插件但只有声明式 commands/*.md → 把 prompt 拼好喂给 runQuery
|
|
12
|
-
* c) 未命中
|
|
13
|
-
*
|
|
12
|
+
* c) 未命中 -> warning 提示未知命令后透传 runQuery(/ 只是激活前缀,
|
|
13
|
+
* 不是严格命令系统;未知的 /xxx 当普通用户输入处理,不报错)
|
|
14
14
|
* ============================================================
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { type RunQueryOptions, runQuery } from '../loop.js';
|
|
18
|
+
import type { LoopEvent, Message, Provider } from '../types.js';
|
|
17
19
|
import {
|
|
20
|
+
buildCommandInput,
|
|
18
21
|
discoverPlugins,
|
|
22
|
+
listAvailableCommands,
|
|
19
23
|
resolveCommand,
|
|
20
|
-
buildCommandInput,
|
|
21
24
|
} from './commandRouter.js';
|
|
22
|
-
import { loadPluginApi } from './pluginLoader.js';
|
|
23
|
-
import { runQuery, type RunQueryOptions } from '../loop.js';
|
|
24
|
-
import type { LoopEvent, Message, Provider } from '../types.js';
|
|
25
25
|
import type { PluginEvent } from './pluginApi.js';
|
|
26
|
+
import { loadPluginApi } from './pluginLoader.js';
|
|
26
27
|
|
|
27
28
|
export interface PluginRunnerOptions {
|
|
28
29
|
provider: Provider;
|
|
@@ -38,6 +39,7 @@ export interface PluginRunnerOptions {
|
|
|
38
39
|
*/
|
|
39
40
|
chat?: RunQueryOptions['chat'];
|
|
40
41
|
executeTool?: RunQueryOptions['executeTool'];
|
|
42
|
+
commitFormalDelivery?: RunQueryOptions['commitFormalDelivery'];
|
|
41
43
|
/** 工具执行闸门(生产用):透传给 runQuery。见 src/toolGate.ts。 */
|
|
42
44
|
toolGate?: RunQueryOptions['toolGate'];
|
|
43
45
|
transientContext?: RunQueryOptions['transientContext'];
|
|
@@ -77,6 +79,7 @@ export async function* runWithPlugins(
|
|
|
77
79
|
sessionState: options.sessionState,
|
|
78
80
|
chat: options.chat,
|
|
79
81
|
executeTool: options.executeTool,
|
|
82
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
80
83
|
toolGate: options.toolGate,
|
|
81
84
|
transientContext: options.transientContext,
|
|
82
85
|
transientBundle: options.transientBundle,
|
|
@@ -104,10 +107,23 @@ export async function* runWithPlugins(
|
|
|
104
107
|
const matched = resolveCommand(userInput);
|
|
105
108
|
|
|
106
109
|
if (!matched) {
|
|
107
|
-
// 未命中任何插件命令
|
|
108
|
-
// / 只是激活前缀(类似 Claude Code 的 /
|
|
109
|
-
//
|
|
110
|
-
//
|
|
110
|
+
// 未命中任何插件命令 -> 提示后透传 runQuery。
|
|
111
|
+
// / 只是激活前缀(类似 Claude Code 的 / 模式),不是严格命令系统:
|
|
112
|
+
// 不匹配仍当普通对话处理,让 LLM 自行理解意图。但 -p 等无人值守场景里
|
|
113
|
+
// 拼错的 /reseach 会被静默当文本烧一轮 LLM(实战:用户以为在跑工作流,
|
|
114
|
+
// 实际什么都没发生)--先 warning 列出已注册命令,零成本纠正拼写。
|
|
115
|
+
const trimmedUnknown = userInput.trimStart();
|
|
116
|
+
const spaceUnknown = trimmedUnknown.indexOf(' ', 1);
|
|
117
|
+
const unknownCmd = (
|
|
118
|
+
spaceUnknown >= 0 ? trimmedUnknown.slice(1, spaceUnknown) : trimmedUnknown.slice(1)
|
|
119
|
+
).trim();
|
|
120
|
+
if (unknownCmd) {
|
|
121
|
+
const available = listAvailableCommands().map((c) => c.command);
|
|
122
|
+
yield {
|
|
123
|
+
type: 'warning',
|
|
124
|
+
message: `未知命令 /${unknownCmd}(已注册:${available.length ? available.join('、') : '无'}),已按普通对话处理。`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
111
127
|
yield* runQuery(userInput, {
|
|
112
128
|
provider,
|
|
113
129
|
history,
|
|
@@ -118,6 +134,7 @@ export async function* runWithPlugins(
|
|
|
118
134
|
sessionState: options.sessionState,
|
|
119
135
|
chat: options.chat,
|
|
120
136
|
executeTool: options.executeTool,
|
|
137
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
121
138
|
toolGate: options.toolGate,
|
|
122
139
|
transientContext: options.transientContext,
|
|
123
140
|
transientBundle: options.transientBundle,
|
|
@@ -154,6 +171,7 @@ export async function* runWithPlugins(
|
|
|
154
171
|
promptCache: options.promptCache,
|
|
155
172
|
runtimeContext: options.runtimeContext,
|
|
156
173
|
allowedTools: options.allowedTools,
|
|
174
|
+
executeTool: options.executeTool,
|
|
157
175
|
// ⚠️ 宿主授权上下文**必须**跟着走。这一处曾经漏掉,而下面两条 runQuery 路径
|
|
158
176
|
// 都带着它 —— 于是富插件成了三条路里唯一拿不到 browserRun/computerRun 的那条。
|
|
159
177
|
// 后果不是"少个字段":workflow 里调 ComputerUse 恒返回「当前宿主未启用
|
|
@@ -180,10 +198,11 @@ export async function* runWithPlugins(
|
|
|
180
198
|
sessionState: options.sessionState,
|
|
181
199
|
chat: options.chat,
|
|
182
200
|
executeTool: options.executeTool,
|
|
201
|
+
commitFormalDelivery: options.commitFormalDelivery,
|
|
183
202
|
toolGate: options.toolGate,
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
transientContext: options.transientContext,
|
|
204
|
+
transientBundle: options.transientBundle,
|
|
205
|
+
promptCache: options.promptCache,
|
|
187
206
|
userAttachments: options.userAttachments,
|
|
188
207
|
userMessageId: options.userMessageId,
|
|
189
208
|
userBrowserReferences: options.userBrowserReferences,
|
|
@@ -84,6 +84,8 @@ const ROUTABLE_TOOLS = [
|
|
|
84
84
|
'ComputerUse', 'WebBrowser', 'WebFetch', 'WebSearch',
|
|
85
85
|
'Download', 'Read', 'Glob', 'Grep', 'Edit', 'MultiEdit', 'Write', 'Bash',
|
|
86
86
|
'NotebookEdit', 'LSP', 'PowerShell', 'TaskOutput', 'TaskStop',
|
|
87
|
+
'Trash', 'TaskCreate', 'TaskGet', 'TaskUpdate', 'TaskList', 'Skill',
|
|
88
|
+
'EnterWorktree', 'ExitWorktree', 'TerminalCapture', 'Snip',
|
|
87
89
|
];
|
|
88
90
|
const PARAMETERIZED_TOOLS = [
|
|
89
91
|
...ROUTABLE_TOOLS, ...MEMORY_TOOLS, 'AskUserQuestion', 'Advisor', 'Agent',
|
|
@@ -173,6 +175,31 @@ const CAPABILITY_POLICIES = [
|
|
|
173
175
|
requires: ['background.process'],
|
|
174
176
|
render: () => '- 后台任务按当前会话隔离。启动后保存 task_id;TaskOutput 可阻塞等待新输出,避免高频轮询;任务不再需要或需要中止时使用 TaskStop,不遗留服务进程。',
|
|
175
177
|
},
|
|
178
|
+
{
|
|
179
|
+
id: 'capability.trash', layer: 'capability', profiles: ['main', 'subagent-general'], source: '06',
|
|
180
|
+
requiresTools: ['Trash'],
|
|
181
|
+
render: () => '- 删除项目文件或目录使用 Trash 移入系统回收站;清理 Git 未跟踪文件使用 Trash(action="git-clean")。Bash/PowerShell 不执行绕过回收站的永久删除命令。',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'capability.task-management', layer: 'capability', profiles: ALL_PROFILES, source: '06',
|
|
185
|
+
requires: ['task.management'],
|
|
186
|
+
render: () => '- 多阶段、跨回合或存在依赖关系的工作用 TaskCreate/TaskUpdate 保持任务图;开始前检查 blockedBy,完成验证后才标记 completed。单步工作不必创建任务。',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'capability.skill-invoke', layer: 'capability', profiles: ALL_PROFILES, source: '07',
|
|
190
|
+
requiresTools: ['Skill'],
|
|
191
|
+
render: () => '- 需求匹配已安装 Skill 或用户明确点名 Skill 时,用 Skill 按精确名称加载完整 SKILL.md;其中引用的资源再按需 Read,不预加载整个目录。',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'capability.session-worktree', layer: 'capability', profiles: MAIN_PROFILE, source: '06',
|
|
195
|
+
requires: ['worktree.session'],
|
|
196
|
+
render: () => '- 主任务需要隔离试验时可 EnterWorktree;完成后用 ExitWorktree apply/keep/discard 收口。apply 必须通过补丁检查且不自动提交;discard 不永久丢弃已有成果。',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
id: 'capability.terminal-capture', layer: 'capability', profiles: MAIN_PROFILE, source: '06',
|
|
200
|
+
requiresTools: ['TerminalCapture'],
|
|
201
|
+
render: () => '- 只有确需查看当前 TUI 屏幕状态时使用 TerminalCapture;项目命令输出仍通过 Bash/TaskOutput 获取。',
|
|
202
|
+
},
|
|
176
203
|
{
|
|
177
204
|
id: 'capability.web-research', layer: 'capability', profiles: ALL_PROFILES, source: '04',
|
|
178
205
|
requiresAnyTools: ['WebSearch', 'WebFetch'],
|
|
@@ -205,7 +232,34 @@ const CAPABILITY_POLICIES = [
|
|
|
205
232
|
{
|
|
206
233
|
id: 'capability.advisor', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
207
234
|
requiresTools: ['Advisor'],
|
|
208
|
-
|
|
235
|
+
// 注意:文本内不得硬编码其他工具名(如 PresentResult),否则该工具缺席时这里
|
|
236
|
+
// 成为唯一残留的「幽灵引用」——capability 规则交叉引用必须在 toolNames 条件下拼接。
|
|
237
|
+
render: ({ toolNames }) => `- Advisor(顾问模型)使用规则:
|
|
238
|
+
**在动手之前问,不是出事之后问**:顾问的价值在方案定型前最大——代码一旦写下去,换方向的成本就变了。
|
|
239
|
+
「摸清现状」不算动手(找文件、读代码、跑命令看状态,这些先做完再问);
|
|
240
|
+
「动手」指写文件、认定某个解释、在假设上继续搭、给出结论。
|
|
241
|
+
**当 Advisor 工具出现在当前可用工具列表中时**,以下 5 种场景必须主动调用,不要等到出错或用户问了才想起来:
|
|
242
|
+
1. 方案/开发计划成型后、动手执行前(先让顾问挑刺,避免做一半才发现方向错了)
|
|
243
|
+
2. 最终结果提交用户前${toolNames.has('PresentResult') ? '(PresentResult 之前)' : ''}:代码评审、评估报告、技术选型、重构方案等所有"给出结论"的场合
|
|
244
|
+
3. 连续 2 次遇到同类错误或修复不收敛(死循环前兆,立刻换思路)
|
|
245
|
+
4. 同一轮内用户第 2 次反馈不满意(不要在原地打转,先找顾问换角度)
|
|
246
|
+
5. 涉及安全性、数据丢失、不可逆操作的决策(顾问双保险)
|
|
247
|
+
调用时 note 里必须包含:你的完整结论/方案 + 关键代码原文或证据摘要,不要只写结论摘要。
|
|
248
|
+
场景 2 有个前置动作:**先把产出落盘再调用**(写文件、保存结果)。这一次调用要花时间,
|
|
249
|
+
回合若在此期间结束,写下的文件还在,没写的答案就没了。
|
|
250
|
+
长任务至少咨询两次:方案定型前一次、宣布完成前一次;短的、下一步由刚读到的输出直接决定的任务,一次即可。
|
|
251
|
+
**顾问返回后必须做验证,不能盲信:**
|
|
252
|
+
- 顾问的意见只是第二视角的候选诊断,不是事实。它没有工具访问权,无法自行验证。
|
|
253
|
+
- 对顾问提出的每一条严重问题/反例,都必须回到代码、测试、原始资料或工具输出核实。
|
|
254
|
+
- 核实后分类:真问题(有证据支撑)、误报(被事实推翻)、待澄清(暂时无法验证)。
|
|
255
|
+
- 未经核实的顾问意见,不得写成确定结论;与实测冲突时,以实测为准。
|
|
256
|
+
- ⚠️ 但「自测通过」不等于顾问说错了:那往往只说明你的测试没测到它指的那一层。
|
|
257
|
+
- 与你已掌握的证据冲突时**不要默默改方向**:再调一次顾问,把冲突摆出来
|
|
258
|
+
(「我实测是 X,你建议 Y,哪个约束更硬?」)。它看过你的证据但可能低估了,
|
|
259
|
+
一次对账远比顺着错误分支做下去便宜。
|
|
260
|
+
**如果 Advisor 不在工具列表中**(未配置、已关闭、仅手动模式或当前不可用):视为用户明确选择跳过,不得尝试调用,也不阻塞流程,无需在输出中专门说明。
|
|
261
|
+
咨询前先落盘成果,顾问意见与实测冲突时以实测为准并明确核对冲突。
|
|
262
|
+
补充:任何评审/评估/结论输出前,先用 adversarial-review 的方法论自我审查(拆原子主张、反向提问、去现场核对),再决定是否需要顾问。`,
|
|
209
263
|
},
|
|
210
264
|
{
|
|
211
265
|
id: 'capability.subagent', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
@@ -133,6 +133,8 @@ const ROUTABLE_TOOLS = [
|
|
|
133
133
|
'ComputerUse', 'WebBrowser', 'WebFetch', 'WebSearch',
|
|
134
134
|
'Download', 'Read', 'Glob', 'Grep', 'Edit', 'MultiEdit', 'Write', 'Bash',
|
|
135
135
|
'NotebookEdit', 'LSP', 'PowerShell', 'TaskOutput', 'TaskStop',
|
|
136
|
+
'Trash', 'TaskCreate', 'TaskGet', 'TaskUpdate', 'TaskList', 'Skill',
|
|
137
|
+
'EnterWorktree', 'ExitWorktree', 'TerminalCapture', 'Snip',
|
|
136
138
|
];
|
|
137
139
|
|
|
138
140
|
const PARAMETERIZED_TOOLS = [
|
|
@@ -215,6 +217,31 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
215
217
|
requires: ['background.process'],
|
|
216
218
|
render: () => '- 后台任务按当前会话隔离。启动后保存 task_id;TaskOutput 可阻塞等待新输出,避免高频轮询;任务不再需要或需要中止时使用 TaskStop,不遗留服务进程。',
|
|
217
219
|
},
|
|
220
|
+
{
|
|
221
|
+
id: 'capability.trash', layer: 'capability', profiles: ['main','subagent-general'], source: '06',
|
|
222
|
+
requiresTools: ['Trash'],
|
|
223
|
+
render: () => '- 删除项目文件或目录使用 Trash 移入系统回收站;清理 Git 未跟踪文件使用 Trash(action="git-clean")。Bash/PowerShell 不执行绕过回收站的永久删除命令。',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
id: 'capability.task-management', layer: 'capability', profiles: ALL_PROFILES, source: '06',
|
|
227
|
+
requires: ['task.management'],
|
|
228
|
+
render: () => '- 多阶段、跨回合或存在依赖关系的工作用 TaskCreate/TaskUpdate 保持任务图;开始前检查 blockedBy,完成验证后才标记 completed。单步工作不必创建任务。',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: 'capability.skill-invoke', layer: 'capability', profiles: ALL_PROFILES, source: '07',
|
|
232
|
+
requiresTools: ['Skill'],
|
|
233
|
+
render: () => '- 需求匹配已安装 Skill 或用户明确点名 Skill 时,用 Skill 按精确名称加载完整 SKILL.md;其中引用的资源再按需 Read,不预加载整个目录。',
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: 'capability.session-worktree', layer: 'capability', profiles: MAIN_PROFILE, source: '06',
|
|
237
|
+
requires: ['worktree.session'],
|
|
238
|
+
render: () => '- 主任务需要隔离试验时可 EnterWorktree;完成后用 ExitWorktree apply/keep/discard 收口。apply 必须通过补丁检查且不自动提交;discard 不永久丢弃已有成果。',
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: 'capability.terminal-capture', layer: 'capability', profiles: MAIN_PROFILE, source: '06',
|
|
242
|
+
requiresTools: ['TerminalCapture'],
|
|
243
|
+
render: () => '- 只有确需查看当前 TUI 屏幕状态时使用 TerminalCapture;项目命令输出仍通过 Bash/TaskOutput 获取。',
|
|
244
|
+
},
|
|
218
245
|
{
|
|
219
246
|
id: 'capability.web-research', layer: 'capability', profiles: ALL_PROFILES, source: '04',
|
|
220
247
|
requiresAnyTools: ['WebSearch', 'WebFetch'],
|
|
@@ -247,7 +274,34 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
247
274
|
{
|
|
248
275
|
id: 'capability.advisor', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
249
276
|
requiresTools: ['Advisor'],
|
|
250
|
-
|
|
277
|
+
// 注意:文本内不得硬编码其他工具名(如 PresentResult),否则该工具缺席时这里
|
|
278
|
+
// 成为唯一残留的「幽灵引用」——capability 规则交叉引用必须在 toolNames 条件下拼接。
|
|
279
|
+
render: ({ toolNames }) => `- Advisor(顾问模型)使用规则:
|
|
280
|
+
**在动手之前问,不是出事之后问**:顾问的价值在方案定型前最大——代码一旦写下去,换方向的成本就变了。
|
|
281
|
+
「摸清现状」不算动手(找文件、读代码、跑命令看状态,这些先做完再问);
|
|
282
|
+
「动手」指写文件、认定某个解释、在假设上继续搭、给出结论。
|
|
283
|
+
**当 Advisor 工具出现在当前可用工具列表中时**,以下 5 种场景必须主动调用,不要等到出错或用户问了才想起来:
|
|
284
|
+
1. 方案/开发计划成型后、动手执行前(先让顾问挑刺,避免做一半才发现方向错了)
|
|
285
|
+
2. 最终结果提交用户前${toolNames.has('PresentResult') ? '(PresentResult 之前)' : ''}:代码评审、评估报告、技术选型、重构方案等所有"给出结论"的场合
|
|
286
|
+
3. 连续 2 次遇到同类错误或修复不收敛(死循环前兆,立刻换思路)
|
|
287
|
+
4. 同一轮内用户第 2 次反馈不满意(不要在原地打转,先找顾问换角度)
|
|
288
|
+
5. 涉及安全性、数据丢失、不可逆操作的决策(顾问双保险)
|
|
289
|
+
调用时 note 里必须包含:你的完整结论/方案 + 关键代码原文或证据摘要,不要只写结论摘要。
|
|
290
|
+
场景 2 有个前置动作:**先把产出落盘再调用**(写文件、保存结果)。这一次调用要花时间,
|
|
291
|
+
回合若在此期间结束,写下的文件还在,没写的答案就没了。
|
|
292
|
+
长任务至少咨询两次:方案定型前一次、宣布完成前一次;短的、下一步由刚读到的输出直接决定的任务,一次即可。
|
|
293
|
+
**顾问返回后必须做验证,不能盲信:**
|
|
294
|
+
- 顾问的意见只是第二视角的候选诊断,不是事实。它没有工具访问权,无法自行验证。
|
|
295
|
+
- 对顾问提出的每一条严重问题/反例,都必须回到代码、测试、原始资料或工具输出核实。
|
|
296
|
+
- 核实后分类:真问题(有证据支撑)、误报(被事实推翻)、待澄清(暂时无法验证)。
|
|
297
|
+
- 未经核实的顾问意见,不得写成确定结论;与实测冲突时,以实测为准。
|
|
298
|
+
- ⚠️ 但「自测通过」不等于顾问说错了:那往往只说明你的测试没测到它指的那一层。
|
|
299
|
+
- 与你已掌握的证据冲突时**不要默默改方向**:再调一次顾问,把冲突摆出来
|
|
300
|
+
(「我实测是 X,你建议 Y,哪个约束更硬?」)。它看过你的证据但可能低估了,
|
|
301
|
+
一次对账远比顺着错误分支做下去便宜。
|
|
302
|
+
**如果 Advisor 不在工具列表中**(未配置、已关闭、仅手动模式或当前不可用):视为用户明确选择跳过,不得尝试调用,也不阻塞流程,无需在输出中专门说明。
|
|
303
|
+
咨询前先落盘成果,顾问意见与实测冲突时以实测为准并明确核对冲突。
|
|
304
|
+
补充:任何评审/评估/结论输出前,先用 adversarial-review 的方法论自我审查(拆原子主张、反向提问、去现场核对),再决定是否需要顾问。`,
|
|
251
305
|
},
|
|
252
306
|
{
|
|
253
307
|
id: 'capability.subagent', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|