@gt-fe/eap-runtime 0.0.12 → 0.0.14

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 (112) hide show
  1. package/README.md +3 -1
  2. package/dist/api/handlers/invoke.handler.d.ts +1 -1
  3. package/dist/api/handlers/invoke.handler.js +3 -0
  4. package/dist/api/handlers/stream.handler.d.ts +1 -1
  5. package/dist/api/handlers/stream.handler.js +4 -1
  6. package/dist/api/routes/agent.routes.d.ts +2 -2
  7. package/dist/common/index.d.ts +1 -0
  8. package/dist/common/index.js +1 -0
  9. package/dist/common/logger.d.ts +16 -4
  10. package/dist/common/logger.js +82 -16
  11. package/dist/common/runtime-package-version.d.ts +2 -0
  12. package/dist/common/runtime-package-version.js +5 -0
  13. package/dist/common/types.d.ts +5 -5
  14. package/dist/common/types.js +2 -2
  15. package/dist/contracts/permission-checker.d.ts +1 -1
  16. package/dist/debug/llm-turn-log.d.ts +12 -0
  17. package/dist/debug/llm-turn-log.js +1 -0
  18. package/dist/governance/governance-client.d.ts +1 -1
  19. package/dist/governance/index.d.ts +1 -1
  20. package/dist/governance/permission-guard.d.ts +1 -1
  21. package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
  22. package/dist/graph/adapters/langgraph.adapter.js +67 -14
  23. package/dist/graph/adapters/subgraph-compiler.js +5 -2
  24. package/dist/graph/contracts/executable-graph.d.ts +19 -8
  25. package/dist/graph/contracts/executable-graph.js +1 -1
  26. package/dist/graph/nodes/llm-node.d.ts +3 -0
  27. package/dist/graph/nodes/llm-node.js +8 -0
  28. package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
  29. package/dist/graph/nodes/skill-bind-node.js +1 -20
  30. package/dist/graph/nodes/tool-node.d.ts +2 -0
  31. package/dist/graph/nodes/tool-node.js +20 -14
  32. package/dist/index.d.ts +1 -1
  33. package/dist/memory/compression/index.d.ts +1 -1
  34. package/dist/memory/compression/index.js +1 -1
  35. package/dist/memory/compression/sliding-window.compression.js +7 -7
  36. package/dist/memory/index.d.ts +1 -1
  37. package/dist/memory/index.js +1 -1
  38. package/dist/memory/message-compression.service.d.ts +12 -1
  39. package/dist/memory/message-compression.service.js +29 -1
  40. package/dist/providers/http-llm-provider.d.ts +1 -0
  41. package/dist/providers/http-llm-provider.js +4 -1
  42. package/dist/providers/index.d.ts +1 -1
  43. package/dist/providers/index.js +1 -1
  44. package/dist/providers/local-llm-provider.d.ts +4 -1
  45. package/dist/providers/local-llm-provider.js +15 -3
  46. package/dist/providers/routing-llm-provider.js +13 -4
  47. package/dist/registry/agent-models.d.ts +1 -1
  48. package/dist/registry/agent-models.js +1 -1
  49. package/dist/registry/agent-registry-client.d.ts +1 -1
  50. package/dist/registry/deployment-snapshot.js +19 -17
  51. package/dist/registry/graph-loader.d.ts +11 -1
  52. package/dist/registry/graph-loader.js +115 -56
  53. package/dist/registry/preload-errors.d.ts +1 -1
  54. package/dist/registry/preload-errors.js +1 -1
  55. package/dist/registry/skill-io-guard.js +1 -1
  56. package/dist/registry/skill-tree.js +1 -1
  57. package/dist/runtime/agent-runtime.d.ts +3 -3
  58. package/dist/runtime/agent-runtime.js +14 -4
  59. package/dist/runtime/dev-runtime.d.ts +2 -0
  60. package/dist/runtime/dev-runtime.js +2 -0
  61. package/dist/runtime/types.d.ts +4 -4
  62. package/dist/server.js +1 -1
  63. package/dist/session/compression-middleware.d.ts +2 -1
  64. package/dist/session/compression-middleware.js +7 -4
  65. package/dist/sse/events/skill-events.d.ts +1 -1
  66. package/dist/sse/events/skill-events.js +1 -1
  67. package/dist/sse/events/stats-events.d.ts +9 -0
  68. package/dist/token/compression-budget-integration.d.ts +1 -1
  69. package/dist/token/compression-budget-integration.js +18 -10
  70. package/dist/token/context-window.d.ts +42 -0
  71. package/dist/token/context-window.js +73 -0
  72. package/dist/token/index.d.ts +1 -0
  73. package/dist/token/index.js +1 -0
  74. package/dist/tool/create-tool-executor.d.ts +1 -1
  75. package/dist/tool/dev-http-executor.d.ts +2 -1
  76. package/dist/tool/dev-http-executor.js +14 -2
  77. package/dist/tool/dev-mcp-executor.d.ts +11 -1
  78. package/dist/tool/dev-mcp-executor.js +27 -1
  79. package/dist/tool/dev-tool-executor.d.ts +2 -1
  80. package/dist/tool/dev-tool-executor.js +6 -0
  81. package/dist/tool/harness-command.js +1 -1
  82. package/dist/tool/harness-primitives.d.ts +7 -0
  83. package/dist/tool/harness-primitives.js +57 -6
  84. package/dist/tool/http-tool-executor.js +1 -1
  85. package/dist/tool/index.d.ts +2 -1
  86. package/dist/tool/index.js +1 -0
  87. package/dist/tool/local-mcp-client.d.ts +5 -2
  88. package/dist/tool/local-mcp-client.js +130 -3
  89. package/dist/tool/local-tool-executor.d.ts +3 -2
  90. package/dist/tool/local-tool-executor.js +16 -1
  91. package/dist/tool/mcp-list.d.ts +53 -0
  92. package/dist/tool/mcp-list.js +199 -0
  93. package/dist/tool/mcp-tool-executor.d.ts +6 -1
  94. package/dist/tool/mcp-tool-executor.js +53 -1
  95. package/dist/tool/skill-dir-infer.d.ts +17 -0
  96. package/dist/tool/skill-dir-infer.js +81 -0
  97. package/dist/tool/skill-script-refs.d.ts +1 -8
  98. package/dist/tool/skill-script-refs.js +32 -20
  99. package/dist/tool/system-prompt-builder.d.ts +35 -16
  100. package/dist/tool/system-prompt-builder.js +30 -58
  101. package/dist/tool/timeout.d.ts +2 -2
  102. package/dist/tool/timeout.js +6 -3
  103. package/dist/tool/tool-execution-router.d.ts +3 -2
  104. package/dist/tool/tool-execution-router.js +8 -1
  105. package/dist/tool/tool-executor.interface.d.ts +11 -0
  106. package/dist/tool/tool-orchestrator.d.ts +1 -1
  107. package/dist/tool/tool-orchestrator.js +1 -1
  108. package/dist/utils/format-error.d.ts +2 -2
  109. package/dist/utils/format-error.js +2 -2
  110. package/dist/utils/http-error.d.ts +2 -2
  111. package/dist/utils/http-error.js +2 -2
  112. package/package.json +6 -33
