@gt-fe/eap-runtime 0.0.8 → 0.0.11

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.
Files changed (130) hide show
  1. package/README.md +15 -20
  2. package/dist/api/handlers/invoke.handler.js +7 -3
  3. package/dist/api/handlers/messages.handler.js +0 -1
  4. package/dist/api/handlers/stream.handler.js +8 -4
  5. package/dist/api/routes/agent.routes.d.ts +2 -2
  6. package/dist/api/routes/agent.routes.js +2 -2
  7. package/dist/checkpoint/checkpoint-ref-notify.d.ts +10 -0
  8. package/dist/checkpoint/checkpoint-ref-notify.js +59 -0
  9. package/dist/checkpoint/index.d.ts +1 -0
  10. package/dist/checkpoint/index.js +1 -0
  11. package/dist/checkpoint/pg-checkpoint-saver.js +25 -2
  12. package/dist/common/agent-code.d.ts +2 -4
  13. package/dist/common/agent-code.js +4 -5
  14. package/dist/common/types.d.ts +5 -5
  15. package/dist/common/types.js +2 -2
  16. package/dist/contracts/permission-checker.d.ts +1 -1
  17. package/dist/contracts/trace-collector.d.ts +16 -2
  18. package/dist/debug/llm-turn-log.d.ts +100 -0
  19. package/dist/debug/llm-turn-log.js +130 -0
  20. package/dist/debug/pg-llm-turn-log.d.ts +22 -0
  21. package/dist/debug/pg-llm-turn-log.js +57 -0
  22. package/dist/governance/governance-client.d.ts +1 -1
  23. package/dist/governance/governance-client.js +40 -16
  24. package/dist/governance/index.d.ts +2 -2
  25. package/dist/governance/index.js +1 -1
  26. package/dist/governance/permission-guard.d.ts +2 -1
  27. package/dist/governance/permission-guard.js +13 -4
  28. package/dist/graph/adapters/langgraph-checkpointer.js +3 -1
  29. package/dist/graph/adapters/langgraph.adapter.d.ts +1 -0
  30. package/dist/graph/adapters/langgraph.adapter.js +153 -92
  31. package/dist/graph/adapters/skill-read.d.ts +12 -0
  32. package/dist/graph/adapters/skill-read.js +63 -0
  33. package/dist/graph/adapters/subgraph-compiler.js +5 -0
  34. package/dist/graph/contracts/executable-graph.d.ts +11 -9
  35. package/dist/graph/contracts/executable-graph.js +1 -1
  36. package/dist/graph/hitl/hitl-handler.d.ts +10 -0
  37. package/dist/graph/hitl/hitl-handler.js +50 -6
  38. package/dist/graph/nodes/llm-node.d.ts +14 -0
  39. package/dist/graph/nodes/skill-bind-node.js +1 -2
  40. package/dist/graph/nodes/tool-node.d.ts +2 -0
  41. package/dist/graph/nodes/tool-node.js +50 -10
  42. package/dist/graph/trace-span.d.ts +22 -0
  43. package/dist/graph/trace-span.js +69 -0
  44. package/dist/index.d.ts +5 -2
  45. package/dist/index.js +3 -1
  46. package/dist/memory/compression/compression.repository.d.ts +1 -15
  47. package/dist/providers/echo-llm-provider.d.ts +1 -1
  48. package/dist/providers/echo-llm-provider.js +4 -3
  49. package/dist/providers/http-llm-provider.d.ts +4 -0
  50. package/dist/providers/http-llm-provider.js +129 -4
  51. package/dist/providers/local-llm-provider.d.ts +5 -1
  52. package/dist/providers/local-llm-provider.js +14 -6
  53. package/dist/providers/routing-llm-provider.d.ts +4 -0
  54. package/dist/providers/routing-llm-provider.js +12 -2
  55. package/dist/registry/agent-models.d.ts +1 -1
  56. package/dist/registry/agent-models.js +1 -1
  57. package/dist/registry/agent-registry-client.d.ts +3 -3
  58. package/dist/registry/deployment-snapshot.d.ts +17 -5
  59. package/dist/registry/deployment-snapshot.js +97 -49
  60. package/dist/registry/graph-loader.d.ts +2 -2
  61. package/dist/registry/graph-loader.js +43 -80
  62. package/dist/registry/index.d.ts +2 -2
  63. package/dist/registry/index.js +2 -2
  64. package/dist/registry/preload-errors.d.ts +1 -1
  65. package/dist/registry/preload-errors.js +1 -1
  66. package/dist/registry/skill-io-guard.js +1 -1
  67. package/dist/registry/skill-tree.d.ts +3 -0
  68. package/dist/registry/skill-tree.js +41 -3
  69. package/dist/runtime/agent-runtime.d.ts +42 -3
  70. package/dist/runtime/agent-runtime.js +129 -39
  71. package/dist/runtime/dev-runtime.d.ts +7 -4
  72. package/dist/runtime/dev-runtime.js +24 -21
  73. package/dist/runtime/index.d.ts +3 -3
  74. package/dist/runtime/index.js +1 -1
  75. package/dist/runtime/storage-profile.d.ts +19 -10
  76. package/dist/runtime/storage-profile.js +42 -29
  77. package/dist/runtime/types.d.ts +17 -19
  78. package/dist/server.js +2 -2
  79. package/dist/session/session.service.d.ts +5 -0
  80. package/dist/session/session.service.js +12 -0
  81. package/dist/sse/events/index.d.ts +5 -3
  82. package/dist/sse/events/index.js +2 -0
  83. package/dist/sse/events/skill-events.d.ts +12 -1
  84. package/dist/sse/events/skill-events.js +1 -1
  85. package/dist/sse/trace-collector.d.ts +10 -0
  86. package/dist/sse/trace-collector.js +1 -0
  87. package/dist/token/token-budget.service.d.ts +2 -2
  88. package/dist/token/token-budget.service.js +1 -1
  89. package/dist/tool/create-tool-executor.d.ts +1 -1
  90. package/dist/tool/create-tool-executor.js +3 -2
  91. package/dist/tool/dev-http-executor.d.ts +1 -1
  92. package/dist/tool/dev-http-executor.js +10 -7
  93. package/dist/tool/dev-mcp-executor.js +5 -3
  94. package/dist/tool/dev-tool-executor.d.ts +1 -1
  95. package/dist/tool/dev-tool-executor.js +2 -2
  96. package/dist/tool/harness-command.d.ts +32 -0
  97. package/dist/tool/harness-command.js +276 -0
  98. package/dist/tool/harness-primitives.d.ts +54 -0
  99. package/dist/tool/harness-primitives.js +492 -0
  100. package/dist/tool/http-tool-executor.js +16 -21
  101. package/dist/tool/index.d.ts +4 -2
  102. package/dist/tool/index.js +4 -2
  103. package/dist/tool/local-mcp-client.d.ts +1 -1
  104. package/dist/tool/local-mcp-client.js +54 -23
  105. package/dist/tool/local-tool-executor.d.ts +4 -4
  106. package/dist/tool/local-tool-executor.js +18 -16
  107. package/dist/tool/mcp-tool-executor.js +23 -7
  108. package/dist/tool/skill-script-refs.d.ts +20 -0
  109. package/dist/tool/skill-script-refs.js +62 -0
  110. package/dist/tool/system-prompt-builder.d.ts +61 -25
  111. package/dist/tool/system-prompt-builder.js +198 -92
  112. package/dist/tool/timeout.d.ts +14 -0
  113. package/dist/tool/timeout.js +36 -0
  114. package/dist/tool/tool-confirmation.js +2 -1
  115. package/dist/tool/tool-execution-router.d.ts +1 -1
  116. package/dist/tool/tool-execution-router.js +2 -2
  117. package/dist/tool/tool-executor.interface.d.ts +23 -7
  118. package/dist/tool/tool-orchestrator.d.ts +1 -1
  119. package/dist/tool/tool-orchestrator.js +6 -3
  120. package/dist/utils/format-error.d.ts +2 -2
  121. package/dist/utils/format-error.js +2 -2
  122. package/dist/utils/http-error.d.ts +2 -2
  123. package/dist/utils/http-error.js +2 -2
  124. package/package.json +3 -3
  125. package/dist/common/concurrency.d.ts +0 -79
  126. package/dist/common/concurrency.js +0 -121
  127. package/dist/common/queue-consumer.d.ts +0 -125
  128. package/dist/common/queue-consumer.js +0 -159
  129. package/dist/tool/mock-tool-executor.d.ts +0 -28
  130. package/dist/tool/mock-tool-executor.js +0 -65
