@gt-fe/eap-runtime 0.0.29 → 0.0.31
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/api/handlers/invoke.handler.js +9 -5
- package/dist/api/handlers/stream.handler.js +15 -4
- package/dist/common/index.d.ts +1 -0
- package/dist/common/index.js +1 -0
- package/dist/common/user-root.d.ts +8 -0
- package/dist/common/user-root.js +19 -0
- package/dist/graph/adapters/langgraph.adapter.js +193 -134
- package/dist/graph/adapters/skill-read.js +13 -4
- package/dist/graph/contracts/executable-graph.d.ts +7 -1
- package/dist/graph/hitl/hitl-handler.js +6 -0
- package/dist/graph/nodes/tool-node.d.ts +4 -1
- package/dist/graph/nodes/tool-node.js +15 -1
- package/dist/graph/trace-span.d.ts +15 -1
- package/dist/graph/trace-span.js +69 -1
- package/dist/registry/deployment-snapshot.d.ts +1 -1
- package/dist/registry/deployment-snapshot.js +6 -3
- package/dist/registry/skill-tree.d.ts +1 -1
- package/dist/registry/skill-tree.js +11 -6
- package/dist/runtime/agent-runtime.js +36 -5
- package/dist/session/persist-turn-messages.d.ts +13 -6
- package/dist/session/persist-turn-messages.js +4 -0
- package/dist/sse/events/tool-events.d.ts +29 -0
- package/dist/tool/harness-command.d.ts +1 -1
- package/dist/tool/harness-command.js +8 -7
- package/dist/tool/harness-primitives.d.ts +3 -3
- package/dist/tool/harness-primitives.js +185 -60
- package/dist/tool/index.d.ts +2 -0
- package/dist/tool/index.js +2 -0
- package/dist/tool/inject-public-artifact-links.d.ts +32 -0
- package/dist/tool/inject-public-artifact-links.js +142 -0
- package/dist/tool/static-artifact-url.d.ts +66 -0
- package/dist/tool/static-artifact-url.js +561 -0
- package/dist/tool/system-prompt-builder.js +8 -4
- package/dist/tool/tool-event-emitter.d.ts +2 -1
- package/dist/tool/tool-event-emitter.js +30 -18
- package/dist/tool/tool-executor.interface.d.ts +8 -1
- package/dist/tool/tool-result-injector.d.ts +3 -3
- package/dist/tool/tool-result-injector.js +7 -3
- package/package.json +2 -2
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { randomUUID } from 'node:crypto';
|
|
18
18
|
import { buildHitlWaitingTaskOutput, loadModelFacingHistory, messageRecordsToGraphMessages, persistGraphMessageBaseline, turnGraphMessagesAfterCompression, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, sessionVisibleGraphMessages, } from '../../session/persist-turn-messages.js';
|
|
19
|
+
import { applyInjectedAssistant, collectPublicArtifactLinks, injectPublicArtifactLinks, } from '../../tool/inject-public-artifact-links.js';
|
|
19
20
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
20
21
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
21
22
|
import { recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../../token/record-task-token-usage.js';
|
|
@@ -380,21 +381,24 @@ export class InvokeOrchestrator {
|
|
|
380
381
|
},
|
|
381
382
|
]);
|
|
382
383
|
}
|
|
383
|
-
// ─── Step 7: 完成 Task (running → success) ───
|
|
384
|
-
await this.taskService.updateStatus(task.id, 'success', graphResult.output);
|
|
385
384
|
// ─── Step 7b: 持久化本轮 transcript(统一顺序:assistant tool_calls → tool → assistant 文本)───
|
|
386
385
|
// 切点是入图 messages 基线(压缩 `__set` 后 Graph 短于 Session 全文)。user 已在 4b 写入。
|
|
387
386
|
const allMessages = Array.isArray(graphResult.finalState?.messages)
|
|
388
387
|
? graphResult.finalState.messages
|
|
389
388
|
: [];
|
|
390
389
|
const newMessages = sessionVisibleGraphMessages(allMessages.slice(persistBaseline), finishPoints);
|
|
390
|
+
const links = collectPublicArtifactLinks(allMessages);
|
|
391
|
+
const { injected } = injectPublicArtifactLinks(graphResult.output ?? '', links);
|
|
392
|
+
const persisted = applyInjectedAssistant(newMessages, injected, links);
|
|
393
|
+
// ─── Step 7: 完成 Task (running → success) ───
|
|
394
|
+
await this.taskService.updateStatus(task.id, 'success', injected);
|
|
391
395
|
const duration = Date.now() - startTime;
|
|
392
396
|
await persistTurnTranscript(this.messageService, {
|
|
393
397
|
sessionId,
|
|
394
398
|
taskId: task.id,
|
|
395
399
|
correlationId,
|
|
396
|
-
messages:
|
|
397
|
-
fallbackAssistantContent:
|
|
400
|
+
messages: persisted,
|
|
401
|
+
fallbackAssistantContent: injected,
|
|
398
402
|
assistantTokenCount: tokenUsage.output,
|
|
399
403
|
assistantDurationMs: duration,
|
|
400
404
|
});
|
|
@@ -404,7 +408,7 @@ export class InvokeOrchestrator {
|
|
|
404
408
|
return this.buildResponse(traceId, {
|
|
405
409
|
taskId: task.id,
|
|
406
410
|
sessionId,
|
|
407
|
-
output:
|
|
411
|
+
output: injected,
|
|
408
412
|
tokenUsage,
|
|
409
413
|
duration,
|
|
410
414
|
status: 'success',
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* @see requirements.md Requirement 11 AC2
|
|
21
21
|
*/
|
|
22
22
|
import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, appendTerminalToolResult, } from '../../session/persist-turn-messages.js';
|
|
23
|
+
import { collectPublicArtifactLinks, collectPublicArtifactLinksFromEvent, injectPublicArtifactLinks, mergeArtifactLinks, } from '../../tool/inject-public-artifact-links.js';
|
|
23
24
|
import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
|
|
24
25
|
import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
|
|
25
26
|
import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
|
|
@@ -268,6 +269,7 @@ export class StreamOrchestrator {
|
|
|
268
269
|
let assistantReasoningContent;
|
|
269
270
|
const pendingToolCalls = [];
|
|
270
271
|
const toolResults = [];
|
|
272
|
+
const artifactLinks = [];
|
|
271
273
|
// ─── Step 6: Graph.stream() 流式执行 ───
|
|
272
274
|
try {
|
|
273
275
|
const eventStream = runWithExecutionTrace({ abortSignal }, () => this.graphEngine.stream(graph, {
|
|
@@ -326,6 +328,7 @@ export class StreamOrchestrator {
|
|
|
326
328
|
}
|
|
327
329
|
}
|
|
328
330
|
appendTerminalToolResult(runtimeEvent.type, runtimeEvent.payload, toolResults);
|
|
331
|
+
collectPublicArtifactLinksFromEvent(runtimeEvent.type, runtimeEvent.payload, artifactLinks);
|
|
329
332
|
const streamedReasoning = extractStreamReasoning(runtimeEvent.type, runtimeEvent.payload);
|
|
330
333
|
if (streamedReasoning)
|
|
331
334
|
assistantReasoningContent = streamedReasoning;
|
|
@@ -387,10 +390,18 @@ export class StreamOrchestrator {
|
|
|
387
390
|
this.timeoutManager?.clearTimer(taskId);
|
|
388
391
|
}
|
|
389
392
|
// ─── Step 7: 发送结束事件序列 ───
|
|
390
|
-
// Emit: response.final(带完整 assistant 文本,供客户端兜底)
|
|
391
393
|
const tokenStats = this.tokenStatsService.getTaskStats(taskId);
|
|
394
|
+
const visible = visibleStreamAssistantText(assistantBuckets);
|
|
395
|
+
const links = mergeArtifactLinks(artifactLinks, collectPublicArtifactLinks(toolResults));
|
|
396
|
+
const { injected, suffix } = injectPublicArtifactLinks(visible, links);
|
|
397
|
+
if (suffix) {
|
|
398
|
+
emitter.emit(this.buildSSEEvent('response.chunk', correlationId, taskId, {
|
|
399
|
+
content: suffix,
|
|
400
|
+
format: 'text',
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
392
403
|
emitter.emit(this.buildSSEEvent('response.final', correlationId, taskId, {
|
|
393
|
-
content:
|
|
404
|
+
content: injected,
|
|
394
405
|
format: 'text',
|
|
395
406
|
}));
|
|
396
407
|
const totalTokens = (tokenStats.total.input ?? 0) + (tokenStats.total.output ?? 0);
|
|
@@ -424,8 +435,8 @@ export class StreamOrchestrator {
|
|
|
424
435
|
messages: buildTurnTranscriptFromStreamEvents({
|
|
425
436
|
pendingToolCalls,
|
|
426
437
|
toolResults,
|
|
427
|
-
assistantTexts: streamAssistantTexts(assistantBuckets),
|
|
428
|
-
assistantText:
|
|
438
|
+
assistantTexts: suffix ? undefined : streamAssistantTexts(assistantBuckets),
|
|
439
|
+
assistantText: injected,
|
|
429
440
|
finishPoints: graph.finishPoints,
|
|
430
441
|
toolCallReasoningContent,
|
|
431
442
|
assistantReasoningContent,
|
package/dist/common/index.d.ts
CHANGED
package/dist/common/index.js
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const MISSING_USER_DIR = "\u7F3A\u5C11\u7528\u6237\u76EE\u5F55\uFF0C\u62D2\u7EDD\u8BE5\u64CD\u4F5C";
|
|
2
|
+
/** `agents/<code>/users/`:与 `<ver>` 平级,不是版本目录。 */
|
|
3
|
+
export declare const USERS_DIR_NAME = "users";
|
|
4
|
+
/**
|
|
5
|
+
* jail 写根:`dirname(agentDir)/users/<userId>`。
|
|
6
|
+
* `agentDir` 仍是版本目录 `agents/<code>/<ver>`。
|
|
7
|
+
*/
|
|
8
|
+
export declare function userRoot(agentDir: string, userId: string): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export const MISSING_USER_DIR = '缺少用户目录,拒绝该操作';
|
|
3
|
+
/** `agents/<code>/users/`:与 `<ver>` 平级,不是版本目录。 */
|
|
4
|
+
export const USERS_DIR_NAME = 'users';
|
|
5
|
+
/**
|
|
6
|
+
* jail 写根:`dirname(agentDir)/users/<userId>`。
|
|
7
|
+
* `agentDir` 仍是版本目录 `agents/<code>/<ver>`。
|
|
8
|
+
*/
|
|
9
|
+
export function userRoot(agentDir, userId) {
|
|
10
|
+
const dir = agentDir.trim();
|
|
11
|
+
const uid = userId.trim();
|
|
12
|
+
if (!dir || !uid) {
|
|
13
|
+
throw new Error(MISSING_USER_DIR);
|
|
14
|
+
}
|
|
15
|
+
if (uid === '.' || uid === '..' || /[\\/]/.test(uid)) {
|
|
16
|
+
throw new Error('非法用户标识');
|
|
17
|
+
}
|
|
18
|
+
return path.resolve(path.dirname(dir), USERS_DIR_NAME, uid);
|
|
19
|
+
}
|
|
@@ -36,7 +36,7 @@ import { pickContextFields } from '../../token/context-window.js';
|
|
|
36
36
|
import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
|
|
37
37
|
import { classifySkillRead } from './skill-read.js';
|
|
38
38
|
import { skillEntryInvocationFromTool } from './skill-entry-span.js';
|
|
39
|
-
import { withGraphSpan } from '../trace-span.js';
|
|
39
|
+
import { llmSpanInput, llmSpanOutputFromResult, withGraphSpan } from '../trace-span.js';
|
|
40
40
|
import { executionTraceFromState, getExecutionStream, getExecutionTrace, runWithExecutionTrace, } from '../execution-trace-context.js';
|
|
41
41
|
import { isRetryableToolFailure, resolveToolRetryConfig, sleepToolRetry, toolRetryDelayMs, } from '../../tool/tool-retry.js';
|
|
42
42
|
/**
|
|
@@ -374,6 +374,8 @@ export class LangGraphAdapter {
|
|
|
374
374
|
...(progress.toolset ? { toolset: progress.toolset } : {}),
|
|
375
375
|
content: progress.content,
|
|
376
376
|
success: true,
|
|
377
|
+
...(progress.file ? { file: progress.file } : {}),
|
|
378
|
+
...(progress.files?.length ? { files: progress.files } : {}),
|
|
377
379
|
...loc,
|
|
378
380
|
},
|
|
379
381
|
});
|
|
@@ -390,6 +392,9 @@ export class LangGraphAdapter {
|
|
|
390
392
|
...(progress.toolset ? { toolset: progress.toolset } : {}),
|
|
391
393
|
error: progress.error ?? 'Unknown error',
|
|
392
394
|
willRetry: progress.willRetry === true,
|
|
395
|
+
...(progress.content ? { content: progress.content } : {}),
|
|
396
|
+
...(progress.file ? { file: progress.file } : {}),
|
|
397
|
+
...(progress.files?.length ? { files: progress.files } : {}),
|
|
393
398
|
...loc,
|
|
394
399
|
},
|
|
395
400
|
});
|
|
@@ -788,17 +793,30 @@ export class LangGraphAdapter {
|
|
|
788
793
|
case 'tool':
|
|
789
794
|
return this.createToolNodeFunction(node);
|
|
790
795
|
case 'condition':
|
|
791
|
-
return async (state) =>
|
|
796
|
+
return async (state) => {
|
|
792
797
|
const conditionExpr = node.config?.['conditionExpr']
|
|
793
798
|
?? node.config?.['conditionFn'];
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
799
|
+
const branchValues = node.config?.['branchValues']
|
|
800
|
+
?? this.extractBranchValuesFromDsl(node.id);
|
|
801
|
+
const fallback = node.config?.['fallback'] ?? '__default__';
|
|
802
|
+
return withGraphSpan(state, {
|
|
803
|
+
name: node.id,
|
|
804
|
+
type: 'condition',
|
|
805
|
+
extra: {
|
|
806
|
+
input: {
|
|
807
|
+
conditionExpr: conditionExpr ?? null,
|
|
808
|
+
branchValues,
|
|
809
|
+
fallback,
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
}, async () => {
|
|
813
|
+
if (conditionExpr) {
|
|
814
|
+
const result = await this.conditionEvaluator.evaluate(conditionExpr, state, branchValues, fallback);
|
|
815
|
+
return { currentNode: node.id, __conditionResult: result };
|
|
816
|
+
}
|
|
817
|
+
return { currentNode: node.id };
|
|
818
|
+
}, { outputFrom: (result) => result['__conditionResult'] });
|
|
819
|
+
};
|
|
802
820
|
case 'subgraph':
|
|
803
821
|
return this.createSubGraphNodeFunction(node);
|
|
804
822
|
case 'human':
|
|
@@ -1019,136 +1037,155 @@ export class LangGraphAdapter {
|
|
|
1019
1037
|
lastAssistantHasUnansweredToolCalls(rawMessages)) {
|
|
1020
1038
|
return { currentNode: node.id };
|
|
1021
1039
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
:
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
},
|
|
1049
|
-
});
|
|
1050
|
-
if (loadedTools && loadedTools !== loadedToolsBase) {
|
|
1051
|
-
state['loadedTools'] = loadedTools;
|
|
1052
|
-
}
|
|
1053
|
-
// [DEBUG] 打印 loadedTools 实际状态
|
|
1054
|
-
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
1055
|
-
const toolNames = loadedTools ? Object.keys(loadedTools) : [];
|
|
1056
|
-
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
1057
|
-
}
|
|
1058
|
-
const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
|
|
1059
|
-
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
1060
|
-
const jail = resolveJailFromState({
|
|
1061
|
-
agentDir: state['agentDir'],
|
|
1062
|
-
userId,
|
|
1063
|
-
executionContext: execCtx,
|
|
1064
|
-
loadedTools,
|
|
1065
|
-
skillDefinitions: skillDefs,
|
|
1066
|
-
});
|
|
1067
|
-
if (this.overlayExtraRoots.length > 0) {
|
|
1068
|
-
jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
|
|
1069
|
-
}
|
|
1070
|
-
const workingDirectory = jail.workdir;
|
|
1071
|
-
const jailRoots = resolveJailRoots(jail);
|
|
1072
|
-
const skillDirs = Object.values(skillDefs ?? {})
|
|
1073
|
-
.filter((skill) => Boolean(skill.bundlePath))
|
|
1074
|
-
.map((skill) => ({
|
|
1075
|
-
skillId: skill.skillId,
|
|
1076
|
-
bundlePath: skill.bundlePath,
|
|
1077
|
-
entryFile: skill.entryFile,
|
|
1078
|
-
}));
|
|
1079
|
-
const promptInputs = resolveLlmPromptInputs({
|
|
1080
|
-
silent,
|
|
1081
|
-
agentSystemPrompt: state['agentSystemPrompt'],
|
|
1082
|
-
nodePrompt: resolvedPrompt,
|
|
1040
|
+
const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
|
|
1041
|
+
const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
|
|
1042
|
+
const loadedToolsBase = state['loadedTools'];
|
|
1043
|
+
const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
|
|
1044
|
+
const skillDefs = state['skillDefinitions'];
|
|
1045
|
+
const execCtx = state['executionContext'];
|
|
1046
|
+
const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
|
|
1047
|
+
const loadedTools = disableTools
|
|
1048
|
+
? loadedToolsBase
|
|
1049
|
+
: await hydrateMcpTools(loadedToolsBase, this.toolExecutor, {
|
|
1050
|
+
authContext: {
|
|
1051
|
+
userId: String(userId ?? ''),
|
|
1052
|
+
delegationToken: String(execCtx?.delegationToken ?? state['delegationToken'] ?? ''),
|
|
1053
|
+
agentCode: String(agentCode ?? ''),
|
|
1054
|
+
},
|
|
1055
|
+
correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
|
|
1056
|
+
taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
|
|
1057
|
+
onListFailure: ({ toolset, error }) => {
|
|
1058
|
+
this.emitToolExecutionEvents({
|
|
1059
|
+
toolCallId: `mcp-list:${toolset}`,
|
|
1060
|
+
toolName: MCP_LIST_TOOL_NAME,
|
|
1061
|
+
toolset,
|
|
1062
|
+
status: 'error',
|
|
1063
|
+
error,
|
|
1064
|
+
});
|
|
1065
|
+
},
|
|
1083
1066
|
});
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1067
|
+
if (loadedTools && loadedTools !== loadedToolsBase) {
|
|
1068
|
+
state['loadedTools'] = loadedTools;
|
|
1069
|
+
}
|
|
1070
|
+
// [DEBUG] 打印 loadedTools 实际状态
|
|
1071
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
1072
|
+
const toolNames = loadedTools ? Object.keys(loadedTools) : [];
|
|
1073
|
+
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
1074
|
+
}
|
|
1075
|
+
const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
|
|
1076
|
+
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
1077
|
+
const jail = resolveJailFromState({
|
|
1078
|
+
agentDir: state['agentDir'],
|
|
1079
|
+
userId,
|
|
1080
|
+
executionContext: execCtx,
|
|
1081
|
+
loadedTools,
|
|
1082
|
+
skillDefinitions: skillDefs,
|
|
1083
|
+
});
|
|
1084
|
+
if (this.overlayExtraRoots.length > 0) {
|
|
1085
|
+
jail.extraRoots = [...(jail.extraRoots ?? []), ...this.overlayExtraRoots];
|
|
1086
|
+
}
|
|
1087
|
+
const workingDirectory = jail.workdir;
|
|
1088
|
+
const jailRoots = resolveJailRoots(jail);
|
|
1089
|
+
const skillDirs = Object.values(skillDefs ?? {})
|
|
1090
|
+
.filter((skill) => Boolean(skill.bundlePath))
|
|
1091
|
+
.map((skill) => ({
|
|
1092
|
+
skillId: skill.skillId,
|
|
1093
|
+
bundlePath: skill.bundlePath,
|
|
1094
|
+
entryFile: skill.entryFile,
|
|
1095
|
+
}));
|
|
1096
|
+
const promptInputs = resolveLlmPromptInputs({
|
|
1097
|
+
silent,
|
|
1098
|
+
agentSystemPrompt: state['agentSystemPrompt'],
|
|
1099
|
+
nodePrompt: resolvedPrompt,
|
|
1100
|
+
});
|
|
1101
|
+
const assembled = this.systemPromptBuilder.build({
|
|
1102
|
+
agentPrompt: promptInputs.agentPrompt,
|
|
1103
|
+
nodePrompt: promptInputs.nodePrompt,
|
|
1104
|
+
skillCatalog: catalog,
|
|
1105
|
+
toolCatalog,
|
|
1106
|
+
agentCode,
|
|
1107
|
+
userId,
|
|
1108
|
+
workingDirectory,
|
|
1109
|
+
os: { platform: os.platform(), release: os.release() },
|
|
1110
|
+
jailRoots,
|
|
1111
|
+
skillDirs,
|
|
1112
|
+
});
|
|
1113
|
+
const apiTools = disableTools ? [] : loadedToolsToApiTools(loadedTools);
|
|
1114
|
+
const finalConfig = {
|
|
1115
|
+
...baseConfig,
|
|
1116
|
+
systemPrompt: assembled.text,
|
|
1117
|
+
systemSections: assembled.sections,
|
|
1118
|
+
turn: {
|
|
1119
|
+
sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
|
|
1120
|
+
taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
|
|
1121
|
+
nodeId: node.id,
|
|
1089
1122
|
agentCode,
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
// 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
|
|
1112
|
-
const stream = getExecutionStream();
|
|
1113
|
-
const enableStream = Boolean(stream) && !silent;
|
|
1114
|
-
if (stream) {
|
|
1115
|
-
stream.reasoningStartedThisTurn = false;
|
|
1116
|
-
stream.streamedTextThisTurn = false;
|
|
1117
|
-
}
|
|
1118
|
-
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
1119
|
-
? (text) => {
|
|
1120
|
-
const live = getExecutionStream();
|
|
1121
|
-
if (!live || !text)
|
|
1122
|
-
return;
|
|
1123
|
-
const timestamp = new Date().toISOString();
|
|
1124
|
-
if (!live.reasoningStartedThisTurn) {
|
|
1125
|
-
live.reasoningStartedThisTurn = true;
|
|
1126
|
-
live.iteration += 1;
|
|
1127
|
-
live.sink({
|
|
1128
|
-
type: 'reasoning.started',
|
|
1129
|
-
taskId: undefined,
|
|
1130
|
-
correlationId: undefined,
|
|
1131
|
-
timestamp,
|
|
1132
|
-
payload: {
|
|
1133
|
-
iterationCount: live.iteration,
|
|
1134
|
-
model: finalConfig.model,
|
|
1135
|
-
},
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
live.streamedTextThisTurn = true;
|
|
1123
|
+
correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
|
|
1124
|
+
},
|
|
1125
|
+
tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
|
|
1126
|
+
toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
|
|
1127
|
+
};
|
|
1128
|
+
// 流式旁路:有本次 stream 袋时启用 LLM token 推送;silent 节点不推给用户
|
|
1129
|
+
const stream = getExecutionStream();
|
|
1130
|
+
const enableStream = Boolean(stream) && !silent;
|
|
1131
|
+
if (stream) {
|
|
1132
|
+
stream.reasoningStartedThisTurn = false;
|
|
1133
|
+
stream.streamedTextThisTurn = false;
|
|
1134
|
+
}
|
|
1135
|
+
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
1136
|
+
? (text) => {
|
|
1137
|
+
const live = getExecutionStream();
|
|
1138
|
+
if (!live || !text)
|
|
1139
|
+
return;
|
|
1140
|
+
const timestamp = new Date().toISOString();
|
|
1141
|
+
if (!live.reasoningStartedThisTurn) {
|
|
1142
|
+
live.reasoningStartedThisTurn = true;
|
|
1143
|
+
live.iteration += 1;
|
|
1139
1144
|
live.sink({
|
|
1140
|
-
type: '
|
|
1145
|
+
type: 'reasoning.started',
|
|
1141
1146
|
taskId: undefined,
|
|
1142
1147
|
correlationId: undefined,
|
|
1143
1148
|
timestamp,
|
|
1144
1149
|
payload: {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
node: node.id,
|
|
1150
|
+
iterationCount: live.iteration,
|
|
1151
|
+
model: finalConfig.model,
|
|
1148
1152
|
},
|
|
1149
1153
|
});
|
|
1150
1154
|
}
|
|
1151
|
-
|
|
1155
|
+
live.streamedTextThisTurn = true;
|
|
1156
|
+
live.sink({
|
|
1157
|
+
type: 'response.chunk',
|
|
1158
|
+
taskId: undefined,
|
|
1159
|
+
correlationId: undefined,
|
|
1160
|
+
timestamp,
|
|
1161
|
+
payload: {
|
|
1162
|
+
content: text,
|
|
1163
|
+
index: live.chunkIndex++,
|
|
1164
|
+
node: node.id,
|
|
1165
|
+
},
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
: undefined, this.interceptors);
|
|
1169
|
+
const toolsForSpan = disableTools || !loadedTools
|
|
1170
|
+
? undefined
|
|
1171
|
+
: Object.values(loadedTools).map((t) => ({
|
|
1172
|
+
name: t.name,
|
|
1173
|
+
...(t.manifest.description?.trim()
|
|
1174
|
+
? { description: t.manifest.description.trim() }
|
|
1175
|
+
: {}),
|
|
1176
|
+
}));
|
|
1177
|
+
// 组装后再 createNode:input 必须是发给模型的 system/tools/messages;completeNode 不能补 input。
|
|
1178
|
+
return withGraphSpan(state, {
|
|
1179
|
+
name: node.id,
|
|
1180
|
+
type: 'llm',
|
|
1181
|
+
extra: {
|
|
1182
|
+
input: llmSpanInput({
|
|
1183
|
+
system: assembled.text,
|
|
1184
|
+
tools: toolsForSpan,
|
|
1185
|
+
messages: state['messages'],
|
|
1186
|
+
}),
|
|
1187
|
+
},
|
|
1188
|
+
}, async () => {
|
|
1152
1189
|
const next = await llmNode.execute(state);
|
|
1153
1190
|
const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
|
|
1154
1191
|
if (!silent)
|
|
@@ -1163,7 +1200,7 @@ export class LangGraphAdapter {
|
|
|
1163
1200
|
silentOutput: raw,
|
|
1164
1201
|
...hydratedUpdate,
|
|
1165
1202
|
};
|
|
1166
|
-
});
|
|
1203
|
+
}, { outputFrom: llmSpanOutputFromResult });
|
|
1167
1204
|
};
|
|
1168
1205
|
}
|
|
1169
1206
|
/**
|
|
@@ -1560,10 +1597,20 @@ export class LangGraphAdapter {
|
|
|
1560
1597
|
continue;
|
|
1561
1598
|
}
|
|
1562
1599
|
if (msg.role === 'tool') {
|
|
1563
|
-
// tool.result / tool.error 由 Tool
|
|
1600
|
+
// tool.result / tool.error 由 Tool 节点旁路发出(已带 file/files);此处再补 file.created
|
|
1564
1601
|
const raw = typeof msg.content === 'string' ? msg.content : '';
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1602
|
+
const files = [
|
|
1603
|
+
...(msg.file ? [msg.file] : []),
|
|
1604
|
+
...(msg.files ?? []),
|
|
1605
|
+
];
|
|
1606
|
+
if (files.length === 0) {
|
|
1607
|
+
const parsed = fileArtifactFromToolOutput(raw);
|
|
1608
|
+
if (parsed)
|
|
1609
|
+
files.push(parsed);
|
|
1610
|
+
}
|
|
1611
|
+
for (const file of files) {
|
|
1612
|
+
if (!file?.url)
|
|
1613
|
+
continue;
|
|
1567
1614
|
yield {
|
|
1568
1615
|
type: 'file.created',
|
|
1569
1616
|
taskId: undefined,
|
|
@@ -1630,6 +1677,7 @@ export class LangGraphAdapter {
|
|
|
1630
1677
|
success: typeof m['success'] === 'boolean' ? m['success'] : undefined,
|
|
1631
1678
|
reasoningContent: typeof m['reasoningContent'] === 'string' ? m['reasoningContent'] : undefined,
|
|
1632
1679
|
file: readFileArtifact(m['file']),
|
|
1680
|
+
files: readFileArtifactList(m['files']),
|
|
1633
1681
|
};
|
|
1634
1682
|
});
|
|
1635
1683
|
}
|
|
@@ -1647,3 +1695,14 @@ function readFileArtifact(value) {
|
|
|
1647
1695
|
url: rec.url,
|
|
1648
1696
|
};
|
|
1649
1697
|
}
|
|
1698
|
+
function readFileArtifactList(value) {
|
|
1699
|
+
if (!Array.isArray(value))
|
|
1700
|
+
return undefined;
|
|
1701
|
+
const out = [];
|
|
1702
|
+
for (const item of value) {
|
|
1703
|
+
const file = readFileArtifact(item);
|
|
1704
|
+
if (file)
|
|
1705
|
+
out.push(file);
|
|
1706
|
+
}
|
|
1707
|
+
return out.length > 0 ? out : undefined;
|
|
1708
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { userRoot } from '../../common/user-root.js';
|
|
3
4
|
function tryRealpath(p) {
|
|
4
5
|
try {
|
|
5
6
|
return realpathSync(p);
|
|
@@ -49,12 +50,20 @@ export function classifySkillSpace(bundleOrFilePath, opts) {
|
|
|
49
50
|
return 'agent';
|
|
50
51
|
const uid = opts?.userId?.trim();
|
|
51
52
|
if (uid) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
try {
|
|
54
|
+
const user = normalizeForMatch(path.join(userRoot(agentDir, uid), 'skills'));
|
|
55
|
+
if (target === user || target.startsWith(`${user}/`))
|
|
56
|
+
return 'user';
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// 非法 userId 时不按用户槽判定
|
|
60
|
+
}
|
|
55
61
|
}
|
|
56
|
-
|
|
62
|
+
const usersRoot = normalizeForMatch(path.join(path.dirname(agentDir), 'users'));
|
|
63
|
+
if ((target === usersRoot || target.startsWith(`${usersRoot}/`)) &&
|
|
64
|
+
/(^|\/)skills(\/|$)/.test(target.slice(usersRoot.length))) {
|
|
57
65
|
return 'user';
|
|
66
|
+
}
|
|
58
67
|
return 'local';
|
|
59
68
|
}
|
|
60
69
|
if (/(^|\/)preinstall\/skills(\/|$)/i.test(target))
|
|
@@ -276,13 +276,19 @@ export interface GraphMessage {
|
|
|
276
276
|
* 多轮请求必须原样回传,否则上游会 400。
|
|
277
277
|
*/
|
|
278
278
|
reasoningContent?: string;
|
|
279
|
-
/**
|
|
279
|
+
/** 工具产出的文件;SSE tool.result / tool.error 与 file.created 共用,不进模型正文 */
|
|
280
280
|
file?: {
|
|
281
281
|
fileName: string;
|
|
282
282
|
fileType: string;
|
|
283
283
|
size: number;
|
|
284
284
|
url: string;
|
|
285
285
|
};
|
|
286
|
+
files?: Array<{
|
|
287
|
+
fileName: string;
|
|
288
|
+
fileType: string;
|
|
289
|
+
size: number;
|
|
290
|
+
url: string;
|
|
291
|
+
}>;
|
|
286
292
|
}
|
|
287
293
|
/** Tool 调用信息 */
|
|
288
294
|
export interface ToolCallInfo {
|
|
@@ -289,6 +289,12 @@ export class HitlHandler {
|
|
|
289
289
|
name: extras.toolName ?? 'hitl',
|
|
290
290
|
type: 'hitl',
|
|
291
291
|
parentSpanId: extras.toolSpanId ?? skillSpanId,
|
|
292
|
+
extra: {
|
|
293
|
+
input: {
|
|
294
|
+
question: config.question,
|
|
295
|
+
options: config.options,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
292
298
|
});
|
|
293
299
|
const persisted = {
|
|
294
300
|
taskId,
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @see requirements.md Requirement 8 AC1, AC3, AC5
|
|
11
11
|
*/
|
|
12
12
|
import type { NodeRetryConfig } from '../contracts/executable-graph.js';
|
|
13
|
-
import type { AuthContext } from '../../tool/tool-executor.interface.js';
|
|
13
|
+
import type { AuthContext, ToolCallResult } from '../../tool/tool-executor.interface.js';
|
|
14
14
|
import type { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
|
|
15
15
|
import type { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
16
16
|
import type { SkillDirRef } from '../adapters/skill-read.js';
|
|
@@ -41,6 +41,9 @@ export interface ToolNodeProgress {
|
|
|
41
41
|
arguments?: Record<string, unknown>;
|
|
42
42
|
content?: string;
|
|
43
43
|
error?: string;
|
|
44
|
+
/** 与 ToolCallResult.file / files 同形;completed 与失败终态都带 */
|
|
45
|
+
file?: ToolCallResult['file'];
|
|
46
|
+
files?: ToolCallResult['files'];
|
|
44
47
|
executionTime?: number;
|
|
45
48
|
node?: string;
|
|
46
49
|
skillId?: string;
|