@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
@@ -7,12 +7,15 @@
7
7
  * @see 04-runtime-core/design.md §1.5 / §1.7
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
+ import { DEFAULT_GRAPH_TIMEOUT_MS } from '../common/types.js';
11
+ import { resolveTaskTimeoutMs } from '../task/resolve-task-timeout.js';
12
+ import { runWithExecutionTrace } from '../graph/execution-trace-context.js';
10
13
  import { HITL_TIMEOUT_ERROR_CODE, HITL_TIMEOUT_MESSAGE, HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, loadHitlCheckpoint, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
11
14
  import { resolveAgentSystemPromptFromLoader } from '../registry/graph-loader.js';
12
15
  import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
13
16
  import { assembleLoadedTools } from '../tool/harness-primitives.js';
14
17
  import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
15
- import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
18
+ import { buildHitlWaitingTaskOutput, buildTurnTranscriptFromStreamEvents, loadModelFacingHistory, turnGraphMessagesAfterCompression, extractHitlThreadId, extractStreamReasoning, applyStreamTextDelta, createStreamAssistantBuckets, visibleStreamAssistantText, streamAssistantTexts, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, resolveCheckpointThreadId, appendTerminalToolResult, } from '../session/persist-turn-messages.js';
16
19
  import { iterSessionCompression, withCompressionSummaryModel, } from '../session/apply-session-compression.js';
17
20
  import { applySessionMemory } from '../session/apply-session-memory.js';
18
21
  import { resolveTokenDetail, shouldEmitTokenSse } from '../token/token-detail.js';
@@ -37,7 +40,7 @@ const DEFAULT_GRAPH_DSL = {
37
40
  { from: '__start__', to: 'llm' },
38
41
  { from: 'llm', to: '__end__' },
39
42
  ],
40
- config: { timeout: 60000 },
43
+ config: { timeout: DEFAULT_GRAPH_TIMEOUT_MS },
41
44
  };
