@gt-fe/eap-runtime 0.0.16 → 0.0.18

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
@@ -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
 
@@ -277,26 +277,28 @@ EAP_TOOL_EXECUTION_MODE=mcp # 本地默认;http-debug 走 Gat
277
277
 
278
278
  ---
279
279
 
280
- ## 11. 发布到公司私有 npm(可选)
280
+ ## 11. 发布 npm 包
281
281
 
282
- 私服:`https://maven.gtcloud.cn/repository/npm-local/`(模板 [`.npmrc.example`](./.npmrc.example))。
282
+ 升版、CHANGELOG、`git commit` / `git push` 不在脚本里,先做完再跑脚本。脚本只做构建、发布、核对 registry 上的版本号。
283
283
 
284
- > `@eap:registry=...` 须写入 `.npmrc` 文件,不可在 shell 中作为命令直接执行。
284
+ | 脚本 | 包 | registry |
285
+ |------|----|----------|
286
+ | [`scripts/publish-intranet.sh`](./scripts/publish-intranet.sh) | `@eap/runtime` | Nexus `https://maven.gtcloud.cn/repository/npm-local/` |
287
+ | [`scripts/publish-public.sh`](./scripts/publish-public.sh) | `@gt-fe/eap-runtime` | npmjs。临时目录改名;dist 里 `@eap/contracts` 改为 `@gt-fe/eap-contracts`。不改源码 `package.json` |
285
288
 
286
289
  ```bash
290
+ # 登录(一次性)
287
291
  cp .npmrc.example .npmrc
288
292
  npm login --registry https://maven.gtcloud.cn/repository/npm-local/
293
+ npm login --registry https://registry.npmjs.org/
289
294
 
290
- # 1) 先发 @eap/contracts(version 改为目标版,如 0.0.1)
291
- cd ../eap-contracts && cp .npmrc.example .npmrc
292
- pnpm run build && npm publish --registry https://maven.gtcloud.cn/repository/npm-local/
293
-
294
- # 2) runtime:package.json 中 @eap/contracts 改为版本号(勿再用 file:)
295
- cd ../runtime && pnpm run build
296
- npm publish --registry https://maven.gtcloud.cn/repository/npm-local/
295
+ ./scripts/publish-intranet.sh
296
+ ./scripts/publish-public.sh
297
297
  ```
298
298
 
299
- 含 `_authToken` 的 `.npmrc` 已 gitignore,勿提交凭证。
299
+ 不要在源码目录对 npmjs 执行 `npm publish`(`publishConfig` 指向 Nexus;公网 dist 必须改写 contracts)。含 `_authToken` 的 `.npmrc` 已 gitignore,勿提交凭证。`@eap:registry=...` 须写入 `.npmrc` 文件,不可在 shell 中当命令执行。
300
+
301
+ UAT Hosting 吃公网包:把 `@eap/runtime` 指到 `npm:@gt-fe/eap-runtime@<本版>` 后重建镜像。
300
302
 
301
303
  ---
302
304
 
