@gt-fe/eap-runtime 0.0.8 → 0.0.10
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 +12 -17
- package/dist/api/handlers/invoke.handler.js +3 -3
- package/dist/api/handlers/messages.handler.js +0 -1
- package/dist/api/handlers/stream.handler.js +3 -3
- package/dist/api/routes/agent.routes.js +2 -2
- package/dist/checkpoint/checkpoint-ref-notify.d.ts +10 -0
- package/dist/checkpoint/checkpoint-ref-notify.js +59 -0
- package/dist/checkpoint/index.d.ts +1 -0
- package/dist/checkpoint/index.js +1 -0
- package/dist/checkpoint/pg-checkpoint-saver.js +25 -2
- package/dist/common/agent-code.d.ts +2 -4
- package/dist/common/agent-code.js +4 -5
- package/dist/contracts/trace-collector.d.ts +15 -1
- package/dist/debug/llm-turn-log.d.ts +100 -0
- package/dist/debug/llm-turn-log.js +130 -0
- package/dist/debug/pg-llm-turn-log.d.ts +22 -0
- package/dist/debug/pg-llm-turn-log.js +57 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +1 -0
- package/dist/graph/adapters/langgraph.adapter.js +147 -91
- package/dist/graph/adapters/skill-read.d.ts +12 -0
- package/dist/graph/adapters/skill-read.js +63 -0
- package/dist/graph/adapters/subgraph-compiler.js +5 -0
- package/dist/graph/contracts/executable-graph.d.ts +3 -3
- package/dist/graph/hitl/hitl-handler.d.ts +7 -0
- package/dist/graph/hitl/hitl-handler.js +47 -6
- package/dist/graph/nodes/llm-node.d.ts +14 -0
- package/dist/graph/nodes/skill-bind-node.js +1 -2
- package/dist/graph/nodes/tool-node.d.ts +2 -0
- package/dist/graph/nodes/tool-node.js +24 -9
- package/dist/graph/trace-span.d.ts +22 -0
- package/dist/graph/trace-span.js +69 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/memory/compression/compression.repository.d.ts +1 -15
- package/dist/providers/echo-llm-provider.d.ts +1 -1
- package/dist/providers/echo-llm-provider.js +2 -1
- package/dist/providers/http-llm-provider.d.ts +4 -0
- package/dist/providers/http-llm-provider.js +129 -4
- package/dist/providers/local-llm-provider.d.ts +5 -1
- package/dist/providers/local-llm-provider.js +14 -6
- package/dist/providers/routing-llm-provider.d.ts +4 -0
- package/dist/providers/routing-llm-provider.js +12 -2
- package/dist/registry/agent-models.d.ts +1 -1
- package/dist/registry/agent-models.js +1 -1
- package/dist/registry/agent-registry-client.d.ts +2 -2
- package/dist/registry/deployment-snapshot.d.ts +4 -4
- package/dist/registry/deployment-snapshot.js +50 -28
- package/dist/registry/graph-loader.d.ts +1 -1
- package/dist/registry/graph-loader.js +39 -76
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/skill-tree.d.ts +3 -0
- package/dist/registry/skill-tree.js +41 -3
- package/dist/runtime/agent-runtime.d.ts +42 -3
- package/dist/runtime/agent-runtime.js +110 -34
- package/dist/runtime/dev-runtime.d.ts +6 -3
- package/dist/runtime/dev-runtime.js +21 -14
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/storage-profile.d.ts +19 -10
- package/dist/runtime/storage-profile.js +42 -29
- package/dist/runtime/types.d.ts +15 -17
- package/dist/server.js +1 -1
- package/dist/session/session.service.d.ts +5 -0
- package/dist/session/session.service.js +12 -0
- package/dist/sse/events/index.d.ts +5 -3
- package/dist/sse/events/index.js +2 -0
- package/dist/sse/events/skill-events.d.ts +11 -0
- package/dist/sse/trace-collector.d.ts +10 -0
- package/dist/sse/trace-collector.js +1 -0
- package/dist/token/token-budget.service.d.ts +2 -2
- package/dist/token/token-budget.service.js +1 -1
- package/dist/tool/create-tool-executor.js +3 -2
- package/dist/tool/harness-primitives.d.ts +32 -0
- package/dist/tool/harness-primitives.js +385 -0
- package/dist/tool/http-tool-executor.js +1 -1
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/local-tool-executor.d.ts +3 -3
- package/dist/tool/local-tool-executor.js +5 -7
- package/dist/tool/mcp-tool-executor.js +9 -1
- package/dist/tool/system-prompt-builder.d.ts +61 -25
- package/dist/tool/system-prompt-builder.js +195 -92
- package/dist/tool/tool-execution-router.js +1 -1
- package/dist/tool/tool-executor.interface.d.ts +12 -6
- package/package.json +2 -2
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台默认 Harness 原语:read_file / write_file / execute_command。
|
|
3
|
+
*
|
|
4
|
+
* 不写进 agent.manifest.toolsets。同名业务工具优先(问数 file-io 等)。
|
|
5
|
+
* 读:工作目录 + 技能包 + EAP_LOCAL_SKILLS_DIR + FILE_IO_ROOT。
|
|
6
|
+
* 写:仅工作目录,且不跟随符号链接。
|
|
7
|
+
* 命令:cwd 落在 jail 内;环境变量白名单,不继承 process.env。
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import { constants as fsConstants, realpathSync } from 'node:fs';
|
|
11
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { BUILTIN_TOOL_SOURCE, DEFAULT_BUILTIN_TIMEOUT_MS, MAX_BUILTIN_TIMEOUT_MS, clampBuiltinTimeout, isBuiltinToolExecution, resolveBuiltinToolsConfig, } from '@gt-fe/eap-contracts';
|
|
14
|
+
function builtinConfig() {
|
|
15
|
+
return resolveBuiltinToolsConfig(process.env);
|
|
16
|
+
}
|
|
17
|
+
function harnessDefinitions() {
|
|
18
|
+
const common = {
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
type: 'code',
|
|
21
|
+
status: 'active',
|
|
22
|
+
toolRuntime: 'local',
|
|
23
|
+
execution: { source: BUILTIN_TOOL_SOURCE },
|
|
24
|
+
};
|
|
25
|
+
return [
|
|
26
|
+
{
|
|
27
|
+
toolId: 'builtin:read_file',
|
|
28
|
+
name: 'read_file',
|
|
29
|
+
manifest: {
|
|
30
|
+
id: 'builtin:read_file',
|
|
31
|
+
name: 'read_file',
|
|
32
|
+
description: '读取工作目录或技能目录内的文本。要用某个技能时读该技能文件夹下的 SKILL.md。超限会截断并注明。',
|
|
33
|
+
...common,
|
|
34
|
+
},
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
required: ['path'],
|
|
38
|
+
properties: {
|
|
39
|
+
path: { type: 'string', description: '相对工作目录或技能根的路径' },
|
|
40
|
+
offset: { type: 'integer', description: '从第几行开始读,从 1 起。省略则从头。' },
|
|
41
|
+
limit: { type: 'integer', description: '最多读多少行。省略则读到末尾或字节上限。' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
toolId: 'builtin:write_file',
|
|
47
|
+
name: 'write_file',
|
|
48
|
+
manifest: {
|
|
49
|
+
id: 'builtin:write_file',
|
|
50
|
+
name: 'write_file',
|
|
51
|
+
description: '写入工作目录内的文本文件。技能目录只读,禁止写出 jail。',
|
|
52
|
+
...common,
|
|
53
|
+
},
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
required: ['path', 'content'],
|
|
57
|
+
properties: {
|
|
58
|
+
path: { type: 'string' },
|
|
59
|
+
content: { type: 'string' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
toolId: 'builtin:execute_command',
|
|
65
|
+
name: 'execute_command',
|
|
66
|
+
manifest: {
|
|
67
|
+
id: 'builtin:execute_command',
|
|
68
|
+
name: 'execute_command',
|
|
69
|
+
description: `在 jail 内执行命令(跑技能包 scripts、pip 等)。默认超时 ${DEFAULT_BUILTIN_TIMEOUT_MS}ms,可用 timeoutMs 延长,上限 ${MAX_BUILTIN_TIMEOUT_MS}ms。不继承密钥环境变量。`,
|
|
70
|
+
...common,
|
|
71
|
+
},
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
required: ['command'],
|
|
75
|
+
properties: {
|
|
76
|
+
command: { type: 'string' },
|
|
77
|
+
cwd: { type: 'string', description: '相对工作目录的子目录,可选' },
|
|
78
|
+
timeoutMs: {
|
|
79
|
+
type: 'integer',
|
|
80
|
+
description: `超时毫秒。省略则 ${DEFAULT_BUILTIN_TIMEOUT_MS}。上限 ${MAX_BUILTIN_TIMEOUT_MS}。`,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
export function mergeHarnessPrimitives(loadedTools) {
|
|
88
|
+
if (!builtinConfig().enabled) {
|
|
89
|
+
return loadedTools;
|
|
90
|
+
}
|
|
91
|
+
const merged = { ...(loadedTools ?? {}) };
|
|
92
|
+
for (const tool of harnessDefinitions()) {
|
|
93
|
+
if (!merged[tool.name])
|
|
94
|
+
merged[tool.name] = tool;
|
|
95
|
+
}
|
|
96
|
+
return merged;
|
|
97
|
+
}
|
|
98
|
+
/** 装配点:业务 toolsets + 平台 Harness 原语。 */
|
|
99
|
+
export function assembleLoadedTools(loadedTools) {
|
|
100
|
+
return mergeHarnessPrimitives(loadedTools);
|
|
101
|
+
}
|
|
102
|
+
function defaultExtraRoots() {
|
|
103
|
+
const extra = [path.resolve(process.cwd(), 'skills')];
|
|
104
|
+
for (const key of ['EAP_LOCAL_SKILLS_DIR', 'FILE_IO_ROOT']) {
|
|
105
|
+
const value = process.env[key]?.trim();
|
|
106
|
+
if (value)
|
|
107
|
+
extra.push(value);
|
|
108
|
+
}
|
|
109
|
+
return extra;
|
|
110
|
+
}
|
|
111
|
+
function tryRealpath(p) {
|
|
112
|
+
try {
|
|
113
|
+
return realpathSync(p);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function resolveJailRoots(options) {
|
|
120
|
+
const workdir = path.resolve(options?.workdir ?? process.cwd());
|
|
121
|
+
const candidates = [workdir, ...defaultExtraRoots(), ...(options?.extraRoots ?? [])];
|
|
122
|
+
const roots = [];
|
|
123
|
+
for (const raw of candidates) {
|
|
124
|
+
const resolved = path.resolve(raw);
|
|
125
|
+
const real = tryRealpath(resolved) ?? resolved;
|
|
126
|
+
if (!roots.includes(real))
|
|
127
|
+
roots.push(real);
|
|
128
|
+
}
|
|
129
|
+
return roots;
|
|
130
|
+
}
|
|
131
|
+
function sandboxEnv(workdir, cwd, allowlist) {
|
|
132
|
+
const env = {
|
|
133
|
+
PATH: process.env.PATH ?? '/usr/bin:/bin:/usr/local/bin',
|
|
134
|
+
LANG: process.env.LANG ?? 'C.UTF-8',
|
|
135
|
+
LC_ALL: process.env.LC_ALL ?? process.env.LANG ?? 'C.UTF-8',
|
|
136
|
+
HOME: workdir,
|
|
137
|
+
TMPDIR: workdir,
|
|
138
|
+
TMP: workdir,
|
|
139
|
+
TEMP: workdir,
|
|
140
|
+
PWD: cwd,
|
|
141
|
+
};
|
|
142
|
+
const pinned = new Set(['HOME', 'TMPDIR', 'TMP', 'TEMP', 'PWD']);
|
|
143
|
+
for (const key of allowlist) {
|
|
144
|
+
if (pinned.has(key))
|
|
145
|
+
continue;
|
|
146
|
+
const value = process.env[key];
|
|
147
|
+
if (value !== undefined)
|
|
148
|
+
env[key] = value;
|
|
149
|
+
}
|
|
150
|
+
return env;
|
|
151
|
+
}
|
|
152
|
+
async function writeFileNoFollow(target, content) {
|
|
153
|
+
const flags = fsConstants.O_WRONLY |
|
|
154
|
+
fsConstants.O_CREAT |
|
|
155
|
+
fsConstants.O_TRUNC |
|
|
156
|
+
(fsConstants.O_NOFOLLOW ?? 0);
|
|
157
|
+
let handle;
|
|
158
|
+
try {
|
|
159
|
+
handle = await open(target, flags, 0o644);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
const code = err.code;
|
|
163
|
+
if (code === 'ELOOP') {
|
|
164
|
+
throw new Error('拒绝写入符号链接');
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
await handle.writeFile(content, 'utf8');
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
await handle.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function isInsideRoot(candidate, root) {
|
|
176
|
+
const rel = path.relative(root, candidate);
|
|
177
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
178
|
+
}
|
|
179
|
+
function resolveViaExistingAncestor(abs) {
|
|
180
|
+
const suffix = [];
|
|
181
|
+
let cur = abs;
|
|
182
|
+
while (true) {
|
|
183
|
+
const real = tryRealpath(cur);
|
|
184
|
+
if (real)
|
|
185
|
+
return path.resolve(real, ...suffix);
|
|
186
|
+
const parent = path.dirname(cur);
|
|
187
|
+
if (parent === cur)
|
|
188
|
+
return abs;
|
|
189
|
+
suffix.unshift(path.basename(cur));
|
|
190
|
+
cur = parent;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
export function resolveJailedPath(requested, roots, opts) {
|
|
194
|
+
const trimmed = requested.trim();
|
|
195
|
+
if (!trimmed || trimmed.includes('\0')) {
|
|
196
|
+
throw new Error('非法路径');
|
|
197
|
+
}
|
|
198
|
+
const workdir = roots[0] ?? path.resolve(process.cwd());
|
|
199
|
+
const abs = path.isAbsolute(trimmed) ? path.normalize(trimmed) : path.resolve(workdir, trimmed);
|
|
200
|
+
const existing = tryRealpath(abs);
|
|
201
|
+
if (opts?.mustExist && !existing) {
|
|
202
|
+
throw new Error(`文件不存在:${trimmed}`);
|
|
203
|
+
}
|
|
204
|
+
const target = existing ?? resolveViaExistingAncestor(abs);
|
|
205
|
+
if (!roots.some((root) => isInsideRoot(target, root))) {
|
|
206
|
+
throw new Error(`路径超出允许目录:${trimmed}`);
|
|
207
|
+
}
|
|
208
|
+
return target;
|
|
209
|
+
}
|
|
210
|
+
function fail(request, error) {
|
|
211
|
+
return {
|
|
212
|
+
toolCallId: request.toolCallId,
|
|
213
|
+
toolName: request.toolName,
|
|
214
|
+
content: '',
|
|
215
|
+
success: false,
|
|
216
|
+
error,
|
|
217
|
+
runtime: 'local',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function ok(request, content) {
|
|
221
|
+
return {
|
|
222
|
+
toolCallId: request.toolCallId,
|
|
223
|
+
toolName: request.toolName,
|
|
224
|
+
content,
|
|
225
|
+
success: true,
|
|
226
|
+
runtime: 'local',
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
export class HarnessPrimitiveExecutor {
|
|
230
|
+
workdir;
|
|
231
|
+
extraRoots;
|
|
232
|
+
constructor(options) {
|
|
233
|
+
this.workdir = options?.workdir;
|
|
234
|
+
this.extraRoots = options?.extraRoots;
|
|
235
|
+
}
|
|
236
|
+
rootsFor(request) {
|
|
237
|
+
return resolveJailRoots({
|
|
238
|
+
workdir: this.workdir,
|
|
239
|
+
extraRoots: [...(this.extraRoots ?? []), ...(request.jailExtraRoots ?? [])],
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async execute(request) {
|
|
243
|
+
try {
|
|
244
|
+
switch (request.toolName) {
|
|
245
|
+
case 'read_file':
|
|
246
|
+
return await this.readFile(request);
|
|
247
|
+
case 'write_file':
|
|
248
|
+
return await this.writeFile(request);
|
|
249
|
+
case 'execute_command':
|
|
250
|
+
return await this.executeCommand(request);
|
|
251
|
+
default:
|
|
252
|
+
return fail(request, `未知 Harness 原语:${request.toolName}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
return fail(request, err instanceof Error ? err.message : String(err));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async readFile(request) {
|
|
260
|
+
const filePath = String(request.arguments.path ?? request.arguments.file ?? '');
|
|
261
|
+
const target = resolveJailedPath(filePath, this.rootsFor(request), { mustExist: true });
|
|
262
|
+
const buf = await readFile(target);
|
|
263
|
+
const config = builtinConfig();
|
|
264
|
+
const lines = buf.toString('utf8').split('\n');
|
|
265
|
+
const startLine = typeof request.arguments.offset === 'number' && request.arguments.offset > 1
|
|
266
|
+
? Math.floor(request.arguments.offset) - 1
|
|
267
|
+
: 0;
|
|
268
|
+
const maxLines = typeof request.arguments.limit === 'number' && request.arguments.limit > 0
|
|
269
|
+
? Math.floor(request.arguments.limit)
|
|
270
|
+
: undefined;
|
|
271
|
+
const sliced = maxLines != null ? lines.slice(startLine, startLine + maxLines) : lines.slice(startLine);
|
|
272
|
+
let text = sliced.join('\n');
|
|
273
|
+
const notes = [];
|
|
274
|
+
if (startLine > 0 || maxLines != null) {
|
|
275
|
+
notes.push(`showing lines ${startLine + 1}-${startLine + sliced.length} of ${lines.length}`);
|
|
276
|
+
}
|
|
277
|
+
const byteLength = Buffer.byteLength(text, 'utf8');
|
|
278
|
+
if (byteLength > config.maxReadBytes) {
|
|
279
|
+
let cut = text;
|
|
280
|
+
while (Buffer.byteLength(cut, 'utf8') > config.maxReadBytes && cut.length > 0) {
|
|
281
|
+
cut = cut.slice(0, Math.floor(cut.length * 0.9));
|
|
282
|
+
}
|
|
283
|
+
text = cut;
|
|
284
|
+
notes.push(`truncated to ${config.maxReadBytes} bytes (file was ${buf.byteLength} bytes)`);
|
|
285
|
+
}
|
|
286
|
+
if (notes.length > 0) {
|
|
287
|
+
text = `${text}\n[${notes.join('; ')}]`;
|
|
288
|
+
}
|
|
289
|
+
return ok(request, text);
|
|
290
|
+
}
|
|
291
|
+
async writeFile(request) {
|
|
292
|
+
const filePath = String(request.arguments.path ?? '');
|
|
293
|
+
const content = String(request.arguments.content ?? '');
|
|
294
|
+
const workdir = this.rootsFor(request)[0];
|
|
295
|
+
const writeRoots = [workdir];
|
|
296
|
+
const target = resolveJailedPath(filePath, writeRoots);
|
|
297
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
298
|
+
const recast = resolveJailedPath(filePath, writeRoots);
|
|
299
|
+
await writeFileNoFollow(recast, content);
|
|
300
|
+
return ok(request, JSON.stringify({ ok: true, path: path.relative(workdir, recast) }));
|
|
301
|
+
}
|
|
302
|
+
async executeCommand(request) {
|
|
303
|
+
const command = String(request.arguments.command ?? '').trim();
|
|
304
|
+
if (!command)
|
|
305
|
+
return fail(request, 'command 不能为空');
|
|
306
|
+
const roots = this.rootsFor(request);
|
|
307
|
+
const workdir = roots[0];
|
|
308
|
+
const cwdArg = request.arguments.cwd != null ? String(request.arguments.cwd) : workdir;
|
|
309
|
+
const cwd = resolveJailedPath(cwdArg, roots, { mustExist: true });
|
|
310
|
+
const config = builtinConfig();
|
|
311
|
+
const timeoutMs = clampBuiltinTimeout(request.arguments.timeoutMs, config);
|
|
312
|
+
const maxOutput = config.maxOutputBytes;
|
|
313
|
+
const output = await new Promise((resolve, reject) => {
|
|
314
|
+
const child = spawn('/bin/sh', ['-c', command], {
|
|
315
|
+
cwd,
|
|
316
|
+
env: sandboxEnv(workdir, cwd, config.envAllowlist),
|
|
317
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
318
|
+
});
|
|
319
|
+
let stdout = Buffer.alloc(0);
|
|
320
|
+
let stderr = Buffer.alloc(0);
|
|
321
|
+
let stdoutTruncated = false;
|
|
322
|
+
let stderrTruncated = false;
|
|
323
|
+
const timer = setTimeout(() => {
|
|
324
|
+
child.kill('SIGKILL');
|
|
325
|
+
reject(new Error(`命令超时(${timeoutMs}ms)`));
|
|
326
|
+
}, timeoutMs);
|
|
327
|
+
child.stdout?.on('data', (chunk) => {
|
|
328
|
+
if (stdout.length >= maxOutput) {
|
|
329
|
+
stdoutTruncated = true;
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
stdout = Buffer.concat([stdout, chunk]).subarray(0, maxOutput);
|
|
333
|
+
if (stdout.length >= maxOutput)
|
|
334
|
+
stdoutTruncated = true;
|
|
335
|
+
});
|
|
336
|
+
child.stderr?.on('data', (chunk) => {
|
|
337
|
+
if (stderr.length >= maxOutput) {
|
|
338
|
+
stderrTruncated = true;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
stderr = Buffer.concat([stderr, chunk]).subarray(0, maxOutput);
|
|
342
|
+
if (stderr.length >= maxOutput)
|
|
343
|
+
stderrTruncated = true;
|
|
344
|
+
});
|
|
345
|
+
child.on('error', (err) => {
|
|
346
|
+
clearTimeout(timer);
|
|
347
|
+
reject(err);
|
|
348
|
+
});
|
|
349
|
+
child.on('close', (code) => {
|
|
350
|
+
clearTimeout(timer);
|
|
351
|
+
resolve({
|
|
352
|
+
stdout: stdout.toString('utf8'),
|
|
353
|
+
stderr: stderr.toString('utf8'),
|
|
354
|
+
code,
|
|
355
|
+
stdoutTruncated,
|
|
356
|
+
stderrTruncated,
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
const body = [
|
|
361
|
+
output.stdout.trimEnd(),
|
|
362
|
+
output.stdoutTruncated ? `[stdout truncated at ${maxOutput} bytes]` : '',
|
|
363
|
+
output.stderr.trim() ? `stderr:\n${output.stderr.trimEnd()}` : '',
|
|
364
|
+
output.stderrTruncated ? `[stderr truncated at ${maxOutput} bytes]` : '',
|
|
365
|
+
`exit ${output.code ?? -1}`,
|
|
366
|
+
]
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.join('\n');
|
|
369
|
+
if (output.code !== 0) {
|
|
370
|
+
return { ...fail(request, `命令退出码 ${output.code}`), content: body };
|
|
371
|
+
}
|
|
372
|
+
return ok(request, body);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
export function withHarnessPrimitives(inner, options) {
|
|
376
|
+
const harness = new HarnessPrimitiveExecutor(options);
|
|
377
|
+
return {
|
|
378
|
+
execute(request) {
|
|
379
|
+
if (isBuiltinToolExecution(request.execution)) {
|
|
380
|
+
return harness.execute(request);
|
|
381
|
+
}
|
|
382
|
+
return inner.execute(request);
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
}
|
|
@@ -57,7 +57,7 @@ export class HttpToolExecutor {
|
|
|
57
57
|
}
|
|
58
58
|
async execute(request) {
|
|
59
59
|
const url = `${this.baseUrl}/api/v1/tools/${encodeURIComponent(request.toolName)}/execute`;
|
|
60
|
-
const agentCode = request.authContext.agentCode
|
|
60
|
+
const agentCode = request.authContext.agentCode;
|
|
61
61
|
const correlationId = request.correlationId ?? request.toolCallId;
|
|
62
62
|
const taskId = request.taskId ?? request.toolCallId;
|
|
63
63
|
const body = {
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -12,7 +12,8 @@ export { LocalMcpClient, type ILocalMcpClient, type LocalMcpInvokeContext, type
|
|
|
12
12
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, type FileArtifact, } from './file-artifact.js';
|
|
13
13
|
export { ToolExecutionRouter, type DeploymentContext, type ToolExecutionRouterOptions, } from './tool-execution-router.js';
|
|
14
14
|
export { createToolExecutor, EmptyInstallStore, type CreateToolExecutorOptions, } from './create-tool-executor.js';
|
|
15
|
+
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, type HarnessJailOptions as BuiltinJailOptions, } from './harness-primitives.js';
|
|
15
16
|
export { ToolResultInjector, type ToolMessage, } from './tool-result-injector.js';
|
|
16
17
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
17
18
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
18
|
-
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap,
|
|
19
|
+
export { SystemPromptBuilder, naturalTimeWindows, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, type PromptBuildContext, type PromptSection, type AssembledSystemPrompt, type ApiToolDefinition, type LoadedSkillInfo, type LoadedSkillMap, } from './system-prompt-builder.js';
|
package/dist/tool/index.js
CHANGED
|
@@ -11,7 +11,8 @@ export { LocalMcpClient, } from './local-mcp-client.js';
|
|
|
11
11
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, } from './file-artifact.js';
|
|
12
12
|
export { ToolExecutionRouter, } from './tool-execution-router.js';
|
|
13
13
|
export { createToolExecutor, EmptyInstallStore, } from './create-tool-executor.js';
|
|
14
|
+
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, } from './harness-primitives.js';
|
|
14
15
|
export { ToolResultInjector, } from './tool-result-injector.js';
|
|
15
16
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
16
17
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
17
|
-
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap,
|
|
18
|
+
export { SystemPromptBuilder, naturalTimeWindows, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* LocalToolExecutor — 桌面本地 Code / MCP Tool 执行
|
|
3
3
|
*
|
|
4
4
|
* 优先 ILocalToolInstallStore;若无安装记录但 request.execution 带
|
|
5
|
-
*
|
|
5
|
+
* 或 request.execution 带可解析的 code bundlePath+handler,则直接本机执行。
|
|
6
6
|
*/
|
|
7
7
|
import type { ILocalToolInstallStore } from '@gt-fe/eap-contracts';
|
|
8
8
|
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
@@ -19,10 +19,10 @@ interface CodeExecutionSpec {
|
|
|
19
19
|
args?: string[];
|
|
20
20
|
timeoutMs?: number;
|
|
21
21
|
}
|
|
22
|
-
/** 是否为应本机执行的
|
|
22
|
+
/** 是否为应本机执行的 code(显式 localPreferred,例如 DevToolExecutor) */
|
|
23
23
|
export declare function isLocalPreferredExecution(raw: unknown): boolean;
|
|
24
24
|
/**
|
|
25
|
-
* 从 ToolCallRequest.execution 解析本机 code
|
|
25
|
+
* 从 ToolCallRequest.execution 解析本机 code 执行规格。
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveCodeExecutionFromRequest(request: ToolCallRequest): CodeExecutionSpec | null;
|
|
28
28
|
export declare class LocalToolExecutor implements IToolExecutor {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* LocalToolExecutor — 桌面本地 Code / MCP Tool 执行
|
|
3
3
|
*
|
|
4
4
|
* 优先 ILocalToolInstallStore;若无安装记录但 request.execution 带
|
|
5
|
-
*
|
|
5
|
+
* 或 request.execution 带可解析的 code bundlePath+handler,则直接本机执行。
|
|
6
6
|
*/
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { existsSync } from 'node:fs';
|
|
@@ -88,19 +88,17 @@ function runHandler(handlerPath, input, timeoutMs, execution) {
|
|
|
88
88
|
child.stdin.end();
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
-
/** 是否为应本机执行的
|
|
91
|
+
/** 是否为应本机执行的 code(显式 localPreferred,例如 DevToolExecutor) */
|
|
92
92
|
export function isLocalPreferredExecution(raw) {
|
|
93
93
|
if (!raw || typeof raw !== 'object')
|
|
94
94
|
return false;
|
|
95
95
|
const e = raw;
|
|
96
96
|
if (e.protocol === 'mcp')
|
|
97
97
|
return false;
|
|
98
|
-
|
|
99
|
-
return true;
|
|
100
|
-
return false;
|
|
98
|
+
return e.localPreferred === true;
|
|
101
99
|
}
|
|
102
100
|
/**
|
|
103
|
-
* 从 ToolCallRequest.execution 解析本机 code
|
|
101
|
+
* 从 ToolCallRequest.execution 解析本机 code 执行规格。
|
|
104
102
|
*/
|
|
105
103
|
export function resolveCodeExecutionFromRequest(request) {
|
|
106
104
|
const raw = request.execution;
|
|
@@ -153,7 +151,7 @@ export class LocalToolExecutor {
|
|
|
153
151
|
}
|
|
154
152
|
return this.executeCode(request, install);
|
|
155
153
|
}
|
|
156
|
-
//
|
|
154
|
+
// 无本地安装:仅显式 localPreferred 走 request.execution
|
|
157
155
|
if (isLocalPreferredExecution(request.execution)) {
|
|
158
156
|
const fromReq = resolveCodeExecutionFromRequest(request);
|
|
159
157
|
if (fromReq) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @see docs/contracts/tool-execution.md §2.0
|
|
5
5
|
*/
|
|
6
6
|
function resolveAgentCode(request) {
|
|
7
|
-
return request.authContext.agentCode
|
|
7
|
+
return request.authContext.agentCode;
|
|
8
8
|
}
|
|
9
9
|
function formatContent(result) {
|
|
10
10
|
const text = result?.content?.[0]?.text;
|
|
@@ -57,6 +57,12 @@ export class McpToolExecutor {
|
|
|
57
57
|
params: {
|
|
58
58
|
name: request.toolName,
|
|
59
59
|
arguments: request.arguments,
|
|
60
|
+
...(request.toolVersion
|
|
61
|
+
? {
|
|
62
|
+
version: request.toolVersion,
|
|
63
|
+
_meta: { version: request.toolVersion },
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
60
66
|
},
|
|
61
67
|
}, request, agentCode, correlationId);
|
|
62
68
|
if (!response.ok) {
|
|
@@ -140,6 +146,8 @@ export class McpToolExecutor {
|
|
|
140
146
|
method: 'POST',
|
|
141
147
|
headers: {
|
|
142
148
|
'Content-Type': 'application/json',
|
|
149
|
+
// Streamable HTTP:Gateway /mcp 要求同时接受 JSON 与 SSE
|
|
150
|
+
Accept: 'application/json, text/event-stream',
|
|
143
151
|
Authorization: `Bearer ${request.authContext.delegationToken}`,
|
|
144
152
|
'X-Agent-Code': agentCode,
|
|
145
153
|
'X-Agent-Id': agentCode,
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
* [平台规则常量] ← 代码内置,安全约束 + 行为准则(最高优先级)
|
|
9
9
|
* [当前时间] ← 运行时动态注入(自然周等时间规则的判定基准)
|
|
10
10
|
* [Agent 身份] ← agentName / agentCode
|
|
11
|
+
* [可用工具短目录] ← 名称 + 一句话用途;完整 schema 仍走 API tools
|
|
11
12
|
* [开发者提示词] ← Graph LLM 节点 config.systemPrompt,平台透传
|
|
12
13
|
* (Registry 无内嵌 Graph 的兜底路径读 agent.manifest.json prompts.system)
|
|
13
14
|
* ```
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
+
* 工具 schema 走 API 的 tools 参数(见 loadedToolsToApiTools);system 短目录给模型发现用,不是对用户报菜名。
|
|
16
17
|
*
|
|
17
18
|
* @see docs/status/module-status-and-backlog.md RT-5
|
|
18
19
|
*/
|
|
@@ -28,38 +29,34 @@ export interface LoadedSkillInfo {
|
|
|
28
29
|
name: string;
|
|
29
30
|
description?: string;
|
|
30
31
|
version?: string;
|
|
32
|
+
/** Skill 包目录(含 SKILL.md) */
|
|
33
|
+
bundlePath?: string;
|
|
34
|
+
/** SKILL.md 绝对路径 */
|
|
35
|
+
entryFile?: string;
|
|
31
36
|
/** Skill 工作流(GraphDSL);缺省时子图节点须自带 subgraphDsl */
|
|
32
37
|
workflow?: import('../graph/contracts/executable-graph.js').GraphDSL;
|
|
33
|
-
/**
|
|
38
|
+
/** 子图入口可选上下文;Harness 主路径不灌 system */
|
|
34
39
|
prompt?: string;
|
|
35
|
-
|
|
36
|
-
toolId?: string;
|
|
40
|
+
toolsets?: Array<string | {
|
|
37
41
|
name?: string;
|
|
38
42
|
version?: string;
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
code?: string;
|
|
44
|
+
enabled?: string[];
|
|
41
45
|
autoApprove?: string[];
|
|
42
46
|
}>;
|
|
43
47
|
inputDef?: unknown;
|
|
44
48
|
outputDef?: unknown;
|
|
45
|
-
/** Skill 包 scripts[] 物化出的虚拟 Tool(装配时并入 loadedTools) */
|
|
46
|
-
scriptTools?: import('./loaded-tool-definition.js').LoadedToolDefinition[];
|
|
47
49
|
}
|
|
48
50
|
/** skillId → 完整定义 */
|
|
49
51
|
export type LoadedSkillMap = Record<string, LoadedSkillInfo>;
|
|
50
52
|
export declare function toLoadedSkillMap(skills: LoadedSkillInfo[] | undefined): LoadedSkillMap | undefined;
|
|
51
53
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*
|
|
58
|
-
* - 无 toolRefs / 空数组:不收窄(保持 Agent 全量,兼容旧 Skill)
|
|
59
|
-
* - string / name / toolId:精确匹配工具名
|
|
60
|
-
* - toolset + enabledTools:仅白名单子工具
|
|
61
|
-
* - toolset 无 enabledTools:匹配 toolId 前缀 `<toolset>:` 或 id 等于 toolset
|
|
62
|
-
* - 始终保留该 Skill 的 scriptTools
|
|
54
|
+
* 按 Skill.toolsets 收窄工具集(子图用)。
|
|
55
|
+
* - 无 toolsets / 空数组:不收窄
|
|
56
|
+
* - string / name:精确匹配工具名
|
|
57
|
+
* - code + enabled:仅白名单子工具
|
|
58
|
+
* - code 无 enabled:匹配 toolId 前缀 `<code>:` 或 id 等于 code
|
|
59
|
+
* - 始终保留 Harness 原语
|
|
63
60
|
*/
|
|
64
61
|
export declare function filterLoadedToolsForSkill(loadedTools: LoadedToolMap | undefined, skill: LoadedSkillInfo | undefined): LoadedToolMap | undefined;
|
|
65
62
|
/**
|
|
@@ -78,22 +75,59 @@ export interface PromptBuildContext {
|
|
|
78
75
|
agentCode?: string;
|
|
79
76
|
/** 当前调用用户标识(可选,供「我 / 我的」类指代) */
|
|
80
77
|
userId?: string | null;
|
|
78
|
+
/** 进程工作目录(文件/命令 jail) */
|
|
79
|
+
workingDirectory?: string;
|
|
80
|
+
os?: {
|
|
81
|
+
platform: string;
|
|
82
|
+
release: string;
|
|
83
|
+
};
|
|
84
|
+
/** 允许读写/执行的根目录(工作目录 + 技能包 + FILE_IO_ROOT 等) */
|
|
85
|
+
jailRoots?: string[];
|
|
86
|
+
skillDirs?: Array<{
|
|
87
|
+
skillId: string;
|
|
88
|
+
bundlePath: string;
|
|
89
|
+
entryFile?: string;
|
|
90
|
+
}>;
|
|
91
|
+
/** 已装配 Skill 短目录;单独成段,不并进开发者提示词 */
|
|
92
|
+
skillCatalog?: string;
|
|
93
|
+
/** 已装配工具短目录(名称 + 用途);schema 仍走 API tools */
|
|
94
|
+
toolCatalog?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface PromptSection {
|
|
97
|
+
id: string;
|
|
98
|
+
title: string;
|
|
99
|
+
body: string;
|
|
100
|
+
}
|
|
101
|
+
export interface AssembledSystemPrompt {
|
|
102
|
+
text: string;
|
|
103
|
+
sections: PromptSection[];
|
|
81
104
|
}
|
|
82
105
|
/**
|
|
83
106
|
* SystemPromptBuilder — 系统提示词拼接
|
|
84
107
|
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
108
|
+
* 具名段拼接(无模板引擎)。顺序固定,段标题用于排查。
|
|
109
|
+
* 工具短目录进 system;完整 schema 走 API tools 参数。
|
|
110
|
+
* 开发者提示词整段透传,不做 {{ }} 替换。
|
|
88
111
|
*/
|
|
89
112
|
export declare class SystemPromptBuilder {
|
|
90
113
|
/**
|
|
91
|
-
*
|
|
114
|
+
* 构建完整系统提示词(含具名段,便于对照落盘)。
|
|
92
115
|
*/
|
|
93
|
-
build(context: PromptBuildContext):
|
|
94
|
-
private
|
|
116
|
+
build(context: PromptBuildContext): AssembledSystemPrompt;
|
|
117
|
+
private buildEnvironment;
|
|
95
118
|
private buildAgentIdentity;
|
|
119
|
+
private buildJail;
|
|
96
120
|
}
|
|
121
|
+
/** 自然周(周一为一周第一天)及本月/上月窗口,供时间类推理。 */
|
|
122
|
+
export declare function naturalTimeWindows(now?: Date): {
|
|
123
|
+
now: string;
|
|
124
|
+
weekday: string;
|
|
125
|
+
thisWeek: string;
|
|
126
|
+
lastWeek: string;
|
|
127
|
+
lastWeekend: string;
|
|
128
|
+
thisMonth: string;
|
|
129
|
+
lastMonth: string;
|
|
130
|
+
};
|
|
97
131
|
/**
|
|
98
132
|
* OpenAI function-calling tool definition(API tools 参数格式)
|
|
99
133
|
*
|
|
@@ -111,4 +145,6 @@ export type ApiToolDefinition = LLMToolDefinition;
|
|
|
111
145
|
* @param skillTools - 可选,当前 Skill 所需的工具名称列表(过滤子集)
|
|
112
146
|
* @returns OpenAI API tools 参数数组
|
|
113
147
|
*/
|
|
148
|
+
/** 名称 + 一句话用途,供 system「可用工具」段。schema 仍走 loadedToolsToApiTools。 */
|
|
149
|
+
export declare function buildToolCatalog(loadedTools: LoadedToolMap | undefined): string | undefined;
|
|
114
150
|
export declare function loadedToolsToApiTools(loadedTools: LoadedToolMap | undefined, skillTools?: string[]): ApiToolDefinition[];
|