@gt-fe/eap-runtime 0.0.8 → 0.0.10

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 (87) hide show
  1. package/README.md +12 -17
  2. package/dist/api/handlers/invoke.handler.js +3 -3
  3. package/dist/api/handlers/messages.handler.js +0 -1
  4. package/dist/api/handlers/stream.handler.js +3 -3
  5. package/dist/api/routes/agent.routes.js +2 -2
  6. package/dist/checkpoint/checkpoint-ref-notify.d.ts +10 -0
  7. package/dist/checkpoint/checkpoint-ref-notify.js +59 -0
  8. package/dist/checkpoint/index.d.ts +1 -0
  9. package/dist/checkpoint/index.js +1 -0
  10. package/dist/checkpoint/pg-checkpoint-saver.js +25 -2
  11. package/dist/common/agent-code.d.ts +2 -4
  12. package/dist/common/agent-code.js +4 -5
  13. package/dist/contracts/trace-collector.d.ts +15 -1
  14. package/dist/debug/llm-turn-log.d.ts +100 -0
  15. package/dist/debug/llm-turn-log.js +130 -0
  16. package/dist/debug/pg-llm-turn-log.d.ts +22 -0
  17. package/dist/debug/pg-llm-turn-log.js +57 -0
  18. package/dist/graph/adapters/langgraph-checkpointer.js +3 -1
  19. package/dist/graph/adapters/langgraph.adapter.d.ts +1 -0
  20. package/dist/graph/adapters/langgraph.adapter.js +147 -91
  21. package/dist/graph/adapters/skill-read.d.ts +12 -0
  22. package/dist/graph/adapters/skill-read.js +63 -0
  23. package/dist/graph/adapters/subgraph-compiler.js +5 -0
  24. package/dist/graph/contracts/executable-graph.d.ts +3 -3
  25. package/dist/graph/hitl/hitl-handler.d.ts +7 -0
  26. package/dist/graph/hitl/hitl-handler.js +47 -6
  27. package/dist/graph/nodes/llm-node.d.ts +14 -0
  28. package/dist/graph/nodes/skill-bind-node.js +1 -2
  29. package/dist/graph/nodes/tool-node.d.ts +2 -0
  30. package/dist/graph/nodes/tool-node.js +24 -9
  31. package/dist/graph/trace-span.d.ts +22 -0
  32. package/dist/graph/trace-span.js +69 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.js +2 -0
  35. package/dist/memory/compression/compression.repository.d.ts +1 -15
  36. package/dist/providers/echo-llm-provider.d.ts +1 -1
  37. package/dist/providers/echo-llm-provider.js +2 -1
  38. package/dist/providers/http-llm-provider.d.ts +4 -0
  39. package/dist/providers/http-llm-provider.js +129 -4
  40. package/dist/providers/local-llm-provider.d.ts +5 -1
  41. package/dist/providers/local-llm-provider.js +14 -6
  42. package/dist/providers/routing-llm-provider.d.ts +4 -0
  43. package/dist/providers/routing-llm-provider.js +12 -2
  44. package/dist/registry/agent-models.d.ts +1 -1
  45. package/dist/registry/agent-models.js +1 -1
  46. package/dist/registry/agent-registry-client.d.ts +2 -2
  47. package/dist/registry/deployment-snapshot.d.ts +4 -4
  48. package/dist/registry/deployment-snapshot.js +50 -28
  49. package/dist/registry/graph-loader.d.ts +1 -1
  50. package/dist/registry/graph-loader.js +39 -76
  51. package/dist/registry/index.d.ts +1 -1
  52. package/dist/registry/index.js +1 -1
  53. package/dist/registry/skill-tree.d.ts +3 -0
  54. package/dist/registry/skill-tree.js +41 -3
  55. package/dist/runtime/agent-runtime.d.ts +42 -3
  56. package/dist/runtime/agent-runtime.js +110 -34
  57. package/dist/runtime/dev-runtime.d.ts +6 -3
  58. package/dist/runtime/dev-runtime.js +21 -14
  59. package/dist/runtime/index.d.ts +3 -3
  60. package/dist/runtime/index.js +1 -1
  61. package/dist/runtime/storage-profile.d.ts +19 -10
  62. package/dist/runtime/storage-profile.js +42 -29
  63. package/dist/runtime/types.d.ts +15 -17
  64. package/dist/server.js +1 -1
  65. package/dist/session/session.service.d.ts +5 -0
  66. package/dist/session/session.service.js +12 -0
  67. package/dist/sse/events/index.d.ts +5 -3
  68. package/dist/sse/events/index.js +2 -0
  69. package/dist/sse/events/skill-events.d.ts +11 -0
  70. package/dist/sse/trace-collector.d.ts +10 -0
  71. package/dist/sse/trace-collector.js +1 -0
  72. package/dist/token/token-budget.service.d.ts +2 -2
  73. package/dist/token/token-budget.service.js +1 -1
  74. package/dist/tool/create-tool-executor.js +3 -2
  75. package/dist/tool/harness-primitives.d.ts +32 -0
  76. package/dist/tool/harness-primitives.js +385 -0
  77. package/dist/tool/http-tool-executor.js +1 -1
  78. package/dist/tool/index.d.ts +2 -1
  79. package/dist/tool/index.js +2 -1
  80. package/dist/tool/local-tool-executor.d.ts +3 -3
  81. package/dist/tool/local-tool-executor.js +5 -7
  82. package/dist/tool/mcp-tool-executor.js +9 -1
  83. package/dist/tool/system-prompt-builder.d.ts +61 -25
  84. package/dist/tool/system-prompt-builder.js +195 -92
  85. package/dist/tool/tool-execution-router.js +1 -1
  86. package/dist/tool/tool-executor.interface.d.ts +12 -6
  87. package/package.json +2 -2
