minimal-agent 0.11.0 → 0.12.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 +4 -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/index.js +5 -3
- package/plugins/workflow-runner/src/index.ts +31 -13
- package/plugins/workflow-runner/src/multimodal.js +2 -1
- package/plugins/workflow-runner/src/multimodal.ts +2 -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 +68 -57
- package/plugins/workflow-runner/src/stepExecutors/tool.ts +88 -66
- package/plugins/workflow-runner/src/toolRuntime.js +48 -0
- package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
- package/plugins/workflow-runner/src/types.ts +26 -7
- package/plugins/workflow-runner/test/context-files.test.ts +47 -16
- package/plugins/workflow-runner/test/hostCapability.test.ts +9 -3
- package/plugins/workflow-runner/test/multimodal.test.ts +61 -10
- package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
- package/src/cli/print.js +19 -4
- package/src/cli/print.ts +17 -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/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/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 +6 -2
- package/src/plugins/pluginRunner.ts +12 -7
- package/src/prompts/policyRegistry.js +27 -0
- package/src/prompts/policyRegistry.ts +27 -0
- 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/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 +31 -11
- package/src/tools/bash/bash.ts +29 -10
- 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 +116 -26
- package/src/tools/powershell/powershell.ts +155 -30
- package/src/tools/presentresult/presentresult.js +10 -0
- package/src/tools/presentresult/presentresult.ts +10 -0
- package/src/tools/read/read.js +244 -29
- package/src/tools/read/read.ts +391 -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 +26 -0
- package/src/tools/shared/permanentDeleteGuard.ts +34 -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/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 +12 -0
- package/src/ui/hooks/useChat.js +31 -11
- package/src/ui/hooks/useChat.ts +31 -13
- package/src/web/protocol.ts +78 -3
|
@@ -25,6 +25,7 @@ import { existsSync } from 'node:fs';
|
|
|
25
25
|
import { resolve } from 'node:path';
|
|
26
26
|
import { executeTool, getToolByName, recordRead, } from '../../../../src/plugin-sdk.js';
|
|
27
27
|
import { interpolateDeep } from '../expressions.js';
|
|
28
|
+
import { buildWorkflowToolContext, effectiveWorkflowCwd, } from '../toolRuntime.js';
|
|
28
29
|
export async function* execToolStep(step, vars, ctx) {
|
|
29
30
|
if (!step.tool)
|
|
30
31
|
throw new Error(`step ${step.id}: 缺少 tool 字段`);
|
|
@@ -56,8 +57,8 @@ export async function* execToolStep(step, vars, ctx) {
|
|
|
56
57
|
argsFriendly: friendlyToolDesc(tool.name, args),
|
|
57
58
|
};
|
|
58
59
|
// ════ 声明产物覆盖判定(解 Write/Edit 的 pre-read 守卫) ════
|
|
59
|
-
// 内核写类工具有「文件已存在必须先 Read」守卫(fileState
|
|
60
|
-
// 跨进程 / resume /
|
|
60
|
+
// 内核写类工具有「文件已存在必须先 Read」守卫(fileState 按会话隔离,
|
|
61
|
+
// 跨进程 / resume / 残留旧产物时可能为空)。确定性 tool 步的 args 是 yaml 作者
|
|
61
62
|
// 写死的,模型没有机会先 Read —— 目标文件若是工作流**声明的产物**
|
|
62
63
|
// (def 级 + step 级 artifacts:,= 工作流自己拥有、本就要覆盖的文件),
|
|
63
64
|
// 这里自动 pre-Read 一次解除守卫(管线动作,不 yield 事件、结果丢弃)。
|
|
@@ -65,16 +66,60 @@ export async function* execToolStep(step, vars, ctx) {
|
|
|
65
66
|
await preReadDeclaredArtifact(tool, args, step, ctx);
|
|
66
67
|
// 宿主能力按 `uses:` 切片后转发。`executeTool` 的第四参此前从未传过 ——
|
|
67
68
|
// 这正是「workflow 里 ComputerUse 恒失败」的末端表现。
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const pendingProgress = [];
|
|
70
|
+
let wakeProgress;
|
|
71
|
+
const toolContext = buildWorkflowToolContext(step, ctx, toolCallId, (message, detail) => {
|
|
72
|
+
pendingProgress.push({
|
|
73
|
+
type: 'tool_progress',
|
|
74
|
+
toolName: tool.name,
|
|
75
|
+
toolCallId,
|
|
76
|
+
message,
|
|
77
|
+
detail,
|
|
78
|
+
});
|
|
79
|
+
wakeProgress?.();
|
|
80
|
+
});
|
|
81
|
+
if ('error' in toolContext) {
|
|
82
|
+
throw new Error(`step ${step.id}: ${toolContext.error}`);
|
|
83
|
+
}
|
|
84
|
+
const dispatch = ctx.executeTool ?? executeTool;
|
|
85
|
+
let settled = false;
|
|
86
|
+
let result;
|
|
87
|
+
void dispatch(tool.name, argsJson, ctx.signal, toolContext)
|
|
88
|
+
.then((value) => {
|
|
89
|
+
result = value;
|
|
90
|
+
})
|
|
91
|
+
.catch((error) => {
|
|
92
|
+
result = {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: error instanceof Error ? error.message : String(error),
|
|
95
|
+
};
|
|
96
|
+
})
|
|
97
|
+
.finally(() => {
|
|
98
|
+
settled = true;
|
|
99
|
+
wakeProgress?.();
|
|
100
|
+
});
|
|
101
|
+
while (!settled || pendingProgress.length > 0) {
|
|
102
|
+
const progress = pendingProgress.shift();
|
|
103
|
+
if (progress) {
|
|
104
|
+
yield progress;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
await new Promise((resolveWake) => {
|
|
108
|
+
wakeProgress = resolveWake;
|
|
109
|
+
if (settled || pendingProgress.length > 0)
|
|
110
|
+
resolveWake();
|
|
111
|
+
});
|
|
112
|
+
wakeProgress = undefined;
|
|
113
|
+
}
|
|
114
|
+
if (!result)
|
|
115
|
+
throw new Error(`step ${step.id}: 工具执行未返回结果`);
|
|
72
116
|
yield {
|
|
73
117
|
type: 'tool_end',
|
|
74
118
|
toolName: tool.name,
|
|
75
119
|
toolCallId,
|
|
76
120
|
ok: result.ok,
|
|
77
121
|
content: result.ok ? result.content : result.error,
|
|
122
|
+
metadata: result.metadata,
|
|
78
123
|
};
|
|
79
124
|
if (!result.ok) {
|
|
80
125
|
throw new Error(result.error);
|
|
@@ -101,56 +146,19 @@ export async function* execToolStep(step, vars, ctx) {
|
|
|
101
146
|
.map((image) => (typeof image.source === 'string' ? image.source : ''))
|
|
102
147
|
.filter((path) => path.length > 0);
|
|
103
148
|
return {
|
|
104
|
-
raw: {
|
|
149
|
+
raw: {
|
|
150
|
+
content,
|
|
151
|
+
result: content,
|
|
152
|
+
args,
|
|
153
|
+
images,
|
|
154
|
+
metadata: result.metadata,
|
|
155
|
+
recovery: result.recovery,
|
|
156
|
+
sensitivity: result.sensitivity,
|
|
157
|
+
visionNote: result.visionNote,
|
|
158
|
+
},
|
|
105
159
|
preview: content.length > 200 ? `${content.slice(0, 200)}...` : content,
|
|
106
160
|
};
|
|
107
161
|
}
|
|
108
|
-
/**
|
|
109
|
-
* 按 `step.uses` 从宿主上下文里切出这一步该拿到的那片。
|
|
110
|
-
*
|
|
111
|
-
* 三条语义,缺一条这个机制就退化:
|
|
112
|
-
* · 未声明 `uses` → 返回**空上下文**(不是 undefined,`toolGate` 这类与能力无关的
|
|
113
|
-
* 宿主策略仍要生效)。工具因此走自己的清晰报错,不会静默降级;
|
|
114
|
-
* · 声明了但宿主没提供 → **响亮报错**。yaml 说了要用电脑,而这次运行根本没开
|
|
115
|
-
* `--computer-use`,那是配置错误,应该在这一步就停,不是跑到一半才发现;
|
|
116
|
-
* · 只转发声明过的那一片 —— 声明 `[computer]` 的步拿不到 `browserRun`。
|
|
117
|
-
*
|
|
118
|
-
* 单一出处:切片逻辑只在这里,runner / 其它 executor 都不许自己拼。
|
|
119
|
-
*/
|
|
120
|
-
function hostContextFor(step, ctx) {
|
|
121
|
-
// toolGate 是宿主的通用策略缝(与具体能力无关),任何步都该受它约束。
|
|
122
|
-
const base = ctx.host?.toolGate
|
|
123
|
-
? { toolGate: ctx.host.toolGate }
|
|
124
|
-
: {};
|
|
125
|
-
const uses = step.uses ?? [];
|
|
126
|
-
if (uses.length === 0)
|
|
127
|
-
return { ctx: base };
|
|
128
|
-
const out = { ...base };
|
|
129
|
-
for (const capability of uses) {
|
|
130
|
-
if (capability === 'computer') {
|
|
131
|
-
if (!ctx.host?.computerRun) {
|
|
132
|
-
return {
|
|
133
|
-
error: 'uses:[computer] 声明了需要电脑操控能力,但本次运行的宿主没有提供 —— '
|
|
134
|
-
+ '请用 `--computer-use interactive` 启动,或去掉该步的 uses 声明。',
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
out.computerRun = ctx.host.computerRun;
|
|
138
|
-
// 人工接管通道与能力同进退:没有它,遇到验证/密码只能硬失败。
|
|
139
|
-
if (ctx.host.computerIntervention)
|
|
140
|
-
out.computerIntervention = ctx.host.computerIntervention;
|
|
141
|
-
}
|
|
142
|
-
else if (capability === 'browser') {
|
|
143
|
-
if (!ctx.host?.browserRun) {
|
|
144
|
-
return {
|
|
145
|
-
error: 'uses:[browser] 声明了需要浏览器授权上下文,但本次运行的宿主没有提供。'
|
|
146
|
-
+ '(基础浏览操作不声明 uses 也能用;upload/submit 这类才需要它。)',
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
out.browserRun = ctx.host.browserRun;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return { ctx: out };
|
|
153
|
-
}
|
|
154
162
|
/**
|
|
155
163
|
* 写类 tool 步的目标文件若命中声明产物且已存在 → pre-Read 解除内核守卫。
|
|
156
164
|
*
|
|
@@ -164,14 +172,17 @@ async function preReadDeclaredArtifact(tool, args, step, ctx) {
|
|
|
164
172
|
if (typeof args.file_path !== 'string')
|
|
165
173
|
return;
|
|
166
174
|
const norm = (p) => {
|
|
167
|
-
const abs = resolve(ctx
|
|
175
|
+
const abs = resolve(effectiveWorkflowCwd(ctx), p);
|
|
168
176
|
return process.platform === 'win32' ? abs.toLowerCase() : abs;
|
|
169
177
|
};
|
|
170
178
|
const target = norm(args.file_path);
|
|
171
|
-
const declared = [
|
|
179
|
+
const declared = [
|
|
180
|
+
...(ctx.declaredArtifacts ?? []),
|
|
181
|
+
...(step.artifacts ?? []),
|
|
182
|
+
];
|
|
172
183
|
if (!declared.some((a) => norm(a) === target))
|
|
173
184
|
return;
|
|
174
|
-
const abs = resolve(ctx
|
|
185
|
+
const abs = resolve(effectiveWorkflowCwd(ctx), args.file_path);
|
|
175
186
|
if (!existsSync(abs))
|
|
176
187
|
return; // 新建文件不需要解守卫
|
|
177
188
|
// 直接登记一次「完整已读」状态解除内核 pre-read 守卫——不真正 Read 内容:
|
|
@@ -182,7 +193,7 @@ async function preReadDeclaredArtifact(tool, args, step, ctx) {
|
|
|
182
193
|
// (首轮新建 PLAN.md 不存在直接放行,审批反馈后回写已存在的大 PLAN.md 被挡死)。
|
|
183
194
|
// 两道守卫(never-read + truncated)必须一并解除,声明产物的整体覆盖才算真正授权。
|
|
184
195
|
// recordRead 内部自带 statSync 失败兜底(返回 1、不抛),无需再包 try/catch。
|
|
185
|
-
recordRead(abs, { truncated: false });
|
|
196
|
+
recordRead(abs, { truncated: false }, ctx.sessionState?.fileReads);
|
|
186
197
|
}
|
|
187
198
|
/**
|
|
188
199
|
* 给 tool_start 生成人话描述(LiveArea spinner 行用)。
|
|
@@ -26,13 +26,17 @@ import { existsSync } from 'node:fs';
|
|
|
26
26
|
import { resolve } from 'node:path';
|
|
27
27
|
|
|
28
28
|
import {
|
|
29
|
+
type LoopEvent,
|
|
30
|
+
type ToolResult,
|
|
29
31
|
executeTool,
|
|
30
32
|
getToolByName,
|
|
31
33
|
recordRead,
|
|
32
|
-
type LoopEvent,
|
|
33
|
-
type ToolCallContext,
|
|
34
34
|
} from '../../../../src/plugin-sdk.js';
|
|
35
35
|
import { interpolateDeep } from '../expressions.js';
|
|
36
|
+
import {
|
|
37
|
+
buildWorkflowToolContext,
|
|
38
|
+
effectiveWorkflowCwd,
|
|
39
|
+
} from '../toolRuntime.js';
|
|
36
40
|
import type { RunContext, StepDef, StepResult, VarStack } from '../types.js';
|
|
37
41
|
|
|
38
42
|
export async function* execToolStep(
|
|
@@ -74,19 +78,73 @@ export async function* execToolStep(
|
|
|
74
78
|
};
|
|
75
79
|
|
|
76
80
|
// ════ 声明产物覆盖判定(解 Write/Edit 的 pre-read 守卫) ════
|
|
77
|
-
// 内核写类工具有「文件已存在必须先 Read」守卫(fileState
|
|
78
|
-
// 跨进程 / resume /
|
|
81
|
+
// 内核写类工具有「文件已存在必须先 Read」守卫(fileState 按会话隔离,
|
|
82
|
+
// 跨进程 / resume / 残留旧产物时可能为空)。确定性 tool 步的 args 是 yaml 作者
|
|
79
83
|
// 写死的,模型没有机会先 Read —— 目标文件若是工作流**声明的产物**
|
|
80
84
|
// (def 级 + step 级 artifacts:,= 工作流自己拥有、本就要覆盖的文件),
|
|
81
85
|
// 这里自动 pre-Read 一次解除守卫(管线动作,不 yield 事件、结果丢弃)。
|
|
82
86
|
// 非声明产物的盲写保护原样生效;pre-Read 失败也不拦,让写工具自己报错。
|
|
83
|
-
await preReadDeclaredArtifact(
|
|
87
|
+
await preReadDeclaredArtifact(
|
|
88
|
+
tool,
|
|
89
|
+
args as Record<string, unknown>,
|
|
90
|
+
step,
|
|
91
|
+
ctx,
|
|
92
|
+
);
|
|
84
93
|
|
|
85
94
|
// 宿主能力按 `uses:` 切片后转发。`executeTool` 的第四参此前从未传过 ——
|
|
86
95
|
// 这正是「workflow 里 ComputerUse 恒失败」的末端表现。
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
const
|
|
96
|
+
const pendingProgress: LoopEvent[] = [];
|
|
97
|
+
let wakeProgress: (() => void) | undefined;
|
|
98
|
+
const toolContext = buildWorkflowToolContext(
|
|
99
|
+
step,
|
|
100
|
+
ctx,
|
|
101
|
+
toolCallId,
|
|
102
|
+
(message, detail) => {
|
|
103
|
+
pendingProgress.push({
|
|
104
|
+
type: 'tool_progress',
|
|
105
|
+
toolName: tool.name,
|
|
106
|
+
toolCallId,
|
|
107
|
+
message,
|
|
108
|
+
detail,
|
|
109
|
+
});
|
|
110
|
+
wakeProgress?.();
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
if ('error' in toolContext) {
|
|
114
|
+
throw new Error(`step ${step.id}: ${toolContext.error}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const dispatch = ctx.executeTool ?? executeTool;
|
|
118
|
+
let settled = false;
|
|
119
|
+
let result: ToolResult | undefined;
|
|
120
|
+
void dispatch(tool.name, argsJson, ctx.signal, toolContext)
|
|
121
|
+
.then((value) => {
|
|
122
|
+
result = value;
|
|
123
|
+
})
|
|
124
|
+
.catch((error: unknown) => {
|
|
125
|
+
result = {
|
|
126
|
+
ok: false,
|
|
127
|
+
error: error instanceof Error ? error.message : String(error),
|
|
128
|
+
};
|
|
129
|
+
})
|
|
130
|
+
.finally(() => {
|
|
131
|
+
settled = true;
|
|
132
|
+
wakeProgress?.();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
while (!settled || pendingProgress.length > 0) {
|
|
136
|
+
const progress = pendingProgress.shift();
|
|
137
|
+
if (progress) {
|
|
138
|
+
yield progress;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
await new Promise<void>((resolveWake) => {
|
|
142
|
+
wakeProgress = resolveWake;
|
|
143
|
+
if (settled || pendingProgress.length > 0) resolveWake();
|
|
144
|
+
});
|
|
145
|
+
wakeProgress = undefined;
|
|
146
|
+
}
|
|
147
|
+
if (!result) throw new Error(`step ${step.id}: 工具执行未返回结果`);
|
|
90
148
|
|
|
91
149
|
yield {
|
|
92
150
|
type: 'tool_end',
|
|
@@ -94,6 +152,7 @@ export async function* execToolStep(
|
|
|
94
152
|
toolCallId,
|
|
95
153
|
ok: result.ok,
|
|
96
154
|
content: result.ok ? result.content : result.error,
|
|
155
|
+
metadata: result.metadata,
|
|
97
156
|
};
|
|
98
157
|
|
|
99
158
|
if (!result.ok) {
|
|
@@ -105,9 +164,10 @@ export async function* execToolStep(
|
|
|
105
164
|
// 后续 agent/llm/skill 步骤走 runQuery() 时,LLM 能在上下文中看到
|
|
106
165
|
// 这步做了什么(与正常 TAOR 循环中 tool_call → tool_result 的记录方式一致)。
|
|
107
166
|
// 只记录精简摘要(不把完整工具回执塞入 history,避免 token 浪费)。
|
|
108
|
-
const summary =
|
|
109
|
-
|
|
110
|
-
|
|
167
|
+
const summary =
|
|
168
|
+
content.length > 500
|
|
169
|
+
? `${content.slice(0, 500)}…(已截断,共 ${content.length} 字符)`
|
|
170
|
+
: content;
|
|
111
171
|
ctx.history.push({
|
|
112
172
|
role: 'assistant',
|
|
113
173
|
content: `[workflow:${step.id}] ✅ ${tool.name} 执行完成:${summary}`,
|
|
@@ -124,61 +184,20 @@ export async function* execToolStep(
|
|
|
124
184
|
.filter((path) => path.length > 0);
|
|
125
185
|
|
|
126
186
|
return {
|
|
127
|
-
raw: {
|
|
187
|
+
raw: {
|
|
188
|
+
content,
|
|
189
|
+
result: content,
|
|
190
|
+
args,
|
|
191
|
+
images,
|
|
192
|
+
metadata: result.metadata,
|
|
193
|
+
recovery: result.recovery,
|
|
194
|
+
sensitivity: result.sensitivity,
|
|
195
|
+
visionNote: result.visionNote,
|
|
196
|
+
},
|
|
128
197
|
preview: content.length > 200 ? `${content.slice(0, 200)}...` : content,
|
|
129
198
|
};
|
|
130
199
|
}
|
|
131
200
|
|
|
132
|
-
/**
|
|
133
|
-
* 按 `step.uses` 从宿主上下文里切出这一步该拿到的那片。
|
|
134
|
-
*
|
|
135
|
-
* 三条语义,缺一条这个机制就退化:
|
|
136
|
-
* · 未声明 `uses` → 返回**空上下文**(不是 undefined,`toolGate` 这类与能力无关的
|
|
137
|
-
* 宿主策略仍要生效)。工具因此走自己的清晰报错,不会静默降级;
|
|
138
|
-
* · 声明了但宿主没提供 → **响亮报错**。yaml 说了要用电脑,而这次运行根本没开
|
|
139
|
-
* `--computer-use`,那是配置错误,应该在这一步就停,不是跑到一半才发现;
|
|
140
|
-
* · 只转发声明过的那一片 —— 声明 `[computer]` 的步拿不到 `browserRun`。
|
|
141
|
-
*
|
|
142
|
-
* 单一出处:切片逻辑只在这里,runner / 其它 executor 都不许自己拼。
|
|
143
|
-
*/
|
|
144
|
-
function hostContextFor(
|
|
145
|
-
step: StepDef,
|
|
146
|
-
ctx: RunContext,
|
|
147
|
-
): { ctx: ToolCallContext } | { error: string } {
|
|
148
|
-
// toolGate 是宿主的通用策略缝(与具体能力无关),任何步都该受它约束。
|
|
149
|
-
const base: ToolCallContext = ctx.host?.toolGate
|
|
150
|
-
? { toolGate: ctx.host.toolGate }
|
|
151
|
-
: {};
|
|
152
|
-
const uses = step.uses ?? [];
|
|
153
|
-
if (uses.length === 0) return { ctx: base };
|
|
154
|
-
|
|
155
|
-
const out: ToolCallContext = { ...base };
|
|
156
|
-
for (const capability of uses) {
|
|
157
|
-
if (capability === 'computer') {
|
|
158
|
-
if (!ctx.host?.computerRun) {
|
|
159
|
-
return {
|
|
160
|
-
error:
|
|
161
|
-
'uses:[computer] 声明了需要电脑操控能力,但本次运行的宿主没有提供 —— '
|
|
162
|
-
+ '请用 `--computer-use interactive` 启动,或去掉该步的 uses 声明。',
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
out.computerRun = ctx.host.computerRun;
|
|
166
|
-
// 人工接管通道与能力同进退:没有它,遇到验证/密码只能硬失败。
|
|
167
|
-
if (ctx.host.computerIntervention) out.computerIntervention = ctx.host.computerIntervention;
|
|
168
|
-
} else if (capability === 'browser') {
|
|
169
|
-
if (!ctx.host?.browserRun) {
|
|
170
|
-
return {
|
|
171
|
-
error:
|
|
172
|
-
'uses:[browser] 声明了需要浏览器授权上下文,但本次运行的宿主没有提供。'
|
|
173
|
-
+ '(基础浏览操作不声明 uses 也能用;upload/submit 这类才需要它。)',
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
out.browserRun = ctx.host.browserRun;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return { ctx: out };
|
|
180
|
-
}
|
|
181
|
-
|
|
182
201
|
/**
|
|
183
202
|
* 写类 tool 步的目标文件若命中声明产物且已存在 → pre-Read 解除内核守卫。
|
|
184
203
|
*
|
|
@@ -196,14 +215,17 @@ async function preReadDeclaredArtifact(
|
|
|
196
215
|
if (typeof args.file_path !== 'string') return;
|
|
197
216
|
|
|
198
217
|
const norm = (p: string): string => {
|
|
199
|
-
const abs = resolve(ctx
|
|
218
|
+
const abs = resolve(effectiveWorkflowCwd(ctx), p);
|
|
200
219
|
return process.platform === 'win32' ? abs.toLowerCase() : abs;
|
|
201
220
|
};
|
|
202
221
|
const target = norm(args.file_path);
|
|
203
|
-
const declared = [
|
|
222
|
+
const declared = [
|
|
223
|
+
...(ctx.declaredArtifacts ?? []),
|
|
224
|
+
...(step.artifacts ?? []),
|
|
225
|
+
];
|
|
204
226
|
if (!declared.some((a) => norm(a) === target)) return;
|
|
205
227
|
|
|
206
|
-
const abs = resolve(ctx
|
|
228
|
+
const abs = resolve(effectiveWorkflowCwd(ctx), args.file_path);
|
|
207
229
|
if (!existsSync(abs)) return; // 新建文件不需要解守卫
|
|
208
230
|
|
|
209
231
|
// 直接登记一次「完整已读」状态解除内核 pre-read 守卫——不真正 Read 内容:
|
|
@@ -214,7 +236,7 @@ async function preReadDeclaredArtifact(
|
|
|
214
236
|
// (首轮新建 PLAN.md 不存在直接放行,审批反馈后回写已存在的大 PLAN.md 被挡死)。
|
|
215
237
|
// 两道守卫(never-read + truncated)必须一并解除,声明产物的整体覆盖才算真正授权。
|
|
216
238
|
// recordRead 内部自带 statSync 失败兜底(返回 1、不抛),无需再包 try/catch。
|
|
217
|
-
recordRead(abs, { truncated: false });
|
|
239
|
+
recordRead(abs, { truncated: false }, ctx.sessionState?.fileReads);
|
|
218
240
|
}
|
|
219
241
|
|
|
220
242
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The workflow identity stays anchored to the registered workspace, while file
|
|
3
|
+
* and process tools follow a session worktree as soon as EnterWorktree creates
|
|
4
|
+
* one. Keeping this decision in one helper prevents executors from drifting.
|
|
5
|
+
*/
|
|
6
|
+
export function effectiveWorkflowCwd(ctx) {
|
|
7
|
+
return ctx.sessionState?.activeWorktree?.path ?? ctx.cwd;
|
|
8
|
+
}
|
|
9
|
+
export function buildWorkflowToolContext(step, ctx, toolCallId, reportProgress) {
|
|
10
|
+
const context = {
|
|
11
|
+
...ctx.toolContext,
|
|
12
|
+
history: ctx.history,
|
|
13
|
+
sessionState: ctx.sessionState,
|
|
14
|
+
provider: ctx.provider,
|
|
15
|
+
toolGate: ctx.host?.toolGate ?? ctx.toolContext?.toolGate,
|
|
16
|
+
promptCache: ctx.promptCache ?? ctx.toolContext?.promptCache,
|
|
17
|
+
workspaceId: ctx.runtimeContext?.workspaceId ?? ctx.toolContext?.workspaceId,
|
|
18
|
+
sessionId: ctx.runtimeContext?.sessionId ?? ctx.toolContext?.sessionId,
|
|
19
|
+
memoryAccess: ctx.runtimeContext?.memoryAccess ?? ctx.toolContext?.memoryAccess,
|
|
20
|
+
cwd: effectiveWorkflowCwd(ctx),
|
|
21
|
+
toolCallId,
|
|
22
|
+
reportProgress,
|
|
23
|
+
};
|
|
24
|
+
for (const capability of step.uses ?? []) {
|
|
25
|
+
if (capability === 'computer') {
|
|
26
|
+
if (!ctx.host?.computerRun) {
|
|
27
|
+
return {
|
|
28
|
+
error: 'uses:[computer] 声明了需要电脑操控能力,但本次运行的宿主没有提供 —— ' +
|
|
29
|
+
'请在支持 Computer Use 的宿主启动,或去掉该步的 uses 声明。',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
context.computerRun = ctx.host.computerRun;
|
|
33
|
+
if (ctx.host.computerIntervention) {
|
|
34
|
+
context.computerIntervention = ctx.host.computerIntervention;
|
|
35
|
+
}
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (capability === 'browser') {
|
|
39
|
+
if (!ctx.host?.browserRun) {
|
|
40
|
+
return {
|
|
41
|
+
error: 'uses:[browser] 声明了需要浏览器授权上下文,但本次运行的宿主没有提供。',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
context.browserRun = ctx.host.browserRun;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return context;
|
|
48
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { ToolCallContext } from '../../../src/plugin-sdk.js';
|
|
2
|
+
import type { RunContext, StepDef } from './types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The workflow identity stays anchored to the registered workspace, while file
|
|
6
|
+
* and process tools follow a session worktree as soon as EnterWorktree creates
|
|
7
|
+
* one. Keeping this decision in one helper prevents executors from drifting.
|
|
8
|
+
*/
|
|
9
|
+
export function effectiveWorkflowCwd(ctx: RunContext): string {
|
|
10
|
+
return ctx.sessionState?.activeWorktree?.path ?? ctx.cwd;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildWorkflowToolContext(
|
|
14
|
+
step: StepDef,
|
|
15
|
+
ctx: RunContext,
|
|
16
|
+
toolCallId: string,
|
|
17
|
+
reportProgress: NonNullable<ToolCallContext['reportProgress']>,
|
|
18
|
+
): ToolCallContext | { error: string } {
|
|
19
|
+
const context: ToolCallContext = {
|
|
20
|
+
...ctx.toolContext,
|
|
21
|
+
history: ctx.history,
|
|
22
|
+
sessionState: ctx.sessionState,
|
|
23
|
+
provider: ctx.provider,
|
|
24
|
+
toolGate: ctx.host?.toolGate ?? ctx.toolContext?.toolGate,
|
|
25
|
+
promptCache: ctx.promptCache ?? ctx.toolContext?.promptCache,
|
|
26
|
+
workspaceId:
|
|
27
|
+
ctx.runtimeContext?.workspaceId ?? ctx.toolContext?.workspaceId,
|
|
28
|
+
sessionId: ctx.runtimeContext?.sessionId ?? ctx.toolContext?.sessionId,
|
|
29
|
+
memoryAccess:
|
|
30
|
+
ctx.runtimeContext?.memoryAccess ?? ctx.toolContext?.memoryAccess,
|
|
31
|
+
cwd: effectiveWorkflowCwd(ctx),
|
|
32
|
+
toolCallId,
|
|
33
|
+
reportProgress,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (const capability of step.uses ?? []) {
|
|
37
|
+
if (capability === 'computer') {
|
|
38
|
+
if (!ctx.host?.computerRun) {
|
|
39
|
+
return {
|
|
40
|
+
error:
|
|
41
|
+
'uses:[computer] 声明了需要电脑操控能力,但本次运行的宿主没有提供 —— ' +
|
|
42
|
+
'请在支持 Computer Use 的宿主启动,或去掉该步的 uses 声明。',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
context.computerRun = ctx.host.computerRun;
|
|
46
|
+
if (ctx.host.computerIntervention) {
|
|
47
|
+
context.computerIntervention = ctx.host.computerIntervention;
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (capability === 'browser') {
|
|
53
|
+
if (!ctx.host?.browserRun) {
|
|
54
|
+
return {
|
|
55
|
+
error:
|
|
56
|
+
'uses:[browser] 声明了需要浏览器授权上下文,但本次运行的宿主没有提供。',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
context.browserRun = ctx.host.browserRun;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return context;
|
|
64
|
+
}
|
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
import type {
|
|
17
17
|
ImageDetail,
|
|
18
18
|
Message,
|
|
19
|
+
PluginContext,
|
|
19
20
|
Provider,
|
|
20
21
|
SessionState,
|
|
21
22
|
Tool,
|
|
23
|
+
ToolCallContext,
|
|
22
24
|
} from '../../../src/plugin-sdk.js';
|
|
23
25
|
|
|
24
26
|
// ---------------- 1. Workflow / Step 定义 ----------------
|
|
@@ -237,7 +239,7 @@ export interface StepDef {
|
|
|
237
239
|
/** ADR-05: 仅 llm 节点声明的上下文文件清单(skill 节点 loader 拒绝);runner 拼 path+hint 进 prompt 并开放 Read 子循环,不 inline 内容。 */
|
|
238
240
|
context_files?: Array<{ path: string; hint?: string }>;
|
|
239
241
|
|
|
240
|
-
/** ADR-05: agent
|
|
242
|
+
/** ADR-05: agent 节点的工具白名单;未声明时继承宿主过滤后的有效工具集。 */
|
|
241
243
|
allowed_tools?: string[];
|
|
242
244
|
|
|
243
245
|
/**
|
|
@@ -437,10 +439,9 @@ export interface RunContext {
|
|
|
437
439
|
cwd: string;
|
|
438
440
|
/**
|
|
439
441
|
* 可用工具列表。
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
443
|
-
* 以及测试兼容。生产路径由 plugin.ts 注入 ALL_TOOLS。
|
|
442
|
+
* agent/llm/skill 与确定性 tool 步都以此列表为宿主授权上限。
|
|
443
|
+
* plugin.ts 先按 runtime 和父宿主白名单过滤,再把同一列表交给提示词、
|
|
444
|
+
* Schema 和真实执行,任何工作流节点都不能从 ALL_TOOLS 恢复隐藏能力。
|
|
444
445
|
*/
|
|
445
446
|
tools: Tool[];
|
|
446
447
|
transientContext?: string;
|
|
@@ -494,11 +495,29 @@ export interface RunContext {
|
|
|
494
495
|
tools: import('../../../src/plugin-sdk.js').Tool[];
|
|
495
496
|
signal?: AbortSignal;
|
|
496
497
|
promptCache?: import('../../../src/plugin-sdk.js').PromptCacheContext;
|
|
497
|
-
}) => AsyncGenerator<
|
|
498
|
+
}) => AsyncGenerator<
|
|
499
|
+
import('../../../src/plugin-sdk.js').LlmStreamEvent,
|
|
500
|
+
void,
|
|
501
|
+
undefined
|
|
502
|
+
>;
|
|
498
503
|
/**
|
|
499
504
|
* DI:executeTool 实现(仅测试用)。同上。
|
|
500
505
|
*/
|
|
501
|
-
executeTool?:
|
|
506
|
+
executeTool?: PluginContext['executeTool'];
|
|
507
|
+
/**
|
|
508
|
+
* Canonical non-model-visible context shared by deterministic tool nodes.
|
|
509
|
+
* Browser/computer capabilities are deliberately excluded here and are only
|
|
510
|
+
* added by the step-level `uses` slicer.
|
|
511
|
+
*/
|
|
512
|
+
toolContext?: Omit<
|
|
513
|
+
ToolCallContext,
|
|
514
|
+
| 'browserRun'
|
|
515
|
+
| 'computerRun'
|
|
516
|
+
| 'computerIntervention'
|
|
517
|
+
| 'reportProgress'
|
|
518
|
+
| 'toolCallId'
|
|
519
|
+
| 'cwd'
|
|
520
|
+
>;
|
|
502
521
|
/**
|
|
503
522
|
* 会话状态(驱动 microCompact / reactiveCompact)。
|
|
504
523
|
*
|