@gt-fe/eap-runtime 0.0.20 → 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 (118) 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 +48 -9
  4. package/dist/api/handlers/stream.handler.d.ts +6 -0
  5. package/dist/api/handlers/stream.handler.js +56 -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/execution/index.d.ts +2 -0
  10. package/dist/execution/index.js +2 -0
  11. package/dist/execution/interceptors.d.ts +40 -0
  12. package/dist/execution/interceptors.js +23 -0
  13. package/dist/execution/ports.d.ts +18 -0
  14. package/dist/execution/ports.js +13 -0
  15. package/dist/governance/audit-reporter.d.ts +14 -0
  16. package/dist/governance/audit-reporter.js +18 -0
  17. package/dist/governance/index.d.ts +2 -0
  18. package/dist/governance/index.js +1 -0
  19. package/dist/governance/permission-guard.d.ts +6 -2
  20. package/dist/governance/permission-guard.js +38 -5
  21. package/dist/graph/adapters/graph-nesting.d.ts +12 -0
  22. package/dist/graph/adapters/graph-nesting.js +29 -0
  23. package/dist/graph/adapters/index.d.ts +2 -0
  24. package/dist/graph/adapters/index.js +2 -0
  25. package/dist/graph/adapters/langgraph.adapter.d.ts +25 -11
  26. package/dist/graph/adapters/langgraph.adapter.js +142 -82
  27. package/dist/graph/adapters/skill-isolate.d.ts +17 -0
  28. package/dist/graph/adapters/skill-isolate.js +50 -0
  29. package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
  30. package/dist/graph/adapters/subgraph-compiler.js +68 -32
  31. package/dist/graph/contracts/executable-graph.d.ts +27 -5
  32. package/dist/graph/hitl/hitl-handler.js +2 -0
  33. package/dist/graph/nodes/index.d.ts +1 -0
  34. package/dist/graph/nodes/index.js +1 -0
  35. package/dist/graph/nodes/llm-node.d.ts +3 -1
  36. package/dist/graph/nodes/llm-node.js +26 -2
  37. package/dist/graph/nodes/rag/dedup.d.ts +20 -0
  38. package/dist/graph/nodes/rag/dedup.js +77 -0
  39. package/dist/graph/nodes/rag/denoise.d.ts +58 -0
  40. package/dist/graph/nodes/rag/denoise.js +220 -0
  41. package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
  42. package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
  43. package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
  44. package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
  45. package/dist/graph/nodes/rag/index.d.ts +18 -0
  46. package/dist/graph/nodes/rag/index.js +17 -0
  47. package/dist/graph/nodes/rag/prompts.d.ts +12 -0
  48. package/dist/graph/nodes/rag/prompts.js +40 -0
  49. package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
  50. package/dist/graph/nodes/rag/rag-node.js +135 -0
  51. package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
  52. package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
  53. package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
  54. package/dist/graph/nodes/rag/react-loop.js +467 -0
  55. package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
  56. package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
  57. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
  58. package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
  59. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
  60. package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
  61. package/dist/graph/nodes/rag/tools.d.ts +90 -0
  62. package/dist/graph/nodes/rag/tools.js +260 -0
  63. package/dist/graph/nodes/rag/types.d.ts +226 -0
  64. package/dist/graph/nodes/rag/types.js +9 -0
  65. package/dist/graph/nodes/tool-node.d.ts +19 -1
  66. package/dist/graph/nodes/tool-node.js +162 -33
  67. package/dist/index.d.ts +2 -0
  68. package/dist/index.js +1 -0
  69. package/dist/memory/semantic-retrieval.service.d.ts +1 -1
  70. package/dist/memory/semantic-retrieval.service.js +13 -23
  71. package/dist/providers/index.d.ts +1 -1
  72. package/dist/providers/index.js +1 -1
  73. package/dist/providers/routing-llm-provider.d.ts +5 -0
  74. package/dist/providers/routing-llm-provider.js +66 -1
  75. package/dist/runtime/agent-runtime.d.ts +5 -0
  76. package/dist/runtime/agent-runtime.js +86 -66
  77. package/dist/runtime/dev-rag-deps.d.ts +21 -0
  78. package/dist/runtime/dev-rag-deps.js +186 -0
  79. package/dist/runtime/dev-runtime.d.ts +16 -1
  80. package/dist/runtime/dev-runtime.js +59 -5
  81. package/dist/runtime/index.d.ts +1 -1
  82. package/dist/runtime/types.d.ts +10 -9
  83. package/dist/session/apply-session-compression.d.ts +81 -0
  84. package/dist/session/apply-session-compression.js +262 -0
  85. package/dist/session/apply-session-memory.d.ts +21 -0
  86. package/dist/session/apply-session-memory.js +48 -0
  87. package/dist/session/index.d.ts +2 -0
  88. package/dist/session/index.js +2 -0
  89. package/dist/session/persist-turn-messages.d.ts +51 -0
  90. package/dist/session/persist-turn-messages.js +140 -3
  91. package/dist/sse/events/index.d.ts +13 -4
  92. package/dist/sse/events/index.js +9 -1
  93. package/dist/sse/events/rag-events.d.ts +63 -0
  94. package/dist/sse/events/rag-events.js +9 -0
  95. package/dist/sse/events/session-events.d.ts +38 -0
  96. package/dist/sse/events/session-events.js +4 -0
  97. package/dist/token/index.d.ts +2 -1
  98. package/dist/token/index.js +2 -1
  99. package/dist/token/record-task-token-usage.d.ts +2 -0
  100. package/dist/token/record-task-token-usage.js +24 -0
  101. package/dist/token/token-detail-mode.d.ts +4 -4
  102. package/dist/token/token-detail-mode.js +4 -4
  103. package/dist/token/token-detail.d.ts +8 -0
  104. package/dist/token/token-detail.js +22 -0
  105. package/dist/token/token-stats.service.d.ts +2 -2
  106. package/dist/token/token-stats.service.js +1 -1
  107. package/dist/tool/create-tool-executor.d.ts +6 -0
  108. package/dist/tool/create-tool-executor.js +4 -3
  109. package/dist/tool/harness-primitives.d.ts +9 -1
  110. package/dist/tool/harness-primitives.js +43 -12
  111. package/dist/tool/index.d.ts +2 -1
  112. package/dist/tool/index.js +2 -1
  113. package/dist/tool/tool-executor.interface.d.ts +27 -0
  114. package/dist/tool/tool-orchestrator.d.ts +3 -1
  115. package/dist/tool/tool-orchestrator.js +15 -6
  116. package/dist/tool/tool-retry.d.ts +15 -0
  117. package/dist/tool/tool-retry.js +69 -0
  118. 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,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, } 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,19 @@ 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: this.sessionCompression,
