@gt-fe/eap-runtime 0.0.20 → 0.0.22

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 (133) hide show
  1. package/README.md +4 -3
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +52 -10
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +60 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/checkpoint/file-checkpoint-saver.d.ts +4 -0
  8. package/dist/checkpoint/file-checkpoint-saver.js +51 -22
  9. package/dist/checkpoint/index.d.ts +1 -1
  10. package/dist/checkpoint/index.js +1 -1
  11. package/dist/common/types.d.ts +2 -2
  12. package/dist/common/types.js +1 -1
  13. package/dist/execution/index.d.ts +2 -0
  14. package/dist/execution/index.js +2 -0
  15. package/dist/execution/interceptors.d.ts +40 -0
  16. package/dist/execution/interceptors.js +23 -0
  17. package/dist/execution/ports.d.ts +18 -0
  18. package/dist/execution/ports.js +13 -0
  19. package/dist/governance/audit-reporter.d.ts +14 -0
  20. package/dist/governance/audit-reporter.js +18 -0
  21. package/dist/governance/index.d.ts +2 -0
  22. package/dist/governance/index.js +1 -0
  23. package/dist/governance/permission-guard.d.ts +6 -2
  24. package/dist/governance/permission-guard.js +38 -5
  25. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  26. package/dist/graph/adapters/graph-nesting.js +29 -0
  27. package/dist/graph/adapters/index.d.ts +2 -0
  28. package/dist/graph/adapters/index.js +2 -0
  29. package/dist/graph/adapters/langgraph-checkpointer.d.ts +11 -0
  30. package/dist/graph/adapters/langgraph-checkpointer.js +48 -9
  31. package/dist/graph/adapters/langgraph.adapter.d.ts +33 -11
  32. package/dist/graph/adapters/langgraph.adapter.js +200 -98
  33. package/dist/graph/adapters/skill-isolate.d.ts +13 -0
  34. package/dist/graph/adapters/skill-isolate.js +39 -0
  35. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  36. package/dist/graph/adapters/subgraph-compiler.js +63 -30
  37. package/dist/graph/contracts/executable-graph.d.ts +33 -5
  38. package/dist/graph/hitl/hitl-handler.d.ts +13 -4
  39. package/dist/graph/hitl/hitl-handler.js +80 -7
  40. package/dist/graph/nodes/index.d.ts +1 -0
  41. package/dist/graph/nodes/index.js +1 -0
  42. package/dist/graph/nodes/llm-node.d.ts +3 -1
  43. package/dist/graph/nodes/llm-node.js +26 -2
  44. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  45. package/dist/graph/nodes/rag/dedup.js +77 -0
  46. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  47. package/dist/graph/nodes/rag/denoise.js +220 -0
  48. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  49. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  50. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  51. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  52. package/dist/graph/nodes/rag/index.d.ts +18 -0
  53. package/dist/graph/nodes/rag/index.js +17 -0
  54. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  55. package/dist/graph/nodes/rag/prompts.js +40 -0
  56. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  57. package/dist/graph/nodes/rag/rag-node.js +135 -0
  58. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  59. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  60. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  61. package/dist/graph/nodes/rag/react-loop.js +467 -0
  62. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  63. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  64. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  65. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  66. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  67. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  68. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  69. package/dist/graph/nodes/rag/tools.js +260 -0
  70. package/dist/graph/nodes/rag/types.d.ts +226 -0
  71. package/dist/graph/nodes/rag/types.js +9 -0
  72. package/dist/graph/nodes/tool-node.d.ts +19 -1
  73. package/dist/graph/nodes/tool-node.js +158 -33
  74. package/dist/index.d.ts +2 -0
  75. package/dist/index.js +1 -0
  76. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  77. package/dist/memory/semantic-retrieval.service.js +13 -23
  78. package/dist/providers/index.d.ts +2 -2
  79. package/dist/providers/index.js +2 -2
  80. package/dist/providers/local-llm-provider.d.ts +6 -0
  81. package/dist/providers/local-llm-provider.js +29 -5
  82. package/dist/providers/routing-llm-provider.d.ts +7 -2
  83. package/dist/providers/routing-llm-provider.js +71 -16
  84. package/dist/registry/deployment-snapshot.js +18 -4
  85. package/dist/registry/graph-loader.d.ts +2 -0
  86. package/dist/registry/graph-loader.js +39 -2
  87. package/dist/registry/preload-flags.d.ts +1 -1
  88. package/dist/registry/preload-flags.js +1 -1
  89. package/dist/runtime/agent-runtime.d.ts +5 -0
  90. package/dist/runtime/agent-runtime.js +107 -70
  91. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  92. package/dist/runtime/dev-rag-deps.js +186 -0
  93. package/dist/runtime/dev-runtime.d.ts +18 -2
  94. package/dist/runtime/dev-runtime.js +68 -8
  95. package/dist/runtime/index.d.ts +1 -1
  96. package/dist/runtime/types.d.ts +10 -9
  97. package/dist/session/apply-session-compression.d.ts +85 -0
  98. package/dist/session/apply-session-compression.js +281 -0
  99. package/dist/session/apply-session-memory.d.ts +21 -0
  100. package/dist/session/apply-session-memory.js +48 -0
  101. package/dist/session/index.d.ts +2 -0
  102. package/dist/session/index.js +2 -0
  103. package/dist/session/persist-turn-messages.d.ts +57 -1
  104. package/dist/session/persist-turn-messages.js +160 -5
  105. package/dist/sse/events/index.d.ts +13 -4
  106. package/dist/sse/events/index.js +9 -1
  107. package/dist/sse/events/rag-events.d.ts +63 -0
  108. package/dist/sse/events/rag-events.js +9 -0
  109. package/dist/sse/events/session-events.d.ts +38 -0
  110. package/dist/sse/events/session-events.js +4 -0
  111. package/dist/token/index.d.ts +2 -1
  112. package/dist/token/index.js +2 -1
  113. package/dist/token/record-task-token-usage.d.ts +25 -0
  114. package/dist/token/record-task-token-usage.js +44 -0
  115. package/dist/token/token-detail-mode.d.ts +4 -4
  116. package/dist/token/token-detail-mode.js +4 -4
  117. package/dist/token/token-detail.d.ts +8 -0
  118. package/dist/token/token-detail.js +22 -0
  119. package/dist/token/token-stats.service.d.ts +2 -2
  120. package/dist/token/token-stats.service.js +1 -1
  121. package/dist/tool/create-tool-executor.d.ts +6 -0
  122. package/dist/tool/create-tool-executor.js +4 -3
  123. package/dist/tool/harness-primitives.d.ts +9 -1
  124. package/dist/tool/harness-primitives.js +43 -12
  125. package/dist/tool/index.d.ts +2 -1
  126. package/dist/tool/index.js +2 -1
  127. package/dist/tool/tool-confirmation.js +8 -5
  128. package/dist/tool/tool-executor.interface.d.ts +27 -0
  129. package/dist/tool/tool-orchestrator.d.ts +3 -1
  130. package/dist/tool/tool-orchestrator.js +15 -6
  131. package/dist/tool/tool-retry.d.ts +15 -0
  132. package/dist/tool/tool-retry.js +69 -0
  133. package/package.json +2 -2
