@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
package/README.md CHANGED
@@ -8,7 +8,8 @@ Enterprise Agent Platform **执行引擎**(LangGraph.js):Agent Graph 编
8
8
 
9
9
  | 日期 | 摘要 |
10
10
  |------|------|
11
- | 2026-08-29 | 待办收口:[`docs/TODO.md`](./docs/TODO.md)。压缩主路径未接;窗口展示不在 Runtime;Gateway 须 `tools/list` |
11
+ | 2026-09-08 | [`docs/TODO.md`](./docs/TODO.md) 改为主路径状态(已落地 / 不在本包),不是排期 |
12
+ | 2026-08-29 | 待办收口:[`docs/TODO.md`](./docs/TODO.md)。此后压缩已挂主路径(默认关);窗口展示不在 Runtime;Gateway 须 `tools/list` |
12
13
  | 2026-08-24 | 0.0.10:官方 Skill / Harness、`ForkParams`、对话详情、存储两档。依赖 `@eap/contracts@0.0.9`。详见 [CHANGELOG.md](./CHANGELOG.md) |
13
14
  | 2026-08-21 | 0.0.9:Gateway `/mcp` JSON-RPC 补 `Accept`;`tools/call` 透传工具版本。详见 [CHANGELOG.md](./CHANGELOG.md) |
14
15
  | 2026-08-19 | 0.0.8:Skill 子图 SSE 闭环;`ExecuteParams.traceId`;`tool.called` 改为 Tool 节点开始执行。依赖 `@eap/contracts@0.0.8`。详见 [CHANGELOG.md](./CHANGELOG.md) |
@@ -29,7 +30,7 @@ Enterprise Agent Platform **执行引擎**(LangGraph.js):Agent Graph 编
29
30
  | 环境变量总表 | [`docs/governance/governance-env-config.md`](../docs/governance/governance-env-config.md) |
30
31
  | 预装联调契约 | [`docs/contracts/preload-hosting-runtime-contract.md`](../docs/contracts/preload-hosting-runtime-contract.md) |
31
32
  | 预装加载与请求流转 | [`docs/contracts/runtime-preinstall-load-flow.md`](../docs/contracts/runtime-preinstall-load-flow.md) |
32
- | Runtime 待办 | [`docs/TODO.md`](./docs/TODO.md) | 压缩主路径、窗口展示、Gateway `tools/list` |
33
+ | Runtime 主路径状态 | [`docs/TODO.md`](./docs/TODO.md) |
33
34
  | 技能 FS 待办 | [`docs/TODO-skill-fs.md`](./docs/TODO-skill-fs.md) |
34
35
  | 本地 Agent 闭环 | [`docs/workflows/local-agent-dev-loop.md`](../docs/workflows/local-agent-dev-loop.md) |
35
36
 
