@gt-fe/eap-runtime 0.0.23 → 0.0.25

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 (39) hide show
  1. package/README.md +1 -0
  2. package/dist/api/handlers/invoke.handler.d.ts +4 -0
  3. package/dist/api/handlers/invoke.handler.js +69 -37
  4. package/dist/api/handlers/stream.handler.d.ts +4 -0
  5. package/dist/api/handlers/stream.handler.js +40 -21
  6. package/dist/api/routes/agent.routes.js +6 -5
  7. package/dist/checkpoint/checkpoint-ref-notify.js +1 -1
  8. package/dist/common/types.d.ts +6 -2
  9. package/dist/common/types.js +3 -1
  10. package/dist/graph/adapters/langgraph.adapter.d.ts +6 -4
  11. package/dist/graph/adapters/langgraph.adapter.js +103 -18
  12. package/dist/graph/contracts/executable-graph.d.ts +11 -2
  13. package/dist/graph/execution-trace-context.d.ts +4 -0
  14. package/dist/graph/execution-trace-context.js +3 -1
  15. package/dist/graph/nodes/rag/types.d.ts +4 -1
  16. package/dist/providers/http-llm-provider.js +25 -0
  17. package/dist/registry/graph-loader.js +5 -4
  18. package/dist/runtime/agent-runtime.d.ts +4 -1
  19. package/dist/runtime/agent-runtime.js +110 -20
  20. package/dist/runtime/dev-runtime.d.ts +1 -0
  21. package/dist/runtime/dev-runtime.js +9 -2
  22. package/dist/runtime/types.d.ts +1 -1
  23. package/dist/session/apply-session-compression.d.ts +6 -4
  24. package/dist/session/apply-session-compression.js +24 -11
  25. package/dist/session/index.d.ts +1 -1
  26. package/dist/session/index.js +1 -1
  27. package/dist/session/persist-turn-messages.d.ts +50 -9
  28. package/dist/session/persist-turn-messages.js +133 -15
  29. package/dist/sse/events/response-events.d.ts +2 -2
  30. package/dist/sse/events/response-events.js +1 -1
  31. package/dist/task/contracts/task-manager.contract.d.ts +2 -0
  32. package/dist/task/index.d.ts +1 -0
  33. package/dist/task/index.js +1 -0
  34. package/dist/task/resolve-task-timeout.d.ts +6 -0
  35. package/dist/task/resolve-task-timeout.js +16 -0
  36. package/dist/task/task-timeout.d.ts +1 -1
  37. package/dist/task/task-timeout.js +3 -2
  38. package/dist/task/task.service.js +1 -0
  39. package/package.json +2 -2
package/README.md CHANGED
@@ -8,6 +8,7 @@ Enterprise Agent Platform **执行引擎**(LangGraph.js):Agent Graph 编
8
8
 
9
9
  | 日期 | 摘要 |
10
10
  |------|------|
11
+ | 2026-09-14 | 会话滑动窗口压缩默认开;`EAP_RUNTIME_COMPRESSION=0` 关闭 |
11
12
  | 2026-09-08 | [`docs/TODO.md`](./docs/TODO.md) 改为主路径状态(已落地 / 不在本包),不是排期 |
12
13
  | 2026-08-29 | 待办收口:[`docs/TODO.md`](./docs/TODO.md)。此后压缩已挂主路径(默认关);窗口展示不在 Runtime;Gateway 须 `tools/list` |
13
14
  | 2026-08-24 | 0.0.10:官方 Skill / Harness、`ForkParams`、对话详情、存储两档。依赖 `@eap/contracts@0.0.9`。详见 [CHANGELOG.md](./CHANGELOG.md) |
@@ -22,6 +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 { TaskTimeoutManager } from '../../task/task-timeout.js';
25
26
  import { type GraphLoader } from '../../registry/graph-loader.js';
26
27
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
27
28
  import type { ITraceCollector } from '../../contracts/trace-collector.js';
@@ -46,6 +47,8 @@ export interface InvokeOrchestratorDeps {
46
47
  traceCollector?: ITraceCollector;
47
48
  sessionCompression?: SessionCompressionOptions;
48
49
  sessionMemory?: SessionMemoryOptions;
50
+ /** 未注入时不启动整图超时(测试桩) */
51
+ timeoutManager?: TaskTimeoutManager;
49
52
  }