@@ -7,13 +7,26 @@
7
7
  * 3. 用返回的连接配置创建 HttpLLMProvider,发送请求
8
8
  *
9
9
  * 设计原则:
10
- * - Runtime 不存任何 LLM 凭证,全部从 Foundation 获取
10
+ * - 生产路径:Foundation `/models/select` 返回连接,本机 HTTP
11
+ * - select 失败或没有连接:走构造时注入的 fallback(dev 为 LocalLlmProvider / `resolveLocalLlmConnection`)
11
12
  * - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
12
- * - 连接配置由 Foundation 统一管理,Runtime 无感知
13
13
  */
14
- import { mergeModelCallParams } from '@gt-fe/eap-contracts';
14
+ import { AppError, ErrorCodes, mergeModelCallParams } from '@gt-fe/eap-contracts';
15
15
  import { EchoLLMProvider } from './echo-llm-provider.js';
16
16
  import { DEFAULT_LLM_HTTP_TIMEOUT_MS, HttpLLMProvider } from './http-llm-provider.js';
17
+ export const MODELS_INVOKE_STREAM_MESSAGE = 'EAP_RUNTIME_MODELS_INVOKE 实验口只支持同步 invoke(POST /models/invoke),不支持 stream。关掉该开关,或改走 Foundation /models/select + 本机 HTTP。';
18
+ export function isFoundationModelsInvokeEnabled(env = process.env) {
19
+ const raw = env.EAP_RUNTIME_MODELS_INVOKE;
20
+ return raw === '1' || raw === 'true';
21
+ }
22
+ /** `/models/invoke` 无真流式;开了该开关时 RoutingLLMProvider.stream 必须失败。 */
23
+ export function assertFoundationModelsInvokeAllowsStream(env = process.env) {
24
+ if (!isFoundationModelsInvokeEnabled(env))
25
+ return;
26
+ throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
27
+ exposeMessage: true,
28
+ });
29
+ }
17
30
  export class RoutingLLMProvider {
18
31
  foundationUrl;
19
32
  fallback;
@@ -29,6 +42,7 @@ export class RoutingLLMProvider {
29
42
  return provider.invoke(messages, this.withCallDefaults(config, selected));
30
43
  }
31
44
  async *stream(messages, config) {
45
+ assertFoundationModelsInvokeAllowsStream();
32
46
  const selected = await this.selectModel(config);
33
47
  const provider = this.createProviderForModel(selected);
34
48
  yield* provider.stream(messages, this.withCallDefaults(config, selected));
@@ -38,7 +52,7 @@ export class RoutingLLMProvider {
38
52
  return {
39
53
  ...config,
40
54
  ...merged,
41
- model: selected.name,
55
+ model: selected.name || config.model,
42
56
  maxContextTokens: selected.maxContextTokens ?? config.maxContextTokens,
43
57
  };
44
58
  }
@@ -70,10 +84,15 @@ export class RoutingLLMProvider {
70
84
  catch {
71
85
  // fallback to local
72
86
  }
73
- return { name: config.model ?? 'echo', provider: 'local', connection: null };
87
+ return { name: config.model ?? '', provider: 'local', connection: null };
88
+ }
89
+ useFoundationInvoke() {
90
+ return isFoundationModelsInvokeEnabled();
74
91
  }
75
92
  createProviderForModel(selected) {
76
- // 优先使用 Foundation 返回的连接配置
93
+ if (this.useFoundationInvoke()) {
94
+ return new FoundationModelsInvokeProvider(this.foundationUrl, selected.name, this.turnLog);
95
+ }
77
96
  if (selected.connection) {
78
97
  return new HttpLLMProvider({
79
98
  apiKey: selected.connection.apiKey ?? '',
@@ -83,16 +102,52 @@ export class RoutingLLMProvider {
83
102
  turnLog: this.turnLog,
84
103
  });
85
104
  }
86
- // Fallback: 兼容旧逻辑(直接读 env)—— 开发环境快速启动用
87
- if (process.env.OPENAI_API_KEY && selected.provider === 'openai') {
88
- return new HttpLLMProvider({
89
- apiKey: process.env.OPENAI_API_KEY,
90
- baseUrl: process.env.OPENAI_BASE_URL,
91
- defaultModel: selected.name,
92
- timeoutMs: DEFAULT_LLM_HTTP_TIMEOUT_MS,
93
- turnLog: this.turnLog,
94
- });
95
- }
105
+ // Foundation 未返回连接:与 LocalLlmProvider 同一把钥匙(由调用方注入 fallback)。
96
106
  return this.fallback;
97
107
  }
98
108
  }
109
+ /**
110
+ * Foundation `/models/invoke` 实验口。默认关。
111
+ * 只实现同步 invoke;stream 拒绝(无真流式、无鉴权头)。
112
+ * Foundation 未提供该接口时不要开 EAP_RUNTIME_MODELS_INVOKE。
113
+ */
114
+ class FoundationModelsInvokeProvider {
115
+ foundationUrl;
116
+ model;
117
+ turnLog;
118
+ constructor(foundationUrl, model, turnLog) {
119
+ this.foundationUrl = foundationUrl;
120
+ this.model = model;
121
+ this.turnLog = turnLog;
122
+ }
123
+ async invoke(messages, config) {
124
+ const res = await fetch(`${this.foundationUrl}/api/v1/models/invoke`, {
125
+ method: 'POST',
126
+ headers: { 'Content-Type': 'application/json' },
127
+ body: JSON.stringify({
128
+ model: config.model ?? this.model,
129
+ messages,
130
+ temperature: config.temperature,
131
+ maxTokens: config.maxTokens,
132
+ }),
133
+ });
134
+ if (!res.ok) {
135
+ throw new Error(`Foundation /models/invoke HTTP ${res.status}`);
136
+ }
137
+ const body = (await res.json());
138
+ const content = body.data?.content ?? body.content ?? '';
139
+ void this.turnLog;
140
+ return {
141
+ content,
142
+ inputTokens: body.data?.inputTokens ?? 0,
143
+ outputTokens: body.data?.outputTokens ?? 0,
144
+ model: body.data?.model ?? config.model ?? this.model,
145
+ };
146
+ }
147
+ async *stream(_messages, _config) {
148
+ assertFoundationModelsInvokeAllowsStream();
149
+ throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, MODELS_INVOKE_STREAM_MESSAGE, {
150
+ exposeMessage: true,
151
+ });
152
+ }
153
+ }
@@ -116,7 +116,7 @@ async function findDigestMatch(items, expectedRaw) {
116
116
  }
117
117
  return undefined;
118
118
  }
