@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
@@ -11,8 +11,12 @@ import { StateGraph, START, END, } from '@langchain/langgraph';
11
11
  import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
12
12
  import { LangGraphCheckpointer } from './langgraph-checkpointer.js';
13
13
  import { SubGraphCompiler } from './subgraph-compiler.js';
14
+ import { assertEmbeddedSubgraphsAreFlat } from './graph-nesting.js';
15
+ import { isSkillContextIsolated } from './skill-isolate.js';
14
16
  import { LLMNode } from '../nodes/llm-node.js';
15
17
  import { ToolNode } from '../nodes/tool-node.js';
18
+ import { RagNode, RAG_SEARCH_TOOL_NAME, RagSearchToolExecutor } from '../nodes/rag/index.js';
19
+ import { resolveRagSources, appendSourceCatalog } from '../nodes/rag/tools.js';
16
20
  import { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
17
21
  import { ToolResultInjector } from '../../tool/tool-result-injector.js';
18
22
  import { fileArtifactFromToolOutput } from '../../tool/file-artifact.js';
@@ -30,8 +34,10 @@ import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
30
34
  import { pickContextFields } from '../../token/context-window.js';
31
35
  import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
32
36
  import { classifySkillRead } from './skill-read.js';
37
+ import { skillEntryInvocationFromTool } from './skill-entry-span.js';
33
38
  import { withGraphSpan } from '../trace-span.js';
34
39
  import { executionTraceFromState, getExecutionStream, runWithExecutionTrace, } from '../execution-trace-context.js';
40
+ import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
35
41
  /** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
36
42
  export const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
37
43
  /**
@@ -60,6 +66,7 @@ function langGraphRunOptions(dsl, extra) {
60
66
  * 3. 提供 Checkpoint 提取与恢复能力
61
67
  */
62
68
  export class LangGraphAdapter {
69
+ auditReporter;
63
70
  checkpointer;
64
71
  subGraphCompiler;
65
72
  llmProvider;
@@ -76,17 +83,29 @@ export class LangGraphAdapter {
76
83
  */
77
84
  governanceClient;
78
85
  hitlHandler;
79
- constructor(checkpointSaver, llmProvider, toolExecutor, governanceClient, hitlHandler) {
86
+ ragNodeDeps;
87
+ interceptors;
88
+ /** 开发 overlay 根;createDevRuntime 在恢复 process.env 之前钉死 */
89
+ overlayExtraRoots = [];
90
+ constructor(checkpointSaver, llmProvider, toolExecutor, governanceClient, hitlHandler, ragNodeDeps, interceptors, auditReporter) {
91
+ this.auditReporter = auditReporter;
80
92
  this.governanceClient = governanceClient;
81
93
  this.hitlHandler = hitlHandler;
94
+ this.ragNodeDeps = ragNodeDeps;
95
+ this.interceptors = interceptors;
82
96
  this.checkpointer = checkpointSaver
83
97
  ? new LangGraphCheckpointer(checkpointSaver)
84
98
  : undefined;
85
99
  this.llmProvider = llmProvider ?? new EchoLLMProvider();
86
100
  this.toolExecutor = toolExecutor;
87
101
  this.toolOrchestrator = toolExecutor
88
- ? new ToolOrchestrator(toolExecutor, governanceClient)
102
+ ? new ToolOrchestrator(toolExecutor, governanceClient, this.auditReporter)
89
103
  : undefined;
104
+ // rag_search 装饰器注入同款编排器(内层 kb 派发与 tool 节点同一链路;
105
+ // 装饰器 deps 已显式提供 orchestrator 时不覆盖)
106
+ if (toolExecutor instanceof RagSearchToolExecutor && this.toolOrchestrator) {
107
+ toolExecutor.attachToolOrchestrator(this.toolOrchestrator);
108
+ }
90
109
  this.toolResultInjector = new ToolResultInjector();
91
110
  this.conditionEvaluator = new ConditionEvaluator();
92
111
  this.systemPromptBuilder = new SystemPromptBuilder();
@@ -124,7 +143,7 @@ export class LangGraphAdapter {
124
143
  return 'none';
125
144
  });
126
145
  // 必须在其他依赖初始化后再创建 SubGraphCompiler(它持有 adapter 引用)
127
- this.subGraphCompiler = new SubGraphCompiler(this, governanceClient, this.skillAuditRecorder);
146
+ this.subGraphCompiler = new SubGraphCompiler(this, governanceClient, this.skillAuditRecorder, this.auditReporter);
128
147
  }
129
148
  /**
130
149
  * 注册自定义条件评估函数
@@ -132,6 +151,10 @@ export class LangGraphAdapter {
132
151
  registerConditionEvaluator(name, fn) {
133
152
  this.conditionEvaluator.register(name, fn);
134
153
  }
154
+ /** SDK 恢复 env 之后仍要把 overlay 算进 LLM jail 根,不能再读 process.env。 */
155
+ bindOverlayExtraRoots(roots) {
156
+ this.overlayExtraRoots = (roots ?? []).filter((root) => root.trim());
157
+ }
135
158
  /**
136
159
  * 节点旁路推送 RuntimeEvent(子图 Skill 生命周期、Tool 执行)。
137
160
  * 无本次执行流式袋时为空操作(纯 invoke)。
@@ -171,6 +194,7 @@ export class LangGraphAdapter {
171
194
  * Tool 节点执行旁路:started → tool.called + tool.progress;
172
195
  * completed/error → 对应终态 + tool.progress。同一 toolCallId。
173
196
  * 读 SKILL.md / 技能包内文件:成功后再发 skill.loaded / skill.resource_read,同时保留 tool.*。
197
+ * 读 SKILL.md 另发 skill.started / skill.completed(与 Trace type=skill Span 对齐);子图内不重复。
174
198
  */
175
199
  emitToolExecutionEvents(progress) {
176
200
  const stream = getExecutionStream();
@@ -185,16 +209,48 @@ export class LangGraphAdapter {
185
209
  const skillRead = classifySkillRead(progress.toolName, progress.arguments, progress.skillDirs);
186
210
  if (skillRead && progress.status === 'completed') {
187
211
  if (skillRead.kind === 'loaded') {
212
+ const skillId = skillRead.skillId || loc.skillId || skillRead.path;
188
213
  stream.sink({
189
214
  type: 'skill.loaded',
190
215
  taskId,
191
216
  correlationId,
192
217
  timestamp,
193
218
  payload: {
194
- skillId: skillRead.skillId || loc.skillId || skillRead.path,
219
+ skillId,
195
220
  path: skillRead.path,
196
221
  },
197
222
  });
223
+ const catalogSkill = skillEntryInvocationFromTool({
224
+ toolName: progress.toolName,
225
+ arguments: progress.arguments,
226
+ skillDirs: progress.skillDirs,
227
+ alreadyInSkillSpan: progress.inSkillSubgraph,
228
+ });
229
+ if (catalogSkill) {
230
+ stream.sink({
231
+ type: 'skill.started',
232
+ taskId,
233
+ correlationId,
234
+ timestamp,
235
+ payload: {
236
+ skillId: catalogSkill.skillId,
237
+ skillName: progress.skillName || catalogSkill.skillId,
238
+ path: catalogSkill.path,
239
+ ...(progress.skillSpace ? { skillSpace: progress.skillSpace } : {}),
240
+ ...(progress.skillVersion ? { skillVersion: progress.skillVersion } : {}),
241
+ },
242
+ });
243
+ stream.sink({
244
+ type: 'skill.completed',
245
+ taskId,
246
+ correlationId,
247
+ timestamp,
248
+ payload: {
249
+ skillId: catalogSkill.skillId,
250
+ skillName: catalogSkill.skillId,
251
+ },
252
+ });
253
+ }
198
254
  }
199
255
  else {
200
256
  stream.sink({
@@ -216,6 +272,7 @@ export class LangGraphAdapter {
216
272
  toolCallId: progress.toolCallId,
217
273
  toolName: progress.toolName,
218
274
  ...(progress.toolset ? { toolset: progress.toolset } : {}),
275
+ ...(progress.toolVersion ? { toolVersion: progress.toolVersion } : {}),
219
276
  arguments: progress.arguments ?? {},
220
277
  ...loc,
221
278
  },
@@ -255,7 +312,7 @@ export class LangGraphAdapter {
255
312
  toolName: progress.toolName,
256
313
  ...(progress.toolset ? { toolset: progress.toolset } : {}),
257
314
  error: progress.error ?? 'Unknown error',
258
- willRetry: false,
315
+ willRetry: progress.willRetry === true,
259
316
  ...loc,
260
317
  },
261
318
  });
@@ -277,6 +334,7 @@ export class LangGraphAdapter {
277
334
  */
278
335
  async buildGraph(dsl) {
279
336
  this.assertShouldContinueTargetsTool(dsl);
337
+ assertEmbeddedSubgraphsAreFlat(dsl);
280
338
  const silentNodeIds = new Set(dsl.nodes
281
339
  .filter((node) => node.type === 'llm' && node.config?.['silent'] === true)
282
340
  .map((node) => node.id));
@@ -317,6 +375,7 @@ export class LangGraphAdapter {
317
375
  correlationId: ephemeral(''),
318
376
  taskId: ephemeral(''),
319
377
  sessionId: ephemeral(''),
378
+ skillIsolateContext: ephemeral(undefined),
320
379
  loadedTools: ephemeral(undefined),
321
380
  loadedSkills: ephemeral(undefined),
322
381
  skillDefinitions: ephemeral(undefined),
@@ -329,9 +388,15 @@ export class LangGraphAdapter {
329
388
  agentDir: ephemeral(undefined),
330
389
  executionContext: ephemeral(undefined),
331
390
  forceToolServer: ephemeral(undefined),
391
+ // 请求级检索范围(前端圈定 fileIds/dirPaths;rag 节点读取,仅本地检索源生效)
392
+ ragScope: ephemeral(undefined),
393
+ // 会话级数据源勾选(数据源 id 列表,undefined=全部启用;rag 节点读取)
394
+ ragSources: ephemeral(undefined),
332
395
  traceCollector: ephemeral(undefined),
333
396
  traceId: ephemeral(undefined),
334
397
  skillSpanId: ephemeral(undefined),
398
+ toolSpanId: ephemeral(undefined),
399
+ hitlToolCallId: ephemeral(undefined),
335
400
  lastSpanId: ephemeral(undefined),
336
401
  hitlChoice: ephemeral(undefined),
337
402
  // ── LLM Token 使用量:数组累加器 reducer,每次 LLM 节点追加一条使用明细
@@ -343,7 +408,6 @@ export class LangGraphAdapter {
343
408
  },
344
409
  default: () => [],
345
410
  },
346
- __parallelResults: ephemeral(undefined),
347
411
  __codeResult: ephemeral(undefined),
348
412
  __codeError: ephemeral(undefined),
349
413
  __conditionResult: ephemeral(undefined),
@@ -415,7 +479,7 @@ export class LangGraphAdapter {
415
479
  */
416
480
  async *stream(graph, state) {
417
481
  const ref = this.getCompiledRef(graph);
418
- const input = this.graphStateToLangGraphInput(state);
482
+ const input = this.graphStateToLangGraphInput(this.applyDeclaredSearchFace(state, ref.dsl));
419
483
  const queue = [];
420
484
  const waiter = { fn: null };
421
485
  let finished = false;
@@ -488,7 +552,7 @@ export class LangGraphAdapter {
488
552
  */
489
553
  async invoke(graph, state) {
490
554
  const ref = this.getCompiledRef(graph);
491
- const input = this.graphStateToLangGraphInput(state);
555
+ const input = this.graphStateToLangGraphInput(this.applyDeclaredSearchFace(state, ref.dsl));
492
556
  const startTime = Date.now();
493
557
  const traceCtx = executionTraceFromState(state);
494
558
  // 只补 collector/traceId。子图套娃时外层 stream 袋经 ?? 保留。
@@ -616,23 +680,26 @@ export class LangGraphAdapter {
616
680
  case 'subgraph':
617
681
  return this.createSubGraphNodeFunction(node);
618
682
  case 'human':
619
- return async (state) => {
620
- return { currentNode: node.id };
621
- };
622
- case 'parallel':
623
- return this.createParallelNodeFunction(node);
683
+ throw new AppError(ErrorCodes.GRAPH_BUILD_FAILED, `Graph 节点 "${node.id}" type=human 未实现;人工确认请用 require_confirmation`, { exposeMessage: true });
624
684
  case 'code':
625
685
  if (node.config?.['bindSkill'] === true) {
626
686
  return this.createSkillBindNodeFunction(node);
627
687
  }
628
688
  return this.createCodeNodeFunction(node);
629
- default:
689
+ case 'rag':
690
+ return this.createRagNodeFunction(node);
691
+ default: {
630
692
  if (node.config?.['bindSkill'] === true) {
631
693
  return this.createSkillBindNodeFunction(node);
632
694
  }
633
- return async (state) => {
634
- return { ...state, currentNode: node.id };
635
- };
695
+ const type = String(node.type);
696
+ if (type === 'start' || type === 'end') {
697
+ return async (state) => {
698
+ return { ...state, currentNode: node.id };
699
+ };
700
+ }
701
+ throw new AppError(ErrorCodes.GRAPH_BUILD_FAILED, `Graph 节点 "${node.id}" type=${type} 不是合法类型`, { exposeMessage: true });
702
+ }
636
703
  }
637
704
  }
638
705
  createSkillBindNodeFunction(node) {
@@ -709,7 +776,7 @@ export class LangGraphAdapter {
709
776
  userId: 'system',
710
777
  delegationToken: '',
711
778
  agentCode: '',
712
- }, (progress) => this.emitToolExecutionEvents(progress), this.hitlHandler, node.id);
779
+ }, (progress) => this.emitToolExecutionEvents(progress), this.hitlHandler, node.id, this.interceptors, node.config?.['retrievalSources'], node.config?.['totalK'], node.retry);
713
780
  return (state) => toolNode.execute(state);
714
781
  }
715
782
  if (this.toolExecutor) {
@@ -732,7 +799,7 @@ export class LangGraphAdapter {
732
799
  const jailExtraRoots = Object.values(skillDefs ?? {})
733
800
  .map((skill) => skill.bundlePath)
734
801
  .filter((p) => Boolean(p));
735
- const result = await this.toolExecutor.execute({
802
+ const request = {
736
803
  toolCallId,
737
804
  toolName,
738
805
  arguments: defaultArgs,
@@ -740,7 +807,25 @@ export class LangGraphAdapter {
740
807
  correlationId,
741
808
  taskId,
742
809
  ...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
743
- });
810
+ };
811
+ const retry = resolveToolRetryConfig(node.retry);
812
+ const maxRetries = retry?.maxRetries ?? 0;
813
+ let result = await this.toolExecutor.execute(request);
814
+ for (let attempt = 0; !result.success && attempt < maxRetries; attempt++) {
815
+ if (!isRetryableToolFailure(result))
816
+ break;
817
+ this.emitToolExecutionEvents({
818
+ toolCallId: result.toolCallId,
819
+ toolName: result.toolName,
820
+ status: 'error',
821
+ arguments: defaultArgs,
822
+ error: result.error,
823
+ willRetry: true,
824
+ node: node.id,
825
+ });
826
+ await sleepToolRetry(toolRetryDelayMs(attempt, retry.backoff));
827
+ result = await this.toolExecutor.execute(request);
828
+ }
744
829
  const executionTime = Date.now() - startedAt;
745
830
  const skillDirs = Object.values(skillDefs ?? {})
746
831
  .filter((skill) => Boolean(skill.bundlePath))
@@ -752,6 +837,7 @@ export class LangGraphAdapter {
752
837
  arguments: defaultArgs,
753
838
  content: result.content,
754
839
  error: result.error,
840
+ willRetry: false,
755
841
  executionTime,
756
842
  node: node.id,
757
843
  ...(skillDirs.length > 0 ? { skillDirs } : {}),
@@ -856,6 +942,9 @@ export class LangGraphAdapter {
856
942
  loadedTools,
857
943
  skillDefinitions: skillDefs,
858
944
  });
945
+ if (this.overlayExtraRoots.length > 0) {
946
+ jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
947
+ }
859
948
  const workingDirectory = jail.workdir;
860
949
  const jailRoots = resolveJailRoots(jail);
861
950
  const skillDirs = Object.values(skillDefs ?? {})
@@ -937,7 +1026,7 @@ export class LangGraphAdapter {
937
1026
  },
938
1027
  });
939
1028
  }
940
- : undefined);
1029
+ : undefined, this.interceptors);
941
1030
  const next = await llmNode.execute(state);
942
1031
  const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
943
1032
  if (!silent)
@@ -955,6 +1044,31 @@ export class LangGraphAdapter {
955
1044
  });
956
1045
  };
957
1046
  }
1047
+ /**
1048
+ * RAG 节点:编译期 fail-fast(deps 缺失),请求级状态经 execute(state) 流转
1049
+ */
1050
+ createRagNodeFunction(node) {
1051
+ if (!this.ragNodeDeps) {
1052
+ throw new Error(`图使用了 type:rag 节点 "${node.id}",但宿主未注入 ragNodeDeps(LangGraphAdapter 第 6 参)`);
1053
+ }
1054
+ const ragNode = new RagNode(node.id, (node.config ?? {}), {
1055
+ ...this.ragNodeDeps,
1056
+ // 未显式提供时复用 adapter 已组装的 orchestrator(§9.3:toolExecutor+governance 透传)
1057
+ toolOrchestrator: this.ragNodeDeps.toolOrchestrator ?? this.toolOrchestrator,
1058
+ // rag.* 事件接 SSE 旁路(请求级执行流存活期间有效;宿主/测试注入的 emitEvent 优先)
1059
+ emitEvent: this.ragNodeDeps.emitEvent ??
1060
+ ((type, payload) => {
1061
+ this.emitStreamEvent({
1062
+ type,
1063
+ taskId: undefined,
1064
+ correlationId: undefined,
1065
+ timestamp: new Date().toISOString(),
1066
+ payload,
1067
+ });
1068
+ }),
1069
+ });
1070
+ return (state) => ragNode.execute(state);
1071
+ }
958
1072
  /**
959
1073
  * 子图节点:优先 config.subgraphDsl;否则按 skillId 从 state.skillDefinitions.workflow 解析。
960
1074
  * 技能正文用短目录 + read_file,不灌 system。
@@ -965,7 +1079,10 @@ export class LangGraphAdapter {
965
1079
  node.config?.['skillId'] ??
966
1080
  embeddedDsl?.name;
967
1081
  if (embeddedDsl) {
968
- return this.subGraphCompiler.compileSubGraph(embeddedDsl, configuredSkillId);
1082
+ return this.subGraphCompiler.compileSubGraph(embeddedDsl, configuredSkillId, {
1083
+ isolateContext: isSkillContextIsolated(node),
1084
+ parentNodeId: node.id,
1085
+ });
969
1086
  }
970
1087
  return async (state) => {
971
1088
  const skillId = configuredSkillId ??
@@ -976,70 +1093,13 @@ export class LangGraphAdapter {
976
1093
  if (!workflow?.nodes?.length) {
977
1094
  throw preloadSkillNotFound(`子图节点无法解析 Skill workflow: node=${node.id}, skillId=${skillId ?? '(none)'}`, { nodeId: node.id, skillId });
978
1095
  }
979
- const fn = this.subGraphCompiler.compileSubGraph(workflow, skillId ?? def?.skillId);
1096
+ const fn = this.subGraphCompiler.compileSubGraph(workflow, skillId ?? def?.skillId, {
1097
+ isolateContext: isSkillContextIsolated(node),
1098
+ parentNodeId: node.id,
1099
+ });
980
1100
  return fn(state);
981
1101
  };
982
1102
  }
983
- /**
984
- * 并行节点:并行执行多个子任务分支
985
- *
986
- * 支持两种并行模式:
987
- * 1. branches 模式:node.branches 列出要并行执行的节点 ID,每个节点的执行函数由 DSL 定义
988
- * 2. tasks 模式:node.config.tasks 列出要并行执行的异步任务函数
989
- *
990
- * 执行结果合并到 state.__parallelResults 中。
991
- */
992
- createParallelNodeFunction(node) {
993
- return async (state) => {
994
- const branchNodeIds = node.branches ?? node.config?.['branches'] ?? [];
995
- const tasks = node.config?.['tasks'];
996
- const results = [];
997
- if (tasks?.length) {
998
- // tasks 模式:并行执行配置的任务
999
- const promises = tasks.map(async (task) => {
1000
- try {
1001
- // 任务结果由外部注入的 task executor 处理
1002
- // 此处仅记录任务 ID,实际执行依赖外部编排
1003
- return { branchId: task.id, success: true, result: { taskId: task.id, status: 'dispatched' } };
1004
- }
1005
- catch (err) {
1006
- return {
1007
- branchId: task.id,
1008
- success: false,
1009
- error: err instanceof Error ? err.message : String(err),
1010
- };
1011
- }
1012
- });
1013
- results.push(...await Promise.all(promises));
1014
- }
1015
- else if (branchNodeIds.length) {
1016
- // branches 模式:并行执行分支节点
1017
- const promises = branchNodeIds.map(async (branchId) => {
1018
- try {
1019
- // 每个分支节点的实际执行由 LangGraph 编排层驱动
1020
- // 并行节点仅负责触发并汇总状态
1021
- return { branchId, success: true, result: { dispatched: true } };
1022
- }
1023
- catch (err) {
1024
- return {
1025
- branchId,
1026
- success: false,
1027
- error: err instanceof Error ? err.message : String(err),
1028
- };
1029
- }
1030
- });
1031
- results.push(...await Promise.all(promises));
1032
- }
1033
- const allSuccess = results.every((r) => r.success);
1034
- return {
1035
- ...state,
1036
- currentNode: node.id,
1037
- __parallelResults: results,
1038
- __parallelAllSuccess: allSuccess,
1039
- __conditionResult: allSuccess ? 'success' : 'partial_failure',
1040
- };
1041
- };
1042
- }
1043
1103
  /**
1044
1104
  * 代码执行节点:在受限沙箱中执行用户代码
1045
1105
  *
@@ -1215,6 +1275,41 @@ export class LangGraphAdapter {
1215
1275
  * 否则多轮回放时 DeepSeek thinking 会因缺少 reasoning_content 而 400,
1216
1276
  * tool 消息也会丢掉 tool_call_id。
1217
1277
  */
1278
+ /**
1279
+ * 工具形态检索面白名单(图声明即真相):tool 节点 config.retrievalSources 声明的
1280
+ * 数据源目录 → 本图工具白名单(声明源解析出的工具 + rag_search)。未声明的检索类
1281
+ * 工具对主 LLM 工具表与执行链整体不可见/不可用;图未声明 = 不收敓名单(通用形态)。
1282
+ *
1283
+ * 约束——单检索面假设:find 只取全图首个带 retrievalSources 的 tool 节点、按它过滤
1284
+ * 全图 loadedTools。多 tool 节点各声明检索面属未支持场景:第二面的工具会被当成
1285
+ * 未声明而过滤,无法各自收敛;如需支持须改为 per-node 收敛(在节点边界分别过滤
1286
+ * loadedTools,而非此处全图一次性收敛)。
1287
+ */
1288
+ applyDeclaredSearchFace(state, dsl) {
1289
+ const face = dsl?.nodes?.find((n) => n.type === 'tool' && n.config?.['retrievalSources']);
1290
+ const declaration = face?.config?.['retrievalSources'];
1291
+ const loaded = state.loadedTools;
1292
+ if (!declaration || !loaded || typeof loaded !== 'object')
1293
+ return state;
1294
+ const resolved = resolveRagSources({ retrievalSources: declaration });
1295
+ const allowed = new Set([...(resolved.toolScope ?? []), RAG_SEARCH_TOOL_NAME]);
1296
+ const filtered = {};
1297
+ for (const [name, def] of Object.entries(loaded)) {
1298
+ if (!allowed.has(name))
1299
+ continue;
1300
+ // 数据源目录写进保留工具的 description(幂等)——主 LLM 直调时与 rag 内核
1301
+ // 同一份目录,杜绝瞎猜 datasetCode;内核组表侧 appendSourceCatalog 幂等不重复
1302
+ const catalog = resolved.sourceCatalog[name];
1303
+ if (catalog?.length) {
1304
+ const desc = appendSourceCatalog(def.manifest.description, catalog);
1305
+ filtered[name] = { ...def, manifest: { ...def.manifest, description: desc } };
1306
+ }
1307
+ else {
1308
+ filtered[name] = def;
1309
+ }
1310
+ }
1311
+ return { ...state, loadedTools: filtered };
1312
+ }
1218
1313
  graphStateToLangGraphInput(state) {
1219
1314
  const mapped = (state.messages ?? []).map((msg) => ({
1220
1315
  role: msg.role,
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 成功读 SKILL.md 时写 Trace `type=skill` Span。
3
+ *
4
+ * 假设:Skill 被调用 = 一次成功读取技能包入口(SKILL.md),
5
+ * 与子图激活走同一 collector.createNode({ type: 'skill' })。
6
+ * 声明版本在 createNode.version;skillSpace 只在 input。
7
+ * 子图已有 skillSpanId 时不记第二条。write_file / 包内附属读 / 失败读不是调用。
8
+ */
9
+ import { type SkillDirRef, type SkillSpace } from './skill-read.js';
10
+ export interface SkillTraceIdentity {
11
+ skillId: string;
12
+ skillName?: string;
13
+ skillVersion?: string;
14
+ skillSpace?: SkillSpace;
15
+ }
16
+ export declare function skillTraceIdentity(state: Record<string, unknown>, skillId: string, fallbackPath?: string): SkillTraceIdentity;
17
+ export declare function skillSpanFields(identity: SkillTraceIdentity, extra?: {
18
+ path?: string;
19
+ source?: string;
20
+ }): {
21
+ version?: string;
22
+ input: Record<string, unknown>;
23
+ };
24
+ export declare function skillEntryInvocationFromTool(input: {
25
+ toolName: string;
26
+ arguments?: Record<string, unknown>;
27
+ skillDirs?: SkillDirRef[];
28
+ alreadyInSkillSpan?: boolean;
29
+ }): {
30
+ skillId: string;
31
+ path: string;
32
+ } | null;
33
+ export declare function recordSkillEntrySpan(state: Record<string, unknown>, input: {
34
+ toolName: string;
35
+ arguments?: Record<string, unknown>;
36
+ skillDirs?: SkillDirRef[];
37
+ }): Promise<void>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * 成功读 SKILL.md 时写 Trace `type=skill` Span。
3
+ *
4
+ * 假设:Skill 被调用 = 一次成功读取技能包入口(SKILL.md),
5
+ * 与子图激活走同一 collector.createNode({ type: 'skill' })。
6
+ * 声明版本在 createNode.version;skillSpace 只在 input。
7
+ * 子图已有 skillSpanId 时不记第二条。write_file / 包内附属读 / 失败读不是调用。
8
+ */
9
+ import { classifySkillRead, classifySkillSpace } from './skill-read.js';
10
+ import { completeGraphSpan, startGraphSpan } from '../trace-span.js';
11
+ function declaredSkillVersion(skillId, version) {
12
+ const raw = version?.trim();
13
+ if (!raw || raw === skillId)
14
+ return undefined;
15
+ return raw;
16
+ }
17
+ export function skillTraceIdentity(state, skillId, fallbackPath) {
18
+ const defs = state['skillDefinitions'];
19
+ const info = defs?.[skillId];
20
+ const exec = state['executionContext'];
21
+ const userId = (typeof exec?.userId === 'string' && exec.userId.trim()) ||
22
+ (typeof state['userId'] === 'string' && state['userId'].trim()) ||
23
+ undefined;
24
+ const agentDir = typeof state['agentDir'] === 'string' ? state['agentDir'] : undefined;
25
+ const bundle = info?.bundlePath || fallbackPath;
26
+ const skillSpace = bundle ? classifySkillSpace(bundle, { agentDir, userId }) : undefined;
27
+ const skillVersion = declaredSkillVersion(skillId, info?.version);
28
+ return {
29
+ skillId,
30
+ ...(info?.name ? { skillName: info.name } : {}),
31
+ ...(skillVersion ? { skillVersion } : {}),
32
+ ...(skillSpace ? { skillSpace } : {}),
33
+ };
34
+ }
35
+ export function skillSpanFields(identity, extra) {
36
+ return {
37
+ ...(identity.skillVersion ? { version: identity.skillVersion } : {}),
38
+ input: {
39
+ skillId: identity.skillId,
40
+ ...(identity.skillSpace ? { skillSpace: identity.skillSpace } : {}),
41
+ ...(extra?.path ? { path: extra.path } : {}),
42
+ ...(extra?.source ? { source: extra.source } : {}),
43
+ },
44
+ };
45
+ }
46
+ export function skillEntryInvocationFromTool(input) {
47
+ if (input.alreadyInSkillSpan)
48
+ return null;
49
+ const hit = classifySkillRead(input.toolName, input.arguments, input.skillDirs);
50
+ if (!hit || hit.kind !== 'loaded' || !hit.skillId)
51
+ return null;
52
+ return { skillId: hit.skillId, path: hit.path };
53
+ }
54
+ export async function recordSkillEntrySpan(state, input) {
55
+ const alreadyInSkillSpan = typeof state['skillSpanId'] === 'string' && Boolean(state['skillSpanId']);
56
+ const hit = skillEntryInvocationFromTool({
57
+ toolName: input.toolName,
58
+ arguments: input.arguments,
59
+ skillDirs: input.skillDirs,
60
+ alreadyInSkillSpan,
61
+ });
62
+ if (!hit)
63
+ return;
64
+ const identity = skillTraceIdentity(state, hit.skillId, hit.path);
65
+ const fields = skillSpanFields(identity, { path: hit.path, source: 'skill_md' });
66
+ const spanId = await startGraphSpan(state, {
67
+ name: hit.skillId,
68
+ type: 'skill',
69
+ extra: {
70
+ ...(fields.version ? { version: fields.version } : {}),
71
+ input: fields.input,
72
+ },
73
+ });
74
+ await completeGraphSpan(state, spanId, {
75
+ status: 'completed',
76
+ output: { skillId: hit.skillId, path: hit.path },
77
+ });
78
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
3
+ * 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
4
+ * 隔离时不写父 session Checkpoint。
5
+ * 子图内 HITL 未按隔离线程设计:skillIsolateContext 为 true 时禁止 interrupt。
6
+ */
7
+ import type { GraphMessage } from '../contracts/executable-graph.js';
8
+ import type { GraphNode } from '../contracts/executable-graph.js';
9
+ export declare const SKILL_ISOLATE_HITL_MESSAGE = "Skill isolateContext \u4E0D\u80FD\u4E0E\u5B50\u56FE\u5185\u4EBA\u5DE5\u786E\u8BA4\u4E00\u8D77\u4F7F\u7528\u3002\u5173\u6389 isolate\uFF0C\u6216\u4E0D\u8981\u5728\u8BE5 Skill \u91CC\u4F7F\u7528 require_confirmation / beforeTool ask\u3002";
10
+ /** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
11
+ export declare function isSkillContextIsolated(node: GraphNode | undefined): boolean;
12
+ export declare function isolatedSkillMessages(parentMessages: GraphMessage[], skillInput: unknown): GraphMessage[];
13
+ /** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
14
+ export declare function isolatedSkillOutput(added: GraphMessage[]): GraphMessage[];
15
+ export declare function isIsolatedSkillState(state: Record<string, unknown> | undefined): boolean;
16
+ /** 隔离子图即将 HITL 时拒绝,避免写错父线程 Checkpoint。 */
17
+ export declare function assertIsolatedSkillAllowsHitl(state: Record<string, unknown> | undefined): void;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Skill 子图可选隔离(实验口)。仅节点 `isolateContext: true`。
3
+ * 只把本轮输入给子图,回写最后一条带正文的 assistant;没有则不回写。
4
+ * 隔离时不写父 session Checkpoint。
5
+ * 子图内 HITL 未按隔离线程设计:skillIsolateContext 为 true 时禁止 interrupt。
6
+ */
7
+ import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
8
+ export const SKILL_ISOLATE_HITL_MESSAGE = 'Skill isolateContext 不能与子图内人工确认一起使用。关掉 isolate,或不要在该 Skill 里使用 require_confirmation / beforeTool ask。';
9
+ /** 只认节点配置。进程环境变量不会给所有 subgraph 开隔离。 */
10
+ export function isSkillContextIsolated(node) {
11
+ return node?.config?.['isolateContext'] === true;
12
+ }
13
+ export function isolatedSkillMessages(parentMessages, skillInput) {
14
+ let text = '';
15
+ if (typeof skillInput === 'string' && skillInput.trim()) {
16
+ text = skillInput.trim();
17
+ }
18
+ else if (skillInput && typeof skillInput === 'object' && 'text' in skillInput) {
19
+ text = String(skillInput.text ?? '').trim();
20
+ }
21
+ if (!text) {
22
+ for (let i = parentMessages.length - 1; i >= 0; i--) {
23
+ if (parentMessages[i]?.role === 'user' && parentMessages[i]?.content) {
24
+ text = parentMessages[i].content;
25
+ break;
26
+ }
27
+ }
28
+ }
29
+ return [{ role: 'user', content: text || '' }];
30
+ }
31
+ /** 只回写最后一条带正文的 assistant;没有则空数组,避免把 tool 行丢给父图。 */
32
+ export function isolatedSkillOutput(added) {
33
+ for (let i = added.length - 1; i >= 0; i--) {
34
+ if (added[i]?.role === 'assistant' && added[i]?.content) {
35
+ return [{ role: 'assistant', content: added[i].content }];
36
+ }
37
+ }
38
+ return [];
39
+ }
40
+ export function isIsolatedSkillState(state) {
41
+ return state?.['skillIsolateContext'] === true;
42
+ }
43
+ /** 隔离子图即将 HITL 时拒绝,避免写错父线程 Checkpoint。 */
44
+ export function assertIsolatedSkillAllowsHitl(state) {
45
+ if (!isIsolatedSkillState(state))
46
+ return;
47
+ throw new AppError(ErrorCodes.GRAPH_EXECUTION_ERROR, SKILL_ISOLATE_HITL_MESSAGE, {
48
+ exposeMessage: true,
49
+ });
50
+ }