@@ -163,7 +164,7 @@ DSL / 契约细节见 `.kiro/specs/04-runtime-core/design.md` §1.4 与 `src/gra
163
164
  |------|------|------|
164
165
  | `EAP_PREINSTALL_ROOT` | — | 预装根;也可用 `GraphLoader({ preinstallRoot })` |
165
166
  | `EAP_PRELOAD_REQUIRE_READY` | 关 | 缺 `.ready` → `runtime.preload.missing` |
166
- | `EAP_PRELOAD_REQUIRE_CHECKSUM` | 关 | snapshot 须含 sha256 或 artifacts。加载期比对文件摘要,不匹配则 warn、不中断 |
167
+ | `EAP_PRELOAD_REQUIRE_CHECKSUM` | 关 | snapshot 须含 sha256 或 artifacts。开启时摘要不匹配抛 `RUNTIME_PRELOAD_PACKAGE_CORRUPT`;未开仍 warn、不中断 |
167
168
  | `EAP_PRELOAD_USER_SKILL_OVERRIDE` | 关 | `users` 优先于 `shared` |
168
169
  | `EAP_PRELOAD_VALIDATE_SKILL_IO` | 关 | 校验 Skill `inputDef.required`(`state.skillInput`) |
169
170
  | `EAP_PRELOAD_DEFINITION_CACHE` | 有预装时开 | 定义缓存;`0`/`false` 关闭,`1`/`true` 强制开 |
@@ -25,6 +25,8 @@ import type { InvokeRequest, InvokeResponse } from '../routes/agent.routes.js';
25
25
  import { type GraphLoader } from '../../registry/graph-loader.js';
26
26
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
27
27
  import type { ITraceCollector } from '../../contracts/trace-collector.js';
28
+ import { type SessionCompressionOptions } from '../../session/apply-session-compression.js';
29
+ import { type SessionMemoryOptions } from '../../session/apply-session-memory.js';
28
30
  /**
29
31
  * InvokeOrchestrator 构造器依赖
30
32
  */
@@ -42,6 +44,8 @@ export interface InvokeOrchestratorDeps {
42
44
  /** 用于判断 session Checkpoint 是否已有历史,避免双重 append */
43
45
  checkpointSaver?: ICheckpointSaver;
44
46
  traceCollector?: ITraceCollector;
47
+ sessionCompression?: SessionCompressionOptions;
48
+ sessionMemory?: SessionMemoryOptions;
45
49
  }
46
50
  /**
47
51
  * InvokeOrchestrator — 同步调用全流程编排
@@ -60,6 +64,8 @@ export declare class InvokeOrchestrator {
60
64
  private readonly graphLoader?;
61
65
  private readonly checkpointSaver?;
62
66
  private readonly traceCollector?;
67
+ private readonly sessionCompression?;
68
+ private readonly sessionMemory?;
63
69
  constructor(deps: InvokeOrchestratorDeps);
64
70
  /**
65
71
  * 执行同步调用编排
@@ -72,6 +78,7 @@ export declare class InvokeOrchestrator {
72
78
  private buildResponse;
73
79
  private buildErrorResponse;
74
80
  private isTimeoutError;
81
+ private persistInvokeUsages;
75
82
  }
76
83
  /** @deprecated 请直接从 `graph/hitl/hitl-handler` 导入;此处保留 re-export 兼容旧测试引用 */
77
84
  export { unwrapHitlPaused } from '../../graph/hitl/hitl-handler.js';
@@ -15,10 +15,10 @@
15
15
  * @see requirements.md Requirement 11 AC1-AC2
16
16
  */
17
17
  import { randomUUID } from 'node:crypto';
18
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../../session/persist-turn-messages.js';
18
+ import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, sessionVisibleGraphMessages, } from '../../session/persist-turn-messages.js';
19
19
  import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
20
20
  import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
21
- import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
21
+ import { recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../../token/record-task-token-usage.js';
22
22
  import { resolveExecutionContext } from '../../governance/governance-client.js';
23
23
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
24
24
  import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
@@ -28,6 +28,8 @@ import { assembleLoadedTools } from '../../tool/harness-primitives.js';
28
28
  import { resolveAgentCode } from '../../common/agent-code.js';
29
29
  import { unwrapHitlPaused } from '../../graph/hitl/hitl-handler.js';
30
30
  import { resolveTraceId } from '../../common/trace-id.js';
31
+ import { applySessionCompression, withCompressionSummaryModel, } from '../../session/apply-session-compression.js';
32
+ import { applySessionMemory, } from '../../session/apply-session-memory.js';
31
33
  // ─────────────────────────────────────────────
32
34
  // Default Graph DSL fallback
33
35
  // ─────────────────────────────────────────────
@@ -64,6 +66,8 @@ export class InvokeOrchestrator {
64
66
  graphLoader;
65
67
  checkpointSaver;
66
68
  traceCollector;
69
+ sessionCompression;
70
+ sessionMemory;
67
71
  constructor(deps) {
68
72
  this.sessionContextService = deps.sessionContextService;
69
73
  this.taskService = deps.taskService;
@@ -75,6 +79,8 @@ export class InvokeOrchestrator {
75
79
  this.graphLoader = deps.graphLoader;
76
80
  this.checkpointSaver = deps.checkpointSaver;
77
81
  this.traceCollector = deps.traceCollector;
82
+ this.sessionCompression = deps.sessionCompression;
83
+ this.sessionMemory = deps.sessionMemory;
78
84
  }
79
85
  /**
80
86
  * 执行同步调用编排
@@ -159,6 +165,7 @@ export class InvokeOrchestrator {
159
165
  });
160
166
  // ─── Step 5: 构建/执行 Graph ───
161
167
  let graphResult;
168
+ let finishPoints;
162
169
  try {
163
170
  // 构建图 — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态
164
171
  const loadOpts = { agentVersion: req.agentVersion };
@@ -166,6 +173,7 @@ export class InvokeOrchestrator {
166
173
  ? await this.graphLoader.loadGraph(agentCode, loadOpts)
167
174
  : DEFAULT_GRAPH_DSL;
168
175
  const graph = await this.graphEngine.buildGraph(graphDsl);
176
+ finishPoints = graph.finishPoints;
169
177
  const loadedToolsBase = this.graphLoader
170
178
  ? toLoadedToolMap(await this.graphLoader.loadToolDefinitions(agentCode, loadOpts))
171
179
  : undefined;
@@ -185,7 +193,21 @@ export class InvokeOrchestrator {
185
193
  : undefined;
186
194
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
187
195
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
188
- const history = messageRecordsToGraphMessages(sessionContext.messages);
196
+ const history = await applySessionMemory({
197
+ history: await applySessionCompression({
198
+ history: messageRecordsToGraphMessages(sessionContext.messages),
199
+ sessionId,
200
+ userId: effectiveUserId,
201
+ taskId: task.id,
202
+ options: await withCompressionSummaryModel(this.sessionCompression, () => this.graphLoader
203
+ ? this.graphLoader.resolveDefaultModel(agentCode, req.agentVersion)
204
+ : Promise.resolve(undefined)),
205
+ }),
206
+ userId: effectiveUserId,
207
+ agentCode,
208
+ query: typeof req.input === 'string' ? req.input : undefined,
209
+ options: this.sessionMemory,
210
+ });
189
211
  const messages = await buildTurnGraphMessages({
190
212
  history,
191
213
  input: req.input,
@@ -205,6 +227,8 @@ export class InvokeOrchestrator {
205
227
  sessionId,
206
228
  executionContext: ctx,
207
229
  forceToolServer: req.config?.forceToolServer,
230
+ ragScope: req.ragScope,
231
+ ragSources: req.ragSources,
208
232
  loadedTools,
209
233
  loadedSkills,
210
234
  skillDefinitions,
@@ -217,6 +241,8 @@ export class InvokeOrchestrator {
217
241
  });
218
242
  }
219
243
  catch (error) {
244
+ const usages = await this.persistInvokeUsages(task.id, sessionId);
245
+ const tokenUsage = tokenUsageTotals(usages);
220
246
  const hitlPaused = unwrapHitlPaused(error);
221
247
  if (hitlPaused) {
222
248
  await this.taskService.updateStatus(task.id, 'waiting_hitl', buildHitlWaitingTaskOutput({
@@ -236,7 +262,7 @@ export class InvokeOrchestrator {
236
262
  taskId: task.id,
237
263
  sessionId,
238
264
  output: hitlPaused.question,
239
- tokenUsage: { input: 0, output: 0, total: 0 },
265
+ tokenUsage,
240
266
  duration: Date.now() - startTime,
241
267
  status: 'waiting_hitl',
242
268
  hitl: {
@@ -253,7 +279,7 @@ export class InvokeOrchestrator {
253
279
  taskId: task.id,
254
280
  sessionId,
255
281
  output: error.message,
256
- tokenUsage: { input: 0, output: 0, total: 0 },
282
+ tokenUsage,
257
283
  duration: Date.now() - startTime,
258
284
  status: 'token_exhausted',
259
285
  });
@@ -265,7 +291,7 @@ export class InvokeOrchestrator {
265
291
  taskId: task.id,
266
292
  sessionId,
267
293
  output: 'Task execution timed out',
268
- tokenUsage: { input: 0, output: 0, total: 0 },
294
+ tokenUsage,
269
295
  duration: Date.now() - startTime,
270
296
  status: 'timeout',
271
297
  });
@@ -277,7 +303,7 @@ export class InvokeOrchestrator {
277
303
  taskId: task.id,
278
304
  sessionId,
279
305
  output: errorMessage,
280
- tokenUsage: { input: 0, output: 0, total: 0 },
306
+ tokenUsage,
281
307
  duration: Date.now() - startTime,
282
308
  status: 'failed',
283
309
  });
@@ -326,11 +352,13 @@ export class InvokeOrchestrator {
326
352
  // ─── Step 7: 完成 Task (running → success) ───
327
353
  await this.taskService.updateStatus(task.id, 'success', graphResult.output);
328
354
  // ─── Step 7b: 持久化本轮 transcript(统一顺序:assistant tool_calls → tool → assistant 文本)───
329
- const initialMessagesCount = sessionContext.messages.length + 1; // history + 本轮 user
355
+ // 按 Session 已有行数切 Graph 增量。多段正文收成一条后 Session 比 Graph 短,切点可能提前;finishPoints 只含一个实际节点 id 时靠最后一条纯文本仍落到 merge。
356
+ const initialMessagesCount = sessionContext.messages.length + 1;
330
357
  const allMessages = Array.isArray(graphResult.finalState?.messages)
331
358
  ? graphResult.finalState.messages
332
359
  : [];
333
- const newMessages = allMessages.slice(initialMessagesCount);
360
+ const newMessages = sessionVisibleGraphMessages(allMessages.slice(initialMessagesCount), finishPoints);
361
+ const duration = Date.now() - startTime;
334
362
  await persistTurnTranscript(this.messageService, {
335
363
  sessionId,
336
364
  taskId: task.id,
@@ -338,9 +366,9 @@ export class InvokeOrchestrator {
338
366
  messages: newMessages,
339
367
  fallbackAssistantContent: graphResult.output ?? '',
340
368
  assistantTokenCount: tokenUsage.output,
369
+ assistantDurationMs: duration,
341
370
  });
342
371
  // ─── Step 8: 返回 InvokeResponse ───
343
- const duration = Date.now() - startTime;
344
372
  // 从本轮 Graph 执行结果中提取 tool 事件明细(供 CLI / UI 渲染 ReAct 循环)
345
373
  const toolEvents = extractToolEvents(newMessages);
346
374
  return this.buildResponse(traceId, {
@@ -369,6 +397,20 @@ export class InvokeOrchestrator {
369
397
  }
370
398
  return false;
371
399
  }
400
+ async persistInvokeUsages(taskId, sessionId) {
401
+ if (!this.checkpointSaver)
402
+ return [];
403
+ const threadId = resolveCheckpointThreadId({ sessionId, taskId });
404
+ const ck = await this.checkpointSaver.load(threadId);
405
+ const usages = tokenUsagesFromLlmChannel(ck?.state);
406
+ await recordTaskTokenUsages(this.taskService, taskId, usages);
407
+ return usages;
408
+ }
409
+ }
410
+ function tokenUsageTotals(usages) {
411
+ const input = usages.reduce((sum, row) => sum + row.inputTokens, 0);
412
+ const output = usages.reduce((sum, row) => sum + row.outputTokens, 0);
413
+ return { input, output, total: input + output };
372
414
  }
373
415
  // ─────────────────────────────────────────────
374
416
  // Module-level Helpers
@@ -27,6 +27,8 @@ import type { IStateGraphEngine } from '../../graph/contracts/state-graph.contra
27
27
  import type { TokenStatsService } from '../../token/token-stats.service.js';
28
28
  import type { TokenBudgetGuard } from '../../token/token-budget-guard.js';
29
29
  import type { SessionBudgetChecker } from '../../token/session-budget-checker.js';
30
+ import { type SessionCompressionOptions } from '../../session/apply-session-compression.js';
31
+ import { type SessionMemoryOptions } from '../../session/apply-session-memory.js';
30
32
  import type { StreamRequest } from '../routes/agent.routes.js';
31
33
  import { type GraphLoader } from '../../registry/graph-loader.js';
32
34
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
@@ -48,6 +50,8 @@ export interface StreamOrchestratorDeps {
48
50
  checkpointSaver?: ICheckpointSaver;
49
51
  /** Hosting 注入的 Trace 采集器;与 traceId 同时存在时创建 Skill/Tool span */
50
52
  traceCollector?: ITraceCollector;
53
+ sessionCompression?: SessionCompressionOptions;
54
+ sessionMemory?: SessionMemoryOptions;
51
55
  }
52
56
  /**
53
57
  * StreamOrchestrator — 流式调用全流程编排
@@ -66,6 +70,8 @@ export declare class StreamOrchestrator {
66
70
  private readonly graphLoader?;
67
71
  private readonly checkpointSaver?;
68
72
  private readonly traceCollector?;
73
+ private readonly sessionCompression?;
74
+ private readonly sessionMemory?;
69
75
  constructor(deps: StreamOrchestratorDeps);
70
76
  /**
71
77
  * 执行流式调用编排
@@ -19,10 +19,13 @@
19
19
  * @see design.md §1.5 /stream 内部执行逻辑
20
20
  * @see requirements.md Requirement 11 AC2
21
21
  */
22
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../../session/persist-turn-messages.js';
22
+ import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, appendTerminalToolResult, } from '../../session/persist-turn-messages.js';
23
23
  import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
24
24
  import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
25
25
  import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
26
+ import { iterSessionCompression, withCompressionSummaryModel, } from '../../session/apply-session-compression.js';
27
+ import { applySessionMemory, } from '../../session/apply-session-memory.js';
28
+ import { resolveTokenDetail, shouldEmitTokenSse } from '../../token/token-detail.js';
26
29
  import { SSEEmitter } from '../../sse/sse-emitter.js';
27
30
  import { resolveExecutionContext } from '../../governance/governance-client.js';
28
31
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
@@ -72,6 +75,8 @@ export class StreamOrchestrator {
72
75
  graphLoader;
73
76
  checkpointSaver;
74
77
  traceCollector;
78
+ sessionCompression;
79
+ sessionMemory;
75
80
  constructor(deps) {
76
81
  this.sessionContextService = deps.sessionContextService;
77
82
  this.taskService = deps.taskService;
@@ -83,6 +88,8 @@ export class StreamOrchestrator {
83
88
  this.graphLoader = deps.graphLoader;
84
89
  this.checkpointSaver = deps.checkpointSaver;
85
90
  this.traceCollector = deps.traceCollector;
91
+ this.sessionCompression = deps.sessionCompression;
92
+ this.sessionMemory = deps.sessionMemory;
86
93
  }
87
94
  /**
88
95
  * 执行流式调用编排
@@ -207,7 +214,31 @@ export class StreamOrchestrator {
207
214
  : undefined;
208
215
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
209
216
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
210
- const history = messageRecordsToGraphMessages(sessionContext.messages);
217
+ let history = messageRecordsToGraphMessages(sessionContext.messages);
218
+ const compressionOptions = await withCompressionSummaryModel(this.sessionCompression, () => this.graphLoader
219
+ ? this.graphLoader.resolveDefaultModel(agentCode, req.agentVersion)
220
+ : Promise.resolve(undefined));
221
+ for await (const step of iterSessionCompression({
222
+ history,
223
+ sessionId,
224
+ userId,
225
+ taskId,
226
+ options: compressionOptions,
227
+ })) {
228
+ if (step.kind === 'history') {
229
+ history = step.messages;
230
+ continue;
231
+ }
232
+ emitter.emit(this.buildSSEEvent(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, taskId, { ...step.payload }));
233
+ }
234
+ history = await applySessionMemory({
235
+ history,
236
+ userId,
237
+ agentCode,
238
+ query: typeof req.input === 'string' ? req.input : undefined,
239
+ options: this.sessionMemory,
240
+ });
241
+ const tokenDetail = resolveTokenDetail(req.config?.tokenDetail);
211
242
  const messages = await buildTurnGraphMessages({
212
243
  history,
213
244
  input: req.input,
@@ -215,7 +246,7 @@ export class StreamOrchestrator {
215
246
  threadId: sessionId,
216
247
  });
217
248
  const skillInput = resolveSkillInputFromInvokeInput(req.input);
218
- let assistantText = '';
249
+ const assistantBuckets = createStreamAssistantBuckets();
219
250
  let toolCallReasoningContent;
220
251
  let assistantReasoningContent;
221
252
  const pendingToolCalls = [];
@@ -233,6 +264,8 @@ export class StreamOrchestrator {
233
264
  sessionId,
234
265
  executionContext,
235
266
  forceToolServer: req.config?.forceToolServer,
267
+ ragScope: req.ragScope,
268
+ ragSources: req.ragSources,
236
269
  loadedTools,
237
270
  loadedSkills,
238
271
  skillDefinitions,
@@ -272,33 +305,14 @@ export class StreamOrchestrator {
272
305
  toolCallReasoningContent = p.reasoningContent;
273
306
  }
274
307
  }
275
- if (runtimeEvent.type === 'tool.result') {
276
- const p = runtimeEvent.payload;
277
- if (p.toolCallId && p.toolName) {
278
- toolResults.push({
279
- toolCallId: p.toolCallId,
280
- toolName: p.toolName,
281
- content: p.content,
282
- success: p.success !== false,
283
- });
284
- }
285
- }
308
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
286
309
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
287
310
  if (streamedReasoning)
288
311
  assistantReasoningContent = streamedReasoning;
289
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
290
- if (delta) {
291
- if (runtimeEvent.type === 'response.final') {
292
- // final 若带完整文本则覆盖累加,避免与 chunk 重复拼接
293
- if (delta.trim())
294
- assistantText = delta;
295
- }
296
- else {
297
- assistantText += delta;
298
- }
312
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
313
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
314
+ emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
299
315
  }
300
- // 将 RuntimeEvent 转为 SSEEvent 并推送
301
- emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
302
316
  }
303
317
  }
304
318
  catch (error) {
@@ -325,12 +339,14 @@ export class StreamOrchestrator {
325
339
  }
326
340
  // ─── 错误处理:TokenBudgetExhausted ───
327
341
  if (error instanceof TokenBudgetExhaustedError) {
342
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
328
343
  await this.taskService.updateStatus(taskId, 'token_exhausted', error.message);
329
344
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
330
345
  return;
331
346
  }
332
347
  // ─── 错误处理:Timeout ───
333
348
  if (this.isTimeoutError(error)) {
349
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
334
350
  await this.taskService.updateStatus(taskId, 'timeout');
335
351
  emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
336
352
  code: ErrorCodes.TASK_TIMEOUT,
@@ -342,6 +358,7 @@ export class StreamOrchestrator {
342
358
  }
343
359
  // ─── 错误处理:通用错误 ───
344
360
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
361
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
345
362
  await this.taskService.updateStatus(taskId, 'failed', errorMessage);
346
363
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
347
364
  return;
@@ -350,21 +367,22 @@ export class StreamOrchestrator {
350
367
  // Emit: response.final(带完整 assistant 文本,供客户端兜底)
351
368
  const tokenStats = this.tokenStatsService.getTaskStats(taskId);
352
369
  emitter.emit(this.buildSSEEvent('response.final', correlationId, taskId, {
353
- content: assistantText,
370
+ content: visibleStreamAssistantText(assistantBuckets),
354
371
  format: 'text',
355
372
  }));
356
- // Emit: stats.token_usage
357
373
  const totalTokens = (tokenStats.total.input ?? 0) + (tokenStats.total.output ?? 0);
358
- emitter.emit(this.buildSSEEvent('stats.token_usage', correlationId, taskId, {
359
- total: {
360
- input: tokenStats.total.input,
361
- output: tokenStats.total.output,
362
- total: tokenStats.total.total ?? totalTokens,
363
- },
364
- byNode: tokenStats.byNode,
365
- byModel: tokenStats.byModel,
366
- costEstimate: tokenStats.costEstimate,
367
- }));
374
+ if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
375
+ emitter.emit(this.buildSSEEvent('stats.token_usage', correlationId, taskId, {
376
+ total: {
377
+ input: tokenStats.total.input,
378
+ output: tokenStats.total.output,
379
+ total: tokenStats.total.total ?? totalTokens,
380
+ },
381
+ byNode: tokenStats.byNode,
382
+ byModel: tokenStats.byModel,
383
+ costEstimate: tokenStats.costEstimate,
384
+ }));
385
+ }
368
386
  // ─── Step 8: 记录 Token + 完成 Task + 关闭 SSE ───
369
387
  const duration = Date.now() - startTime;
370
388
  // Emit: task.completed
@@ -383,11 +401,14 @@ export class StreamOrchestrator {
383
401
  messages: buildTurnTranscriptFromStreamEvents({
384
402
  pendingToolCalls,
385
403
  toolResults,
386
- assistantText,
404
+ assistantTexts: streamAssistantTexts(assistantBuckets),
405
+ assistantText: visibleStreamAssistantText(assistantBuckets),
406
+ finishPoints: graph.finishPoints,
387
407
  toolCallReasoningContent,
388
408
  assistantReasoningContent,
389
409
  }),
390
410
  assistantTokenCount: tokenStats.total.output ?? 0,
411
+ assistantDurationMs: duration,
391
412
  });
392
413
  // 关闭 SSE 连接
393
414
  emitter.close();
@@ -11,10 +11,17 @@ import type { IStateGraphEngine } from '../../graph/contracts/executable-graph.j
11
11
  import type { SSEEmitter } from '../../sse/sse-emitter.js';
12
12
  export type { InvokeConfig, InvokeResponseData } from '@gt-fe/eap-contracts';
13
13
  /**
14
- * dev HTTP 层 Invoke 请求(扩展 userId,Hosting 从 auth 注入)
14
+ * dev HTTP 层 Invoke 请求(扩展 userId,Hosting 从 auth 注入;ragScope 为请求级
15
+ * 检索范围——前端圈定已向量化文件/目录,仅 rag 节点本地检索源生效)
15
16
  */
16
17
  export type InvokeRequest = ContractInvokeRequest & {
17
18
  userId?: string;
19
+ ragScope?: {
20
+ fileIds?: string[];
21
+ dirPaths?: string[];
22
+ };
23
+ /** 会话级数据源勾选(数据源 id 列表;缺省 = 智能体预定义目录全部启用) */
24
+ ragSources?: string[];
18
25
  };
19
26
  export type InvokeResponse = ContractInvokeResponse;
20
27
  /** 流式调用请求 — 与 InvokeRequest 相同 */
@@ -1,9 +1,12 @@
1
1
  /**
2
2
  * FileCheckpointSaver — 本地 JSON 文件存储
3
3
  * 适用场景:本地开发、单机部署
4
+ * 目录名对 threadId 做 encodeURIComponent:子图槽 `sessionId#父节点id` 不会变成带 `#` 的目录。
4
5
  */
