@gt-fe/eap-runtime 0.0.31 → 0.0.33
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 +3 -1
- package/dist/api/handlers/stream.handler.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +0 -1
- package/dist/graph/adapters/langgraph.adapter.js +36 -26
- package/dist/graph/adapters/skill-entry-span.d.ts +4 -2
- package/dist/graph/adapters/skill-entry-span.js +14 -4
- package/dist/graph/adapters/skill-read.d.ts +18 -2
- package/dist/graph/adapters/skill-read.js +53 -27
- package/dist/graph/adapters/subgraph-compiler.js +11 -9
- package/dist/graph/contracts/executable-graph.d.ts +2 -0
- package/dist/graph/nodes/skill-bind-node.d.ts +1 -1
- package/dist/graph/nodes/skill-bind-node.js +6 -1
- package/dist/graph/nodes/tool-node.d.ts +2 -0
- package/dist/graph/nodes/tool-node.js +4 -2
- package/dist/registry/graph-loader.d.ts +11 -0
- package/dist/registry/graph-loader.js +28 -4
- package/dist/runtime/agent-runtime.js +10 -1
- package/dist/tool/harness-primitives.d.ts +1 -7
- package/dist/tool/harness-primitives.js +4 -70
- package/dist/tool/index.d.ts +1 -1
- package/dist/tool/index.js +1 -1
- package/dist/tool/mcp-list.d.ts +3 -1
- package/dist/tool/mcp-list.js +4 -1
- package/dist/tool/skill-relative-read.d.ts +14 -0
- package/dist/tool/skill-relative-read.js +98 -0
- package/dist/tool/system-prompt-builder.d.ts +8 -2
- package/dist/tool/system-prompt-builder.js +36 -8
- package/dist/tool/tool-executor.interface.d.ts +5 -0
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ import { classifyRuntimeError } from '../../utils/http-error.js';
|
|
|
27
27
|
import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
|
|
28
28
|
import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
|
|
29
29
|
import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
|
|
30
|
-
import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
|
|
30
|
+
import { resolveAgentDisplayNameFromLoader, resolveAgentSystemPromptFromLoader, } from '../../registry/graph-loader.js';
|
|
31
31
|
import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
|
|
32
32
|
import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
|
|
33
33
|
import { assembleLoadedTools } from '../../tool/harness-primitives.js';
|
|
@@ -204,6 +204,7 @@ export class InvokeOrchestrator {
|
|
|
204
204
|
? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
|
|
205
205
|
: undefined;
|
|
206
206
|
const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
|
|
207
|
+
const agentName = await resolveAgentDisplayNameFromLoader(this.graphLoader, agentCode, req.agentVersion);
|
|
207
208
|
// 模型上下文:Checkpoint 已有 messages 则压它;压成功用 __set 写回最新 Checkpoint。Message 库不改。
|
|
208
209
|
const facing = await loadModelFacingHistory({
|
|
209
210
|
sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
|
|
@@ -261,6 +262,7 @@ export class InvokeOrchestrator {
|
|
|
261
262
|
promptRoots,
|
|
262
263
|
agentDir,
|
|
263
264
|
agentSystemPrompt,
|
|
265
|
+
agentName,
|
|
264
266
|
traceId: resolveTraceId(req),
|
|
265
267
|
traceCollector: this.traceCollector,
|
|
266
268
|
abortSignal,
|
|
@@ -31,7 +31,7 @@ import { SSEEmitter } from '../../sse/sse-emitter.js';
|
|
|
31
31
|
import { resolveExecutionContext } from '../../governance/governance-client.js';
|
|
32
32
|
import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
|
|
33
33
|
import { ErrorCodes } from '@gt-fe/eap-contracts';
|
|
34
|
-
import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
|
|
34
|
+
import { resolveAgentDisplayNameFromLoader, resolveAgentSystemPromptFromLoader, } from '../../registry/graph-loader.js';
|
|
35
35
|
import { classifyRuntimeError } from '../../utils/http-error.js';
|
|
36
36
|
import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
|
|
37
37
|
import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
|
|
@@ -222,6 +222,7 @@ export class StreamOrchestrator {
|
|
|
222
222
|
? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
|
|
223
223
|
: undefined;
|
|
224
224
|
const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
|
|
225
|
+
const agentName = await resolveAgentDisplayNameFromLoader(this.graphLoader, agentCode, req.agentVersion);
|
|
225
226
|
// 模型上下文:Checkpoint 已有 messages 则压它;压成功用 __set 写回最新 Checkpoint。Message 库不改。
|
|
226
227
|
const facing = await loadModelFacingHistory({
|
|
227
228
|
sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
|
|
@@ -293,6 +294,7 @@ export class StreamOrchestrator {
|
|
|
293
294
|
promptRoots,
|
|
294
295
|
agentDir,
|
|
295
296
|
agentSystemPrompt,
|
|
297
|
+
agentName,
|
|
296
298
|
traceId: resolveTraceId(req),
|
|
297
299
|
traceCollector: this.traceCollector,
|
|
298
300
|
abortSignal,
|
|
@@ -127,7 +127,6 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
127
127
|
private createNodeFunction;
|
|
128
128
|
private createNodeFunctionBody;
|
|
129
129
|
private createSkillBindNodeFunction;
|
|
130
|
-
private buildSkillCatalog;
|
|
131
130
|
private createToolNodeFunction;
|
|
132
131
|
private createLLMNodeFunction;
|
|
133
132
|
/**
|
|
@@ -23,7 +23,7 @@ import { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
|
23
23
|
import { fileArtifactFromToolOutput } from '../../tool/file-artifact.js';
|
|
24
24
|
import { EchoLLMProvider } from '../../providers/echo-llm-provider.js';
|
|
25
25
|
import { ConditionEvaluator } from '../condition-evaluator.js';
|
|
26
|
-
import { SystemPromptBuilder, buildToolCatalog, loadedToolsToApiTools, resolveLlmPromptInputs } from '../../tool/system-prompt-builder.js';
|
|
26
|
+
import { SystemPromptBuilder, buildSkillCatalog, buildToolCatalog, loadedToolsToApiTools, resolveLlmPromptInputs, spokenName } from '../../tool/system-prompt-builder.js';
|
|
27
27
|
import { resolveJailFromState, resolveJailRoots } from '../../tool/harness-primitives.js';
|
|
28
28
|
import os from 'node:os';
|
|
29
29
|
import { bindSelectedSkill } from '../nodes/skill-bind-node.js';
|
|
@@ -34,8 +34,8 @@ import { preloadSkillNotFound } from '../../registry/preload-errors.js';
|
|
|
34
34
|
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
35
35
|
import { pickContextFields } from '../../token/context-window.js';
|
|
36
36
|
import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
|
|
37
|
-
import {
|
|
38
|
-
import { skillEntryInvocationFromTool } from './skill-entry-span.js';
|
|
37
|
+
import { classifySkillFile, isReadTool } from './skill-read.js';
|
|
38
|
+
import { recordSkillEntrySpan, skillEntryInvocationFromTool } from './skill-entry-span.js';
|
|
39
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';
|
|
@@ -283,8 +283,10 @@ export class LangGraphAdapter {
|
|
|
283
283
|
...(progress.node ? { node: progress.node } : {}),
|
|
284
284
|
...(progress.skillId ? { skillId: progress.skillId } : {}),
|
|
285
285
|
};
|
|
286
|
-
const skillRead =
|
|
287
|
-
|
|
286
|
+
const skillRead = progress.status === 'completed' && progress.resolvedPath && isReadTool(progress.toolName)
|
|
287
|
+
? classifySkillFile(progress.resolvedPath, progress.skillDirs)
|
|
288
|
+
: null;
|
|
289
|
+
if (skillRead) {
|
|
288
290
|
if (skillRead.kind === 'loaded') {
|
|
289
291
|
const skillId = skillRead.skillId || loc.skillId || skillRead.path;
|
|
290
292
|
stream.sink({
|
|
@@ -299,11 +301,12 @@ export class LangGraphAdapter {
|
|
|
299
301
|
});
|
|
300
302
|
const catalogSkill = skillEntryInvocationFromTool({
|
|
301
303
|
toolName: progress.toolName,
|
|
302
|
-
|
|
304
|
+
resolvedPath: progress.resolvedPath,
|
|
303
305
|
skillDirs: progress.skillDirs,
|
|
304
306
|
alreadyInSkillSpan: progress.inSkillSubgraph,
|
|
305
307
|
});
|
|
306
308
|
if (catalogSkill) {
|
|
309
|
+
const skillName = spokenName(progress.skillName, catalogSkill.skillId) ?? catalogSkill.skillId;
|
|
307
310
|
stream.sink({
|
|
308
311
|
type: 'skill.started',
|
|
309
312
|
taskId,
|
|
@@ -311,7 +314,7 @@ export class LangGraphAdapter {
|
|
|
311
314
|
timestamp,
|
|
312
315
|
payload: {
|
|
313
316
|
skillId: catalogSkill.skillId,
|
|
314
|
-
skillName
|
|
317
|
+
skillName,
|
|
315
318
|
path: catalogSkill.path,
|
|
316
319
|
...(progress.skillSpace ? { skillSpace: progress.skillSpace } : {}),
|
|
317
320
|
...(progress.skillVersion ? { skillVersion: progress.skillVersion } : {}),
|
|
@@ -324,7 +327,7 @@ export class LangGraphAdapter {
|
|
|
324
327
|
timestamp,
|
|
325
328
|
payload: {
|
|
326
329
|
skillId: catalogSkill.skillId,
|
|
327
|
-
skillName
|
|
330
|
+
skillName,
|
|
328
331
|
},
|
|
329
332
|
});
|
|
330
333
|
}
|
|
@@ -355,12 +358,14 @@ export class LangGraphAdapter {
|
|
|
355
358
|
},
|
|
356
359
|
});
|
|
357
360
|
}
|
|
361
|
+
// resolvedPath / skillDirs 只给上面 classifySkillFile;inSkillSubgraph 只给 skill.started 去重。不进对话流。
|
|
362
|
+
const { resolvedPath: _resolvedPath, skillDirs: _skillDirs, inSkillSubgraph: _inSkillSubgraph, ...progressView } = progress;
|
|
358
363
|
stream.sink({
|
|
359
364
|
type: 'tool.progress',
|
|
360
365
|
taskId,
|
|
361
366
|
correlationId,
|
|
362
367
|
timestamp,
|
|
363
|
-
payload:
|
|
368
|
+
payload: progressView,
|
|
364
369
|
});
|
|
365
370
|
if (progress.status === 'completed') {
|
|
366
371
|
stream.sink({
|
|
@@ -456,6 +461,7 @@ export class LangGraphAdapter {
|
|
|
456
461
|
// agentId 已废弃:channel 保留仅为旧 Checkpoint 反序列化兼容,新代码只写 agentCode
|
|
457
462
|
agentId: ephemeral(''),
|
|
458
463
|
agentCode: ephemeral(''),
|
|
464
|
+
agentName: ephemeral(undefined),
|
|
459
465
|
agentVersion: ephemeral(undefined),
|
|
460
466
|
delegationToken: ephemeral(''),
|
|
461
467
|
correlationId: ephemeral(''),
|
|
@@ -852,7 +858,7 @@ export class LangGraphAdapter {
|
|
|
852
858
|
correlationId: undefined,
|
|
853
859
|
timestamp: new Date().toISOString(),
|
|
854
860
|
payload: {
|
|
855
|
-
skillName: patch.skillName
|
|
861
|
+
skillName: spokenName(patch.skillName, patch.activatedSkill) ?? patch.activatedSkill,
|
|
856
862
|
skillId: patch.activatedSkill,
|
|
857
863
|
confidence: 1,
|
|
858
864
|
},
|
|
@@ -894,20 +900,6 @@ export class LangGraphAdapter {
|
|
|
894
900
|
};
|
|
895
901
|
};
|
|
896
902
|
}
|
|
897
|
-
buildSkillCatalog(defs) {
|
|
898
|
-
if (!defs)
|
|
899
|
-
return undefined;
|
|
900
|
-
const rows = Object.values(defs).map((skill) => {
|
|
901
|
-
const desc = (skill.description ?? '').replace(/\s+/g, ' ').trim();
|
|
902
|
-
const entry = skill.entryFile ?? (skill.bundlePath ? `${skill.bundlePath}/SKILL.md` : '');
|
|
903
|
-
const title = skill.name && skill.name !== skill.skillId ? `${skill.skillId}(${skill.name})` : skill.skillId;
|
|
904
|
-
const pathLine = entry ? `\n SKILL.md: ${entry}` : '';
|
|
905
|
-
return `- **${title}**${desc ? `:${desc}` : ''}${pathLine}`;
|
|
906
|
-
});
|
|
907
|
-
if (rows.length === 0)
|
|
908
|
-
return undefined;
|
|
909
|
-
return `# 可用技能\n要用某个技能时,调用 read_file 读取其 SKILL.md。脚本和参考文档默认在同一目录。\n${rows.join('\n')}`;
|
|
910
|
-
}
|
|
911
903
|
createToolNodeFunction(node) {
|
|
912
904
|
const toolName = node.config?.['tool'] ?? node.id;
|
|
913
905
|
const defaultArgs = node.config?.['arguments'] ?? {};
|
|
@@ -936,6 +928,13 @@ export class LangGraphAdapter {
|
|
|
936
928
|
node: node.id,
|
|
937
929
|
});
|
|
938
930
|
const skillDefs = state['skillDefinitions'];
|
|
931
|
+
const jail = resolveJailFromState({
|
|
932
|
+
agentDir: state['agentDir'],
|
|
933
|
+
userId: state['userId'],
|
|
934
|
+
executionContext: state['executionContext'],
|
|
935
|
+
loadedTools: state['loadedTools'],
|
|
936
|
+
skillDefinitions: skillDefs,
|
|
937
|
+
});
|
|
939
938
|
const jailExtraRoots = Object.values(skillDefs ?? {})
|
|
940
939
|
.map((skill) => skill.bundlePath)
|
|
941
940
|
.filter((p) => Boolean(p));
|
|
@@ -946,6 +945,7 @@ export class LangGraphAdapter {
|
|
|
946
945
|
authContext: { userId, delegationToken, agentCode },
|
|
947
946
|
correlationId,
|
|
948
947
|
taskId,
|
|
948
|
+
...(jail.workdir ? { jailWorkdir: jail.workdir } : {}),
|
|
949
949
|
...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
|
|
950
950
|
};
|
|
951
951
|
const retry = resolveToolRetryConfig(node.retry);
|
|
@@ -981,13 +981,21 @@ export class LangGraphAdapter {
|
|
|
981
981
|
executionTime,
|
|
982
982
|
node: node.id,
|
|
983
983
|
...(skillDirs.length > 0 ? { skillDirs } : {}),
|
|
984
|
+
...(result.resolvedPath ? { resolvedPath: result.resolvedPath } : {}),
|
|
984
985
|
});
|
|
986
|
+
if (result.success) {
|
|
987
|
+
await recordSkillEntrySpan(state, {
|
|
988
|
+
toolName: result.toolName,
|
|
989
|
+
resolvedPath: result.resolvedPath,
|
|
990
|
+
skillDirs,
|
|
991
|
+
});
|
|
992
|
+
}
|
|
985
993
|
// 重要:messages channel 的 reducer 是 append 语义,只返回新增消息
|
|
986
994
|
return {
|
|
987
995
|
messages: [
|
|
988
996
|
{
|
|
989
997
|
role: 'tool',
|
|
990
|
-
content: result.content
|
|
998
|
+
content: result.content ?? '',
|
|
991
999
|
toolCallId,
|
|
992
1000
|
toolName,
|
|
993
1001
|
success: result.success ?? true,
|
|
@@ -1041,6 +1049,7 @@ export class LangGraphAdapter {
|
|
|
1041
1049
|
const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
|
|
1042
1050
|
const loadedToolsBase = state['loadedTools'];
|
|
1043
1051
|
const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
|
|
1052
|
+
const agentName = typeof state['agentName'] === 'string' ? state['agentName'] : undefined;
|
|
1044
1053
|
const skillDefs = state['skillDefinitions'];
|
|
1045
1054
|
const execCtx = state['executionContext'];
|
|
1046
1055
|
const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
|
|
@@ -1072,7 +1081,7 @@ export class LangGraphAdapter {
|
|
|
1072
1081
|
const toolNames = loadedTools ? Object.keys(loadedTools) : [];
|
|
1073
1082
|
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
1074
1083
|
}
|
|
1075
|
-
const catalog = includeSkillCatalog ?
|
|
1084
|
+
const catalog = includeSkillCatalog ? buildSkillCatalog(skillDefs) : undefined;
|
|
1076
1085
|
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
1077
1086
|
const jail = resolveJailFromState({
|
|
1078
1087
|
agentDir: state['agentDir'],
|
|
@@ -1103,6 +1112,7 @@ export class LangGraphAdapter {
|
|
|
1103
1112
|
nodePrompt: promptInputs.nodePrompt,
|
|
1104
1113
|
skillCatalog: catalog,
|
|
1105
1114
|
toolCatalog,
|
|
1115
|
+
agentName,
|
|
1106
1116
|
agentCode,
|
|
1107
1117
|
userId,
|
|
1108
1118
|
workingDirectory,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* 成功读 SKILL.md 时写 Trace `type=skill` Span。
|
|
3
3
|
*
|
|
4
4
|
* 假设:Skill 被调用 = 一次成功读取技能包入口(SKILL.md),
|
|
5
|
+
* 入口是执行器返回的实际文件(resolvedPath)对已装配 skillDirs。
|
|
6
|
+
* 没有实际路径不记,不再用 arguments.path 猜。
|
|
5
7
|
* 与子图激活走同一 collector.createNode({ type: 'skill' })。
|
|
6
8
|
* 声明版本在 createNode.version;skillSpace 只在 input。
|
|
7
9
|
* 子图已有 skillSpanId 时不记第二条。write_file / 包内附属读 / 失败读不是调用。
|
|
@@ -23,7 +25,7 @@ export declare function skillSpanFields(identity: SkillTraceIdentity, extra?: {
|
|
|
23
25
|
};
|
|
24
26
|
export declare function skillEntryInvocationFromTool(input: {
|
|
25
27
|
toolName: string;
|
|
26
|
-
|
|
28
|
+
resolvedPath?: string;
|
|
27
29
|
skillDirs?: SkillDirRef[];
|
|
28
30
|
alreadyInSkillSpan?: boolean;
|
|
29
31
|
}): {
|
|
@@ -32,6 +34,6 @@ export declare function skillEntryInvocationFromTool(input: {
|
|
|
32
34
|
} | null;
|
|
33
35
|
export declare function recordSkillEntrySpan(state: Record<string, unknown>, input: {
|
|
34
36
|
toolName: string;
|
|
35
|
-
|
|
37
|
+
resolvedPath?: string;
|
|
36
38
|
skillDirs?: SkillDirRef[];
|
|
37
39
|
}): Promise<void>;
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* 成功读 SKILL.md 时写 Trace `type=skill` Span。
|
|
3
3
|
*
|
|
4
4
|
* 假设:Skill 被调用 = 一次成功读取技能包入口(SKILL.md),
|
|
5
|
+
* 入口是执行器返回的实际文件(resolvedPath)对已装配 skillDirs。
|
|
6
|
+
* 没有实际路径不记,不再用 arguments.path 猜。
|
|
5
7
|
* 与子图激活走同一 collector.createNode({ type: 'skill' })。
|
|
6
8
|
* 声明版本在 createNode.version;skillSpace 只在 input。
|
|
7
9
|
* 子图已有 skillSpanId 时不记第二条。write_file / 包内附属读 / 失败读不是调用。
|
|
8
10
|
*/
|
|
9
|
-
import {
|
|
11
|
+
import { classifySkillFile, classifySkillSpace, isReadTool, } from './skill-read.js';
|
|
10
12
|
import { completeGraphSpan, startGraphSpan } from '../trace-span.js';
|
|
13
|
+
import { spokenName } from '../../tool/system-prompt-builder.js';
|
|
11
14
|
function declaredSkillVersion(skillId, version) {
|
|
12
15
|
const raw = version?.trim();
|
|
13
16
|
if (!raw || raw === skillId)
|
|
@@ -25,9 +28,10 @@ export function skillTraceIdentity(state, skillId, fallbackPath) {
|
|
|
25
28
|
const bundle = info?.bundlePath || fallbackPath;
|
|
26
29
|
const skillSpace = bundle ? classifySkillSpace(bundle, { agentDir, userId }) : undefined;
|
|
27
30
|
const skillVersion = declaredSkillVersion(skillId, info?.version);
|
|
31
|
+
const skillName = spokenName(info?.name, undefined);
|
|
28
32
|
return {
|
|
29
33
|
skillId,
|
|
30
|
-
...(
|
|
34
|
+
...(skillName ? { skillName } : {}),
|
|
31
35
|
...(skillVersion ? { skillVersion } : {}),
|
|
32
36
|
...(skillSpace ? { skillSpace } : {}),
|
|
33
37
|
};
|
|
@@ -37,6 +41,7 @@ export function skillSpanFields(identity, extra) {
|
|
|
37
41
|
...(identity.skillVersion ? { version: identity.skillVersion } : {}),
|
|
38
42
|
input: {
|
|
39
43
|
skillId: identity.skillId,
|
|
44
|
+
...(identity.skillName ? { skillName: identity.skillName } : {}),
|
|
40
45
|
...(identity.skillSpace ? { skillSpace: identity.skillSpace } : {}),
|
|
41
46
|
...(extra?.path ? { path: extra.path } : {}),
|
|
42
47
|
...(extra?.source ? { source: extra.source } : {}),
|
|
@@ -46,7 +51,12 @@ export function skillSpanFields(identity, extra) {
|
|
|
46
51
|
export function skillEntryInvocationFromTool(input) {
|
|
47
52
|
if (input.alreadyInSkillSpan)
|
|
48
53
|
return null;
|
|
49
|
-
|
|
54
|
+
if (!isReadTool(input.toolName))
|
|
55
|
+
return null;
|
|
56
|
+
const resolved = input.resolvedPath?.trim();
|
|
57
|
+
if (!resolved)
|
|
58
|
+
return null;
|
|
59
|
+
const hit = classifySkillFile(resolved, input.skillDirs);
|
|
50
60
|
if (!hit || hit.kind !== 'loaded' || !hit.skillId)
|
|
51
61
|
return null;
|
|
52
62
|
return { skillId: hit.skillId, path: hit.path };
|
|
@@ -55,7 +65,7 @@ export async function recordSkillEntrySpan(state, input) {
|
|
|
55
65
|
const alreadyInSkillSpan = typeof state['skillSpanId'] === 'string' && Boolean(state['skillSpanId']);
|
|
56
66
|
const hit = skillEntryInvocationFromTool({
|
|
57
67
|
toolName: input.toolName,
|
|
58
|
-
|
|
68
|
+
resolvedPath: input.resolvedPath,
|
|
59
69
|
skillDirs: input.skillDirs,
|
|
60
70
|
alreadyInSkillSpan,
|
|
61
71
|
});
|
|
@@ -12,10 +12,26 @@ export declare function classifySkillSpace(bundleOrFilePath: string, opts?: {
|
|
|
12
12
|
agentDir?: string;
|
|
13
13
|
userId?: string;
|
|
14
14
|
}): SkillSpace | undefined;
|
|
15
|
+
/** 与 Harness `read_file` 相同的相对路径定位(workdir / skillDir / overlay extraRoots)。 */
|
|
16
|
+
export interface SkillReadLocate {
|
|
17
|
+
workdir?: string;
|
|
18
|
+
skillDir?: string;
|
|
19
|
+
extraRoots?: string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function isReadTool(toolName: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* 实际读到的文件是否落在已装配技能包内。不解析定位符。
|
|
24
|
+
*/
|
|
25
|
+
export declare function classifySkillFile(absPath: string, skillDirs?: SkillDirRef[]): {
|
|
26
|
+
kind: 'loaded' | 'resource';
|
|
27
|
+
path: string;
|
|
28
|
+
skillId: string;
|
|
29
|
+
} | null;
|
|
15
30
|
/**
|
|
16
|
-
*
|
|
31
|
+
* 给 EAP_SKILL_DIR 推断:把 `arguments.path` 按 Harness 规则落到文件再对 skillDirs。
|
|
32
|
+
* Trace / SSE 入口判定走 `classifySkillFile`(实际读到的文件),不走这里。
|
|
17
33
|
*/
|
|
18
|
-
export declare function classifySkillRead(toolName: string, args: Record<string, unknown> | undefined, skillDirs?: SkillDirRef[]): {
|
|
34
|
+
export declare function classifySkillRead(toolName: string, args: Record<string, unknown> | undefined, skillDirs?: SkillDirRef[], locate?: SkillReadLocate): {
|
|
19
35
|
kind: 'loaded' | 'resource';
|
|
20
36
|
path: string;
|
|
21
37
|
skillId: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { userRoot } from '../../common/user-root.js';
|
|
4
|
+
import { resolveSkillReadTarget } from '../../tool/skill-relative-read.js';
|
|
4
5
|
function tryRealpath(p) {
|
|
5
6
|
try {
|
|
6
7
|
return realpathSync(p);
|
|
@@ -30,10 +31,6 @@ function longestSkillMatch(filePath, skillDirs) {
|
|
|
30
31
|
}
|
|
31
32
|
return best;
|
|
32
33
|
}
|
|
33
|
-
function skillIdFromSkillsSegment(norm) {
|
|
34
|
-
const match = norm.match(/(?:^|\/)(?:preinstall\/)?skills\/([^/]+)(?:\/|$)/i);
|
|
35
|
-
return match?.[1] ?? '';
|
|
36
|
-
}
|
|
37
34
|
/**
|
|
38
35
|
* 按实际 bundle 路径判定空间。有 agentDir 时相对预装根与 users/ 判定;
|
|
39
36
|
* 否则看路径段 `preinstall/skills` / `users/<id>/skills`。
|
|
@@ -72,34 +69,63 @@ export function classifySkillSpace(bundleOrFilePath, opts) {
|
|
|
72
69
|
return 'user';
|
|
73
70
|
return undefined;
|
|
74
71
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
72
|
+
function extraRootsForSkillRead(skillDirs, locate) {
|
|
73
|
+
const out = [];
|
|
74
|
+
for (const dir of skillDirs ?? []) {
|
|
75
|
+
if (dir.bundlePath)
|
|
76
|
+
out.push(dir.bundlePath);
|
|
77
|
+
}
|
|
78
|
+
for (const root of locate?.extraRoots ?? []) {
|
|
79
|
+
if (root)
|
|
80
|
+
out.push(root);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
export function isReadTool(toolName) {
|
|
85
|
+
const n = toolName.toLowerCase().replace(/-/g, '_');
|
|
86
|
+
return n === 'read_file' || n === 'read_skill_file' || n === 'readfile';
|
|
77
87
|
}
|
|
78
88
|
/**
|
|
79
|
-
*
|
|
89
|
+
* 实际读到的文件是否落在已装配技能包内。不解析定位符。
|
|
80
90
|
*/
|
|
81
|
-
export function
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
91
|
+
export function classifySkillFile(absPath, skillDirs) {
|
|
92
|
+
const trimmed = absPath.trim();
|
|
93
|
+
if (!trimmed)
|
|
94
|
+
return null;
|
|
95
|
+
const matched = longestSkillMatch(trimmed, skillDirs);
|
|
96
|
+
if (!matched)
|
|
97
|
+
return null;
|
|
98
|
+
const resolved = normalizeForMatch(trimmed);
|
|
99
|
+
const isEntry = /(^|\/)(SKILL\.md|skill.md)$/i.test(resolved);
|
|
100
|
+
return {
|
|
101
|
+
kind: isEntry ? 'loaded' : 'resource',
|
|
102
|
+
path: resolved,
|
|
103
|
+
skillId: matched.skillId,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 给 EAP_SKILL_DIR 推断:把 `arguments.path` 按 Harness 规则落到文件再对 skillDirs。
|
|
108
|
+
* Trace / SSE 入口判定走 `classifySkillFile`(实际读到的文件),不走这里。
|
|
109
|
+
*/
|
|
110
|
+
export function classifySkillRead(toolName, args, skillDirs, locate) {
|
|
111
|
+
if (!isReadTool(toolName))
|
|
84
112
|
return null;
|
|
85
113
|
const filePath = String(args?.path ?? args?.file ?? '');
|
|
86
114
|
if (!filePath)
|
|
87
115
|
return null;
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
116
|
+
const target = resolveSkillReadTarget(filePath, {
|
|
117
|
+
workdir: locate?.workdir,
|
|
118
|
+
skillDir: locate?.skillDir,
|
|
119
|
+
extraRoots: extraRootsForSkillRead(skillDirs, locate),
|
|
120
|
+
});
|
|
121
|
+
const matched = longestSkillMatch(target, skillDirs);
|
|
122
|
+
if (!matched)
|
|
123
|
+
return null;
|
|
124
|
+
const slash = target.replace(/\\/g, '/');
|
|
125
|
+
const isEntry = /(^|\/)(SKILL\.md|skill.md)$/i.test(slash);
|
|
126
|
+
return {
|
|
127
|
+
kind: isEntry ? 'loaded' : 'resource',
|
|
128
|
+
path: target,
|
|
129
|
+
skillId: matched.skillId,
|
|
130
|
+
};
|
|
105
131
|
}
|
|
@@ -13,7 +13,7 @@ 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
15
|
import { skillSpanFields, skillTraceIdentity } from './skill-entry-span.js';
|
|
16
|
-
import { filterLoadedToolsForSkill, } from '../../tool/system-prompt-builder.js';
|
|
16
|
+
import { filterLoadedToolsForSkill, spokenName, } from '../../tool/system-prompt-builder.js';
|
|
17
17
|
import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
|
|
18
18
|
import { pickContextFields } from '../../token/context-window.js';
|
|
19
19
|
import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../registry/skill-io-guard.js';
|
|
@@ -65,11 +65,14 @@ export class SubGraphCompiler {
|
|
|
65
65
|
? state['skillSpanId']
|
|
66
66
|
: '';
|
|
67
67
|
const resuming = Boolean(existingSpanId);
|
|
68
|
+
const identity = skillTraceIdentity(state, resolvedSkillId);
|
|
69
|
+
const spanName = resolvedSkillId || dsl.name || 'skill';
|
|
70
|
+
const skillName = spokenName(identity.skillName, resolvedSkillId) ?? spanName;
|
|
68
71
|
const auditCtx = resuming
|
|
69
72
|
? undefined
|
|
70
73
|
: this.skillAuditRecorder?.startInvocation({
|
|
71
74
|
skillId: resolvedSkillId,
|
|
72
|
-
skillName
|
|
75
|
+
skillName,
|
|
73
76
|
agentCode: (state['agentCode'] ?? state['agentId']),
|
|
74
77
|
userId: state['userId'] ?? '',
|
|
75
78
|
taskId: state['taskId'],
|
|
@@ -77,8 +80,6 @@ export class SubGraphCompiler {
|
|
|
77
80
|
});
|
|
78
81
|
// 用父图这次调用上的 collector;内层 invoke 只补 collector/traceId,沿用外层流式袋。
|
|
79
82
|
const { collector: traceCollector, traceId } = resolveExecutionTrace(state);
|
|
80
|
-
const skillName = dsl.name || resolvedSkillId;
|
|
81
|
-
const identity = skillTraceIdentity(state, resolvedSkillId);
|
|
82
83
|
if (!resuming) {
|
|
83
84
|
adapter.emitStreamEvent({
|
|
84
85
|
type: 'skill.started',
|
|
@@ -87,7 +88,7 @@ export class SubGraphCompiler {
|
|
|
87
88
|
timestamp: new Date().toISOString(),
|
|
88
89
|
payload: {
|
|
89
90
|
skillId: resolvedSkillId,
|
|
90
|
-
skillName
|
|
91
|
+
skillName,
|
|
91
92
|
...(identity.skillSpace ? { skillSpace: identity.skillSpace } : {}),
|
|
92
93
|
...(identity.skillVersion ? { skillVersion: identity.skillVersion } : {}),
|
|
93
94
|
},
|
|
@@ -99,7 +100,7 @@ export class SubGraphCompiler {
|
|
|
99
100
|
const fields = skillSpanFields(identity, { source: 'subgraph' });
|
|
100
101
|
skillSpanId = await traceCollector.createNode({
|
|
101
102
|
traceId,
|
|
102
|
-
name:
|
|
103
|
+
name: spanName,
|
|
103
104
|
type: 'skill',
|
|
104
105
|
agentCode: state['agentCode'] ?? state['agentId'],
|
|
105
106
|
...(fields.version ? { version: fields.version } : {}),
|
|
@@ -111,7 +112,7 @@ export class SubGraphCompiler {
|
|
|
111
112
|
});
|
|
112
113
|
}
|
|
113
114
|
catch (err) {
|
|
114
|
-
warnTraceSpanFailure('createNode', { type: 'skill', name:
|
|
115
|
+
warnTraceSpanFailure('createNode', { type: 'skill', name: spanName, traceId }, err);
|
|
115
116
|
skillSpanId = null;
|
|
116
117
|
}
|
|
117
118
|
}
|
|
@@ -154,6 +155,7 @@ export class SubGraphCompiler {
|
|
|
154
155
|
userId,
|
|
155
156
|
delegationToken,
|
|
156
157
|
agentCode,
|
|
158
|
+
agentName: typeof state['agentName'] === 'string' ? state['agentName'] : undefined,
|
|
157
159
|
taskId: state['taskId'],
|
|
158
160
|
correlationId: state['correlationId'],
|
|
159
161
|
sessionId: state['sessionId'],
|
|
@@ -194,7 +196,7 @@ export class SubGraphCompiler {
|
|
|
194
196
|
});
|
|
195
197
|
}
|
|
196
198
|
catch (err) {
|
|
197
|
-
warnTraceSpanFailure('completeNode', { type: 'skill', name:
|
|
199
|
+
warnTraceSpanFailure('completeNode', { type: 'skill', name: spanName, traceId, spanId: skillSpanId }, err);
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
// 4. 只回写子图本步新增消息(含内部 tool 回合),禁止把入口历史再 append
|
|
@@ -269,7 +271,7 @@ export class SubGraphCompiler {
|
|
|
269
271
|
});
|
|
270
272
|
}
|
|
271
273
|
catch (completeErr) {
|
|
272
|
-
warnTraceSpanFailure('completeNode', { type: 'skill', name:
|
|
274
|
+
warnTraceSpanFailure('completeNode', { type: 'skill', name: spanName, traceId, spanId: skillSpanId }, completeErr);
|
|
273
275
|
}
|
|
274
276
|
}
|
|
275
277
|
adapter.emitStreamEvent({
|
|
@@ -181,6 +181,8 @@ export interface GraphState {
|
|
|
181
181
|
agentId?: string;
|
|
182
182
|
/** Agent 对外主键 */
|
|
183
183
|
agentCode?: string;
|
|
184
|
+
/** Agent 展示名(manifest.name);身份段对用户说话用,缺省回退 agentCode */
|
|
185
|
+
agentName?: string;
|
|
184
186
|
/** Hosting 路由选定的预装版本(AR-3b;写入 Checkpoint 供 HITL/Fork 恢复) */
|
|
185
187
|
agentVersion?: string;
|
|
186
188
|
correlationId?: string;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
|
|
3
3
|
* 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
|
|
4
4
|
*/
|
|
5
|
-
import type
|
|
5
|
+
import { type LoadedSkillInfo, type LoadedSkillMap } from '../../tool/system-prompt-builder.js';
|
|
6
6
|
export type SkillRoute = 'skill' | 'clarify' | 'none';
|
|
7
7
|
export interface SkillBindPatch {
|
|
8
8
|
skillRoute: SkillRoute;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
|
|
3
|
+
* 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
|
|
4
|
+
*/
|
|
5
|
+
import { spokenName } from '../../tool/system-prompt-builder.js';
|
|
1
6
|
function lastAssistantText(messages) {
|
|
2
7
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3
8
|
const msg = messages[i];
|
|
@@ -89,7 +94,7 @@ export function bindSelectedSkill(state) {
|
|
|
89
94
|
return {
|
|
90
95
|
skillRoute: 'skill',
|
|
91
96
|
activatedSkill: skill.skillId,
|
|
92
|
-
skillName: skill.name
|
|
97
|
+
skillName: spokenName(skill.name, skill.skillId),
|
|
93
98
|
skillInput: params,
|
|
94
99
|
messages: nextMessages(),
|
|
95
100
|
};
|
|
@@ -48,6 +48,8 @@ export interface ToolNodeProgress {
|
|
|
48
48
|
node?: string;
|
|
49
49
|
skillId?: string;
|
|
50
50
|
skillDirs?: SkillDirRef[];
|
|
51
|
+
/** read_file 实际读到的文件;与 ToolCallResult.resolvedPath 相同 */
|
|
52
|
+
resolvedPath?: string;
|
|
51
53
|
/** 已在 Skill 子图内(state.skillSpanId);读 SKILL.md 不再发 skill.started */
|
|
52
54
|
inSkillSubgraph?: boolean;
|
|
53
55
|
skillSpace?: 'agent' | 'user' | 'local';
|
|
@@ -203,6 +203,7 @@ export class ToolNode {
|
|
|
203
203
|
correlationId: req.correlationId,
|
|
204
204
|
skillId,
|
|
205
205
|
...(identity.toolset ? { toolset: identity.toolset } : {}),
|
|
206
|
+
...(identity.toolDisplayName ? { toolDisplayName: identity.toolDisplayName } : {}),
|
|
206
207
|
},
|
|
207
208
|
});
|
|
208
209
|
}
|
|
@@ -295,7 +296,7 @@ export class ToolNode {
|
|
|
295
296
|
if (result.success) {
|
|
296
297
|
const catalog = skillEntryInvocationFromTool({
|
|
297
298
|
toolName: req.toolName,
|
|
298
|
-
|
|
299
|
+
resolvedPath: result.resolvedPath,
|
|
299
300
|
skillDirs,
|
|
300
301
|
alreadyInSkillSpan: inSkillSubgraph,
|
|
301
302
|
});
|
|
@@ -312,6 +313,7 @@ export class ToolNode {
|
|
|
312
313
|
content: result.content,
|
|
313
314
|
...toolArtifactFields(result),
|
|
314
315
|
executionTime,
|
|
316
|
+
...(result.resolvedPath ? { resolvedPath: result.resolvedPath } : {}),
|
|
315
317
|
...(skillIdentity?.skillSpace ? { skillSpace: skillIdentity.skillSpace } : {}),
|
|
316
318
|
...(skillIdentity?.skillVersion ? { skillVersion: skillIdentity.skillVersion } : {}),
|
|
317
319
|
...(skillIdentity?.skillName ? { skillName: skillIdentity.skillName } : {}),
|
|
@@ -346,7 +348,7 @@ export class ToolNode {
|
|
|
346
348
|
if (result.success) {
|
|
347
349
|
await recordSkillEntrySpan(state, {
|
|
348
350
|
toolName: req.toolName,
|
|
349
|
-
|
|
351
|
+
resolvedPath: result.resolvedPath,
|
|
350
352
|
skillDirs,
|
|
351
353
|
});
|
|
352
354
|
}
|
|
@@ -20,8 +20,13 @@ export interface LoadAgentOptions {
|
|
|
20
20
|
export declare function resolveAgentSystemPromptFromLoader(loader: {
|
|
21
21
|
resolveAgentSystemPrompt?: (agentCode: string, version?: string) => Promise<string | undefined>;
|
|
22
22
|
} | undefined, agentCode: string, version?: string): Promise<string | undefined>;
|
|
23
|
+
export declare function resolveAgentDisplayNameFromLoader(loader: {
|
|
24
|
+
resolveAgentDisplayName?: (agentCode: string, version?: string) => Promise<string | undefined>;
|
|
25
|
+
} | undefined, agentCode: string, version?: string): Promise<string | undefined>;
|
|
23
26
|
/** manifest.prompts.system;空白视为缺省。 */
|
|
24
27
|
export declare function extractAgentSystemPrompt(manifest: unknown): string | undefined;
|
|
28
|
+
/** manifest.name;空白视为缺省。身份段对用户说话用,不当路由键。 */
|
|
29
|
+
export declare function extractAgentDisplayName(manifest: unknown): string | undefined;
|
|
25
30
|
export interface GraphLoaderConfig {
|
|
26
31
|
registryClient?: AgentRegistryClient;
|
|
27
32
|
deploymentContext?: DeploymentContext;
|
|
@@ -116,6 +121,12 @@ export declare class GraphLoader {
|
|
|
116
121
|
* 每轮 execute/stream 注入 GraphState.agentSystemPrompt;不写进节点 config。
|
|
117
122
|
*/
|
|
118
123
|
resolveAgentSystemPrompt(agentCode: string, version?: string): Promise<string | undefined>;
|
|
124
|
+
/**
|
|
125
|
+
* Agent 展示名(manifest.name)。
|
|
126
|
+
* 每轮 execute/stream 注入 GraphState.agentName;身份段对用户说话用。
|
|
127
|
+
*/
|
|
128
|
+
resolveAgentDisplayName(agentCode: string, version?: string): Promise<string | undefined>;
|
|
129
|
+
private fromAgentManifest;
|
|
119
130
|
/**
|
|
120
131
|
* 解析 agent tools[] + graph tool 节点 union;
|
|
121
132
|
* server+预装:以预装 manifest 为准;desktop 合并 ILocalToolInstallStore。
|
|
@@ -64,6 +64,11 @@ export async function resolveAgentSystemPromptFromLoader(loader, agentCode, vers
|
|
|
64
64
|
return undefined;
|
|
65
65
|
return loader.resolveAgentSystemPrompt(agentCode, version);
|
|
66
66
|
}
|
|
67
|
+
export async function resolveAgentDisplayNameFromLoader(loader, agentCode, version) {
|
|
68
|
+
if (!loader || typeof loader.resolveAgentDisplayName !== 'function')
|
|
69
|
+
return undefined;
|
|
70
|
+
return loader.resolveAgentDisplayName(agentCode, version);
|
|
71
|
+
}
|
|
67
72
|
/** manifest.prompts.system;空白视为缺省。 */
|
|
68
73
|
export function extractAgentSystemPrompt(manifest) {
|
|
69
74
|
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
@@ -77,6 +82,16 @@ export function extractAgentSystemPrompt(manifest) {
|
|
|
77
82
|
const trimmed = system.trim();
|
|
78
83
|
return trimmed ? trimmed : undefined;
|
|
79
84
|
}
|
|
85
|
+
/** manifest.name;空白视为缺省。身份段对用户说话用,不当路由键。 */
|
|
86
|
+
export function extractAgentDisplayName(manifest) {
|
|
87
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
|
|
88
|
+
return undefined;
|
|
89
|
+
const name = manifest.name;
|
|
90
|
+
if (typeof name !== 'string')
|
|
91
|
+
return undefined;
|
|
92
|
+
const trimmed = name.trim();
|
|
93
|
+
return trimmed ? trimmed : undefined;
|
|
94
|
+
}
|
|
80
95
|
function extractSkillPrompt(manifest) {
|
|
81
96
|
const prompt = manifest.prompt;
|
|
82
97
|
if (typeof prompt === 'string' && prompt.trim())
|
|
@@ -769,18 +784,27 @@ export class GraphLoader {
|
|
|
769
784
|
* 每轮 execute/stream 注入 GraphState.agentSystemPrompt;不写进节点 config。
|
|
770
785
|
*/
|
|
771
786
|
async resolveAgentSystemPrompt(agentCode, version) {
|
|
787
|
+
return this.fromAgentManifest(agentCode, version, extractAgentSystemPrompt);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Agent 展示名(manifest.name)。
|
|
791
|
+
* 每轮 execute/stream 注入 GraphState.agentName;身份段对用户说话用。
|
|
792
|
+
*/
|
|
793
|
+
async resolveAgentDisplayName(agentCode, version) {
|
|
794
|
+
return this.fromAgentManifest(agentCode, version, extractAgentDisplayName);
|
|
795
|
+
}
|
|
796
|
+
async fromAgentManifest(agentCode, version, extract) {
|
|
772
797
|
if (this.deploymentSnapshot) {
|
|
773
798
|
const snap = await this.deploymentSnapshot.loadAgentSnapshot(agentCode, version);
|
|
774
|
-
if (snap)
|
|
775
|
-
return
|
|
776
|
-
}
|
|
799
|
+
if (snap)
|
|
800
|
+
return extract(snap.manifest);
|
|
777
801
|
if (this.blockRegistryDefinitions)
|
|
778
802
|
return undefined;
|
|
779
803
|
}
|
|
780
804
|
if (this.blockRegistryDefinitions || !this.registryClient)
|
|
781
805
|
return undefined;
|
|
782
806
|
const record = await this.registryClient.getAgent(agentCode);
|
|
783
|
-
return
|
|
807
|
+
return extract(record?.manifest);
|
|
784
808
|
}
|
|
785
809
|
/**
|
|
786
810
|
* 解析 agent tools[] + graph tool 节点 union;
|
|
@@ -11,7 +11,7 @@ import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
|
|
|
11
11
|
import { resolveTaskTimeoutMs } from '../task/resolve-task-timeout.js';
|
|
12
12
|
import { runWithExecutionTrace } from '../graph/execution-trace-context.js';
|
|
13
13
|
import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
|
|
14
|
-
import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
|
|
14
|
+
import { resolveAgentDisplayNameFromLoader, resolveAgentSystemPromptFromLoader, } from '../registry/graph-loader.js';
|
|
15
15
|
import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
|
|
16
16
|
import { assembleLoadedTools } from '../tool/harness-primitives.js';
|
|
17
17
|
import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
|
|
@@ -330,6 +330,7 @@ export class AgentRuntime {
|
|
|
330
330
|
? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
|
|
331
331
|
: undefined;
|
|
332
332
|
const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
|
|
333
|
+
const agentName = await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
|
|
333
334
|
const facing = await loadModelFacingHistory({
|
|
334
335
|
sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
|
|
335
336
|
checkpointSaver: this.deps.checkpointSaver,
|
|
@@ -402,6 +403,7 @@ export class AgentRuntime {
|
|
|
402
403
|
promptRoots,
|
|
403
404
|
agentDir,
|
|
404
405
|
agentSystemPrompt,
|
|
406
|
+
agentName,
|
|
405
407
|
skillInput: resolveSkillInputFromInvokeInput(params.input),
|
|
406
408
|
traceId: resolveTraceId(params),
|
|
407
409
|
traceCollector: this.deps.traceCollector,
|
|
@@ -617,6 +619,9 @@ export class AgentRuntime {
|
|
|
617
619
|
agentSystemPrompt: (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof resumedState.agentSystemPrompt === 'string'
|
|
618
620
|
? resumedState.agentSystemPrompt
|
|
619
621
|
: undefined),
|
|
622
|
+
agentName: (await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof resumedState.agentName === 'string' && resumedState.agentName.trim()
|
|
623
|
+
? resumedState.agentName.trim()
|
|
624
|
+
: undefined),
|
|
620
625
|
activatedSkill: resumedState.activatedSkill,
|
|
621
626
|
skillRoute: resumedState.skillRoute ??
|
|
622
627
|
(typeof resumedState.activatedSkill === 'string' && resumedState.activatedSkill
|
|
@@ -814,6 +819,9 @@ export class AgentRuntime {
|
|
|
814
819
|
const agentSystemPrompt = (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentSystemPrompt === 'string'
|
|
815
820
|
? forkedCheckpoint.state.agentSystemPrompt
|
|
816
821
|
: undefined);
|
|
822
|
+
const agentName = (await resolveAgentDisplayNameFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentName === 'string' && forkedCheckpoint.state.agentName.trim()
|
|
823
|
+
? forkedCheckpoint.state.agentName.trim()
|
|
824
|
+
: undefined);
|
|
817
825
|
const abortDeadlineMs = Date.now() + timeout;
|
|
818
826
|
try {
|
|
819
827
|
const forkState = { ...forkedCheckpoint.state };
|
|
@@ -827,6 +835,7 @@ export class AgentRuntime {
|
|
|
827
835
|
agentVersion,
|
|
828
836
|
agentDir,
|
|
829
837
|
agentSystemPrompt,
|
|
838
|
+
agentName,
|
|
830
839
|
userId,
|
|
831
840
|
delegationToken: overrides.executionContext?.delegationToken,
|
|
832
841
|
executionContext: overrides.executionContext,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* `createToolExecutor` 时钉进执行器 extraRoots;SDK `createAgent` 构造完会恢复 process.env,
|
|
10
10
|
* 请求时不再读这两项。相对 `read_file` 会把 overlay 根下直接子目录里带 SKILL.md 的包纳入唯一命中。
|
|
11
11
|
*/
|
|
12
|
+
export { locateSkillRelativeRead } from './skill-relative-read.js';
|
|
12
13
|
import type { LoadedToolMap } from './loaded-tool-definition.js';
|
|
13
14
|
import type { IToolExecutor, ToolCallRequest, ToolCallResult } from './tool-executor.interface.js';
|
|
14
15
|
export declare function mergeHarnessPrimitives(loadedTools: LoadedToolMap | undefined): LoadedToolMap | undefined;
|
|
@@ -42,13 +43,6 @@ export declare function captureOverlayJailOptions(env?: NodeJS.ProcessEnv): Pick
|
|
|
42
43
|
export declare function resolveJailRoots(options?: HarnessJailOptions): string[];
|
|
43
44
|
/** 路径落在写根外的 extraRoot 时,返回该 extraRoot。 */
|
|
44
45
|
export declare function extraRootContaining(abs: string, extraRoots: string[], workdir: string): string | undefined;
|
|
45
|
-
/**
|
|
46
|
-
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
47
|
-
*/
|
|
48
|
-
export declare function locateSkillRelativeRead(filePath: string, workdir: string | undefined, skillDir: string | undefined, extraRoots?: string[]): {
|
|
49
|
-
path: string;
|
|
50
|
-
relativeBase: string | undefined;
|
|
51
|
-
};
|
|
52
46
|
export declare function resolveJailedPath(requested: string, roots: string[], opts?: {
|
|
53
47
|
mustExist?: boolean;
|
|
54
48
|
relativeBase?: string;
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
* 请求时不再读这两项。相对 `read_file` 会把 overlay 根下直接子目录里带 SKILL.md 的包纳入唯一命中。
|
|
11
11
|
*/
|
|
12
12
|
import { spawn } from 'node:child_process';
|
|
13
|
-
import { constants as fsConstants,
|
|
13
|
+
import { constants as fsConstants, realpathSync } from 'node:fs';
|
|
14
14
|
import { chmod, mkdir, open, readFile, stat } from 'node:fs/promises';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { MISSING_USER_DIR, userRoot } from '../common/user-root.js';
|
|
17
|
+
import { locateSkillRelativeRead, userReadBase } from './skill-relative-read.js';
|
|
18
|
+
export { locateSkillRelativeRead } from './skill-relative-read.js';
|
|
17
19
|
import { MAX_SNAPSHOT_ENUM, MAX_SNAPSHOT_URLS, PUBLIC_FILE_MODE, ensureUserSlots, maxWriteBytesFromEnv, publishFromFiles, resolveUserProductPath, snapshotCandidates, walkFilesTree, } from './static-artifact-url.js';
|
|
18
20
|
import { BUILTIN_TOOL_SOURCE, DEFAULT_BUILTIN_TIMEOUT_MS, MAX_BUILTIN_TIMEOUT_MS, clampBuiltinTimeout, isBuiltinToolExecution, resolveBuiltinToolsConfig, } from '@gt-fe/eap-contracts';
|
|
19
21
|
import { ensureInterpreterShims, formatSpawnError, killProcessTree, resolveShellSpawn, sandboxEnv, spawnOptionsFor, substituteSkillDir, } from './harness-command.js';
|
|
@@ -316,74 +318,6 @@ function resolveViaExistingAncestor(abs) {
|
|
|
316
318
|
cur = parent;
|
|
317
319
|
}
|
|
318
320
|
}
|
|
319
|
-
function userReadBase(workdir) {
|
|
320
|
-
return workdir?.trim()
|
|
321
|
-
? (tryRealpath(path.resolve(workdir)) ?? path.resolve(workdir))
|
|
322
|
-
: undefined;
|
|
323
|
-
}
|
|
324
|
-
function isSkillPackageDir(dir) {
|
|
325
|
-
return existsSync(path.join(dir, 'SKILL.md')) || existsSync(path.join(dir, 'skill.md'));
|
|
326
|
-
}
|
|
327
|
-
function skillPackageRoots(roots) {
|
|
328
|
-
const out = [];
|
|
329
|
-
const seen = new Set();
|
|
330
|
-
const add = (dir) => {
|
|
331
|
-
if (seen.has(dir))
|
|
332
|
-
return;
|
|
333
|
-
seen.add(dir);
|
|
334
|
-
out.push(dir);
|
|
335
|
-
};
|
|
336
|
-
for (const root of roots) {
|
|
337
|
-
const resolved = path.resolve(root);
|
|
338
|
-
if (isSkillPackageDir(resolved)) {
|
|
339
|
-
add(resolved);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
let entries;
|
|
343
|
-
try {
|
|
344
|
-
entries = readdirSync(resolved, { withFileTypes: true });
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
for (const ent of entries) {
|
|
350
|
-
if (!ent.isDirectory())
|
|
351
|
-
continue;
|
|
352
|
-
const child = path.join(resolved, ent.name);
|
|
353
|
-
if (isSkillPackageDir(child))
|
|
354
|
-
add(child);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return out;
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
361
|
-
*/
|
|
362
|
-
export function locateSkillRelativeRead(filePath, workdir, skillDir, extraRoots) {
|
|
363
|
-
const trimmed = filePath.trim();
|
|
364
|
-
const userBase = userReadBase(workdir);
|
|
365
|
-
if (!trimmed || path.isAbsolute(trimmed))
|
|
366
|
-
return { path: trimmed, relativeBase: userBase };
|
|
367
|
-
const packages = skillPackageRoots([
|
|
368
|
-
...(skillDir?.trim() ? [skillDir] : []),
|
|
369
|
-
...(extraRoots ?? []),
|
|
370
|
-
]);
|
|
371
|
-
const slash = trimmed.replace(/\\/g, '/');
|
|
372
|
-
const segs = slash.split('/').filter(Boolean);
|
|
373
|
-
if (segs.length >= 2) {
|
|
374
|
-
const hit = packages.find((pkg) => path.basename(pkg) === segs[0]);
|
|
375
|
-
if (hit)
|
|
376
|
-
return { path: segs.slice(1).join('/'), relativeBase: hit };
|
|
377
|
-
}
|
|
378
|
-
const matches = packages.filter((pkg) => existsSync(path.resolve(pkg, trimmed)));
|
|
379
|
-
if (matches.length === 1)
|
|
380
|
-
return { path: trimmed, relativeBase: matches[0] };
|
|
381
|
-
const skillBase = skillDir?.trim() ? path.resolve(skillDir) : undefined;
|
|
382
|
-
if (skillBase && existsSync(path.resolve(skillBase, trimmed))) {
|
|
383
|
-
return { path: trimmed, relativeBase: skillBase };
|
|
384
|
-
}
|
|
385
|
-
return { path: trimmed, relativeBase: userBase };
|
|
386
|
-
}
|
|
387
321
|
export function resolveJailedPath(requested, roots, opts) {
|
|
388
322
|
const trimmed = requested.trim();
|
|
389
323
|
if (!trimmed || trimmed.includes('\0')) {
|
|
@@ -530,7 +464,7 @@ export class HarnessPrimitiveExecutor {
|
|
|
530
464
|
if (isSkillEntryFile(target)) {
|
|
531
465
|
text = expandSkillScriptRefs(text, path.dirname(target));
|
|
532
466
|
}
|
|
533
|
-
return ok(request, text);
|
|
467
|
+
return { ...ok(request, text), resolvedPath: target };
|
|
534
468
|
}
|
|
535
469
|
async writeFile(request, append) {
|
|
536
470
|
const filePath = String(request.arguments.path ?? '');
|
package/dist/tool/index.d.ts
CHANGED
|
@@ -22,4 +22,4 @@ export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './s
|
|
|
22
22
|
export { ToolResultInjector, type ToolMessage, } from './tool-result-injector.js';
|
|
23
23
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
24
24
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
25
|
-
export { SystemPromptBuilder, naturalTimeWindows, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, type PromptBuildContext, type PromptSection, type AssembledSystemPrompt, type ApiToolDefinition, type LoadedSkillInfo, type LoadedSkillMap, } from './system-prompt-builder.js';
|
|
25
|
+
export { SystemPromptBuilder, naturalTimeWindows, buildSkillCatalog, buildToolCatalog, catalogEntryTitle, spokenName, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, type PromptBuildContext, type PromptSection, type AssembledSystemPrompt, type ApiToolDefinition, type LoadedSkillInfo, type LoadedSkillMap, } from './system-prompt-builder.js';
|
package/dist/tool/index.js
CHANGED
|
@@ -21,4 +21,4 @@ export { expandSkillScriptRefs, isSkillEntryFile, EAP_SKILL_DIR_ENV, } from './s
|
|
|
21
21
|
export { ToolResultInjector, } from './tool-result-injector.js';
|
|
22
22
|
export { ToolEventEmitter } from './tool-event-emitter.js';
|
|
23
23
|
export { ToolConfirmationInterceptor } from './tool-confirmation.js';
|
|
24
|
-
export { SystemPromptBuilder, naturalTimeWindows, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
|
|
24
|
+
export { SystemPromptBuilder, naturalTimeWindows, buildSkillCatalog, buildToolCatalog, catalogEntryTitle, spokenName, loadedToolsToApiTools, toLoadedSkillMap, filterLoadedToolsForSkill, } from './system-prompt-builder.js';
|
package/dist/tool/mcp-list.d.ts
CHANGED
|
@@ -53,17 +53,19 @@ export declare function mcpToolsetCode(loaded: {
|
|
|
53
53
|
execution?: unknown;
|
|
54
54
|
};
|
|
55
55
|
} | undefined): string | undefined;
|
|
56
|
-
/** Tool Span:工具集 code +
|
|
56
|
+
/** Tool Span:工具集 code + 声明版本 + 展示名(与函数名不同才写)。 */
|
|
57
57
|
export declare function toolTraceIdentity(loaded: {
|
|
58
58
|
toolId?: string;
|
|
59
59
|
name?: string;
|
|
60
60
|
manifest?: {
|
|
61
61
|
id?: string;
|
|
62
|
+
name?: string;
|
|
62
63
|
version?: string;
|
|
63
64
|
};
|
|
64
65
|
} | undefined, toolName: string): {
|
|
65
66
|
toolset?: string;
|
|
66
67
|
toolVersion?: string;
|
|
68
|
+
toolDisplayName?: string;
|
|
67
69
|
};
|
|
68
70
|
export interface HydrateMcpToolsAuth {
|
|
69
71
|
authContext: AuthContext;
|
package/dist/tool/mcp-list.js
CHANGED
|
@@ -130,13 +130,16 @@ function declaredToolVersion(toolName, version) {
|
|
|
130
130
|
return undefined;
|
|
131
131
|
return raw;
|
|
132
132
|
}
|
|
133
|
-
/** Tool Span:工具集 code +
|
|
133
|
+
/** Tool Span:工具集 code + 声明版本 + 展示名(与函数名不同才写)。 */
|
|
134
134
|
export function toolTraceIdentity(loaded, toolName) {
|
|
135
135
|
const toolset = toolsetCodeFromLoaded(loaded);
|
|
136
136
|
const toolVersion = declaredToolVersion(toolName, loaded?.manifest?.version);
|
|
137
|
+
const display = loaded?.manifest?.name?.trim();
|
|
138
|
+
const toolDisplayName = display && display !== toolName ? display : undefined;
|
|
137
139
|
return {
|
|
138
140
|
...(toolset ? { toolset } : {}),
|
|
139
141
|
...(toolVersion ? { toolVersion } : {}),
|
|
142
|
+
...(toolDisplayName ? { toolDisplayName } : {}),
|
|
140
143
|
};
|
|
141
144
|
}
|
|
142
145
|
/**
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function userReadBase(workdir: string | undefined): string | undefined;
|
|
2
|
+
/**
|
|
3
|
+
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
4
|
+
*/
|
|
5
|
+
export declare function locateSkillRelativeRead(filePath: string, workdir: string | undefined, skillDir: string | undefined, extraRoots?: string[]): {
|
|
6
|
+
path: string;
|
|
7
|
+
relativeBase: string | undefined;
|
|
8
|
+
};
|
|
9
|
+
/** 定位符落到实际文件:绝对路径原样;相对路径按 locateSkillRelativeRead 的包根拼接。 */
|
|
10
|
+
export declare function resolveSkillReadTarget(filePath: string, opts?: {
|
|
11
|
+
workdir?: string;
|
|
12
|
+
skillDir?: string;
|
|
13
|
+
extraRoots?: string[];
|
|
14
|
+
}): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* read_file 相对路径如何落到技能包:Harness 读文件与 Trace 判定入口共用。
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readdirSync, realpathSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
function tryRealpath(p) {
|
|
7
|
+
try {
|
|
8
|
+
return realpathSync(p);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function userReadBase(workdir) {
|
|
15
|
+
return workdir?.trim()
|
|
16
|
+
? (tryRealpath(path.resolve(workdir)) ?? path.resolve(workdir))
|
|
17
|
+
: undefined;
|
|
18
|
+
}
|
|
19
|
+
function isSkillPackageDir(dir) {
|
|
20
|
+
return existsSync(path.join(dir, 'SKILL.md')) || existsSync(path.join(dir, 'skill.md'));
|
|
21
|
+
}
|
|
22
|
+
function skillPackageRoots(roots) {
|
|
23
|
+
const out = [];
|
|
24
|
+
const seen = new Set();
|
|
25
|
+
const add = (dir) => {
|
|
26
|
+
if (seen.has(dir))
|
|
27
|
+
return;
|
|
28
|
+
seen.add(dir);
|
|
29
|
+
out.push(dir);
|
|
30
|
+
};
|
|
31
|
+
for (const root of roots) {
|
|
32
|
+
const resolved = path.resolve(root);
|
|
33
|
+
if (isSkillPackageDir(resolved)) {
|
|
34
|
+
add(resolved);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = readdirSync(resolved, { withFileTypes: true });
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
for (const ent of entries) {
|
|
45
|
+
if (!ent.isDirectory())
|
|
46
|
+
continue;
|
|
47
|
+
const child = path.join(resolved, ent.name);
|
|
48
|
+
if (isSkillPackageDir(child))
|
|
49
|
+
add(child);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 相对 read_file:`技能名/文件`、全库唯一命中、显式 skillDir,否则用户目录。
|
|
56
|
+
*/
|
|
57
|
+
export function locateSkillRelativeRead(filePath, workdir, skillDir, extraRoots) {
|
|
58
|
+
const trimmed = filePath.trim();
|
|
59
|
+
const userBase = userReadBase(workdir);
|
|
60
|
+
if (!trimmed || path.isAbsolute(trimmed))
|
|
61
|
+
return { path: trimmed, relativeBase: userBase };
|
|
62
|
+
const packages = skillPackageRoots([
|
|
63
|
+
...(skillDir?.trim() ? [skillDir] : []),
|
|
64
|
+
...(extraRoots ?? []),
|
|
65
|
+
]);
|
|
66
|
+
const slash = trimmed.replace(/\\/g, '/');
|
|
67
|
+
const segs = slash.split('/').filter(Boolean);
|
|
68
|
+
if (segs.length >= 2) {
|
|
69
|
+
const hit = packages.find((pkg) => path.basename(pkg) === segs[0]);
|
|
70
|
+
if (hit)
|
|
71
|
+
return { path: segs.slice(1).join('/'), relativeBase: hit };
|
|
72
|
+
}
|
|
73
|
+
const matches = packages.filter((pkg) => existsSync(path.resolve(pkg, trimmed)));
|
|
74
|
+
if (matches.length === 1)
|
|
75
|
+
return { path: trimmed, relativeBase: matches[0] };
|
|
76
|
+
const skillBase = skillDir?.trim() ? path.resolve(skillDir) : undefined;
|
|
77
|
+
if (skillBase && existsSync(path.resolve(skillBase, trimmed))) {
|
|
78
|
+
return { path: trimmed, relativeBase: skillBase };
|
|
79
|
+
}
|
|
80
|
+
return { path: trimmed, relativeBase: userBase };
|
|
81
|
+
}
|
|
82
|
+
/** 定位符落到实际文件:绝对路径原样;相对路径按 locateSkillRelativeRead 的包根拼接。 */
|
|
83
|
+
export function resolveSkillReadTarget(filePath, opts) {
|
|
84
|
+
const trimmed = filePath.trim();
|
|
85
|
+
if (!trimmed)
|
|
86
|
+
return '';
|
|
87
|
+
const located = locateSkillRelativeRead(trimmed, opts?.workdir, opts?.skillDir, opts?.extraRoots);
|
|
88
|
+
if (path.isAbsolute(trimmed)) {
|
|
89
|
+
const resolved = path.resolve(trimmed);
|
|
90
|
+
return (tryRealpath(resolved) ?? resolved).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
91
|
+
}
|
|
92
|
+
if (located.relativeBase) {
|
|
93
|
+
const resolved = path.resolve(located.relativeBase, located.path);
|
|
94
|
+
return (tryRealpath(resolved) ?? resolved).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
95
|
+
}
|
|
96
|
+
const resolved = path.resolve(trimmed);
|
|
97
|
+
return (tryRealpath(resolved) ?? resolved).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
98
|
+
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* ```
|
|
8
8
|
* [平台规则常量] ← 代码内置,安全约束 + 行为准则(最高优先级)
|
|
9
9
|
* [当前时间] ← 运行时动态注入(自然周等时间规则的判定基准)
|
|
10
|
-
* [Agent 身份] ← agentName
|
|
11
|
-
* [可用工具短目录] ←
|
|
10
|
+
* [Agent 身份] ← 展示名 agentName,缺省 agentCode(对用户说话,不是路由键)
|
|
11
|
+
* [可用工具短目录] ← 展示名 + 调用名 + 一句话用途;完整 schema 仍走 API tools
|
|
12
12
|
* [Agent 基线] ← agent.manifest.json prompts.system;非 silent LLM
|
|
13
13
|
* [节点提示词] ← Graph LLM 节点 config.systemPrompt / systemPromptFile
|
|
14
14
|
* ```
|
|
@@ -109,6 +109,10 @@ export interface AssembledSystemPrompt {
|
|
|
109
109
|
text: string;
|
|
110
110
|
sections: PromptSection[];
|
|
111
111
|
}
|
|
112
|
+
/** 对人展示的名称。有展示名用展示名,否则用编码。 */
|
|
113
|
+
export declare function spokenName(displayName: string | undefined, code: string | undefined): string | undefined;
|
|
114
|
+
/** 短目录标题:展示名在前,调用键在行内。名称与编码相同则只写编码。 */
|
|
115
|
+
export declare function catalogEntryTitle(code: string, displayName: string | undefined, codePhrase: string): string;
|
|
112
116
|
/**
|
|
113
117
|
* 非 silent LLM 才带 Agent 基线;节点提示词空则省略。
|
|
114
118
|
* silent 只保留节点增量(选技能 / JSON)。
|
|
@@ -166,4 +170,6 @@ export type ApiToolDefinition = LLMToolDefinition;
|
|
|
166
170
|
*/
|
|
167
171
|
/** 名称 + 一句话用途,供 system「可用工具」段。schema 仍走 loadedToolsToApiTools。 */
|
|
168
172
|
export declare function buildToolCatalog(loadedTools: LoadedToolMap | undefined): string | undefined;
|
|
173
|
+
/** 展示名 + skillId,供 system「可用技能」段。read_file 仍用 skillId。 */
|
|
174
|
+
export declare function buildSkillCatalog(defs: LoadedSkillMap | undefined): string | undefined;
|
|
169
175
|
export declare function loadedToolsToApiTools(loadedTools: LoadedToolMap | undefined, skillTools?: string[]): ApiToolDefinition[];
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* ```
|
|
8
8
|
* [平台规则常量] ← 代码内置,安全约束 + 行为准则(最高优先级)
|
|
9
9
|
* [当前时间] ← 运行时动态注入(自然周等时间规则的判定基准)
|
|
10
|
-
* [Agent 身份] ← agentName
|
|
11
|
-
* [可用工具短目录] ←
|
|
10
|
+
* [Agent 身份] ← 展示名 agentName,缺省 agentCode(对用户说话,不是路由键)
|
|
11
|
+
* [可用工具短目录] ← 展示名 + 调用名 + 一句话用途;完整 schema 仍走 API tools
|
|
12
12
|
* [Agent 基线] ← agent.manifest.json prompts.system;非 silent LLM
|
|
13
13
|
* [节点提示词] ← Graph LLM 节点 config.systemPrompt / systemPromptFile
|
|
14
14
|
* ```
|
|
@@ -86,6 +86,22 @@ function trimPrompt(value) {
|
|
|
86
86
|
const trimmed = value.trim();
|
|
87
87
|
return trimmed ? trimmed : undefined;
|
|
88
88
|
}
|
|
89
|
+
/** 对人展示的名称。有展示名用展示名,否则用编码。 */
|
|
90
|
+
export function spokenName(displayName, code) {
|
|
91
|
+
const name = displayName?.trim();
|
|
92
|
+
if (name)
|
|
93
|
+
return name;
|
|
94
|
+
const id = code?.trim();
|
|
95
|
+
return id || undefined;
|
|
96
|
+
}
|
|
97
|
+
/** 短目录标题:展示名在前,调用键在行内。名称与编码相同则只写编码。 */
|
|
98
|
+
export function catalogEntryTitle(code, displayName, codePhrase) {
|
|
99
|
+
const id = code.trim();
|
|
100
|
+
const name = displayName?.trim();
|
|
101
|
+
if (name && name !== id)
|
|
102
|
+
return `**${name}**(${codePhrase})`;
|
|
103
|
+
return `**${id}**`;
|
|
104
|
+
}
|
|
89
105
|
/**
|
|
90
106
|
* 非 silent LLM 才带 Agent 基线;节点提示词空则省略。
|
|
91
107
|
* silent 只保留节点增量(选技能 / JSON)。
|
|
@@ -154,7 +170,7 @@ export class SystemPromptBuilder {
|
|
|
154
170
|
return lines.join('\n');
|
|
155
171
|
}
|
|
156
172
|
buildAgentIdentity(context) {
|
|
157
|
-
const name = context.agentName
|
|
173
|
+
const name = spokenName(context.agentName, context.agentCode);
|
|
158
174
|
return name ? `## Agent 身份\n你的名称是「${name}」。` : '';
|
|
159
175
|
}
|
|
160
176
|
buildJail(context) {
|
|
@@ -262,11 +278,8 @@ export function buildToolCatalog(loadedTools) {
|
|
|
262
278
|
return undefined;
|
|
263
279
|
const rows = Object.values(loadedTools).map((tool) => {
|
|
264
280
|
const desc = (tool.manifest.description ?? '').replace(/\s+/g, ' ').trim();
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
? `${tool.name}(${displayName})`
|
|
268
|
-
: tool.name;
|
|
269
|
-
return `- **${title}**${desc ? `:${desc}` : ''}`;
|
|
281
|
+
const title = catalogEntryTitle(tool.name, tool.manifest.name, `调用名 ${tool.name}`);
|
|
282
|
+
return `- ${title}${desc ? `:${desc}` : ''}`;
|
|
270
283
|
});
|
|
271
284
|
if (rows.length === 0)
|
|
272
285
|
return undefined;
|
|
@@ -276,6 +289,21 @@ export function buildToolCatalog(loadedTools) {
|
|
|
276
289
|
rows.join('\n'),
|
|
277
290
|
].join('\n');
|
|
278
291
|
}
|
|
292
|
+
/** 展示名 + skillId,供 system「可用技能」段。read_file 仍用 skillId。 */
|
|
293
|
+
export function buildSkillCatalog(defs) {
|
|
294
|
+
if (!defs)
|
|
295
|
+
return undefined;
|
|
296
|
+
const rows = Object.values(defs).map((skill) => {
|
|
297
|
+
const desc = (skill.description ?? '').replace(/\s+/g, ' ').trim();
|
|
298
|
+
const entry = skill.entryFile ?? (skill.bundlePath ? `${skill.bundlePath}/SKILL.md` : '');
|
|
299
|
+
const title = catalogEntryTitle(skill.skillId, skill.name, `skillId ${skill.skillId}`);
|
|
300
|
+
const pathLine = entry ? `\n SKILL.md: ${entry}` : '';
|
|
301
|
+
return `- ${title}${desc ? `:${desc}` : ''}${pathLine}`;
|
|
302
|
+
});
|
|
303
|
+
if (rows.length === 0)
|
|
304
|
+
return undefined;
|
|
305
|
+
return `# 可用技能\n要用某个技能时,调用 read_file 读取其 SKILL.md。脚本和参考文档默认在同一目录。\n${rows.join('\n')}`;
|
|
306
|
+
}
|
|
279
307
|
export function loadedToolsToApiTools(loadedTools, skillTools) {
|
|
280
308
|
if (!loadedTools)
|
|
281
309
|
return [];
|
|
@@ -122,6 +122,11 @@ export interface ToolCallResult {
|
|
|
122
122
|
size: number;
|
|
123
123
|
url: string;
|
|
124
124
|
}>;
|
|
125
|
+
/**
|
|
126
|
+
* `read_file` 实际读到的文件。只给 Runtime 记 Skill 节点 / SSE,不进模型正文。
|
|
127
|
+
* 没有该字段时不记 Skill 节点,不再用 `arguments.path` 猜。
|
|
128
|
+
*/
|
|
129
|
+
resolvedPath?: string;
|
|
125
130
|
}
|
|
126
131
|
export interface LLMToolCall {
|
|
127
132
|
id: string;
|