119
- async function warnIfDigestMismatch(expectedRaw, actualHex, label, details) {
119
+ async function warnIfDigestMismatch(expectedRaw, actualHex, label, details, fail = false) {
120
120
  const expected = normalizeDigest(expectedRaw);
121
121
  if (!expected)
122
122
  return;
@@ -129,6 +129,13 @@ async function warnIfDigestMismatch(expectedRaw, actualHex, label, details) {
129
129
  actual,
130
130
  ...details,
131
131
  });
132
+ if (fail) {
133
+ throw preloadPackageCorrupt(`预装校验和不匹配: ${label}`, {
134
+ expected,
135
+ actual,
136
+ ...details,
137
+ });
138
+ }
132
139
  }
133
140
  async function pickLatestVersionDir(parent) {
134
141
  if (!(await pathExists(parent)))
@@ -373,6 +380,13 @@ export class FileSystemDeploymentSnapshot {
373
380
  actual: fileDigest,
374
381
  path: pkgFile,
375
382
  });
383
+ if (this.flags.requireChecksum || isRequireChecksum()) {
384
+ throw preloadPackageCorrupt('snapshot.sha256 与 .package.sha256 不一致', {
385
+ expected: expectedPkg,
386
+ actual: fileDigest,
387
+ path: pkgFile,
388
+ });
389
+ }
376
390
  }
