@gt-fe/eap-runtime 0.0.19 → 0.0.21

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 (131) hide show
  1. package/README.md +3 -2
  2. package/dist/api/handlers/invoke.handler.d.ts +7 -0
  3. package/dist/api/handlers/invoke.handler.js +57 -24
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +59 -39
  6. package/dist/api/routes/agent.routes.d.ts +8 -1
  7. package/dist/common/types.d.ts +2 -2
  8. package/dist/common/types.js +1 -1
  9. package/dist/contracts/index.d.ts +1 -1
  10. package/dist/contracts/trace-collector.d.ts +16 -8
  11. package/dist/execution/index.d.ts +2 -0
  12. package/dist/execution/index.js +2 -0
  13. package/dist/execution/interceptors.d.ts +40 -0
  14. package/dist/execution/interceptors.js +23 -0
  15. package/dist/execution/ports.d.ts +18 -0
  16. package/dist/execution/ports.js +13 -0
  17. package/dist/governance/audit-reporter.d.ts +14 -0
  18. package/dist/governance/audit-reporter.js +18 -0
  19. package/dist/governance/index.d.ts +2 -0
  20. package/dist/governance/index.js +1 -0
  21. package/dist/governance/permission-guard.d.ts +6 -2
  22. package/dist/governance/permission-guard.js +38 -5
  23. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  24. package/dist/graph/adapters/graph-nesting.js +29 -0
  25. package/dist/graph/adapters/index.d.ts +2 -0
  26. package/dist/graph/adapters/index.js +2 -0
  27. package/dist/graph/adapters/langgraph.adapter.d.ts +26 -11
  28. package/dist/graph/adapters/langgraph.adapter.js +178 -83
  29. package/dist/graph/adapters/skill-entry-span.d.ts +37 -0
  30. package/dist/graph/adapters/skill-entry-span.js +78 -0
  31. package/dist/graph/adapters/skill-isolate.d.ts +17 -0
  32. package/dist/graph/adapters/skill-isolate.js +50 -0
  33. package/dist/graph/adapters/skill-read.d.ts +10 -0
  34. package/dist/graph/adapters/skill-read.js +33 -0
  35. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  36. package/dist/graph/adapters/subgraph-compiler.js +81 -35
  37. package/dist/graph/contracts/executable-graph.d.ts +27 -5
  38. package/dist/graph/hitl/hitl-handler.js +2 -0
  39. package/dist/graph/nodes/index.d.ts +1 -0
  40. package/dist/graph/nodes/index.js +1 -0
  41. package/dist/graph/nodes/llm-node.d.ts +3 -1
  42. package/dist/graph/nodes/llm-node.js +26 -2
  43. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  44. package/dist/graph/nodes/rag/dedup.js +77 -0
  45. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  46. package/dist/graph/nodes/rag/denoise.js +220 -0
  47. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  48. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  49. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  50. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  51. package/dist/graph/nodes/rag/index.d.ts +18 -0
  52. package/dist/graph/nodes/rag/index.js +17 -0
  53. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  54. package/dist/graph/nodes/rag/prompts.js +40 -0
  55. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  56. package/dist/graph/nodes/rag/rag-node.js +135 -0
  57. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  58. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  59. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  60. package/dist/graph/nodes/rag/react-loop.js +467 -0
  61. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  62. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  63. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  64. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  65. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  66. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  67. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  68. package/dist/graph/nodes/rag/tools.js +260 -0
  69. package/dist/graph/nodes/rag/types.d.ts +226 -0
  70. package/dist/graph/nodes/rag/types.js +9 -0
  71. package/dist/graph/nodes/tool-node.d.ts +25 -1
  72. package/dist/graph/nodes/tool-node.js +193 -35
  73. package/dist/graph/skill-audit-recorder.d.ts +4 -4
  74. package/dist/graph/skill-audit-recorder.js +3 -3
  75. package/dist/graph/trace-span.d.ts +2 -9
  76. package/dist/graph/trace-span.js +1 -0
  77. package/dist/index.d.ts +3 -1
  78. package/dist/index.js +1 -0
  79. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  80. package/dist/memory/semantic-retrieval.service.js +13 -23
  81. package/dist/providers/index.d.ts +1 -1
  82. package/dist/providers/index.js +1 -1
  83. package/dist/providers/routing-llm-provider.d.ts +5 -0
  84. package/dist/providers/routing-llm-provider.js +66 -1
  85. package/dist/runtime/agent-runtime.d.ts +5 -0
  86. package/dist/runtime/agent-runtime.js +105 -63
  87. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  88. package/dist/runtime/dev-rag-deps.js +186 -0
  89. package/dist/runtime/dev-runtime.d.ts +16 -1
  90. package/dist/runtime/dev-runtime.js +59 -5
  91. package/dist/runtime/index.d.ts +1 -1
  92. package/dist/runtime/types.d.ts +10 -9
  93. package/dist/session/apply-session-compression.d.ts +81 -0
  94. package/dist/session/apply-session-compression.js +262 -0
  95. package/dist/session/apply-session-memory.d.ts +21 -0
  96. package/dist/session/apply-session-memory.js +48 -0
  97. package/dist/session/index.d.ts +2 -0
  98. package/dist/session/index.js +2 -0
  99. package/dist/session/persist-turn-messages.d.ts +51 -0
  100. package/dist/session/persist-turn-messages.js +140 -3
  101. package/dist/sse/events/index.d.ts +13 -4
  102. package/dist/sse/events/index.js +9 -1
  103. package/dist/sse/events/rag-events.d.ts +63 -0
  104. package/dist/sse/events/rag-events.js +9 -0
  105. package/dist/sse/events/session-events.d.ts +38 -0
  106. package/dist/sse/events/session-events.js +4 -0
  107. package/dist/sse/trace-collector.d.ts +2 -9
  108. package/dist/token/index.d.ts +3 -0
  109. package/dist/token/index.js +2 -0
  110. package/dist/token/record-task-token-usage.d.ts +23 -0
  111. package/dist/token/record-task-token-usage.js +76 -0
  112. package/dist/token/token-detail-mode.d.ts +4 -4
  113. package/dist/token/token-detail-mode.js +4 -4
  114. package/dist/token/token-detail.d.ts +8 -0
  115. package/dist/token/token-detail.js +22 -0
  116. package/dist/token/token-stats.service.d.ts +4 -2
  117. package/dist/token/token-stats.service.js +5 -1
  118. package/dist/tool/create-tool-executor.d.ts +6 -0
  119. package/dist/tool/create-tool-executor.js +4 -3
  120. package/dist/tool/harness-primitives.d.ts +9 -1
  121. package/dist/tool/harness-primitives.js +43 -12
  122. package/dist/tool/index.d.ts +3 -2
  123. package/dist/tool/index.js +3 -2
  124. package/dist/tool/mcp-list.d.ts +12 -0
  125. package/dist/tool/mcp-list.js +15 -0
  126. package/dist/tool/tool-executor.interface.d.ts +27 -0
  127. package/dist/tool/tool-orchestrator.d.ts +3 -1
  128. package/dist/tool/tool-orchestrator.js +15 -6
  129. package/dist/tool/tool-retry.d.ts +15 -0
  130. package/dist/tool/tool-retry.js +69 -0
  131. 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
 
