@gt-fe/eap-runtime 0.0.10 → 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.
Files changed (87) hide show
  1. package/README.md +3 -3
  2. package/dist/api/handlers/invoke.handler.js +4 -0
  3. package/dist/api/handlers/stream.handler.js +5 -1
  4. package/dist/api/routes/agent.routes.d.ts +2 -2
  5. package/dist/common/types.d.ts +5 -5
  6. package/dist/common/types.js +2 -2
  7. package/dist/contracts/permission-checker.d.ts +1 -1
  8. package/dist/contracts/trace-collector.d.ts +1 -1
  9. package/dist/governance/governance-client.d.ts +1 -1
  10. package/dist/governance/governance-client.js +40 -16
  11. package/dist/governance/index.d.ts +2 -2
  12. package/dist/governance/index.js +1 -1
  13. package/dist/governance/permission-guard.d.ts +2 -1
  14. package/dist/governance/permission-guard.js +13 -4
  15. package/dist/graph/adapters/langgraph.adapter.js +12 -7
  16. package/dist/graph/contracts/executable-graph.d.ts +8 -6
  17. package/dist/graph/contracts/executable-graph.js +1 -1
  18. package/dist/graph/hitl/hitl-handler.d.ts +3 -0
  19. package/dist/graph/hitl/hitl-handler.js +4 -1
  20. package/dist/graph/nodes/tool-node.js +27 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +1 -1
  23. package/dist/providers/echo-llm-provider.js +2 -2
  24. package/dist/providers/local-llm-provider.d.ts +1 -1
  25. package/dist/providers/local-llm-provider.js +1 -1
  26. package/dist/providers/routing-llm-provider.js +1 -1
  27. package/dist/registry/agent-models.d.ts +1 -1
  28. package/dist/registry/agent-models.js +1 -1
  29. package/dist/registry/agent-registry-client.d.ts +1 -1
  30. package/dist/registry/deployment-snapshot.d.ts +14 -2
  31. package/dist/registry/deployment-snapshot.js +57 -31
  32. package/dist/registry/graph-loader.d.ts +1 -1
  33. package/dist/registry/graph-loader.js +4 -4
  34. package/dist/registry/index.d.ts +1 -1
  35. package/dist/registry/index.js +1 -1
  36. package/dist/registry/preload-errors.d.ts +1 -1
  37. package/dist/registry/preload-errors.js +1 -1
  38. package/dist/registry/skill-io-guard.js +1 -1
  39. package/dist/registry/skill-tree.js +1 -1
  40. package/dist/runtime/agent-runtime.js +19 -5
  41. package/dist/runtime/dev-runtime.d.ts +1 -1
  42. package/dist/runtime/dev-runtime.js +6 -10
  43. package/dist/runtime/types.d.ts +2 -2
  44. package/dist/server.js +1 -1
  45. package/dist/sse/events/skill-events.d.ts +1 -1
  46. package/dist/sse/events/skill-events.js +1 -1
  47. package/dist/tool/create-tool-executor.d.ts +1 -1
  48. package/dist/tool/dev-http-executor.d.ts +1 -1
  49. package/dist/tool/dev-http-executor.js +10 -7
  50. package/dist/tool/dev-mcp-executor.js +5 -3
  51. package/dist/tool/dev-tool-executor.d.ts +1 -1
  52. package/dist/tool/dev-tool-executor.js +2 -2
  53. package/dist/tool/harness-command.d.ts +32 -0
  54. package/dist/tool/harness-command.js +276 -0
  55. package/dist/tool/harness-primitives.d.ts +25 -3
  56. package/dist/tool/harness-primitives.js +157 -50
  57. package/dist/tool/http-tool-executor.js +15 -20
  58. package/dist/tool/index.d.ts +3 -2
  59. package/dist/tool/index.js +3 -2
  60. package/dist/tool/local-mcp-client.d.ts +1 -1
  61. package/dist/tool/local-mcp-client.js +54 -23
  62. package/dist/tool/local-tool-executor.d.ts +1 -1
  63. package/dist/tool/local-tool-executor.js +13 -9
  64. package/dist/tool/mcp-tool-executor.js +14 -6
  65. package/dist/tool/skill-script-refs.d.ts +20 -0
  66. package/dist/tool/skill-script-refs.js +62 -0
  67. package/dist/tool/system-prompt-builder.d.ts +2 -2
  68. package/dist/tool/system-prompt-builder.js +8 -5
  69. package/dist/tool/timeout.d.ts +14 -0
  70. package/dist/tool/timeout.js +36 -0
  71. package/dist/tool/tool-confirmation.js +2 -1
  72. package/dist/tool/tool-execution-router.d.ts +1 -1
  73. package/dist/tool/tool-execution-router.js +1 -1
  74. package/dist/tool/tool-executor.interface.d.ts +13 -3
  75. package/dist/tool/tool-orchestrator.d.ts +1 -1
  76. package/dist/tool/tool-orchestrator.js +6 -3
  77. package/dist/utils/format-error.d.ts +2 -2
  78. package/dist/utils/format-error.js +2 -2
  79. package/dist/utils/http-error.d.ts +2 -2
  80. package/dist/utils/http-error.js +2 -2
  81. package/package.json +2 -2
  82. package/dist/common/concurrency.d.ts +0 -79
  83. package/dist/common/concurrency.js +0 -121
  84. package/dist/common/queue-consumer.d.ts +0 -125
  85. package/dist/common/queue-consumer.js +0 -159
  86. package/dist/tool/mock-tool-executor.d.ts +0 -28
  87. package/dist/tool/mock-tool-executor.js +0 -65