42
45
  /**
43
46
  * AgentRuntime — 管理 Graph 执行的 SDK 门面
@@ -214,6 +217,14 @@ export class AgentRuntime {
214
217
  ragScope: params.ragScope,
215
218
  ragSources: params.ragSources,
216
219
  }, userId);
220
+ if (response.code !== 'ok') {
221
+ const output = typeof response.data?.output === 'string' && response.data.output
222
+ ? response.data.output
223
+ : response.code;
224
+ throw new AppError(response.code, output, {
225
+ exposeMessage: true,
226
+ });
227
+ }
217
228
  return response.data;
218
229
  }
219
230
  /**
@@ -241,6 +252,11 @@ export class AgentRuntime {
241
252
  userId,
242
253
  });
243
254
  }
255
+ const loadOpts = { agentVersion: params.agentVersion };
256
+ const graphDsl = this.deps.graphLoader
257
+ ? await this.deps.graphLoader.loadGraph(agentCode, loadOpts)
258
+ : DEFAULT_GRAPH_DSL;
259
+ const timeout = resolveTaskTimeoutMs(params.config?.timeout, graphDsl.config?.timeout);
244
260
  const task = await this.deps.taskService.create({
245
261
  id: params.taskId,
246
262
  agentId: agentCode,
@@ -248,9 +264,12 @@ export class AgentRuntime {
248
264
  userId,
249
265
  correlationId,
250
266
  input: params.input,
251
- timeout: params.config?.timeout ?? 60_000,
267
+ timeout,
252
268
  });
253
269
  await this.deps.taskService.updateStatus(task.id, 'running');
270
+ const abortSignal = this.deps.timeoutManager?.startTimer(task.id, timeout, async () => {
271
+ await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
272
+ });
254
273
  await persistUserTurnMessage(this.deps.messageService, {
255
274
  sessionId,
256
275
  taskId: task.id,
@@ -258,10 +277,6 @@ export class AgentRuntime {
258
277
  content: params.input,
259
278
  });
260
279
  yield this.toSSE('task.started', correlationId, task.id, { taskId: task.id, sessionId });
261
- const loadOpts = { agentVersion: params.agentVersion };
262
- const graphDsl = this.deps.graphLoader
263
- ? await this.deps.graphLoader.loadGraph(agentCode, loadOpts)
264
- : DEFAULT_GRAPH_DSL;
265
280
  const graph = await this.deps.graphEngine.buildGraph(graphDsl);
266
281
  const loadedToolsBase = await this.resolveLoadedTools(agentCode, params.agentVersion);
267
282
  const loadedSkills = this.deps.graphLoader
@@ -279,7 +294,13 @@ export class AgentRuntime {
279
294
  ? (await this.deps.graphLoader.resolveAgentDir(agentCode, params.agentVersion)) ?? undefined
280
295
  : undefined;
281
296
  const agentSystemPrompt = await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, params.agentVersion);
282
- let history = messageRecordsToGraphMessages(sessionContext.messages);
297
+ const facing = await loadModelFacingHistory({
298
+ sessionHistory: messageRecordsToGraphMessages(sessionContext.messages),
299
+ checkpointSaver: this.deps.checkpointSaver,
300
+ threadId: sessionId,
301
+ });
302
+ let history = facing.messages;
303
+ let compressed = false;
283
304
  const compressionOptions = await withCompressionSummaryModel({
284
305
  ...this.deps.sessionCompression,
285
306
  turnLog: this.deps.sessionCompression?.turnLog ?? this.deps.turnLog,
@@ -297,6 +318,9 @@ export class AgentRuntime {
297
318
  history = step.messages;
298
319
  continue;
299
320
  }
321
+ if (step.kind === 'completed') {
322
+ compressed = step.payload.success === true;
323
+ }
300
324
  yield this.toSSE(step.kind === 'started' ? 'session.compression.started' : 'session.compression.completed', correlationId, task.id, { ...step.payload });
301
325
  }
302
326
  history = await applySessionMemory({
@@ -307,11 +331,11 @@ export class AgentRuntime {
307
331
  options: this.deps.sessionMemory,
308
332
  });
309
333
  const tokenDetail = resolveTokenDetail(params.config?.tokenDetail);
310
- const messages = await buildTurnGraphMessages({
334
+ const { messages, replaceCheckpointMessages } = turnGraphMessagesAfterCompression({
311
335
  history,
312
336
  input: params.input,
313
- checkpointSaver: this.deps.checkpointSaver,
314
- threadId: sessionId,
337
+ fromCheckpoint: facing.fromCheckpoint,
338
+ compressed,
315
339
  });
316
340
  const startTime = Date.now();
317
341
  const assistantBuckets = createStreamAssistantBuckets();
@@ -321,8 +345,9 @@ export class AgentRuntime {
321
345
  const toolResults = [];
322
346
  const nodeTokenUsages = [];
323
347
  try {
324
- for await (const runtimeEvent of this.deps.graphEngine.stream(graph, {
348
+ for await (const runtimeEvent of runWithExecutionTrace({ abortSignal }, () => this.deps.graphEngine.stream(graph, {
325
349
  messages,
350
+ replaceCheckpointMessages,
326
351
  userId,
327
352
  delegationToken: ctx.delegationToken,
328
353
  agentCode,
@@ -343,7 +368,9 @@ export class AgentRuntime {
343
368
  skillInput: resolveSkillInputFromInvokeInput(params.input),
344
369
  traceId: resolveTraceId(params),
345
370
  traceCollector: this.deps.traceCollector,
346
- })) {
371
+ abortSignal,
372
+ abortDeadlineMs: Date.now() + timeout,
373
+ }))) {
347
374
  if (runtimeEvent.type === 'tool.called') {
348
375
  const p = runtimeEvent.payload;
349
376
  if (p.toolCallId && p.toolName) {
@@ -415,6 +442,17 @@ export class AgentRuntime {
415
442
  yield this.toSSE('hitl.waited', correlationId, task.id, buildHitlWaitedPayload(hitlPaused));
416
443
  return;
417
444
  }
445
+ const timedOut = err instanceof Error && /timed out/i.test(err.message);
446
+ if (timedOut) {
447
+ await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
448
+ await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
449
+ yield this.toSSE('task.error', correlationId, task.id, {
450
+ code: ErrorCodes.TASK_TIMEOUT,
451
+ message: 'Task execution timed out',
452
+ recoverable: false,
453
+ });
454
+ return;
455
+ }
418
456
  const message = err instanceof Error ? err.message : 'Unknown error';
419
457
  await recordTaskTokenUsages(this.deps.taskService, task.id, nodeTokenUsages);
420
458
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
@@ -425,6 +463,9 @@ export class AgentRuntime {
425
463
  });
426
464
  throw err;
427
465
  }
466
+ finally {
467
+ this.deps.timeoutManager?.clearTimer(task.id);
468
+ }
428
469
  }
429
470
  /**
430
471
  * HITL 用户响应后继续执行(同步结果)。
@@ -483,6 +524,9 @@ export class AgentRuntime {
483
524
  if (!claimed) {
484
525
  throw new AppError(ErrorCodes.TASK_HITL_INVALID_STATE, `Task ${taskId} is no longer waiting for HITL`, { exposeMessage: true });
485
526
  }
527
+ const abortSignal = this.deps.timeoutManager?.startTimer(taskId, resolveTaskTimeoutMs(task.timeout), async () => {
528
+ await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout', 'Task execution timed out');
529
+ });
486
530
  const tokenDetail = resolveTokenDetail(response.tokenDetail);
487
531
  yield this.toSSE('hitl.resumed', correlationId, taskId, {
488
532
  choice: response.choice,
@@ -507,6 +551,8 @@ export class AgentRuntime {
507
551
  : await this.resolveLoadedSkills(agentCode, response.userId, agentVersion);
508
552
  const continueState = {
509
553
  ...resumedState,
554
+ abortSignal,
555
+ abortDeadlineMs: Date.now() + resolveTaskTimeoutMs(task.timeout),
510
556
  messages: resumedState.messages ?? [],
511
557
  replaceCheckpointMessages: true,
512
558
  sessionId: resumedState.sessionId ?? sessionId,
@@ -542,7 +588,7 @@ export class AgentRuntime {
542
588
  const toolResults = [];
543
589
  const nodeTokenUsages = [];
544
590
  try {
545
- for await (const runtimeEvent of this.deps.graphEngine.stream(graph, continueState)) {
591
+ for await (const runtimeEvent of runWithExecutionTrace({ abortSignal, abortDeadlineMs: continueState.abortDeadlineMs }, () => this.deps.graphEngine.stream(graph, continueState))) {
546
592
  if (runtimeEvent.type === 'tool.called') {
547
593
  const p = runtimeEvent.payload;
548
594
  if (p.toolCallId && p.toolName) {
@@ -617,6 +663,17 @@ export class AgentRuntime {
617
663
  yield this.toSSE('hitl.waited', correlationId, taskId, buildHitlWaitedPayload(hitlPaused));
618
664
  return;
619
665
  }
666
+ const timedOut = err instanceof Error && /timed out/i.test(err.message);
667
+ if (timedOut) {
668
+ await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
669
+ await this.deps.taskService.tryUpdateStatus(taskId, 'running', 'timeout');
670
+ yield this.toSSE('task.error', correlationId, taskId, {
671
+ code: ErrorCodes.TASK_TIMEOUT,
672
+ message: 'Task execution timed out',
673
+ recoverable: false,
674
+ });
675
+ return;
676
+ }
620
677
  const appErr = err instanceof AppError ? err : AppError.fromUnknown(err);
621
678
  const failMsg = appErr.exposeMessage ? appErr.message : '系统内部错误';
622
679
  await recordTaskTokenUsages(this.deps.taskService, taskId, nodeTokenUsages);
@@ -628,6 +685,9 @@ export class AgentRuntime {
628
685
  });
629
686
  throw err instanceof AppError ? err : appErr;
630
687
  }
688
+ finally {
689
+ this.deps.timeoutManager?.clearTimer(taskId);
690
+ }
631
691
  }
632
692
  /**
633
693
  * 从 Checkpoint Fork 并真实重新执行。
@@ -658,6 +718,12 @@ export class AgentRuntime {
658
718
  const newThreadId = randomUUID();
659
719
  const forkedCheckpoint = await this.deps.checkpointSaver.fork(checkpointId, newThreadId, overrides);
660
720
  const correlationId = overrides.executionContext?.correlationId ?? randomUUID();
721
+ const agentVersion = overrides.agentVersion ??
722
+ (typeof source.state.agentVersion === 'string' ? source.state.agentVersion : undefined);
723
+ const graphDsl = this.deps.graphLoader && typeof this.deps.graphLoader.loadGraph === 'function'
724
+ ? await this.deps.graphLoader.loadGraph(agentCode, { agentVersion })
725
+ : DEFAULT_GRAPH_DSL;
726
+ const timeout = resolveTaskTimeoutMs(undefined, graphDsl.config?.timeout);
661
727
  const task = await this.deps.taskService.create({
662
728
  id: taskId,
663
729
  agentId: agentCode,
@@ -665,12 +731,13 @@ export class AgentRuntime {
665
731
  userId,
666
732
  correlationId,
667
733
  input: { forkFrom: checkpointId, overrides },
668
- timeout: 600_000,
734
+ timeout,
669
735
  });
670
736
  await this.deps.taskService.updateStatus(task.id, 'running');
671
- const agentVersion = overrides.agentVersion ??
672
- (typeof source.state.agentVersion === 'string' ? source.state.agentVersion : undefined);
673
- const graph = await this.rebuildExecutableGraph(agentCode, agentVersion);
737
+ const abortSignal = this.deps.timeoutManager?.startTimer(task.id, timeout, async () => {
738
+ await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout', 'Task execution timed out');
739
+ });
740
+ const graph = await this.deps.graphEngine.buildGraph(graphDsl);
674
741
  const loadedToolsBase = await this.resolveLoadedTools(agentCode, agentVersion);
675
742
  const skillBundle = await this.resolveLoadedSkills(agentCode, userId, agentVersion);
676
743
  const loadedTools = assembleLoadedTools(loadedToolsBase);
@@ -681,11 +748,12 @@ export class AgentRuntime {
681
748
  const agentSystemPrompt = (await resolveAgentSystemPromptFromLoader(this.deps.graphLoader, agentCode, agentVersion)) ?? (typeof forkedCheckpoint.state.agentSystemPrompt === 'string'
682
749
  ? forkedCheckpoint.state.agentSystemPrompt
683
750
  : undefined);
751
+ const abortDeadlineMs = Date.now() + timeout;
684
752
  try {
685
753
  const forkState = { ...forkedCheckpoint.state };
686
754
  delete forkState.toolSpanId;
687
755
  delete forkState.hitlToolCallId;
688
- const result = await this.deps.graphEngine.invoke(graph, {
756
+ const result = await runWithExecutionTrace({ abortSignal, abortDeadlineMs }, () => this.deps.graphEngine.invoke(graph, {
689
757
  messages: Array.isArray(forkState.messages)
690
758
  ? forkState.messages
691
759
  : [],
@@ -701,7 +769,9 @@ export class AgentRuntime {
701
769
  taskId: task.id,
702
770
  traceId,
703
771
  traceCollector: this.deps.traceCollector,
704
- });
772
+ abortSignal,
773
+ abortDeadlineMs,
774
+ }));
705
775
  if (result.tokenUsage.perNode && result.tokenUsage.perNode.length > 0) {
706
776
  await recordTaskTokenUsages(this.deps.taskService, task.id, result.tokenUsage.perNode);
707
777
  }
@@ -760,6 +830,23 @@ export class AgentRuntime {
760
830
  await recordTaskTokenUsages(this.deps.taskService, task.id, usages);
761
831
  const input_tokens = usages.reduce((sum, row) => sum + row.inputTokens, 0);
762
832
  const output_tokens = usages.reduce((sum, row) => sum + row.outputTokens, 0);
833
+ const timedOut = err instanceof Error && /timed out/i.test(err.message);
834
+ if (timedOut) {
835
+ await this.deps.taskService.tryUpdateStatus(task.id, 'running', 'timeout');
836
+ return {
837
+ newTraceId: traceId,
838
+ originalTraceId,
839
+ newTaskId: task.id,
840
+ checkpointId,
841
+ status: 'timeout',
842
+ output: message,
843
+ tokenUsage: {
844
+ input_tokens,
845
+ output_tokens,
846
+ total_tokens: input_tokens + output_tokens,
847
+ },
848
+ };
849
+ }
763
850
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
764
851
  return {
765
852
  newTraceId: traceId,
@@ -775,6 +862,9 @@ export class AgentRuntime {
775
862
  },
776
863
  };
777
864
  }
865
+ finally {
866
+ this.deps.timeoutManager?.clearTimer(task.id);
867
+ }
778
868
  }
779
869
  async prepareHitlResume(taskId, response) {
780
870
  const task = await this.deps.taskService.getTask(taskId);
@@ -33,6 +33,7 @@ import { LongTermMemoryService } from '../memory/long-term-memory.service.js';
33
33
  export interface CreateDevRuntimeOptions {
34
34
  interceptors?: ExecutionInterceptors;
35
35
  compression?: {
36
+ /** 默认开。false 或 EAP_RUNTIME_COMPRESSION=0 关闭 */
36
37
  enabled?: boolean;