@@ -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,9 +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, tokenUsagesFromLlmChannel, } from '../../token/record-task-token-usage.js';
21
22
  import { resolveExecutionContext } from '../../governance/governance-client.js';
22
23
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
23
24
  import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
@@ -27,6 +28,8 @@ import { assembleLoadedTools } from '../../tool/harness-primitives.js';
27
28
  import { resolveAgentCode } from '../../common/agent-code.js';
28
29
  import { unwrapHitlPaused } from '../../graph/hitl/hitl-handler.js';
29
30
  import { resolveTraceId } from '../../common/trace-id.js';
31
+ import { applySessionCompression, } from '../../session/apply-session-compression.js';
32
+ import { applySessionMemory, } from '../../session/apply-session-memory.js';
30
33
  // ─────────────────────────────────────────────
31
34
  // Default Graph DSL fallback
32
35
  // ─────────────────────────────────────────────
@@ -63,6 +66,8 @@ export class InvokeOrchestrator {
63
66
  graphLoader;
64
67
  checkpointSaver;
65
68
  traceCollector;
69
+ sessionCompression;
70
+ sessionMemory;
66
71
  constructor(deps) {
67
72
  this.sessionContextService = deps.sessionContextService;
68
73
  this.taskService = deps.taskService;
@@ -74,6 +79,8 @@ export class InvokeOrchestrator {
74
79
  this.graphLoader = deps.graphLoader;
75
80
  this.checkpointSaver = deps.checkpointSaver;
76
81
  this.traceCollector = deps.traceCollector;
82
+ this.sessionCompression = deps.sessionCompression;
83
+ this.sessionMemory = deps.sessionMemory;
77
84
  }
78
85
  /**
79
86
  * 执行同步调用编排
@@ -158,6 +165,7 @@ export class InvokeOrchestrator {
158
165
  });
159
166
  // ─── Step 5: 构建/执行 Graph ───
160
167
  let graphResult;
168
+ let finishPoints;
161
169
  try {
162
170
  // 构建图 — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态
163
171
  const loadOpts = { agentVersion: req.agentVersion };
@@ -165,6 +173,7 @@ export class InvokeOrchestrator {
165
173
  ? await this.graphLoader.loadGraph(agentCode, loadOpts)
166
174
  : DEFAULT_GRAPH_DSL;
167
175
  const graph = await this.graphEngine.buildGraph(graphDsl);
176
+ finishPoints = graph.finishPoints;
168
177
  const loadedToolsBase = this.graphLoader
169
178
  ? toLoadedToolMap(await this.graphLoader.loadToolDefinitions(agentCode, loadOpts))
170
179
  : undefined;
@@ -184,7 +193,19 @@ export class InvokeOrchestrator {
184
193
  : undefined;
185
194
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
186
195
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
187
- 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: this.sessionCompression,
203
+ }),
204
+ userId: effectiveUserId,
205
+ agentCode,
206
+ query: typeof req.input === 'string' ? req.input : undefined,
207
+ options: this.sessionMemory,
208
+ });
188
209
  const messages = await buildTurnGraphMessages({
189
210
  history,
190
211
  input: req.input,
@@ -204,6 +225,8 @@ export class InvokeOrchestrator {
204
225
  sessionId,
205
226
  executionContext: ctx,
206
227
  forceToolServer: req.config?.forceToolServer,
228
+ ragScope: req.ragScope,
229
+ ragSources: req.ragSources,
207
230
  loadedTools,
208
231
  loadedSkills,
209
232
  skillDefinitions,
@@ -216,6 +239,8 @@ export class InvokeOrchestrator {
216
239
  });
217
240
  }
218
241
  catch (error) {
242
+ const usages = await this.persistInvokeUsages(task.id, sessionId);
243
+ const tokenUsage = tokenUsageTotals(usages);
219
244
  const hitlPaused = unwrapHitlPaused(error);
220
245
  if (hitlPaused) {
221
246
  await this.taskService.updateStatus(task.id, 'waiting_hitl', buildHitlWaitingTaskOutput({
@@ -235,7 +260,7 @@ export class InvokeOrchestrator {
235
260
  taskId: task.id,
236
261
  sessionId,
237
262
  output: hitlPaused.question,
238
- tokenUsage: { input: 0, output: 0, total: 0 },
263
+ tokenUsage,
239
264
  duration: Date.now() - startTime,
240
265
  status: 'waiting_hitl',
241
266
  hitl: {
@@ -252,7 +277,7 @@ export class InvokeOrchestrator {
252
277
  taskId: task.id,
253
278
  sessionId,
254
279
  output: error.message,
255
- tokenUsage: { input: 0, output: 0, total: 0 },
280
+ tokenUsage,
256
281
  duration: Date.now() - startTime,
257
282
  status: 'token_exhausted',
258
283
  });
@@ -264,7 +289,7 @@ export class InvokeOrchestrator {
264
289
  taskId: task.id,
265
290
  sessionId,
266
291
  output: 'Task execution timed out',
267
- tokenUsage: { input: 0, output: 0, total: 0 },
292
+ tokenUsage,
268
293
  duration: Date.now() - startTime,
269
294
  status: 'timeout',
270
295
  });
@@ -276,7 +301,7 @@ export class InvokeOrchestrator {
276
301
  taskId: task.id,
277
302
  sessionId,
278
303
  output: errorMessage,
279
- tokenUsage: { input: 0, output: 0, total: 0 },
304
+ tokenUsage,
280
305
  duration: Date.now() - startTime,
281
306
  status: 'failed',
282
307
  });
@@ -310,33 +335,27 @@ export class InvokeOrchestrator {
310
335
  };
311
336
  // 将 Graph 层面的 token 消耗记录到 Task(按节点明细逐个记录,便于审计)
312
337
  if (graphUsage.perNode && graphUsage.perNode.length > 0) {
313
- for (const u of graphUsage.perNode) {
314
- await this.taskService.recordTokenUsage(task.id, {
315
- nodeId: u.nodeId,
316
- model: u.model,
317
- inputTokens: u.inputTokens,
318
- outputTokens: u.outputTokens,
319
- costEstimate: 0,
320
- });
321
- }
338
+ await recordTaskTokenUsages(this.taskService, task.id, graphUsage.perNode);
322
339
  }
323
340
  else if (graphUsage.total > 0) {
324
- await this.taskService.recordTokenUsage(task.id, {
325
- nodeId: 'graph',
326
- model: 'unknown',
327
- inputTokens: graphUsage.input,
328
- outputTokens: graphUsage.output,
329
- costEstimate: 0,
330
- });
341
+ await recordTaskTokenUsages(this.taskService, task.id, [
342
+ {
343
+ nodeId: 'graph',
344
+ model: 'unknown',
345
+ inputTokens: graphUsage.input,
346
+ outputTokens: graphUsage.output,
347
+ },
348
+ ]);
331
349
  }
332
350
  // ─── Step 7: 完成 Task (running → success) ───
333
351
  await this.taskService.updateStatus(task.id, 'success', graphResult.output);
334
352
  // ─── Step 7b: 持久化本轮 transcript(统一顺序:assistant tool_calls → tool → assistant 文本)───
335
- const initialMessagesCount = sessionContext.messages.length + 1; // history + 本轮 user
353
+ // 按 Session 已有行数切 Graph 增量。多段正文收成一条后 Session 比 Graph 短,切点可能提前;finishPoints 只含一个实际节点 id 时靠最后一条纯文本仍落到 merge。
354
+ const initialMessagesCount = sessionContext.messages.length + 1;
336
355
  const allMessages = Array.isArray(graphResult.finalState?.messages)
337
356
  ? graphResult.finalState.messages
338
357
  : [];
339
- const newMessages = allMessages.slice(initialMessagesCount);
358
+ const newMessages = sessionVisibleGraphMessages(allMessages.slice(initialMessagesCount), finishPoints);
340
359
  await persistTurnTranscript(this.messageService, {
341
360
  sessionId,
342
361
  taskId: task.id,
@@ -375,6 +394,20 @@ export class InvokeOrchestrator {
375
394
  }
376
395
  return false;
377
396
  }
397
+ async persistInvokeUsages(taskId, sessionId) {
398
+ if (!this.checkpointSaver)
399
+ return [];
400
+ const threadId = resolveCheckpointThreadId({ sessionId, taskId });
401
+ const ck = await this.checkpointSaver.load(threadId);
402
+ const usages = tokenUsagesFromLlmChannel(ck?.state);
403
+ await recordTaskTokenUsages(this.taskService, taskId, usages);
404
+ return usages;
405
+ }
406
+ }
407
+ function tokenUsageTotals(usages) {
408
+ const input = usages.reduce((sum, row) => sum + row.inputTokens, 0);
409
+ const output = usages.reduce((sum, row) => sum + row.outputTokens, 0);
410
+ return { input, output, total: input + output };
378
411
  }
379
412
  // ─────────────────────────────────────────────
380
413
  // 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,9 +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
+ import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
26
+ import { iterSessionCompression, } from '../../session/apply-session-compression.js';
27
+ import { applySessionMemory, } from '../../session/apply-session-memory.js';
28
+ import { resolveTokenDetail, shouldEmitTokenSse } from '../../token/token-detail.js';
25
29
  import { SSEEmitter } from '../../sse/sse-emitter.js';
26
30
  import { resolveExecutionContext } from '../../governance/governance-client.js';
27
31
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
@@ -71,6 +75,8 @@ export class StreamOrchestrator {
71
75
  graphLoader;
72
76
  checkpointSaver;
73
77
  traceCollector;
78
+ sessionCompression;
79
+ sessionMemory;
74
80
  constructor(deps) {
75
81
  this.sessionContextService = deps.sessionContextService;
76
82
  this.taskService = deps.taskService;
@@ -82,6 +88,8 @@ export class StreamOrchestrator {
82
88
  this.graphLoader = deps.graphLoader;
83
89
  this.checkpointSaver = deps.checkpointSaver;
84
90
  this.traceCollector = deps.traceCollector;
91
+ this.sessionCompression = deps.sessionCompression;
92
+ this.sessionMemory = deps.sessionMemory;
85
93
  }
86
94
  /**
87
95
  * 执行流式调用编排
@@ -206,7 +214,28 @@ export class StreamOrchestrator {
206
214
  : undefined;
207
215
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
208
216
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
209
- const history = messageRecordsToGraphMessages(sessionContext.messages);
217
+ let history = messageRecordsToGraphMessages(sessionContext.messages);
218
+ for await (const step of iterSessionCompression({
219
+ history,
220
+ sessionId,
221
+ userId,
222
+ taskId,
223
+ options: this.sessionCompression,
224
+ })) {
225
+ if (step.kind === 'history') {
226
+ history = step.messages;
227
+ continue;
228
+ }
229
+ emitter.emit(this.buildSSEEvent(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, taskId, { ...step.payload }));
230
+ }
231
+ history = await applySessionMemory({
232
+ history,
233
+ userId,
234
+ agentCode,
235
+ query: typeof req.input === 'string' ? req.input : undefined,
236
+ options: this.sessionMemory,
237
+ });
238
+ const tokenDetail = resolveTokenDetail(req.config?.tokenDetail);
210
239
  const messages = await buildTurnGraphMessages({
211
240
  history,
212
241
  input: req.input,
@@ -214,7 +243,7 @@ export class StreamOrchestrator {
214
243
  threadId: sessionId,
215
244
  });
216
245
  const skillInput = resolveSkillInputFromInvokeInput(req.input);
217
- let assistantText = '';
246
+ const assistantBuckets = createStreamAssistantBuckets();
218
247
  let toolCallReasoningContent;
219
248
  let assistantReasoningContent;
220
249
  const pendingToolCalls = [];
@@ -232,6 +261,8 @@ export class StreamOrchestrator {
232
261
  sessionId,
233
262
  executionContext,
234
263
  forceToolServer: req.config?.forceToolServer,
264
+ ragScope: req.ragScope,
265
+ ragSources: req.ragSources,
235
266
  loadedTools,
236
267
  loadedSkills,
237
268
  skillDefinitions,
@@ -271,38 +302,20 @@ export class StreamOrchestrator {
271
302
  toolCallReasoningContent = p.reasoningContent;
272
303
  }
273
304
  }
274
- if (runtimeEvent.type === 'tool.result') {
275
- const p = runtimeEvent.payload;
276
- if (p.toolCallId && p.toolName) {
277
- toolResults.push({
278
- toolCallId: p.toolCallId,
279
- toolName: p.toolName,
280
- content: p.content,
281
- success: p.success !== false,
282
- });
283
- }
284
- }
305
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
285
306
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
286
307
  if (streamedReasoning)
287
308
  assistantReasoningContent = streamedReasoning;
288
- const delta = extractStreamTextDelta(runtimeEvent.type, runtimeEvent.payload);
289
- if (delta) {
290
- if (runtimeEvent.type === 'response.final') {
291
- // final 若带完整文本则覆盖累加,避免与 chunk 重复拼接
292
- if (delta.trim())
293
- assistantText = delta;
294
- }
295
- else {
296
- assistantText += delta;
297
- }
309
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
310
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
311
+ emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
298
312
  }
299
- // 将 RuntimeEvent 转为 SSEEvent 并推送
300
- emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
301
313
  }
302
314
  }
303
315
  catch (error) {
304
316
  const hitlPaused = unwrapHitlPaused(error);
305
317
  if (hitlPaused) {
318
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
306
319
  await this.taskService.updateStatus(taskId, 'waiting_hitl', buildHitlWaitingTaskOutput({
307
320
  threadId: hitlPaused.threadId,
308
321
  question: hitlPaused.question,
@@ -323,12 +336,14 @@ export class StreamOrchestrator {
323
336
  }
324
337
  // ─── 错误处理:TokenBudgetExhausted ───
325
338
  if (error instanceof TokenBudgetExhaustedError) {
339
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
326
340
  await this.taskService.updateStatus(taskId, 'token_exhausted', error.message);
327
341
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
328
342
  return;
329
343
  }
330
344
  // ─── 错误处理:Timeout ───
331
345
  if (this.isTimeoutError(error)) {
346
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
332
347
  await this.taskService.updateStatus(taskId, 'timeout');
333
348
  emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
334
349
  code: ErrorCodes.TASK_TIMEOUT,
@@ -340,6 +355,7 @@ export class StreamOrchestrator {
340
355
  }
341
356
  // ─── 错误处理:通用错误 ───
342
357
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
358
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
343
359
  await this.taskService.updateStatus(taskId, 'failed', errorMessage);
344
360
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
345
361
  return;
@@ -348,21 +364,22 @@ export class StreamOrchestrator {
348
364
  // Emit: response.final(带完整 assistant 文本,供客户端兜底)
349
365
  const tokenStats = this.tokenStatsService.getTaskStats(taskId);
350
366
  emitter.emit(this.buildSSEEvent('response.final', correlationId, taskId, {
351
- content: assistantText,
367
+ content: visibleStreamAssistantText(assistantBuckets),
352
368
  format: 'text',
353
369
  }));
354
- // Emit: stats.token_usage
355
370
  const totalTokens = (tokenStats.total.input ?? 0) + (tokenStats.total.output ?? 0);
356
- emitter.emit(this.buildSSEEvent('stats.token_usage', correlationId, taskId, {
357
- total: {
358
- input: tokenStats.total.input,
359
- output: tokenStats.total.output,
360
- total: tokenStats.total.total ?? totalTokens,
361
- },
362
- byNode: tokenStats.byNode,
363
- byModel: tokenStats.byModel,
364
- costEstimate: tokenStats.costEstimate,
365
- }));
371
+ if (shouldEmitTokenSse(tokenDetail, 'stats.token_usage')) {
372
+ emitter.emit(this.buildSSEEvent('stats.token_usage', correlationId, taskId, {
373
+ total: {
374
+ input: tokenStats.total.input,
375
+ output: tokenStats.total.output,
376
+ total: tokenStats.total.total ?? totalTokens,
377
+ },
378
+ byNode: tokenStats.byNode,
379
+ byModel: tokenStats.byModel,
380
+ costEstimate: tokenStats.costEstimate,
381
+ }));
382
+ }
366
383
  // ─── Step 8: 记录 Token + 完成 Task + 关闭 SSE ───
367
384
  const duration = Date.now() - startTime;
368
385
  // Emit: task.completed
@@ -370,6 +387,7 @@ export class StreamOrchestrator {
370
387
  taskId,
371
388
  duration,
372
389
  }));
390
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
373
391
  // Task 状态流转 → success
374
392
  await this.taskService.updateStatus(taskId, 'success');
375
393
  // ─── Step 8b: 持久化 transcript(与 invoke 同序)───
@@ -380,7 +398,9 @@ export class StreamOrchestrator {
380
398
  messages: buildTurnTranscriptFromStreamEvents({
381
399
  pendingToolCalls,
382
400
  toolResults,
383
- assistantText,
401
+ assistantTexts: streamAssistantTexts(assistantBuckets),
402
+ assistantText: visibleStreamAssistantText(assistantBuckets),
403
+ finishPoints: graph.finishPoints,
384
404
  toolCallReasoningContent,
385
405
  assistantReasoningContent,
386
406
  }),
@@ -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 相同 */
@@ -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 数 */
@@ -7,7 +7,7 @@
7
7
  */
8
8
  /** RuntimeConfig 默认值 */
9
9
  export const DEFAULT_RUNTIME_CONFIG = {
10
- tokenDetail: 'summary',
10
+ tokenDetail: 'detail',
11
11
  timeout: 60000,
12
12
  maxIterations: 10,
13
13
  heartbeatInterval: 30000,
@@ -7,5 +7,5 @@
7
7
  * - LLM 调用:由模型路由模块实现
8
8
  * - 存储接口:由各存储后端实现
9
9
  */
10
- export type { ITraceCollector, TraceEvent, TraceSession, } from './trace-collector.js';
10
+ export type { ITraceCollector, CompleteNodeParams, TraceEvent, TraceSession, } from './trace-collector.js';
11
11
  export type { IPermissionChecker } from './permission-checker.js';
@@ -85,6 +85,8 @@ export interface ExecutionNodeEvent {
85
85
  type: 'start' | 'llm' | 'tool' | 'skill' | 'condition' | 'hitl' | 'end';
86
86
  /** 工具运行环境(type=tool) */
87
87
  toolRuntime?: 'local' | 'server' | 'both';
88
+ /** skill/tool 包版本;与 TraceClient RecordSpanParams.version 同义 */
89
+ version?: string;
88
90
  /** Agent 对外主键 */
89
91
  agentCode?: string;
90
92
  /** 输入数据 */
@@ -92,6 +94,19 @@ export interface ExecutionNodeEvent {
92
94
  /** 扩展元数据(如 skillId) */
93
95
  metadata?: Record<string, unknown>;
94
96
  }
97
+ /**
98
+ * 完成执行节点参数。llm Span 的 model / token 与 stats.node_token_usage 同源。
99
+ */
100
+ export interface CompleteNodeParams {
101
+ status: 'completed' | 'failed';
102
+ output?: unknown;
103
+ error?: string;
104
+ inputTokens?: number;
105
+ outputTokens?: number;
106
+ totalTokens?: number;
107
+ /** llm Span:实际模型标识;与 NodeTokenUsage.model 相同 */
108
+ model?: string;
109
+ }
95
110
  /**
96
111
  * 链路事件
97
112
  *
@@ -176,14 +191,7 @@ export interface ITraceCollector {
176
191
  * @param nodeId - 节点 ID
177
192
  * @param params - 完成参数
178
193
  */
179
- completeNode(nodeId: string, params: {
180
- status: 'completed' | 'failed';
181
- output?: unknown;
182
- error?: string;
183
- inputTokens?: number;
184
- outputTokens?: number;
185
- totalTokens?: number;
186
- }): Promise<void>;
194
+ completeNode(nodeId: string, params: CompleteNodeParams): Promise<void>;
187
195
  /**
188
196
  * Checkpoint 已落盘后的引用回调。Hosting 适配 TraceClient.recordCheckpointRef。
189
197
  * Runtime 仅在同时有 traceId 与 spanId 时调用;不传 graph state 正文。
@@ -0,0 +1,2 @@
1
+ export { decideBeforeModel, decideBeforeTool, type AfterModelInterceptor, type AfterToolInterceptor, type BeforeModelInput, type BeforeModelInterceptor, type BeforeToolInterceptor, type ExecutionInterceptors, type ModelInterceptorDecision, type ToolInterceptorDecision, } from './interceptors.js';
2
+ export { sandboxFromJail, type ISandbox, type IApproval } from './ports.js';
@@ -0,0 +1,2 @@
1
+ export { decideBeforeModel, decideBeforeTool, } from './interceptors.js';
2
+ export { sandboxFromJail } from './ports.js';
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 执行拦截:Tool / LLM 调用前可拒绝,默认无注入则与未装配时相同。
3
+ * 不是规格 R13 Hook,不做插件发现、不读项目 hook 目录。
4
+ */
5
+ import type { ExecutionContext } from '@gt-fe/eap-contracts';
6
+ import type { GraphMessage } from '../graph/contracts/executable-graph.js';
7
+ import type { LLMNodeConfig, LLMResponse } from '../graph/nodes/llm-node.js';
8
+ import type { ToolCallRequest, ToolCallResult } from '../tool/tool-executor.interface.js';
9
+ export type ToolInterceptorDecision = {
10
+ action: 'allow';
11
+ } | {
12
+ action: 'deny';
13
+ reason: string;
14
+ } | {
15
+ action: 'ask';
16
+ };
17
+ export type BeforeToolInterceptor = (request: ToolCallRequest, ctx?: ExecutionContext) => Promise<ToolInterceptorDecision | void> | ToolInterceptorDecision | void;
18
+ export type AfterToolInterceptor = (request: ToolCallRequest, result: ToolCallResult) => Promise<void> | void;
19
+ export interface BeforeModelInput {
20
+ messages: GraphMessage[];
21
+ config: LLMNodeConfig;
22
+ nodeId: string;
23
+ }
24
+ export type ModelInterceptorDecision = {
25
+ action: 'allow';
26
+ messages?: GraphMessage[];
27
+ } | {
28
+ action: 'deny';
29
+ reason: string;
30
+ };
31
+ export type BeforeModelInterceptor = (input: BeforeModelInput) => Promise<ModelInterceptorDecision | void> | ModelInterceptorDecision | void;
32
+ export type AfterModelInterceptor = (input: BeforeModelInput, response: LLMResponse) => Promise<void> | void;
33
+ export interface ExecutionInterceptors {
34
+ beforeTool?: BeforeToolInterceptor;
35
+ afterTool?: AfterToolInterceptor;
36
+ beforeModel?: BeforeModelInterceptor;
37
+ afterModel?: AfterModelInterceptor;
38
+ }
39
+ export declare function decideBeforeTool(interceptor: BeforeToolInterceptor | undefined, request: ToolCallRequest, ctx?: ExecutionContext): Promise<ToolInterceptorDecision>;
40
+ export declare function decideBeforeModel(interceptor: BeforeModelInterceptor | undefined, input: BeforeModelInput): Promise<ModelInterceptorDecision>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 执行拦截:Tool / LLM 调用前可拒绝,默认无注入则与未装配时相同。
3
+ * 不是规格 R13 Hook,不做插件发现、不读项目 hook 目录。
4
+ */
5
+ export async function decideBeforeTool(interceptor, request, ctx) {
6
+ if (!interceptor)
7
+ return { action: 'allow' };
8
+ const raw = await interceptor(request, ctx);
9
+ if (!raw || raw.action === 'allow')
10
+ return { action: 'allow' };
11
+ if (raw.action === 'ask')
12
+ return { action: 'ask' };
13
+ return { action: 'deny', reason: raw.reason || '拦截拒绝' };
14
+ }
15
+ export async function decideBeforeModel(interceptor, input) {
16
+ if (!interceptor)
17
+ return { action: 'allow' };
18
+ const raw = await interceptor(input);
19
+ if (!raw || raw.action === 'allow') {
20
+ return { action: 'allow', messages: raw && raw.action === 'allow' ? raw.messages : undefined };
21
+ }
22
+ return { action: 'deny', reason: raw.reason || '拦截拒绝' };
23
+ }