@@ -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, loadedToolsToApiTools, filterLoadedToolsForSkill } from '../../tool/system-prompt-builder.js';
21
+ import { SystemPromptBuilder, buildToolCatalog, loadedToolsToApiTools } from '../../tool/system-prompt-builder.js';
22
+ import { resolveJailFromState, 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
- import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
28
+ import { AppError, ErrorCodes } from '@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,14 +303,15 @@ 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),
308
+ agentDir: ephemeral(undefined),
280
309
  executionContext: ephemeral(undefined),
281
310
  forceToolServer: ephemeral(undefined),
282
311
  traceCollector: ephemeral(undefined),
283
312
  traceId: ephemeral(undefined),
284
313
  skillSpanId: ephemeral(undefined),
314
+ lastSpanId: ephemeral(undefined),
285
315
  hitlChoice: ephemeral(undefined),
286
316
  // ── LLM Token 使用量:数组累加器 reducer,每次 LLM 节点追加一条使用明细
287
317
  __llmTokenUsage: {
@@ -546,25 +576,22 @@ export class LangGraphAdapter {
546
576
  case 'tool':
547
577
  return this.createToolNodeFunction(node);
548
578
  case 'condition':
549
- return async (state) => {
550
- // 条件节点:根据 config 中的 conditionExpr 评估并写入 __conditionResult
579
+ return async (state) => withGraphSpan(state, { name: node.id, type: 'condition' }, async () => {
551
580
  const conditionExpr = node.config?.['conditionExpr']
552
581
  ?? node.config?.['conditionFn'];
553
582
  if (conditionExpr) {
554
583
  const branchValues = node.config?.['branchValues']
555
584
  ?? this.extractBranchValuesFromDsl(node.id);
556
585
  const result = await this.conditionEvaluator.evaluate(conditionExpr, state, branchValues, node.config?.['fallback'] ?? '__default__');
557
- return { ...state, currentNode: node.id, __conditionResult: result };
586
+ return { currentNode: node.id, __conditionResult: result };
558
587
  }
559
- // 无表达式时透传(由 addConditionalEdge 自行处理)
560
- return { ...state, currentNode: node.id };
561
- };
588
+ return { currentNode: node.id };
589
+ });
562
590
  case 'subgraph':
563
591
  return this.createSubGraphNodeFunction(node);
564
592
  case 'human':
565
593
  return async (state) => {
566
- // HITL 节点:暂停等待人工介入(Task 14)
567
- return { ...state, currentNode: node.id };
594
+ return { currentNode: node.id };
568
595
  };
569
596
  case 'parallel':
570
597
  return this.createParallelNodeFunction(node);
@@ -639,11 +666,14 @@ export class LangGraphAdapter {
639
666
  return undefined;
640
667
  const rows = Object.values(defs).map((skill) => {
641
668
  const desc = (skill.description ?? '').replace(/\s+/g, ' ').trim();
642
- return `- ${skill.skillId}${skill.name && skill.name !== skill.skillId ? `(${skill.name})` : ''}${desc ? `:${desc}` : ''}`;
669
+ const entry = skill.entryFile ?? (skill.bundlePath ? `${skill.bundlePath}/SKILL.md` : '');
670
+ const title = skill.name && skill.name !== skill.skillId ? `${skill.skillId}(${skill.name})` : skill.skillId;
671
+ const pathLine = entry ? `\n SKILL.md: ${entry}` : '';
672
+ return `- **${title}**${desc ? `:${desc}` : ''}${pathLine}`;
643
673
  });
644
674
  if (rows.length === 0)
645
675
  return undefined;
646
- return `# 可用技能\n下列技能已装配,引用时使用 skillId 或 name。\n${rows.join('\n')}`;
676
+ return `# 可用技能\n要用某个技能时,调用 read_file 读取其 SKILL.md。脚本和参考文档默认在同一目录。\n${rows.join('\n')}`;
647
677
  }
648
678
  createToolNodeFunction(node) {
649
679
  const toolName = node.config?.['tool'] ?? node.id;
@@ -672,6 +702,10 @@ export class LangGraphAdapter {
672
702
  arguments: defaultArgs,
673
703
  node: node.id,
674
704
  });
705
+ const skillDefs = state['skillDefinitions'];
706
+ const jailExtraRoots = Object.values(skillDefs ?? {})
707
+ .map((skill) => skill.bundlePath)
708
+ .filter((p) => Boolean(p));
675
709
  const result = await this.toolExecutor.execute({
676
710
  toolCallId,
677
711
  toolName,
@@ -679,16 +713,22 @@ export class LangGraphAdapter {
679
713
  authContext: { userId, delegationToken, agentCode },
680
714
  correlationId,
681
715
  taskId,
716
+ ...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
682
717
  });
683
718
  const executionTime = Date.now() - startedAt;
719
+ const skillDirs = Object.values(skillDefs ?? {})
720
+ .filter((skill) => Boolean(skill.bundlePath))
721
+ .map((skill) => ({ skillId: skill.skillId, bundlePath: skill.bundlePath }));
684
722
  this.emitToolExecutionEvents({
685
723
  toolCallId: result.toolCallId,
686
724
  toolName: result.toolName,
687
725
  status: result.success ? 'completed' : 'error',
726
+ arguments: defaultArgs,
688
727
  content: result.content,
689
728
  error: result.error,
690
729
  executionTime,
691
730
  node: node.id,
731
+ ...(skillDirs.length > 0 ? { skillDirs } : {}),
692
732
  });
693
733
  // 重要:messages channel 的 reducer 是 append 语义,只返回新增消息
694
734
  return {
@@ -733,8 +773,8 @@ export class LangGraphAdapter {
733
773
  toolChoice: node.config?.['toolChoice'],
734
774
  };
735
775
  const disableTools = node.config?.['disableTools'] === true;
736
- const includeSkillCatalog = node.config?.['includeSkillCatalog'] === true;
737
776
  const silent = node.config?.['silent'] === true;
777
+ const includeSkillCatalog = node.config?.['includeSkillCatalog'] !== false;
738
778
  return async (state) => {
739
779
  // HITL 续跑:已有未闭合 toolCalls + hitlChoice 时不要再调 LLM(否则会换新 toolCallId)
740
780
  const hitlChoice = state['hitlChoice'];
@@ -745,92 +785,113 @@ export class LangGraphAdapter {
745
785
  lastAssistantHasUnansweredToolCalls(rawMessages)) {
746
786
  return { currentNode: node.id };
747
787
  }
748
- const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
749
- const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
750
- const loadedTools = state['loadedTools'];
751
- // 旧 Checkpoint 兼容:agentId 仅作读兜底
752
- const agentCode = (state['agentCode'] ?? state['agentId']);
753
- const activeSkillPrompt = state['activeSkillPrompt'];
754
- const skillDefs = state['skillDefinitions'];
755
- const activatedSkillId = state['activatedSkill'];
756
- // [DEBUG] 打印 loadedTools 实际状态
757
- if (process.env.EAP_LLM_DEBUG === '1') {
758
- const toolNames = loadedTools ? Object.keys(loadedTools) : [];
759
- console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
760
- }
761
- const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
762
- const developerPrompt = [catalog, activeSkillPrompt, resolvedPrompt]
763
- .filter((s) => Boolean(s && s.trim()))
764
- .join('\n\n');
765
- const execCtx = state['executionContext'];
766
- const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
767
- // 1. 构建 system prompt(平台规则 + 技能提示 + 开发者提示词)
768
- const builtPrompt = this.systemPromptBuilder.build({
769
- developerPrompt: developerPrompt || undefined,
770
- agentCode,
771
- userId,
772
- });
773
- // 2. 工具走 API 的 tools 参数,不塞进 system prompt
774
- const scopedTools = activatedSkillId && skillDefs
775
- ? filterLoadedToolsForSkill(loadedTools, skillDefs[activatedSkillId])
776
- : loadedTools;
777
- const apiTools = disableTools ? [] : loadedToolsToApiTools(scopedTools);
778
- const finalConfig = {
779
- ...baseConfig,
780
- systemPrompt: builtPrompt,
781
- tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
782
- toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
783
- };
784
- // 流式旁路:有 streamSink 时启用 LLM token 推送;silent 节点不推给用户
785
- const enableStream = Boolean(this.streamSink) && !silent;
786
- this.reasoningStartedThisTurn = false;
787
- this.streamedTextThisTurn = false;
788
- const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
789
- ? (text) => {
790
- if (!this.streamSink || !text)
791
- return;
792
- const timestamp = new Date().toISOString();
793
- if (!this.reasoningStartedThisTurn) {
794
- this.reasoningStartedThisTurn = true;
795
- this.streamIteration += 1;
788
+ return withGraphSpan(state, { name: node.id, type: 'llm' }, async () => {
789
+ const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
790
+ const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
791
+ const loadedTools = state['loadedTools'];
792
+ const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
793
+ const skillDefs = state['skillDefinitions'];
794
+ // [DEBUG] 打印 loadedTools 实际状态
795
+ if (process.env.EAP_LLM_DEBUG === '1') {
796
+ const toolNames = loadedTools ? Object.keys(loadedTools) : [];
797
+ console.log(`[LLM-DEBUG] node=${node.id} loadedTools=${toolNames.length}:`, toolNames);
798
+ }
799
+ const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
800
+ const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
801
+ const execCtx = state['executionContext'];
802
+ const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
803
+ const jail = resolveJailFromState({
804
+ agentDir: state['agentDir'],
805
+ userId,
806
+ executionContext: execCtx,
807
+ loadedTools,
808
+ skillDefinitions: skillDefs,
809
+ });
810
+ const workingDirectory = jail.workdir;
811
+ const jailRoots = resolveJailRoots(jail);
812
+ const skillDirs = Object.values(skillDefs ?? {})
813
+ .filter((skill) => Boolean(skill.bundlePath))
814
+ .map((skill) => ({
815
+ skillId: skill.skillId,
816
+ bundlePath: skill.bundlePath,
817
+ entryFile: skill.entryFile,
818
+ }));
819
+ const assembled = this.systemPromptBuilder.build({
820
+ developerPrompt: resolvedPrompt || undefined,
821
+ skillCatalog: catalog,
822
+ toolCatalog,
823
+ agentCode,
824
+ userId,
825
+ workingDirectory,
826
+ os: { platform: os.platform(), release: os.release() },
827
+ jailRoots,
828
+ skillDirs,
829
+ });
830
+ const apiTools = disableTools ? [] : loadedToolsToApiTools(loadedTools);
831
+ const finalConfig = {
832
+ ...baseConfig,
833
+ systemPrompt: assembled.text,
834
+ systemSections: assembled.sections,
835
+ turn: {
836
+ sessionId: typeof state['sessionId'] === 'string' ? state['sessionId'] : undefined,
837
+ taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
838
+ nodeId: node.id,
839
+ agentCode,
840
+ correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
841
+ },
842
+ tools: disableTools ? undefined : (apiTools.length > 0 ? apiTools : baseConfig.tools),
843
+ toolChoice: disableTools ? 'none' : baseConfig.toolChoice,
844
+ };
845
+ // 流式旁路:有 streamSink 时启用 LLM token 推送;silent 节点不推给用户
846
+ const enableStream = Boolean(this.streamSink) && !silent;
847
+ this.reasoningStartedThisTurn = false;
848
+ this.streamedTextThisTurn = false;
849
+ const llmNode = new LLMNode(node.id, finalConfig, this.llmProvider, enableStream, enableStream
850
+ ? (text) => {
851
+ if (!this.streamSink || !text)
852
+ return;
853
+ const timestamp = new Date().toISOString();
854
+ if (!this.reasoningStartedThisTurn) {
855
+ this.reasoningStartedThisTurn = true;
856
+ this.streamIteration += 1;
857
+ this.streamSink({
858
+ type: 'reasoning.started',
859
+ taskId: undefined,
860
+ correlationId: undefined,
861
+ timestamp,
862
+ payload: {
863
+ iterationCount: this.streamIteration,
864
+ model: finalConfig.model,
865
+ },
866
+ });
867
+ }
868
+ this.streamedTextThisTurn = true;
796
869
  this.streamSink({
797
- type: 'reasoning.started',
870
+ type: 'response.chunk',
798
871
  taskId: undefined,
799
872
  correlationId: undefined,
800
873
  timestamp,
801
874
  payload: {
802
- iterationCount: this.streamIteration,
803
- model: finalConfig.model,
875
+ content: text,
876
+ index: this.streamChunkIndex++,
877
+ node: node.id,
804
878
  },
805
879
  });
806
880
  }
807
- this.streamedTextThisTurn = true;
808
- this.streamSink({
809
- type: 'response.chunk',
810
- taskId: undefined,
811
- correlationId: undefined,
812
- timestamp,
813
- payload: {
814
- content: text,
815
- index: this.streamChunkIndex++,
816
- node: node.id,
817
- },
818
- });
819
- }
820
- : undefined);
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
- };
881
+ : undefined);
882
+ const next = await llmNode.execute(state);
883
+ if (!silent)
884
+ return next;
885
+ const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
886
+ const raw = added && typeof added === 'object' && typeof added.content === 'string'
887
+ ? added.content
888
+ : '';
889
+ return {
890
+ ...next,
891
+ messages: [],
892
+ silentOutput: raw,
893
+ };
894
+ });
834
895
  };
835
896
  }
836
897
  /**
@@ -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) => ({
@@ -2,17 +2,17 @@
2
2
  * Executable Graph 类型定义
3
3
  *
4
4
  * 定义 Graph 引擎核心的可执行图、DSL、节点、边等类型。
5
- * 与 @gt-fe/eap-contracts 的 GraphDSL 基础类型互补,在 Runtime 层提供更丰富的类型支持。
5
+ * 与 @eap/contracts 的 GraphDSL 基础类型互补,在 Runtime 层提供更丰富的类型支持。
6
6
  *
7
7
  * @see design.md §1.4 StateGraph Contract
8
8
  * @see requirements.md Requirement 1 AC1-AC2
9
9
  */
10
- import type { GraphDSL as BaseGraphDSL, GraphNode as BaseGraphNode, GraphEdge as BaseGraphEdge, GraphNodeType, GraphConfig } from '@gt-fe/eap-contracts';
10
+ import type { GraphDSL as BaseGraphDSL, GraphNode as BaseGraphNode, GraphEdge as BaseGraphEdge, GraphNodeType, GraphConfig } from '@eap/contracts';
11
11
  export type { GraphNodeType, GraphConfig };
12
12
  /**
13
13
  * RuntimeGraphConfig — Runtime 层扩展的 Graph 配置
14
14
  *
15
- * 在 @gt-fe/eap-contracts GraphConfig 基础上增加 Runtime 执行所需的 maxRetries。
15
+ * 在 @eap/contracts GraphConfig 基础上增加 Runtime 执行所需的 maxRetries。
16
16
  */
17
17
  export interface RuntimeGraphConfig extends GraphConfig {
18
18
  /** Graph 级默认重试次数(节点级 retry 可覆盖) */
@@ -21,7 +21,7 @@ export interface RuntimeGraphConfig extends GraphConfig {
21
21
  /**
22
22
  * GraphNode — Runtime 层扩展的节点定义
23
23
  *
24
- * 继承 @gt-fe/eap-contracts 基础 GraphNode,增加 Runtime 执行所需的字段:
24
+ * 继承 @eap/contracts 基础 GraphNode,增加 Runtime 执行所需的字段:
25
25
  * - 重试配置(节点级 maxRetries + 退避策略)
26
26
  * - HITL 标记(interrupt_before / interrupt_after)
27
27
  * - 描述信息(调试/Trace 用)
@@ -79,7 +79,7 @@ export interface ConditionalBranch {
79
79
  /**
80
80
  * GraphDSL — Runtime 层扩展的声明式工作流定义
81
81
  *
82
- * 扩展 @gt-fe/eap-contracts 的 BaseGraphDSL,增加条件路由、入口/终止节点等。
82
+ * 扩展 @eap/contracts 的 BaseGraphDSL,增加条件路由、入口/终止节点等。
83
83
  */
84
84
  export interface GraphDSL extends Omit<BaseGraphDSL, 'config' | 'nodes' | 'edges'> {
85
85
  /** 覆盖节点类型为 Runtime 扩展版本 */
@@ -163,7 +163,7 @@ export interface ExecutableGraph {
163
163
  /** 底层引擎内部实例引用(opaque,由 adapter 管理) */
164
164
  _engineInstance?: unknown;
165
165
  }
166
- import type { ExecutionContext as PlatformExecutionContext } from '@gt-fe/eap-contracts';
166
+ import type { ExecutionContext as PlatformExecutionContext } from '@eap/contracts';
167
167
  /**
168
168
  * GraphState — 传入 stream/invoke 的上下文数据
169
169
  */
@@ -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,10 +193,10 @@ 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[];
198
+ /** 预装 Agent 包目录:`{EAP_PREINSTALL_ROOT}/agents/<code>/<ver>` */
199
+ agentDir?: string;
200
200
  activatedSkill?: string | null;
201
201
  skillRoute?: string;
202
202
  /** bind / invoke 解析出的 Skill 入参;子图 IO 校验读取 */
@@ -207,6 +207,8 @@ export interface GraphState {
207
207
  traceCollector?: import('../../contracts/trace-collector.js').ITraceCollector;
208
208
  /** 当前 Skill 子图 createNode 返回的 span id;子图内 Tool span 的 parentNodeId */
209
209
  skillSpanId?: string;
210
+ /** 最近一次 createNode 的 span id;Checkpoint 引用挂在此 Span 上 */
211
+ lastSpanId?: string;
210
212
  /**
211
213
  * HITL 续跑:入口 messages 以 __set 覆盖 checkpoint 历史,避免 append 重复。
212
214
  * 仅 stream/invoke 入口使用,不入持久化语义。
@@ -2,7 +2,7 @@
2
2
  * Executable Graph 类型定义
3
3
  *
4
4
  * 定义 Graph 引擎核心的可执行图、DSL、节点、边等类型。
5
- * 与 @gt-fe/eap-contracts 的 GraphDSL 基础类型互补,在 Runtime 层提供更丰富的类型支持。
5
+ * 与 @eap/contracts 的 GraphDSL 基础类型互补,在 Runtime 层提供更丰富的类型支持。
6
6
  *
7
7
  * @see design.md §1.4 StateGraph Contract
8
8
  * @see requirements.md Requirement 1 AC1-AC2
@@ -11,10 +11,14 @@
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` 对外用秒。 */
17
18
  export declare const DEFAULT_HITL_TIMEOUT_MS = 300000;
19
+ /** 超时收口对外文案(Task / Trace / AppError);枚举走 `hitl_timeout`。 */
20
+ export declare const HITL_TIMEOUT_MESSAGE = "HITL \u786E\u8BA4\u8D85\u65F6";
21
+ export declare const HITL_TIMEOUT_ERROR_CODE = "hitl_timeout";
18
22
  /** HITL 超时行为 */
19
23
  export type HitlTimeoutBehavior = 'error' | 'cancel' | 'auto-confirm';
20
24
  /** HITL 中断配置 */
@@ -51,6 +55,7 @@ export interface HitlWaitingState {
51
55
  toolName?: string;
52
56
  toolSpanId?: string;
53
57
  skillSpanId?: string;
58
+ hitlSpanId?: string;
54
59
  }
55
60
  /** HITL 超时错误(保留类型;timer 不再 throw,resume 逾期改走 AppError) */
56
61
  export declare class HitlTimeoutError extends Error {
@@ -112,6 +117,7 @@ export interface PersistedHitlWaiting {
112
117
  toolName?: string;
113
118
  toolSpanId?: string;
114
119
  skillSpanId?: string;
120
+ hitlSpanId?: string;
115
121
  /** error/cancel 超时已占用此等待;resume 必须拒绝 */
116
122
  timedOut?: boolean;
117
123
  }
@@ -162,8 +168,12 @@ export declare class HitlHandler {
162
168
  private timeoutListener?;
163
169
  /** 同一 taskId 上串行化 resume 与 timeout,避免双边占用 */
164
170
  private taskLocks;
171
+ private traceCollector?;
172
+ /** 进程内 interrupt 的 collector,不进 Checkpoint */
173
+ private readonly waitingCollectors;
165
174
  constructor(checkpointSaver: ICheckpointSaver, sseEmitter: ISSEEmitter, timeoutListener?: HitlTimeoutListener);
166
175
  setTimeoutListener(listener: HitlTimeoutListener | undefined): void;
176
+ setTraceCollector(collector: ITraceCollector | undefined): void;
167
177
  /**
168
178
  * 中断 Graph 执行,等待人工确认
169
179
  *