50
53
  /**
51
54
  * InvokeOrchestrator — 同步调用全流程编排
@@ -66,6 +69,7 @@ export declare class InvokeOrchestrator {
66
69
  private readonly traceCollector?;
67
70
  private readonly sessionCompression?;
68
71
  private readonly sessionMemory?;
72
+ private readonly timeoutManager?;
69
73
  constructor(deps: InvokeOrchestratorDeps);
70
74
  /**
71
75
  * 执行同步调用编排
@@ -15,12 +15,17 @@
15
15
  * @see requirements.md Requirement 11 AC1-AC2
16
16
  */
17
17
  import { randomUUID } from 'node:crypto';
18
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, sessionVisibleGraphMessages, } from '../../session/persist-turn-messages.js';
18
+ import { buildHitlWaitingTaskOutput, loadModelFacingHistory, messageRecordsToGraphMessages, persistGraphMessageBaseline, turnGraphMessagesAfterCompression, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, sessionVisibleGraphMessages, } from '../../session/persist-turn-messages.js';
19
19
  import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
20
20
  import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
21
21
  import { recordTaskTokenUsages, tokenUsagesFromLlmChannel, } from '../../token/record-task-token-usage.js';
22
22
  import { resolveExecutionContext } from '../../governance/governance-client.js';
23
23
  import { resolveSkillInputFromInvokeInput } from '../../registry/skill-io-guard.js';
24
+ import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
25
+ import { classifyRuntimeError } from '../../utils/http-error.js';
26
+ import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
27
+ import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
28
+ import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
24
29
  import { resolveAgentSystemPromptFromLoader } from '../../registry/graph-loader.js';
25
30
  import { toLoadedToolMap } from '../../tool/loaded-tool-definition.js';
26
31
  import { toLoadedSkillMap } from '../../tool/system-prompt-builder.js';
@@ -28,7 +33,7 @@ import { assembleLoadedTools } from '../../tool/harness-primitives.js';
28
33
  import { resolveAgentCode } from '../../common/agent-code.js';
29
34
  import { unwrapHitlPaused } from '../../graph/hitl/hitl-handler.js';
30
35
  import { resolveTraceId } from '../../common/trace-id.js';
31
- import { applySessionCompression, withCompressionSummaryModel, } from '../../session/apply-session-compression.js';
36
+ import { runSessionCompression, withCompressionSummaryModel, } from '../../session/apply-session-compression.js';
32
37
  import { applySessionMemory, } from '../../session/apply-session-memory.js';
33
38
  // ─────────────────────────────────────────────
34
39
  // Default Graph DSL fallback
@@ -44,7 +49,7 @@ const DEFAULT_GRAPH_DSL = {
44
49
  { from: '__start__', to: 'llm' },
45
50
  { from: 'llm', to: '__end__' },
46
51
  ],
47
- config: { timeout: 60000 },
52
+ config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
48
53
  };
49
54
  // ─────────────────────────────────────────────
50
55
  // InvokeOrchestrator