377
391
  }
378
392
  }
@@ -394,7 +408,7 @@ export class FileSystemDeploymentSnapshot {
394
408
  throw preloadPackageCorrupt(`artifacts 指向的文件不存在: ${rel}`, { path: full });
395
409
  }
396
410
  const actual = await sha256OfFile(full);
397
- await warnIfDigestMismatch(expected, actual, `artifact ${rel}`, { path: full });
411
+ await warnIfDigestMismatch(expected, actual, `artifact ${rel}`, { path: full }, this.flags.requireChecksum || isRequireChecksum());
398
412
  }
399
413
  }
400
414
  async resolveAgentDir(agentCode, version) {
@@ -499,7 +513,7 @@ export class FileSystemDeploymentSnapshot {
499
513
  if (ref.sha256) {
500
514
  const matched = await findDigestMatch(existingManifests, ref.sha256);
501
515
  if (!matched) {
502
- await warnIfDigestMismatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path });
516
+ await warnIfDigestMismatch(ref.sha256, await sha256OfFile(existingManifests[0].path), `tool ${ref.name}`, { path: existingManifests[0].path }, this.flags.requireChecksum || isRequireChecksum());
503
517
  }
504
518
  chosen = matched ?? existingManifests[0];
505
519
  }
@@ -576,7 +590,7 @@ export class FileSystemDeploymentSnapshot {
576
590
  !isAuthorOverlay &&
577
591
  manifestPath.endsWith('.json')) {
578
592
  const actual = await sha256OfFile(manifestPath);
579
- await warnIfDigestMismatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
593
+ await warnIfDigestMismatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath }, this.flags.requireChecksum || isRequireChecksum());
580
594
  }
581
595
  const skillId = String(ref.skillId);