@@ -18,17 +18,35 @@ 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, buildToolCatalog, loadedToolsToApiTools } from '../../tool/system-prompt-builder.js';
21
+ import { SystemPromptBuilder, buildToolCatalog, loadedToolsToApiTools, resolveLlmPromptInputs } from '../../tool/system-prompt-builder.js';
22
22
  import { resolveJailFromState, resolveJailRoots } from '../../tool/harness-primitives.js';
23
23
  import os from 'node:os';
24
24
  import { bindSelectedSkill } from '../nodes/skill-bind-node.js';
25
25
  import { resolveGraphPrompt } from '../resolve-graph-prompt.js';
26
+ import { hydrateMcpTools, MCP_LIST_TOOL_NAME } from '../../tool/mcp-list.js';
26
27
  import { SkillAuditRecorder } from '../skill-audit-recorder.js';
27
28
  import { preloadSkillNotFound } from '../../registry/preload-errors.js';
28
- import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
29
+ import { AppError, ErrorCodes } from '@eap/contracts';
30
+ import { pickContextFields } from '../../token/context-window.js';
29
31
  import { isHitlDecision, lastAssistantHasUnansweredToolCalls } from '../hitl/hitl-handler.js';
30
32
  import { classifySkillRead } from './skill-read.js';
31
33
  import { withGraphSpan } from '../trace-span.js';
34
+ /** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
35
+ export const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
36
+ /**
37
+ * LangGraph `recursionLimit` 计 superstep(每进一个节点 +1),不是 ReAct 圈数。
38
+ * 图上 `config.maxIterations` 优先;缺省 {@link DEFAULT_GRAPH_RECURSION_LIMIT}。
39
+ */
40
+ function langGraphRunOptions(dsl, extra) {
41
+ const raw = dsl.config?.maxIterations;
42
+ const recursionLimit = typeof raw === 'number' && Number.isFinite(raw) && raw > 0
43
+ ? Math.trunc(raw)
44
+ : DEFAULT_GRAPH_RECURSION_LIMIT;
45
+ return {
46
+ ...extra,
47
+ recursionLimit,
48
+ };
49
+ }
32
50
  // ─────────────────────────────────────────────
33
51
  // LangGraphAdapter
34
52
  // ─────────────────────────────────────────────
