@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
@@ -9,10 +9,15 @@
9
9
  */
10
10
  import { StateGraph, START, END, } from '@langchain/langgraph';
11
11
  import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
12
+ import { checkpointStorageThreadId } from './langgraph-checkpointer.js';
12
13
  import { LangGraphCheckpointer } from './langgraph-checkpointer.js';
13
14
  import { SubGraphCompiler } from './subgraph-compiler.js';
15
+ import { assertEmbeddedSubgraphsAreFlat } from './graph-nesting.js';
16
+ import { isSkillContextIsolated } from './skill-isolate.js';
14
17
  import { LLMNode } from '../nodes/llm-node.js';
15
18
  import { ToolNode } from '../nodes/tool-node.js';
19
+ import { RagNode, RAG_SEARCH_TOOL_NAME, RagSearchToolExecutor } from '../nodes/rag/index.js';
20
+ import { resolveRagSources, appendSourceCatalog } from '../nodes/rag/tools.js';
16
21
  import { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
17
22
  import { ToolResultInjector } from '../../tool/tool-result-injector.js';
18
23
  import { fileArtifactFromToolOutput } from '../../tool/file-artifact.js';
@@ -33,12 +38,31 @@ import { classifySkillRead } from './skill-read.js';
33
38
  import { skillEntryInvocationFromTool } from './skill-entry-span.js';
34
39
  import { withGraphSpan } from '../trace-span.js';
35
40
  import { executionTraceFromState, getExecutionStream, runWithExecutionTrace, } from '../execution-trace-context.js';
41
+ import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
36
42
  /** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
37
43
  export const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
38
44
  /**
39
45
  * LangGraph `recursionLimit` 计 superstep(每进一个节点 +1),不是 ReAct 圈数。
40
46
  * 图上 `config.maxIterations` 优先;缺省 {@link DEFAULT_GRAPH_RECURSION_LIMIT}。
41
47
  */
48
+ /** 并行节点各自返回 [id] 时合并进同一路径;末项相同不重复。 */
49
+ export function mergeHistoryPath(current, update) {
50
+ const path = Array.isArray(current)
51
+ ? current.filter((id) => typeof id === 'string' && id.trim().length > 0)
52
+ : [];
53
+ const add = Array.isArray(update) ? update : [];
54
+ for (const raw of add) {
55
+ if (typeof raw !== 'string')
56
+ continue;
57
+ const id = raw.trim();
58
+ if (!id)
59
+ continue;
60
+ if (path[path.length - 1] === id)
61
+ continue;
62
+ path.push(id);
63
+ }
64
+ return path.length > 1000 ? path.slice(path.length - 1000) : path;
65
+ }
42
66
  function langGraphRunOptions(dsl, extra) {
43
67
  const raw = dsl.config?.maxIterations;
44
68
  const recursionLimit = typeof raw === 'number' && Number.isFinite(raw) && raw > 0
@@ -61,6 +85,7 @@ function langGraphRunOptions(dsl, extra) {
61
85
  * 3. 提供 Checkpoint 提取与恢复能力
62
86
  */
63
87
  export class LangGraphAdapter {
88
+ auditReporter;
64
89
  checkpointer;
65
90
  subGraphCompiler;
66
91
  llmProvider;
@@ -77,17 +102,29 @@ export class LangGraphAdapter {
77
102
  */
78
103
  governanceClient;
79
104
  hitlHandler;
80
- constructor(checkpointSaver, llmProvider, toolExecutor, governanceClient, hitlHandler) {
105
+ ragNodeDeps;
106
+ interceptors;
107
+ /** 开发 overlay 根;createDevRuntime 在恢复 process.env 之前钉死 */
108
+ overlayExtraRoots = [];
109
+ constructor(checkpointSaver, llmProvider, toolExecutor, governanceClient, hitlHandler, ragNodeDeps, interceptors, auditReporter) {
110
+ this.auditReporter = auditReporter;
81
111
  this.governanceClient = governanceClient;
82
112
  this.hitlHandler = hitlHandler;
113
+ this.ragNodeDeps = ragNodeDeps;
114
+ this.interceptors = interceptors;
83
115
  this.checkpointer = checkpointSaver
84
116
  ? new LangGraphCheckpointer(checkpointSaver)
85
117
  : undefined;
86
118
  this.llmProvider = llmProvider ?? new EchoLLMProvider();
87
119
  this.toolExecutor = toolExecutor;
88
120
  this.toolOrchestrator = toolExecutor
89
- ? new ToolOrchestrator(toolExecutor, governanceClient)
121
+ ? new ToolOrchestrator(toolExecutor, governanceClient, this.auditReporter)
90
122
  : undefined;
123
+ // rag_search 装饰器注入同款编排器(内层 kb 派发与 tool 节点同一链路;
124
+ // 装饰器 deps 已显式提供 orchestrator 时不覆盖)
125
+ if (toolExecutor instanceof RagSearchToolExecutor && this.toolOrchestrator) {
126
+ toolExecutor.attachToolOrchestrator(this.toolOrchestrator);
127
+ }
91
128
  this.toolResultInjector = new ToolResultInjector();
92
129
  this.conditionEvaluator = new ConditionEvaluator();
93
130
  this.systemPromptBuilder = new SystemPromptBuilder();
@@ -125,7 +162,7 @@ export class LangGraphAdapter {
125
162
  return 'none';
126
163
  });
127
164
  // 必须在其他依赖初始化后再创建 SubGraphCompiler(它持有 adapter 引用)
128
- this.subGraphCompiler = new SubGraphCompiler(this, governanceClient, this.skillAuditRecorder);
165
+ this.subGraphCompiler = new SubGraphCompiler(this, governanceClient, this.skillAuditRecorder, this.auditReporter);
129
166
  }
130
167
  /**
131
168
  * 注册自定义条件评估函数
@@ -133,6 +170,10 @@ export class LangGraphAdapter {
133
170
  registerConditionEvaluator(name, fn) {
134
171
  this.conditionEvaluator.register(name, fn);
135
172
  }
173
+ /** SDK 恢复 env 之后仍要把 overlay 算进 LLM jail 根,不能再读 process.env。 */
174
+ bindOverlayExtraRoots(roots) {
175
+ this.overlayExtraRoots = (roots ?? []).filter((root) => root.trim());
176
+ }
136
177
  /**
137
178
  * 节点旁路推送 RuntimeEvent(子图 Skill 生命周期、Tool 执行)。
138
179
  * 无本次执行流式袋时为空操作(纯 invoke)。
@@ -290,7 +331,7 @@ export class LangGraphAdapter {
290
331
  toolName: progress.toolName,
291
332
  ...(progress.toolset ? { toolset: progress.toolset } : {}),
292
333
  error: progress.error ?? 'Unknown error',
293
- willRetry: false,
334
+ willRetry: progress.willRetry === true,
294
335
  ...loc,
295
336
  },
296
337
  });
@@ -312,6 +353,7 @@ export class LangGraphAdapter {
312
353
  */
313
354
  async buildGraph(dsl) {
314
355
  this.assertShouldContinueTargetsTool(dsl);
356
+ assertEmbeddedSubgraphsAreFlat(dsl);
315
357
  const silentNodeIds = new Set(dsl.nodes
316
358
  .filter((node) => node.type === 'llm' && node.config?.['silent'] === true)
317
359
  .map((node) => node.id));
@@ -342,6 +384,10 @@ export class LangGraphAdapter {
342
384
  value: (_current, update) => update,
343
385
  default: () => '',
344
386
  },
387
+ historyPath: {
388
+ value: (current, update) => mergeHistoryPath(current, update),
389
+ default: () => [],
390
+ },
345
391
  // ── 运行期上下文(ephemeral):由 invoke/stream 入口注入,节点只读 ──
346
392
  userId: ephemeral(''),
347
393
  // agentId 已废弃:channel 保留仅为旧 Checkpoint 反序列化兼容,新代码只写 agentCode
@@ -352,6 +398,8 @@ export class LangGraphAdapter {
352
398
  correlationId: ephemeral(''),
353
399
  taskId: ephemeral(''),
354
400
  sessionId: ephemeral(''),
401
+ checkpointNs: ephemeral(undefined),
402
+ skillIsolateContext: ephemeral(undefined),
355
403
  loadedTools: ephemeral(undefined),
356
404
  loadedSkills: ephemeral(undefined),
357
405
  skillDefinitions: ephemeral(undefined),
@@ -364,9 +412,15 @@ export class LangGraphAdapter {
364
412
  agentDir: ephemeral(undefined),
365
413
  executionContext: ephemeral(undefined),
366
414
  forceToolServer: ephemeral(undefined),
415
+ // 请求级检索范围(前端圈定 fileIds/dirPaths;rag 节点读取,仅本地检索源生效)
416
+ ragScope: ephemeral(undefined),
417
+ // 会话级数据源勾选(数据源 id 列表,undefined=全部启用;rag 节点读取)
418
+ ragSources: ephemeral(undefined),
367
419
  traceCollector: ephemeral(undefined),
368
420
  traceId: ephemeral(undefined),
369
421
  skillSpanId: ephemeral(undefined),
422
+ toolSpanId: ephemeral(undefined),
423
+ hitlToolCallId: ephemeral(undefined),
370
424
  lastSpanId: ephemeral(undefined),
371
425
  hitlChoice: ephemeral(undefined),
372
426
  // ── LLM Token 使用量:数组累加器 reducer,每次 LLM 节点追加一条使用明细
@@ -378,7 +432,6 @@ export class LangGraphAdapter {
378
432
  },
379
433
  default: () => [],
380
434
  },
381
- __parallelResults: ephemeral(undefined),
382
435
  __codeResult: ephemeral(undefined),
383
436
  __codeError: ephemeral(undefined),
384
437
  __conditionResult: ephemeral(undefined),
@@ -450,7 +503,7 @@ export class LangGraphAdapter {
450
503
  */
451
504
  async *stream(graph, state) {
452
505
  const ref = this.getCompiledRef(graph);
453
- const input = this.graphStateToLangGraphInput(state);
506
+ const input = this.graphStateToLangGraphInput(this.applyDeclaredSearchFace(state, ref.dsl));
454
507
  const queue = [];
455
508
  const waiter = { fn: null };
456
509
  let finished = false;
@@ -482,13 +535,7 @@ export class LangGraphAdapter {
482
535
  const producer = runWithExecutionTrace({ ...traceCtx, stream: streamBag }, () => (async () => {
483
536
  try {
484
537
  const streamIterator = await ref.compiled.stream(input, langGraphRunOptions(ref.dsl, {
485
- configurable: {
486
- thread_id: resolveCheckpointThreadId({
487
- sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
488
- taskId: state.taskId,
489
- graphId: graph.id,
490
- }),
491
- },
538
+ configurable: this.checkpointConfigurable(state, graph),
492
539
  streamMode: 'updates',
493
540
  }));
494
541
  for await (const update of streamIterator) {
@@ -523,18 +570,12 @@ export class LangGraphAdapter {
523
570
  */
524
571
  async invoke(graph, state) {
525
572
  const ref = this.getCompiledRef(graph);
526
- const input = this.graphStateToLangGraphInput(state);
573
+ const input = this.graphStateToLangGraphInput(this.applyDeclaredSearchFace(state, ref.dsl));
527
574
  const startTime = Date.now();
528
575
  const traceCtx = executionTraceFromState(state);
529
576
  // 只补 collector/traceId。子图套娃时外层 stream 袋经 ?? 保留。
530
577
  const result = await runWithExecutionTrace(traceCtx, () => ref.compiled.invoke(input, langGraphRunOptions(ref.dsl, {
531
- configurable: {
532
- thread_id: resolveCheckpointThreadId({
533
- sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
534
- taskId: state.taskId,
535
- graphId: graph.id,
536
- }),
537
- },
578
+ configurable: this.checkpointConfigurable(state, graph),
538
579
  })));
539
580
  const duration = Date.now() - startTime;
540
581
  const messages = Array.isArray(result['messages']) ? result['messages'] : [];
@@ -627,10 +668,35 @@ export class LangGraphAdapter {
627
668
  // ─────────────────────────────────────────────
628
669
  // Private helpers
629
670
  // ─────────────────────────────────────────────
671
+ checkpointConfigurable(state, graph) {
672
+ const thread_id = resolveCheckpointThreadId({
673
+ sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
674
+ taskId: state.taskId,
675
+ graphId: graph.id,
676
+ });
677
+ const ns = typeof state.checkpointNs === 'string' ? state.checkpointNs.trim() : '';
678
+ // 子图是独立 compile 再 invoke,LangGraph put 不一定带回 checkpoint_ns;槽位编进 thread_id。
679
+ return { thread_id: checkpointStorageThreadId(thread_id, ns) };
680
+ }
630
681
  /**
631
682
  * 根据节点类型创建对应的执行函数
632
683
  */
633
684
  createNodeFunction(node) {
685
+ const impl = this.createNodeFunctionBody(node);
686
+ return async (state) => {
687
+ const update = await impl(state);
688
+ const patch = update && typeof update === 'object' ? { ...update } : {};
689
+ const currentNode = typeof patch['currentNode'] === 'string' && String(patch['currentNode']).trim()
690
+ ? String(patch['currentNode']).trim()
691
+ : node.id;
692
+ if (!Array.isArray(patch['historyPath'])) {
693
+ patch['historyPath'] = [currentNode];
694
+ }
695
+ patch['currentNode'] = currentNode;
696
+ return patch;
697
+ };
698
+ }
699
+ createNodeFunctionBody(node) {
634
700
  switch (node.type) {
635
701
  case 'llm':
636
702
  return this.createLLMNodeFunction(node);
@@ -651,23 +717,26 @@ export class LangGraphAdapter {
651
717
  case 'subgraph':
652
718
  return this.createSubGraphNodeFunction(node);
653
719
  case 'human':
654
- return async (state) => {
655
- return { currentNode: node.id };
656
- };
657
- case 'parallel':
658
- return this.createParallelNodeFunction(node);
720
+ throw new AppError(ErrorCodes.GRAPH_BUILD_FAILED, `Graph 节点 "${node.id}" type=human 未实现;人工确认请用 require_confirmation`, { exposeMessage: true });
659
721
  case 'code':
660
722
  if (node.config?.['bindSkill'] === true) {
661
723
  return this.createSkillBindNodeFunction(node);
662
724
  }
663
725
  return this.createCodeNodeFunction(node);
664
- default:
726
+ case 'rag':
727
+ return this.createRagNodeFunction(node);
728
+ default: {
665
729
  if (node.config?.['bindSkill'] === true) {
666
730
  return this.createSkillBindNodeFunction(node);
667
731
  }
668
- return async (state) => {
669
- return { ...state, currentNode: node.id };
670
- };
732
+ const type = String(node.type);
733
+ if (type === 'start' || type === 'end') {
734
+ return async (state) => {
735
+ return { ...state, currentNode: node.id };
736
+ };
737
+ }
738
+ throw new AppError(ErrorCodes.GRAPH_BUILD_FAILED, `Graph 节点 "${node.id}" type=${type} 不是合法类型`, { exposeMessage: true });
739
+ }
671
740
  }
672
741
  }
673
742
  createSkillBindNodeFunction(node) {
@@ -744,7 +813,7 @@ export class LangGraphAdapter {
744
813
  userId: 'system',
745
814
  delegationToken: '',
746
815
  agentCode: '',
747
- }, (progress) => this.emitToolExecutionEvents(progress), this.hitlHandler, node.id);
816
+ }, (progress) => this.emitToolExecutionEvents(progress), this.hitlHandler, node.id, this.interceptors, node.config?.['retrievalSources'], node.config?.['totalK'], node.retry);
748
817
  return (state) => toolNode.execute(state);
749
818
  }
750
819
  if (this.toolExecutor) {
@@ -767,7 +836,7 @@ export class LangGraphAdapter {
767
836
  const jailExtraRoots = Object.values(skillDefs ?? {})
768
837
  .map((skill) => skill.bundlePath)
769
838
  .filter((p) => Boolean(p));
770
- const result = await this.toolExecutor.execute({
839
+ const request = {
771
840
  toolCallId,
772
841
  toolName,
773
842
  arguments: defaultArgs,
@@ -775,7 +844,25 @@ export class LangGraphAdapter {
775
844
  correlationId,
776
845
  taskId,
777
846
  ...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
778
- });
847
+ };
848
+ const retry = resolveToolRetryConfig(node.retry);
849
+ const maxRetries = retry?.maxRetries ?? 0;
850
+ let result = await this.toolExecutor.execute(request);
851
+ for (let attempt = 0; !result.success && attempt < maxRetries; attempt++) {
852
+ if (!isRetryableToolFailure(result))
853
+ break;
854
+ this.emitToolExecutionEvents({
855
+ toolCallId: result.toolCallId,
856
+ toolName: result.toolName,
857
+ status: 'error',
858
+ arguments: defaultArgs,
859
+ error: result.error,
860
+ willRetry: true,
861
+ node: node.id,
862
+ });
863
+ await sleepToolRetry(toolRetryDelayMs(attempt, retry.backoff));
864
+ result = await this.toolExecutor.execute(request);
865
+ }
779
866
  const executionTime = Date.now() - startedAt;
780
867
  const skillDirs = Object.values(skillDefs ?? {})
781
868
  .filter((skill) => Boolean(skill.bundlePath))
@@ -787,6 +874,7 @@ export class LangGraphAdapter {
787
874
  arguments: defaultArgs,
788
875
  content: result.content,
789
876
  error: result.error,
877
+ willRetry: false,
790
878
  executionTime,
791
879
  node: node.id,
792
880
  ...(skillDirs.length > 0 ? { skillDirs } : {}),
@@ -891,6 +979,9 @@ export class LangGraphAdapter {
891
979
  loadedTools,
892
980
  skillDefinitions: skillDefs,
893
981
  });
982
+ if (this.overlayExtraRoots.length > 0) {
983
+ jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
984
+ }
894
985
  const workingDirectory = jail.workdir;
895
986
  const jailRoots = resolveJailRoots(jail);
896
987
  const skillDirs = Object.values(skillDefs ?? {})
@@ -972,7 +1063,7 @@ export class LangGraphAdapter {
972
1063
  },
973
1064
  });
974
1065
  }
975
- : undefined);
1066
+ : undefined, this.interceptors);
976
1067
  const next = await llmNode.execute(state);
977
1068
  const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
978
1069
  if (!silent)
@@ -990,6 +1081,31 @@ export class LangGraphAdapter {
990
1081
  });
991
1082
  };
992
1083
  }
1084
+ /**
1085
+ * RAG 节点:编译期 fail-fast(deps 缺失),请求级状态经 execute(state) 流转
1086
+ */
1087
+ createRagNodeFunction(node) {
1088
+ if (!this.ragNodeDeps) {
1089
+ throw new Error(`图使用了 type:rag 节点 "${node.id}",但宿主未注入 ragNodeDeps(LangGraphAdapter 第 6 参)`);
1090
+ }
1091
+ const ragNode = new RagNode(node.id, (node.config ?? {}), {
1092
+ ...this.ragNodeDeps,
1093
+ // 未显式提供时复用 adapter 已组装的 orchestrator(§9.3:toolExecutor+governance 透传)
1094
+ toolOrchestrator: this.ragNodeDeps.toolOrchestrator ?? this.toolOrchestrator,
1095
+ // rag.* 事件接 SSE 旁路(请求级执行流存活期间有效;宿主/测试注入的 emitEvent 优先)
1096
+ emitEvent: this.ragNodeDeps.emitEvent ??
1097
+ ((type, payload) => {
1098
+ this.emitStreamEvent({
1099
+ type,
1100
+ taskId: undefined,
1101
+ correlationId: undefined,
1102
+ timestamp: new Date().toISOString(),
1103
+ payload,
1104
+ });
1105
+ }),
1106
+ });
1107
+ return (state) => ragNode.execute(state);
1108
+ }
993
1109
  /**
994
1110
  * 子图节点:优先 config.subgraphDsl;否则按 skillId 从 state.skillDefinitions.workflow 解析。
995
1111
  * 技能正文用短目录 + read_file,不灌 system。
@@ -1000,7 +1116,10 @@ export class LangGraphAdapter {
1000
1116
  node.config?.['skillId'] ??
1001
1117
  embeddedDsl?.name;
1002
1118
  if (embeddedDsl) {
1003
- return this.subGraphCompiler.compileSubGraph(embeddedDsl, configuredSkillId);
1119
+ return this.subGraphCompiler.compileSubGraph(embeddedDsl, configuredSkillId, {
1120
+ isolateContext: isSkillContextIsolated(node),
1121
+ parentNodeId: node.id,
1122
+ });
1004
1123
  }
1005
1124
  return async (state) => {
1006
1125
  const skillId = configuredSkillId ??
@@ -1011,70 +1130,13 @@ export class LangGraphAdapter {
1011
1130
  if (!workflow?.nodes?.length) {
1012
1131
  throw preloadSkillNotFound(`子图节点无法解析 Skill workflow: node=${node.id}, skillId=${skillId ?? '(none)'}`, { nodeId: node.id, skillId });
1013
1132
  }
1014
- const fn = this.subGraphCompiler.compileSubGraph(workflow, skillId ?? def?.skillId);
1133
+ const fn = this.subGraphCompiler.compileSubGraph(workflow, skillId ?? def?.skillId, {
1134
+ isolateContext: isSkillContextIsolated(node),
1135
+ parentNodeId: node.id,
1136
+ });
1015
1137
  return fn(state);
1016
1138
  };
1017
1139
  }
1018
- /**
1019
- * 并行节点:并行执行多个子任务分支
1020
- *
1021
- * 支持两种并行模式:
1022
- * 1. branches 模式:node.branches 列出要并行执行的节点 ID,每个节点的执行函数由 DSL 定义
1023
- * 2. tasks 模式:node.config.tasks 列出要并行执行的异步任务函数
1024
- *
1025
- * 执行结果合并到 state.__parallelResults 中。
1026
- */
1027
- createParallelNodeFunction(node) {
1028
- return async (state) => {
1029
- const branchNodeIds = node.branches ?? node.config?.['branches'] ?? [];
1030
- const tasks = node.config?.['tasks'];
1031
- const results = [];
1032
- if (tasks?.length) {
1033
- // tasks 模式:并行执行配置的任务
1034
- const promises = tasks.map(async (task) => {
1035
- try {
1036
- // 任务结果由外部注入的 task executor 处理
1037
- // 此处仅记录任务 ID,实际执行依赖外部编排
1038
- return { branchId: task.id, success: true, result: { taskId: task.id, status: 'dispatched' } };
1039
- }
1040
- catch (err) {
1041
- return {
1042
- branchId: task.id,
1043
- success: false,
1044
- error: err instanceof Error ? err.message : String(err),
1045
- };
1046
- }
1047
- });
1048
- results.push(...await Promise.all(promises));
1049
- }
1050
- else if (branchNodeIds.length) {
1051
- // branches 模式:并行执行分支节点
1052
- const promises = branchNodeIds.map(async (branchId) => {
1053
- try {
1054
- // 每个分支节点的实际执行由 LangGraph 编排层驱动
1055
- // 并行节点仅负责触发并汇总状态
1056
- return { branchId, success: true, result: { dispatched: true } };
1057
- }
1058
- catch (err) {
1059
- return {
1060
- branchId,
1061
- success: false,
1062
- error: err instanceof Error ? err.message : String(err),
1063
- };
1064
- }
1065
- });
1066
- results.push(...await Promise.all(promises));
1067
- }
1068
- const allSuccess = results.every((r) => r.success);
1069
- return {
1070
- ...state,
1071
- currentNode: node.id,
1072
- __parallelResults: results,
1073
- __parallelAllSuccess: allSuccess,
1074
- __conditionResult: allSuccess ? 'success' : 'partial_failure',
1075
- };
1076
- };
1077
- }
1078
1140
  /**
1079
1141
  * 代码执行节点:在受限沙箱中执行用户代码
1080
1142
  *
@@ -1250,6 +1312,41 @@ export class LangGraphAdapter {
1250
1312
  * 否则多轮回放时 DeepSeek thinking 会因缺少 reasoning_content 而 400,
1251
1313
  * tool 消息也会丢掉 tool_call_id。
1252
1314
  */
1315
+ /**
1316
+ * 工具形态检索面白名单(图声明即真相):tool 节点 config.retrievalSources 声明的
1317
+ * 数据源目录 → 本图工具白名单(声明源解析出的工具 + rag_search)。未声明的检索类
1318
+ * 工具对主 LLM 工具表与执行链整体不可见/不可用;图未声明 = 不收敓名单(通用形态)。
1319
+ *
1320
+ * 约束——单检索面假设:find 只取全图首个带 retrievalSources 的 tool 节点、按它过滤
1321
+ * 全图 loadedTools。多 tool 节点各声明检索面属未支持场景:第二面的工具会被当成
1322
+ * 未声明而过滤,无法各自收敛;如需支持须改为 per-node 收敛(在节点边界分别过滤
1323
+ * loadedTools,而非此处全图一次性收敛)。
1324
+ */
1325
+ applyDeclaredSearchFace(state, dsl) {
1326
+ const face = dsl?.nodes?.find((n) => n.type === 'tool' && n.config?.['retrievalSources']);
1327
+ const declaration = face?.config?.['retrievalSources'];
1328
+ const loaded = state.loadedTools;
1329
+ if (!declaration || !loaded || typeof loaded !== 'object')
1330
+ return state;
1331
+ const resolved = resolveRagSources({ retrievalSources: declaration });
1332
+ const allowed = new Set([...(resolved.toolScope ?? []), RAG_SEARCH_TOOL_NAME]);
1333
+ const filtered = {};
1334
+ for (const [name, def] of Object.entries(loaded)) {
1335
+ if (!allowed.has(name))
1336
+ continue;
1337
+ // 数据源目录写进保留工具的 description(幂等)——主 LLM 直调时与 rag 内核
1338
+ // 同一份目录,杜绝瞎猜 datasetCode;内核组表侧 appendSourceCatalog 幂等不重复
1339
+ const catalog = resolved.sourceCatalog[name];
1340
+ if (catalog?.length) {
1341
+ const desc = appendSourceCatalog(def.manifest.description, catalog);
1342
+ filtered[name] = { ...def, manifest: { ...def.manifest, description: desc } };
1343
+ }
1344
+ else {
1345
+ filtered[name] = def;
1346
+ }
1347
+ }
1348
+ return { ...state, loadedTools: filtered };
1349
+ }
1253
1350
  graphStateToLangGraphInput(state) {
1254
1351
  const mapped = (state.messages ?? []).map((msg) => ({
1255
1352
  role: msg.role,
@@ -1306,7 +1403,10 @@ export class LangGraphAdapter {
1306
1403
  if (msg.role === 'assistant' && Array.isArray(msg.toolCalls) && msg.toolCalls.length > 0) {
1307
1404
  // tool.called 由 Tool 节点在真正执行时发出,此处不再把 LLM function-call 当执行开始
1308
1405
  // 子图节点的 updates 是内层已 stream 过的回写,不要再发空 reasoning chunk
1309
- if (subgraphNodeIds.has(nodeId) || silentNodeIds.has(nodeId)) {
1406
+ if (silentNodeIds.has(nodeId)) {
1407
+ continue;
1408
+ }
1409
+ if (subgraphNodeIds.has(nodeId) && streamedTextThisTurn) {
1310
1410
  continue;
1311
1411
  }
1312
1412
  const hasText = typeof msg.content === 'string' && msg.content.trim().length > 0;
@@ -1333,7 +1433,9 @@ export class LangGraphAdapter {
1333
1433
  continue;
1334
1434
  }
1335
1435
  if (msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.length > 0) {
1336
- if (silentNodeIds.has(nodeId) || subgraphNodeIds.has(nodeId))
1436
+ if (silentNodeIds.has(nodeId))
1437
+ continue;
1438
+ if (subgraphNodeIds.has(nodeId) && streamedTextThisTurn)
1337
1439
  continue;
1338
1440
  // 已通过 onChunk 推送过 token,避免整段重复
1339
1441
  if (streamedTextThisTurn) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
3
+ * 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
4
+ * 子图 Checkpoint / HITL 走 `sessionId#父节点id`,不写父 latest。
5
+ */
6
+ import type { GraphMessage } from '../contracts/executable-graph.js';
7
+ import type { GraphNode } from '../contracts/executable-graph.js';
8
+ /** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
9
+ export declare function isSkillContextIsolated(node: GraphNode | undefined): boolean;
10
+ export declare function isolatedSkillMessages(parentMessages: GraphMessage[], skillInput: unknown): GraphMessage[];
11
+ /** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
12
+ export declare function isolatedSkillOutput(added: GraphMessage[]): GraphMessage[];
13
+ export declare function isIsolatedSkillState(state: Record<string, unknown> | undefined): boolean;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
3
+ * 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
4
+ * 子图 Checkpoint / HITL 走 `sessionId#父节点id`,不写父 latest。
5
+ */
6
+ /** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
7
+ export function isSkillContextIsolated(node) {
8
+ return node?.config?.['isolateContext'] === true;
9
+ }
10
+ export function isolatedSkillMessages(parentMessages, skillInput) {
11
+ let text = '';
12
+ if (typeof skillInput === 'string' && skillInput.trim()) {
13
+ text = skillInput.trim();
14
+ }
15
+ else if (skillInput && typeof skillInput === 'object' && 'text' in skillInput) {
16
+ text = String(skillInput.text ?? '').trim();
17
+ }
18
+ if (!text) {
19
+ for (let i = parentMessages.length - 1; i >= 0; i--) {
20
+ if (parentMessages[i]?.role === 'user' && parentMessages[i]?.content) {
21
+ text = parentMessages[i].content;
22
+ break;
23
+ }
24
+ }
25
+ }
26
+ return [{ role: 'user', content: text || '' }];
27
+ }
28
+ /** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
29
+ export function isolatedSkillOutput(added) {
30
+ for (let i = added.length - 1; i >= 0; i--) {
31
+ if (added[i]?.role === 'assistant' && added[i]?.content) {
32
+ return [{ role: 'assistant', content: added[i].content }];
33
+ }
34
+ }
35
+ return [];
36
+ }
37
+ export function isIsolatedSkillState(state) {
38
+ return state?.['skillIsolateContext'] === true;
39
+ }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * 将 Skill DSL 编译为子图节点函数,嵌入父 Graph 执行。
5
5
  * 子图作为独立 StateGraph 编译,父图通过节点函数调用子图并合并输出。
6
+ * Skill 子图不得再含 type=subgraph(G-3 / B14)。
6
7
  *
7
8
  * @see design.md §1.4 子图嵌套: Skill作为子图嵌入主Graph
8
9
  * @see requirements.md Requirement 1 AC2 — 子图嵌套(Skill作为子图)
@@ -24,16 +25,20 @@ type LangGraphState = Record<string, unknown>;
24
25
  * 4. 执行时,子图内部运行完整的 graph invoke 流程,输出合并回父图 state
25
26
  */
26
27
  export declare class SubGraphCompiler {
28
+ private readonly auditReporter?;
27
29
  private readonly adapter;
28
30
  private readonly governanceClient?;
29
31
  private readonly skillAuditRecorder?;
30
- constructor(adapter?: LangGraphAdapter, governanceClient?: IGovernanceClient, skillAuditRecorder?: SkillAuditRecorder);
32
+ constructor(adapter?: LangGraphAdapter, governanceClient?: IGovernanceClient, skillAuditRecorder?: SkillAuditRecorder, auditReporter?: import("../../governance/audit-reporter.js").IAuditReporter | undefined);
31
33
  /**
32
34
  * 将 Skill DSL 编译为可嵌入父图的 NodeFunction
33
35
  *
34
36
  * @param dsl - Skill 的 GraphDSL 定义
35
37
  * @param skillId - Skill 资源 ID(用于 checkPermission skill:use)
36
38
  */
37
- compileSubGraph(dsl: GraphDSL, skillId?: string): NodeFunction<LangGraphState>;
39
+ compileSubGraph(dsl: GraphDSL, skillId?: string, options?: {
40
+ isolateContext?: boolean;
41
+ parentNodeId?: string;
42
+ }): NodeFunction<LangGraphState>;
38
43
  }
39
44
  export {};