@@ -10,6 +10,8 @@
10
10
  * @see design.md §1.6 hitl.waiting / hitl.resumed 事件
11
11
  * @see tasks.md Task 14
12
12
  */
13
+ import { notifyCheckpointRef, stripCheckpointCollectors, } from '../../checkpoint/checkpoint-ref-notify.js';
14
+ import { startGraphSpan, completeGraphSpan } from '../trace-span.js';
13
15
  // ─────────────────────────────────────────────
14
16
  // Types
15
17
  // ─────────────────────────────────────────────
@@ -151,8 +153,9 @@ export function buildHitlWaitedPayload(paused) {
151
153
  */
152
154
  function prepareHitlCheckpointState(state) {
153
155
  const raw = state;
156
+ const { traceCollector: _drop, ...rest } = raw;
154
157
  return {
155
- ...state,
158
+ ...rest,
156
159
  userId: state.userId ?? raw.userId ?? '',
157
160
  loadedTools: state.loadedTools ?? raw.loadedTools,
158
161
  forceToolServer: state.forceToolServer ?? raw.forceToolServer,
@@ -162,6 +165,7 @@ function prepareHitlCheckpointState(state) {
162
165
  delegationToken: state.delegationToken ?? raw.delegationToken,
163
166
  skillSpanId: state.skillSpanId ?? raw.skillSpanId,
164
167
  traceId: state.traceId ?? (typeof raw.traceId === 'string' ? raw.traceId : undefined),
168
+ lastSpanId: state.lastSpanId ?? (typeof raw.lastSpanId === 'string' ? raw.lastSpanId : undefined),
165
169
  };
166
170
  }
167
171
  function toTimeoutEvent(waiting) {
@@ -193,6 +197,7 @@ function waitingFromPersisted(persisted, taskId) {
193
197
  toolName: persisted.toolName,
194
198
  toolSpanId: persisted.toolSpanId,
195
199
  skillSpanId: persisted.skillSpanId,
200
+ hitlSpanId: persisted.hitlSpanId,
196
201
  };
197
202
  }
198
203
  // ─────────────────────────────────────────────
@@ -216,6 +221,9 @@ export class HitlHandler {
216
221
  timeoutListener;
217
222
  /** 同一 taskId 上串行化 resume 与 timeout,避免双边占用 */
218
223
  taskLocks = new Map();
224
+ traceCollector;
225
+ /** 进程内 interrupt 的 collector,不进 Checkpoint */
226
+ waitingCollectors = new Map();
219
227
  constructor(checkpointSaver, sseEmitter, timeoutListener) {
220
228
  this.checkpointSaver = checkpointSaver;
221
229
  this.sseEmitter = sseEmitter;
@@ -224,6 +232,9 @@ export class HitlHandler {
224
232
  setTimeoutListener(listener) {
225
233
  this.timeoutListener = listener;
226
234
  }
235
+ setTraceCollector(collector) {
236
+ this.traceCollector = collector;
237
+ }
227
238
  /**
228
239
  * 中断 Graph 执行,等待人工确认
229
240
  *
@@ -237,6 +248,11 @@ export class HitlHandler {
237
248
  const deadlineAt = new Date(Date.now() + timeoutMs).toISOString();
238
249
  const skillSpanId = extras.skillSpanId ??
239
250
  (typeof state.skillSpanId === 'string' ? state.skillSpanId : undefined);
251
+ const hitlSpanId = await startGraphSpan(state, {
252
+ name: extras.toolName ?? 'hitl',
253
+ type: 'hitl',
254
+ parentSpanId: extras.toolSpanId ?? skillSpanId,
255
+ });
240
256
  const persisted = {
241
257
  taskId,
242
258
  threadId,
@@ -247,11 +263,16 @@ export class HitlHandler {
247
263
  toolName: extras.toolName,
248
264
  toolSpanId: extras.toolSpanId,
249
265
  skillSpanId,
266
+ hitlSpanId,
250
267
  };
251
268
  const existing = await this.checkpointSaver.load(threadId);
269
+ const lastSpanId = hitlSpanId ??
270
+ extras.toolSpanId ??
271
+ (typeof state.lastSpanId === 'string' ? state.lastSpanId : undefined);
252
272
  const persistedState = {
253
273
  ...(existing?.state ?? {}),
254
274
  ...prepareHitlCheckpointState(state),
275
+ ...(lastSpanId ? { lastSpanId } : {}),
255
276
  [HITL_WAITING_STATE_KEY]: persisted,
256
277
  };
257
278
  const checkpointData = {
@@ -266,7 +287,13 @@ export class HitlHandler {
266
287
  updatedAt: now,
267
288
  isDeleted: false,
268
289
  };
269
- await this.checkpointSaver.save(threadId, checkpointData);
290
+ const persistedCheckpoint = stripCheckpointCollectors(checkpointData);
291
+ await this.checkpointSaver.save(threadId, persistedCheckpoint);
292
+ const liveCollector = state.traceCollector;
293
+ if (liveCollector) {
294
+ this.waitingCollectors.set(taskId, liveCollector);
295
+ }
296
+ await notifyCheckpointRef(persistedCheckpoint, liveCollector);
270
297
  const event = {
271
298
  type: 'hitl.waiting',
272
299
  data: {
@@ -300,6 +327,7 @@ export class HitlHandler {
300
327
  toolName: extras.toolName,
301
328
  toolSpanId: extras.toolSpanId,
302
329
  skillSpanId,
330
+ hitlSpanId,
303
331
  });
304
332
  }
305
333
  /**
@@ -335,6 +363,7 @@ export class HitlHandler {
335
363
  if (!waiting) {
336
364
  throw new HitlNotWaitingError(taskId);
337
365
  }
366
+ const liveCollector = this.waitingCollectors.get(taskId) ?? this.traceCollector;
338
367
  this.clearWaiting(taskId);
339
368
  this.resolvedChoices.set(taskId, choice);
340
369
  const baseState = prepareHitlCheckpointState(checkpoint.state);
@@ -346,13 +375,18 @@ export class HitlHandler {
346
375
  hitlRespondedBy: userId,
347
376
  };
348
377
  const now = new Date().toISOString();
349
- await this.checkpointSaver.save(waiting.threadId, {
378
+ await this.checkpointSaver.save(waiting.threadId, stripCheckpointCollectors({
350
379
  ...checkpoint,
351
380
  engineSnapshot: undefined,
352
381
  state: resumedState,
353
382
  updatedBy: userId,
354
383
  updatedAt: now,
355
- });
384
+ }));
385
+ const hitlSpanId = waiting.hitlSpanId ?? persisted?.hitlSpanId;
386
+ await completeGraphSpan(resumedState, hitlSpanId, {
387
+ status: 'completed',
388
+ output: { choice, userId },
389
+ }, liveCollector);
356
390
  const event = {
357
391
  type: 'hitl.resumed',
358
392
  data: { choice, userId },
@@ -390,6 +424,7 @@ export class HitlHandler {
390
424
  toolSpanId: waiting.toolSpanId ?? persisted.toolSpanId,
391
425
  skillSpanId: waiting.skillSpanId ?? persisted.skillSpanId,
392
426
  });
427
+ const liveCollector = this.waitingCollectors.get(taskId) ?? this.traceCollector;
393
428
  this.clearWaiting(taskId);
394
429
  if (timeoutEvent.behavior !== 'auto-confirm' && checkpoint) {
395
430
  const marked = {
@@ -400,12 +435,17 @@ export class HitlHandler {
400
435
  timeoutBehavior: timeoutEvent.behavior,
401
436
  },
402
437
  };
403
- await this.checkpointSaver.save(waiting.threadId, {
438
+ await this.checkpointSaver.save(waiting.threadId, stripCheckpointCollectors({
404
439
  ...checkpoint,
405
440
  engineSnapshot: undefined,
406
441
  state: marked,
407
442
  updatedAt: new Date().toISOString(),
408
- });
443
+ }));
444
+ const hitlSpanId = waiting.hitlSpanId ?? persisted.hitlSpanId;
445
+ await completeGraphSpan(checkpoint.state, hitlSpanId, {
446
+ status: 'failed',
447
+ error: 'hitl_timeout',
448
+ }, liveCollector);
409
449
  }
410
450
  return timeoutEvent;
411
451
  });
@@ -439,6 +479,7 @@ export class HitlHandler {
439
479
  clearTimeout(waiting.timer);
440
480
  }
441
481
  this.waitingTasks.delete(taskId);
482
+ this.waitingCollectors.delete(taskId);
442
483
  }
443
484
  async withTaskLock(taskId, fn) {
444
485
  const prev = this.taskLocks.get(taskId) ?? Promise.resolve();
@@ -44,6 +44,20 @@ export interface LLMNodeConfig {
44
44
  name: string;
45
45
  };
46
46
  };
47
+ /** 具名 system 段(落盘排查,不另发给模型) */
48
+ systemSections?: Array<{
49
+ id: string;
50
+ title: string;
51
+ body: string;
52
+ }>;
53
+ /** 本轮调用定位(session / task / node) */
54
+ turn?: {
55
+ sessionId?: string;
56
+ taskId?: string;
57
+ nodeId?: string;
58
+ agentCode?: string;
59
+ correlationId?: string;
60
+ };
47
61
  }
48
62
  /**
49
63
  * LLMResponse — LLM 完整响应
@@ -62,8 +62,7 @@ function buildSkillPrompt(skill, params) {
62
62
  */
63
63
  export function bindSelectedSkill(state) {
64
64
  const incoming = Array.isArray(state.messages) ? [...state.messages] : [];
65
- const fromState = (typeof state.silentOutput === 'string' && state.silentOutput)
66
- || (typeof state.skillSelectRaw === 'string' ? state.skillSelectRaw : '');
65
+ const fromState = typeof state.silentOutput === 'string' ? state.silentOutput : '';
67
66
  const lastText = lastAssistantText(incoming);
68
67
  const lastIsSelector = extractJsonObject(lastText) !== null;
69
68
  const parsed = extractJsonObject(fromState) ?? extractJsonObject(lastText);
@@ -12,6 +12,7 @@
12
12
  import type { AuthContext } from '../../tool/tool-executor.interface.js';
13
13
  import type { ToolOrchestrator } from '../../tool/tool-orchestrator.js';
14
14
  import type { ToolResultInjector } from '../../tool/tool-result-injector.js';
15
+ import type { SkillDirRef } from '../adapters/skill-read.js';
15
16
  import type { HitlHandler } from '../hitl/hitl-handler.js';
16
17
  /**
17
18
  * LangGraphState — LangGraph 内部状态(与 LLMNode 对齐)
@@ -39,6 +40,7 @@ export interface ToolNodeProgress {
39
40
  executionTime?: number;
40
41
  node?: string;
41
42
  skillId?: string;
43
+ skillDirs?: SkillDirRef[];
42
44
  }
43
45
  export declare class ToolNode {
44
46
  private readonly orchestrator;
@@ -13,6 +13,12 @@ import { isHitlDecision, unwrapHitlPaused } from '../hitl/hitl-handler.js';
13
13
  import { ToolConfirmationInterceptor } from '../../tool/tool-confirmation.js';
14
14
  import { resolveCheckpointThreadId } from '../../session/persist-turn-messages.js';
15
15
  import { resolveTraceId } from '../../common/trace-id.js';
16
+ function skillDirsFromState(state) {
17
+ const defs = state['skillDefinitions'];
18
+ return Object.values(defs ?? {})
19
+ .filter((skill) => Boolean(skill.bundlePath))
20
+ .map((skill) => ({ skillId: skill.skillId, bundlePath: skill.bundlePath }));
21
+ }
16
22
  export class ToolNode {
17
23
  orchestrator;
18
24
  injector;
@@ -46,6 +52,8 @@ export class ToolNode {
46
52
  return state;
47
53
  }
48
54
  const loadedTools = state['loadedTools'];
55
+ const skillDirs = skillDirsFromState(state);
56
+ const jailExtraRoots = skillDirs.map((dir) => dir.bundlePath);
49
57
  // 4. 转换为 ToolCallRequest[](附带 manifest 路由字段)
50
58
  const requests = lastAssistantMessage.toolCalls.map((tc) => {
51
59
  const loaded = loadedTools?.[tc.name];
@@ -62,20 +70,27 @@ export class ToolNode {
62
70
  dataScope: loaded?.manifest.dataScope,
63
71
  // dev 上下文 DevMcpExecutor 直连所需;server 上下文会忽略
64
72
  execution: loaded?.manifest.execution,
73
+ ...(loaded?.manifest.version ? { toolVersion: loaded.manifest.version } : {}),
65
74
  correlationId,
66
75
  taskId,
76
+ ...(jailExtraRoots.length > 0 ? { jailExtraRoots } : {}),
67
77
  };
68
78
  });
69
79
  const executionContext = state['executionContext'];
70
80
  const traceCollector = state['traceCollector'];
71
81
  const traceId = resolveTraceId(state);
72
- const agentCode = String(state['agentCode'] ?? state['agentId'] ?? '');
82
+ const agentCode = String(state['agentCode'] ?? '');
73
83
  const nodeId = this.nodeId ?? 'tool-node';
74
84
  const skillId = typeof state['activatedSkill'] === 'string' && state['activatedSkill']
75
85
  ? state['activatedSkill']
76
86
  : undefined;
77
87
  const emitProgress = (data) => {
78
- this.onProgress?.({ ...data, node: nodeId, ...(skillId ? { skillId } : {}) });
88
+ this.onProgress?.({
89
+ ...data,
90
+ node: nodeId,
91
+ ...(skillId ? { skillId } : {}),
92
+ ...(skillDirs.length > 0 ? { skillDirs } : {}),
93
+ });
79
94
  };
80
95
  // 5. 并行执行所有 Tool 调用(附 Trace span)
81
96
  const spanIds = await Promise.all(requests.map(async (req) => {
@@ -158,6 +173,7 @@ export class ToolNode {
158
173
  toolCallId: result.toolCallId,
159
174
  toolName: result.toolName,
160
175
  status: 'completed',
176
+ arguments: req.arguments,
161
177
  content: result.content,
162
178
  executionTime,
163
179
  });
@@ -167,6 +183,7 @@ export class ToolNode {
167
183
  toolCallId: result.toolCallId,
168
184
  toolName: result.toolName,
169
185
  status: 'error',
186
+ arguments: req.arguments,
170
187
  error: result.error ?? 'unknown error',
171
188
  executionTime,
172
189
  });
@@ -202,6 +219,7 @@ export class ToolNode {
202
219
  toolCallId: req.toolCallId,
203
220
  toolName: req.toolName,
204
221
  status: 'error',
222
+ arguments: req.arguments,
205
223
  error: message,
206
224
  executionTime,
207
225
  });
@@ -240,10 +258,11 @@ export class ToolNode {
240
258
  success: result.success,
241
259
  ...('file' in result && result.file ? { file: result.file } : {}),
242
260
  }));
243
- // 7. 返回更新后的 state(仅含新增 messages + currentNode)
261
+ const lastSpanId = [...spanIds].reverse().find((id) => Boolean(id));
244
262
  return {
245
263
  messages: toolMessages,
246
264
  currentNode: nodeId,
265
+ ...(lastSpanId ? { lastSpanId } : {}),
247
266
  };
248
267
  }
249
268
  // ─────────────────────────────────────────────
@@ -282,11 +301,7 @@ export class ToolNode {
282
301
  }
283
302
  buildAuthContext(state) {
284
303
  const executionContext = state['executionContext'];
285
- // 旧 Checkpoint / 历史调用方兼容:agentId 仅作读兜底
286
- const agentCode = String(state['agentCode'] ??
287
- state['agentId'] ??
288
- this.defaultAuthContext.agentCode ??
289
- this.defaultAuthContext.agentId);
304
+ const agentCode = String(state['agentCode'] ?? this.defaultAuthContext.agentCode ?? '');
290
305
  // 优先 ExecutionContext(invoke Token.sub),保证所有 Tool/MCP 出站同一身份
291
306
  const userId = String(executionContext?.userId ?? state['userId'] ?? this.defaultAuthContext.userId);
292
307
  const delegationToken = String(executionContext?.delegationToken ??
@@ -315,7 +330,7 @@ export class ToolNode {
315
330
  loadedTools: state['loadedTools'],
316
331
  forceToolServer: state['forceToolServer'],
317
332
  executionContext: state['executionContext'],
318
- agentCode: (state['agentCode'] ?? state['agentId']),
333
+ agentCode: state['agentCode'],
319
334
  correlationId: state['correlationId'],
320
335
  taskId: state['taskId'],
321
336
  sessionId: state['sessionId'],
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
3
+ */
4
+ import type { ITraceCollector, ExecutionNodeEvent } from '../contracts/trace-collector.js';
5
+ export declare function startGraphSpan(state: Record<string, unknown>, input: {
6
+ name: string;
7
+ type: ExecutionNodeEvent['type'];
8
+ parentSpanId?: string;
9
+ extra?: Partial<Omit<ExecutionNodeEvent, 'traceId' | 'name' | 'type' | 'parentNodeId'>>;
10
+ }): Promise<string | undefined>;
11
+ export declare function completeGraphSpan(state: Record<string, unknown>, spanId: string | undefined, params: {
12
+ status: 'completed' | 'failed';
13
+ output?: unknown;
14
+ error?: string;
15
+ inputTokens?: number;
16
+ outputTokens?: number;
17
+ totalTokens?: number;
18
+ }, collectorOverride?: ITraceCollector): Promise<void>;
19
+ export declare function withGraphSpan(state: Record<string, unknown>, spec: {
20
+ name: string;
21
+ type: ExecutionNodeEvent['type'];
22
+ }, run: () => Promise<Record<string, unknown>>): Promise<Record<string, unknown>>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Graph 节点 Span 辅助:createNode / completeNode 失败不挡主路径。
3
+ */
4
+ import { resolveTraceId } from '../common/trace-id.js';
5
+ export async function startGraphSpan(state, input) {
6
+ const collector = state['traceCollector'];
7
+ const traceId = resolveTraceId(state);
8
+ if (!collector || !traceId)
9
+ return undefined;
10
+ const parentNodeId = input.parentSpanId ??
11
+ (typeof state['skillSpanId'] === 'string' && state['skillSpanId']
12
+ ? state['skillSpanId']
13
+ : undefined);
14
+ try {
15
+ return await collector.createNode({
16
+ traceId,
17
+ name: input.name,
18
+ type: input.type,
19
+ parentNodeId,
20
+ agentCode: (typeof state['agentCode'] === 'string' && state['agentCode']) ||
21
+ (typeof state['agentId'] === 'string' ? state['agentId'] : undefined),
22
+ ...input.extra,
23
+ metadata: {
24
+ taskId: state['taskId'],
25
+ correlationId: state['correlationId'],
26
+ ...(input.extra?.metadata ?? {}),
27
+ },
28
+ });
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
34
+ export async function completeGraphSpan(state, spanId, params, collectorOverride) {
35
+ const collector = collectorOverride ?? state['traceCollector'];
36
+ if (!collector?.completeNode || !spanId)
37
+ return;
38
+ try {
39
+ await collector.completeNode(spanId, params);
40
+ }
41
+ catch {
42
+ // Trace 失败不影响主路径
43
+ }
44
+ }
45
+ export async function withGraphSpan(state, spec, run) {
46
+ const spanId = await startGraphSpan(state, spec);
47
+ try {
48
+ const result = await run();
49
+ const usage = Array.isArray(result['__llmTokenUsage'])
50
+ ? result['__llmTokenUsage'].at(-1)
51
+ : undefined;
52
+ await completeGraphSpan(state, spanId, {
53
+ status: 'completed',
54
+ inputTokens: usage?.inputTokens,
55
+ outputTokens: usage?.outputTokens,
56
+ totalTokens: usage?.inputTokens != null || usage?.outputTokens != null
57
+ ? (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
58
+ : undefined,
59
+ });
60
+ return spanId ? { ...result, lastSpanId: spanId } : result;
61
+ }
62
+ catch (err) {
63
+ await completeGraphSpan(state, spanId, {
64
+ status: 'failed',
65
+ error: err instanceof Error ? err.message : String(err),
66
+ });
67
+ throw err;
68
+ }
69
+ }
package/dist/index.d.ts CHANGED
@@ -19,3 +19,6 @@ export type { ITraceCollector, TraceEvent, TraceSession, ExecutionFlowEvent, Exe
19
19
  export type { ExecutionContext, CallerType, GovernancePermissionCheckParams, GovernancePermissionCheckResult, PermissionDenyReason, } from '@gt-fe/eap-contracts';
20
20
  export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, assertToolPermission, assertSkillPermission, PermissionDeniedError, GovernanceUnavailableError, } from './governance/index.js';
21
21
  export type { IGovernanceClient } from './governance/index.js';
22
+ export { FileLlmTurnLog, InMemoryLlmTurnLog, createFileLlmTurnLogFromEnv, maskApiKey, resolveLlmTurnDir, } from './debug/llm-turn-log.js';
23
+ export { PostgresLlmTurnLog } from './debug/pg-llm-turn-log.js';
24
+ export type { ILlmTurnLog, LlmTurnRecord, LlmTurnIndexEntry, LlmTurnContext, LlmTurnLogKind, } from './debug/llm-turn-log.js';
package/dist/index.js CHANGED
@@ -30,3 +30,5 @@ export * from './registry/index.js';
30
30
  // LLM Providers
31
31
  export * from './providers/index.js';
32
32
  export { HttpGovernanceClient, AllowAllGovernanceClient, resolveExecutionContext, assertToolPermission, assertSkillPermission, PermissionDeniedError, GovernanceUnavailableError, } from './governance/index.js';
33
+ export { FileLlmTurnLog, InMemoryLlmTurnLog, createFileLlmTurnLogFromEnv, maskApiKey, resolveLlmTurnDir, } from './debug/llm-turn-log.js';
34
+ export { PostgresLlmTurnLog } from './debug/pg-llm-turn-log.js';
@@ -13,21 +13,7 @@ export interface IPgPool {
13
13
  }>;
14
14
  }
15
15
  /**
16
- * CREATE TABLE compression_records (
17
- * id VARCHAR(36) PRIMARY KEY,
18
- * session_id VARCHAR(36) NOT NULL,
19
- * task_id VARCHAR(36) NULL,
20
- * original_message_count INT NOT NULL,
21
- * compressed_message_count INT NOT NULL,
22
- * original_tokens INT NOT NULL,
23
- * compressed_tokens INT NOT NULL,
24
- * strategy VARCHAR(20) NOT NULL,
25
- * summary_content TEXT NOT NULL,
26
- * created_by VARCHAR(36) NOT NULL DEFAULT 'system',
27
- * created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
28
- * INDEX idx_compression_session (session_id),
29
- * INDEX idx_compression_task (task_id)
30
- * );
16
+ * `compression_records` 不在现行 Runtime DDL 内。见 runtime/docs/ddl.md。
31
17
  */
32
18
  /**
33
19
  * ICompressionRepository — 压缩记录持久化接口
@@ -7,4 +7,4 @@ export declare class EchoLLMProvider implements ILLMProvider {
7
7
  invoke(messages: GraphMessage[], config: LLMNodeConfig): Promise<LLMResponse>;
8
8
  stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
9
9
  }
10
- export declare function createDefaultLLMProvider(): ILLMProvider;
10
+ export declare function createDefaultLLMProvider(turnLog?: import('../debug/llm-turn-log.js').ILlmTurnLog): ILLMProvider;
@@ -75,12 +75,13 @@ export class EchoLLMProvider {
75
75
  * - 否则 → EchoLLMProvider
76
76
  */
77
77
  import { HttpLLMProvider } from './http-llm-provider.js';
78
- export function createDefaultLLMProvider() {
78
+ export function createDefaultLLMProvider(turnLog) {
79
79
  if (process.env.OPENAI_API_KEY) {
80
80
  return new HttpLLMProvider({
81
81
  apiKey: process.env.OPENAI_API_KEY,
82
82
  baseUrl: process.env.OPENAI_BASE_URL ?? 'https://api.openai.com/v1',
83
83
  defaultModel: process.env.EAP_DEFAULT_MODEL ?? 'DeepSeek-V4-Pro',
84
+ turnLog,
84
85
  });
85
86
  }
86
87
  return new EchoLLMProvider();
@@ -8,18 +8,22 @@
8
8
  */
9
9
  import type { GraphMessage } from '../graph/contracts/executable-graph.js';
10
10
  import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
11
+ import { type ILlmTurnLog } from '../debug/llm-turn-log.js';
11
12
  export interface HttpLLMProviderConfig {
12
13
  apiKey: string;
13
14
  baseUrl?: string;
14
15
  defaultModel?: string;
15
16
  timeoutMs?: number;
17
+ turnLog?: ILlmTurnLog;
16
18
  }
17
19
  export declare class HttpLLMProvider implements ILLMProvider {
18
20
  private readonly apiKey;
19
21
  private readonly baseUrl;
20
22
  private readonly defaultModel;
21
23
  private readonly timeoutMs;
24
+ private readonly turnLog?;
22
25
  constructor(config: HttpLLMProviderConfig);
26
+ private writeTurn;
23
27
  invoke(messages: GraphMessage[], config: LLMNodeConfig): Promise<LLMResponse>;
24
28
  stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
25
29
  private buildRequestBody;