@gt-fe/eap-runtime 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -17
- package/dist/api/handlers/invoke.handler.js +3 -3
- package/dist/api/handlers/messages.handler.js +0 -1
- package/dist/api/handlers/stream.handler.js +3 -3
- package/dist/api/routes/agent.routes.js +2 -2
- package/dist/checkpoint/checkpoint-ref-notify.d.ts +10 -0
- package/dist/checkpoint/checkpoint-ref-notify.js +59 -0
- package/dist/checkpoint/index.d.ts +1 -0
- package/dist/checkpoint/index.js +1 -0
- package/dist/checkpoint/pg-checkpoint-saver.js +25 -2
- package/dist/common/agent-code.d.ts +2 -4
- package/dist/common/agent-code.js +4 -5
- package/dist/contracts/trace-collector.d.ts +15 -1
- package/dist/debug/llm-turn-log.d.ts +100 -0
- package/dist/debug/llm-turn-log.js +130 -0
- package/dist/debug/pg-llm-turn-log.d.ts +22 -0
- package/dist/debug/pg-llm-turn-log.js +57 -0
- package/dist/graph/adapters/langgraph-checkpointer.js +3 -1
- package/dist/graph/adapters/langgraph.adapter.d.ts +1 -0
- package/dist/graph/adapters/langgraph.adapter.js +147 -91
- package/dist/graph/adapters/skill-read.d.ts +12 -0
- package/dist/graph/adapters/skill-read.js +63 -0
- package/dist/graph/adapters/subgraph-compiler.js +5 -0
- package/dist/graph/contracts/executable-graph.d.ts +3 -3
- package/dist/graph/hitl/hitl-handler.d.ts +7 -0
- package/dist/graph/hitl/hitl-handler.js +47 -6
- package/dist/graph/nodes/llm-node.d.ts +14 -0
- package/dist/graph/nodes/skill-bind-node.js +1 -2
- package/dist/graph/nodes/tool-node.d.ts +2 -0
- package/dist/graph/nodes/tool-node.js +24 -9
- package/dist/graph/trace-span.d.ts +22 -0
- package/dist/graph/trace-span.js +69 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/memory/compression/compression.repository.d.ts +1 -15
- package/dist/providers/echo-llm-provider.d.ts +1 -1
- package/dist/providers/echo-llm-provider.js +2 -1
- package/dist/providers/http-llm-provider.d.ts +4 -0
- package/dist/providers/http-llm-provider.js +129 -4
- package/dist/providers/local-llm-provider.d.ts +5 -1
- package/dist/providers/local-llm-provider.js +14 -6
- package/dist/providers/routing-llm-provider.d.ts +4 -0
- package/dist/providers/routing-llm-provider.js +12 -2
- package/dist/registry/agent-models.d.ts +1 -1
- package/dist/registry/agent-models.js +1 -1
- package/dist/registry/agent-registry-client.d.ts +2 -2
- package/dist/registry/deployment-snapshot.d.ts +4 -4
- package/dist/registry/deployment-snapshot.js +50 -28
- package/dist/registry/graph-loader.d.ts +1 -1
- package/dist/registry/graph-loader.js +39 -76
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/skill-tree.d.ts +3 -0
- package/dist/registry/skill-tree.js +41 -3
- package/dist/runtime/agent-runtime.d.ts +42 -3
- package/dist/runtime/agent-runtime.js +110 -34
- package/dist/runtime/dev-runtime.d.ts +6 -3
- package/dist/runtime/dev-runtime.js +21 -14
- package/dist/runtime/index.d.ts +3 -3
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/storage-profile.d.ts +19 -10
- package/dist/runtime/storage-profile.js +42 -29
- package/dist/runtime/types.d.ts +15 -17
- package/dist/server.js +1 -1
- package/dist/session/session.service.d.ts +5 -0
- package/dist/session/session.service.js +12 -0
- package/dist/sse/events/index.d.ts +5 -3
- package/dist/sse/events/index.js +2 -0
- package/dist/sse/events/skill-events.d.ts +11 -0
- package/dist/sse/trace-collector.d.ts +10 -0
- package/dist/sse/trace-collector.js +1 -0
- package/dist/token/token-budget.service.d.ts +2 -2
- package/dist/token/token-budget.service.js +1 -1
- package/dist/tool/create-tool-executor.js +3 -2
- package/dist/tool/harness-primitives.d.ts +32 -0
- package/dist/tool/harness-primitives.js +385 -0
- package/dist/tool/http-tool-executor.js +1 -1
- package/dist/tool/index.d.ts +2 -1
- package/dist/tool/index.js +2 -1
- package/dist/tool/local-tool-executor.d.ts +3 -3
- package/dist/tool/local-tool-executor.js +5 -7
- package/dist/tool/mcp-tool-executor.js +9 -1
- package/dist/tool/system-prompt-builder.d.ts +61 -25
- package/dist/tool/system-prompt-builder.js +195 -92
- package/dist/tool/tool-execution-router.js +1 -1
- package/dist/tool/tool-executor.interface.d.ts +12 -6
- package/package.json +2 -2
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @see tasks.md Task 11.5
|
|
9
9
|
*/
|
|
10
10
|
import { BaseCheckpointSaver, } from '@langchain/langgraph';
|
|
11
|
+
import { notifyCheckpointRef, stripCheckpointCollectors, } from '../../checkpoint/checkpoint-ref-notify.js';
|
|
11
12
|
/**
|
|
12
13
|
* LangGraphCheckpointer — 桥接 LangGraph BaseCheckpointSaver → ICheckpointSaver
|
|
13
14
|
*
|
|
@@ -42,7 +43,8 @@ export class LangGraphCheckpointer extends BaseCheckpointSaver {
|
|
|
42
43
|
async put(config, checkpoint, metadata, _newVersions) {
|
|
43
44
|
const threadId = this.extractThreadId(config) ?? `thread-${Date.now()}`;
|
|
44
45
|
const data = this.checkpointToData(threadId, checkpoint, metadata);
|
|
45
|
-
await this.saver.save(threadId, data);
|
|
46
|
+
await this.saver.save(threadId, stripCheckpointCollectors(data));
|
|
47
|
+
await notifyCheckpointRef(data);
|
|
46
48
|
return {
|
|
47
49
|
...config,
|
|
48
50
|
configurable: {
|
|
@@ -64,6 +64,7 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
|
|
|
64
64
|
/**
|
|
65
65
|
* Tool 节点执行旁路:started → tool.called + tool.progress;
|
|
66
66
|
* completed/error → 对应终态 + tool.progress。同一 toolCallId。
|
|
67
|
+
* 读 SKILL.md / 技能包内文件:成功后再发 skill.loaded / skill.resource_read,同时保留 tool.*。
|
|
67
68
|
*/
|
|
68
69
|
private emitToolExecutionEvents;
|
|
69
70
|
/**
|
|
@@ -18,13 +18,17 @@ import { ToolResultInjector } from '../../tool/tool-result-injector.js';
|
|
|
18
18
|
import { fileArtifactFromToolOutput } from '../../tool/file-artifact.js';
|
|
19
19
|
import { EchoLLMProvider } from '../../providers/echo-llm-provider.js';
|
|
20
20
|
import { ConditionEvaluator } from '../condition-evaluator.js';
|
|
21
|
-
import { SystemPromptBuilder,
|
|
21
|
+
import { SystemPromptBuilder, buildToolCatalog, loadedToolsToApiTools } from '../../tool/system-prompt-builder.js';
|
|
22
|
+
import { resolveJailRoots } from '../../tool/harness-primitives.js';
|
|
23
|
+
import os from 'node:os';
|
|
22
24
|
import { bindSelectedSkill } from '../nodes/skill-bind-node.js';
|
|
23
25
|
import { resolveGraphPrompt } from '../resolve-graph-prompt.js';
|
|
24
26
|
import { SkillAuditRecorder } from '../skill-audit-recorder.js';
|
|
25
27
|
import { preloadSkillNotFound } from '../../registry/preload-errors.js';
|
|
26
28
|
import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
|
|
27
29
|
import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
|
|
30
|
+
import { classifySkillRead } from './skill-read.js';
|
|
31
|
+
import { withGraphSpan } from '../trace-span.js';
|
|
28
32
|
// ─────────────────────────────────────────────
|
|
29
33
|
// LangGraphAdapter
|
|
30
34
|
// ─────────────────────────────────────────────
|
|
@@ -148,6 +152,7 @@ export class LangGraphAdapter {
|
|
|
148
152
|
/**
|
|
149
153
|
* Tool 节点执行旁路:started → tool.called + tool.progress;
|
|
150
154
|
* completed/error → 对应终态 + tool.progress。同一 toolCallId。
|
|
155
|
+
* 读 SKILL.md / 技能包内文件:成功后再发 skill.loaded / skill.resource_read,同时保留 tool.*。
|
|
151
156
|
*/
|
|
152
157
|
emitToolExecutionEvents(progress) {
|
|
153
158
|
if (!this.streamSink)
|
|
@@ -158,6 +163,30 @@ export class LangGraphAdapter {
|
|
|
158
163
|
...(progress.node ? { node: progress.node } : {}),
|
|
159
164
|
...(progress.skillId ? { skillId: progress.skillId } : {}),
|
|
160
165
|
};
|
|
166
|
+
const skillRead = classifySkillRead(progress.toolName, progress.arguments, progress.skillDirs);
|
|
167
|
+
if (skillRead && progress.status === 'completed') {
|
|
168
|
+
if (skillRead.kind === 'loaded') {
|
|
169
|
+
this.streamSink({
|
|
170
|
+
type: 'skill.loaded',
|
|
171
|
+
taskId,
|
|
172
|
+
correlationId,
|
|
173
|
+
timestamp,
|
|
174
|
+
payload: {
|
|
175
|
+
skillId: skillRead.skillId || loc.skillId || skillRead.path,
|
|
176
|
+
path: skillRead.path,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
this.streamSink({
|
|
182
|
+
type: 'skill.resource_read',
|
|
183
|
+
taskId,
|
|
184
|
+
correlationId,
|
|
185
|
+
timestamp,
|
|
186
|
+
payload: { skillId: skillRead.skillId || loc.skillId, path: skillRead.path },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
161
190
|
if (progress.status === 'started') {
|
|
162
191
|
this.streamSink({
|
|
163
192
|
type: 'tool.called',
|
|
@@ -274,7 +303,6 @@ export class LangGraphAdapter {
|
|
|
274
303
|
activeSkillPrompt: ephemeral(undefined),
|
|
275
304
|
activatedSkill: ephemeral(undefined),
|
|
276
305
|
skillRoute: ephemeral(undefined),
|
|
277
|
-
skillSelectRaw: ephemeral(undefined),
|
|
278
306
|
silentOutput: ephemeral(undefined),
|
|
279
307
|
promptRoots: ephemeral(undefined),
|
|
280
308
|
executionContext: ephemeral(undefined),
|
|
@@ -282,6 +310,7 @@ export class LangGraphAdapter {
|
|
|
282
310
|
traceCollector: ephemeral(undefined),
|
|
283
311
|
traceId: ephemeral(undefined),
|
|
284
312
|
skillSpanId: ephemeral(undefined),
|
|
313
|
+
lastSpanId: ephemeral(undefined),
|
|
285
314
|
hitlChoice: ephemeral(undefined),
|
|
286
315
|
// ── LLM Token 使用量:数组累加器 reducer,每次 LLM 节点追加一条使用明细
|
|
287
316
|
__llmTokenUsage: {
|
|
@@ -546,25 +575,22 @@ export class LangGraphAdapter {
|
|
|
546
575
|
case 'tool':
|
|
547
576
|
return this.createToolNodeFunction(node);
|
|
548
577
|
case 'condition':
|
|
549
|
-
return async (state) => {
|
|
550
|
-
// 条件节点:根据 config 中的 conditionExpr 评估并写入 __conditionResult
|
|
578
|
+
return async (state) => withGraphSpan(state, { name: node.id, type: 'condition' }, async () => {
|
|
551
579
|
const conditionExpr = node.config?.['conditionExpr']
|
|
552
580
|
?? node.config?.['conditionFn'];
|
|
553
581
|
if (conditionExpr) {
|
|
554
582
|
const branchValues = node.config?.['branchValues']
|
|
555
583
|
?? this.extractBranchValuesFromDsl(node.id);
|
|
556
584
|
const result = await this.conditionEvaluator.evaluate(conditionExpr, state, branchValues, node.config?.['fallback'] ?? '__default__');
|
|
557
|
-
return {
|
|
585
|
+
return { currentNode: node.id, __conditionResult: result };
|
|
558
586
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
};
|
|
587
|
+
return { currentNode: node.id };
|
|
588
|
+
});
|
|
562
589
|
case 'subgraph':
|
|
563
590
|
return this.createSubGraphNodeFunction(node);
|
|
564
591
|
case 'human':
|
|
565
592
|
return async (state) => {
|
|
566
|
-
|
|
567
|
-
return { ...state, currentNode: node.id };
|
|
593
|
+
return { currentNode: node.id };
|
|
568
594
|
};
|
|
569
595
|
case 'parallel':
|
|
570
596
|
return this.createParallelNodeFunction(node);
|
|
@@ -639,11 +665,14 @@ export class LangGraphAdapter {
|
|
|
639
665
|
return undefined;
|
|
640
666
|
const rows = Object.values(defs).map((skill) => {
|
|
641
667
|
const desc = (skill.description ?? '').replace(/\s+/g, ' ').trim();
|
|
642
|
-
|
|
668
|
+
const entry = skill.entryFile ?? (skill.bundlePath ? `${skill.bundlePath}/SKILL.md` : '');
|
|
669
|
+
const title = skill.name && skill.name !== skill.skillId ? `${skill.skillId}(${skill.name})` : skill.skillId;
|
|
670
|
+
const pathLine = entry ? `\n SKILL.md: ${entry}` : '';
|
|
671
|
+
return `- **${title}**${desc ? `:${desc}` : ''}${pathLine}`;
|
|
643
672
|
});
|
|
644
673
|
if (rows.length === 0)
|
|
645
674
|
return undefined;
|
|
646
|
-
return `# 可用技能\n
|
|
675
|
+
return `# 可用技能\n要用某个技能时,调用 read_file 读取其 SKILL.md。脚本和参考文档默认在同一目录。\n${rows.join('\n')}`;
|
|
647
676
|
}
|
|
648
677
|
createToolNodeFunction(node) {
|
|
649
678
|
const toolName = node.config?.['tool'] ?? node.id;
|
|
@@ -672,6 +701,10 @@ export class LangGraphAdapter {
|
|
|
672
701
|
arguments: defaultArgs,
|
|
673
702
|
node: node.id,
|
|
674
703
|
});
|
|
704
|
+
const skillDefs = state['skillDefinitions'];
|
|
705
|
+
const jailExtraRoots = Object.values(skillDefs ?? {})
|
|
706
|
+
.map((skill) => skill.bundlePath)
|
|
707
|
+
.filter((p) => Boolean(p));
|
|
675
708
|
const result = await this.toolExecutor.execute({
|
|
676
709
|
toolCallId,
|
|
677
710
|
toolName,
|
|
@@ -679,16 +712,22 @@ export class LangGraphAdapter {
|
|
|
679
712
|
authContext: { userId, delegationToken, agentCode },
|
|
680
713
|
correlationId,
|
|
681
714
|
taskId,
|
|
715
|
+
...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
|
|
682
716
|
});
|
|
683
717
|
const executionTime = Date.now() - startedAt;
|
|
718
|
+
const skillDirs = Object.values(skillDefs ?? {})
|
|
719
|
+
.filter((skill) => Boolean(skill.bundlePath))
|
|
720
|
+
.map((skill) => ({ skillId: skill.skillId, bundlePath: skill.bundlePath }));
|
|
684
721
|
this.emitToolExecutionEvents({
|
|
685
722
|
toolCallId: result.toolCallId,
|
|
686
723
|
toolName: result.toolName,
|
|
687
724
|
status: result.success ? 'completed' : 'error',
|
|
725
|
+
arguments: defaultArgs,
|
|
688
726
|
content: result.content,
|
|
689
727
|
error: result.error,
|
|
690
728
|
executionTime,
|
|
691
729
|
node: node.id,
|
|
730
|
+
...(skillDirs.length > 0 ? { skillDirs } : {}),
|
|
692
731
|
});
|
|
693
732
|
// 重要:messages channel 的 reducer 是 append 语义,只返回新增消息
|
|
694
733
|
return {
|
|
@@ -733,8 +772,8 @@ export class LangGraphAdapter {
|
|
|
733
772
|
toolChoice: node.config?.['toolChoice'],
|
|
734
773
|
};
|
|
735
774
|
const disableTools = node.config?.['disableTools'] === true;
|
|
736
|
-
const includeSkillCatalog = node.config?.['includeSkillCatalog'] === true;
|
|
737
775
|
const silent = node.config?.['silent'] === true;
|
|
776
|
+
const includeSkillCatalog = node.config?.['includeSkillCatalog'] !== false;
|
|
738
777
|
return async (state) => {
|
|
739
778
|
// HITL 续跑:已有未闭合 toolCalls + hitlChoice 时不要再调 LLM(否则会换新 toolCallId)
|
|
740
779
|
const hitlChoice = state['hitlChoice'];
|
|
@@ -745,92 +784,109 @@ export class LangGraphAdapter {
|
|
|
745
784
|
lastAssistantHasUnansweredToolCalls(rawMessages)) {
|
|
746
785
|
return { currentNode: node.id };
|
|
747
786
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
.
|
|
764
|
-
.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
787
|
+
return withGraphSpan(state, { name: node.id, type: 'llm' }, async () => {
|
|
788
|
+
const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
|
|
789
|
+
const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
|
|
790
|
+
const loadedTools = state['loadedTools'];
|
|
791
|
+
const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
|
|
792
|
+
const skillDefs = state['skillDefinitions'];
|
|
793
|
+
// [DEBUG] 打印 loadedTools 实际状态
|
|
794
|
+
if (process.env.EAP_LLM_DEBUG === '1') {
|
|
795
|
+
const toolNames = loadedTools ? Object.keys(loadedTools) : [];
|
|
796
|
+
console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
|
|
797
|
+
}
|
|
798
|
+
const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
|
|
799
|
+
const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
|
|
800
|
+
const execCtx = state['executionContext'];
|
|
801
|
+
const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
|
|
802
|
+
const workingDirectory = typeof process.cwd === 'function' ? process.cwd() : undefined;
|
|
803
|
+
const skillDirs = Object.values(skillDefs ?? {})
|
|
804
|
+
.filter((skill) => Boolean(skill.bundlePath))
|
|
805
|
+
.map((skill) => ({
|
|
806
|
+
skillId: skill.skillId,
|
|
807
|
+
bundlePath: skill.bundlePath,
|
|
808
|
+
entryFile: skill.entryFile,
|
|
809
|
+
}));
|
|
810
|
+
const jailRoots = resolveJailRoots({
|
|
811
|
+
workdir: workingDirectory,
|
|
812
|
+
extraRoots: skillDirs.map((s) => s.bundlePath),
|
|
813
|
+
});
|
|
814
|
+
const assembled = this.systemPromptBuilder.build({
|
|
815
|
+
developerPrompt: resolvedPrompt || undefined,
|
|
816
|
+
skillCatalog: catalog,
|
|
817
|
+
toolCatalog,
|
|
818
|
+
agentCode,
|
|
819
|
+
userId,
|
|
820
|
+
workingDirectory,
|
|
821
|
+
os: { platform: os.platform(), release: os.release() },
|
|
822
|
+
jailRoots,
|
|
823
|
+
skillDirs,
|
|
824
|
+
});
|
|
825
|
+
const apiTools = disableTools ? [] : loadedToolsToApiTools(loadedTools);
|
|
826
|
+
const finalConfig = {
|
|
827
|
+
...baseConfig,
|
|
828
|
+
systemPrompt: assembled.text,
|
|
829
|
+
systemSections: assembled.sections,
|
|
830
|
+
turn: {
|
|
831
|
+
sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
|
|
832
|
+
taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
|
|
833
|
+
nodeId: node.id,
|
|
834
|
+
agentCode,
|
|
835
|
+
correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
|
|
836
|
+
},
|
|
837
|
+
tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
|
|
838
|
+
toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
|
|
839
|
+
};
|
|
840
|
+
// 流式旁路:有 streamSink 时启用 LLM token 推送;silent 节点不推给用户
|
|
841
|
+
const enableStream = Boolean(this.streamSink) && !silent;
|
|
842
|
+
this.reasoningStartedThisTurn = false;
|
|
843
|
+
this.streamedTextThisTurn = false;
|
|
844
|
+
const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
|
|
845
|
+
? (text) => {
|
|
846
|
+
if (!this.streamSink || !text)
|
|
847
|
+
return;
|
|
848
|
+
const timestamp = new Date().toISOString();
|
|
849
|
+
if (!this.reasoningStartedThisTurn) {
|
|
850
|
+
this.reasoningStartedThisTurn = true;
|
|
851
|
+
this.streamIteration += 1;
|
|
852
|
+
this.streamSink({
|
|
853
|
+
type: 'reasoning.started',
|
|
854
|
+
taskId: undefined,
|
|
855
|
+
correlationId: undefined,
|
|
856
|
+
timestamp,
|
|
857
|
+
payload: {
|
|
858
|
+
iterationCount: this.streamIteration,
|
|
859
|
+
model: finalConfig.model,
|
|
860
|
+
},
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
this.streamedTextThisTurn = true;
|
|
796
864
|
this.streamSink({
|
|
797
|
-
type: '
|
|
865
|
+
type: 'response.chunk',
|
|
798
866
|
taskId: undefined,
|
|
799
867
|
correlationId: undefined,
|
|
800
868
|
timestamp,
|
|
801
869
|
payload: {
|
|
802
|
-
|
|
803
|
-
|
|
870
|
+
content: text,
|
|
871
|
+
index: this.streamChunkIndex++,
|
|
872
|
+
node: node.id,
|
|
804
873
|
},
|
|
805
874
|
});
|
|
806
875
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const next = await llmNode.execute(state);
|
|
822
|
-
if (!silent)
|
|
823
|
-
return next;
|
|
824
|
-
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
825
|
-
const raw = added && typeof added === 'object' && typeof added.content === 'string'
|
|
826
|
-
? added.content
|
|
827
|
-
: '';
|
|
828
|
-
return {
|
|
829
|
-
...next,
|
|
830
|
-
messages: [],
|
|
831
|
-
silentOutput: raw,
|
|
832
|
-
skillSelectRaw: raw,
|
|
833
|
-
};
|
|
876
|
+
: undefined);
|
|
877
|
+
const next = await llmNode.execute(state);
|
|
878
|
+
if (!silent)
|
|
879
|
+
return next;
|
|
880
|
+
const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
|
|
881
|
+
const raw = added && typeof added === 'object' && typeof added.content === 'string'
|
|
882
|
+
? added.content
|
|
883
|
+
: '';
|
|
884
|
+
return {
|
|
885
|
+
...next,
|
|
886
|
+
messages: [],
|
|
887
|
+
silentOutput: raw,
|
|
888
|
+
};
|
|
889
|
+
});
|
|
834
890
|
};
|
|
835
891
|
}
|
|
836
892
|
/**
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface SkillDirRef {
|
|
2
|
+
skillId: string;
|
|
3
|
+
bundlePath: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* 判断 harness 读操作是否命中技能包,并用 bundlePath 最长前缀解析 skillId。
|
|
7
|
+
*/
|
|
8
|
+
export declare function classifySkillRead(toolName: string, args: Record<string, unknown> | undefined, skillDirs?: SkillDirRef[]): {
|
|
9
|
+
kind: 'loaded' | 'resource';
|
|
10
|
+
path: string;
|
|
11
|
+
skillId: string;
|
|
12
|
+
} | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
function tryRealpath(p) {
|
|
4
|
+
try {
|
|
5
|
+
return realpathSync(p);
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function normalizeForMatch(p) {
|
|
12
|
+
const resolved = path.resolve(p);
|
|
13
|
+
return (tryRealpath(resolved) ?? resolved).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
14
|
+
}
|
|
15
|
+
function longestSkillMatch(filePath, skillDirs) {
|
|
16
|
+
if (!skillDirs?.length)
|
|
17
|
+
return undefined;
|
|
18
|
+
const target = normalizeForMatch(filePath);
|
|
19
|
+
let best;
|
|
20
|
+
for (const dir of skillDirs) {
|
|
21
|
+
if (!dir.bundlePath)
|
|
22
|
+
continue;
|
|
23
|
+
const root = normalizeForMatch(dir.bundlePath);
|
|
24
|
+
if (target === root || target.startsWith(`${root}/`)) {
|
|
25
|
+
if (!best || root.length > best.len) {
|
|
26
|
+
best = { skillId: dir.skillId, bundlePath: root, len: root.length };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return best;
|
|
31
|
+
}
|
|
32
|
+
function skillIdFromSkillsSegment(norm) {
|
|
33
|
+
const match = norm.match(/(?:^|\/)(?:preinstall\/)?skills\/([^/]+)(?:\/|$)/i);
|
|
34
|
+
return match?.[1] ?? '';
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 判断 harness 读操作是否命中技能包,并用 bundlePath 最长前缀解析 skillId。
|
|
38
|
+
*/
|
|
39
|
+
export function classifySkillRead(toolName, args, skillDirs) {
|
|
40
|
+
const n = toolName.toLowerCase().replace(/-/g, '_');
|
|
41
|
+
if (n !== 'read_file' && n !== 'read_skill_file' && n !== 'readfile')
|
|
42
|
+
return null;
|
|
43
|
+
const filePath = String(args?.path ?? args?.file ?? '');
|
|
44
|
+
if (!filePath)
|
|
45
|
+
return null;
|
|
46
|
+
const norm = filePath.replace(/\\/g, '/');
|
|
47
|
+
const matched = longestSkillMatch(filePath, skillDirs);
|
|
48
|
+
const isEntry = /(^|\/)(SKILL\.md|skill\.md)$/i.test(norm);
|
|
49
|
+
if (matched) {
|
|
50
|
+
return {
|
|
51
|
+
kind: isEntry ? 'loaded' : 'resource',
|
|
52
|
+
path: filePath,
|
|
53
|
+
skillId: matched.skillId,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (isEntry) {
|
|
57
|
+
return { kind: 'loaded', path: filePath, skillId: skillIdFromSkillsSegment(norm) };
|
|
58
|
+
}
|
|
59
|
+
if (/(^|\/)(?:preinstall\/)?skills\//i.test(norm)) {
|
|
60
|
+
return { kind: 'resource', path: filePath, skillId: skillIdFromSkillsSegment(norm) };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
@@ -138,6 +138,7 @@ export class SubGraphCompiler {
|
|
|
138
138
|
traceId,
|
|
139
139
|
traceCollector,
|
|
140
140
|
skillSpanId: skillSpanId ?? undefined,
|
|
141
|
+
lastSpanId: skillSpanId ?? state['lastSpanId'],
|
|
141
142
|
hitlChoice: state['hitlChoice'],
|
|
142
143
|
hitlRespondedBy: state['hitlRespondedBy'],
|
|
143
144
|
};
|
|
@@ -185,9 +186,13 @@ export class SubGraphCompiler {
|
|
|
185
186
|
? [{ role: 'assistant', content: result.output }]
|
|
186
187
|
: [];
|
|
187
188
|
const perNode = result.tokenUsage?.perNode;
|
|
189
|
+
const nestedLastSpanId = (typeof result.finalState?.lastSpanId === 'string' && result.finalState.lastSpanId) ||
|
|
190
|
+
skillSpanId ||
|
|
191
|
+
undefined;
|
|
188
192
|
return {
|
|
189
193
|
messages: outputMessages,
|
|
190
194
|
currentNode: `subgraph:${dsl.name}`,
|
|
195
|
+
...(nestedLastSpanId ? { lastSpanId: nestedLastSpanId } : {}),
|
|
191
196
|
...(Array.isArray(perNode) && perNode.length > 0
|
|
192
197
|
? {
|
|
193
198
|
__llmTokenUsage: perNode.map((u) => ({
|
|
@@ -185,7 +185,7 @@ export interface GraphState {
|
|
|
185
185
|
forceToolServer?: string[];
|
|
186
186
|
/** GraphLoader 注入:toolName → 运行期 manifest 引用 */
|
|
187
187
|
loadedTools?: import('../../tool/loaded-tool-definition.js').LoadedToolMap;
|
|
188
|
-
/** GraphLoader 注入:已装配 Skill
|
|
188
|
+
/** GraphLoader 注入:已装配 Skill 列表 */
|
|
189
189
|
loadedSkills?: import('../../tool/system-prompt-builder.js').LoadedSkillInfo[];
|
|
190
190
|
/** GraphLoader 注入:skillId → 完整定义(子图解析 / prompt) */
|
|
191
191
|
skillDefinitions?: import('../../tool/system-prompt-builder.js').LoadedSkillMap;
|
|
@@ -193,8 +193,6 @@ export interface GraphState {
|
|
|
193
193
|
activeSkillPrompt?: string;
|
|
194
194
|
/** silent LLM 的原始输出;不入对话 messages */
|
|
195
195
|
silentOutput?: string;
|
|
196
|
-
/** @deprecated 同 silentOutput,兼容旧图 */
|
|
197
|
-
skillSelectRaw?: string;
|
|
198
196
|
/** 解析 graph 内 ./prompts/*.md 的根目录(agentDir + 工程根) */
|
|
199
197
|
promptRoots?: string[];
|
|
200
198
|
activatedSkill?: string | null;
|
|
@@ -207,6 +205,8 @@ export interface GraphState {
|
|
|
207
205
|
traceCollector?: import('../../contracts/trace-collector.js').ITraceCollector;
|
|
208
206
|
/** 当前 Skill 子图 createNode 返回的 span id;子图内 Tool span 的 parentNodeId */
|
|
209
207
|
skillSpanId?: string;
|
|
208
|
+
/** 最近一次 createNode 的 span id;Checkpoint 引用挂在此 Span 上 */
|
|
209
|
+
lastSpanId?: string;
|
|
210
210
|
/**
|
|
211
211
|
* HITL 续跑:入口 messages 以 __set 覆盖 checkpoint 历史,避免 append 重复。
|
|
212
212
|
* 仅 stream/invoke 入口使用,不入持久化语义。
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @see tasks.md Task 14
|
|
12
12
|
*/
|
|
13
13
|
import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
|
|
14
|
+
import type { ITraceCollector } from '../../contracts/trace-collector.js';
|
|
14
15
|
import type { ISSEEmitter } from '../../sse/contracts/sse-emitter.contract.js';
|
|
15
16
|
import type { GraphState } from '../contracts/executable-graph.js';
|
|
16
17
|
/** HITL 默认等待时长(毫秒)= 300 秒。SSE `hitl.waited.timeout` 对外用秒。 */
|
|
@@ -51,6 +52,7 @@ export interface HitlWaitingState {
|
|
|
51
52
|
toolName?: string;
|
|
52
53
|
toolSpanId?: string;
|
|
53
54
|
skillSpanId?: string;
|
|
55
|
+
hitlSpanId?: string;
|
|
54
56
|
}
|
|
55
57
|
/** HITL 超时错误(保留类型;timer 不再 throw,resume 逾期改走 AppError) */
|
|
56
58
|
export declare class HitlTimeoutError extends Error {
|
|
@@ -112,6 +114,7 @@ export interface PersistedHitlWaiting {
|
|
|
112
114
|
toolName?: string;
|
|
113
115
|
toolSpanId?: string;
|
|
114
116
|
skillSpanId?: string;
|
|
117
|
+
hitlSpanId?: string;
|
|
115
118
|
/** error/cancel 超时已占用此等待;resume 必须拒绝 */
|
|
116
119
|
timedOut?: boolean;
|
|
117
120
|
}
|
|
@@ -162,8 +165,12 @@ export declare class HitlHandler {
|
|
|
162
165
|
private timeoutListener?;
|
|
163
166
|
/** 同一 taskId 上串行化 resume 与 timeout,避免双边占用 */
|
|
164
167
|
private taskLocks;
|
|
168
|
+
private traceCollector?;
|
|
169
|
+
/** 进程内 interrupt 的 collector,不进 Checkpoint */
|
|
170
|
+
private readonly waitingCollectors;
|
|
165
171
|
constructor(checkpointSaver: ICheckpointSaver, sseEmitter: ISSEEmitter, timeoutListener?: HitlTimeoutListener);
|
|
166
172
|
setTimeoutListener(listener: HitlTimeoutListener | undefined): void;
|
|
173
|
+
setTraceCollector(collector: ITraceCollector | undefined): void;
|
|
167
174
|
/**
|
|
168
175
|
* 中断 Graph 执行,等待人工确认
|
|
169
176
|
*
|