@gt-fe/eap-runtime 0.0.15 → 0.0.17

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 CHANGED
@@ -163,7 +163,7 @@ DSL / 契约细节见 `.kiro/specs/04-runtime-core/design.md` §1.4 与 `src/gra
163
163
  |------|------|------|
164
164
  | `EAP_PREINSTALL_ROOT` | — | 预装根;也可用 `GraphLoader({ preinstallRoot })` |
165
165
  | `EAP_PRELOAD_REQUIRE_READY` | 关 | 缺 `.ready` → `runtime.preload.missing` |
166
- | `EAP_PRELOAD_REQUIRE_CHECKSUM` | 关 | snapshot 须含 sha256 或 artifacts |
166
+ | `EAP_PRELOAD_REQUIRE_CHECKSUM` | 关 | snapshot 须含 sha256 或 artifacts。加载期比对文件摘要,不匹配则 warn、不中断 |
167
167
  | `EAP_PRELOAD_USER_SKILL_OVERRIDE` | 关 | `users` 优先于 `shared` |
168
168
  | `EAP_PRELOAD_VALIDATE_SKILL_IO` | 关 | 校验 Skill `inputDef.required`(`state.skillInput`) |
169
169
  | `EAP_PRELOAD_DEFINITION_CACHE` | 有预装时开 | 定义缓存;`0`/`false` 关闭,`1`/`true` 强制开 |
@@ -256,7 +256,7 @@ FOUNDATION_SERVICE_URL=...
256
256
  EAP_DEPLOYMENT_CONTEXT=dev
257
257
  EAP_PREINSTALL_ROOT=/abs/path/to/preinstall # 或 Agent 工程内预装树
258
258
  EAP_RUNTIME_STORAGE_PROFILE=dev
259
- EAP_TOOL_EXECUTION_MODE=mcp # 本地默认;http-debug 走 Gateway 调试口
259
+ EAP_TOOL_EXECUTION_MODE=mcp # 仅 createDevRuntime / eap chat;Hosting 的 TOOL_EXECUTION_MODE 不读
260
260
  # OPENAI_BASE_URL / OPENAI_API_KEY / EAP_DEFAULT_MODEL
261
261
  ```
262
262
 
@@ -17,6 +17,7 @@ import { parseSkillMd } from '@gt-fe/eap-contracts';
17
17
  import { findSkillEntryFile } from './skill-tree.js';
18
18
  import { preloadMissing, preloadPackageCorrupt, } from './preload-errors.js';
19
19
  import { isRequireChecksum, resolvePreloadBehaviorFlags, } from './preload-flags.js';