37
38
  persistCompressed?: boolean;
38
39
  };
@@ -31,7 +31,7 @@ import { createToolExecutor } from '../tool/create-tool-executor.js';
31
31
  import { DevToolExecutor } from '../tool/dev-tool-executor.js';
32
32
  import { captureOverlayJailOptions, withHarnessPrimitives } from '../tool/harness-primitives.js';
33
33
  import { AllowAllGovernanceClient, HttpGovernanceClient, } from '../governance/index.js';
34
- import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService, SessionContextService, TaskService, LangGraphAdapter, HitlHandler, TokenBudgetService, TokenStatsService, TokenBudgetGuard, SessionBudgetChecker, createAgentRegistryClient, GraphLoader, } from '../index.js';
34
+ import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService, SessionContextService, TaskService, TaskTimeoutManager, LangGraphAdapter, HitlHandler, TokenBudgetService, TokenStatsService, TokenBudgetGuard, SessionBudgetChecker, createAgentRegistryClient, GraphLoader, } from '../index.js';
35
35
  import { createAgentRuntime } from './agent-runtime.js';
36
36
  import { assembleRuntimeStorage } from './storage-profile.js';
37
37
  import { createDevRagNodeDeps } from './dev-rag-deps.js';
@@ -139,6 +139,7 @@ export function createDevRuntime(options) {
139
139
  const sessionContextService = new SessionContextService(sessionService, messageService);
140
140
  // ── Task / Token ──
141
141
  const taskService = new TaskService();
142
+ const timeoutManager = new TaskTimeoutManager();
142
143
  const budgetService = new TokenBudgetService(storage.tokenBudgetRepository);
143
144
  const tokenStatsService = new TokenStatsService();
144
145
  const tokenBudgetGuard = new TokenBudgetGuard(budgetService);
@@ -202,6 +203,7 @@ export function createDevRuntime(options) {
202
203
  checkpointSaver: storage.checkpointSaver,
203
204
  sessionCompression,
204
205
  sessionMemory,
206
+ timeoutManager,
205
207
  });