@@ -68,6 +73,7 @@ export class InvokeOrchestrator {
68
73
  traceCollector;
69
74
  sessionCompression;
70
75
  sessionMemory;
76
+ timeoutManager;
71
77
  constructor(deps) {
72
78
  this.sessionContextService = deps.sessionContextService;
73
79
  this.taskService = deps.taskService;
@@ -81,6 +87,7 @@ export class InvokeOrchestrator {
81
87
  this.traceCollector = deps.traceCollector;
82
88
  this.sessionCompression = deps.sessionCompression;
83
89
  this.sessionMemory = deps.sessionMemory;
90
+ this.timeoutManager = deps.timeoutManager;
84
91
  }
85
92
  /**
86
93
  * 执行同步调用编排
@@ -143,8 +150,12 @@ export class InvokeOrchestrator {
143
150
  throw error;
144
151
  }
145
152
  }
146
- // ─── Step 3: 创建 Task(Hosting 传入 taskId 时复用)───
147
- const timeout = req.config?.timeout ?? 60000;
153
+ // ─── Step 3: 加载图并创建 Task(Hosting 传入 taskId 时复用)───
154
+ const loadOpts = { agentVersion: req.agentVersion };
155
+ const graphDsl = this.graphLoader
156
+ ? await this.graphLoader.loadGraph(agentCode, loadOpts)
157
+ : DEFAULT_GRAPH_DSL;
158
+ const timeout = resolveTaskTimeoutMs(req.config?.timeout, graphDsl.config?.timeout);
148
159
  const task = await this.taskService.create({
149
160
  id: req.taskId,
150
161
  agentId: agentCode,
@@ -156,6 +167,9 @@ export class InvokeOrchestrator {
156
167
  });
157
168
  // ─── Step 4: 启动 Task (pending → running) ───
158
169
  await this.taskService.updateStatus(task.id, 'running');
170
+ const abortSignal = this.timeoutManager?.startTimer(task.id, timeout, async () => {
171
+ await this.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
172
+ });
159
173
  // ─── Step 4b: 持久化本轮 user(多轮短期记忆)───
160
174
  await persistUserTurnMessage(this.messageService, {
161
175
  sessionId,
@@ -166,12 +180,9 @@ export class InvokeOrchestrator {
166
180
  // ─── Step 5: 构建/执行 Graph ───
167
181
  let graphResult;
168
182
  let finishPoints;
183
+ let persistBaseline = 0;
169
184
  try {
170
185
  // 构建图 — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态
171
- const loadOpts = { agentVersion: req.agentVersion };
172
- const graphDsl = this.graphLoader
173
- ? await this.graphLoader.loadGraph(agentCode, loadOpts)
174
- : DEFAULT_GRAPH_DSL;
175
186
  const graph = await this.graphEngine.buildGraph(graphDsl);
176
187
  finishPoints = graph.finishPoints;
177
188
  const loadedToolsBase = this.graphLoader
@@ -192,32 +203,45 @@ export class InvokeOrchestrator {
192
203
  ? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
193
204
  : undefined;
194
205
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
195
- // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
206
+ // 模型上下文:Checkpoint 已有 messages 则压它;压成功用 __set 写回最新 Checkpoint。Message 库不改。
207
+ const facing = await loadModelFacingHistory({
208
+ sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
209
+ checkpointSaver: this.checkpointSaver,
210
+ threadId: sessionId,
211
+ });
212
+ const compression = await runSessionCompression({
213
+ history: facing.messages,
214
+ sessionId,
215
+ userId: effectiveUserId,
216
+ taskId: task.id,
217
+ options: await withCompressionSummaryModel(this.sessionCompression, () => this.graphLoader
218
+ ? this.graphLoader.resolveDefaultModel(agentCode, req.agentVersion)
219
+ : Promise.resolve(undefined)),
220
+ });
196
221
  const history = await applySessionMemory({
197
- history: await applySessionCompression({
198
- history: messageRecordsToGraphMessages(sessionContext.messages),
199
- sessionId,
200
- userId: effectiveUserId,
201
- taskId: task.id,
202
- options: await withCompressionSummaryModel(this.sessionCompression, () => this.graphLoader
203
- ? this.graphLoader.resolveDefaultModel(agentCode, req.agentVersion)
204
- : Promise.resolve(undefined)),
205
- }),
222
+ history: compression.messages,
206
223
  userId: effectiveUserId,
207
224
  agentCode,
208
225
  query: typeof req.input === 'string' ? req.input : undefined,
209
226
  options: this.sessionMemory,
210
227
  });
211
- const messages = await buildTurnGraphMessages({
228
+ const { messages, replaceCheckpointMessages } = turnGraphMessagesAfterCompression({
212
229
  history,
213
230
  input: req.input,
214
- checkpointSaver: this.checkpointSaver,
215
- threadId: sessionId,
231
+ fromCheckpoint: facing.fromCheckpoint,
232
+ compressed: compression.compressed,
233
+ });
234
+ persistBaseline = persistGraphMessageBaseline({
235
+ fromCheckpoint: facing.fromCheckpoint,
236
+ replaceCheckpointMessages,
237
+ graphInputCount: messages.length,
238
+ modelFacingCount: facing.messages.length,
216
239
  });
217
240
  const skillInput = resolveSkillInputFromInvokeInput(req.input);
218
241
  // 同步执行图
219
- graphResult = await this.graphEngine.invoke(graph, {
242
+ graphResult = await runWithExecutionTrace({ abortSignal }, () => this.graphEngine.invoke(graph, {
220
243
  messages,
244
+ replaceCheckpointMessages,
221
245
  userId: effectiveUserId,
222
246
  delegationToken: ctx.delegationToken,
223
247
  agentCode,
@@ -238,7 +262,9 @@ export class InvokeOrchestrator {
238
262
  agentSystemPrompt,
239
263
  traceId: resolveTraceId(req),
240
264
  traceCollector: this.traceCollector,
241
- });
265
+ abortSignal,
266
+ abortDeadlineMs: Date.now() + timeout,
267
+ }));
242
268
  }
243
269
  catch (error) {
244
270
  const usages = await this.persistInvokeUsages(task.id, sessionId);
@@ -286,7 +312,7 @@ export class InvokeOrchestrator {
286
312
  }
287
313
  // ─── 错误处理:Timeout ───
288
314
  if (this.isTimeoutError(error)) {
289
- await this.taskService.updateStatus(task.id, 'timeout');
315
+ await this.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
290
316
  return this.buildResponse(traceId, {
291
317
  taskId: task.id,
292
318
  sessionId,
@@ -297,7 +323,9 @@ export class InvokeOrchestrator {
297
323
  });
298
324
  }
299
325
  // ─── 错误处理:通用错误 ───
300
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
326
+ const classified = classifyRuntimeError(error);
327
+ const fuse = classified.code === ErrorCodes.GRAPH_MAX_ITERATIONS ? classified : undefined;
328
+ const errorMessage = fuse?.message ?? (error instanceof Error ? error.message : 'Unknown error');
301
329
  await this.taskService.updateStatus(task.id, 'failed', errorMessage);
302
330
  return this.buildErrorResponse(traceId, {
303
331
  taskId: task.id,
@@ -306,7 +334,10 @@ export class InvokeOrchestrator {
306
334
  tokenUsage,
307
335
  duration: Date.now() - startTime,
308
336
  status: 'failed',
309
- });
337
+ }, fuse ?? error);
338
+ }
339
+ finally {
340
+ this.timeoutManager?.clearTimer(task.id);
310
341
  }
311
342
  // ─── Step 6: 记录 Token 消耗 ───
312
343
  //
@@ -352,12 +383,11 @@ export class InvokeOrchestrator {
352
383
  // ─── Step 7: 完成 Task (running → success) ───
353
384
  await this.taskService.updateStatus(task.id, 'success', graphResult.output);
354
385
  // ─── Step 7b: 持久化本轮 transcript(统一顺序:assistant tool_calls → tool → assistant 文本)───
355
- // 按 Session 已有行数切 Graph 增量。多段正文收成一条后 Session 比 Graph 短,切点可能提前;finishPoints 只含一个实际节点 id 时靠最后一条纯文本仍落到 merge。
356
- const initialMessagesCount = sessionContext.messages.length + 1;
386
+ // 切点是入图 messages 基线(压缩 `__set` 后 Graph 短于 Session 全文)。user 已在 4b 写入。
357
387
  const allMessages = Array.isArray(graphResult.finalState?.messages)
358
388
  ? graphResult.finalState.messages
359
389
  : [];
360
- const newMessages = sessionVisibleGraphMessages(allMessages.slice(initialMessagesCount), finishPoints);
390
+ const newMessages = sessionVisibleGraphMessages(allMessages.slice(persistBaseline), finishPoints);
361
391
  const duration = Date.now() - startTime;
362
392
  await persistTurnTranscript(this.messageService, {
363
393
  sessionId,
@@ -387,15 +417,17 @@ export class InvokeOrchestrator {
387
417
  buildResponse(traceId, data) {
388
418
  return { code: 'ok', data, traceId };
389
419
  }
390
- buildErrorResponse(traceId, data) {
391
- return { code: data.status === 'token_exhausted' ? 'runtime.budget.task_exhausted' : 'runtime.graph.execution_error', data, traceId };
420
+ buildErrorResponse(traceId, data, error) {
421
+ if (data.status === 'token_exhausted') {
422
+ return { code: 'runtime.budget.task_exhausted', data, traceId };
423
+ }
424
+ if (error instanceof AppError) {
425
+ return { code: error.code, data, traceId };
426
+ }
427
+ return { code: 'runtime.graph.execution_error', data, traceId };
392
428
  }
393
429
  isTimeoutError(error) {
394
- if (error instanceof Error) {
395
- const msg = error.message.toLowerCase();
396
- return msg.includes('timeout') || msg.includes('timed out');
397
- }
398
- return false;
430
+ return error instanceof Error && /timed out/i.test(error.message);
399
431
  }
400
432
  async persistInvokeUsages(taskId, sessionId) {
401
433
  if (!this.checkpointSaver)
@@ -33,6 +33,7 @@ import type { StreamRequest } from '../routes/agent.routes.js';
33
33
  import { type GraphLoader } from '../../registry/graph-loader.js';
34
34
  import type { ICheckpointSaver } from '../../checkpoint/contracts/checkpoint-saver.contract.js';
35
35
  import type { ITraceCollector } from '../../contracts/trace-collector.js';
36
+ import type { TaskTimeoutManager } from '../../task/task-timeout.js';
36
37
  /**
37
38
  * StreamOrchestrator 构造器依赖
38
39
  */
@@ -52,6 +53,8 @@ export interface StreamOrchestratorDeps {
52
53
  traceCollector?: ITraceCollector;
53
54
  sessionCompression?: SessionCompressionOptions;
54
55
  sessionMemory?: SessionMemoryOptions;
56
+ /** 未注入时不启动整图超时(测试桩) */
57
+ timeoutManager?: TaskTimeoutManager;
55
58
  }
56
59
  /**
57
60
  * StreamOrchestrator — 流式调用全流程编排
@@ -72,6 +75,7 @@ export declare class StreamOrchestrator {
72
75
  private readonly traceCollector?;
73
76
  private readonly sessionCompression?;
74
77
  private readonly sessionMemory?;
78
+ private readonly timeoutManager?;
75
79
  constructor(deps: StreamOrchestratorDeps);
76
80
  /**
77
81
  * 执行流式调用编排
@@ -19,7 +19,7 @@
19
19
  * @see design.md §1.5 /stream 内部执行逻辑
20
20
  * @see requirements.md Requirement 11 AC2
21
21
  */
22
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, appendTerminalToolResult, } from '../../session/persist-turn-messages.js';
22
+ import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, appendTerminalToolResult, } from '../../session/persist-turn-messages.js';
23
23
  import { SessionBudgetExhaustedError } from '../../token/session-budget-checker.js';