20
+ import { emitRuntimeLog } from '../common/logger.js';
20
21
  async function pathExists(p) {
21
22
  try {
22
23
  await access(p, fsConstants.F_OK);
@@ -115,17 +116,19 @@ async function findDigestMatch(items, expectedRaw) {
115
116
  }
116
117
  return undefined;
117
118
  }
118
- async function assertDigestMatch(expectedRaw, actualHex, label, details) {
119
+ async function warnIfDigestMismatch(expectedRaw, actualHex, label, details) {
119
120
  const expected = normalizeDigest(expectedRaw);
120
121
  if (!expected)
121
122
  return;
122
- if (expected !== actualHex.toLowerCase()) {
123
- throw preloadPackageCorrupt(`预装校验和不匹配: ${label}`, {
124
- expected,
125
- actual: actualHex.toLowerCase(),
126
- ...details,
127
- });
128
- }
123
+ const actual = actualHex.toLowerCase();
124
+ if (expected === actual)
125
+ return;
126
+ emitRuntimeLog('warn', 'preload', `预装校验和不匹配: ${label}`, {
127
+ event: 'runtime.preload.digest_mismatch',
128
+ expected,
129
+ actual,
130
+ ...details,
131
+ });
129
132
  }
130
133
  async function pickLatestVersionDir(parent) {
131
134
  if (!(await pathExists(parent)))
@@ -358,10 +361,14 @@ export class FileSystemDeploymentSnapshot {
358
361
  const raw = (await readTextFile(pkgFile))?.trim() ?? '';
359
362
  const fileDigest = normalizeDigest(raw);
360
363
  if (!fileDigest) {
361
- throw preloadPackageCorrupt(`.package.sha256 内容非法`, { path: pkgFile });
364
+ emitRuntimeLog('warn', 'preload', '.package.sha256 内容非法', {
365
+ event: 'runtime.preload.digest_mismatch',
366
+ path: pkgFile,
367
+ });
362
368
  }
363
- if (fileDigest !== expectedPkg) {
364
- throw preloadPackageCorrupt(`snapshot.sha256 与 .package.sha256 不一致(以 snapshot 为准核对)`, {
369
+ else if (fileDigest !== expectedPkg) {
370
+ emitRuntimeLog('warn', 'preload', 'snapshot.sha256 与 .package.sha256 不一致', {
371
+ event: 'runtime.preload.digest_mismatch',
365
372
  expected: expectedPkg,
366
373
  actual: fileDigest,
367
374
  path: pkgFile,
@@ -387,7 +394,7 @@ export class FileSystemDeploymentSnapshot {
387
394
  throw preloadPackageCorrupt(`artifacts 指向的文件不存在: ${rel}`, { path: full });
388
395
  }
389
396
  const actual = await sha256OfFile(full);
390
- await assertDigestMatch(expected, actual, `artifact ${rel}`, { path: full });
397
+ await warnIfDigestMismatch(expected, actual, `artifact ${rel}`, { path: full });
391
398
  }
392
399
  }
393
400
  async resolveAgentDir(agentCode, version) {
@@ -492,7 +499,7 @@ export class FileSystemDeploymentSnapshot {
492
499
  if (ref.sha256) {
493
500
  const matched = await findDigestMatch(existingManifests, ref.sha256);
494
501
  if (!matched) {
495
- await assertDigestMatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path });
502
+ await warnIfDigestMismatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path });
496
503
  }
497
504
  chosen = matched ?? existingManifests[0];
498
505
  }
@@ -564,9 +571,12 @@ export class FileSystemDeploymentSnapshot {
564
571
  continue;
565
572
  const isUserOverridePath = bundlePath.includes('/users/') || bundlePath.includes('\\users\\');
566
573
  const isAuthorOverlay = extraRoots.some((root) => root && isPathInside(root, bundlePath));
567
- if (ref.sha256 && !isUserOverridePath && !isAuthorOverlay && manifestPath.endsWith('.json')) {
574
+ if (ref.sha256 &&
575
+ !isUserOverridePath &&
576
+ !isAuthorOverlay &&
577
+ manifestPath.endsWith('.json')) {
568
578
  const actual = await sha256OfFile(manifestPath);
569
- await assertDigestMatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
579
+ await warnIfDigestMismatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
570
580
  }
571
581
  const skillId = String(ref.skillId);
572
582
  return {
@@ -5,7 +5,7 @@
5
5
  * |------|------|------|
6
6
  * | EAP_PRELOAD_REQUIRE_READY | 关 | 强制校验 .ready |
7
7
  * | EAP_PRELOAD_VALIDATE_SKILL_IO | **开** | 执行子图前校验 skill inputDef.required;`0`/`false` 关闭 |
8
- * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[] |
8
+ * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[](字段存在性)。加载期摘要不匹配为 warn |
9
9
  * | EAP_PRELOAD_DEFINITION_CACHE | 有预装时开 | 进程内定义缓存;`0`/`false` 关闭 |
10
10
  * | EAP_SKILL_HOT_RELOAD | **dev 开 / server 关** | 已装配 Skill 的目录 mtime 变化则丢缓存;未写入装配名单的目录不进清单 |
11
11
  * | EAP_LOCAL_SKILLS_DIR | 关 | 开发态作者源 Skill 根;查找优先于 preinstall,低于 users/ |
@@ -5,7 +5,7 @@
5
5
  * |------|------|------|
6
6
  * | EAP_PRELOAD_REQUIRE_READY | 关 | 强制校验 .ready |
7
7
  * | EAP_PRELOAD_VALIDATE_SKILL_IO | **开** | 执行子图前校验 skill inputDef.required;`0`/`false` 关闭 |
8
- * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[] |
8
+ * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[](字段存在性)。加载期摘要不匹配为 warn |
9
9
  * | EAP_PRELOAD_DEFINITION_CACHE | 有预装时开 | 进程内定义缓存;`0`/`false` 关闭 |
10
10
  * | EAP_SKILL_HOT_RELOAD | **dev 开 / server 关** | 已装配 Skill 的目录 mtime 变化则丢缓存;未写入装配名单的目录不进清单 |
11
11
  * | EAP_LOCAL_SKILLS_DIR | 关 | 开发态作者源 Skill 根;查找优先于 preinstall,低于 users/ |
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * createToolExecutor — 按部署上下文组装 Router + Gateway/Local 执行器
3
3
  *
4
- * server / hosting:**默认 MCP**(McpToolExecutor → POST /api/v1/mcp);
4
+ * server / hosting:**默认 MCP**(McpToolExecutor → POST {绝对基址}/api/v1/mcp);
5
5
  * `EAP_TOOL_GATEWAY_TRANSPORT=http` 时用 HttpToolExecutor(仅调试)。
6
6
  * desktop 经 ToolExecutionRouter 分流 Local / Gateway。
7
+ * desktop|dev 且 foundationUrl 不是绝对 http(s):不创建 Gateway 客户端,只挂 LocalToolExecutor。
8
+ * 不读 Hosting 的 TOOL_EXECUTION_MODE。
7
9
  */
8
10
  import type { ILocalToolInstallStore, LocalToolInstallRecord } from '@gt-fe/eap-contracts';
9
11
  import { type DeploymentContext } from './tool-execution-router.js';
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * createToolExecutor — 按部署上下文组装 Router + Gateway/Local 执行器
3
3
  *
4
- * server / hosting:**默认 MCP**(McpToolExecutor → POST /api/v1/mcp);
4
+ * server / hosting:**默认 MCP**(McpToolExecutor → POST {绝对基址}/api/v1/mcp);
5
5
  * `EAP_TOOL_GATEWAY_TRANSPORT=http` 时用 HttpToolExecutor(仅调试)。
6
6
  * desktop 经 ToolExecutionRouter 分流 Local / Gateway。
7
+ * desktop|dev 且 foundationUrl 不是绝对 http(s):不创建 Gateway 客户端,只挂 LocalToolExecutor。
8
+ * 不读 Hosting 的 TOOL_EXECUTION_MODE。
7
9
  */
8
10
  import { HttpToolExecutor } from './http-tool-executor.js';
9
- import { McpToolExecutor } from './mcp-tool-executor.js';
11
+ import { isAbsoluteHttpUrl, McpToolExecutor } from './mcp-tool-executor.js';
10
12
  import { LocalToolExecutor } from './local-tool-executor.js';
11
13
  import { ToolExecutionRouter, } from './tool-execution-router.js';
12
14
  import { withHarnessPrimitives } from './harness-primitives.js';
@@ -46,14 +48,19 @@ function createServerExecutor(foundationUrl, transport, timeout) {
46
48
  */
47
49
  export function createToolExecutor(options) {
48
50
  const installStore = options.installStore ?? new EmptyInstallStore();
51
+ const localExecutor = new LocalToolExecutor({ installStore });
52
+ const deploymentContext = resolveDeploymentContext(options.deploymentContext);
53
+ const hasGateway = isAbsoluteHttpUrl(options.foundationUrl);
54
+ if (!hasGateway && (deploymentContext === 'desktop' || deploymentContext === 'dev')) {
55
+ return withHarnessPrimitives(localExecutor);
56
+ }
49
57
  const transport = resolveGatewayTransport(options.gatewayTransport);
50
58
  const serverExecutor = createServerExecutor(options.foundationUrl, transport, options.httpTimeout);
51
- const localExecutor = new LocalToolExecutor({ installStore });
52
59
  return withHarnessPrimitives(new ToolExecutionRouter({
53
60
  httpExecutor: serverExecutor,
54
61
  localExecutor,
55
62
  installStore,
56
- deploymentContext: resolveDeploymentContext(options.deploymentContext),
63
+ deploymentContext,
57
64
  toolTypes: options.toolTypes,
58
65
  toolRuntimes: options.toolRuntimes,
59
66
  forceToolServer: options.forceToolServer,
@@ -3,7 +3,7 @@ export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools
3
3
  export { toLoadedToolMap, type LoadedToolDefinition, type LoadedToolMap, type LoadedToolManifestRef, } from './loaded-tool-definition.js';
4
4
  export { ToolOrchestrator } from './tool-orchestrator.js';
5
5
  export { HttpToolExecutor, type HttpToolExecutorOptions, } from './http-tool-executor.js';
6
- export { McpToolExecutor, type McpToolExecutorOptions, } from './mcp-tool-executor.js';
6
+ export { McpToolExecutor, isAbsoluteHttpUrl, type McpToolExecutorOptions, } from './mcp-tool-executor.js';
7
7
  export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromRequest } from './local-tool-executor.js';
8
8
  export { DevMcpExecutor, type DevMcpExecutorOptions, } from './dev-mcp-executor.js';
9
9
  export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, type DevHttpExecution, } from './dev-http-executor.js';
@@ -2,7 +2,7 @@ export { hydrateMcpTools, isMcpDiscoverStub, needsMcpHydration, parseListedTools
2
2
  export { toLoadedToolMap, } from './loaded-tool-definition.js';
3
3
  export { ToolOrchestrator } from './tool-orchestrator.js';
4
4
  export { HttpToolExecutor, } from './http-tool-executor.js';
5
- export { McpToolExecutor, } from './mcp-tool-executor.js';
5
+ export { McpToolExecutor, isAbsoluteHttpUrl, } from './mcp-tool-executor.js';
6
6
  export { LocalToolExecutor, isLocalPreferredExecution, resolveCodeExecutionFromRequest } from './local-tool-executor.js';
7
7
  export { DevMcpExecutor, } from './dev-mcp-executor.js';
8
8
  export { DevHttpExecutor, buildHttpToolUrl, resolveHttpExecutionFromRequest, } from './dev-http-executor.js';
@@ -4,6 +4,8 @@
4
4
  * @see docs/contracts/tool-execution.md §2.0
5
5
  */
6
6
  import type { IToolExecutor, McpListedTool, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
7
+ /** Tool Gateway 基址:必须是绝对 http(s)。空串 / 相对路径禁止 fetch。 */
8
+ export declare function isAbsoluteHttpUrl(value: unknown): boolean;
7
9
  export interface McpToolExecutorOptions {
8
10
  baseUrl: string;
9
11
  timeout?: number;
@@ -4,8 +4,24 @@
4
4
  * @see docs/contracts/tool-execution.md §2.0
5
5
  */
6
6
  import { ErrorCodes } from '@gt-fe/eap-contracts';
7
+ import { emitRuntimeLog } from '../common/logger.js';
7
8
  import { abortOrTimeoutMessage, isAbortError } from './timeout.js';
8
9
  import { nextListCursor, parseListedTools } from './mcp-list.js';
10
+ /** Tool Gateway 基址:必须是绝对 http(s)。空串 / 相对路径禁止 fetch。 */
11
+ export function isAbsoluteHttpUrl(value) {
12
+ if (typeof value !== 'string')
13
+ return false;
14
+ const trimmed = value.trim();
15
+ if (!trimmed)
16
+ return false;
17
+ try {
18
+ const url = new URL(trimmed);
19
+ return url.protocol === 'http:' || url.protocol === 'https:';
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
9
25
  function resolveAgentCode(request) {
10
26
  return request.authContext.agentCode;
11
27
  }
@@ -49,7 +65,14 @@ export class McpToolExecutor {
49
65
  /** endpoint+user+agent → in-flight / completed tools/list */
50
66
  listCache = new Map();
51
67
  constructor(options) {
52
- this.baseUrl = options.baseUrl.replace(/\/+$/, '');
68
+ if (!isAbsoluteHttpUrl(options.baseUrl)) {
69
+ emitRuntimeLog('warn', 'tool', 'McpToolExecutor 基址不是绝对 http(s),拒绝请求', {
70
+ event: 'runtime.tool.gateway_base_invalid',
71
+ baseUrl: options.baseUrl,
72
+ });
73
+ throw new Error(`McpToolExecutor 需要绝对 http(s) 基址,收到 ${JSON.stringify(options.baseUrl ?? '')}`);
74
+ }
75
+ this.baseUrl = options.baseUrl.trim().replace(/\/+$/, '');
53
76
  this.timeout = options.timeout ?? 30_000;
54
77
  const path = options.mcpPath ?? '/api/v1/mcp';
55
78
  this.mcpUrl = `${this.baseUrl}${path.startsWith('/') ? path : `/${path}`}`;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gt-fe/eap-runtime",
3
- "version": "0.0.15",
4
- "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
3
+ "version": "0.0.17",
4
+ "description": "Enterprise Agent Platform \u2014 Runtime\u6838\u5fc3\uff1aGraph\u6267\u884c\u5f15\u64ce\u3001Task\u751f\u547d\u5468\u671f\u3001Session\u7ba1\u7406\u3001Checkpoint\u6301\u4e45\u5316",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",