582
596
  return {
@@ -108,6 +108,8 @@ export declare class GraphLoader {
108
108
  definitionCacheSize(): number;
109
109
  private cloneCached;
110
110
  loadGraph(agentCode: string, options?: LoadAgentOptions): Promise<GraphDSL>;
111
+ /** Agent `models.default`;无清单或未声明则 undefined。 */
112
+ resolveDefaultModel(agentCode: string, version?: string): Promise<string | undefined>;
111
113
  private loadGraphFromSource;
112
114
  /**
113
115
  * Agent 基线系统提示词(manifest.prompts.system)。
@@ -144,6 +144,26 @@ const DEFAULT_GRAPH_DSL = {
144
144
  finishPoints: ['__end__'],
145
145
  config: { timeout: 60000 },
146
146
  };
147
+ /** 源图有非空字符串则保留;缺省或空数组为 `['__end__']`。预装 graph.json 与清单内嵌图同一收口。 */
148
+ function finishPointsFromGraphDocument(graph) {
149
+ const raw = graph.finishPoints;
150
+ if (!Array.isArray(raw))
151
+ return ['__end__'];
152
+ const ids = raw
153
+ .filter((id) => typeof id === 'string')
154
+ .map((id) => id.trim())
155
+ .filter((id) => id.length > 0);
156
+ return ids.length > 0 ? ids : ['__end__'];
157
+ }
158
+ const DEFAULT_LOADED_GRAPH_CONFIG = { timeout: 60000 };
159
+ /** 默认 timeout 60000;源图 `config` 覆盖。缺 `maxIterations` 时由引擎用 50。 */
160
+ function configFromGraphDocument(graph) {
161
+ const raw = graph.config;
162
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
163
+ return { ...DEFAULT_LOADED_GRAPH_CONFIG };
164
+ }
165
+ return { ...DEFAULT_LOADED_GRAPH_CONFIG, ...raw };
166
+ }
147
167
  function yamlGraphToDsl(agentCode, graph) {
148
168
  const nodes = graph.nodes ?? [];
149
169
  const edges = graph.edges ?? [];
@@ -155,8 +175,8 @@ function yamlGraphToDsl(agentCode, graph) {
155
175
  edges,
156
176
  conditionalEdges: graph.conditionalEdges,
157
177
  entryPoint: graph.entryPoint ?? nodes[0]?.id ?? 'llm',
158
- finishPoints: ['__end__'],
159
- config: { timeout: 60000 },
178
+ finishPoints: finishPointsFromGraphDocument(graph),
179
+ config: configFromGraphDocument(graph),
160
180
  };
161
181
  }
162
182
  function collectToolNames(agent, graphDsl) {
@@ -620,6 +640,23 @@ export class GraphLoader {
620
640
  }
621
641
  return this.cloneCached(graph);
622
642
  }
643
+ /** Agent `models.default`;无清单或未声明则 undefined。 */
644
+ async resolveDefaultModel(agentCode, version) {
645
+ const requested = this.resolveVersion({ agentVersion: version });
646
+ if (this.deploymentSnapshot) {
647
+ try {
648
+ const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, requested);
649
+ const model = resolveAgentDefaultModel((snap?.manifest ?? {}));
650
+ const trimmed = model?.trim();
651
+ if (trimmed && trimmed.toLowerCase() !== 'default')
652
+ return trimmed;
653
+ }
654
+ catch {
655
+ return undefined;
656
+ }
657
+ }
658
+ return undefined;
659
+ }
623
660
  async loadGraphFromSource(agentCode, version) {
624
661
  if (this.deploymentSnapshot) {
625
662
  const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, version);
@@ -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[](字段存在性)。加载期摘要不匹配为 warn |
8
+ * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]。开启时摘要不匹配抛 RUNTIME_PRELOAD_PACKAGE_CORRUPT;未开仍 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[](字段存在性)。加载期摘要不匹配为 warn |
8
+ * | EAP_PRELOAD_REQUIRE_CHECKSUM | 关 | snapshot 必须带 sha256 或 artifacts[]。开启时摘要不匹配抛 RUNTIME_PRELOAD_PACKAGE_CORRUPT;未开仍 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/ |
@@ -16,6 +16,7 @@ import type { SessionContextService } from '../session/session-context.service.j
16
16
  import type { MessageService } from '../session/message.service.js';
17
17
  import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
18
18
  import { type ILlmTurnLog, type LlmTurnIndexEntry, type LlmTurnLogKind, type LlmTurnRecord } from '../debug/llm-turn-log.js';
19
+ import { type SessionCompressionOptions } from '../session/apply-session-compression.js';
19
20
  import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
20
21
  import type { TaskService } from '../task/task.service.js';
21
22
  import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
@@ -46,6 +47,10 @@ export interface AgentRuntimeDeps {
46
47
  */
47
48
  traceCollector?: ITraceCollector;
48
49
  recordForkRelation?: RecordForkRelation;
50
+ /** 历史压缩;默认关。见 EAP_RUNTIME_COMPRESSION */
51
+ sessionCompression?: SessionCompressionOptions;
52
+ /** 长期记忆注入;默认关。见 EAP_RUNTIME_MEMORY */
53
+ sessionMemory?: import('../session/apply-session-memory.js').SessionMemoryOptions;
49
54
  }
50
55
  /** 一次对话详情:消息 + 每步 LLM 入参/响应。File / 内存 / Postgres 同一套结构。 */
51
56
  export interface ConversationDetail {
@@ -7,17 +7,20 @@
7
7
  * @see 04-runtime-core/design.md §1.5 / §1.7
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
- import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
10
+ import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
11
11
  import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
12
12
  import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
13
13
  import { assembleLoadedTools } from '../tool/harness-primitives.js';
14
14
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
15
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../session/persist-turn-messages.js';
15
+ import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
16
+ import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
17
+ import { applySessionMemory } from '../session/apply-session-memory.js';
18
+ import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
16
19
  import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
17
20
  import { resolveExecutionContext } from '../governance/governance-client.js';
18
21
  import { resolveAgentCode } from '../common/agent-code.js';
19
22
  import { resolveTraceId } from '../common/trace-id.js';
20
- import { collectStreamNodeTokenUsage, recordTaskTokenUsages, } from '../token/record-task-token-usage.js';
23
+ import { collectStreamNodeTokenUsage, recordTaskTokenUsages, summarizeTokenUsages, tokenUsagesFromLlmChannel, } from '../token/record-task-token-usage.js';
21
24
  import { resolveSkillInputFromInvokeInput } from '../registry/skill-io-guard.js';
22
25
  const HITL_TERMINAL_STATUSES = [
23
26
  'success',
@@ -208,6 +211,8 @@ export class AgentRuntime {
208
211
  executionContext: ctx,
209
212
  userId,
210
213
  traceId: resolveTraceId(params),
214
+ ragScope: params.ragScope,
215
+ ragSources: params.ragSources,
211
216
  }, userId);
212
217
  return response.data;
213
218
  }