24
24
  import { TokenBudgetExhaustedError } from '../../token/token-budget-guard.js';
25
25
  import { recordTaskTokenUsages } from '../../token/record-task-token-usage.js';
@@ -39,6 +39,9 @@ import { resolveAgentCode } from '../../common/agent-code.js';
39
39
  import { unwrapHitlPaused } from './invoke.handler.js';
40
40
  import { buildHitlWaitedPayload } from '../../graph/hitl/hitl-handler.js';
41
41
  import { resolveTraceId } from '../../common/trace-id.js';
42
+ import { DEFAULT_GRAPH_TIMEOUT_MS } from '../../common/types.js';
43
+ import { resolveTaskTimeoutMs } from '../../task/resolve-task-timeout.js';
44
+ import { runWithExecutionTrace } from '../../graph/execution-trace-context.js';
42
45
  // ─────────────────────────────────────────────
43
46
  // Placeholder Graph DSL (Agent Registry 接入前)
44
47
  // ─────────────────────────────────────────────
@@ -53,7 +56,7 @@ const DEFAULT_GRAPH_DSL = {
53
56
  { from: '__start__', to: 'llm' },
54
57
  { from: 'llm', to: '__end__' },
55
58
  ],
56
- config: { timeout: 60000 },
59
+ config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
57
60
  };
