@gt-fe/eap-runtime 0.0.13 → 0.0.15

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 (86) 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 +3 -0
  6. package/dist/common/index.d.ts +1 -0
  7. package/dist/common/index.js +1 -0
  8. package/dist/common/logger.d.ts +16 -4
  9. package/dist/common/logger.js +82 -16
  10. package/dist/common/runtime-package-version.d.ts +2 -0
  11. package/dist/common/runtime-package-version.js +5 -0
  12. package/dist/debug/llm-turn-log.d.ts +12 -0
  13. package/dist/debug/llm-turn-log.js +1 -0
  14. package/dist/graph/adapters/langgraph.adapter.d.ts +3 -1
  15. package/dist/graph/adapters/langgraph.adapter.js +66 -13
  16. package/dist/graph/adapters/subgraph-compiler.js +5 -2
  17. package/dist/graph/contracts/executable-graph.d.ts +13 -2
  18. package/dist/graph/nodes/llm-node.d.ts +3 -0
  19. package/dist/graph/nodes/llm-node.js +8 -0
  20. package/dist/graph/nodes/skill-bind-node.d.ts +3 -4
  21. package/dist/graph/nodes/skill-bind-node.js +1 -20
  22. package/dist/graph/nodes/tool-node.d.ts +2 -0
  23. package/dist/graph/nodes/tool-node.js +20 -14
  24. package/dist/memory/compression/index.d.ts +1 -1
  25. package/dist/memory/compression/index.js +1 -1
  26. package/dist/memory/compression/sliding-window.compression.js +7 -7
  27. package/dist/memory/index.d.ts +1 -1
  28. package/dist/memory/index.js +1 -1
  29. package/dist/memory/message-compression.service.d.ts +12 -1
  30. package/dist/memory/message-compression.service.js +29 -1
  31. package/dist/providers/http-llm-provider.d.ts +1 -0
  32. package/dist/providers/http-llm-provider.js +4 -1
  33. package/dist/providers/index.d.ts +1 -1
  34. package/dist/providers/index.js +1 -1
  35. package/dist/providers/local-llm-provider.d.ts +3 -0
  36. package/dist/providers/local-llm-provider.js +14 -2
  37. package/dist/providers/routing-llm-provider.js +12 -3
  38. package/dist/registry/deployment-snapshot.js +18 -16
  39. package/dist/registry/graph-loader.d.ts +10 -0
  40. package/dist/registry/graph-loader.js +115 -66
  41. package/dist/runtime/agent-runtime.d.ts +3 -3
  42. package/dist/runtime/agent-runtime.js +13 -3
  43. package/dist/runtime/dev-runtime.d.ts +2 -0
  44. package/dist/runtime/dev-runtime.js +2 -0
  45. package/dist/runtime/types.d.ts +2 -2
  46. package/dist/session/compression-middleware.d.ts +2 -1
  47. package/dist/session/compression-middleware.js +7 -4
  48. package/dist/sse/events/stats-events.d.ts +9 -0
  49. package/dist/token/compression-budget-integration.d.ts +1 -1
  50. package/dist/token/compression-budget-integration.js +18 -10
  51. package/dist/token/context-window.d.ts +42 -0
  52. package/dist/token/context-window.js +73 -0
  53. package/dist/token/index.d.ts +1 -0
  54. package/dist/token/index.js +1 -0
  55. package/dist/tool/dev-http-executor.d.ts +1 -0
  56. package/dist/tool/dev-http-executor.js +12 -0
  57. package/dist/tool/dev-mcp-executor.d.ts +11 -1
  58. package/dist/tool/dev-mcp-executor.js +26 -0
  59. package/dist/tool/dev-tool-executor.d.ts +2 -1
  60. package/dist/tool/dev-tool-executor.js +6 -0
  61. package/dist/tool/harness-command.js +1 -1
  62. package/dist/tool/harness-primitives.d.ts +7 -0
  63. package/dist/tool/harness-primitives.js +56 -5
  64. package/dist/tool/index.d.ts +2 -1
  65. package/dist/tool/index.js +1 -0
  66. package/dist/tool/local-mcp-client.d.ts +4 -1
  67. package/dist/tool/local-mcp-client.js +130 -3
  68. package/dist/tool/local-tool-executor.d.ts +2 -1
  69. package/dist/tool/local-tool-executor.js +15 -0
  70. package/dist/tool/mcp-list.d.ts +53 -0
  71. package/dist/tool/mcp-list.js +199 -0
  72. package/dist/tool/mcp-tool-executor.d.ts +6 -1
  73. package/dist/tool/mcp-tool-executor.js +52 -0
  74. package/dist/tool/skill-dir-infer.d.ts +17 -0
  75. package/dist/tool/skill-dir-infer.js +81 -0
  76. package/dist/tool/skill-script-refs.d.ts +1 -8
  77. package/dist/tool/skill-script-refs.js +32 -20
  78. package/dist/tool/system-prompt-builder.d.ts +35 -16
  79. package/dist/tool/system-prompt-builder.js +30 -58
  80. package/dist/tool/timeout.js +4 -1
  81. package/dist/tool/tool-execution-router.d.ts +2 -1
  82. package/dist/tool/tool-execution-router.js +7 -0
  83. package/dist/tool/tool-executor.interface.d.ts +11 -0
  84. package/package.json +6 -33
  85. package/dist/registry/mcp-stdio-cwd.d.ts +0 -5
  86. package/dist/registry/mcp-stdio-cwd.js +0 -13