5
6
  import type { ICheckpointSaver } from './contracts/checkpoint-saver.contract.js';
6
7
  import type { CheckpointData } from '../graph/contracts/executable-graph.js';
8
+ /** 存储键可含 `#`(子图槽);目录名百分号编码,避免 URL/Markdown 把 `#` 当片段。 */
9
+ export declare function checkpointFileThreadDir(baseDir: string, threadId: string): string;
7
10
  export declare class FileCheckpointSaver implements ICheckpointSaver {
8
11
  private readonly baseDir;
9
12
  constructor(baseDir: string);
@@ -11,6 +14,7 @@ export declare class FileCheckpointSaver implements ICheckpointSaver {
11
14
  load(threadId: string): Promise<CheckpointData | null>;
12
15
  loadByCheckpointId(checkpointId: string, threadId?: string): Promise<CheckpointData | null>;
13
16
  list(threadId: string): Promise<CheckpointData[]>;
17
+ private readLatestId;
14
18
  private readCheckpoint;
15
19
  /**
16
20
  * Fork: 从指定 Checkpoint 创建新的执行线程
@@ -1,36 +1,51 @@
1
1
  /**
2
2
  * FileCheckpointSaver — 本地 JSON 文件存储
3
3
  * 适用场景:本地开发、单机部署
4
+ * 目录名对 threadId 做 encodeURIComponent:子图槽 `sessionId#父节点id` 不会变成带 `#` 的目录。
4
5
  */
5
6
  import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
6
7
  import { join } from 'node:path';
7
8
  import { stringifyJsonSafe } from './checkpoint-serializer.js';
9
+ /** 存储键可含 `#`(子图槽);目录名百分号编码,避免 URL/Markdown 把 `#` 当片段。 */
10
+ export function checkpointFileThreadDir(baseDir, threadId) {
11
+ return join(baseDir, encodeURIComponent(threadId));
12
+ }
13
+ function threadDirCandidates(baseDir, threadId) {
14
+ const encoded = checkpointFileThreadDir(baseDir, threadId);
15
+ const raw = join(baseDir, threadId);
16
+ return encoded === raw ? [encoded] : [encoded, raw];
17
+ }
8
18
  export class FileCheckpointSaver {
9
19
  baseDir;
10
20
  constructor(baseDir) {
11
21
  this.baseDir = baseDir;
12
22
  }
13
23
  async save(threadId, data) {
14
- const dir = join(this.baseDir, threadId);
24
+ const dir = checkpointFileThreadDir(this.baseDir, threadId);
15
25
  await mkdir(dir, { recursive: true });
16
26
  await writeFile(join(dir, `${data.checkpointId}.json`), stringifyJsonSafe(data));
17
27
  // 更新 latest 指针
18
28
  await writeFile(join(dir, 'latest.json'), JSON.stringify({ checkpointId: data.checkpointId }));
19
29
  }
20
30
  async load(threadId) {
21
- try {
22
- const latestRaw = await readFile(join(this.baseDir, threadId, 'latest.json'), 'utf-8');
23
- const { checkpointId } = JSON.parse(latestRaw);
24
- return this.loadByCheckpointId(checkpointId, threadId);
25
- }
26
- catch {
27
- return null;
31
+ for (const dir of threadDirCandidates(this.baseDir, threadId)) {
32
+ const checkpointId = await this.readLatestId(dir);
33
+ if (!checkpointId)
34
+ continue;
35
+ const data = await this.readCheckpoint(join(dir, `${checkpointId}.json`));
36
+ if (data)
37
+ return data;
28
38
  }
39
+ return null;
29
40
  }
30
41
  async loadByCheckpointId(checkpointId, threadId) {
31
- // If threadId provided, look directly; otherwise scan all thread dirs
32
42
  if (threadId) {
33
- return this.readCheckpoint(join(this.baseDir, threadId, `${checkpointId}.json`));
43
+ for (const dir of threadDirCandidates(this.baseDir, threadId)) {
44
+ const data = await this.readCheckpoint(join(dir, `${checkpointId}.json`));
45
+ if (data)
46
+ return data;
47
+ }
48
+ return null;
34
49
  }
35
50
  // ponytail: brute scan — upgrade to index file if perf matters
36
51
  try {
@@ -45,21 +60,35 @@ export class FileCheckpointSaver {
45
60
  return null;
46
61
  }
47
62
  async list(threadId) {
48
- const dir = join(this.baseDir, threadId);
49
- try {
50
- const files = await readdir(dir);
51
- const results = [];
52
- for (const f of files) {
53
- if (f === 'latest.json' || !f.endsWith('.json'))
54
- continue;
55
- const data = await this.readCheckpoint(join(dir, f));
56
- if (data)
57
- results.push(data);
63
+ for (const dir of threadDirCandidates(this.baseDir, threadId)) {
64
+ try {
65
+ const files = await readdir(dir);
66
+ const results = [];
67
+ for (const f of files) {
68
+ if (f === 'latest.json' || !f.endsWith('.json'))
69
+ continue;
70
+ const data = await this.readCheckpoint(join(dir, f));
71
+ if (data)
72
+ results.push(data);
73
+ }
74
+ if (results.length > 0) {
75
+ return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
76
+ }
77
+ }
78
+ catch {
79
+ // try next candidate
58
80
  }
59
- return results.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
81
+ }
82
+ return [];
83
+ }
84
+ async readLatestId(dir) {
85
+ try {
86
+ const latestRaw = await readFile(join(dir, 'latest.json'), 'utf-8');
87
+ const { checkpointId } = JSON.parse(latestRaw);
88
+ return typeof checkpointId === 'string' && checkpointId.trim() ? checkpointId : null;
60
89
  }
61
90
  catch {
62
- return [];
91
+ return null;
63
92
  }
64
93
  }
65
94
  async readCheckpoint(filePath) {
@@ -6,7 +6,7 @@ export { restoreCheckpoint } from './restore-checkpoint.js';
6
6
  export { notifyCheckpointRef, stripCheckpointCollectors } from './checkpoint-ref-notify.js';
7
7
  export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, stringifyJsonSafe, toJsonSafe, } from './checkpoint-serializer.js';
8
8
  export type { CreateCheckpointParams } from './checkpoint-serializer.js';
9
- export { FileCheckpointSaver } from './file-checkpoint-saver.js';
9
+ export { FileCheckpointSaver, checkpointFileThreadDir } from './file-checkpoint-saver.js';
10
10
  export { InMemoryCheckpointSaver } from './in-memory-checkpoint-saver.js';
11
11
  export { MongoCheckpointSaver } from './mongo-checkpoint-saver.js';
12
12
  export type { IMongoCollection, CheckpointDocument } from './mongo-checkpoint-saver.js';
@@ -5,7 +5,7 @@ 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
7
  export { createCheckpointData, serializeCheckpoint, deserializeCheckpoint, stringifyJsonSafe, toJsonSafe, } from './checkpoint-serializer.js';
8
- export { FileCheckpointSaver } from './file-checkpoint-saver.js';
8
+ export { FileCheckpointSaver, checkpointFileThreadDir } from './file-checkpoint-saver.js';
9
9
  export { InMemoryCheckpointSaver } from './in-memory-checkpoint-saver.js';
10
10
  export { MongoCheckpointSaver } from './mongo-checkpoint-saver.js';
11
11
  export { PostgresCheckpointSaver } from './pg-checkpoint-saver.js';
@@ -9,7 +9,7 @@
9
9
  * Token 统计输出粒度控制
10
10
  * - none: 不输出任何 Token 统计事件
11
11
  * - summary: 仅 Task 结束时输出 stats.token_usage 汇总
12
- * - detail: 每次 LLM 调用输出 stats.node_token + 结束时汇总
12
+ * - detail: 每次 LLM 调用输出 stats.node_token_usage + 结束时汇总
13
13
  */
14
14
  export type TokenDetailLevel = 'none' | 'summary' | 'detail';
15
15
  /**
@@ -79,7 +79,7 @@ export type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
79
79
  *
80
80
  * 与 @gt-fe/eap-contracts.TokenUsage 的区别:
81
81
  * - contracts 版本是 Task 级聚合结果(含 byNode/byModel)
82
- * - 本类型是单次调用粒度的原始记录,用于 Runtime 内部累计和 stats.node_token 事件
82
+ * - 本类型是单次调用粒度的原始记录,用于 Runtime 内部累计和 stats.node_token_usage 事件
83
83
  */
84
84
  export interface TokenUsage {
85
85
  /** 输入 Token 数 */