58
61
  // ─────────────────────────────────────────────
59
62
  // StreamOrchestrator
@@ -77,6 +80,7 @@ export class StreamOrchestrator {
77
80
  traceCollector;
78
81
  sessionCompression;
79
82
  sessionMemory;
83
+ timeoutManager;
80
84
  constructor(deps) {
81
85
  this.sessionContextService = deps.sessionContextService;
82
86
  this.taskService = deps.taskService;
@@ -90,6 +94,7 @@ export class StreamOrchestrator {
90
94
  this.traceCollector = deps.traceCollector;
91
95
  this.sessionCompression = deps.sessionCompression;
92
96
  this.sessionMemory = deps.sessionMemory;
97
+ this.timeoutManager = deps.timeoutManager;
93
98
  }
94
99
  /**
95
100
  * 执行流式调用编排
@@ -166,8 +171,12 @@ export class StreamOrchestrator {
166
171
  userId,
167
172
  }));
168
173
  }
169
- // ─── Step 3: 创建 Task(Hosting 传入 taskId 时复用)───
170
- const timeout = req.config?.timeout ?? 60000;
174
+ // ─── Step 3: 加载图并创建 Task(Hosting 传入 taskId 时复用)───
175
+ const loadOpts = { agentVersion: req.agentVersion };
176
+ const graphDsl = this.graphLoader
177
+ ? await this.graphLoader.loadGraph(agentCode, loadOpts)
178
+ : DEFAULT_GRAPH_DSL;
179
+ const timeout = resolveTaskTimeoutMs(req.config?.timeout, graphDsl.config?.timeout);
171
180
  const task = await this.taskService.create({
172
181
  id: req.taskId,
173
182
  agentId: agentCode,
@@ -180,6 +189,9 @@ export class StreamOrchestrator {
180
189
  const taskId = task.id;
181
190
  // ─── Step 4: 启动 Task (pending → running) ───
182
191
  await this.taskService.updateStatus(taskId, 'running');
192
+ const abortSignal = this.timeoutManager?.startTimer(taskId, timeout, async () => {
193
+ await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout', 'Task execution timed out');
194
+ });
183
195
  // ─── Step 4b: 持久化本轮 user(多轮短期记忆)───
184
196
  await persistUserTurnMessage(this.messageService, {
185
197
  sessionId,
@@ -193,10 +205,6 @@ export class StreamOrchestrator {
193
205
  sessionId,
194
206
  }));
195
207
  // ─── Step 5: 构建 Graph — 每次请求创建独立 StateGraph 实例,确保请求间无共享可变状态 ───
196
- const loadOpts = { agentVersion: req.agentVersion };
197
- const graphDsl = this.graphLoader
198
- ? await this.graphLoader.loadGraph(agentCode, loadOpts)
199
- : DEFAULT_GRAPH_DSL;
200
208
  const graph = await this.graphEngine.buildGraph(graphDsl);
201
209
  const loadedToolsBase = this.graphLoader
202
210
  ? toLoadedToolMap(await this.graphLoader.loadToolDefinitions(agentCode, loadOpts))
@@ -213,8 +221,14 @@ export class StreamOrchestrator {
213
221
  ? (await this.graphLoader.resolveAgentDir(agentCode, req.agentVersion)) ?? undefined
214
222
  : undefined;
215
223
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.graphLoader, agentCode, req.agentVersion);
216
- // 组装 GraphState:有 Checkpoint 历史则只追加本轮 user,避免双重 append
217
- let history = messageRecordsToGraphMessages(sessionContext.messages);
224
+ // 模型上下文:Checkpoint 已有 messages 则压它;压成功用 __set 写回最新 Checkpoint。Message 库不改。
225
+ const facing = await loadModelFacingHistory({
226
+ sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
227
+ checkpointSaver: this.checkpointSaver,
228
+ threadId: sessionId,
229
+ });
230
+ let history = facing.messages;
231
+ let compressed = false;
218
232
  const compressionOptions = await withCompressionSummaryModel(this.sessionCompression, () => this.graphLoader
219
233
  ? this.graphLoader.resolveDefaultModel(agentCode, req.agentVersion)
220
234
  : Promise.resolve(undefined));
@@ -229,6 +243,9 @@ export class StreamOrchestrator {
229
243
  history = step.messages;
230
244
  continue;
231
245
  }
246
+ if (step.kind === 'completed') {
247
+ compressed = step.payload.success === true;
248
+ }
232
249
  emitter.emit(this.buildSSEEvent(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, taskId, { ...step.payload }));
233
250
  }
234
251
  history = await applySessionMemory({
@@ -239,11 +256,11 @@ export class StreamOrchestrator {
239
256
  options: this.sessionMemory,
240
257
  });
241
258
  const tokenDetail = resolveTokenDetail(req.config?.tokenDetail);
242
- const messages = await buildTurnGraphMessages({
259
+ const { messages, replaceCheckpointMessages } = turnGraphMessagesAfterCompression({
243
260
  history,
244
261
  input: req.input,
245
- checkpointSaver: this.checkpointSaver,
246
- threadId: sessionId,
262
+ fromCheckpoint: facing.fromCheckpoint,
263
+ compressed,
247
264
  });
248
265
  const skillInput = resolveSkillInputFromInvokeInput(req.input);
249
266
  const assistantBuckets = createStreamAssistantBuckets();
@@ -253,8 +270,9 @@ export class StreamOrchestrator {
253
270
  const toolResults = [];
254
271
  // ─── Step 6: Graph.stream() 流式执行 ───
255
272
  try {
256
- const eventStream = this.graphEngine.stream(graph, {
273
+ const eventStream = runWithExecutionTrace({ abortSignal }, () => this.graphEngine.stream(graph, {
257
274
  messages,
275
+ replaceCheckpointMessages,
258
276
  userId,
259
277
  delegationToken: executionContext.delegationToken,
260
278
  agentCode,
@@ -275,7 +293,9 @@ export class StreamOrchestrator {
275
293
  agentSystemPrompt,
276
294
  traceId: resolveTraceId(req),
277
295
  traceCollector: this.traceCollector,
278
- });
296
+ abortSignal,
297
+ abortDeadlineMs: Date.now() + timeout,
298
+ }));
279
299
  // 逐事件转发到 SSE;顺带把节点 token 记入 TokenStatsService(stream 原先未 record → 结尾全 0)
280
300
  for await (const runtimeEvent of eventStream) {
281
301
  // 连接已断开则提前退出
@@ -347,7 +367,7 @@ export class StreamOrchestrator {
347
367
  // ─── 错误处理:Timeout ───
348
368
  if (this.isTimeoutError(error)) {
349
369
  await recordTaskTokenUsages(this.taskService, taskId, this.tokenStatsService.list(taskId));
350
- await this.taskService.updateStatus(taskId, 'timeout');
370
+ await this.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
351
371
  emitter.emit(this.buildSSEEvent('task.error', correlationId, taskId, {
352
372
  code: ErrorCodes.TASK_TIMEOUT,
353
373
  message: 'Task execution timed out',
@@ -363,6 +383,9 @@ export class StreamOrchestrator {
363
383
  this.emitErrorAndClose(emitter, correlationId, taskId, error);
364
384
  return;
365
385
  }
386
+ finally {
387
+ this.timeoutManager?.clearTimer(taskId);
388
+ }
366
389
  // ─── Step 7: 发送结束事件序列 ───
367
390
  // Emit: response.final(带完整 assistant 文本,供客户端兜底)
368
391
  const tokenStats = this.tokenStatsService.getTaskStats(taskId);
@@ -454,10 +477,6 @@ export class StreamOrchestrator {
454
477
  emitter.close();
455
478
  }
456
479
  isTimeoutError(error) {
457
- if (error instanceof Error) {
458
- const msg = error.message.toLowerCase();
459
- return msg.includes('timeout') || msg.includes('timed out');
460
- }
461
- return false;
480
+ return error instanceof Error && /timed out/i.test(error.message);
462
481
  }
463
482
  }
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * 生产环境由 Hosting 提供对外 HTTP;本模块供 `EAP_DEV_HTTP=1` 本地调试。
5
5
  */
6
+ import { classifyRuntimeError } from '../../utils/http-error.js';
6
7
  // ─────────────────────────────────────────────
7
8
  // Route Handlers
8
9
  // ─────────────────────────────────────────────
@@ -85,18 +86,18 @@ export function createAgentRouteHandlers(deps) {
85
86
  };
86
87
  }
87
88
  catch (error) {
88
- // 错误处理
89
89
  const duration = Date.now() - startTime;
90
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
90
+ const classified = classifyRuntimeError(error);
91
+ const output = classified.exposeMessage ? classified.message : '系统内部错误';
91
92
  return {
92
- code: 'runtime.graph.execution_error',
93
+ code: classified.code,
93
94
  data: {
94
95
  taskId: '',
95
96
  sessionId: sessionId ?? '',
96
- output: '',
97
+ output,
97
98
  tokenUsage: { input: 0, output: 0, total: 0 },
98
99
  duration,
99
- status: 'failed',
100
+ status: classified.code === 'runtime.task.timeout' ? 'timeout' : 'failed',
100
101
  },
101
102
  traceId: '',
102
103
  };
@@ -12,7 +12,7 @@ function pickString(...values) {
12
12
  return undefined;
13
13
  }
14
14
  function omitTraceCollector(record) {
15
- const { traceCollector: _drop, ...rest } = record;
15
+ const { traceCollector: _drop, abortSignal: _abort, abortDeadlineMs: _deadline, ...rest } = record;
16
16
  return rest;
17
17
  }
18
18
  /** 去掉不可序列化的 collector,再交给 Saver。 */
@@ -12,6 +12,8 @@
12
12
  * - detail: 每次 LLM 调用输出 stats.node_token_usage + 结束时汇总
13
13
  */
14
14
  export type TokenDetailLevel = 'none' | 'summary' | 'detail';
15
+ /** 未写请求/图 timeout 时的整图缺省(毫秒)。20 分钟,须大于单次 LLM HTTP 缺省 180s。 */
16
+ export declare const DEFAULT_GRAPH_TIMEOUT_MS = 1200000;
15
17
  /**
16
18
  * Runtime 执行引擎配置
17
19
  *
@@ -20,9 +22,11 @@ export type TokenDetailLevel = 'none' | 'summary' | 'detail';
20
22
  export interface RuntimeConfig {
21
23
  /** Token 统计输出粒度 */
22
24
  tokenDetail: TokenDetailLevel;
23
- /** Task 执行超时时间(毫秒) */
25
+ /** Task 执行超时时间(毫秒)。未写请求/图 timeout 时为 {@link DEFAULT_GRAPH_TIMEOUT_MS} */
24
26
  timeout: number;
25
- /** ReAct 循环最大迭代次数 */
27
+ /**
28
+ * @deprecated Runtime 忽略。图级 / 请求级步数不是限长手段。整图限长用 timeout。
29
+ */
26
30
  maxIterations: number;
27
31
  /** SSE 心跳间隔(毫秒),默认 30000 */
28
32
  heartbeatInterval: number;
@@ -5,10 +5,12 @@
5
5
  * - @gt-fe/eap-contracts 定义跨模块的公共协议(如 Task 实体、Session 实体、Agent Manifest)
6
6
  * - 本文件定义 Runtime 执行引擎内部流转的配置、上下文、枚举等
7
7
  */
8
+ /** 未写请求/图 timeout 时的整图缺省(毫秒)。20 分钟,须大于单次 LLM HTTP 缺省 180s。 */
9
+ export const DEFAULT_GRAPH_TIMEOUT_MS = 1_200_000;
8
10
  /** RuntimeConfig 默认值 */
9
11
  export const DEFAULT_RUNTIME_CONFIG = {
10
12
  tokenDetail: 'detail',
11
- timeout: 60000,
13
+ timeout: DEFAULT_GRAPH_TIMEOUT_MS,
12
14
  maxIterations: 10,
13
15
  heartbeatInterval: 30000,
14
16
  };
@@ -17,12 +17,14 @@ import type { IToolExecutor } from '../../tool/tool-executor.interface.js';
17
17
  import type { IGovernanceClient } from '../../governance/governance-client.js';
18
18
  import { type ConditionEvaluatorFn } from '../condition-evaluator.js';
19
19
  import type { ExecutionInterceptors } from '../../execution/interceptors.js';
20
- /** 图未写 `config.maxIterations` 时的 recursionLimit。`agent ⇄ tools` 一圈约 2,50 ≈ 24 次工具往返。 */
21
- export declare const DEFAULT_GRAPH_RECURSION_LIMIT = 50;
22
20
  /**
23
- * LangGraph `recursionLimit` 计 superstep(每进一个节点 +1),不是 ReAct 圈数。
24
- * 图上 `config.maxIterations` 优先;缺省 {@link DEFAULT_GRAPH_RECURSION_LIMIT}。
21
+ * 传给 LangGraph 的 `recursionLimit`:一轮调度(superstep)+1。
22
+ * 同一步里并行的多个节点只 +1。ReAct 的 llm 再 tool 大约 +2。
23
+ * 不读图级 / 请求级 `maxIterations`。整图运行时长用 `timeout`。
24
+ * 配了 Token 配额才会因预算停止。
25
25
  */
26
+ export declare const DEFAULT_GRAPH_RECURSION_LIMIT = 5000;
27
+ export declare function isGraphRecursionError(error: unknown): boolean;
26
28
  /** 并行节点各自返回 [id] 时合并进同一路径;末项相同不重复。 */
27
29
  export declare function mergeHistoryPath(current: unknown, update: unknown): string[];
28
30
  /**