package/README.md CHANGED
@@ -8,6 +8,7 @@ Enterprise Agent Platform **执行引擎**(LangGraph.js):Agent Graph 编
8
8
 
9
9
  | 日期 | 摘要 |
10
10
  |------|------|
11
+ | 2026-08-29 | 待办收口:[`docs/TODO.md`](./docs/TODO.md)。压缩主路径未接;窗口展示不在 Runtime;Gateway 须 `tools/list` |
11
12
  | 2026-08-24 | 0.0.10:官方 Skill / Harness、`ForkParams`、对话详情、存储两档。依赖 `@eap/contracts@0.0.9`。详见 [CHANGELOG.md](./CHANGELOG.md) |
12
13
  | 2026-08-21 | 0.0.9:Gateway `/mcp` JSON-RPC 补 `Accept`;`tools/call` 透传工具版本。详见 [CHANGELOG.md](./CHANGELOG.md) |
13
14
  | 2026-08-19 | 0.0.8:Skill 子图 SSE 闭环;`ExecuteParams.traceId`;`tool.called` 改为 Tool 节点开始执行。依赖 `@eap/contracts@0.0.8`。详见 [CHANGELOG.md](./CHANGELOG.md) |
@@ -28,6 +29,7 @@ Enterprise Agent Platform **执行引擎**(LangGraph.js):Agent Graph 编
28
29
  | 环境变量总表 | [`docs/governance/governance-env-config.md`](../docs/governance/governance-env-config.md) |
29
30
  | 预装联调契约 | [`docs/contracts/preload-hosting-runtime-contract.md`](../docs/contracts/preload-hosting-runtime-contract.md) |
30
31
  | 预装加载与请求流转 | [`docs/contracts/runtime-preinstall-load-flow.md`](../docs/contracts/runtime-preinstall-load-flow.md) |
32
+ | Runtime 待办 | [`docs/TODO.md`](./docs/TODO.md) | 压缩主路径、窗口展示、Gateway `tools/list` |
31
33
  | 技能 FS 待办 | [`docs/TODO-skill-fs.md`](./docs/TODO-skill-fs.md) |
32
34
  | 本地 Agent 闭环 | [`docs/workflows/local-agent-dev-loop.md`](../docs/workflows/local-agent-dev-loop.md) |
33
35
 
@@ -131,7 +133,7 @@ dev-tools (eap chat / eap dev)
131
133
 
132
134
  - **引擎**:`LangGraphAdapter`(`IStateGraphEngine`)将平台 Graph DSL 编成 LangGraph StateGraph
133
135
  - **节点**:`llm`、`tool`、`subgraph`(Skill)、并行/条件边、HITL 等待
134
- - **Skill 子图**:节点可带内嵌 `subgraphDsl`;或仅 `skillId`,从 state.`skillDefinitions` 取 `workflow` 执行;技能 `prompt` 经 `activeSkillPrompt` 注入子图入口 LLM
136
+ - **Skill 子图**:节点可带内嵌 `subgraphDsl`;或仅 `skillId`,从 state.`skillDefinitions` 取 `workflow` 执行。技能正文用短目录 + `read_file`,不灌 system
135
137
  - **权限**:子图执行前 `skill:{skillId}:use`(Governance)
136
138
  - **流式**:`stream()` 产出 RuntimeEvent,再映射为 SSE(`response.chunk`、`tool.*`、`task.*` 等)
137
139
 
@@ -22,7 +22,7 @@ import type { TokenStatsService } from '../../token/token-stats.service.js';
22
22
  import type { TokenBudgetGuard } from '../../token/token-budget-guard.js';
23
23
  import type { SessionBudgetChecker } from '../../token/session-budget-checker.js';