@@ -40,13 +40,17 @@ export interface CreateCheckpointParams {
40
40
  * 确保所有必要字段的一致性。
41
41
  */
42
42
  export declare function createCheckpointData(params: CreateCheckpointParams): CheckpointData;
43
+ /**
44
+ * 落盘用快照:可 JSON 往返的数据留下;定时器 / 函数 / 环丢掉。
45
+ * 环用栈判定,同一对象出现两次但不成环(菱形)仍保留。
46
+ */
47
+ export declare function toJsonSafe(value: unknown, stack?: Set<object>): unknown;
48
+ /** 所有 Checkpoint Saver 落盘共用,禁止裸 JSON.stringify(state)。 */
49
+ export declare function stringifyJsonSafe(value: unknown): string;
43
50
  /**
44
51
  * 序列化 CheckpointData 为 JSON 字符串
45
52
  *
46
- * 处理边界情况:
47
- * - Date 对象 → ISO 字符串
48
- * - undefined 值自动剔除(JSON.stringify 默认行为)
49
- * - 循环引用检测(抛出明确错误而非默认 TypeError)
53
+ * Date → ISO;定时器 / 循环引用省略,不抛 TypeError。
50
54
  */
51
55
  export declare function serializeCheckpoint(data: CheckpointData): string;
52
56
  /**
@@ -37,30 +37,69 @@ export function createCheckpointData(params) {
37
37
  // ─────────────────────────────────────────────
38
38
  // 序列化 / 反序列化
39
39
  // ─────────────────────────────────────────────
40
+ function isTimerHandle(value) {
41
+ const name = value.constructor?.name;
42
+ if (name === 'Timeout' || name === 'TimersList' || name === 'Immediate')
43
+ return true;
44
+ return Object.prototype.hasOwnProperty.call(value, '_idlePrev')
45
+ && Object.prototype.hasOwnProperty.call(value, '_idleNext');
46
+ }
47
+ /**
48
+ * 落盘用快照:可 JSON 往返的数据留下;定时器 / 函数 / 环丢掉。
49
+ * 环用栈判定,同一对象出现两次但不成环(菱形)仍保留。
50
+ */
51
+ export function toJsonSafe(value, stack = new Set()) {
52
+ if (value === null || typeof value === 'string' || typeof value === 'boolean')
53
+ return value;
54
+ if (typeof value === 'number')
55
+ return Number.isFinite(value) ? value : null;
56
+ if (typeof value === 'bigint')
57
+ return value.toString();
58
+ if (typeof value === 'undefined' || typeof value === 'function' || typeof value === 'symbol') {
59
+ return undefined;
60
+ }
61
+ if (value instanceof Date)
62
+ return value.toISOString();
63
+ if (typeof value !== 'object')
64
+ return undefined;
65
+ if (isTimerHandle(value))
66
+ return undefined;
67
+ if (stack.has(value))
68
+ return undefined;
69
+ const maybeToJSON = value.toJSON;
70
+ if (typeof maybeToJSON === 'function') {
71
+ return toJsonSafe(maybeToJSON.call(value), stack);
72
+ }
73
+ if (Array.isArray(value)) {
74
+ stack.add(value);
75
+ const out = value.map((item) => {
76
+ const next = toJsonSafe(item, stack);
77
+ return next === undefined ? null : next;
78
+ });
79
+ stack.delete(value);
80
+ return out;
81
+ }
82
+ stack.add(value);
83
+ const out = {};
84
+ for (const [key, child] of Object.entries(value)) {
85
+ const next = toJsonSafe(child, stack);
86
+ if (next !== undefined)
87
+ out[key] = next;
88
+ }
89
+ stack.delete(value);
90
+ return out;
91
+ }
92
+ /** 所有 Checkpoint Saver 落盘共用,禁止裸 JSON.stringify(state)。 */
93
+ export function stringifyJsonSafe(value) {
94
+ return JSON.stringify(toJsonSafe(value));
95
+ }
40
96
  /**
41
97
  * 序列化 CheckpointData 为 JSON 字符串
42
98
  *
43
- * 处理边界情况:
44
- * - Date 对象 → ISO 字符串
45
- * - undefined 值自动剔除(JSON.stringify 默认行为)
46
- * - 循环引用检测(抛出明确错误而非默认 TypeError)
99
+ * Date → ISO;定时器 / 循环引用省略,不抛 TypeError。
47
100
  */
48
101
  export function serializeCheckpoint(data) {
49
- const seen = new WeakSet();
50
- return JSON.stringify(data, (_key, value) => {
51
- // Date → ISO 字符串
52
- if (value instanceof Date) {
53
- return value.toISOString();
54
- }
55
- // 循环引用检测
56
- if (typeof value === 'object' && value !== null) {
57
- if (seen.has(value)) {
58
- throw new Error(`[CheckpointSerializer] Circular reference detected during serialization`);
59
- }
60
- seen.add(value);
61
- }
62
- return value;
63
- });
102
+ return stringifyJsonSafe(data);
64
103
  }
65
104
  /**
66
105
  * 反序列化 JSON 字符串为 CheckpointData
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
6
6
  import { join } from 'node:path';
7
+ import { stringifyJsonSafe } from './checkpoint-serializer.js';
7
8
  export class FileCheckpointSaver {
8
9
  baseDir;
9
10
  constructor(baseDir) {
@@ -12,7 +13,7 @@ export class FileCheckpointSaver {
12
13
  async save(threadId, data) {
13
14
  const dir = join(this.baseDir, threadId);
14
15
  await mkdir(dir, { recursive: true });
15
- await writeFile(join(dir, `${data.checkpointId}.json`), JSON.stringify(data));
16
+ await writeFile(join(dir, `${data.checkpointId}.json`), stringifyJsonSafe(data));
16
17
  // 更新 latest 指针
17
18
  await writeFile(join(dir, 'latest.json'), JSON.stringify({ checkpointId: data.checkpointId }));
18
19
  }
@@ -4,7 +4,7 @@ export type { RedisCheckpointSaverOptions } from './redis-checkpoint-saver.js';
4
4
  export { CompositeCheckpointSaver } from './composite-checkpoint-saver.js';
5
5
  export { restoreCheckpoint } from './restore-checkpoint.js';
6
6
  export { notifyCheckpointRef, stripCheckpointCollectors } from './checkpoint-ref-notify.js';
7
- export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, } from './checkpoint-serializer.js';
7
+ export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, stringifyJsonSafe, toJsonSafe, } from './checkpoint-serializer.js';
8
8
  export type { CreateCheckpointParams } from './checkpoint-serializer.js';
9
9
  export { FileCheckpointSaver } from './file-checkpoint-saver.js';
10
10
  export { InMemoryCheckpointSaver } from './in-memory-checkpoint-saver.js';
@@ -4,7 +4,7 @@ export { RedisCheckpointSaver } from './redis-checkpoint-saver.js';
4
4
  export { CompositeCheckpointSaver } from './composite-checkpoint-saver.js';
5
5
  export { restoreCheckpoint } from './restore-checkpoint.js';
6
6
  export { notifyCheckpointRef, stripCheckpointCollectors } from './checkpoint-ref-notify.js';
7
- export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, } from './checkpoint-serializer.js';
7
+ export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, stringifyJsonSafe, toJsonSafe, } from './checkpoint-serializer.js';
8
8
  export { FileCheckpointSaver } from './file-checkpoint-saver.js';
9
9
  export { InMemoryCheckpointSaver } from './in-memory-checkpoint-saver.js';
10
10
  export { MongoCheckpointSaver } from './mongo-checkpoint-saver.js';
@@ -7,6 +7,7 @@
7
7
  * @see design.md §1.3 checkpoints 数据模型
8
8
  * @see checkpoint-saver.contract.ts ICheckpointSaver 接口定义
9
9
  */
10
+ import { stringifyJsonSafe } from './checkpoint-serializer.js';
10
11
  // ─────────────────────────────────────────────
11
12
  // PostgresCheckpointSaver
12
13
  // ─────────────────────────────────────────────
@@ -38,9 +39,9 @@ export class PostgresCheckpointSaver {
38
39
  threadId,
39
40
  data.checkpointId,
40
41
  data.currentNodeId,
41
- JSON.stringify(data.historyPath),
42
- JSON.stringify(data.state),
43
- data.engineSnapshot == null ? null : JSON.stringify(data.engineSnapshot),
42
+ stringifyJsonSafe(data.historyPath),
43
+ stringifyJsonSafe(data.state),
44
+ data.engineSnapshot == null ? null : stringifyJsonSafe(data.engineSnapshot),
44
45
  data.createdBy || 'system',
45
46
  data.createdAt,
46
47
  data.updatedBy || 'system',
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * @see design.md §1.3 checkpoints 数据模型
11
11
  */
12
+ import { stringifyJsonSafe } from './checkpoint-serializer.js';
12
13
  /**
13
14
  * RedisCheckpointSaver — ICheckpointSaver 的 Redis 实现
14
15
  *
@@ -52,7 +53,7 @@ export class RedisCheckpointSaver {
52
53
  const idxKey = this.threadIndexKey(threadId);
53
54
  const ltKey = this.latestKey(threadId);
54
55
  const revKey = this.reverseIndexKey(data.checkpointId);
55
- const json = JSON.stringify(data);
56
+ const json = stringifyJsonSafe(data);
56
57
  const score = new Date(data.createdAt).getTime();
57
58
  const pipeline = this.redis.pipeline();
58
59
  // 存储 Checkpoint JSON
@@ -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.16",
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",
3
+ "version": "0.0.18",
4
+ "description": "Enterprise Agent Platform — Runtime核心:Graph执行引擎、Task生命周期、Session管理、Checkpoint持久化",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",