@gt-fe/eap-runtime 0.0.39 → 0.0.41
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/dist/graph/adapters/langgraph.adapter.js +1 -0
- package/dist/graph/adapters/subgraph-compiler.js +9 -18
- package/dist/graph/contracts/executable-graph.d.ts +5 -0
- package/dist/graph/hitl/hitl-handler.d.ts +3 -0
- package/dist/graph/hitl/hitl-handler.js +7 -0
- package/dist/graph/nodes/rag/rag-node.js +34 -26
- package/dist/graph/nodes/rag/react-loop.d.ts +2 -0
- package/dist/graph/nodes/rag/react-loop.js +1 -0
- package/dist/graph/nodes/tool-node.js +8 -6
- package/dist/graph/trace-span.d.ts +6 -1
- package/dist/graph/trace-span.js +28 -6
- package/dist/registry/graph-loader.d.ts +2 -2
- package/dist/registry/graph-loader.js +78 -121
- package/dist/tool/tool-confirmation.js +2 -0
- package/package.json +1 -1
|
@@ -572,6 +572,7 @@ export class LangGraphAdapter {
|
|
|
572
572
|
traceCollector: ephemeral(undefined),
|
|
573
573
|
traceId: ephemeral(undefined),
|
|
574
574
|
skillSpanId: ephemeral(undefined),
|
|
575
|
+
llmSpanId: ephemeral(undefined),
|
|
575
576
|
toolSpanId: ephemeral(undefined),
|
|
576
577
|
hitlToolCallId: ephemeral(undefined),
|
|
577
578
|
lastSpanId: ephemeral(undefined),
|
|
@@ -12,6 +12,7 @@ import { assertSkillPermission } from '../../governance/permission-guard.js';
|
|
|
12
12
|
import { LangGraphAdapter } from './langgraph.adapter.js';
|
|
13
13
|
import { assertSkillGraphHasNoSubgraphNodes } from './graph-nesting.js';
|
|
14
14
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
15
|
+
import { startGraphSpan } from '../trace-span.js';
|
|
15
16
|
import { skillSpanFields, skillTraceIdentity } from './skill-entry-span.js';
|
|
16
17
|
import { filterLoadedToolsForSkill, spokenName, } from '../../tool/system-prompt-builder.js';
|
|
17
18
|
import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
@@ -96,26 +97,16 @@ export class SubGraphCompiler {
|
|
|
96
97
|
});
|
|
97
98
|
}
|
|
98
99
|
let skillSpanId = existingSpanId || null;
|
|
99
|
-
if (!resuming
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
type: 'skill',
|
|
106
|
-
agentCode: state['agentCode'] ?? state['agentId'],
|
|
100
|
+
if (!resuming) {
|
|
101
|
+
const fields = skillSpanFields(identity, { source: 'subgraph' });
|
|
102
|
+
skillSpanId = (await startGraphSpan(state, {
|
|
103
|
+
name: spanName,
|
|
104
|
+
type: 'skill',
|
|
105
|
+
extra: {
|
|
107
106
|
...(fields.version ? { version: fields.version } : {}),
|
|
108
107
|
input: fields.input,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
correlationId: state['correlationId'],
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
warnTraceSpanFailure('createNode', { type: 'skill', name: spanName, traceId }, err);
|
|
117
|
-
skillSpanId = null;
|
|
118
|
-
}
|
|
108
|
+
},
|
|
109
|
+
})) ?? null;
|
|
119
110
|
}
|
|
120
111
|
try {
|
|
121
112
|
// 1. 编译子图(每次执行时编译,确保子图实例独立)
|
|
@@ -240,6 +240,11 @@ export interface GraphState {
|
|
|
240
240
|
skillIsolateContext?: boolean;
|
|
241
241
|
/** 当前 Skill 子图 createNode 返回的 span id;子图内 Tool span 的 parentNodeId */
|
|
242
242
|
skillSpanId?: string;
|
|
243
|
+
/**
|
|
244
|
+
* 本回合父图模型调用的 span id。父图 tool / 手册技能 / 子图技能入口挂到它下面。
|
|
245
|
+
* 只在 llm createNode 成功时更新。HITL 等待态一起带上。
|
|
246
|
+
*/
|
|
247
|
+
llmSpanId?: string;
|
|
243
248
|
/**
|
|
244
249
|
* HITL 续跑:暂停时已 create 的 tool Span id。
|
|
245
250
|
* 按 hitlToolCallId 复用,禁止再 createNode。
|
|
@@ -40,6 +40,7 @@ export interface HitlInterruptExtras {
|
|
|
40
40
|
toolName?: string;
|
|
41
41
|
toolSpanId?: string;
|
|
42
42
|
skillSpanId?: string;
|
|
43
|
+
llmSpanId?: string;
|
|
43
44
|
}
|
|
44
45
|
/** HITL 等待中状态(内存中维护) */
|
|
45
46
|
export interface HitlWaitingState {
|
|
@@ -55,6 +56,7 @@ export interface HitlWaitingState {
|
|
|
55
56
|
toolName?: string;
|
|
56
57
|
toolSpanId?: string;
|
|
57
58
|
skillSpanId?: string;
|
|
59
|
+
llmSpanId?: string;
|
|
58
60
|
hitlSpanId?: string;
|
|
59
61
|
}
|
|
60
62
|
/** HITL 超时错误(保留类型;timer 不再 throw,resume 逾期改走 AppError) */
|
|
@@ -119,6 +121,7 @@ export interface PersistedHitlWaiting {
|
|
|
119
121
|
toolName?: string;
|
|
120
122
|
toolSpanId?: string;
|
|
121
123
|
skillSpanId?: string;
|
|
124
|
+
llmSpanId?: string;
|
|
122
125
|
hitlSpanId?: string;
|
|
123
126
|
/** error/cancel 超时已占用此等待;resume 必须拒绝 */
|
|
124
127
|
timedOut?: boolean;
|
|
@@ -194,6 +194,7 @@ function prepareHitlCheckpointState(state) {
|
|
|
194
194
|
correlationId: state.correlationId ?? raw.correlationId,
|
|
195
195
|
delegationToken: state.delegationToken ?? raw.delegationToken,
|
|
196
196
|
skillSpanId: state.skillSpanId ?? raw.skillSpanId,
|
|
197
|
+
llmSpanId: state.llmSpanId ?? (typeof raw.llmSpanId === 'string' ? raw.llmSpanId : undefined),
|
|
197
198
|
traceId: state.traceId ?? (typeof raw.traceId === 'string' ? raw.traceId : undefined),
|
|
198
199
|
lastSpanId: state.lastSpanId ?? (typeof raw.lastSpanId === 'string' ? raw.lastSpanId : undefined),
|
|
199
200
|
};
|
|
@@ -227,6 +228,7 @@ function waitingFromPersisted(persisted, taskId) {
|
|
|
227
228
|
toolName: persisted.toolName,
|
|
228
229
|
toolSpanId: persisted.toolSpanId,
|
|
229
230
|
skillSpanId: persisted.skillSpanId,
|
|
231
|
+
llmSpanId: persisted.llmSpanId,
|
|
230
232
|
hitlSpanId: persisted.hitlSpanId,
|
|
231
233
|
};
|
|
232
234
|
}
|
|
@@ -285,6 +287,8 @@ export class HitlHandler {
|
|
|
285
287
|
const deadlineAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
286
288
|
const skillSpanId = extras.skillSpanId ??
|
|
287
289
|
(typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined);
|
|
290
|
+
const llmSpanId = extras.llmSpanId ??
|
|
291
|
+
(typeof state.llmSpanId === 'string' ? state.llmSpanId : undefined);
|
|
288
292
|
const hitlSpanId = await startGraphSpan(state, {
|
|
289
293
|
name: extras.toolName ?? 'hitl',
|
|
290
294
|
type: 'hitl',
|
|
@@ -306,6 +310,7 @@ export class HitlHandler {
|
|
|
306
310
|
toolName: extras.toolName,
|
|
307
311
|
toolSpanId: extras.toolSpanId,
|
|
308
312
|
skillSpanId,
|
|
313
|
+
llmSpanId,
|
|
309
314
|
hitlSpanId,
|
|
310
315
|
};
|
|
311
316
|
const existing = await this.checkpointSaver.load(threadId);
|
|
@@ -392,6 +397,7 @@ export class HitlHandler {
|
|
|
392
397
|
toolName: extras.toolName,
|
|
393
398
|
toolSpanId: extras.toolSpanId,
|
|
394
399
|
skillSpanId,
|
|
400
|
+
llmSpanId,
|
|
395
401
|
hitlSpanId,
|
|
396
402
|
});
|
|
397
403
|
}
|
|
@@ -448,6 +454,7 @@ export class HitlHandler {
|
|
|
448
454
|
hitlRespondedBy: userId,
|
|
449
455
|
...(waiting.toolSpanId ? { toolSpanId: waiting.toolSpanId } : {}),
|
|
450
456
|
...(waiting.toolCallId ? { hitlToolCallId: waiting.toolCallId } : {}),
|
|
457
|
+
...(waiting.llmSpanId ? { llmSpanId: waiting.llmSpanId } : {}),
|
|
451
458
|
};
|
|
452
459
|
const now = new Date().toISOString();
|
|
453
460
|
await this.checkpointSaver.save(waiting.threadId, stripCheckpointCollectors({
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @see design-v2 §5.2 / §5.3
|
|
9
9
|
*/
|
|
10
10
|
import { resolveTraceId } from '../../../common/trace-id.js';
|
|
11
|
+
import { withGraphSpan } from '../../trace-span.js';
|
|
11
12
|
import { buildRagTools, resolveRagSources, mergeLocalDirs } from './tools.js';
|
|
12
13
|
import { runReactLoop } from './react-loop.js';
|
|
13
14
|
export class RagNode {
|
|
@@ -49,33 +50,40 @@ export class RagNode {
|
|
|
49
50
|
if (excludedConfirmToolNames.length > 0) {
|
|
50
51
|
throw new Error(`RagNode "${this.nodeId}" 检索源 tools 引用了需人工确认的工具: ${excludedConfirmToolNames.join(', ')}(requireConfirmation 工具的确认流挂在图级 tool 节点,rag 循环内执行会绕过确认,不支持挂载)`);
|
|
51
52
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
return withGraphSpan(state, {
|
|
54
|
+
name: this.nodeId,
|
|
55
|
+
type: 'llm',
|
|
56
|
+
extra: { metadata: { rag: true } },
|
|
57
|
+
}, async (spanId) => {
|
|
58
|
+
const result = await runReactLoop({
|
|
59
|
+
nodeId: this.nodeId,
|
|
60
|
+
query: extractQuery(state),
|
|
61
|
+
history: extractHistory(state),
|
|
62
|
+
config: this.config,
|
|
63
|
+
apiTools,
|
|
64
|
+
kbToolNames,
|
|
65
|
+
llmProvider: this.deps.llmProvider,
|
|
66
|
+
toolOrchestrator: this.deps.toolOrchestrator,
|
|
67
|
+
retrievers,
|
|
68
|
+
loadedTools,
|
|
69
|
+
authContext: this.buildAuthContext(state),
|
|
70
|
+
executionContext: state['executionContext'],
|
|
71
|
+
forceToolServer: state['forceToolServer'],
|
|
72
|
+
userId: String(state['userId'] ?? ''),
|
|
73
|
+
ragScope,
|
|
74
|
+
emitEvent: this.deps.emitEvent,
|
|
75
|
+
traceCollector: state['traceCollector'],
|
|
76
|
+
traceId: resolveTraceId(state),
|
|
77
|
+
parentSpanId: spanId,
|
|
78
|
+
});
|
|
79
|
+
// partial state 三连(与 LLMNode 同惯例):
|
|
80
|
+
// messages 只返回新增(channel 为 append reducer);token 走 __llmTokenUsage 累加器
|
|
81
|
+
return {
|
|
82
|
+
messages: [{ role: 'assistant', content: result.contextText }],
|
|
83
|
+
currentNode: this.nodeId,
|
|
84
|
+
__llmTokenUsage: result.tokenUsage,
|
|
85
|
+
};
|
|
71
86
|
});
|
|
72
|
-
// partial state 三连(与 LLMNode 同惯例):
|
|
73
|
-
// messages 只返回新增(channel 为 append reducer);token 走 __llmTokenUsage 累加器
|
|
74
|
-
return {
|
|
75
|
-
messages: [{ role: 'assistant', content: result.contextText }],
|
|
76
|
-
currentNode: this.nodeId,
|
|
77
|
-
__llmTokenUsage: result.tokenUsage,
|
|
78
|
-
};
|
|
79
87
|
}
|
|
80
88
|
/** 身份组装照 ToolNode 惯例:executionContext 优先,state 兜底 */
|
|
81
89
|
buildAuthContext(state) {
|
|
@@ -45,6 +45,8 @@ export interface RagLoopInput {
|
|
|
45
45
|
/** Trace 采集器 + 执行流 ID(缺省不建 span,照 ToolNode 模式:resolveTraceId) */
|
|
46
46
|
traceCollector?: ITraceCollector;
|
|
47
47
|
traceId?: string;
|
|
48
|
+
/** 本 RAG 节点 llm Span;内部检索/降噪挂到它下面 */
|
|
49
|
+
parentSpanId?: string;
|
|
48
50
|
}
|
|
49
51
|
export interface RagLoopOutput {
|
|
50
52
|
/** 降噪上下文(近重去重 + LLM 判定 + 句子抽取后的拼装结果) */
|
|
@@ -19,6 +19,7 @@ import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.j
|
|
|
19
19
|
import { mcpToolsetCode, toolTraceIdentity } from '../../tool/mcp-list.js';
|
|
20
20
|
import { exposureScopeId, exposedNamesForScope, isUnloadedDeferredCall, mcpNamesToExpose, mergeExposed, partitionToolSearchCalls, runToolSearch, unloadedCallToolContent, } from '../../tool/mcp-tool-exposure.js';
|
|
21
21
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from '../execution-trace-context.js';
|
|
22
|
+
import { resolveInvokedSpanParent } from '../trace-span.js';
|
|
22
23
|
import { emitRuntimeLog } from '../../common/logger.js';
|
|
23
24
|
import { toolResultHasArtifacts } from '../../tool/inject-public-artifact-links.js';
|
|
24
25
|
import { decideBeforeTool, } from '../../execution/interceptors.js';
|
|
@@ -212,19 +213,21 @@ export class ToolNode {
|
|
|
212
213
|
if (!traceCollector || !traceId)
|
|
213
214
|
return;
|
|
214
215
|
try {
|
|
215
|
-
const parentNodeId =
|
|
216
|
-
|
|
217
|
-
: undefined;
|
|
216
|
+
const parentNodeId = resolveInvokedSpanParent(state);
|
|
217
|
+
const identity = toolTraceIdentity(loadedTools?.[name], name);
|
|
218
218
|
const spanId = await traceCollector.createNode({
|
|
219
219
|
traceId,
|
|
220
220
|
name,
|
|
221
221
|
type: 'tool',
|
|
222
222
|
parentNodeId,
|
|
223
223
|
agentCode: agentCode || undefined,
|
|
224
|
+
...(identity.toolVersion ? { version: identity.toolVersion } : {}),
|
|
224
225
|
input,
|
|
225
226
|
metadata: {
|
|
226
227
|
toolCallId: callId,
|
|
227
228
|
...(notExecuted ? { notExecuted: true } : {}),
|
|
229
|
+
...(identity.toolset ? { toolset: identity.toolset } : {}),
|
|
230
|
+
...(identity.toolDisplayName ? { toolDisplayName: identity.toolDisplayName } : {}),
|
|
228
231
|
},
|
|
229
232
|
});
|
|
230
233
|
sideSpanIds.push(spanId);
|
|
@@ -293,9 +296,7 @@ export class ToolNode {
|
|
|
293
296
|
if (!traceCollector || !traceId)
|
|
294
297
|
return null;
|
|
295
298
|
try {
|
|
296
|
-
const parentNodeId =
|
|
297
|
-
? state['skillSpanId']
|
|
298
|
-
: undefined;
|
|
299
|
+
const parentNodeId = resolveInvokedSpanParent(state);
|
|
299
300
|
const identity = toolTraceIdentity(loadedTools?.[req.toolName], req.toolName);
|
|
300
301
|
return await traceCollector.createNode({
|
|
301
302
|
traceId,
|
|
@@ -366,6 +367,7 @@ export class ToolNode {
|
|
|
366
367
|
const confirmed = await interceptor.executeWithConfirmation(req, graphState, req.taskId ?? String(state['taskId'] ?? req.toolCallId), threadId, {
|
|
367
368
|
toolSpanId: spanIds[i] ?? undefined,
|
|
368
369
|
skillSpanId: typeof state['skillSpanId'] === 'string' ? state['skillSpanId'] : undefined,
|
|
370
|
+
llmSpanId: typeof state['llmSpanId'] === 'string' ? state['llmSpanId'] : undefined,
|
|
369
371
|
});
|
|
370
372
|
result =
|
|
371
373
|
confirmed ??
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
|
|
3
3
|
*/
|
|
4
4
|
import type { ITraceCollector, ExecutionNodeEvent, CompleteNodeParams } from '../contracts/trace-collector.js';
|
|
5
|
+
/**
|
|
6
|
+
* 被这次模型调用出来的 tool / skill:子图内挂技能,否则挂本回合模型调用。
|
|
7
|
+
* 显式 parent 优先。不用 lastSpanId(工具跑完会把它改成工具自己)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveInvokedSpanParent(state: Record<string, unknown>, explicit?: string): string | undefined;
|
|
5
10
|
export declare function startGraphSpan(state: Record<string, unknown>, input: {
|
|
6
11
|
name: string;
|
|
7
12
|
type: ExecutionNodeEvent['type'];
|
|
@@ -13,7 +18,7 @@ export declare function withGraphSpan(state: Record<string, unknown>, spec: {
|
|
|
13
18
|
name: string;
|
|
14
19
|
type: ExecutionNodeEvent['type'];
|
|
15
20
|
extra?: Partial<Omit<ExecutionNodeEvent, 'traceId' | 'name' | 'type' | 'parentNodeId'>>;
|
|
16
|
-
}, run: () => Promise<Record<string, unknown>>, complete?: {
|
|
21
|
+
}, run: (spanId?: string) => Promise<Record<string, unknown>>, complete?: {
|
|
17
22
|
outputFrom?: (result: Record<string, unknown>) => unknown;
|
|
18
23
|
}): Promise<Record<string, unknown>>;
|
|
19
24
|
/** llm Span create 时的 input:组装后的 system、工具短目录、本回合 messages。 */
|
package/dist/graph/trace-span.js
CHANGED
|
@@ -2,15 +2,27 @@
|
|
|
2
2
|
* Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
|
|
3
3
|
*/
|
|
4
4
|
import { resolveExecutionTrace, warnTraceSpanFailure, } from './execution-trace-context.js';
|
|
5
|
+
function pickSpanId(value) {
|
|
6
|
+
if (typeof value !== 'string')
|
|
7
|
+
return undefined;
|
|
8
|
+
const trimmed = value.trim();
|
|
9
|
+
return trimmed || undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 被这次模型调用出来的 tool / skill:子图内挂技能,否则挂本回合模型调用。
|
|
13
|
+
* 显式 parent 优先。不用 lastSpanId(工具跑完会把它改成工具自己)。
|
|
14
|
+
*/
|
|
15
|
+
export function resolveInvokedSpanParent(state, explicit) {
|
|
16
|
+
return pickSpanId(explicit) ?? pickSpanId(state['skillSpanId']) ?? pickSpanId(state['llmSpanId']);
|
|
17
|
+
}
|
|
5
18
|
export async function startGraphSpan(state, input) {
|
|
6
19
|
// collector 只从这次调用链取;state 只用来拼 parent/task 元数据。
|
|
7
20
|
const { collector, traceId } = resolveExecutionTrace(state);
|
|
8
21
|
if (!collector || !traceId)
|
|
9
22
|
return undefined;
|
|
10
|
-
const parentNodeId = input.
|
|
11
|
-
(
|
|
12
|
-
|
|
13
|
-
: undefined);
|
|
23
|
+
const parentNodeId = input.type === 'tool' || input.type === 'skill'
|
|
24
|
+
? resolveInvokedSpanParent(state, input.parentSpanId)
|
|
25
|
+
: pickSpanId(input.parentSpanId) ?? pickSpanId(state['skillSpanId']);
|
|
14
26
|
try {
|
|
15
27
|
return await collector.createNode({
|
|
16
28
|
traceId,
|
|
@@ -23,6 +35,10 @@ export async function startGraphSpan(state, input) {
|
|
|
23
35
|
metadata: {
|
|
24
36
|
taskId: state['taskId'],
|
|
25
37
|
correlationId: state['correlationId'],
|
|
38
|
+
// 对人展示名;name 仍是图节点 id / 函数名 / skillId。
|
|
39
|
+
...(typeof state['agentName'] === 'string' && state['agentName'].trim()
|
|
40
|
+
? { agentName: state['agentName'].trim() }
|
|
41
|
+
: {}),
|
|
26
42
|
...(input.extra?.metadata ?? {}),
|
|
27
43
|
},
|
|
28
44
|
});
|
|
@@ -46,7 +62,7 @@ export async function completeGraphSpan(state, spanId, params, collectorOverride
|
|
|
46
62
|
export async function withGraphSpan(state, spec, run, complete) {
|
|
47
63
|
const spanId = await startGraphSpan(state, spec);
|
|
48
64
|
try {
|
|
49
|
-
const result = await run();
|
|
65
|
+
const result = await run(spanId);
|
|
50
66
|
const usage = Array.isArray(result['__llmTokenUsage'])
|
|
51
67
|
? result['__llmTokenUsage'].at(-1)
|
|
52
68
|
: undefined;
|
|
@@ -61,7 +77,13 @@ export async function withGraphSpan(state, spec, run, complete) {
|
|
|
61
77
|
: undefined,
|
|
62
78
|
...(typeof usage?.model === 'string' && usage.model ? { model: usage.model } : {}),
|
|
63
79
|
});
|
|
64
|
-
return spanId
|
|
80
|
+
return spanId
|
|
81
|
+
? {
|
|
82
|
+
...result,
|
|
83
|
+
lastSpanId: spanId,
|
|
84
|
+
...(spec.type === 'llm' ? { llmSpanId: spanId } : {}),
|
|
85
|
+
}
|
|
86
|
+
: result;
|
|
65
87
|
}
|
|
66
88
|
catch (err) {
|
|
67
89
|
await completeGraphSpan(state, spanId, {
|
|
@@ -128,8 +128,8 @@ export declare class GraphLoader {
|
|
|
128
128
|
resolveAgentDisplayName(agentCode: string, version?: string): Promise<string | undefined>;
|
|
129
129
|
private fromAgentManifest;
|
|
130
130
|
/**
|
|
131
|
-
*
|
|
132
|
-
* server
|
|
131
|
+
* 装工具只认登记的工具集(snapshot.tools / 清单 toolsets、toolRefs、tools)和内联 manifest。
|
|
132
|
+
* 图上 config.tool、节点 id 不参与。server 预装优先;dev 再读 ./tools 下与登记 code 同名的目录;desktop 合并本机安装。
|
|
133
133
|
*/
|
|
134
134
|
loadToolDefinitions(agentCode: string, options?: LoadToolDefinitionsOptions): Promise<LoadedToolDefinition[]>;
|
|
135
135
|
private loadToolDefinitionsFromSource;
|
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
* 开发态:Registry + 可选 ./tools 本地 manifest。
|
|
6
6
|
*/
|
|
7
7
|
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
8
|
-
import {
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import { resolveAgentModelAllowlist, resolveAgentDefaultModel } from './agent-models.js';
|
|
11
11
|
import { DEFAULT_HTTP_TOOL_TIMEOUT_SEC, DEFAULT_TOOL_TIMEOUT_SEC } from '../tool/timeout.js';
|
|
12
|
-
import { isMcpDiscoverStub } from '../tool/mcp-list.js';
|
|
13
12
|
import { createDeploymentSnapshotFromEnv, isInlineToolDeclaration, overlayToolRefPolicies, } from './deployment-snapshot.js';
|
|
14
13
|
import { preloadGraphNotFound, preloadManifestNotFound, preloadSkillNotFound, preloadToolNotFound, } from './preload-errors.js';
|
|
15
14
|
import { PreinstallDefinitionCache, resolveDefinitionCacheEnabled, } from './definition-cache.js';
|
|
@@ -195,14 +194,39 @@ function yamlGraphToDsl(agentCode, graph) {
|
|
|
195
194
|
config: configFromGraphDocument(graph),
|
|
196
195
|
};
|
|
197
196
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
197
|
+
/** Registry `manifest.tools` 里的字符串 code。内联对象已在前面转成定义,这里不重复取。 */
|
|
198
|
+
function manifestStringToolIds(raw) {
|
|
199
|
+
if (!Array.isArray(raw))
|
|
200
|
+
return [];
|
|
201
|
+
const out = [];
|
|
202
|
+
for (const item of raw) {
|
|
203
|
+
if (typeof item !== 'string')
|
|
204
|
+
continue;
|
|
205
|
+
const name = item.trim();
|
|
206
|
+
if (name)
|
|
207
|
+
out.push(name);
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* 还要向 Registry / 开发态目录补的登记项。
|
|
213
|
+
* 图上 `config.tool` 与节点 id 不是登记项。
|
|
214
|
+
*/
|
|
215
|
+
function pendingToolRefs(snapToolRefs, loadedRefNames, loadedNames, registryTools) {
|
|
216
|
+
const out = [];
|
|
217
|
+
const seen = new Set();
|
|
218
|
+
const push = (ref) => {
|
|
219
|
+
if (!ref.name || seen.has(ref.name) || loadedRefNames.has(ref.name) || loadedNames.has(ref.name)) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
seen.add(ref.name);
|
|
223
|
+
out.push(ref);
|
|
224
|
+
};
|
|
225
|
+
for (const ref of snapToolRefs)
|
|
226
|
+
push(ref);
|
|
227
|
+
for (const id of manifestStringToolIds(registryTools))
|
|
228
|
+
push({ name: id });
|
|
229
|
+
return out;
|
|
206
230
|
}
|
|
207
231
|
function normalizeDataScope(raw) {
|
|
208
232
|
if (raw === 'user-bound' || raw === 'department-bound' || raw === 'global')
|
|
@@ -437,27 +461,6 @@ function expandPreinstallToolset(pre, options) {
|
|
|
437
461
|
}
|
|
438
462
|
return out;
|
|
439
463
|
}
|
|
440
|
-
function fromLocalManifestFile(toolName, manifest) {
|
|
441
|
-
const permissions = manifest.permissions;
|
|
442
|
-
const metadata = manifest.metadata;
|
|
443
|
-
const manifestRef = {
|
|
444
|
-
id: String(manifest.id ?? toolName),
|
|
445
|
-
name: String(manifest.name ?? toolName),
|
|
446
|
-
version: String(manifest.version ?? '0.0.0'),
|
|
447
|
-
type: manifest.type ?? 'code',
|
|
448
|
-
status: 'active',
|
|
449
|
-
toolRuntime: (manifest.toolRuntime ?? manifest.runtime),
|
|
450
|
-
dataScope: normalizeDataScope(permissions?.dataScope),
|
|
451
|
-
requireConfirmation: Boolean(metadata?.require_confirmation),
|
|
452
|
-
execution: manifest.execution,
|
|
453
|
-
};
|
|
454
|
-
return {
|
|
455
|
-
toolId: String(manifest.id ?? toolName),
|
|
456
|
-
name: String(manifest.name ?? toolName),
|
|
457
|
-
manifest: manifestRef,
|
|
458
|
-
inputSchema: manifest.input,
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
464
|
/**
|
|
462
465
|
* 从内联 Tool 声明(manifest.tools 中的完整 ToolManifest 对象)
|
|
463
466
|
* 转为 LoadedToolDefinition。
|
|
@@ -505,52 +508,24 @@ async function readJsonObject(filePath) {
|
|
|
505
508
|
return null;
|
|
506
509
|
}
|
|
507
510
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
+
/**
|
|
512
|
+
* 开发态只读 `./tools/<code>`,展开整个工具集。不按子工具名扫相邻目录。
|
|
513
|
+
* 找到清单时返回展开结果,白名单把子工具都排除时为空数组。没有清单时返回 null。
|
|
514
|
+
*/
|
|
515
|
+
async function loadDevLocalToolset(toolsetCode, toolsDir, options) {
|
|
516
|
+
const dir = path.join(toolsDir, toolsetCode);
|
|
517
|
+
for (const file of DEV_LOCAL_MANIFEST_FILES) {
|
|
518
|
+
const doc = await readJsonObject(path.join(dir, file));
|
|
519
|
+
if (!doc)
|
|
520
|
+
continue;
|
|
511
521
|
const pre = {
|
|
512
|
-
toolId: String(doc.code ??
|
|
513
|
-
name: String(doc.name ??
|
|
522
|
+
toolId: String(doc.id ?? doc.code ?? toolsetCode),
|
|
523
|
+
name: String(doc.code ?? doc.name ?? toolsetCode),
|
|
514
524
|
version: String(doc.version ?? '0.0.0'),
|
|
515
525
|
manifest: doc,
|
|
516
526
|
bundlePath: dir,
|
|
517
527
|
};
|
|
518
|
-
|
|
519
|
-
return expanded.find((d) => d.name === toolName) ?? null;
|
|
520
|
-
}
|
|
521
|
-
if (toolsetOnly)
|
|
522
|
-
return null;
|
|
523
|
-
return fromLocalManifestFile(toolName, doc);
|
|
524
|
-
}
|
|
525
|
-
async function loadDevLocalManifestFromDir(dir, toolName, toolsetOnly) {
|
|
526
|
-
const files = toolsetOnly ? ['toolset.manifest.json'] : DEV_LOCAL_MANIFEST_FILES;
|
|
527
|
-
for (const file of files) {
|
|
528
|
-
const doc = await readJsonObject(path.join(dir, file));
|
|
529
|
-
if (!doc)
|
|
530
|
-
continue;
|
|
531
|
-
const hit = loadedToolFromDevDir(dir, toolName, doc, toolsetOnly);
|
|
532
|
-
if (hit)
|
|
533
|
-
return hit;
|
|
534
|
-
}
|
|
535
|
-
return null;
|
|
536
|
-
}
|
|
537
|
-
async function loadDevLocalManifest(toolName, toolsDir) {
|
|
538
|
-
const direct = await loadDevLocalManifestFromDir(path.join(toolsDir, toolName), toolName, false);
|
|
539
|
-
if (direct)
|
|
540
|
-
return direct;
|
|
541
|
-
let entries;
|
|
542
|
-
try {
|
|
543
|
-
entries = await readdir(toolsDir, { withFileTypes: true });
|
|
544
|
-
}
|
|
545
|
-
catch {
|
|
546
|
-
return null;
|
|
547
|
-
}
|
|
548
|
-
for (const ent of entries) {
|
|
549
|
-
if (!ent.isDirectory() || ent.name === toolName)
|
|
550
|
-
continue;
|
|
551
|
-
const hit = await loadDevLocalManifestFromDir(path.join(toolsDir, ent.name), toolName, true);
|
|
552
|
-
if (hit)
|
|
553
|
-
return hit;
|
|
528
|
+
return expandPreinstallToolset(pre, options);
|
|
554
529
|
}
|
|
555
530
|
return null;
|
|
556
531
|
}
|
|
@@ -816,8 +791,8 @@ export class GraphLoader {
|
|
|
816
791
|
return extract(record?.manifest);
|
|
817
792
|
}
|
|
818
793
|
/**
|
|
819
|
-
*
|
|
820
|
-
* server
|
|
794
|
+
* 装工具只认登记的工具集(snapshot.tools / 清单 toolsets、toolRefs、tools)和内联 manifest。
|
|
795
|
+
* 图上 config.tool、节点 id 不参与。server 预装优先;dev 再读 ./tools 下与登记 code 同名的目录;desktop 合并本机安装。
|
|
821
796
|
*/
|
|
822
797
|
async loadToolDefinitions(agentCode, options = {}) {
|
|
823
798
|
const requested = this.resolveVersion(options);
|
|
@@ -836,7 +811,8 @@ export class GraphLoader {
|
|
|
836
811
|
}
|
|
837
812
|
async loadToolDefinitionsFromSource(agentCode, options, version) {
|
|
838
813
|
const activeOnly = options.activeOnly !== false;
|
|
839
|
-
|
|
814
|
+
// 缺图时与原先一样失败。图上的 tool 名不加入下面的登记列表。
|
|
815
|
+
await this.loadGraph(agentCode, {
|
|
840
816
|
agentVersion: version,
|
|
841
817
|
bypassCache: options.bypassCache,
|
|
842
818
|
});
|
|
@@ -889,27 +865,16 @@ export class GraphLoader {
|
|
|
889
865
|
}, null, 2));
|
|
890
866
|
}
|
|
891
867
|
const snapToolRefs = overlayToolRefPolicies(snap?.tools ?? [], snap?.manifest);
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
const toolNames = [...new Set([...collectedFromGraph, ...toolNamesFromSnap])];
|
|
895
|
-
// [DEBUG] 最终 toolNames 决策
|
|
896
|
-
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
897
|
-
console.log('[LLM-DEBUG] toolNames:', JSON.stringify({
|
|
898
|
-
fromGraph: collectedFromGraph,
|
|
899
|
-
fromSnap: toolNamesFromSnap,
|
|
900
|
-
final: toolNames,
|
|
901
|
-
}));
|
|
902
|
-
}
|
|
903
|
-
if (this.deploymentSnapshot && (snapToolRefs.length > 0 || toolNames.length > 0)) {
|
|
868
|
+
const loadedRefNames = new Set();
|
|
869
|
+
if (this.deploymentSnapshot && snapToolRefs.length > 0) {
|
|
904
870
|
const missing = [];
|
|
905
|
-
const processedToolsetNames = new Set(snapToolRefs.map((r) => r.name));
|
|
906
|
-
// 1) snapshot 引用的工具集全部加载并展开(enabledTools 白名单过滤)
|
|
907
871
|
for (const ref of snapToolRefs) {
|
|
908
872
|
const pre = await this.deploymentSnapshot.loadToolManifest(ref, agentDir);
|
|
909
873
|
if (!pre) {
|
|
910
874
|
missing.push(ref.name);
|
|
911
875
|
continue;
|
|
912
876
|
}
|
|
877
|
+
loadedRefNames.add(ref.name);
|
|
913
878
|
for (const def of expandPreinstallToolset(pre, {
|
|
914
879
|
enabledTools: ref.enabledTools,
|
|
915
880
|
autoApprove: ref.autoApprove,
|
|
@@ -917,34 +882,37 @@ export class GraphLoader {
|
|
|
917
882
|
tryAddLoadedTool(definitions, byName, def);
|
|
918
883
|
}
|
|
919
884
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
885
|
+
if (this.strictPreinstall && missing.length > 0) {
|
|
886
|
+
throw preloadToolNotFound(`预装快照缺失 Tool: ${missing.join(', ')}(agent=${agentCode})`, { missing, agentCode });
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const pendingRefs = pendingToolRefs(snapToolRefs, loadedRefNames, new Set(byName.keys()), agent?.manifest?.tools);
|
|
890
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
891
|
+
console.log('[LLM-DEBUG] toolRefs:', JSON.stringify({
|
|
892
|
+
fromSnap: snapToolRefs.map((t) => t.name),
|
|
893
|
+
pending: pendingRefs.map((t) => t.name),
|
|
894
|
+
}));
|
|
895
|
+
}
|
|
896
|
+
if (this.deploymentContext === 'dev') {
|
|
897
|
+
const toolsDir = this.localToolsDir ?? process.env.EAP_LOCAL_TOOLS_DIR ?? './tools';
|
|
898
|
+
for (const ref of pendingRefs) {
|
|
899
|
+
const localDefs = await loadDevLocalToolset(ref.name, toolsDir, {
|
|
900
|
+
enabledTools: ref.enabledTools,
|
|
901
|
+
autoApprove: ref.autoApprove,
|
|
902
|
+
});
|
|
903
|
+
if (!localDefs)
|
|
933
904
|
continue;
|
|
934
|
-
|
|
935
|
-
for (const def of
|
|
905
|
+
loadedRefNames.add(ref.name);
|
|
906
|
+
for (const def of localDefs) {
|
|
936
907
|
tryAddLoadedTool(definitions, byName, def);
|
|
937
908
|
}
|
|
938
909
|
}
|
|
939
|
-
if (this.strictPreinstall && missing.length > 0) {
|
|
940
|
-
throw preloadToolNotFound(`预装快照缺失 Tool: ${missing.join(', ')}(agent=${agentCode})`, { missing, agentCode });
|
|
941
|
-
}
|
|
942
910
|
}
|
|
943
911
|
if (this.registryClient && !this.blockRegistryDefinitions) {
|
|
944
|
-
for (const
|
|
945
|
-
if (byName.has(
|
|
912
|
+
for (const ref of pendingRefs) {
|
|
913
|
+
if (loadedRefNames.has(ref.name) || byName.has(ref.name))
|
|
946
914
|
continue;
|
|
947
|
-
const record = await this.registryClient.getToolByName(
|
|
915
|
+
const record = await this.registryClient.getToolByName(ref.name);
|
|
948
916
|
if (!record)
|
|
949
917
|
continue;
|
|
950
918
|
if (activeOnly && record.status !== 'active')
|
|
@@ -952,17 +920,6 @@ export class GraphLoader {
|
|
|
952
920
|
tryAddLoadedTool(definitions, byName, toLoadedToolDefinition(record));
|
|
953
921
|
}
|
|
954
922
|
}
|
|
955
|
-
if (this.deploymentContext === 'dev') {
|
|
956
|
-
const toolsDir = this.localToolsDir ?? process.env.EAP_LOCAL_TOOLS_DIR ?? './tools';
|
|
957
|
-
for (const toolName of toolNames) {
|
|
958
|
-
if (byName.has(toolName))
|
|
959
|
-
continue;
|
|
960
|
-
const localDef = await loadDevLocalManifest(toolName, toolsDir);
|
|
961
|
-
if (localDef) {
|
|
962
|
-
tryAddLoadedTool(definitions, byName, localDef);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
923
|
if (this.deploymentContext === 'desktop' && this.localInstallStore) {
|
|
967
924
|
const localRecords = this.localInstallStore.listReady();
|
|
968
925
|
for (const install of localRecords) {
|
|
@@ -71,6 +71,8 @@ export class ToolConfirmationInterceptor {
|
|
|
71
71
|
toolSpanId: extras.toolSpanId,
|
|
72
72
|
skillSpanId: extras.skillSpanId ??
|
|
73
73
|
(typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined),
|
|
74
|
+
llmSpanId: extras.llmSpanId ??
|
|
75
|
+
(typeof state.llmSpanId === 'string' ? state.llmSpanId : undefined),
|
|
74
76
|
};
|
|
75
77
|
await this.hitlHandler.interrupt(taskId, resolvedThreadId, state, {
|
|
76
78
|
question,
|