24
24
  import type { InvokeRequest, InvokeResponse } from '../routes/agent.routes.js';
25
- import type { GraphLoader } from '../../registry/graph-loader.js';
25
+ import { type GraphLoader } from '../../registry/graph-loader.js';
26
26
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
27
27
  import type { ITraceCollector } from '../../contracts/trace-collector.js';
28
28
  /**
@@ -20,6 +20,7 @@ import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.
20
20
  import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
21
21
  import { resolveExecutionContext } from '../../governance/governance-client.js';
22
22
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
23
+ import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
23
24
  import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
24
25
  import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
25
26
  import { assembleLoadedTools } from '../../tool/harness-primitives.js';
@@ -181,6 +182,7 @@ export class InvokeOrchestrator {
181
182
  const agentDir = this.graphLoader
182
183
  ? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
183
184
  : undefined;
185
+ const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
184
186
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
185
187
  const history = messageRecordsToGraphMessages(sessionContext.messages);
186
188
  const messages = await buildTurnGraphMessages({
@@ -208,6 +210,7 @@ export class InvokeOrchestrator {
208
210
  skillInput,
209
211
  promptRoots,
210
212
  agentDir,
213
+ agentSystemPrompt,
211
214
  traceId: resolveTraceId(req),
212
215
  traceCollector: this.traceCollector,
213
216
  });
@@ -28,7 +28,7 @@ import type { TokenStatsService } from '../../token/token-stats.service.js';
28
28
  import type { TokenBudgetGuard } from '../../token/token-budget-guard.js';
29
29
  import type { SessionBudgetChecker } from '../../token/session-budget-checker.js';
30
30
  import type { StreamRequest } from '../routes/agent.routes.js';
31
- import type { GraphLoader } from '../../registry/graph-loader.js';
31
+ import { type GraphLoader } from '../../registry/graph-loader.js';
32
32
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
33
33
  import type { ITraceCollector } from '../../contracts/trace-collector.js';
34
34
  /**
@@ -26,6 +26,7 @@ import { SSEEmitter } from '../../sse/sse-emitter.js';
26
26
  import { resolveExecutionContext } from '../../governance/governance-client.js';
27
27
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
28
28
  import { ErrorCodes } from '@gt-fe/eap-contracts';
29
+ import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
29
30
  import { classifyRuntimeError } from '../../utils/http-error.js';
30
31
  import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
31
32
  import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
@@ -203,6 +204,7 @@ export class StreamOrchestrator {
203
204
  const agentDir = this.graphLoader
204
205
  ? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
205
206
  : undefined;
207
+ const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
206
208
  // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
207
209
  const history = messageRecordsToGraphMessages(sessionContext.messages);
208
210
  const messages = await buildTurnGraphMessages({
@@ -236,6 +238,7 @@ export class StreamOrchestrator {
236
238
  skillInput,
237
239
  promptRoots,
238
240
  agentDir,
241
+ agentSystemPrompt,
239
242
  traceId: resolveTraceId(req),
240
243
  traceCollector: this.traceCollector,
241
244
  });
@@ -3,3 +3,4 @@ export * from './logger.js';
3
3
  export * from './health.js';
4
4
  export * from './agent-code.js';
5
5
  export * from './trace-id.js';
6
+ export { RUNTIME_PACKAGE_VERSION } from './runtime-package-version.js';
@@ -3,3 +3,4 @@ export * from './logger.js';
3
3
  export * from './health.js';
4
4
  export * from './agent-code.js';
5
5
  export * from './trace-id.js';
6
+ export { RUNTIME_PACKAGE_VERSION } from './runtime-package-version.js';
@@ -1,5 +1,9 @@
1
1
  /**
2
- * 结构化日志接口 — A2
2
+ * 结构化 stdout JSON 日志。
3
+ *
4
+ * EAP_LOG_LEVEL=debug|info|warn|error,默认 info。
5
+ * info 不写 prompt / messages / systemPrompt;正文在 llm_turns。
6
+ * 关联字段:correlationId、taskId、sessionId、agentCode。
3
7
  */