@@ -274,7 +279,34 @@ export class AgentRuntime {
274
279
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
275
280
  : undefined;
276
281
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
277
- const history = messageRecordsToGraphMessages(sessionContext.messages);
282
+ let history = messageRecordsToGraphMessages(sessionContext.messages);
283
+ const compressionOptions = await withCompressionSummaryModel({
284
+ ...this.deps.sessionCompression,
285
+ turnLog: this.deps.sessionCompression?.turnLog ?? this.deps.turnLog,
286
+ }, () => this.deps.graphLoader
287
+ ? this.deps.graphLoader.resolveDefaultModel(agentCode, params.agentVersion)
288
+ : Promise.resolve(undefined));
289
+ for await (const step of iterSessionCompression({
290
+ history,
291
+ sessionId,
292
+ userId,
293
+ taskId: task.id,
294
+ options: compressionOptions,
295
+ })) {
296
+ if (step.kind === 'history') {
297
+ history = step.messages;
298
+ continue;
299
+ }
300
+ yield this.toSSE(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, task.id, { ...step.payload });
301
+ }
302
+ history = await applySessionMemory({
303
+ history,
304
+ userId,
305
+ agentCode,
306
+ query: typeof params.input === 'string' ? params.input : undefined,
307
+ options: this.deps.sessionMemory,
308
+ });
309
+ const tokenDetail = resolveTokenDetail(params.config?.tokenDetail);
278
310
  const messages = await buildTurnGraphMessages({
279
311
  history,
280
312
  input: params.input,
@@ -282,7 +314,7 @@ export class AgentRuntime {
282
314
  threadId: sessionId,
283
315
  });
284
316
  const startTime = Date.now();
285
- let assistantText = '';
317
+ const assistantBuckets = createStreamAssistantBuckets();
286
318
  let toolCallReasoningContent;
287
319
  let assistantReasoningContent;
288
320
  const pendingToolCalls = [];
@@ -300,6 +332,8 @@ export class AgentRuntime {
300
332
  sessionId,
301
333
  executionContext: ctx,
302
334
  forceToolServer: params.config?.forceToolServer,
335
+ ragScope: params.ragScope,
336
+ ragSources: params.ragSources,
303
337
  loadedTools,
304
338
  loadedSkills,
305
339
  skillDefinitions,
@@ -323,35 +357,20 @@ export class AgentRuntime {
323
357
  toolCallReasoningContent = p.reasoningContent;
324
358
  }
325
359
  }
326
- if (runtimeEvent.type === 'tool.result') {
327
- const p = runtimeEvent.payload;
328
- if (p.toolCallId && p.toolName) {
329
- toolResults.push({
330
- toolCallId: p.toolCallId,
331
- toolName: p.toolName,
332
- content: p.content,
333
- success: p.success !== false,
334
- });
335
- }
336
- }
360
+ // 终态 tool.result / tool.error 回填历史;willRetry 的 error 不进 transcript
361
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
337
362
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
338
363
  if (streamedReasoning)
339
364
  assistantReasoningContent = streamedReasoning;
340
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
341
- if (delta) {
342
- if (runtimeEvent.type === 'response.final') {
343
- if (delta.trim())
344
- assistantText = delta;
345
- }
346
- else {
347
- assistantText += delta;
348
- }
349
- }
365
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
350
366
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
351
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
367
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
368
+ yield this.runtimeEventToSSE(runtimeEvent, correlationId, task.id);
369
+ }
352
370
  }
353
371
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
354
372
  await this.deps.taskService.updateStatus(task.id, 'success');