@@ -7,7 +7,8 @@
7
7
  import { spawn } from 'node:child_process';
8
8
  import { existsSync } from 'node:fs';
9
9
  import path from 'node:path';
10
- import { resolveReadyInstallByToolName, resolveMcpExecutionFromInstall, resolveCodeHandlerFromInstall, } from '@gt-fe/eap-contracts';
10
+ import { ErrorCodes, resolveReadyInstallByToolName, resolveMcpExecutionFromInstall, resolveCodeHandlerFromInstall, } from '@eap/contracts';
11
+ import { isAbortError, toToolTimeoutMs, toolTimeoutErrorMessage } from './timeout.js';
11
12
  import { fileArtifactFromToolOutput, fileBasesFromEnv } from './file-artifact.js';
12
13
  import { LocalMcpClient } from './local-mcp-client.js';
13
14
  /**
@@ -66,7 +67,9 @@ function runHandler(handlerPath, input, timeoutMs, execution) {
66
67
  };
67
68
  const timer = setTimeout(() => {
68
69
  child.kill('SIGKILL');
69
- finish(() => reject(new Error(`Local tool timeout after ${timeoutMs}ms`)));
70
+ const timeoutErr = new Error(toolTimeoutErrorMessage(Math.max(1, Math.round(timeoutMs / 1000))));
71
+ timeoutErr.name = 'TimeoutError';
72
+ finish(() => reject(timeoutErr));
70
73
  }, timeoutMs);
71
74
  child.stdout.on('data', (d) => { stdout += d.toString(); });
72
75
  child.stderr.on('data', (d) => { stderr += d.toString(); });
@@ -112,7 +115,7 @@ export function resolveCodeExecutionFromRequest(request) {
112
115
  const handler = typeof e.handler === 'string' ? e.handler : '';
113
116
  const command = typeof e.command === 'string' ? e.command : undefined;
114
117
  const args = Array.isArray(e.args) ? e.args.map(String) : undefined;
115
- const timeoutMs = typeof e.timeout === 'number' && e.timeout > 0 ? e.timeout : undefined;
118
+ const timeoutMs = typeof e.timeout === 'number' && e.timeout > 0 ? toToolTimeoutMs(e.timeout) : undefined;
116
119
  if (runtime === 'executable') {
117
120
  const handlerPath = handler
118
121
  ? (path.isAbsolute(handler) || !bundlePath
@@ -181,14 +184,14 @@ export class LocalToolExecutor {
181
184
  };
182
185
  }
183
186
  catch (error) {
184
- const message = error instanceof Error ? error.message : String(error);
187
+ const aborted = isAbortError(error);
185
188
  return {
186
189
  toolCallId: request.toolCallId,
187
190
  toolName: request.toolName,
188
191
  content: '',
189
192
  success: false,
190
- error: message,
191
- errorCode: 'EXECUTION_FAILED',
193
+ error: error instanceof Error ? error.message : String(error),
194
+ errorCode: aborted ? ErrorCodes.TOOL_TIMEOUT : 'EXECUTION_FAILED',
192
195
  runtime: 'local',
193
196
  };
194
197
  }
@@ -203,6 +206,7 @@ export class LocalToolExecutor {
203
206
  handlerPath,
204
207
  runtime: execution?.runtime,
205
208
  args: execution?.args,
209
+ timeoutMs: typeof execution?.timeout === 'number' ? toToolTimeoutMs(execution.timeout) : undefined,
206
210
  });
207
211
  }
208
212
  async executeMcp(request, install) {
@@ -232,14 +236,14 @@ export class LocalToolExecutor {
232
236
  };
233
237
  }
234
238
  catch (error) {
235
- const message = error instanceof Error ? error.message : String(error);
239
+ const aborted = isAbortError(error);
236
240
  return {
237
241
  toolCallId: request.toolCallId,
238
242
  toolName: request.toolName,
239
243
  content: '',
240
244
  success: false,
241
- error: message,
242
- errorCode: 'EXECUTION_FAILED',
245
+ error: error instanceof Error ? error.message : String(error),
246
+ errorCode: aborted ? ErrorCodes.TOOL_TIMEOUT : 'EXECUTION_FAILED',
243
247
  runtime: 'local',
244
248
  };
245
249
  }
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * @see docs/contracts/tool-execution.md §2.0
5
5
  */