4
8
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
5
9
  export interface LogEntry {
@@ -7,8 +11,13 @@ export interface LogEntry {
7
11
  timestamp: string;
8
12
  module: string;
9
13
  message: string;
14
+ correlationId?: string;
15
+ taskId?: string;
16
+ sessionId?: string;
17
+ agentCode?: string;
18
+ userId?: string;
19
+ event?: string;
10
20
  context?: Record<string, unknown>;
11
- error?: Error;
12
21
  }
13
22
  export interface ILogger {
14
23
  debug(message: string, context?: Record<string, unknown>): void;
@@ -16,6 +25,10 @@ export interface ILogger {
16
25
  warn(message: string, context?: Record<string, unknown>): void;
17
26
  error(message: string, context?: Record<string, unknown>): void;
18
27
  }
28
+ export declare function resolveLogLevel(env?: NodeJS.ProcessEnv): LogLevel;
29
+ export declare function shouldLog(level: LogLevel, min?: LogLevel): boolean;
30
+ export declare function formatLogEntry(level: LogLevel, module: string, message: string, context?: Record<string, unknown>): LogEntry;
31
+ export declare function emitRuntimeLog(level: LogLevel, module: string, message: string, context?: Record<string, unknown>): void;
19
32
  export declare class ConsoleLogger implements ILogger {
20
33
  private readonly module;
21
34
  constructor(module: string);
@@ -23,9 +36,8 @@ export declare class ConsoleLogger implements ILogger {
23
36
  info(message: string, context?: Record<string, unknown>): void;
24
37
  warn(message: string, context?: Record<string, unknown>): void;
25
38
  error(message: string, context?: Record<string, unknown>): void;
26
- private log;
27
39
  }
28
- /** ponytail: 空实现,测试用 */
40
+ /** 测试用空实现 */
29
41
  export declare class NoopLogger implements ILogger {
30
42
  debug(): void;
31
43
  info(): void;
@@ -1,35 +1,101 @@
1
1
  /**
2
- * 结构化日志接口 — A2
2
+ * 结构化 stdout JSON 日志。
3
+ *
4
+ * EAP_LOG_LEVEL=debug|info|warn|error,默认 info。
5
+ * info 不写 prompt / messages / systemPrompt;正文在 llm_turns。
6
+ * 关联字段:correlationId、taskId、sessionId、agentCode。
3
7
  */
8
+ const LEVEL_RANK = {
9
+ debug: 10,
10
+ info: 20,
11
+ warn: 30,
12
+ error: 40,
13
+ };
14
+ const PROMPT_KEYS = new Set([
15
+ 'systemPrompt',
16
+ 'systemSections',
17
+ 'messages',
18
+ 'prompt',
19
+ 'developerPrompt',
20
+ 'agentPrompt',
21
+ 'nodePrompt',
22
+ 'agentSystemPrompt',
23
+ 'apiKey',
24
+ 'content',
25
+ 'body',
26
+ ]);
27
+ const ID_KEYS = ['correlationId', 'taskId', 'sessionId', 'agentCode', 'userId'];
28
+ export function resolveLogLevel(env = process.env) {
29
+ const raw = env.EAP_LOG_LEVEL?.trim().toLowerCase();
30
+ if (raw === 'debug' || raw === 'info' || raw === 'warn' || raw === 'error')
31
+ return raw;
32
+ return 'info';
33
+ }
34
+ export function shouldLog(level, min = resolveLogLevel()) {
35
+ return LEVEL_RANK[level] >= LEVEL_RANK[min];
36
+ }
37
+ function asId(value) {
38
+ return typeof value === 'string' && value.trim() ? value : undefined;
39
+ }
40
+ function stripPromptFields(context, level) {
41
+ if (!context)
42
+ return undefined;
43
+ const out = {};
44
+ for (const [key, value] of Object.entries(context)) {
45
+ if (key === 'event' || ID_KEYS.includes(key))
46
+ continue;
47
+ if (level !== 'debug' && PROMPT_KEYS.has(key))
48
+ continue;
49
+ out[key] = value;
50
+ }
51
+ return Object.keys(out).length > 0 ? out : undefined;
52
+ }
53
+ export function formatLogEntry(level, module, message, context) {
54
+ const safe = stripPromptFields(context, level);
55
+ const event = asId(context?.event) ?? asId(safe?.event);
56
+ const entry = {
57
+ level,
58
+ timestamp: new Date().toISOString(),
59
+ module,
60
+ message,
61
+ correlationId: asId(context?.correlationId),
62
+ taskId: asId(context?.taskId),
63
+ sessionId: asId(context?.sessionId),
64
+ agentCode: asId(context?.agentCode),
65
+ userId: asId(context?.userId),
66
+ ...(event ? { event } : {}),
67
+ ...(safe ? { context: safe } : {}),
68
+ };
69
+ return entry;
70
+ }
71
+ export function emitRuntimeLog(level, module, message, context) {
72
+ if (!shouldLog(level))
73
+ return;
74
+ const line = JSON.stringify(formatLogEntry(level, module, message, context));
75
+ if (level === 'error')
76
+ console.error(line);
77
+ else
78
+ console.log(line);
79
+ }
4
80
  export class ConsoleLogger {
5
81
  module;
6
82
  constructor(module) {
7
83
  this.module = module;
8
84
  }
9
85
  debug(message, context) {
10
- this.log('debug', message, context);
86
+ emitRuntimeLog('debug', this.module, message, context);
11
87
  }
12
88
  info(message, context) {
13
- this.log('info', message, context);
89
+ emitRuntimeLog('info', this.module, message, context);
14
90
  }
15
91
  warn(message, context) {
16
- this.log('warn', message, context);
92
+ emitRuntimeLog('warn', this.module, message, context);
17
93
  }
18
94
  error(message, context) {
19
- this.log('error', message, context);
20
- }
21
- log(level, message, context) {
22
- const entry = {
23
- level,
24
- timestamp: new Date().toISOString(),
25
- module: this.module,
26
- message,
27
- ...(context && { context }),
28
- };
29
- console[level](JSON.stringify(entry));
95
+ emitRuntimeLog('error', this.module, message, context);
30
96
  }
31
97
  }
32
- /** ponytail: 空实现,测试用 */
98
+ /** 测试用空实现 */
33
99
  export class NoopLogger {
34
100
  debug() { }
35
101
  info() { }
@@ -0,0 +1,2 @@
1
+ /** 当前加载的 Runtime 包版本(`package.json`;overlay 为 `@gt-fe/eap-runtime` 的 version) */
2
+ export declare const RUNTIME_PACKAGE_VERSION: string;
@@ -0,0 +1,5 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ const pkg = require('../../package.json');
4
+ /** 当前加载的 Runtime 包版本(`package.json`;overlay 为 `@gt-fe/eap-runtime` 的 version) */
5
+ export const RUNTIME_PACKAGE_VERSION = typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : 'unknown';
@@ -41,6 +41,16 @@ export interface LlmTurnRecord extends LlmTurnContext {
41
41
  error?: string;
42
42
  };
43
43
  latencyMs: number;
44
+ /** 本轮 prompt inputTokens / 模型上下文窗;parts 为字符比例拆分;缺 window 则省略 window */
45
+ context?: {
46
+ used: number;
47
+ window?: number;
48
+ parts?: {
49
+ system: number;
50
+ messages: number;
51
+ tools: number;
52
+ };
53
+ };
44
54
  }
45
55
  export interface LlmTurnIndexEntry {
46
56
  id: string;
@@ -53,6 +63,8 @@ export interface LlmTurnIndexEntry {
53
63
  file: string;
54
64
  inputTokens: number;
55
65
  outputTokens: number;
66
+ used?: number;
67
+ window?: number;
56
68
  }
57
69
  export type LlmTurnLogKind = 'memory' | 'file' | 'postgres';
58
70
  export interface ILlmTurnLog {
@@ -21,6 +21,7 @@ export function toLlmTurnIndexEntry(record, file) {
21
21
  file: file ?? `${sanitize(record.id)}.json`,
22
22
  inputTokens: record.response.inputTokens,
23
23
  outputTokens: record.response.outputTokens,
24
+ ...(record.context ?? {}),
24
25
  };
25
26
  }
26
27
  export function maskApiKey(key) {
@@ -15,6 +15,8 @@ import { type ILLMProvider } from '../nodes/llm-node.js';
15
15
  import type { IToolExecutor } from '../../tool/tool-executor.interface.js';
16
16
  import type { IGovernanceClient } from '../../governance/governance-client.js';
17
17
  import { type ConditionEvaluatorFn } from '../condition-evaluator.js';
18
+ /** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
19
+ export declare const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
18
20
  /**
19
21
  * LangGraphAdapter — 基于 @langchain/langgraph 的 IStateGraphEngine 实现
20
22
  *
@@ -118,7 +120,7 @@ export declare class LangGraphAdapter implements IStateGraphEngine {
118
120
  private createLLMNodeFunction;
119
121
  /**
120
122
  * 子图节点:优先 config.subgraphDsl;否则按 skillId 从 state.skillDefinitions.workflow 解析。
121
- * 技能 prompt 由 SubGraphCompiler 写入 activeSkillPrompt,供入口 LLM 拼接。
123
+ * 技能正文用短目录 + read_file,不灌 system。
122
124
  */
123
125
  private createSubGraphNodeFunction;
124
126
  /**
@@ -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
29
  import { AppError, ErrorCodes } from '@gt-fe/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
  : {}),
@@ -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?: {
@@ -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
  /**