@gt-fe/eap-runtime 0.0.8 → 0.0.11
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 +15 -20
- package/dist/api/handlers/invoke.handler.js +7 -3
- package/dist/api/handlers/messages.handler.js +0 -1
- package/dist/api/handlers/stream.handler.js +8 -4
- package/dist/api/routes/agent.routes.d.ts +2 -2
- 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/common/types.d.ts +5 -5
- package/dist/common/types.js +2 -2
- package/dist/contracts/permission-checker.d.ts +1 -1
- package/dist/contracts/trace-collector.d.ts +16 -2
- 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/governance/governance-client.d.ts +1 -1
- package/dist/governance/governance-client.js +40 -16
- package/dist/governance/index.d.ts +2 -2
- package/dist/governance/index.js +1 -1
- package/dist/governance/permission-guard.d.ts +2 -1
- package/dist/governance/permission-guard.js +13 -4
- 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 +153 -92
- 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 +11 -9
- package/dist/graph/contracts/executable-graph.js +1 -1
- package/dist/graph/hitl/hitl-handler.d.ts +10 -0
- package/dist/graph/hitl/hitl-handler.js +50 -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 +50 -10
- package/dist/graph/trace-span.d.ts +22 -0
- package/dist/graph/trace-span.js +69 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -1
- 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 +4 -3
- 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 +3 -3
- package/dist/registry/deployment-snapshot.d.ts +17 -5
- package/dist/registry/deployment-snapshot.js +97 -49
- package/dist/registry/graph-loader.d.ts +2 -2
- package/dist/registry/graph-loader.js +43 -80
- package/dist/registry/index.d.ts +2 -2
- package/dist/registry/index.js +2 -2
- package/dist/registry/preload-errors.d.ts +1 -1
- package/dist/registry/preload-errors.js +1 -1
- package/dist/registry/skill-io-guard.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 +129 -39
- package/dist/runtime/dev-runtime.d.ts +7 -4
- package/dist/runtime/dev-runtime.js +24 -21
- 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 +17 -19
- package/dist/server.js +2 -2
- 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 +12 -1
- package/dist/sse/events/skill-events.js +1 -1
- 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.d.ts +1 -1
- package/dist/tool/create-tool-executor.js +3 -2
- package/dist/tool/dev-http-executor.d.ts +1 -1
- package/dist/tool/dev-http-executor.js +10 -7
- package/dist/tool/dev-mcp-executor.js +5 -3
- package/dist/tool/dev-tool-executor.d.ts +1 -1
- package/dist/tool/dev-tool-executor.js +2 -2
- package/dist/tool/harness-command.d.ts +32 -0
- package/dist/tool/harness-command.js +276 -0
- package/dist/tool/harness-primitives.d.ts +54 -0
- package/dist/tool/harness-primitives.js +492 -0
- package/dist/tool/http-tool-executor.js +16 -21
- package/dist/tool/index.d.ts +4 -2
- package/dist/tool/index.js +4 -2
- package/dist/tool/local-mcp-client.d.ts +1 -1
- package/dist/tool/local-mcp-client.js +54 -23
- package/dist/tool/local-tool-executor.d.ts +4 -4
- package/dist/tool/local-tool-executor.js +18 -16
- package/dist/tool/mcp-tool-executor.js +23 -7
- package/dist/tool/skill-script-refs.d.ts +20 -0
- package/dist/tool/skill-script-refs.js +62 -0
- package/dist/tool/system-prompt-builder.d.ts +61 -25
- package/dist/tool/system-prompt-builder.js +198 -92
- package/dist/tool/timeout.d.ts +14 -0
- package/dist/tool/timeout.js +36 -0
- package/dist/tool/tool-confirmation.js +2 -1
- package/dist/tool/tool-execution-router.d.ts +1 -1
- package/dist/tool/tool-execution-router.js +2 -2
- package/dist/tool/tool-executor.interface.d.ts +23 -7
- package/dist/tool/tool-orchestrator.d.ts +1 -1
- package/dist/tool/tool-orchestrator.js +6 -3
- package/dist/utils/format-error.d.ts +2 -2
- package/dist/utils/format-error.js +2 -2
- package/dist/utils/http-error.d.ts +2 -2
- package/dist/utils/http-error.js +2 -2
- package/package.json +3 -3
- package/dist/common/concurrency.d.ts +0 -79
- package/dist/common/concurrency.js +0 -121
- package/dist/common/queue-consumer.d.ts +0 -125
- package/dist/common/queue-consumer.js +0 -159
- package/dist/tool/mock-tool-executor.d.ts +0 -28
- package/dist/tool/mock-tool-executor.js +0 -65
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 平台默认 Harness 原语:read_file / write_file / execute_command。
|
|
3
|
+
*
|
|
4
|
+
* 不写进 agent.manifest.toolsets。同名业务工具优先(问数 file-io 等)。
|
|
5
|
+
* 写:`users/<userId>/`(含用户自己的 skills)。
|
|
6
|
+
* 读:写根 + 已装配 `preinstall/tools`、`preinstall/skills`。
|
|
7
|
+
* 命令:cwd 仅写根。技能包只读,EAP_SKILL_DIR 为整包绝对根(不限 scripts/)。不把预装拷进用户装配槽。
|
|
8
|
+
* 开发态可选 overlay:`EAP_LOCAL_SKILLS_DIR` / `EAP_LOCAL_TOOLS_DIR`(只读)。
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
import { constants as fsConstants, realpathSync } from 'node:fs';
|
|
12
|
+
import { mkdir, open, readFile } from 'node:fs/promises';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { BUILTIN_TOOL_SOURCE, DEFAULT_BUILTIN_TIMEOUT_MS, MAX_BUILTIN_TIMEOUT_MS, clampBuiltinTimeout, isBuiltinToolExecution, resolveBuiltinToolsConfig, } from '@eap/contracts';
|
|
15
|
+
import { ensureInterpreterShims, formatSpawnError, killProcessTree, resolveShellSpawn, sandboxEnv, spawnOptionsFor, substituteSkillDir, } from './harness-command.js';
|
|
16
|
+
import { expandSkillScriptRefs, isSkillEntryFile } from './skill-script-refs.js';
|
|
17
|
+
function builtinConfig() {
|
|
18
|
+
return resolveBuiltinToolsConfig(process.env);
|
|
19
|
+
}
|
|
20
|
+
function harnessDefinitions() {
|
|
21
|
+
const common = {
|
|
22
|
+
version: '1.0.0',
|
|
23
|
+
type: 'code',
|
|
24
|
+
status: 'active',
|
|
25
|
+
toolRuntime: 'local',
|
|
26
|
+
execution: { source: BUILTIN_TOOL_SOURCE },
|
|
27
|
+
};
|
|
28
|
+
return [
|
|
29
|
+
{
|
|
30
|
+
toolId: 'builtin:read_file',
|
|
31
|
+
name: 'read_file',
|
|
32
|
+
manifest: {
|
|
33
|
+
id: 'builtin:read_file',
|
|
34
|
+
name: 'read_file',
|
|
35
|
+
description: '读取当前用户目录或预装 tools/skills 内的文本。要用某个技能时读该技能文件夹下的 SKILL.md。超限会截断并注明。',
|
|
36
|
+
...common,
|
|
37
|
+
},
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: 'object',
|
|
40
|
+
required: ['path'],
|
|
41
|
+
properties: {
|
|
42
|
+
path: { type: 'string', description: '相对工作目录或技能根的路径' },
|
|
43
|
+
offset: { type: 'integer', description: '从第几行开始读,从 1 起。省略则从头。' },
|
|
44
|
+
limit: { type: 'integer', description: '最多读多少行。省略则读到末尾或字节上限。' },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
toolId: 'builtin:write_file',
|
|
50
|
+
name: 'write_file',
|
|
51
|
+
manifest: {
|
|
52
|
+
id: 'builtin:write_file',
|
|
53
|
+
name: 'write_file',
|
|
54
|
+
description: '写入当前用户目录(含用户自己的 skills)。预装 tools / skills 只读。',
|
|
55
|
+
...common,
|
|
56
|
+
},
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
required: ['path', 'content'],
|
|
60
|
+
properties: {
|
|
61
|
+
path: { type: 'string' },
|
|
62
|
+
content: { type: 'string' },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
toolId: 'builtin:execute_command',
|
|
68
|
+
name: 'execute_command',
|
|
69
|
+
manifest: {
|
|
70
|
+
id: 'builtin:execute_command',
|
|
71
|
+
name: 'execute_command',
|
|
72
|
+
description: `用本机 shell 在用户目录执行命令(Windows cmd.exe,其它 /bin/sh)。技能包只读,用 EAP_SKILL_DIR 加包内相对路径跑文件(不限 scripts/)。python/python3、node/nodejs、sh/bash 经 shim。默认超时 ${DEFAULT_BUILTIN_TIMEOUT_MS}ms,可用 timeoutMs 延长,上限 ${MAX_BUILTIN_TIMEOUT_MS}ms。不继承密钥。`,
|
|
73
|
+
...common,
|
|
74
|
+
},
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object',
|
|
77
|
+
required: ['command'],
|
|
78
|
+
properties: {
|
|
79
|
+
command: { type: 'string' },
|
|
80
|
+
cwd: { type: 'string', description: '用户目录内的子目录,可选;省略则用户目录。不能是预装技能/工具包' },
|
|
81
|
+
timeoutMs: {
|
|
82
|
+
type: 'integer',
|
|
83
|
+
description: `超时毫秒。省略则 ${DEFAULT_BUILTIN_TIMEOUT_MS}。上限 ${MAX_BUILTIN_TIMEOUT_MS}。`,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
export function mergeHarnessPrimitives(loadedTools) {
|
|
91
|
+
if (!builtinConfig().enabled) {
|
|
92
|
+
return loadedTools;
|
|
93
|
+
}
|
|
94
|
+
const merged = { ...(loadedTools ?? {}) };
|
|
95
|
+
for (const tool of harnessDefinitions()) {
|
|
96
|
+
if (!merged[tool.name])
|
|
97
|
+
merged[tool.name] = tool;
|
|
98
|
+
}
|
|
99
|
+
return merged;
|
|
100
|
+
}
|
|
101
|
+
/** 装配点:业务 toolsets + 平台 Harness 原语。 */
|
|
102
|
+
export function assembleLoadedTools(loadedTools) {
|
|
103
|
+
return mergeHarnessPrimitives(loadedTools);
|
|
104
|
+
}
|
|
105
|
+
const MISSING_USER_DIR = '缺少用户目录,拒绝该操作';
|
|
106
|
+
const PROTECTED_WRITE_NAMES = new Set(['.assembly.json', 'assembly.json', '.ready']);
|
|
107
|
+
/** `{agentDir}/users/<userId>`(写根;含用户 skills) */
|
|
108
|
+
export function resolveUserDir(agentDir, userId) {
|
|
109
|
+
const dir = agentDir.trim();
|
|
110
|
+
const uid = userId.trim();
|
|
111
|
+
if (!dir || !uid) {
|
|
112
|
+
throw new Error(MISSING_USER_DIR);
|
|
113
|
+
}
|
|
114
|
+
if (uid === '.' || uid === '..' || /[\\/]/.test(uid)) {
|
|
115
|
+
throw new Error('非法用户标识');
|
|
116
|
+
}
|
|
117
|
+
return path.resolve(dir, 'users', uid);
|
|
118
|
+
}
|
|
119
|
+
export function collectToolBundlePaths(loadedTools) {
|
|
120
|
+
const roots = [];
|
|
121
|
+
for (const def of Object.values(loadedTools ?? {})) {
|
|
122
|
+
const execution = def.manifest.execution;
|
|
123
|
+
if (!execution || typeof execution !== 'object' || Array.isArray(execution))
|
|
124
|
+
continue;
|
|
125
|
+
const bundlePath = execution.bundlePath;
|
|
126
|
+
if (typeof bundlePath !== 'string' || !bundlePath.trim())
|
|
127
|
+
continue;
|
|
128
|
+
if (!roots.includes(bundlePath))
|
|
129
|
+
roots.push(bundlePath);
|
|
130
|
+
}
|
|
131
|
+
return roots;
|
|
132
|
+
}
|
|
133
|
+
export function resolveJailFromState(state) {
|
|
134
|
+
const execUser = state.executionContext?.userId;
|
|
135
|
+
const userId = (typeof execUser === 'string' && execUser.trim()) ||
|
|
136
|
+
(typeof state.userId === 'string' && state.userId.trim()) ||
|
|
137
|
+
'';
|
|
138
|
+
const agentDir = typeof state.agentDir === 'string' ? state.agentDir.trim() : '';
|
|
139
|
+
const extraRoots = [];
|
|
140
|
+
for (const skill of Object.values(state.skillDefinitions ?? {})) {
|
|
141
|
+
if (skill.bundlePath)
|
|
142
|
+
extraRoots.push(skill.bundlePath);
|
|
143
|
+
}
|
|
144
|
+
extraRoots.push(...collectToolBundlePaths(state.loadedTools));
|
|
145
|
+
const scoped = agentDir
|
|
146
|
+
? extraRoots.filter((root) => isPreinstallExtraRoot(agentDir, root))
|
|
147
|
+
: [];
|
|
148
|
+
return {
|
|
149
|
+
workdir: agentDir && userId ? resolveUserDir(agentDir, userId) : undefined,
|
|
150
|
+
extraRoots: scoped,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function deploymentAllowsLocalOverlay() {
|
|
154
|
+
const ctx = process.env.EAP_DEPLOYMENT_CONTEXT?.trim();
|
|
155
|
+
return ctx === 'dev' || ctx === 'desktop';
|
|
156
|
+
}
|
|
157
|
+
function devOverlayRoots() {
|
|
158
|
+
if (!deploymentAllowsLocalOverlay())
|
|
159
|
+
return [];
|
|
160
|
+
const extra = [];
|
|
161
|
+
for (const key of ['EAP_LOCAL_SKILLS_DIR', 'EAP_LOCAL_TOOLS_DIR']) {
|
|
162
|
+
const value = process.env[key]?.trim();
|
|
163
|
+
if (value)
|
|
164
|
+
extra.push(value);
|
|
165
|
+
}
|
|
166
|
+
return extra;
|
|
167
|
+
}
|
|
168
|
+
function tryRealpath(p) {
|
|
169
|
+
try {
|
|
170
|
+
return realpathSync(p);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function resolveJailRoots(options) {
|
|
177
|
+
const workdir = options?.workdir?.trim() ? path.resolve(options.workdir) : undefined;
|
|
178
|
+
const candidates = [
|
|
179
|
+
...(workdir ? [workdir] : []),
|
|
180
|
+
...devOverlayRoots(),
|
|
181
|
+
...(options?.extraRoots ?? []),
|
|
182
|
+
];
|
|
183
|
+
const roots = [];
|
|
184
|
+
for (const raw of candidates) {
|
|
185
|
+
const resolved = path.resolve(raw);
|
|
186
|
+
const real = tryRealpath(resolved) ?? resolved;
|
|
187
|
+
if (!roots.includes(real))
|
|
188
|
+
roots.push(real);
|
|
189
|
+
}
|
|
190
|
+
return roots;
|
|
191
|
+
}
|
|
192
|
+
async function writeFileNoFollow(target, content) {
|
|
193
|
+
const flags = fsConstants.O_WRONLY |
|
|
194
|
+
fsConstants.O_CREAT |
|
|
195
|
+
fsConstants.O_TRUNC |
|
|
196
|
+
(fsConstants.O_NOFOLLOW ?? 0);
|
|
197
|
+
let handle;
|
|
198
|
+
try {
|
|
199
|
+
handle = await open(target, flags, 0o644);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const code = err.code;
|
|
203
|
+
if (code === 'ELOOP') {
|
|
204
|
+
throw new Error('拒绝写入符号链接');
|
|
205
|
+
}
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
await handle.writeFile(content, 'utf8');
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
await handle.close();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function isInsideRoot(candidate, root) {
|
|
216
|
+
const rel = path.relative(root, candidate);
|
|
217
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
218
|
+
}
|
|
219
|
+
function realOrResolve(p) {
|
|
220
|
+
return tryRealpath(p) ?? path.resolve(p);
|
|
221
|
+
}
|
|
222
|
+
/** 路径落在写根外的 extraRoot 时,返回该 extraRoot。 */
|
|
223
|
+
export function extraRootContaining(abs, extraRoots, workdir) {
|
|
224
|
+
const target = realOrResolve(abs);
|
|
225
|
+
const work = realOrResolve(workdir);
|
|
226
|
+
if (isInsideRoot(target, work))
|
|
227
|
+
return undefined;
|
|
228
|
+
for (const raw of extraRoots) {
|
|
229
|
+
const root = realOrResolve(raw);
|
|
230
|
+
if (isInsideRoot(target, root))
|
|
231
|
+
return root;
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
function isPreinstallExtraRoot(agentDir, root) {
|
|
236
|
+
const resolved = path.resolve(root);
|
|
237
|
+
const tools = path.resolve(agentDir, 'preinstall', 'tools');
|
|
238
|
+
const skills = path.resolve(agentDir, 'preinstall', 'skills');
|
|
239
|
+
return isInsideRoot(resolved, tools) || isInsideRoot(resolved, skills);
|
|
240
|
+
}
|
|
241
|
+
function resolveViaExistingAncestor(abs) {
|
|
242
|
+
const suffix = [];
|
|
243
|
+
let cur = abs;
|
|
244
|
+
while (true) {
|
|
245
|
+
const real = tryRealpath(cur);
|
|
246
|
+
if (real)
|
|
247
|
+
return path.resolve(real, ...suffix);
|
|
248
|
+
const parent = path.dirname(cur);
|
|
249
|
+
if (parent === cur)
|
|
250
|
+
return abs;
|
|
251
|
+
suffix.unshift(path.basename(cur));
|
|
252
|
+
cur = parent;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
export function resolveJailedPath(requested, roots, opts) {
|
|
256
|
+
const trimmed = requested.trim();
|
|
257
|
+
if (!trimmed || trimmed.includes('\0')) {
|
|
258
|
+
throw new Error('非法路径');
|
|
259
|
+
}
|
|
260
|
+
const relativeBase = opts?.relativeBase?.trim()
|
|
261
|
+
? path.resolve(opts.relativeBase)
|
|
262
|
+
: undefined;
|
|
263
|
+
if (!path.isAbsolute(trimmed) && !relativeBase) {
|
|
264
|
+
throw new Error(MISSING_USER_DIR);
|
|
265
|
+
}
|
|
266
|
+
const abs = path.isAbsolute(trimmed)
|
|
267
|
+
? path.normalize(trimmed)
|
|
268
|
+
: path.resolve(relativeBase, trimmed);
|
|
269
|
+
const existing = tryRealpath(abs);
|
|
270
|
+
if (opts?.mustExist && !existing) {
|
|
271
|
+
throw new Error(`文件不存在:${trimmed}`);
|
|
272
|
+
}
|
|
273
|
+
const target = existing ?? resolveViaExistingAncestor(abs);
|
|
274
|
+
if (!roots.some((root) => isInsideRoot(target, root))) {
|
|
275
|
+
throw new Error(`路径超出允许目录:${trimmed}`);
|
|
276
|
+
}
|
|
277
|
+
return target;
|
|
278
|
+
}
|
|
279
|
+
function fail(request, error) {
|
|
280
|
+
return {
|
|
281
|
+
toolCallId: request.toolCallId,
|
|
282
|
+
toolName: request.toolName,
|
|
283
|
+
content: '',
|
|
284
|
+
success: false,
|
|
285
|
+
error,
|
|
286
|
+
runtime: 'local',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function ok(request, content) {
|
|
290
|
+
return {
|
|
291
|
+
toolCallId: request.toolCallId,
|
|
292
|
+
toolName: request.toolName,
|
|
293
|
+
content,
|
|
294
|
+
success: true,
|
|
295
|
+
runtime: 'local',
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
export class HarnessPrimitiveExecutor {
|
|
299
|
+
workdir;
|
|
300
|
+
extraRoots;
|
|
301
|
+
constructor(options) {
|
|
302
|
+
this.workdir = options?.workdir;
|
|
303
|
+
this.extraRoots = options?.extraRoots;
|
|
304
|
+
}
|
|
305
|
+
workdirFor(request) {
|
|
306
|
+
const fromRequest = request.jailWorkdir?.trim();
|
|
307
|
+
if (fromRequest)
|
|
308
|
+
return path.resolve(fromRequest);
|
|
309
|
+
return this.workdir?.trim() ? path.resolve(this.workdir) : undefined;
|
|
310
|
+
}
|
|
311
|
+
rootsFor(request) {
|
|
312
|
+
return resolveJailRoots({
|
|
313
|
+
workdir: this.workdirFor(request),
|
|
314
|
+
extraRoots: this.extraRootsFor(request),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
extraRootsFor(request) {
|
|
318
|
+
return [...(this.extraRoots ?? []), ...(request.jailExtraRoots ?? [])];
|
|
319
|
+
}
|
|
320
|
+
requireWorkdir(request) {
|
|
321
|
+
const workdir = this.workdirFor(request);
|
|
322
|
+
if (!workdir) {
|
|
323
|
+
throw new Error(MISSING_USER_DIR);
|
|
324
|
+
}
|
|
325
|
+
return tryRealpath(workdir) ?? workdir;
|
|
326
|
+
}
|
|
327
|
+
async execute(request) {
|
|
328
|
+
try {
|
|
329
|
+
switch (request.toolName) {
|
|
330
|
+
case 'read_file':
|
|
331
|
+
return await this.readFile(request);
|
|
332
|
+
case 'write_file':
|
|
333
|
+
return await this.writeFile(request);
|
|
334
|
+
case 'execute_command':
|
|
335
|
+
return await this.executeCommand(request);
|
|
336
|
+
default:
|
|
337
|
+
return fail(request, `未知 Harness 原语:${request.toolName}`);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
return fail(request, err instanceof Error ? err.message : String(err));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async readFile(request) {
|
|
345
|
+
const filePath = String(request.arguments.path ?? request.arguments.file ?? '');
|
|
346
|
+
const workdir = this.workdirFor(request);
|
|
347
|
+
const relativeBase = workdir ? (tryRealpath(workdir) ?? path.resolve(workdir)) : undefined;
|
|
348
|
+
const target = resolveJailedPath(filePath, this.rootsFor(request), {
|
|
349
|
+
mustExist: true,
|
|
350
|
+
relativeBase,
|
|
351
|
+
});
|
|
352
|
+
const buf = await readFile(target);
|
|
353
|
+
const config = builtinConfig();
|
|
354
|
+
const lines = buf.toString('utf8').split('\n');
|
|
355
|
+
const startLine = typeof request.arguments.offset === 'number' && request.arguments.offset > 1
|
|
356
|
+
? Math.floor(request.arguments.offset) - 1
|
|
357
|
+
: 0;
|
|
358
|
+
const maxLines = typeof request.arguments.limit === 'number' && request.arguments.limit > 0
|
|
359
|
+
? Math.floor(request.arguments.limit)
|
|
360
|
+
: undefined;
|
|
361
|
+
const sliced = maxLines != null ? lines.slice(startLine, startLine + maxLines) : lines.slice(startLine);
|
|
362
|
+
let text = sliced.join('\n');
|
|
363
|
+
const notes = [];
|
|
364
|
+
if (startLine > 0 || maxLines != null) {
|
|
365
|
+
notes.push(`showing lines ${startLine + 1}-${startLine + sliced.length} of ${lines.length}`);
|
|
366
|
+
}
|
|
367
|
+
const byteLength = Buffer.byteLength(text, 'utf8');
|
|
368
|
+
if (byteLength > config.maxReadBytes) {
|
|
369
|
+
let cut = text;
|
|
370
|
+
while (Buffer.byteLength(cut, 'utf8') > config.maxReadBytes && cut.length > 0) {
|
|
371
|
+
cut = cut.slice(0, Math.floor(cut.length * 0.9));
|
|
372
|
+
}
|
|
373
|
+
text = cut;
|
|
374
|
+
notes.push(`truncated to ${config.maxReadBytes} bytes (file was ${buf.byteLength} bytes)`);
|
|
375
|
+
}
|
|
376
|
+
if (notes.length > 0) {
|
|
377
|
+
text = `${text}\n[${notes.join('; ')}]`;
|
|
378
|
+
}
|
|
379
|
+
if (isSkillEntryFile(target)) {
|
|
380
|
+
text = expandSkillScriptRefs(text, path.dirname(target));
|
|
381
|
+
}
|
|
382
|
+
return ok(request, text);
|
|
383
|
+
}
|
|
384
|
+
async writeFile(request) {
|
|
385
|
+
const filePath = String(request.arguments.path ?? '');
|
|
386
|
+
const content = String(request.arguments.content ?? '');
|
|
387
|
+
const workdir = this.requireWorkdir(request);
|
|
388
|
+
const writeRoots = [workdir];
|
|
389
|
+
const jailed = { relativeBase: workdir };
|
|
390
|
+
const target = resolveJailedPath(filePath, writeRoots, jailed);
|
|
391
|
+
if (PROTECTED_WRITE_NAMES.has(path.basename(target))) {
|
|
392
|
+
throw new Error(`禁止写入平台文件:${path.basename(target)}`);
|
|
393
|
+
}
|
|
394
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
395
|
+
const recast = resolveJailedPath(filePath, writeRoots, jailed);
|
|
396
|
+
if (PROTECTED_WRITE_NAMES.has(path.basename(recast))) {
|
|
397
|
+
throw new Error(`禁止写入平台文件:${path.basename(recast)}`);
|
|
398
|
+
}
|
|
399
|
+
await writeFileNoFollow(recast, content);
|
|
400
|
+
return ok(request, JSON.stringify({ ok: true, path: path.relative(workdir, recast) }));
|
|
401
|
+
}
|
|
402
|
+
async executeCommand(request) {
|
|
403
|
+
const command = substituteSkillDir(String(request.arguments.command ?? '').trim(), request.skillDir);
|
|
404
|
+
if (!command)
|
|
405
|
+
return fail(request, 'command 不能为空');
|
|
406
|
+
const workdir = this.requireWorkdir(request);
|
|
407
|
+
await mkdir(workdir, { recursive: true });
|
|
408
|
+
const cwdArg = request.arguments.cwd != null ? String(request.arguments.cwd) : workdir;
|
|
409
|
+
const cwd = resolveJailedPath(cwdArg, [workdir], {
|
|
410
|
+
mustExist: true,
|
|
411
|
+
relativeBase: workdir,
|
|
412
|
+
});
|
|
413
|
+
const config = builtinConfig();
|
|
414
|
+
const timeoutMs = clampBuiltinTimeout(request.arguments.timeoutMs, config);
|
|
415
|
+
const maxOutput = config.maxOutputBytes;
|
|
416
|
+
const shell = resolveShellSpawn(command);
|
|
417
|
+
const shimDir = ensureInterpreterShims(workdir);
|
|
418
|
+
const env = sandboxEnv(workdir, cwd, config.envAllowlist, shimDir, request.skillDir);
|
|
419
|
+
const output = await new Promise((resolve, reject) => {
|
|
420
|
+
const child = spawn(shell.file, shell.args, {
|
|
421
|
+
cwd,
|
|
422
|
+
env,
|
|
423
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
424
|
+
...spawnOptionsFor(shell),
|
|
425
|
+
});
|
|
426
|
+
let stdout = Buffer.alloc(0);
|
|
427
|
+
let stderr = Buffer.alloc(0);
|
|
428
|
+
let stdoutTruncated = false;
|
|
429
|
+
let stderrTruncated = false;
|
|
430
|
+
const timer = setTimeout(() => {
|
|
431
|
+
killProcessTree(child);
|
|
432
|
+
reject(new Error(`命令超时(${timeoutMs}ms)`));
|
|
433
|
+
}, timeoutMs);
|
|
434
|
+
child.stdout?.on('data', (chunk) => {
|
|
435
|
+
if (stdout.length >= maxOutput) {
|
|
436
|
+
stdoutTruncated = true;
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
stdout = Buffer.concat([stdout, chunk]).subarray(0, maxOutput);
|
|
440
|
+
if (stdout.length >= maxOutput)
|
|
441
|
+
stdoutTruncated = true;
|
|
442
|
+
});
|
|
443
|
+
child.stderr?.on('data', (chunk) => {
|
|
444
|
+
if (stderr.length >= maxOutput) {
|
|
445
|
+
stderrTruncated = true;
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
stderr = Buffer.concat([stderr, chunk]).subarray(0, maxOutput);
|
|
449
|
+
if (stderr.length >= maxOutput)
|
|
450
|
+
stderrTruncated = true;
|
|
451
|
+
});
|
|
452
|
+
child.on('error', (err) => {
|
|
453
|
+
clearTimeout(timer);
|
|
454
|
+
reject(formatSpawnError(err, shell.file));
|
|
455
|
+
});
|
|
456
|
+
child.on('close', (code) => {
|
|
457
|
+
clearTimeout(timer);
|
|
458
|
+
resolve({
|
|
459
|
+
stdout: stdout.toString('utf8'),
|
|
460
|
+
stderr: stderr.toString('utf8'),
|
|
461
|
+
code,
|
|
462
|
+
stdoutTruncated,
|
|
463
|
+
stderrTruncated,
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
const body = [
|
|
468
|
+
output.stdout.trimEnd(),
|
|
469
|
+
output.stdoutTruncated ? `[stdout truncated at ${maxOutput} bytes]` : '',
|
|
470
|
+
output.stderr.trim() ? `stderr:\n${output.stderr.trimEnd()}` : '',
|
|
471
|
+
output.stderrTruncated ? `[stderr truncated at ${maxOutput} bytes]` : '',
|
|
472
|
+
`exit ${output.code ?? -1}`,
|
|
473
|
+
]
|
|
474
|
+
.filter(Boolean)
|
|
475
|
+
.join('\n');
|
|
476
|
+
if (output.code !== 0) {
|
|
477
|
+
return { ...fail(request, `命令退出码 ${output.code}`), content: body };
|
|
478
|
+
}
|
|
479
|
+
return ok(request, body);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
export function withHarnessPrimitives(inner, options) {
|
|
483
|
+
const harness = new HarnessPrimitiveExecutor(options);
|
|
484
|
+
return {
|
|
485
|
+
execute(request) {
|
|
486
|
+
if (isBuiltinToolExecution(request.execution)) {
|
|
487
|
+
return harness.execute(request);
|
|
488
|
+
}
|
|
489
|
+
return inner.execute(request);
|
|
490
|
+
},
|
|
491
|
+
};
|
|
492
|
+
}
|
|
@@ -4,40 +4,45 @@
|
|
|
4
4
|
* @see docs/contracts/tool-execution.md §2
|
|
5
5
|
* @see docs/contracts/cross-service-api.md §4.6
|
|
6
6
|
*/
|
|
7
|
+
import { ErrorCodes } from '@eap/contracts';
|
|
8
|
+
import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
|
|
7
9
|
function mapGatewayHttpError(status, body, statusText) {
|
|
8
10
|
const code = body?.error?.code;
|
|
9
|
-
const
|
|
11
|
+
const bodyMessage = [body?.error?.message, body?.message].find((s) => typeof s === 'string' && s.trim()) ?? '';
|
|
12
|
+
const message = bodyMessage ||
|
|
13
|
+
(typeof statusText === 'string' && statusText.trim()) ||
|
|
14
|
+
'';
|
|
10
15
|
if (status === 403) {
|
|
11
16
|
return {
|
|
12
|
-
error:
|
|
17
|
+
error: bodyMessage || '用户无该 Tool 执行权限',
|
|
13
18
|
errorCode: code ?? 'NO_PERMISSION',
|
|
14
19
|
retryable: false,
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
if (status === 429) {
|
|
18
23
|
return {
|
|
19
|
-
error: message
|
|
24
|
+
error: message || `HTTP ${status}`,
|
|
20
25
|
errorCode: code ?? 'RATE_LIMITED',
|
|
21
26
|
retryable: body?.error?.retryable ?? true,
|
|
22
27
|
};
|
|
23
28
|
}
|
|
24
29
|
if (status === 503) {
|
|
25
30
|
return {
|
|
26
|
-
error: message
|
|
31
|
+
error: message || `HTTP ${status}`,
|
|
27
32
|
errorCode: code ?? 'CIRCUIT_OPEN',
|
|
28
33
|
retryable: body?.error?.retryable ?? true,
|
|
29
34
|
};
|
|
30
35
|
}
|
|
31
36
|
if (status === 404) {
|
|
32
37
|
return {
|
|
33
|
-
error: message
|
|
38
|
+
error: message || `HTTP ${status}`,
|
|
34
39
|
errorCode: code ?? 'TOOL_NOT_FOUND',
|
|
35
40
|
retryable: false,
|
|
36
41
|
};
|
|
37
42
|
}
|
|
38
43
|
if (status === 400) {
|
|
39
44
|
return {
|
|
40
|
-
error: message
|
|
45
|
+
error: message || `HTTP ${status}`,
|
|
41
46
|
errorCode: code ?? 'INVALID_ARGUMENTS',
|
|
42
47
|
retryable: false,
|
|
43
48
|
};
|
|
@@ -57,7 +62,7 @@ export class HttpToolExecutor {
|
|
|
57
62
|
}
|
|
58
63
|
async execute(request) {
|
|
59
64
|
const url = `${this.baseUrl}/api/v1/tools/${encodeURIComponent(request.toolName)}/execute`;
|
|
60
|
-
const agentCode = request.authContext.agentCode
|
|
65
|
+
const agentCode = request.authContext.agentCode;
|
|
61
66
|
const correlationId = request.correlationId ?? request.toolCallId;
|
|
62
67
|
const taskId = request.taskId ?? request.toolCallId;
|
|
63
68
|
const body = {
|
|
@@ -132,25 +137,15 @@ export class HttpToolExecutor {
|
|
|
132
137
|
}
|
|
133
138
|
catch (error) {
|
|
134
139
|
clearTimeout(timeoutId);
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
toolCallId: request.toolCallId,
|
|
138
|
-
toolName: request.toolName,
|
|
139
|
-
content: '',
|
|
140
|
-
success: false,
|
|
141
|
-
error: `Request timeout after ${this.timeout}ms`,
|
|
142
|
-
errorCode: 'EXECUTION_FAILED',
|
|
143
|
-
runtime: 'server',
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
const message = error instanceof Error ? error.message : 'Unknown network error';
|
|
140
|
+
const timeoutSec = Math.max(1, Math.round(this.timeout / 1000));
|
|
141
|
+
const aborted = isAbortError(error);
|
|
147
142
|
return {
|
|
148
143
|
toolCallId: request.toolCallId,
|
|
149
144
|
toolName: request.toolName,
|
|
150
145
|
content: '',
|
|
151
146
|
success: false,
|
|
152
|
-
error:
|
|
153
|
-
errorCode: 'EXECUTION_FAILED',
|
|
147
|
+
error: abortOrTimeoutMessage(error, timeoutSec),
|
|
148
|
+
errorCode: aborted ? ErrorCodes.TOOL_TIMEOUT : 'EXECUTION_FAILED',
|
|
154
149
|
runtime: 'server',
|
|
155
150
|
};
|
|
156
151
|
}
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -7,12 +7,14 @@ export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromR
|
|
|
7
7
|
export { DevMcpExecutor, type DevMcpExecutorOptions, } from './dev-mcp-executor.js';
|
|
8
8
|
export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, type DevHttpExecution, } from './dev-http-executor.js';
|
|
9
9
|
export { DevToolExecutor, type DevToolExecutorOptions } from './dev-tool-executor.js';
|
|
10
|
-
export { MockToolExecutor, type MockToolExecutorOptions, } from './mock-tool-executor.js';
|
|
11
10
|
export { LocalMcpClient, type ILocalMcpClient, type LocalMcpInvokeContext, type LocalMcpInvokeResult, } from './local-mcp-client.js';
|
|
12
11
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, type FileArtifact, } from './file-artifact.js';
|
|
13
12
|
export { ToolExecutionRouter, type DeploymentContext, type ToolExecutionRouterOptions, } from './tool-execution-router.js';
|
|
14
13
|
export { createToolExecutor, EmptyInstallStore, type CreateToolExecutorOptions, } from './create-tool-executor.js';
|
|
14
|
+
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, resolveUserDir, resolveJailFromState, collectToolBundlePaths, type HarnessJailOptions as BuiltinJailOptions, } from './harness-primitives.js';
|
|
15
|
+
export { resolveShellSpawn, shellNameFor, commandDialectLines, findPythonInterpreter, findNodeInterpreter, ensureInterpreterShims, substituteSkillDir, } from './harness-command.js';
|
|
16
|
+
export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './skill-script-refs.js';
|
|
15
17
|
export { ToolResultInjector, type ToolMessage, } from './tool-result-injector.js';
|
|
16
18
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
17
19
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
18
|
-
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap,
|
|
20
|
+
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
|
@@ -6,12 +6,14 @@ export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromR
|
|
|
6
6
|
export { DevMcpExecutor, } from './dev-mcp-executor.js';
|
|
7
7
|
export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, } from './dev-http-executor.js';
|
|
8
8
|
export { DevToolExecutor } from './dev-tool-executor.js';
|
|
9
|
-
export { MockToolExecutor, } from './mock-tool-executor.js';
|
|
10
9
|
export { LocalMcpClient, } from './local-mcp-client.js';
|
|
11
10
|
export { fileArtifactFromToolOutput, extractFileReference, resolveFileArtifact, } from './file-artifact.js';
|
|
12
11
|
export { ToolExecutionRouter, } from './tool-execution-router.js';
|
|
13
12
|
export { createToolExecutor, EmptyInstallStore, } from './create-tool-executor.js';
|
|
13
|
+
export { assembleLoadedTools, mergeHarnessPrimitives as mergeBuiltinTools, withHarnessPrimitives as withBuiltinTools, HarnessPrimitiveExecutor as BuiltinToolExecutor, resolveUserDir, resolveJailFromState, collectToolBundlePaths, } from './harness-primitives.js';
|
|
14
|
+
export { resolveShellSpawn, shellNameFor, commandDialectLines, findPythonInterpreter, findNodeInterpreter, ensureInterpreterShims, substituteSkillDir, } from './harness-command.js';
|
|
15
|
+
export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './skill-script-refs.js';
|
|
14
16
|
export { ToolResultInjector, } from './tool-result-injector.js';
|
|
15
17
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
16
18
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
17
|
-
export { SystemPromptBuilder, loadedToolsToApiTools, toLoadedSkillMap,
|
|
19
|
+
export { SystemPromptBuilder, naturalTimeWindows, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* mcp-data-service 等上游要求 HTTP 头 `userId`(或 query),再注入 `params._meta.userId`。
|
|
5
5
|
* 身份优先来自 Runtime ExecutionContext(Token.sub);manifest headers.userId 作兜底。
|
|
6
6
|
*/
|
|
7
|
-
import type { McpRuntimeExecution } from '@
|
|
7
|
+
import type { McpRuntimeExecution } from '@eap/contracts';
|
|
8
8
|
import { type FileArtifact } from './file-artifact.js';
|
|
9
9
|
export interface LocalMcpInvokeContext {
|
|
10
10
|
/** Platform Token.sub / ExecutionContext.userId */
|