206
208
  const streamOrchestrator = new StreamOrchestrator({
207
209
  sessionContextService,
@@ -215,6 +217,7 @@ export function createDevRuntime(options) {
215
217
  checkpointSaver: storage.checkpointSaver,
216
218
  sessionCompression,
217
219
  sessionMemory,
220
+ timeoutManager,
218
221
  });
219
222
  const agentRuntime = createAgentRuntime({
220
223
  invokeOrchestrator,
@@ -229,6 +232,7 @@ export function createDevRuntime(options) {
229
232
  turnLog,
230
233
  sessionCompression,
231
234
  sessionMemory,
235
+ timeoutManager,
232
236
  });
233
237
  return {
234
238
  invokeOrchestrator,
@@ -243,7 +247,10 @@ export function createDevRuntime(options) {
243
247
  taskService,
244
248
  tokenStatsService,
245
249
  graphLoader,
246
- dispose: stopSkillWatch,
250
+ dispose: () => {
251
+ stopSkillWatch();
252
+ timeoutManager.dispose();
253
+ },
247
254
  diagnostics: {
248
255
  packageVersion: RUNTIME_PACKAGE_VERSION,
249
256
  deploymentContext,
@@ -78,7 +78,7 @@ export interface ForkResult {
78
78
  originalTraceId: string;
79
79
  newTaskId: string;
80
80
  checkpointId: string;
81
- status: 'completed' | 'failed';
81
+ status: 'completed' | 'failed' | 'timeout';
82
82
  output: unknown;
83
83
  tokenUsage: {
84
84
  input_tokens: number;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * execute/stream 装载历史后的压缩入口。默认关。
2
+ * execute/stream 装载历史后的压缩入口。默认开。
3
3
  *
4
- * C-1a 开关:options.enabled 或 EAP_RUNTIME_COMPRESSION=1
4
+ * C-1a 开关:默认开。options.enabled=false 或 EAP_RUNTIME_COMPRESSION=0 / false 关闭
5
5
  * C-1b 有窗口:用上一轮 llm_turns 的 used/window,不用字符估窗
6
6
  * C-1c 无窗口:只按轮次上限,不按窗压
7
- * C-1d 摘要只走 ILLMProvider;失败则不压缩
8
- * C-1e 默认只改本轮内存历史;persistCompressed 时必须能写 compression record
7
+ * C-1d 摘要只走 ILLMProvider;Echo 或未注入 provider 则不压;失败则不压缩
8
+ * C-1e 压成功且来自 Checkpoint 时,入图 `__set` 覆盖最新 Checkpoint messages;不回写 Message 库。persistCompressed 只写审计 record
9
9
  * C-1f 流式路径在摘要前产出 session.compression.started,结束后产出 completed。
10
10
  * 不新开「占用 ≥ 80% 再 compact」链;触发仍是 C-1b/c。
11
11
  */
@@ -15,6 +15,8 @@ import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
15
15
  import { type CompressionRecord } from '../memory/message-compression.service.js';
16
16
  import type { ICompressionRepository } from '../memory/compression/compression.repository.js';
17
17
  import type { SessionCompressionCompletedPayload, SessionCompressionReason, SessionCompressionStartedPayload } from '../sse/events/session-events.js';
18
+ /** 摘要 LLM 写入 llm_turns 的 nodeId。lastTurnUsage 跳过,避免用摘要占用触发下一轮压缩。 */
19
+ export declare const COMPRESSION_TURN_NODE_ID = "session-compression";
18
20
  export interface SessionCompressionOptions {
19
21
  enabled?: boolean;
20
22
  persistCompressed?: boolean;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * execute/stream 装载历史后的压缩入口。默认关。
2
+ * execute/stream 装载历史后的压缩入口。默认开。
3
3
  *
4
- * C-1a 开关:options.enabled 或 EAP_RUNTIME_COMPRESSION=1
4
+ * C-1a 开关:默认开。options.enabled=false 或 EAP_RUNTIME_COMPRESSION=0 / false 关闭
5
5
  * C-1b 有窗口:用上一轮 llm_turns 的 used/window,不用字符估窗
6
6
  * C-1c 无窗口:只按轮次上限,不按窗压
7
- * C-1d 摘要只走 ILLMProvider;失败则不压缩
8
- * C-1e 默认只改本轮内存历史;persistCompressed 时必须能写 compression record
7
+ * C-1d 摘要只走 ILLMProvider;Echo 或未注入 provider 则不压;失败则不压缩
8
+ * C-1e 压成功且来自 Checkpoint 时,入图 `__set` 覆盖最新 Checkpoint messages;不回写 Message 库。persistCompressed 只写审计 record
9
9
  * C-1f 流式路径在摘要前产出 session.compression.started,结束后产出 completed。
10
10
  * 不新开「占用 ≥ 80% 再 compact」链;触发仍是 C-1b/c。
11
11
  */
@@ -13,13 +13,17 @@ import { EchoLLMProvider } from '../providers/echo-llm-provider.js';
13
13
  import { COMPRESSION_WINDOW_FACTOR, countRounds, DEFAULT_COMPRESSION_CONFIG, } from '../memory/message-compression.service.js';
14
14
  import { SlidingWindowCompression } from '../memory/compression/sliding-window.compression.js';
15
15
  import { emitRuntimeLog } from '../common/logger.js';
16
+ /** 摘要 LLM 写入 llm_turns 的 nodeId。lastTurnUsage 跳过,避免用摘要占用触发下一轮压缩。 */
17
+ export const COMPRESSION_TURN_NODE_ID = 'session-compression';
16
18
  export function isRuntimeCompressionEnabled(explicit, env = process.env) {
17
19
  if (explicit === true)
18
20
  return true;
19
21
  if (explicit === false)
20
22
  return false;
21
23
  const raw = env.EAP_RUNTIME_COMPRESSION ?? env.EAP_COMPRESSION_ENABLED;
22
- return raw === '1' || raw === 'true';
24
+ if (raw === '0' || raw === 'false')
25
+ return false;
26
+ return true;
23
27
  }
24
28
  export function isRuntimeCompressionPersist(explicit, env = process.env) {
25
29
  if (explicit === true)
@@ -164,7 +168,7 @@ async function runDecidedSessionCompression(params, decision) {
164
168
  maxRounds,
165
169
  preserveRecentRounds: options.preserveRecentRounds ?? DEFAULT_COMPRESSION_CONFIG.preserveRecentRounds,
166
170
  ...(decision.window ? { maxContextTokens: decision.window } : {}),
167
- }, async (msgs) => summarizeWithProvider(provider, msgs, options.summaryModel));
171
+ }, async (msgs) => summarizeWithProvider(provider, msgs, options.summaryModel, params.sessionId));
168
172
  const compressed = await compression.compress(history);
169
173
  if (compressed === history) {
170
174
  return {
@@ -250,12 +254,17 @@ async function lastTurnUsage(turnLog, sessionId) {
250
254
  if (!turnLog || !sessionId)
251
255
  return undefined;
252
256
  const index = await turnLog.list(sessionId);
253
- const last = index[index.length - 1];
254
- if (!last || typeof last.used !== 'number')
255
- return undefined;
256
- return { used: last.used, window: last.window };
257
+ for (let i = index.length - 1; i >= 0; i--) {
258
+ const entry = index[i];
259
+ if (!entry || entry.nodeId === COMPRESSION_TURN_NODE_ID)
260
+ continue;
261
+ if (typeof entry.used !== 'number')
262
+ continue;
263
+ return { used: entry.used, window: entry.window };
264
+ }
265
+ return undefined;
257
266
  }
258
- async function summarizeWithProvider(provider, messages, summaryModel) {
267
+ async function summarizeWithProvider(provider, messages, summaryModel, sessionId) {
259
268
  const body = messages
260
269
  .map((m) => `${m.role}: ${m.content}`)
261
270
  .join('\n')
@@ -272,6 +281,10 @@ async function summarizeWithProvider(provider, messages, summaryModel) {
272
281
  model,
273
282
  temperature: 0.2,
274
283
  maxTokens: 1024,
284
+ turn: {
285
+ sessionId,
286
+ nodeId: COMPRESSION_TURN_NODE_ID,
287
+ },
275
288
  });
276
289
  const text = response.content?.trim();
277
290
  if (!text) {
@@ -10,6 +10,6 @@ export type { FileMessageRepositoryOptions } from './file-message.repository.js'
10
10
  export { SessionContextService } from './session-context.service.js';
11
11
  export type { ResolveSessionParams, SessionContext, } from './session-context.service.js';
12
12
  export { CompressionMiddleware, createCompressionMiddleware } from './compression-middleware.js';
13
- export { applySessionCompression, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, type SessionCompressionOptions, type RunSessionCompressionParams, type RunSessionCompressionResult, type SessionCompressionDecision, type SessionCompressionStep, } from './apply-session-compression.js';
13
+ export { applySessionCompression, COMPRESSION_TURN_NODE_ID, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, type SessionCompressionOptions, type RunSessionCompressionParams, type RunSessionCompressionResult, type SessionCompressionDecision, type SessionCompressionStep, } from './apply-session-compression.js';
14
14
  export type { CompressionMiddlewareConfig } from './compression-middleware.js';
15
15
  export { applySessionMemory, isRuntimeMemoryEnabled, type SessionMemoryOptions, } from './apply-session-memory.js';
@@ -10,5 +10,5 @@ export { FileMessageRepository } from './file-message.repository.js';
10
10
  export { SessionContextService } from './session-context.service.js';
11
11
  // 压缩中间件
12
12
  export { CompressionMiddleware, createCompressionMiddleware } from './compression-middleware.js';
13
- export { applySessionCompression, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, } from './apply-session-compression.js';
13
+ export { applySessionCompression, COMPRESSION_TURN_NODE_ID, withCompressionSummaryModel, runSessionCompression, decideSessionCompression, iterSessionCompression, formatCompressionStartedMessage, formatCompressionCompletedMessage, isRuntimeCompressionEnabled, isRuntimeCompressionPersist, } from './apply-session-compression.js';
14
14
  export { applySessionMemory, isRuntimeMemoryEnabled, } from './apply-session-memory.js';
@@ -99,6 +99,15 @@ export declare function appendTerminalToolResult(eventType: string, payload: unk
99
99
  }>): void;
100
100
  /** 历史 MessageRecord → GraphMessage(保留 tool 元数据与 reasoning) */
101
101
  export declare function messageRecordsToGraphMessages(records: MessageRecord[]): GraphMessage[];
102
+ type CheckpointLoad = {
103
+ state?: {
104
+ messages?: unknown;
105
+ };
106
+ engineSnapshot?: Record<string, unknown>;
107
+ } | null;
108
+ type CheckpointLoader = {
109
+ load(threadId: string): Promise<CheckpointLoad>;
110
+ };
102
111
  /**
103
112
  * 组装本轮送入 Graph 的 messages。
104
113
  *
@@ -107,21 +116,52 @@ export declare function messageRecordsToGraphMessages(records: MessageRecord[]):
107
116
  * - Session 级 Checkpoint 持有图内 messages(含 reasoning)
108
117
  * - 当 Checkpoint 已有历史时,只追加本轮 user,避免双重 append
109
118
  * - Checkpoint 缺失/无 messages 时,回退 Session 全量历史
119
+ * - 压缩针对 Checkpoint(无则 Session)中的模型上下文;压成功且来自 Checkpoint 时由调用方设 `replaceCheckpointMessages`
110
120
  */
111
121
  export declare function buildTurnGraphMessages(params: {
112
122
  history: GraphMessage[];
113
123
  input: string;
114
- checkpointSaver?: {
115
- load(threadId: string): Promise<{
116
- state?: {
117
- messages?: unknown;
118
- };
119
- engineSnapshot?: Record<string, unknown>;
120
- } | null>;
121
- } | null;
124
+ checkpointSaver?: CheckpointLoader | null;
122
125
  /** 与 LangGraph configurable.thread_id 一致(通常为 sessionId) */
123
126
  threadId?: string;
124
127
  }): Promise<GraphMessage[]>;
128
+ /**
129
+ * 模型上下文:Checkpoint 已有 messages 用它,否则用 Session 历史。
130
+ * 压缩必须压这份列表,不能只压 Session 再在有 Checkpoint 时丢掉。
131
+ */
132
+ export declare function loadModelFacingHistory(params: {
133
+ sessionHistory: GraphMessage[];
134
+ checkpointSaver?: CheckpointLoader | null;
135
+ threadId?: string;
136
+ }): Promise<{
137
+ messages: GraphMessage[];
138
+ fromCheckpoint: boolean;
139
+ }>;
140
+ /**
141
+ * 压缩后的入图列表。来自 Checkpoint 且本轮压成功:整表 `__set` 覆盖,写入最新 Checkpoint。
142
+ * 未压且来自 Checkpoint:只追加本轮 user。无 Checkpoint:历史 + 本轮 user。
143
+ */
144
+ export declare function turnGraphMessagesAfterCompression(params: {
145
+ history: GraphMessage[];
146
+ input: string;
147
+ fromCheckpoint: boolean;
148
+ compressed: boolean;
149
+ }): {
150
+ messages: GraphMessage[];
151
+ replaceCheckpointMessages: boolean;
152
+ };
153
+ /**
154
+ * invoke 从 Graph 最终 messages 切本轮增量的起点。
155
+ * 覆盖(`__set`)或无 Checkpoint:入图数组长度(已含本轮 user)。
156
+ * 追加到已有 Checkpoint:模型上下文条数 + 入图条数(通常 1 条 user)。
157
+ */
158
+ export declare function persistGraphMessageBaseline(params: {
159
+ fromCheckpoint: boolean;
160
+ replaceCheckpointMessages: boolean;
161
+ graphInputCount: number;
162
+ modelFacingCount: number;
163
+ }): number;
164
+ export declare function graphMessagesFromCheckpoint(cp: CheckpointLoad): GraphMessage[];
125
165
  /**
126
166
  * HITL 等待态写入 Task.output 的结构化载荷。
127
167
  * 冷恢复时优先读 `hitlThreadId`,避免误用 correlationId。
@@ -146,7 +186,7 @@ export declare function extractHitlThreadId(output: unknown): string | undefined
146
186
  * 与 LangGraph put / 子图内 HITL interrupt 同一把钥匙。
147
187
  *
148
188
  * 注意:session 级 thread + 全量 Session 历史同时注入会导致 messages 双重 append。
149
- * 组装本轮 Graph 输入请用 {@link buildTurnGraphMessages}(有 Checkpoint 历史则只追加 user)。
189
+ * 组装本轮 Graph 输入请用 {@link loadModelFacingHistory} 与 {@link turnGraphMessagesAfterCompression}。
150
190
  */
151
191
  export declare function resolveCheckpointThreadId(params: {
152
192
  sessionId?: string;
@@ -199,3 +239,4 @@ export declare function persistTurnTranscript(messageService: MessageService | u
199
239
  assistantTokenCount?: number;
200
240
  assistantDurationMs?: number | null;
201
241
  }): Promise<void>;
242
+ export {};