@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.
- package/README.md +3 -2
- package/dist/api/handlers/invoke.handler.d.ts +7 -0
- package/dist/api/handlers/invoke.handler.js +57 -24
- package/dist/api/handlers/stream.handler.d.ts +6 -0
- package/dist/api/handlers/stream.handler.js +59 -39
- package/dist/api/routes/agent.routes.d.ts +8 -1
- package/dist/common/types.d.ts +2 -2
- package/dist/common/types.js +1 -1
- package/dist/contracts/index.d.ts +1 -1
- package/dist/contracts/trace-collector.d.ts +16 -8
- package/dist/execution/index.d.ts +2 -0
- package/dist/execution/index.js +2 -0
- package/dist/execution/interceptors.d.ts +40 -0
- package/dist/execution/interceptors.js +23 -0
- package/dist/execution/ports.d.ts +18 -0
- package/dist/execution/ports.js +13 -0
- package/dist/governance/audit-reporter.d.ts +14 -0
- package/dist/governance/audit-reporter.js +18 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/governance/permission-guard.d.ts +6 -2
- package/dist/governance/permission-guard.js +38 -5
- package/dist/graph/adapters/graph-nesting.d.ts +12 -0
- package/dist/graph/adapters/graph-nesting.js +29 -0
- package/dist/graph/adapters/index.d.ts +2 -0
- package/dist/graph/adapters/index.js +2 -0
- package/dist/graph/adapters/langgraph.adapter.d.ts +26 -11
- package/dist/graph/adapters/langgraph.adapter.js +178 -83
- package/dist/graph/adapters/skill-entry-span.d.ts +37 -0
- package/dist/graph/adapters/skill-entry-span.js +78 -0
- package/dist/graph/adapters/skill-isolate.d.ts +17 -0
- package/dist/graph/adapters/skill-isolate.js +50 -0
- package/dist/graph/adapters/skill-read.d.ts +10 -0
- package/dist/graph/adapters/skill-read.js +33 -0
- package/dist/graph/adapters/subgraph-compiler.d.ts +7 -2
- package/dist/graph/adapters/subgraph-compiler.js +81 -35
- package/dist/graph/contracts/executable-graph.d.ts +27 -5
- package/dist/graph/hitl/hitl-handler.js +2 -0
- package/dist/graph/nodes/index.d.ts +1 -0
- package/dist/graph/nodes/index.js +1 -0
- package/dist/graph/nodes/llm-node.d.ts +3 -1
- package/dist/graph/nodes/llm-node.js +26 -2
- package/dist/graph/nodes/rag/dedup.d.ts +20 -0
- package/dist/graph/nodes/rag/dedup.js +77 -0
- package/dist/graph/nodes/rag/denoise.d.ts +58 -0
- package/dist/graph/nodes/rag/denoise.js +220 -0
- package/dist/graph/nodes/rag/fusion/fuse.d.ts +11 -0
- package/dist/graph/nodes/rag/fusion/fuse.js +25 -0
- package/dist/graph/nodes/rag/fusion/normalize.d.ts +9 -0
- package/dist/graph/nodes/rag/fusion/normalize.js +135 -0
- package/dist/graph/nodes/rag/index.d.ts +18 -0
- package/dist/graph/nodes/rag/index.js +17 -0
- package/dist/graph/nodes/rag/prompts.d.ts +12 -0
- package/dist/graph/nodes/rag/prompts.js +40 -0
- package/dist/graph/nodes/rag/rag-node.d.ts +21 -0
- package/dist/graph/nodes/rag/rag-node.js +135 -0
- package/dist/graph/nodes/rag/rag-search-tool.d.ts +48 -0
- package/dist/graph/nodes/rag/rag-search-tool.js +230 -0
- package/dist/graph/nodes/rag/react-loop.d.ts +57 -0
- package/dist/graph/nodes/rag/react-loop.js +467 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.d.ts +29 -0
- package/dist/graph/nodes/rag/retrievers/http-embedder.js +66 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.d.ts +17 -0
- package/dist/graph/nodes/rag/retrievers/in-memory-retriever.js +18 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.d.ts +46 -0
- package/dist/graph/nodes/rag/retrievers/local-sqlite-retriever.js +85 -0
- package/dist/graph/nodes/rag/tools.d.ts +90 -0
- package/dist/graph/nodes/rag/tools.js +260 -0
- package/dist/graph/nodes/rag/types.d.ts +226 -0
- package/dist/graph/nodes/rag/types.js +9 -0
- package/dist/graph/nodes/tool-node.d.ts +25 -1
- package/dist/graph/nodes/tool-node.js +193 -35
- package/dist/graph/skill-audit-recorder.d.ts +4 -4
- package/dist/graph/skill-audit-recorder.js +3 -3
- package/dist/graph/trace-span.d.ts +2 -9
- package/dist/graph/trace-span.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/memory/semantic-retrieval.service.d.ts +1 -1
- package/dist/memory/semantic-retrieval.service.js +13 -23
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +1 -1
- package/dist/providers/routing-llm-provider.d.ts +5 -0
- package/dist/providers/routing-llm-provider.js +66 -1
- package/dist/runtime/agent-runtime.d.ts +5 -0
- package/dist/runtime/agent-runtime.js +105 -63
- package/dist/runtime/dev-rag-deps.d.ts +21 -0
- package/dist/runtime/dev-rag-deps.js +186 -0
- package/dist/runtime/dev-runtime.d.ts +16 -1
- package/dist/runtime/dev-runtime.js +59 -5
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/types.d.ts +10 -9
- package/dist/session/apply-session-compression.d.ts +81 -0
- package/dist/session/apply-session-compression.js +262 -0
- package/dist/session/apply-session-memory.d.ts +21 -0
- package/dist/session/apply-session-memory.js +48 -0
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/persist-turn-messages.d.ts +51 -0
- package/dist/session/persist-turn-messages.js +140 -3
- package/dist/sse/events/index.d.ts +13 -4
- package/dist/sse/events/index.js +9 -1
- package/dist/sse/events/rag-events.d.ts +63 -0
- package/dist/sse/events/rag-events.js +9 -0
- package/dist/sse/events/session-events.d.ts +38 -0
- package/dist/sse/events/session-events.js +4 -0
- package/dist/sse/trace-collector.d.ts +2 -9
- package/dist/token/index.d.ts +3 -0
- package/dist/token/index.js +2 -0
- package/dist/token/record-task-token-usage.d.ts +23 -0
- package/dist/token/record-task-token-usage.js +76 -0
- package/dist/token/token-detail-mode.d.ts +4 -4
- package/dist/token/token-detail-mode.js +4 -4
- package/dist/token/token-detail.d.ts +8 -0
- package/dist/token/token-detail.js +22 -0
- package/dist/token/token-stats.service.d.ts +4 -2
- package/dist/token/token-stats.service.js +5 -1
- package/dist/tool/create-tool-executor.d.ts +6 -0
- package/dist/tool/create-tool-executor.js +4 -3
- package/dist/tool/harness-primitives.d.ts +9 -1
- package/dist/tool/harness-primitives.js +43 -12
- package/dist/tool/index.d.ts +3 -2
- package/dist/tool/index.js +3 -2
- package/dist/tool/mcp-list.d.ts +12 -0
- package/dist/tool/mcp-list.js +15 -0
- package/dist/tool/tool-executor.interface.d.ts +27 -0
- package/dist/tool/tool-orchestrator.d.ts +3 -1
- package/dist/tool/tool-orchestrator.js +15 -6
- package/dist/tool/tool-retry.d.ts +15 -0
- package/dist/tool/tool-retry.js +69 -0
- 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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
634
|
-
|
|
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
|
|
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
|
+
}
|