6
+ import { ErrorCodes } from '@eap/contracts';
7
+ import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
6
8
  function resolveAgentCode(request) {
7
9
  return request.authContext.agentCode;
8
10
  }
@@ -14,9 +16,14 @@ function formatContent(result) {
14
16
  }
15
17
  function mapMcpError(status, body) {
16
18
  const eapCode = body?.error?.data?.eapCode;
17
- const message = body?.error?.message ?? `HTTP ${status}`;
18
- if (status === 403 || eapCode?.includes('forbidden') || eapCode?.includes('auth')) {
19
- return { error: message, errorCode: eapCode ?? 'NO_PERMISSION', retryable: false };
19
+ const message = (body?.error?.message ?? '').trim() || `HTTP ${status}`;
20
+ // 仅 HTTP 403 视为权限拒绝。eapCode 含 auth 是 hop 认证失败,不是 C2。
21
+ if (status === 403) {
22
+ return {
23
+ error: message === `HTTP ${status}` ? '用户无该 Tool 执行权限' : message,
24
+ errorCode: eapCode ?? 'NO_PERMISSION',
25
+ retryable: false,
26
+ };
20
27
  }
21
28
  if (status === 429) {
22
29
  return { error: message, errorCode: eapCode ?? 'RATE_LIMITED', retryable: true };
@@ -104,14 +111,15 @@ export class McpToolExecutor {
104
111
  };
105
112
  }
106
113
  catch (err) {
107
- const message = err instanceof Error ? err.message : String(err);
114
+ const timeoutSec = Math.max(1, Math.round(this.timeout / 1000));
115
+ const aborted = isAbortError(err);
108
116
  return {
109
117
  toolCallId: request.toolCallId,
110
118
  toolName: request.toolName,
111
119
  content: '',
112
120
  success: false,
113
- error: message,
114
- errorCode: 'EXECUTION_FAILED',
121
+ error: abortOrTimeoutMessage(err, timeoutSec),
122
+ errorCode: aborted ? ErrorCodes.TOOL_TIMEOUT : 'EXECUTION_FAILED',
115
123
  retryable: false,
116
124
  runtime: 'server',
117
125
  };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 喂给模型的 SKILL.md 正文展开(不改磁盘)。
3
+ * 对标 Claude `${CLAUDE_SKILL_DIR}`:作者仍写包内相对路径。
4
+ * 官方规范不要求脚本必须在 `scripts/`;EAP_SKILL_DIR 是整包绝对根。
5
+ * 展开成绝对路径,避免 Windows cmd 不认 `$VAR`、多技能时 env 未注入。
6
+ */
7
+ export declare const EAP_SKILL_DIR_ENV = "EAP_SKILL_DIR";
8
+ export interface ExpandSkillRefsOptions {
9
+ platform?: string;
10
+ exists?: (absPath: string) => boolean;
11
+ }
12
+ export declare function normalizeSkillDir(skillDir: string): string;
13
+ export declare function skillEnvRef(platform?: string): string;
14
+ /**
15
+ * 包内相对脚本路径 → 技能目录绝对路径。
16
+ * 带目录或 `./` 的(`bin/foo.py`、`./main.py`、`scripts/a.py`)一律展开。
17
+ * 根上的裸文件名(`main.py`)仅当包内确实有该文件才展开,以免改写「写到用户目录再跑」的例子。
18
+ */
19
+ export declare function expandSkillScriptRefs(body: string, skillDir: string, platformOrOpts?: string | ExpandSkillRefsOptions): string;
20
+ export declare function isSkillEntryFile(filePath: string): boolean;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 喂给模型的 SKILL.md 正文展开(不改磁盘)。
3
+ * 对标 Claude `${CLAUDE_SKILL_DIR}`:作者仍写包内相对路径。
4
+ * 官方规范不要求脚本必须在 `scripts/`;EAP_SKILL_DIR 是整包绝对根。
5
+ * 展开成绝对路径,避免 Windows cmd 不认 `$VAR`、多技能时 env 未注入。
6
+ */
7
+ import { existsSync } from 'node:fs';
8
+ import path from 'node:path';
9
+ export const EAP_SKILL_DIR_ENV = 'EAP_SKILL_DIR';
10
+ /** 看起来像可执行/源码的扩展名。不是目录白名单。 */
11
+ const CODE_EXT = 'py|js|mjs|cjs|ts|tsx|jsx|sh|bash|zsh|ps1|bat|cmd|exe|rb|pl|php';
12
+ const RELATIVE_CODE_PATH = new RegExp(`(^|[^$\\w./\\\\:%])(\\.\\/)?((?:[\\w.-]+[\\\\/])*[\\w.-]+\\.(?:${CODE_EXT}))\\b`, 'gi');
13
+ export function normalizeSkillDir(skillDir) {
14
+ return skillDir.replace(/\\/g, '/').replace(/\/+$/, '');
15
+ }
16
+ export function skillEnvRef(platform = process.platform) {
17
+ return platform === 'win32' ? `%${EAP_SKILL_DIR_ENV}%` : `$${EAP_SKILL_DIR_ENV}`;
18
+ }
19
+ function asOptions(platformOrOpts) {
20
+ if (typeof platformOrOpts === 'string')
21
+ return { platform: platformOrOpts };
22
+ return platformOrOpts ?? {};
23
+ }
24
+ function defaultExists(absPath) {
25
+ try {
26
+ return existsSync(absPath);
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ /**
33
+ * 包内相对脚本路径 → 技能目录绝对路径。
34
+ * 带目录或 `./` 的(`bin/foo.py`、`./main.py`、`scripts/a.py`)一律展开。
35
+ * 根上的裸文件名(`main.py`)仅当包内确实有该文件才展开,以免改写「写到用户目录再跑」的例子。
36
+ */
37
+ export function expandSkillScriptRefs(body, skillDir, platformOrOpts) {
38
+ const opts = asOptions(platformOrOpts);
39
+ const platform = opts.platform ?? process.platform;
40
+ const exists = opts.exists ?? defaultExists;
41
+ const dir = normalizeSkillDir(skillDir);
42
+ const expanded = body.replace(RELATIVE_CODE_PATH, (full, prefix, dotSlash, rel) => {
43
+ const relNorm = rel.replace(/\\/g, '/');
44
+ const packageRelative = Boolean(dotSlash) || relNorm.includes('/');
45
+ if (!packageRelative && !exists(path.join(skillDir, relNorm))) {
46
+ return full;
47
+ }
48
+ return `${prefix}${dir}/${relNorm}`;
49
+ });
50
+ const envRef = skillEnvRef(platform);
51
+ const envPath = platform === 'win32' ? `${envRef}\\<相对路径>` : `${envRef}/<相对路径>`;
52
+ const note = `技能目录(只读)环境变量 ${EAP_SKILL_DIR_ENV}=${dir}。` +
53
+ `包内任意文件用 ${envPath}(不限 scripts/)。` +
54
+ `读入正文里像包内脚本的相对路径已展开为绝对路径。` +
55
+ `当前工作目录是用户目录,相对路径写出在用户目录。\n\n`;
56
+ if (body.includes(`${EAP_SKILL_DIR_ENV}=`))
57
+ return expanded;
58
+ return note + expanded;
59
+ }
60
+ export function isSkillEntryFile(filePath) {
61
+ return /(^|[\\/])(SKILL\.md|skill\.md)$/i.test(filePath);
62
+ }
@@ -75,13 +75,13 @@ export interface PromptBuildContext {
75
75
  agentCode?: string;
76
76
  /** 当前调用用户标识(可选,供「我 / 我的」类指代) */
77
77
  userId?: string | null;
78
- /** 进程工作目录(文件/命令 jail) */
78
+ /** 当前用户目录(`users/<userId>`,含用户 skills) */
79
79
  workingDirectory?: string;
80
80
  os?: {
81
81
  platform: string;
82
82
  release: string;
83
83
  };
84
- /** 允许读写/执行的根目录(工作目录 + 技能包 + FILE_IO_ROOT 等) */
84
+ /** 允许 read_file 的根目录(写根 + 已装配 tools/skills) */
85
85
  jailRoots?: string[];
86
86
  skillDirs?: Array<{
87
87
  skillId: string;
@@ -17,7 +17,8 @@
17
17
  *
18
18
  * @see docs/status/module-status-and-backlog.md RT-5
19
19
  */
20
- import { isBuiltinToolExecution } from '@gt-fe/eap-contracts';
20
+ import { isBuiltinToolExecution } from '@eap/contracts';
21
+ import { commandDialectLines } from './harness-command.js';
21
22
  // ─────────────────────────────────────────────
22
23
  // 平台规则常量(一年改不了两次,不值得搞配置表)
23
24
  // ─────────────────────────────────────────────
@@ -192,10 +193,12 @@ export class SystemPromptBuilder {
192
193
  const lines = ['## 文件与命令权限'];
193
194
  if (workdir) {
194
195
  lines.push(`工作目录:${workdir}`);
195
- lines.push('`execute_command` 的 cwd **必须**是工作目录,或其位于 jail 内的子目录。');
196
+ lines.push('`execute_command` 的 cwd **必须**是工作目录或其子目录,不能是预装技能/工具包。');
197
+ lines.push('技能包只读。跑包内文件用环境变量 `EAP_SKILL_DIR`(技能包绝对根,不限 `scripts/`)。相对写出在工作目录。');
196
198
  }
199
+ lines.push(...commandDialectLines(context.os?.platform ?? process.platform));
197
200
  if (skills.length > 0) {
198
- lines.push('已装配技能目录(要用时 `read_file` 读该目录下的 `SKILL.md`;scripts / references 默认也在该目录):');
201
+ lines.push('已装配技能目录(要用时 `read_file` 读该目录下的 `SKILL.md`;跑包内文件用 `EAP_SKILL_DIR/<相对路径>`):');
199
202
  for (const skill of skills) {
200
203
  const entry = skill.entryFile ?? `${skill.bundlePath}/SKILL.md`;
201
204
  lines.push(`- ${skill.skillId}:${skill.bundlePath} (入口 ${entry})`);
@@ -208,12 +211,12 @@ export class SystemPromptBuilder {
208
211
  lines.push(`- ${root}`);
209
212
  }
210
213
  if (workdir) {
211
- lines.push('`write_file` **仅**允许写入工作目录;技能包只读。');
214
+ lines.push('`write_file` **仅**允许写入当前用户目录(含用户 skills);预装 tools / skills 只读。');
212
215
  }
213
216
  lines.push([
214
217
  '硬限制:',
215
218
  '- `read_file` / `write_file` 的路径必须落在上述允许根内。用户要求越界必须拒绝。',
216
- '- `execute_command` 的 cwd 必须落在 jail 内;子进程不继承 API Key 等密钥环境变量。命令本身不是操作系统级文件系统沙箱,禁止尝试读取 `/etc`、家目录或 jail 外路径。',
219
+ '- `execute_command` 的 cwd 必须落在工作目录内;命令语法跟当前 shell;子进程不继承 API Key 等密钥环境变量。命令本身不是操作系统级文件系统沙箱,禁止尝试读取 `/etc`、家目录或 jail 外路径。',
217
220
  '- 禁止用 `..` 逃逸;禁止把 jail 外的绝对路径当参数。',
218
221
  '- 不要把内部绝对路径、offload 目录原样展示给用户。',
219
222
  '- 技能正文按需读取,不要把技能目录整包读进回复。',
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tool / toolset `timeout`:秒。
3
+ * 与 `@eap/contracts` `src/tool/timeout.ts` 同语义;Runtime 钉的是 contracts 0.0.9,
4
+ * 尚未含这些导出。发版 contracts 后改为从 `@eap/contracts` 引用。
5
+ */
6
+ export declare const DEFAULT_TOOL_TIMEOUT_SEC = 30;
7
+ export declare const DEFAULT_HTTP_TOOL_TIMEOUT_SEC = 10;
8
+ export declare const MIN_TOOL_TIMEOUT_SEC = 1;
9
+ export declare const MAX_TOOL_TIMEOUT_SEC = 300;
10
+ export declare function resolveToolTimeoutSec(timeoutSec: unknown, fallbackSec?: number): number;
11
+ export declare function toToolTimeoutMs(timeoutSec: unknown, fallbackSec?: number): number;
12
+ export declare function isAbortError(error: unknown): boolean;
13
+ export declare function toolTimeoutErrorMessage(timeoutSec: number): string;
14
+ export declare function abortOrTimeoutMessage(error: unknown, timeoutSec: number): string;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Tool / toolset `timeout`:秒。
3
+ * 与 `@eap/contracts` `src/tool/timeout.ts` 同语义;Runtime 钉的是 contracts 0.0.9,
4
+ * 尚未含这些导出。发版 contracts 后改为从 `@eap/contracts` 引用。
5
+ */
6
+ export const DEFAULT_TOOL_TIMEOUT_SEC = 30;
7
+ export const DEFAULT_HTTP_TOOL_TIMEOUT_SEC = 10;
8
+ export const MIN_TOOL_TIMEOUT_SEC = 1;
9
+ export const MAX_TOOL_TIMEOUT_SEC = 300;
10
+ export function resolveToolTimeoutSec(timeoutSec, fallbackSec = DEFAULT_TOOL_TIMEOUT_SEC) {
11
+ if (typeof timeoutSec !== 'number' || !Number.isFinite(timeoutSec) || timeoutSec <= 0) {
12
+ return fallbackSec;
13
+ }
14
+ return Math.min(MAX_TOOL_TIMEOUT_SEC, Math.max(MIN_TOOL_TIMEOUT_SEC, Math.trunc(timeoutSec)));
15
+ }
16
+ export function toToolTimeoutMs(timeoutSec, fallbackSec = DEFAULT_TOOL_TIMEOUT_SEC) {
17
+ return resolveToolTimeoutSec(timeoutSec, fallbackSec) * 1000;
18
+ }
19
+ export function isAbortError(error) {
20
+ if (!error || typeof error !== 'object')
21
+ return false;
22
+ const err = error;
23
+ if (err.name === 'AbortError' || err.name === 'TimeoutError' || err.code === 'ABORT_ERR') {
24
+ return true;
25
+ }
26
+ const msg = (err.message ?? '').toLowerCase();
27
+ return msg === 'this operation was aborted' || msg === 'the operation was aborted';
28
+ }
29
+ export function toolTimeoutErrorMessage(timeoutSec) {
30
+ return `timeout after ${resolveToolTimeoutSec(timeoutSec)}s`;
31
+ }
32
+ export function abortOrTimeoutMessage(error, timeoutSec) {
33
+ if (isAbortError(error))
34
+ return toolTimeoutErrorMessage(timeoutSec);
35
+ return error instanceof Error ? error.message : String(error);
36
+ }
@@ -50,7 +50,8 @@ export class ToolConfirmationInterceptor {
50
50
  toolName: request.toolName,
51
51
  content: `用户拒绝执行工具 ${request.toolName}`,
52
52
  success: false,
53
- error: 'hitl_rejected',
53
+ error: `用户拒绝执行工具 ${request.toolName}`,
54
+ errorCode: 'hitl_rejected',
54
55
  };
55
56
  }
56
57
  const resolvedThreadId = threadId ??
@@ -5,7 +5,7 @@
5
5
  * @see design.md §1.8.7
6
6
  */
7
7
  import type { IToolExecutor, ToolCallRequest } from './tool-executor.interface.js';
8
- import type { ILocalToolInstallStore } from '@gt-fe/eap-contracts';
8
+ import type { ILocalToolInstallStore } from '@eap/contracts';
9
9
  export type DeploymentContext = 'server' | 'desktop' | 'dev';
10
10
  export interface ToolExecutionRouterOptions {
11
11
  httpExecutor: IToolExecutor;
@@ -4,7 +4,7 @@
4
4
  * @see docs/contracts/tool-execution.md §6
5
5
  * @see design.md §1.8.7
6
6
  */
7
- import { resolveReadyInstallByToolName } from '@gt-fe/eap-contracts';
7
+ import { resolveReadyInstallByToolName } from '@eap/contracts';
8
8
  import { isLocalPreferredExecution } from './local-tool-executor.js';
9
9
  export class ToolExecutionRouter {
10
10
  options;
@@ -47,10 +47,20 @@ export interface ToolCallRequest {
47
47
  */
48
48
  execution?: unknown;
49
49
  /**
50
- * Harness 原语本轮额外 jail 根(已加载 skill 的 bundlePath)。
51
- * 与 system prompt 的 jail 根对齐;未传时用构造时 extraRoots / 默认目录。
50
+ * Harness 写根:`{agentDir}/users/<userId>`(含用户 skills)。
51
+ * 未传时用构造时 workdir;都缺则拒绝 write_file / execute_command。
52
+ */
53
+ jailWorkdir?: string;
54
+ /**
55
+ * Harness 本轮只读根:已装配 skill / toolset 的 bundlePath。
56
+ * 与 system prompt 的 jail 根对齐。
52
57
  */
53
58
  jailExtraRoots?: string[];
59
+ /**
60
+ * 当前技能包绝对路径,注入子进程环境变量 EAP_SKILL_DIR。
61
+ * 对标 Claude CLAUDE_SKILL_DIR。
62
+ */
63
+ skillDir?: string;
54
64
  }
55
65
  /**
56
66
  * ToolCallResult — Tool 调用结果
@@ -64,7 +74,7 @@ export interface ToolCallResult {
64
74
  /** Gateway / 本地执行结构化错误码 */
65
75
  errorCode?: string;
66
76
  retryable?: boolean;
67
- runtime?: 'local' | 'server' | 'mock';
77
+ runtime?: 'local' | 'server';
68
78
  /** 工具产出的可下载/可打开文件;不进模型正文 */
69
79
  file?: {
70
80
  fileName: string;
@@ -4,7 +4,7 @@
4
4
  * @see design.md §1.1 Tool调用编排
5
5
  * @see requirements.md Requirement 8 AC1, AC3
6
6
  */
7
- import type { ExecutionContext } from '@gt-fe/eap-contracts';
7
+ import { type ExecutionContext } from '@eap/contracts';
8
8
  import type { IGovernanceClient } from '../governance/governance-client.js';
9
9
  export type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCall, LLMToolCallResponse, IToolExecutor, } from './tool-executor.interface.js';
10
10
  import type { AuthContext, ToolCallRequest, ToolCallResult, LLMToolCallResponse, IToolExecutor } from './tool-executor.interface.js';
@@ -4,6 +4,7 @@
4
4
  * @see design.md §1.1 Tool调用编排
5
5
  * @see requirements.md Requirement 8 AC1, AC3
6
6
  */
7
+ import { isBuiltinToolExecution } from '@eap/contracts';
7
8
  import { assertToolPermission, GovernanceUnavailableError, PermissionDeniedError, } from '../governance/permission-guard.js';
8
9
  /**
9
10
  * ToolOrchestrator — Tool 调用编排主服务
@@ -36,7 +37,9 @@ export class ToolOrchestrator {
36
37
  });
37
38
  }
38
39
  async executeSingle(request, executionContext) {
39
- if (this.governanceClient && executionContext?.delegationToken) {
40
+ if (this.governanceClient &&
41
+ executionContext?.delegationToken &&
42
+ !isBuiltinToolExecution(request.execution)) {
40
43
  try {
41
44
  await assertToolPermission(this.governanceClient, executionContext, request.toolName, {
42
45
  dataScope: request.dataScope ?? 'user-bound',
@@ -50,8 +53,8 @@ export class ToolOrchestrator {
50
53
  toolName: request.toolName,
51
54
  content: '',
52
55
  success: false,
53
- error: error.reason,
54
- errorCode: error instanceof PermissionDeniedError ? 'NO_PERMISSION' : 'GOVERNANCE_UNAVAILABLE',
56
+ error: error.message,
57
+ errorCode: error.reason,
55
58
  };
56
59
  }
57
60
  throw error;
@@ -1,4 +1,4 @@
1
1
  /**
2
- * 将未知异常格式化为可读字符串 — 委托 @gt-fe/eap-contracts
2
+ * 将未知异常格式化为可读字符串 — 委托 @eap/contracts
3
3
  */
4
- export { formatUnknownError } from '@gt-fe/eap-contracts';
4
+ export { formatUnknownError } from '@eap/contracts';
@@ -1,4 +1,4 @@
1
1
  /**
2
- * 将未知异常格式化为可读字符串 — 委托 @gt-fe/eap-contracts
2
+ * 将未知异常格式化为可读字符串 — 委托 @eap/contracts
3
3
  */
4
- export { formatUnknownError } from '@gt-fe/eap-contracts';
4
+ export { formatUnknownError } from '@eap/contracts';
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Runtime HTTP 错误出口 — 对齐 @gt-fe/eap-contracts ErrorCodes / AppError
2
+ * Runtime HTTP 错误出口 — 对齐 @eap/contracts ErrorCodes / AppError
3
3
  */
4
- import { type ErrorCode, type AppError } from '@gt-fe/eap-contracts';
4
+ import { type ErrorCode, type AppError } from '@eap/contracts';
5
5
  import type { ServerResponse } from 'node:http';
6
6
  /** 公共 classify;连接失败不再误标为 GOVERNANCE_UNAVAILABLE */
7
7
  export declare function classifyRuntimeError(error: unknown): AppError;
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Runtime HTTP 错误出口 — 对齐 @gt-fe/eap-contracts ErrorCodes / AppError
2
+ * Runtime HTTP 错误出口 — 对齐 @eap/contracts ErrorCodes / AppError
3
3
  */
4
- import { classifyError, formatUnknownError, ErrorCodes, } from '@gt-fe/eap-contracts';
4
+ import { classifyError, formatUnknownError, ErrorCodes, } from '@eap/contracts';
5
5
  /** 公共 classify;连接失败不再误标为 GOVERNANCE_UNAVAILABLE */
6
6
  export function classifyRuntimeError(error) {
7
7
  return classifyError(error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gt-fe/eap-runtime",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -23,6 +23,7 @@
23
23
  "start": "node dist/server.js",
24
24
  "dev": "npx tsx src/server.ts",
25
25
  "clean": "rm -rf dist",
26
+ "prepublishOnly": "npm run clean && npm run build",
26
27
  "lint": "eslint src --ext .ts",
27
28
  "test": "vitest run"
28
29
  },
@@ -36,7 +37,6 @@
36
37
  "license": "UNLICENSED",
37
38
  "dependencies": {
38
39
  "@langchain/langgraph": "^0.2.0",
39
- "amqplib": "^0.10.0",
40
40
  "ioredis": "^5.4.0",
41
41
  "pg": "^8.13.0",
42
42
  "yaml": "^2.6.1",
@@ -1,79 +0,0 @@
1
- /**
2
- * 并发控制模块
3
- *
4
- * 提供两种并发限制机制:
5
- * 1. ConcurrencyLimiter — 基于 amqplib channel.prefetch 的 Agent 级并发控制
6
- * 通过 RabbitMQ consumer prefetch 限制单 Agent 实例同时处理的任务数
7
- * 2. Semaphore — 纯内存信号量,用于本地并发控制(不依赖 RabbitMQ)
8
- *
9
- * @see Requirement 14 AC2: 系统通过消息消费者 prefetch 参数控制单 Agent 实例的并发上限(默认 prefetch=5)
10
- */
11
- import type { Channel } from 'amqplib';
12
- /** ConcurrencyLimiter 默认 prefetch 值 */
13
- export declare const DEFAULT_PREFETCH = 5;
14
- /**
15
- * 基于 amqplib consumer prefetch 的 Agent 级并发限制器
16
- *
17
- * RabbitMQ 的 channel.prefetch(n) 表示该 Channel 上的消费者最多同时
18
- * 持有 n 条未确认消息。超出限制的消息留在 Queue 中排队等待。
19
- *
20
- * 整体 QPS = Pod 数 × prefetch。
21
- */
22
- export declare class ConcurrencyLimiter {
23
- private readonly prefetchCount;
24
- /**
25
- * @param prefetchCount 单 Agent 实例最大并发任务数,默认 5
26
- */
27
- constructor(prefetchCount?: number);
28
- /**
29
- * 配置 amqplib Channel 的 prefetch
30
- *
31
- * 调用后该 Channel 上的消费者最多同时处理 prefetchCount 条消息。
32
- * 超出限制的消息在 RabbitMQ Queue 中排队等待。
33
- *
34
- * @param channel amqplib Channel 实例
35
- */
36
- configure(channel: Channel): Promise<void>;
37
- /**
38
- * 获取配置的最大并发数
39
- */
40
- getMaxConcurrency(): number;
41
- }
42
- /**
43
- * 内存信号量 — 用于本地并发控制
44
- *
45
- * 当无需依赖 RabbitMQ 时(如单进程内限流、测试场景),
46
- * 使用 Semaphore 控制并发执行数。
47
- *
48
- * 用法:
49
- * ```ts
50
- * const sem = new Semaphore(3);
51
- * await sem.acquire(); // 等待可用槽位
52
- * try {
53
- * // 执行并发受限的操作
54
- * } finally {
55
- * sem.release(); // 释放槽位
56
- * }
57
- * ```
58
- */
59
- export declare class Semaphore {
60
- private readonly maxSlots;
61
- private currentSlots;
62
- private readonly waitQueue;
63
- /**
64
- * @param maxConcurrency 最大并发数
65
- */
66
- constructor(maxConcurrency: number);
67
- /**
68
- * 获取一个可用槽位。如果当前无可用槽位,则等待直到有释放。
69
- */
70
- acquire(): Promise<void>;
71
- /**
72
- * 释放一个槽位。如果有等待者,唤醒队列中最先等待的。
73
- */
74
- release(): void;
75
- /**
76
- * 当前可用槽位数
77
- */
78
- get availableSlots(): number;
79
- }