203
+ }),
204
+ userId: effectiveUserId,
205
+ agentCode,
206
+ query: typeof req.input === 'string' ? req.input : undefined,
207
+ options: this.sessionMemory,
208
+ });
189
209
  const messages = await buildTurnGraphMessages({
190
210
  history,
191
211
  input: req.input,
@@ -205,6 +225,8 @@ export class InvokeOrchestrator {
205
225
  sessionId,
206
226
  executionContext: ctx,
207
227
  forceToolServer: req.config?.forceToolServer,
228
+ ragScope: req.ragScope,
229
+ ragSources: req.ragSources,
208
230
  loadedTools,
209
231
  loadedSkills,
210
232
  skillDefinitions,
@@ -217,6 +239,8 @@ export class InvokeOrchestrator {
217
239
  });
218
240
  }
219
241
  catch (error) {
242
+ const usages = await this.persistInvokeUsages(task.id, sessionId);
243
+ const tokenUsage = tokenUsageTotals(usages);
220
244
  const hitlPaused = unwrapHitlPaused(error);
221
245
  if (hitlPaused) {
222
246
  await this.taskService.updateStatus(task.id, 'waiting_hitl', buildHitlWaitingTaskOutput({
@@ -236,7 +260,7 @@ export class InvokeOrchestrator {
236
260
  taskId: task.id,
237
261
  sessionId,
238
262
  output: hitlPaused.question,
239
- tokenUsage: { input: 0, output: 0, total: 0 },
263
+ tokenUsage,
240
264
  duration: Date.now() - startTime,
241
265
  status: 'waiting_hitl',
242
266
  hitl: {
@@ -253,7 +277,7 @@ export class InvokeOrchestrator {
253
277
  taskId: task.id,
254
278
  sessionId,
255
279
  output: error.message,
256
- tokenUsage: { input: 0, output: 0, total: 0 },
280
+ tokenUsage,
257
281
  duration: Date.now() - startTime,
258
282
  status: 'token_exhausted',
259
283
  });
@@ -265,7 +289,7 @@ export class InvokeOrchestrator {
265
289
  taskId: task.id,
266
290
  sessionId,
267
291
  output: 'Task execution timed out',
268
- tokenUsage: { input: 0, output: 0, total: 0 },
292
+ tokenUsage,
269
293
  duration: Date.now() - startTime,
270
294
  status: 'timeout',
271
295
  });
@@ -277,7 +301,7 @@ export class InvokeOrchestrator {
277
301
  taskId: task.id,
278
302
  sessionId,
279
303
  output: errorMessage,
280
- tokenUsage: { input: 0, output: 0, total: 0 },
304
+ tokenUsage,
281
305
  duration: Date.now() - startTime,
282
306
  status: 'failed',
283
307
  });
@@ -326,11 +350,12 @@ export class InvokeOrchestrator {
326
350
  // ─── Step 7: 完成 Task (running → success) ───
327
351
  await this.taskService.updateStatus(task.id, 'success', graphResult.output);
328
352
  // ─── Step 7b: 持久化本轮 transcript(统一顺序:assistant tool_calls → tool → assistant 文本)───
329
- const initialMessagesCount = sessionContext.messages.length + 1; // history + 本轮 user
353
+ // 按 Session 已有行数切 Graph 增量。多段正文收成一条后 Session 比 Graph 短,切点可能提前;finishPoints 只含一个实际节点 id 时靠最后一条纯文本仍落到 merge。
354
+ const initialMessagesCount = sessionContext.messages.length + 1;
330
355
  const allMessages = Array.isArray(graphResult.finalState?.messages)
331
356
  ? graphResult.finalState.messages
332
357
  : [];
333
- const newMessages = allMessages.slice(initialMessagesCount);
358
+ const newMessages = sessionVisibleGraphMessages(allMessages.slice(initialMessagesCount), finishPoints);
334
359
  await persistTurnTranscript(this.messageService, {
335
360
  sessionId,
336
361
  taskId: task.id,
@@ -369,6 +394,20 @@ export class InvokeOrchestrator {
369
394
  }
370
395
  return false;
371
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 };
372
411
  }
373
412
  // ─────────────────────────────────────────────
374
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,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, } 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,28 @@ 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
+ 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);
211
239
  const messages = await buildTurnGraphMessages({
212
240
  history,
213
241
  input: req.input,
@@ -215,7 +243,7 @@ export class StreamOrchestrator {
215
243
  threadId: sessionId,
216
244
  });
217
245
  const skillInput = resolveSkillInputFromInvokeInput(req.input);
218
- let assistantText = '';
246
+ const assistantBuckets = createStreamAssistantBuckets();
219
247
  let toolCallReasoningContent;
220
248
  let assistantReasoningContent;
221
249
  const pendingToolCalls = [];
@@ -233,6 +261,8 @@ export class StreamOrchestrator {
233
261
  sessionId,
234
262
  executionContext,
235
263
  forceToolServer: req.config?.forceToolServer,
264
+ ragScope: req.ragScope,
265
+ ragSources: req.ragSources,
236
266
  loadedTools,
237
267
  loadedSkills,
238
268
  skillDefinitions,
@@ -272,33 +302,14 @@ export class StreamOrchestrator {
272
302
  toolCallReasoningContent = p.reasoningContent;
273
303
  }
274
304
  }
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
- }
305
+ appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
286
306
  const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
287
307
  if (streamedReasoning)
288
308
  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
- }
309
+ applyStreamTextDelta(assistantBuckets, runtimeEvent.type, runtimeEvent.payload);
310
+ if (shouldEmitTokenSse(tokenDetail, runtimeEvent.type)) {
311
+ emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
299
312
  }
300
- // 将 RuntimeEvent 转为 SSEEvent 并推送
301
- emitter.emit(this.runtimeEventToSSE(runtimeEvent, correlationId, taskId));
302
313
  }
303
314
  }
304
315
  catch (error) {
@@ -325,12 +336,14 @@ export class StreamOrchestrator {
325
336
  }
326
337
  // ─── 错误处理:TokenBudgetExhausted ───
327
338
  if (error instanceof TokenBudgetExhaustedError) {
339
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
328
340
  await this.taskService.updateStatus(taskId, 'token_exhausted', error.message);
329
341
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
330
342
  return;
331
343
  }
332
344
  // ─── 错误处理:Timeout ───
333
345
  if (this.isTimeoutError(error)) {
346
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
334
347
  await this.taskService.updateStatus(taskId, 'timeout');
335
348
  emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
336
349
  code: ErrorCodes.TASK_TIMEOUT,
@@ -342,6 +355,7 @@ export class StreamOrchestrator {
342
355
  }
343
356
  // ─── 错误处理:通用错误 ───
344
357
  const errorMessage = error instanceof Error ? error.message : 'Unknown error';
358
+ await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
345
359
  await this.taskService.updateStatus(taskId, 'failed', errorMessage);
346
360
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
347
361
  return;
@@ -350,21 +364,22 @@ export class StreamOrchestrator {
350
364
  // Emit: response.final(带完整 assistant 文本,供客户端兜底)
351
365
  const tokenStats = this.tokenStatsService.getTaskStats(taskId);
352
366
  emitter.emit(this.buildSSEEvent('response.final', correlationId, taskId, {
353
- content: assistantText,
367
+ content: visibleStreamAssistantText(assistantBuckets),
354
368
  format: 'text',
355
369
  }));
356
- // Emit: stats.token_usage
357
370
  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
- }));
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
+ }
368
383
  // ─── Step 8: 记录 Token + 完成 Task + 关闭 SSE ───