@@ -196,6 +214,7 @@ export class LangGraphAdapter {
196
214
  payload: {
197
215
  toolCallId: progress.toolCallId,
198
216
  toolName: progress.toolName,
217
+ ...(progress.toolset ? { toolset: progress.toolset } : {}),
199
218
  arguments: progress.arguments ?? {},
200
219
  ...loc,
201
220
  },
@@ -217,6 +236,7 @@ export class LangGraphAdapter {
217
236
  payload: {
218
237
  toolCallId: progress.toolCallId,
219
238
  toolName: progress.toolName,
239
+ ...(progress.toolset ? { toolset: progress.toolset } : {}),
220
240
  content: progress.content,
221
241
  success: true,
222
242
  ...loc,
@@ -232,6 +252,7 @@ export class LangGraphAdapter {
232
252
  payload: {
233
253
  toolCallId: progress.toolCallId,
234
254
  toolName: progress.toolName,
255
+ ...(progress.toolset ? { toolset: progress.toolset } : {}),
235
256
  error: progress.error ?? 'Unknown error',
236
257
  willRetry: false,
237
258
  ...loc,
@@ -300,7 +321,7 @@ export class LangGraphAdapter {
300
321
  loadedSkills: ephemeral(undefined),
301
322
  skillDefinitions: ephemeral(undefined),
302
323
  skillInput: ephemeral(undefined),
303
- activeSkillPrompt: ephemeral(undefined),
324
+ agentSystemPrompt: ephemeral(undefined),
304
325
  activatedSkill: ephemeral(undefined),
305
326
  skillRoute: ephemeral(undefined),
306
327
  silentOutput: ephemeral(undefined),
@@ -421,7 +442,7 @@ export class LangGraphAdapter {
421
442
  this.streamedTextThisTurn = false;
422
443
  const producer = (async () => {
423
444
  try {
424
- const streamIterator = await ref.compiled.stream(input, {
445
+ const streamIterator = await ref.compiled.stream(input, langGraphRunOptions(ref.dsl, {
425
446
  configurable: {
426
447
  thread_id: resolveCheckpointThreadId({
427
448
  sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
@@ -430,7 +451,7 @@ export class LangGraphAdapter {
430
451
  }),
431
452
  },
432
453
  streamMode: 'updates',
433
- });
454
+ }));
434
455
  for await (const update of streamIterator) {
435
456
  for (const event of this.convertUpdatesToEvents(update, graph.id, () => this.streamChunkIndex++, ref.silentNodeIds, ref.subgraphNodeIds)) {
436
457
  enqueue(event);
@@ -467,7 +488,7 @@ export class LangGraphAdapter {
467
488
  const ref = this.getCompiledRef(graph);
468
489
  const input = this.graphStateToLangGraphInput(state);
469
490
  const startTime = Date.now();
470
- const result = await ref.compiled.invoke(input, {
491
+ const result = await ref.compiled.invoke(input, langGraphRunOptions(ref.dsl, {
471
492
  configurable: {
472
493
  thread_id: resolveCheckpointThreadId({
473
494
  sessionId: typeof state.sessionId === 'string' ? state.sessionId : undefined,
@@ -475,7 +496,7 @@ export class LangGraphAdapter {
475
496
  graphId: graph.id,
476
497
  }),
477
498
  },
478
- });
499
+ }));
479
500
  const duration = Date.now() - startTime;
480
501
  const messages = Array.isArray(result['messages']) ? result['messages'] : [];
481
502
  const lastMessage = messages[messages.length - 1];
@@ -492,6 +513,7 @@ export class LangGraphAdapter {
492
513
  model: u.model ?? 'unknown',
493
514
  inputTokens: u.inputTokens ?? 0,
494
515
  outputTokens: u.outputTokens ?? 0,
516
+ ...pickContextFields(u),
495
517
  }));
496
518
  const inputTokens = perNode.reduce((s, u) => s + u.inputTokens, 0);
497
519
  const outputTokens = perNode.reduce((s, u) => s + u.outputTokens, 0);
@@ -655,7 +677,6 @@ export class LangGraphAdapter {
655
677
  currentNode: node.id,
656
678
  skillRoute: patch.skillRoute,
657
679
  activatedSkill: patch.activatedSkill,
658
- activeSkillPrompt: patch.activeSkillPrompt,
659
680
  skillInput: patch.skillInput,
660
681
  messages: { __set: patch.messages },
661
682
  };
@@ -788,9 +809,34 @@ export class LangGraphAdapter {
788
809
  return withGraphSpan(state, { name: node.id, type: 'llm' }, async () => {
789
810
  const promptRoots = Array.isArray(state['promptRoots']) ? state['promptRoots'] : [];
790
811
  const resolvedPrompt = resolveGraphPrompt(baseConfig.systemPrompt, promptRoots);
791
- const loadedTools = state['loadedTools'];
812
+ const loadedToolsBase = state['loadedTools'];
792
813
  const agentCode = typeof state['agentCode'] === 'string' ? state['agentCode'] : undefined;
793
814
  const skillDefs = state['skillDefinitions'];
815
+ const execCtx = state['executionContext'];
816
+ const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
817
+ const loadedTools = disableTools
818
+ ? loadedToolsBase
819
+ : await hydrateMcpTools(loadedToolsBase, this.toolExecutor, {
820
+ authContext: {
821
+ userId: String(userId ?? ''),
822
+ delegationToken: String(execCtx?.delegationToken ?? state['delegationToken'] ?? ''),
823
+ agentCode: String(agentCode ?? ''),
824
+ },
825
+ correlationId: typeof state['correlationId'] === 'string' ? state['correlationId'] : undefined,
826
+ taskId: typeof state['taskId'] === 'string' ? state['taskId'] : undefined,
827
+ onListFailure: ({ toolset, error }) => {
828
+ this.emitToolExecutionEvents({
829
+ toolCallId: `mcp-list:${toolset}`,
830
+ toolName: MCP_LIST_TOOL_NAME,
831
+ toolset,
832
+ status: 'error',
833
+ error,
834
+ });
835
+ },
836
+ });
837
+ if (loadedTools && loadedTools !== loadedToolsBase) {
838
+ state['loadedTools'] = loadedTools;
839
+ }
794
840
  // [DEBUG] 打印 loadedTools 实际状态
795
841
  if (process.env.EAP_LLM_DEBUG === '1') {
796
842
  const toolNames = loadedTools ? Object.keys(loadedTools) : [];
@@ -798,8 +844,6 @@ export class LangGraphAdapter {
798
844
  }
799
845
  const catalog = includeSkillCatalog ? this.buildSkillCatalog(skillDefs) : undefined;
800
846
  const toolCatalog = disableTools ? undefined : buildToolCatalog(loadedTools);
801
- const execCtx = state['executionContext'];
802
- const userId = (typeof execCtx?.userId === 'string' && execCtx.userId) || state['userId'];
803
847
  const jail = resolveJailFromState({
804
848
  agentDir: state['agentDir'],
805
849
  userId,
@@ -816,8 +860,14 @@ export class LangGraphAdapter {
816
860
  bundlePath: skill.bundlePath,
817
861
  entryFile: skill.entryFile,
818
862
  }));
863
+ const promptInputs = resolveLlmPromptInputs({
864
+ silent,
865
+ agentSystemPrompt: state['agentSystemPrompt'],
866
+ nodePrompt: resolvedPrompt,
867
+ });
819
868
  const assembled = this.systemPromptBuilder.build({
820
- developerPrompt: resolvedPrompt || undefined,
869
+ agentPrompt: promptInputs.agentPrompt,
870
+ nodePrompt: promptInputs.nodePrompt,
821
871
  skillCatalog: catalog,
822
872
  toolCatalog,
823
873
  agentCode,
@@ -880,8 +930,9 @@ export class LangGraphAdapter {
880
930
  }
881
931
  : undefined);
882
932
  const next = await llmNode.execute(state);
933
+ const hydratedUpdate = loadedTools && loadedTools !== loadedToolsBase ? { loadedTools } : {};
883
934
  if (!silent)
884
- return next;
935
+ return { ...next, ...hydratedUpdate };
885
936
  const added = Array.isArray(next.messages) ? next.messages[0] : undefined;
886
937
  const raw = added && typeof added === 'object' && typeof added.content === 'string'
887
938
  ? added.content
@@ -890,13 +941,14 @@ export class LangGraphAdapter {
890
941
  ...next,
891
942
  messages: [],
892
943
  silentOutput: raw,
944
+ ...hydratedUpdate,
893
945
  };
894
946
  });
895
947
  };
896
948
  }
897
949
  /**
898
950
  * 子图节点:优先 config.subgraphDsl;否则按 skillId 从 state.skillDefinitions.workflow 解析。
899
- * 技能 prompt 由 SubGraphCompiler 写入 activeSkillPrompt,供入口 LLM 拼接。
951
+ * 技能正文用短目录 + read_file,不灌 system。
900
952
  */
901
953
  createSubGraphNodeFunction(node) {
902
954
  const embeddedDsl = node.config?.['subgraphDsl'];
@@ -1199,6 +1251,7 @@ export class LangGraphAdapter {
1199
1251
  costEstimate: Number(u['costEstimate'] ?? 0),
1200
1252
  durationMs: typeof u['durationMs'] === 'number' ? u['durationMs'] : undefined,
1201
1253
  graphId,
1254
+ ...pickContextFields(u),
1202
1255
  },
1203
1256
  };
1204
1257
  }
@@ -12,6 +12,7 @@ import { LangGraphAdapter } from './langgraph.adapter.js';
12
12
  import { resolveTraceId } from '../../common/trace-id.js';
13
13
  import { filterLoadedToolsForSkill, } from '../../tool/system-prompt-builder.js';
14
14
  import { unwrapHitlPaused } from '../hitl/hitl-handler.js';
15
+ import { pickContextFields } from '../../token/context-window.js';
15
16
  import { assertSkillIoIfEnabled, coerceSkillInputFromMessages, } from '../../registry/skill-io-guard.js';
16
17
  /**
17
18
  * SubGraphCompiler — 子图编译器
@@ -99,7 +100,6 @@ export class SubGraphCompiler {
99
100
  const agentCode = state['agentCode'] ?? state['agentId'];
100
101
  const defs = state['skillDefinitions'];
101
102
  const skillInfo = resolvedSkillId ? defs?.[resolvedSkillId] : undefined;
102
- const skillPrompt = skillInfo?.prompt;
103
103
  const parentTools = state['loadedTools'];
104
104
  const scopedTools = filterLoadedToolsForSkill(parentTools, skillInfo);
105
105
  const skillInput = skillInfo
@@ -132,7 +132,9 @@ export class SubGraphCompiler {
132
132
  loadedTools: scopedTools,
133
133
  loadedSkills: state['loadedSkills'],
134
134
  skillDefinitions: defs,
135
- activeSkillPrompt: skillPrompt,
135
+ agentSystemPrompt: typeof state['agentSystemPrompt'] === 'string'
136
+ ? state['agentSystemPrompt']
137
+ : undefined,
136
138
  activatedSkill: resolvedSkillId || undefined,
137
139
  skillInput,
138
140
  traceId,
@@ -200,6 +202,7 @@ export class SubGraphCompiler {
200
202
  model: u.model,
201
203
  inputTokens: u.inputTokens,
202
204
  outputTokens: u.outputTokens,
205
+ ...pickContextFields(u),
203
206
  })),
204
207
  }
205
208
  : {}),
@@ -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
  */
@@ -189,14 +189,18 @@ export interface GraphState {
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;
192
- /** 当前子图 / 已绑定技能提示词(按 skillId 注入,入口 LLM 读取) */
193
- activeSkillPrompt?: string;
192
+ /**
193
+ * Agent 基线系统提示词(manifest.prompts.system)。
194
+ * execute/stream 每轮注入;非 silent LLM 拼入 system 的 agent 段。
195
+ */
196
+ agentSystemPrompt?: string;
194
197
  /** silent LLM 的原始输出;不入对话 messages */
195
198
  silentOutput?: string;
196
199
  /** 解析 graph 内 ./prompts/*.md 的根目录(agentDir + 工程根) */
197
200
  promptRoots?: string[];
198
201
  /** 预装 Agent 包目录:`{EAP_PREINSTALL_ROOT}/agents/<code>/<ver>` */
199
202
  agentDir?: string;
203
+ /** bind / 子图选中的技能;harness 路径解析不写此字段 */
200
204
  activatedSkill?: string | null;
201
205
  skillRoute?: string;
202
206
  /** bind / invoke 解析出的 Skill 入参;子图 IO 校验读取 */
@@ -331,6 +335,13 @@ export interface GraphTokenUsage {
331
335
  model: string;
332
336
  inputTokens: number;
333
337
  outputTokens: number;
338
+ used?: number;
339
+ window?: number;
340
+ parts?: {
341
+ system: number;
342
+ messages: number;
343
+ tools: number;
344
+ };
334
345
  }>;
335
346
  /** 当前会话跨请求累计消耗(可选;由 session 层服务填充) */
336
347
  conversationTotal?: {
@@ -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
@@ -58,6 +58,8 @@ export interface LLMNodeConfig {
58
58
  agentCode?: string;
59
59
  correlationId?: string;
60
60
  };
61
+ /** ModelRecord.capabilities.maxContextTokens;缺省则不写 used/window */
62
+ maxContextTokens?: number;
61
63
  }
62
64
  /**
63
65
  * LLMResponse — LLM 完整响应
@@ -178,6 +180,7 @@ export declare class LLMNode {
178
180
  *
179
181
  * 根据 streaming 配置选择 invoke 或 stream 模式。
180
182
  * 失败时按 backoff 策略重试,最多重试 maxRetries 次。
183
+ * AbortError / 「This operation was aborted」(含 LLM HTTP 超时包装)立即失败,不重试。
181
184
  */
182
185
  private invokeWithRetry;
183
186
  /**
@@ -10,6 +10,8 @@
10
10
  * @see requirements.md Requirement 1 AC6, Requirement 9
11
11
  */
12
12
  import { formatUnknownError } from '../../utils/format-error.js';
13
+ import { buildContextUsage, turnSectionChars } from '../../token/context-window.js';
14
+ import { isAbortError } from '../../tool/timeout.js';
13
15
  // ─────────────────────────────────────────────
14
16
  // Default retry config
15
17
  // ─────────────────────────────────────────────
@@ -94,6 +96,7 @@ export class LLMNode {
94
96
  // (即 user 消息重复的根因)。
95
97
  //
96
98
  // __llmTokenUsage channel 同样是 **数组累加器 reducer**,每次追加一条使用明细。
99
+ const context = buildContextUsage(response.inputTokens, this.config.maxContextTokens, turnSectionChars(this.config, messages));
97
100
  return {
98
101
  messages: [assistantMessage],
99
102
  currentNode: this.nodeId,
@@ -104,6 +107,7 @@ export class LLMNode {
104
107
  inputTokens: response.inputTokens,
105
108
  outputTokens: response.outputTokens,
106
109
  durationMs,
110
+ ...(context ?? {}),
107
111
  },
108
112
  ],
109
113
  };
@@ -183,6 +187,7 @@ export class LLMNode {
183
187
  *
184
188
  * 根据 streaming 配置选择 invoke 或 stream 模式。
185
189
  * 失败时按 backoff 策略重试,最多重试 maxRetries 次。
190
+ * AbortError / 「This operation was aborted」(含 LLM HTTP 超时包装)立即失败,不重试。
186
191
  */
187
192
  async invokeWithRetry(messages) {
188
193
  let lastError;
@@ -195,6 +200,9 @@ export class LLMNode {
195
200
  }
196
201
  catch (error) {
197
202
  lastError = error instanceof Error ? error : new Error(formatUnknownError(error));
203
+ if (isAbortError(error) || isAbortError(lastError)) {
204
+ throw lastError;
205
+ }
198
206
  // 最后一次尝试不需要等待
199
207
  if (attempt < this.retryConfig.maxRetries) {
200
208
  const delay = this.calculateBackoffDelay(attempt);
@@ -1,6 +1,6 @@
1
1
  /**
2
- * 可选图胶水:把 silent LLM 的结构化输出写成 activeSkillPrompt / skillRoute。
3
- * 不是 Graph 节点类型。任何「多本手册互斥」的域图都可选用;字段是约定不是契约。
2
+ * 可选图胶水:把 silent LLM 的结构化输出写成 skillRoute / activatedSkill。
3
+ * 不是 Graph 节点类型。技能正文走短目录 + read_file,不灌 system。
4
4
  */
5
5
  import type { LoadedSkillInfo, LoadedSkillMap } from '../../tool/system-prompt-builder.js';
6
6
  export type SkillRoute = 'skill' | 'clarify' | 'none';
@@ -9,7 +9,6 @@ export interface SkillBindPatch {
9
9
  activatedSkill: string | null;
10
10
  /** 展示名;未选中时为空 */
11
11
  skillName?: string;
12
- activeSkillPrompt?: string;
13
12
  skillInput?: Record<string, unknown>;
14
13
  messages: Array<Record<string, unknown>>;
15
14
  /** 用户写了 skill 名但清单里没有,才算 unmatched */
@@ -25,7 +24,7 @@ interface SkillSelectPayload {
25
24
  export declare function extractJsonObject(text: string): SkillSelectPayload | null;
26
25
  export declare function resolveSkillDefinition(defs: LoadedSkillMap | undefined, rawName: string): LoadedSkillInfo | undefined;
27
26
  /**
28
- * 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
27
+ * 消费选择节点刚写出的 assistant JSON,改写为路由。
29
28
  * 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
30
29
  */
31
30
  export declare function bindSelectedSkill(state: Record<string, unknown>): SkillBindPatch;
@@ -38,26 +38,8 @@ export function resolveSkillDefinition(defs, rawName) {
38
38
  (skill.name && skill.name.toLowerCase() === needle));
39
39
  });
40
40
  }
41
- function formatParams(params) {
42
- return Object.entries(params)
43
- .map(([key, value]) => `- ${key}: ${JSON.stringify(value)}`)
44
- .join('\n');
45
- }
46
- function buildSkillPrompt(skill, params) {
47
- const parts = [`# 技能:${skill.name || skill.skillId}`];
48
- if (skill.description) {
49
- parts.push(skill.description);
50
- }
51
- if (params && Object.keys(params).length > 0) {
52
- parts.push(`# 提取的参数\n${formatParams(params)}`);
53
- }
54
- if (skill.prompt?.trim()) {
55
- parts.push(skill.prompt.trim());
56
- }
57
- return parts.join('\n\n');
58
- }
59
41
  /**
60
- * 消费选择节点刚写出的 assistant JSON,改写为路由 + Skill 提示词。
42
+ * 消费选择节点刚写出的 assistant JSON,改写为路由。
61
43
  * 返回的 messages 是**整段替换**(调用方须覆盖 messages channel 时改用 replace 语义)。
62
44
  */
63
45
  export function bindSelectedSkill(state) {
@@ -108,7 +90,6 @@ export function bindSelectedSkill(state) {
108
90
  skillRoute: 'skill',
109
91
  activatedSkill: skill.skillId,
110
92
  skillName: skill.name || skill.skillId,
111
- activeSkillPrompt: buildSkillPrompt(skill, params),
112
93
  skillInput: params,
113
94
  messages: nextMessages(),
114
95
  };
@@ -33,6 +33,8 @@ type LangGraphState = Record<string, unknown>;
33
33
  export interface ToolNodeProgress {
34
34
  toolCallId: string;
35
35
  toolName: string;
36
+ /** 工具集编码;与 toolName 一起给 UI 显示 `toolset/toolName` */
37
+ toolset?: string;
36
38
  status: 'started' | 'completed' | 'error';
37
39
  arguments?: Record<string, unknown>;
38
40
  content?: string;
@@ -10,29 +10,18 @@
10
10
  * @see requirements.md Requirement 8 AC1, AC3, AC5
11
11
  */
12
12
  import { resolveJailFromState } from '../../tool/harness-primitives.js';
13
+ import { inferHarnessSkillDir } from '../../tool/skill-dir-infer.js';
13
14
  import { isHitlDecision, unwrapHitlPaused } from '../hitl/hitl-handler.js';
14
15
  import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
15
16
  import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
16
17
  import { resolveTraceId } from '../../common/trace-id.js';
18
+ import { toolsetCodeFromLoaded } from '../../tool/mcp-list.js';
17
19
  function skillDirsFromState(state) {
18
20
  const defs = state['skillDefinitions'];
19
21
  return Object.values(defs ?? {})
20
22
  .filter((skill) => Boolean(skill.bundlePath))
21
23
  .map((skill) => ({ skillId: skill.skillId, bundlePath: skill.bundlePath }));
22
24
  }
23
- function resolveActiveSkillDir(state) {
24
- const defs = state['skillDefinitions'];
25
- if (!defs)
26
- return undefined;
27
- const activated = state['activatedSkill'];
28
- if (typeof activated === 'string' && defs[activated]?.bundlePath) {
29
- return defs[activated].bundlePath;
30
- }
31
- const paths = Object.values(defs)
32
- .map((skill) => skill.bundlePath)
33
- .filter((p) => Boolean(p));
34
- return paths.length === 1 ? paths[0] : undefined;
35
- }
36
25
  export class ToolNode {
37
26
  orchestrator;
38
27
  injector;
@@ -75,12 +64,24 @@ export class ToolNode {
75
64
  skillDefinitions: state['skillDefinitions'],
76
65
  });
77
66
  const jailExtraRoots = jail.extraRoots ?? [];
78
- const skillDir = resolveActiveSkillDir(state);
67
+ const batchCalls = lastAssistantMessage.toolCalls.map((tc) => ({
68
+ name: tc.name,
69
+ arguments: tc.arguments,
70
+ }));
71
+ const boundSkill = typeof state['activatedSkill'] === 'string' ? state['activatedSkill'] : undefined;
79
72
  // 4. 转换为 ToolCallRequest[](附带 manifest 路由字段)
80
73
  const requests = lastAssistantMessage.toolCalls.map((tc) => {
81
74
  const loaded = loadedTools?.[tc.name];
82
75
  const correlationId = String(state['correlationId'] ?? tc.id);
83
76
  const taskId = String(state['taskId'] ?? state['correlationId'] ?? tc.id);
77
+ const command = typeof tc.arguments?.command === 'string' ? tc.arguments.command : undefined;
78
+ const skillDir = inferHarnessSkillDir({
79
+ command,
80
+ toolCalls: batchCalls,
81
+ messages,
82
+ skillDirs,
83
+ activatedSkill: boundSkill,
84
+ });
84
85
  return {
85
86
  toolCallId: tc.id,
86
87
  toolName: tc.name,
@@ -153,11 +154,13 @@ export class ToolNode {
153
154
  const needsHitl = Boolean(this.hitlHandler) &&
154
155
  requests.some((req) => confirmationTools.has(req.toolName));
155
156
  const runOne = async (req, i) => {
157
+ const toolset = toolsetCodeFromLoaded(loadedTools?.[req.toolName]);
156
158
  const resumingHitl = isHitlDecision(state['hitlChoice']);
157
159
  if (!resumingHitl) {
158
160
  emitProgress({
159
161
  toolCallId: req.toolCallId,
160
162
  toolName: req.toolName,
163
+ ...(toolset ? { toolset } : {}),
161
164
  status: 'started',
162
165
  arguments: req.arguments,
163
166
  });
@@ -197,6 +200,7 @@ export class ToolNode {
197
200
  emitProgress({
198
201
  toolCallId: result.toolCallId,
199
202
  toolName: result.toolName,
203
+ ...(toolset ? { toolset } : {}),
200
204
  status: 'completed',
201
205
  arguments: req.arguments,
202
206
  content: result.content,
@@ -207,6 +211,7 @@ export class ToolNode {
207
211
  emitProgress({
208
212
  toolCallId: result.toolCallId,
209
213
  toolName: result.toolName,
214
+ ...(toolset ? { toolset } : {}),
210
215
  status: 'error',
211
216
  arguments: req.arguments,
212
217
  error: result.error ?? 'unknown error',
@@ -243,6 +248,7 @@ export class ToolNode {
243
248
  emitProgress({
244
249
  toolCallId: req.toolCallId,
245
250
  toolName: req.toolName,
251
+ ...(toolset ? { toolset } : {}),
246
252
  status: 'error',
247
253
  arguments: req.arguments,
248
254
  error: message,
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export * from './common/index.js';
16
16
  export * from './registry/index.js';
17
17
  export * from './providers/index.js';
18
18
  export type { ITraceCollector, TraceEvent, TraceSession, ExecutionFlowEvent, ExecutionNodeEvent, TraceLinkEvent, TraceLinkNodeEvent, } from './contracts/trace-collector.js';
19
- export type { ExecutionContext, CallerType, GovernancePermissionCheckParams, GovernancePermissionCheckResult, PermissionDenyReason, } from '@gt-fe/eap-contracts';
19
+ export type { ExecutionContext, CallerType, GovernancePermissionCheckParams, GovernancePermissionCheckResult, PermissionDenyReason, } from '@eap/contracts';
20
20
  export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, assertToolPermission, assertSkillPermission, permissionMessage, PermissionDeniedError, GovernanceUnavailableError, } from './governance/index.js';
21
21
  export type { IGovernanceClient } from './governance/index.js';
22
22
  export { FileLlmTurnLog, InMemoryLlmTurnLog, createFileLlmTurnLogFromEnv, maskApiKey, resolveLlmTurnDir, } from './debug/llm-turn-log.js';
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 提供多种压缩策略用于处理长对话历史。
5
5
  */
6
- export { type CompressionConfig, type CompressionResult, type CompressionRecord, type CompressionStrategy, type CompressionReason, type IMessageCompressionService, DEFAULT_COMPRESSION_CONFIG, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
6
+ export { type CompressionConfig, type CompressionResult, type CompressionRecord, type CompressionStrategy, type CompressionReason, type IMessageCompressionService, DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, COMPRESSION_MAX_TOKENS_FALLBACK, resolveCompressionMaxTokens, mergeCompressionConfig, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
7
7
  export { SlidingWindowCompression, TruncateCompression, type SummarizeFunction, } from './sliding-window.compression.js';
8
8
  export { createLLMSummarize, createSimpleSummarize, type LLMCallFunction, } from './llm-summarize.js';
9
9
  export { PostgresCompressionRepository, InMemoryCompressionRepository, type ICompressionRepository, } from './compression.repository.js';
@@ -4,7 +4,7 @@
4
4
  * 提供多种压缩策略用于处理长对话历史。
5
5
  */
6
6
  // 压缩接口和工具函数
7
- export { DEFAULT_COMPRESSION_CONFIG, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
7
+ export { DEFAULT_COMPRESSION_CONFIG, COMPRESSION_WINDOW_FACTOR, COMPRESSION_MAX_TOKENS_FALLBACK, resolveCompressionMaxTokens, mergeCompressionConfig, estimateTokenCount, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
8
8
  // 滑动窗口压缩策略
9
9
  export { SlidingWindowCompression, TruncateCompression, } from './sliding-window.compression.js';
10
10
  // LLM 摘要函数
@@ -9,7 +9,7 @@
9
9
  * @see message-compression.service.ts 压缩接口定义
10
10
  * @see requirements.md Requirement 4.3
11
11
  */
12
- import { DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, } from '../message-compression.service.js';
12
+ import { DEFAULT_COMPRESSION_CONFIG, estimateMessagesTokens, countRounds, mergeCompressionConfig, } from '../message-compression.service.js';
13
13
  /**
14
14
  * 默认摘要函数(占位)
15
15
  *
@@ -30,14 +30,14 @@ export class SlidingWindowCompression {
30
30
  config;
31
31
  summarize;
32
32
  constructor(config, summarize) {
33
- this.config = { ...DEFAULT_COMPRESSION_CONFIG, ...config };
33
+ this.config = mergeCompressionConfig(DEFAULT_COMPRESSION_CONFIG, config);
34
34
  this.summarize = summarize ?? defaultSummarize;
35
35
  }
36
36
  /**
37
37
  * 检查是否需要压缩
38
38
  */
39
39
  checkCompression(messages, config) {
40
- const mergedConfig = { ...this.config, ...config };
40
+ const mergedConfig = mergeCompressionConfig(this.config, config);
41
41
  const currentTokens = this.estimateTokens(messages);
42
42
  const currentRounds = countRounds(messages);
43
43
  // 检查 Token 数是否超限
@@ -73,7 +73,7 @@ export class SlidingWindowCompression {
73
73
  * 3. 用摘要 + 最近 N 轮替换原始消息
74
74
  */
75
75
  async compress(messages, config) {
76
- const mergedConfig = { ...this.config, ...config };
76
+ const mergedConfig = mergeCompressionConfig(this.config, config);
77
77
  // 1. 找到最近 N 轮的起始位置
78
78
  const splitIndex = this.findSplitIndex(messages, mergedConfig.preserveRecentRounds);
79
79
  // 如果消息太少,不需要压缩
@@ -134,10 +134,10 @@ export class SlidingWindowCompression {
134
134
  export class TruncateCompression {
135
135
  config;
136
136
  constructor(config) {
137
- this.config = { ...DEFAULT_COMPRESSION_CONFIG, ...config };
137
+ this.config = mergeCompressionConfig(DEFAULT_COMPRESSION_CONFIG, config);
138
138
  }
139
139
  checkCompression(messages, config) {
140
- const mergedConfig = { ...this.config, ...config };
140
+ const mergedConfig = mergeCompressionConfig(this.config, config);
141
141
  const currentTokens = this.estimateTokens(messages);
142
142
  const currentRounds = countRounds(messages);
143
143
  if (currentTokens > mergedConfig.maxTokens || currentRounds > mergedConfig.maxRounds) {
@@ -155,7 +155,7 @@ export class TruncateCompression {
155
155
  };
156
156
  }
157
157
  async compress(messages, config) {
158
- const mergedConfig = { ...this.config, ...config };
158
+ const mergedConfig = mergeCompressionConfig(this.config, config);
159
159
  const preserveCount = mergedConfig.preserveRecentRounds * 2; // 每轮 2 条消息
160
160
  if (messages.length <= preserveCount) {
161
161
  return messages;