373
+ const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
355
374
  await persistTurnTranscript(this.deps.messageService, {
356
375
  sessionId,
357
376
  taskId: task.id,
@@ -359,11 +378,18 @@ export class AgentRuntime {
359
378
  messages: buildTurnTranscriptFromStreamEvents({
360
379
  pendingToolCalls,
361
380
  toolResults,
362
- assistantText,
381
+ assistantTexts: streamAssistantTexts(assistantBuckets),
382
+ assistantText: visibleStreamAssistantText(assistantBuckets),
383
+ finishPoints: graph.finishPoints,
363
384
  toolCallReasoningContent,
364
385
  assistantReasoningContent,
365
386
  }),
387
+ assistantTokenCount: tokenSummary.total.output,
388
+ assistantDurationMs: Date.now() - startTime,
366
389
  });
390
+ if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
391
+ yield this.toSSE('stats.token_usage', correlationId, task.id, tokenSummary);
392
+ }
367
393
  yield this.toSSE('task.completed', correlationId, task.id, {
368
394
  taskId: task.id,
369
395
  duration: Date.now() - startTime,
@@ -390,6 +416,7 @@ export class AgentRuntime {
390
416
  return;
391
417
  }
392
418
  const message = err instanceof Error ? err.message : 'Unknown error';
419
+ await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
393
420
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
394
421
  yield this.toSSE('task.error', correlationId, task.id, {
395
422
  code: 'runtime.graph.execution_error',
@@ -409,7 +436,7 @@ export class AgentRuntime {
409
436
  let output = '';
410
437
  let waiting;
411
438
  let sawWait = false;
412
- let assistantText = '';
439
+ const assistantBuckets = createStreamAssistantBuckets();
413
440
  for await (const event of this.streamResumeHitl(taskId, response)) {
414
441
  if (event.type === 'hitl.waited') {
415
442
  sawWait = true;
@@ -421,21 +448,12 @@ export class AgentRuntime {
421
448
  };
422
449
  output = waiting.question;
423
450
  }
424
- const delta = extractStreamTextDelta(event.type, event.data);
425
- if (delta) {
426
- if (event.type === 'response.final') {
427
- if (delta.trim())
428
- assistantText = delta;
429
- }
430
- else {
431
- assistantText += delta;
432
- }
433
- }
451
+ applyStreamTextDelta(assistantBuckets, event.type, event.data);
434
452
  }
435
453
  if (sawWait) {
436
454
  return { taskId, status: 'waiting_hitl', output, hitl: waiting };
437
455
  }
438
- return { taskId, status: 'success', output: assistantText || output };
456
+ return { taskId, status: 'success', output: visibleStreamAssistantText(assistantBuckets) || output };
439
457
  }
440
458
  /**
441
459
  * HITL 恢复的流式入口:新开一条 SSE(第一条 stream 在 `hitl.waited` 后已结束)。
@@ -465,6 +483,7 @@ export class AgentRuntime {
465
483
  if (!claimed) {
466
484
  throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
467
485
  }
486
+ const tokenDetail = resolveTokenDetail(response.tokenDetail);
468
487
  yield this.toSSE('hitl.resumed', correlationId, taskId, {
469
488
  choice: response.choice,
470
489
  userId: response.userId,
@@ -516,7 +535,7 @@ export class AgentRuntime {
516
535
  traceCollector: this.deps.traceCollector,
517
536
  };
518
537
  const startTime = Date.now();
519
- let assistantText = '';
538
+ const assistantBuckets = createStreamAssistantBuckets();
520
539
  let toolCallReasoningContent;
521
540
  let assistantReasoningContent;
522
541
  const pendingToolCalls = [];
@@ -537,35 +556,19 @@ export class AgentRuntime {
537
556
  toolCallReasoningContent = p.reasoningContent;
538
557
  }
539
558
  }
540
- if (runtimeEvent.type === 'tool.result' || runtimeEvent.type === 'tool.error') {
541
- const p = runtimeEvent.payload;
542
- if (p.toolCallId && p.toolName) {
543
- toolResults.push({
544
- toolCallId: p.toolCallId,
545
- toolName: p.toolName,
546
- content: p.content ?? p.error,
547
- success: runtimeEvent.type === 'tool.result' && p.success !== false,
548
- });
549
- }
550
- }
559
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
551
560
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
552
561
  if (streamedReasoning)
553
562
  assistantReasoningContent = streamedReasoning;
554
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
555
- if (delta) {
556
- if (runtimeEvent.type === 'response.final') {
557
- if (delta.trim())
558
- assistantText = delta;
559
- }
560
- else {
561
- assistantText += delta;
562
- }
563
- }
563
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
564
564
  collectStreamNodeTokenUsage(runtimeEvent, nodeTokenUsages);
565
- yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
565
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
566
+ yield this.runtimeEventToSSE(runtimeEvent, correlationId, taskId);
567
+ }
566
568
  }
567
569
  await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
568
- await this.deps.taskService.updateStatus(taskId, 'success', assistantText || undefined);
570
+ await this.deps.taskService.updateStatus(taskId, 'success', visibleStreamAssistantText(assistantBuckets) || undefined);
571
+ const tokenSummary = summarizeTokenUsages(nodeTokenUsages);
569
572
  if (sessionId) {
570
573
  await persistTurnTranscript(this.deps.messageService, {
571
574
  sessionId,
@@ -574,12 +577,19 @@ export class AgentRuntime {
574
577
  messages: buildTurnTranscriptFromStreamEvents({
575
578
  pendingToolCalls,
576
579
  toolResults,
577
- assistantText,
580
+ assistantTexts: streamAssistantTexts(assistantBuckets),
581
+ assistantText: visibleStreamAssistantText(assistantBuckets),
582
+ finishPoints: graph.finishPoints,
578
583
  toolCallReasoningContent,
579
584
  assistantReasoningContent,
580
585
  }),
586
+ assistantTokenCount: tokenSummary.total.output,
587
+ assistantDurationMs: Date.now() - startTime,
581
588
  });
582
589
  }
590
+ if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
591
+ yield this.toSSE('stats.token_usage', correlationId, taskId, tokenSummary);
592
+ }
583
593
  yield this.toSSE('task.completed', correlationId, taskId, {
584
594
  taskId,
585
595
  duration: Date.now() - startTime,
@@ -609,6 +619,7 @@ export class AgentRuntime {
609
619
  }
610
620
  const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
611
621
  const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
622
+ await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
612
623
  await this.deps.taskService.updateStatus(taskId, 'failed', failMsg);
613
624
  yield this.toSSE('task.error', correlationId, taskId, {
614
625
  code: appErr.code,
@@ -671,8 +682,14 @@ export class AgentRuntime {
671
682
  ? forkedCheckpoint.state.agentSystemPrompt
672
683
  : undefined);
673
684
  try {
685
+ const forkState = { ...forkedCheckpoint.state };
686
+ delete forkState.toolSpanId;
687
+ delete forkState.hitlToolCallId;
674
688
  const result = await this.deps.graphEngine.invoke(graph, {
675
- ...forkedCheckpoint.state,
689
+ messages: Array.isArray(forkState.messages)
690
+ ? forkState.messages
691
+ : [],
692
+ ...forkState,
676
693
  agentVersion,
677
694
  agentDir,
678
695
  agentSystemPrompt,
@@ -729,6 +746,20 @@ export class AgentRuntime {
729
746
  }
730
747
  catch (err) {
731
748
  const message = err instanceof Error ? err.message : 'Unknown error';
749
+ const sessionId = typeof forkedCheckpoint.state.sessionId === 'string'
750
+ ? forkedCheckpoint.state.sessionId
751
+ : undefined;
752
+ const threadId = resolveCheckpointThreadId({
753
+ sessionId,
754
+ taskId: task.id,
755
+ });
756
+ const ck = await this.deps.checkpointSaver.load(threadId);
757
+ const baseline = tokenUsagesFromLlmChannel(forkedCheckpoint.state);
758
+ const latest = tokenUsagesFromLlmChannel(ck?.state ?? forkedCheckpoint.state);
759
+ const usages = latest.length >= baseline.length ? latest.slice(baseline.length) : latest;
760
+ await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
761
+ const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
762
+ const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
732
763
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
733
764
  return {
734
765
  newTraceId: traceId,
@@ -737,7 +768,11 @@ export class AgentRuntime {
737
768
  checkpointId,
738
769
  status: 'failed',
739
770
  output: message,
740
- tokenUsage: { input_tokens: 0, output_tokens: 0, total_tokens: 0 },
771
+ tokenUsage: {
772
+ input_tokens,
773
+ output_tokens,
774
+ total_tokens: input_tokens + output_tokens,
775
+ },
741
776
  };
742
777
  }
743
778
  }
@@ -758,16 +793,18 @@ export class AgentRuntime {
758
793
  const sessionId = typeof task.sessionId === 'string' && task.sessionId.trim()
759
794
  ? task.sessionId.trim()
760
795
  : undefined;
761
- const threadId = waiting?.threadId ??
796
+ const hinted = waiting?.threadId ??
762
797
  (typeof response.threadId === 'string' && response.threadId.trim()
763
798
  ? response.threadId.trim()
764
799
  : undefined) ??
765
800
  extractHitlThreadId(task.output) ??
766
801
  sessionId;
767
- if (!threadId) {
802
+ if (!hinted) {
768
803
  throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} 缺少 sessionId/hitlThreadId,无法冷恢复 HITL Checkpoint`, { exposeMessage: true });
769
804
  }
770
- const checkpoint = await this.deps.checkpointSaver.load(threadId);
805
+ const resolved = await loadHitlCheckpoint(this.deps.checkpointSaver, hinted);
806
+ const threadId = resolved.threadId;
807
+ const checkpoint = resolved.checkpoint;
771
808
  const persisted = readPersistedHitlWaiting(checkpoint?.state);
772
809
  const behavior = persisted?.config.timeoutBehavior ?? 'error';
773
810
  if (persisted &&