369
384
  const duration = Date.now() - startTime;
370
385
  // Emit: task.completed
@@ -383,7 +398,9 @@ export class StreamOrchestrator {
383
398
  messages: buildTurnTranscriptFromStreamEvents({
384
399
  pendingToolCalls,
385
400
  toolResults,
386
- assistantText,
401
+ assistantTexts: streamAssistantTexts(assistantBuckets),
402
+ assistantText: visibleStreamAssistantText(assistantBuckets),
403
+ finishPoints: graph.finishPoints,
387
404
  toolCallReasoningContent,
388
405
  assistantReasoningContent,
389
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,
@@ -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
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 执行口类型:不另起引擎,也不改现有调用路径。
3
+ * ISandbox 描述 jail 根目录;execute_command 仍走 harness jail,未经过本接口。
4
+ * IApproval 即 HitlHandler;确认仍走 require_confirmation。
5
+ * OS 级沙箱见 TODO-skill-fs FS-2。
6
+ */
7
+ import type { HitlHandler } from '../graph/hitl/hitl-handler.js';
8
+ /** 命令路径约束。默认:用户目录读写 + 技能包只读。 */
9
+ export interface ISandbox {
10
+ allowedWriteRoots: string[];
11
+ allowedReadRoots: string[];
12
+ }
13
+ export declare function sandboxFromJail(jail: {
14
+ writeRoot: string;
15
+ extraRoots?: string[];
16
+ }): ISandbox;
17
+ /** 人工确认。默认实现是 HitlHandler + require_confirmation。 */
18
+ export type IApproval = HitlHandler;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 执行口类型:不另起引擎,也不改现有调用路径。
3
+ * ISandbox 描述 jail 根目录;execute_command 仍走 harness jail,未经过本接口。
4
+ * IApproval 即 HitlHandler;确认仍走 require_confirmation。
5
+ * OS 级沙箱见 TODO-skill-fs FS-2。
6
+ */
7
+ export function sandboxFromJail(jail) {
8
+ const extra = jail.extraRoots ?? [];
9
+ return {
10
+ allowedWriteRoots: [jail.writeRoot],
11
+ allowedReadRoots: [jail.writeRoot, ...extra],
12
+ };
13
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * 权限拒绝审计。未注入则只抛错(A-4)。上报失败不挡执行。
3
+ */
4
+ export interface PermissionDenialAudit {
5
+ kind: 'tool' | 'skill';
6
+ resourceId: string;
7
+ reason: string;
8
+ userId?: string | null;
9
+ correlationId?: string;
10
+ }
11
+ export interface IAuditReporter {
12
+ recordDenial(event: PermissionDenialAudit): Promise<void>;
13
+ }
14
+ export declare function reportPermissionDenial(reporter: IAuditReporter | undefined, event: PermissionDenialAudit): Promise<void>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * 权限拒绝审计。未注入则只抛错(A-4)。上报失败不挡执行。
3
+ */
4
+ import { emitRuntimeLog } from '../common/logger.js';
5
+ export async function reportPermissionDenial(reporter, event) {
6
+ if (!reporter)
7
+ return;
8
+ try {
9
+ await reporter.recordDenial(event);
10
+ }
11
+ catch (err) {
12
+ emitRuntimeLog('warn', 'runtime.audit', 'permission denial audit failed', {
13
+ event: 'runtime.audit.denial_failed',
14
+ resourceId: event.resourceId,
15
+ error: err instanceof Error ? err.message : String(err),
16
+ });
17
+ }
18
+ }
@@ -5,3 +5,5 @@ export type { CallerType, ExecutionContext, GovernancePermissionCheckParams, Gov
5
5
  export type { IGovernanceClient } from './governance-client.js';
6
6
  export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, } from './governance-client.js';
7
7
  export { assertToolPermission, assertSkillPermission, permissionMessage, PermissionDeniedError, GovernanceUnavailableError, } from './permission-guard.js';
8
+ export type { IAuditReporter, PermissionDenialAudit } from './audit-reporter.js';
9
+ export { reportPermissionDenial } from './audit-reporter.js';
@@ -3,3 +3,4 @@
3
3
  */
4
4
  export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, } from './governance-client.js';
5
5
  export { assertToolPermission, assertSkillPermission, permissionMessage, PermissionDeniedError, GovernanceUnavailableError, } from './permission-guard.js';
6
+ export { reportPermissionDenial } from './audit-reporter.js';