@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
@@ -6,13 +6,13 @@
6
6
  */
7
7
  import { readFile } from 'node:fs/promises';
8
8
  import path from 'node:path';
9
- import { resolveAgentModelAllowlist, resolveAgentPrimaryModel } from './agent-models.js';
9
+ import { resolveAgentModelAllowlist, resolveAgentDefaultModel } from './agent-models.js';
10
10
  import { createDeploymentSnapshotFromEnv, isInlineToolDeclaration, overlayToolRefPolicies, } from './deployment-snapshot.js';
11
11
  import { preloadGraphNotFound, preloadManifestNotFound, preloadSkillNotFound, preloadToolNotFound, } from './preload-errors.js';
12
12
  import { PreinstallDefinitionCache, resolveDefinitionCacheEnabled, } from './definition-cache.js';
13
- import { inspectAgentSkillTree, readSkillAssembly, resolveSkillHotReload, skillAssemblyStamp, } from './skill-tree.js';
14
- function applyPrimaryModelToGraph(graph, primary) {
15
- if (!primary)
13
+ import { inspectAgentSkillTree, listSkillDirs, readSkillAssembly, resolveSkillHotReload, skillAssemblyStamp, } from './skill-tree.js';
14
+ function applyDefaultModelToGraph(graph, defaultModel) {
15
+ if (!defaultModel)
16
16
  return graph;
17
17
  let changed = false;
18
18
  const nodes = graph.nodes.map((node) => {
@@ -22,7 +22,7 @@ function applyPrimaryModelToGraph(graph, primary) {
22
22
  if (current && current.toLowerCase() !== 'default')
23
23
  return node;
24
24
  changed = true;
25
- return { ...node, config: { ...node.config, model: primary } };
25
+ return { ...node, config: { ...node.config, model: defaultModel } };
26
26
  });
27
27
  return changed ? { ...graph, nodes } : graph;
28
28
  }
@@ -41,7 +41,7 @@ function assertGraphModelsAllowed(graph, manifest) {
41
41
  return graph;
42
42
  }
43
43
  function finalizeLoadedGraph(graph, manifest) {
44
- return assertGraphModelsAllowed(applyPrimaryModelToGraph(graph, resolveAgentPrimaryModel((manifest ?? {}))), manifest);
44
+ return assertGraphModelsAllowed(applyDefaultModelToGraph(graph, resolveAgentDefaultModel((manifest ?? {}))), manifest);
45
45
  }
46
46
  function asGraphDsl(value) {
47
47
  if (!value || typeof value !== 'object' || Array.isArray(value))
@@ -94,57 +94,8 @@ async function resolveSkillPrompt(manifest, bundlePath) {
94
94
  }
95
95
  return raw;
96
96
  }
97
- /**
98
- * 解析 Skill scripts[] 为虚拟 Tool 定义。
99
- * scripts 为可选/非标字段:Skill 包仅强制要求 skill.md,其余文件结构不限;
100
- * 存在 scripts 声明时才物化为可调用的本地 Tool。
101
- */
102
- function resolveSkillScripts(manifest, bundlePath) {
103
- const scripts = manifest.scripts;
104
- if (!Array.isArray(scripts))
105
- return [];
106
- const tools = [];
107
- for (const script of scripts) {
108
- if (!script || typeof script !== 'object')
109
- continue;
110
- const s = script;
111
- const name = s.name;
112
- const command = s.command;
113
- if (!name || !command)
114
- continue;
115
- const args = Array.isArray(s.args) ? s.args.map(String) : [];
116
- tools.push({
117
- toolId: `script:${name}`,
118
- name,
119
- manifest: {
120
- id: `script:${name}`,
121
- name,
122
- version: '0.0.0',
123
- type: 'code',
124
- status: 'active',
125
- description: s.description ?? `Skill script: ${name}`,
126
- execution: {
127
- runtime: command === 'python3' || command === 'python' ? 'python'
128
- : command === 'node' ? 'node'
129
- : 'executable',
130
- handler: args[0] ?? command,
131
- args: args.slice(1),
132
- timeout: 30000,
133
- sandbox: false,
134
- bundlePath,
135
- command,
136
- /** 标记为 Skill 虚拟脚本:Router 优先本机执行,不经 Gateway */
137
- localPreferred: true,
138
- source: 'skill-script',
139
- },
140
- },
141
- inputSchema: { type: 'object', properties: {}, additionalProperties: true },
142
- });
143
- }
144
- return tools;
145
- }
146
- function extractSkillToolRefs(manifest) {
147
- const refs = manifest.toolRefs ?? manifest.tools;
97
+ function extractSkillToolsets(manifest) {
98
+ const refs = manifest.toolsets;
148
99
  if (!Array.isArray(refs))
149
100
  return undefined;
150
101
  const out = [];
@@ -156,14 +107,15 @@ function extractSkillToolRefs(manifest) {
156
107
  if (!item || typeof item !== 'object')
157
108
  continue;
158
109
  const o = item;
110
+ const code = o.code != null ? String(o.code) : undefined;
111
+ const enabled = Array.isArray(o.enabled)
112
+ ? o.enabled.filter((x) => typeof x === 'string')
113
+ : undefined;
159
114
  out.push({
160
- toolId: o.toolId != null ? String(o.toolId) : undefined,
161
- name: o.name != null ? String(o.name) : o.toolset != null ? String(o.toolset) : undefined,
115
+ name: o.name != null ? String(o.name) : code,
162
116
  version: o.version != null ? String(o.version) : undefined,
163
- toolset: o.toolset != null ? String(o.toolset) : undefined,
164
- enabledTools: Array.isArray(o.enabledTools)
165
- ? o.enabledTools.filter((x) => typeof x === 'string')
166
- : undefined,
117
+ code,
118
+ enabled,
167
119
  autoApprove: Array.isArray(o.autoApprove)
168
120
  ? o.autoApprove.filter((x) => typeof x === 'string')
169
121
  : undefined,
@@ -305,7 +257,7 @@ function fromPreinstallTool(pre) {
305
257
  * 以工具集为管理单元:单个 MCP 服务 / 单个代码包 = 一个工具集,可含多个子工具;
306
258
  * 子工具继承集级 server/package 配置,合成各自的 execution。
307
259
  * 无 tools[] 时回退为单工具 manifest(tool.manifest.json 结构)。
308
- * enabledTools 为可选白名单(agent.manifest toolRefs);缺省启用全部子工具。
260
+ * enabled 为可选白名单(agent.manifest toolsets[].enabled);缺省启用全部子工具。
309
261
  */
310
262
  function expandPreinstallToolset(pre, options) {
311
263
  const m = pre.manifest;
@@ -659,7 +611,7 @@ export class GraphLoader {
659
611
  if (graphFromManifest && typeof graphFromManifest === 'object') {
660
612
  return finalizeLoadedGraph(yamlGraphToDsl(agentCode, graphFromManifest), manifest);
661
613
  }
662
- const model = resolveAgentPrimaryModel(manifest) ?? 'default';
614
+ const model = resolveAgentDefaultModel(manifest) ?? 'default';
663
615
  return {
664
616
  ...DEFAULT_GRAPH_DSL,
665
617
  name: agentCode,
@@ -851,7 +803,7 @@ export class GraphLoader {
851
803
  return definitions;
852
804
  }
853
805
  /**
854
- * 从预装快照加载已装配 Skill 完整定义(workflow / prompt / toolRefs)
806
+ * 加载已装配 Skill:SKILL.md 目录(Agent Skills)或可选 skill.manifest.json。
855
807
  * @param options.userId 用于解析 users/<userId>/skills(优先于 preinstall/skills)
856
808
  * @param options.allowMissingSkills 缺失时跳过而非抛错(warm 预热用)
857
809
  */
@@ -889,8 +841,7 @@ export class GraphLoader {
889
841
  asGraphDsl(pre.manifest.definitionSnapshot);
890
842
  // 解析 prompt:支持文件路径引用(./ 开头)
891
843
  const prompt = await resolveSkillPrompt(pre.manifest, pre.bundlePath);
892
- // 解析 scripts[] 为虚拟 Tool
893
- const scriptTools = resolveSkillScripts(pre.manifest, pre.bundlePath);
844
+ const entryFile = typeof pre.manifest.skillEntryFile === 'string' ? pre.manifest.skillEntryFile : undefined;
894
845
  skills.push({
895
846
  skillId: pre.skillId,
896
847
  name: pre.name,
@@ -898,10 +849,11 @@ export class GraphLoader {
898
849
  version: pre.version,
899
850
  workflow,
900
851
  prompt,
901
- toolRefs: extractSkillToolRefs(pre.manifest),
852
+ bundlePath: pre.bundlePath,
853
+ entryFile,
854
+ toolsets: extractSkillToolsets(pre.manifest),
902
855
  inputDef: pre.manifest.inputDef,
903
856
  outputDef: pre.manifest.outputDef,
904
- scriptTools: scriptTools.length > 0 ? scriptTools : undefined,
905
857
  });
906
858
  }
907
859
  else if (!this.strictPreinstall) {
@@ -940,16 +892,25 @@ export class GraphLoader {
940
892
  * 磁盘多出来的未声明目录不进选择清单;正文热加载仍看已装配目录的 mtime。
941
893
  */
942
894
  async resolveSkillRefs(snap, agentDir, userId) {
895
+ const byId = new Map();
896
+ const add = (ref) => {
897
+ if (ref.skillId && !byId.has(ref.skillId))
898
+ byId.set(ref.skillId, ref);
899
+ };
943
900
  if (agentDir) {
944
901
  const assembly = await readSkillAssembly(agentDir, userId);
945
902
  if (assembly && assembly.skills.length > 0) {
946
- return assembly.skills.map((skillId) => ({ skillId }));
903
+ for (const skillId of assembly.skills)
904
+ add({ skillId });
947
905
  }
948
906
  }
949
- const byId = new Map();
950
- for (const ref of snap?.skills ?? []) {
951
- if (ref.skillId)
952
- byId.set(ref.skillId, ref);
907
+ if (byId.size === 0) {
908
+ for (const ref of snap?.skills ?? [])
909
+ add(ref);
910
+ }
911
+ for (const root of this.localSkillRoots()) {
912
+ for (const skillId of await listSkillDirs(root))
913
+ add({ skillId });
953
914
  }
954
915
  return [...byId.values()];
955
916
  }
@@ -981,5 +942,7 @@ export class GraphLoader {
981
942
  }
982
943
  function resolveLocalSkillsDir(explicit) {
983
944
  const candidate = explicit?.trim() || process.env.EAP_LOCAL_SKILLS_DIR?.trim();
984
- return candidate || undefined;
945
+ if (candidate)
946
+ return path.resolve(candidate);
947
+ return path.resolve(process.cwd(), 'skills');
985
948
  }
@@ -1,4 +1,4 @@
1
- export { resolveAgentPrimaryModel, resolveAgentModelAllowlist, resolveDeclaredModels } from './agent-models.js';
1
+ export { resolveAgentDefaultModel, resolveAgentModelAllowlist, resolveDeclaredModels } from './agent-models.js';
2
2
  export { createAgentRegistryClient, type AgentRegistryClient, type AgentRegistryRecord, type AgentManifest } from './agent-registry-client.js';
3
3
  export { GraphLoader, type GraphLoaderConfig, type LoadToolDefinitionsOptions, type LoadAgentOptions } from './graph-loader.js';
4
4
  export { PreinstallDefinitionCache, resolveDefinitionCacheEnabled, type CachedAgentDefinitions, } from './definition-cache.js';
@@ -1,4 +1,4 @@
1
- export { resolveAgentPrimaryModel, resolveAgentModelAllowlist, resolveDeclaredModels } from './agent-models.js';
1
+ export { resolveAgentDefaultModel, resolveAgentModelAllowlist, resolveDeclaredModels } from './agent-models.js';
2
2
  export { createAgentRegistryClient } from './agent-registry-client.js';
3
3
  export { GraphLoader } from './graph-loader.js';
4
4
  export { PreinstallDefinitionCache, resolveDefinitionCacheEnabled, } from './definition-cache.js';
@@ -3,6 +3,9 @@ export interface AgentSkillTree {
3
3
  /** `${count}:${maxMtimeMs}`;增删改任一 skill 文件都会变 */
4
4
  stamp: string;
5
5
  }
6
+ /** Agent Skills 入口:目录内的 SKILL.md(规范名优先)。 */
7
+ export declare function findSkillEntryFile(bundlePath: string): Promise<string | null>;
8
+ export declare function listSkillDirs(root: string): Promise<string[]>;
6
9
  export interface SkillAssembly {
7
10
  skills: string[];
8
11
  source: string;
@@ -10,6 +10,7 @@ import { readdir, stat, access, readFile } from 'node:fs/promises';
10
10
  import { watch } from 'node:fs';
11
11
  import { constants as fsConstants } from 'node:fs';
12
12
  import path from 'node:path';
13
+ import { SKILL_ENTRY_CANDIDATES } from '@gt-fe/eap-contracts';
13
14
  const SKILL_CODE = /^[a-z0-9][a-z0-9-]*$/;
14
15
  async function exists(p) {
15
16
  try {
@@ -20,6 +21,43 @@ async function exists(p) {
20
21
  return false;
21
22
  }
22
23
  }
24
+ /** Agent Skills 入口:目录内的 SKILL.md(规范名优先)。 */
25
+ export async function findSkillEntryFile(bundlePath) {
26
+ for (const name of SKILL_ENTRY_CANDIDATES) {
27
+ const candidate = path.join(bundlePath, name);
28
+ if (await exists(candidate))
29
+ return candidate;
30
+ }
31
+ try {
32
+ const files = await readdir(bundlePath);
33
+ const hit = files.find((file) => file.toLowerCase() === 'skill.md');
34
+ return hit ? path.join(bundlePath, hit) : null;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ export async function listSkillDirs(root) {
41
+ let names = [];
42
+ try {
43
+ names = await readdir(root);
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ const codes = [];
49
+ for (const name of names) {
50
+ if (!SKILL_CODE.test(name))
51
+ continue;
52
+ const bundle = path.join(root, name);
53
+ const info = await stat(bundle).catch(() => undefined);
54
+ if (!info?.isDirectory())
55
+ continue;
56
+ if (await findSkillEntryFile(bundle))
57
+ codes.push(name);
58
+ }
59
+ return codes.sort();
60
+ }
23
61
  async function maxMtime(filePath, fallback = 0) {
24
62
  try {
25
63
  const info = await stat(filePath);
@@ -101,9 +139,9 @@ export async function inspectAgentSkillTree(agentDir, userId, extraRoots = []) {
101
139
  const bundleStat = await stat(bundle).catch(() => undefined);
102
140
  if (!bundleStat?.isDirectory())
103
141
  continue;
104
- const hasMain = (await exists(path.join(bundle, 'skill.md'))) ||
105
- (await exists(path.join(bundle, 'skill.manifest.json')));
106
- if (!hasMain)
142
+ const hasEntry = await findSkillEntryFile(bundle);
143
+ const hasOverlay = await exists(path.join(bundle, 'skill.manifest.json'));
144
+ if (!hasEntry && !hasOverlay)
107
145
  continue;
108
146
  codes.add(name);
109
147
  latest = Math.max(latest, bundleStat.mtimeMs);
@@ -14,9 +14,11 @@ import { HitlHandler } from '../graph/hitl/hitl-handler.js';
14
14
  import type { GraphLoader } from '../registry/graph-loader.js';
15
15
  import type { SessionContextService } from '../session/session-context.service.js';
16
16
  import type { MessageService } from '../session/message.service.js';
17
+ import type { SessionService, SessionRecord, MessageRecord } from '../session/session.service.js';
18
+ import { type ILlmTurnLog, type LlmTurnIndexEntry, type LlmTurnLogKind, type LlmTurnRecord } from '../debug/llm-turn-log.js';
17
19
  import type { SSEEvent } from '../sse/contracts/sse-emitter.contract.js';
18
20
  import type { TaskService } from '../task/task.service.js';
19
- import type { ExecuteParams, ExecuteResult, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
21
+ import type { ExecuteParams, ExecuteResult, ForkParams, ForkOverrides, ForkResult, HitlResponse, ResumeHitlResult, SeedSessionMessagesParams, SeedSessionMessagesResult } from './types.js';
20
22
  export interface ForkRelationRecord {
21
23
  originalTraceId: string;
22
24
  forkedTraceId: string;
@@ -34,6 +36,10 @@ export interface AgentRuntimeDeps {
34
36
  graphLoader?: GraphLoader;
35
37
  /** 多轮短期记忆写回;未注入时 AgentRuntime.stream 跳过持久化 */
36
38
  messageService?: MessageService;
39
+ /** 会话查询(getConversation);未注入时仅返回 messages/llmTurns */
40
+ sessionService?: SessionService;
41
+ /** 每步 LLM 入参/响应落盘;未注入时按环境默认 `.eap/llm-turns` */
42
+ turnLog?: ILlmTurnLog;
37
43
  /**
38
44
  * Hosting 注入的 Trace 采集器。与 ExecuteParams.traceId 同时存在时,
39
45
  * Skill/Tool 节点会 createNode/completeNode。不注入则只发 SSE,不写 Span。
@@ -41,6 +47,27 @@ export interface AgentRuntimeDeps {
41
47
  traceCollector?: ITraceCollector;
42
48
  recordForkRelation?: RecordForkRelation;
43
49
  }
50
+ /** 一次对话详情:消息 + 每步 LLM 入参/响应。File / 内存 / Postgres 同一套结构。 */
51
+ export interface ConversationDetail {
52
+ sessionId: string;
53
+ session: SessionRecord | null;
54
+ messages: MessageRecord[];
55
+ llmTurns: LlmTurnRecord[];
56
+ llmTurnIndex: LlmTurnIndexEntry[];
57
+ tokens: {
58
+ input: number;
59
+ output: number;
60
+ total: number;
61
+ byNode: Record<string, {
62
+ input: number;
63
+ output: number;
64
+ }>;
65
+ };
66
+ storage: {
67
+ llmTurnKind?: LlmTurnLogKind;
68
+ llmTurnDir?: string;
69
+ };
70
+ }
44
71
  /**
45
72
  * AgentRuntime — 管理 Graph 执行的 SDK 门面
46
73
  */
@@ -69,6 +96,17 @@ export declare class AgentRuntime {
69
96
  * @throws PARAMS_INVALID messageService 未注入(无存储无法写入)
70
97
  */
71
98
  seedSessionMessages(params: SeedSessionMessagesParams): Promise<SeedSessionMessagesResult>;
99
+ /**
100
+ * 查询一次对话详情:会话元数据、消息、每步 LLM 落盘(入参、响应、URL、模型、脱敏 sk、提示词)。
101
+ * 本地默认读 `.eap/sessions` + `.eap/llm-turns/{sessionId}/`。
102
+ */
103
+ getConversation(sessionId: string): Promise<ConversationDetail>;
104
+ listLlmTurns(sessionId: string): Promise<LlmTurnIndexEntry[]>;
105
+ getLlmTurn(sessionId: string, turnId: string): Promise<LlmTurnRecord | null>;
106
+ listConversations(options?: {
107
+ userId?: string;
108
+ }): Promise<SessionRecord[]>;
109
+ private resolveTurnLog;
72
110
  /** 同步执行 Graph(经 InvokeOrchestrator,内含 loadedTools 注入) */
73
111
  execute(params: ExecuteParams): Promise<ExecuteResult>;
74
112
  /**
@@ -92,9 +130,10 @@ export declare class AgentRuntime {
92
130
  */
93
131
  streamResumeHitl(taskId: string, response: HitlResponse): AsyncIterable<SSEEvent>;
94
132
  /**
95
- * 从 Checkpoint Fork 并真实重新执行
133
+ * 从 Checkpoint Fork 并真实重新执行。
134
+ * taskId / traceId 由上层发号;Runtime 不 createFlow、不另发 Task 号。
96
135
  */
97
- fork(checkpointId: string, overrides?: ForkOverrides): Promise<ForkResult>;
136
+ fork(params: ForkParams): Promise<ForkResult>;
98
137
  private prepareHitlResume;
99
138
  private rebuildExecutableGraph;
100
139
  private handleHitlTimeout;
@@ -8,7 +8,9 @@
8
8
  */
9
9
  import { randomUUID } from 'node:crypto';
10
10
  import { HitlAlreadyTimedOutError, HitlNotWaitingError, buildHitlWaitedPayload, isHitlDeadlinePassed, readPersistedHitlWaiting, unwrapHitlPaused, } from '../graph/hitl/hitl-handler.js';
11
- import { toLoadedSkillMap, mergeSkillScriptTools } from '../tool/system-prompt-builder.js';
11
+ import { toLoadedSkillMap } from '../tool/system-prompt-builder.js';
12
+ import { assembleLoadedTools } from '../tool/harness-primitives.js';
13
+ import { createFileLlmTurnLogFromEnv, } from '../debug/llm-turn-log.js';
12
14
  import { buildHitlWaitingTaskOutput, buildTurnGraphMessages, buildTurnTranscriptFromStreamEvents, extractHitlThreadId, extractStreamReasoning, extractStreamTextDelta, messageRecordsToGraphMessages, persistHitlWaitingPlaceholder, persistTurnTranscript, persistUserTurnMessage, } from '../session/persist-turn-messages.js';
13
15
  import { AppError, ErrorCodes } from '@gt-fe/eap-contracts';
14
16
  import { resolveExecutionContext } from '../governance/governance-client.js';
@@ -42,6 +44,7 @@ export class AgentRuntime {
42
44
  if (typeof this.deps.hitlHandler.setTimeoutListener === 'function') {
43
45
  this.deps.hitlHandler.setTimeoutListener((event) => this.handleHitlTimeout(event));
44
46
  }
47
+ this.deps.hitlHandler.setTraceCollector?.(this.deps.traceCollector);
45
48
  }
46
49
  /** 从 GraphLoader 解析 tool manifest 映射(invoke/stream/resume/fork 共用) */
47
50
  async resolveLoadedTools(agentCode, agentVersion) {
@@ -126,6 +129,64 @@ export class AgentRuntime {
126
129
  })));
127
130
  return { sessionId, seeded: true };
128
131
  }
132
+ /**
133
+ * 查询一次对话详情:会话元数据、消息、每步 LLM 落盘(入参、响应、URL、模型、脱敏 sk、提示词)。
134
+ * 本地默认读 `.eap/sessions` + `.eap/llm-turns/{sessionId}/`。
135
+ */
136
+ async getConversation(sessionId) {
137
+ const turnLog = this.resolveTurnLog();
138
+ const [session, messages, llmTurnIndex] = await Promise.all([
139
+ this.deps.sessionService
140
+ ? this.deps.sessionService.findSession(sessionId).catch(() => null)
141
+ : Promise.resolve(null),
142
+ this.deps.messageService
143
+ ? this.deps.messageService.getMessages(sessionId)
144
+ : Promise.resolve([]),
145
+ turnLog ? turnLog.list(sessionId) : Promise.resolve([]),
146
+ ]);
147
+ const llmTurns = [];
148
+ if (turnLog) {
149
+ for (const entry of llmTurnIndex) {
150
+ const record = await turnLog.read(sessionId, entry.id);
151
+ if (record)
152
+ llmTurns.push(record);
153
+ }
154
+ }
155
+ const tokens = summarizeTurnTokens(llmTurns);
156
+ return {
157
+ sessionId,
158
+ session,
159
+ messages,
160
+ llmTurns,
161
+ llmTurnIndex,
162
+ tokens,
163
+ storage: { llmTurnKind: turnLog?.kind, llmTurnDir: turnLog?.baseDir },
164
+ };
165
+ }
166
+ async listLlmTurns(sessionId) {
167
+ const turnLog = this.resolveTurnLog();
168
+ if (!turnLog)
169
+ return [];
170
+ return turnLog.list(sessionId);
171
+ }
172
+ async getLlmTurn(sessionId, turnId) {
173
+ const turnLog = this.resolveTurnLog();
174
+ if (!turnLog)
175
+ return null;
176
+ return turnLog.read(sessionId, turnId);
177
+ }
178
+ async listConversations(options) {
179
+ if (options?.userId && this.deps.sessionService) {
180
+ return this.deps.sessionService.listSessions(options.userId);
181
+ }
182
+ if (this.deps.sessionService) {
183
+ return this.deps.sessionService.listAllSessions();
184
+ }
185
+ return [];
186
+ }
187
+ resolveTurnLog() {
188
+ return this.deps.turnLog ?? createFileLlmTurnLogFromEnv();
189
+ }
129
190
  /** 同步执行 Graph(经 InvokeOrchestrator,内含 loadedTools 注入) */
130
191
  async execute(params) {
131
192
  const agentCode = resolveAgentCode(params);
@@ -203,8 +264,7 @@ export class AgentRuntime {
203
264
  })
204
265
  : undefined;
205
266
  const skillDefinitions = toLoadedSkillMap(loadedSkills);
206
- // Skill scripts 虚拟 Tool 并入 loadedTools(同名不覆盖平台 Tool)
207
- const loadedTools = mergeSkillScriptTools(loadedToolsBase, loadedSkills);
267
+ const loadedTools = assembleLoadedTools(loadedToolsBase);
208
268
  const promptRoots = this.deps.graphLoader
209
269
  ? await this.deps.graphLoader.resolvePromptRoots(agentCode, params.agentVersion)
210
270
  : [];
@@ -405,8 +465,8 @@ export class AgentRuntime {
405
465
  const agentVersion = (typeof resumedState.agentVersion === 'string' && resumedState.agentVersion) ||
406
466
  undefined;
407
467
  const graph = await this.rebuildExecutableGraph(agentCode, agentVersion);
408
- const loadedTools = resumedState.loadedTools ??
409
- (await this.resolveLoadedTools(agentCode, agentVersion));
468
+ const loadedTools = assembleLoadedTools(resumedState.loadedTools ??
469
+ (await this.resolveLoadedTools(agentCode, agentVersion)));
410
470
  const skillFromState = resumedState.skillDefinitions;
411
471
  const skillBundle = skillFromState
412
472
  ? {
@@ -536,16 +596,23 @@ export class AgentRuntime {
536
596
  }
537
597
  }
538
598
  /**
539
- * 从 Checkpoint Fork 并真实重新执行
599
+ * 从 Checkpoint Fork 并真实重新执行。
600
+ * taskId / traceId 由上层发号;Runtime 不 createFlow、不另发 Task 号。
540
601
  */
541
- async fork(checkpointId, overrides = {}) {
602
+ async fork(params) {
603
+ const { checkpointId, taskId, traceId } = params;
604
+ if (!taskId?.trim() || !traceId?.trim()) {
605
+ throw new AppError(ErrorCodes.PARAMS_INVALID, 'fork requires non-empty taskId and traceId from the caller', { exposeMessage: true });
606
+ }
607
+ const overrides = {
608
+ ...params.overrides,
609
+ executionContext: params.executionContext ?? params.overrides?.executionContext,
610
+ };
542
611
  const source = await this.deps.checkpointSaver.loadByCheckpointId(checkpointId);
543
612
  if (!source) {
544
613
  throw new AppError(ErrorCodes.HOSTING_CHECKPOINT_NOT_FOUND, `Checkpoint not found: ${checkpointId}`, { exposeMessage: true });
545
614
  }
546
- // 优先级:overrides(agentCode > 废弃 agentId)> Checkpoint state
547
615
  const agentCode = overrides.agentCode ??
548
- overrides.agentId ??
549
616
  source.state.agentCode ??
550
617
  source.state.agentId ??
551
618
  'unknown-agent';
@@ -556,8 +623,9 @@ export class AgentRuntime {
556
623
  source.checkpointId;
557
624
  const newThreadId = randomUUID();
558
625
  const forkedCheckpoint = await this.deps.checkpointSaver.fork(checkpointId, newThreadId, overrides);
559
- const correlationId = randomUUID();
626
+ const correlationId = overrides.executionContext?.correlationId ?? randomUUID();
560
627
  const task = await this.deps.taskService.create({
628
+ id: taskId,
561
629
  agentId: agentCode,
562
630
  sessionId,
563
631
  userId,
@@ -566,25 +634,12 @@ export class AgentRuntime {
566
634
  timeout: 600_000,
567
635
  });
568
636
  await this.deps.taskService.updateStatus(task.id, 'running');
569
- let newTraceId = randomUUID();
570
- if (this.deps.traceCollector) {
571
- newTraceId = await this.deps.traceCollector.createFlow({
572
- traceId: newTraceId,
573
- agentId: agentCode,
574
- agentCode,
575
- sessionId,
576
- userId,
577
- correlationId,
578
- callType: 'fork',
579
- metadata: { checkpointId, originalTraceId },
580
- });
581
- }
582
637
  const agentVersion = overrides.agentVersion ??
583
638
  (typeof source.state.agentVersion === 'string' ? source.state.agentVersion : undefined);
584
639
  const graph = await this.rebuildExecutableGraph(agentCode, agentVersion);
585
640
  const loadedToolsBase = await this.resolveLoadedTools(agentCode, agentVersion);
586
641
  const skillBundle = await this.resolveLoadedSkills(agentCode, userId, agentVersion);
587
- const loadedTools = mergeSkillScriptTools(loadedToolsBase, skillBundle.loadedSkills);
642
+ const loadedTools = assembleLoadedTools(loadedToolsBase);
588
643
  try {
589
644
  const result = await this.deps.graphEngine.invoke(graph, {
590
645
  ...forkedCheckpoint.state,
@@ -594,20 +649,26 @@ export class AgentRuntime {
594
649
  executionContext: overrides.executionContext,
595
650
  loadedTools,
596
651
  ...skillBundle,
597
- traceId: newTraceId,
652
+ taskId: task.id,
653
+ traceId,
598
654
  traceCollector: this.deps.traceCollector,
599
655
  });
600
656
  await this.deps.taskService.updateStatus(task.id, 'success', result.output);
601
657
  if (this.deps.recordForkRelation) {
602
- await this.deps.recordForkRelation({
603
- originalTraceId,
604
- forkedTraceId: newTraceId,
605
- checkpointId,
606
- overrides,
607
- });
658
+ try {
659
+ await this.deps.recordForkRelation({
660
+ originalTraceId,
661
+ forkedTraceId: traceId,
662
+ checkpointId,
663
+ overrides,
664
+ });
665
+ }
666
+ catch {
667
+ // Fork 关系上报失败不阻断执行结果
668
+ }
608
669
  }
609
670
  return {
610
- newTraceId,
671
+ newTraceId: traceId,
611
672
  originalTraceId,
612
673
  newTaskId: task.id,
613
674
  checkpointId,
@@ -618,14 +679,14 @@ export class AgentRuntime {
618
679
  output_tokens: result.tokenUsage.output,
619
680
  total_tokens: result.tokenUsage.total,
620
681
  },
621
- compareUrl: `/traces/compare?traceId1=${originalTraceId}&traceId2=${newTraceId}`,
682
+ compareUrl: `/traces/compare?traceId1=${originalTraceId}&traceId2=${traceId}`,
622
683
  };
623
684
  }
624
685
  catch (err) {
625
686
  const message = err instanceof Error ? err.message : 'Unknown error';
626
687
  await this.deps.taskService.updateStatus(task.id, 'failed', message);
627
688
  return {
628
- newTraceId,
689
+ newTraceId: traceId,
629
690
  originalTraceId,
630
691
  newTaskId: task.id,
631
692
  checkpointId,
@@ -771,6 +832,21 @@ export class AgentRuntime {
771
832
  };
772
833
  }
773
834
  }
835
+ function summarizeTurnTokens(turns) {
836
+ const byNode = {};
837
+ let input = 0;
838
+ let output = 0;
839
+ for (const turn of turns) {
840
+ input += turn.response.inputTokens ?? 0;
841
+ output += turn.response.outputTokens ?? 0;
842
+ const nodeId = turn.nodeId ?? 'llm';
843
+ const bucket = byNode[nodeId] ?? { input: 0, output: 0 };
844
+ bucket.input += turn.response.inputTokens ?? 0;
845
+ bucket.output += turn.response.outputTokens ?? 0;
846
+ byNode[nodeId] = bucket;
847
+ }
848
+ return { input, output, total: input + output, byNode };
849
+ }
774
850
  /** 工厂:从运行时依赖组装 AgentRuntime */
775
851
  export function createAgentRuntime(deps) {
776
852
  return new AgentRuntime(deps);
@@ -27,6 +27,7 @@ import { InvokeOrchestrator, StreamOrchestrator, SessionService, MessageService,
27
27
  import { type AgentRuntime } from './agent-runtime.js';
28
28
  import type { RuntimeStorageProfile } from './storage-profile.js';
29
29
  import type { HitlResponse, ResumeHitlResult, SSEEvent } from './types.js';
30
+ import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
30
31
  /** createDevRuntime 返回值(CLI 嵌入用,可选字段后续扩展) */
31
32
  export interface DevRuntime {
32
33
  invokeOrchestrator: InvokeOrchestrator;
@@ -39,6 +40,7 @@ export interface DevRuntime {
39
40
  streamResumeHitl: (taskId: string, response: HitlResponse) => AsyncIterable<SSEEvent>;
40
41
  sessionService: SessionService;
41
42
  messageService: MessageService;
43
+ turnLog?: ILlmTurnLog;
42
44
  sessionContextService: SessionContextService;
43
45
  taskService: TaskService;
44
46
  tokenStatsService: TokenStatsService;
@@ -53,9 +55,10 @@ export interface DevRuntime {
53
55
  llmDefaultModel: string;
54
56
  governanceKind: 'AllowAll' | 'Http';
55
57
  storageProfile: RuntimeStorageProfile;
56
- sessionStoreKind: 'file' | 'in-memory';
57
- messageStoreKind: 'memory' | 'file' | 'postgres';
58
- checkpointKind: 'memory' | 'file' | 'redis+pg' | 'pg';
58
+ sessionStoreKind: 'file' | 'hosting';
59
+ messageStoreKind: 'file' | 'postgres';
60
+ checkpointKind: 'file' | 'redis+pg';
61
+ llmTurnKind: 'file' | 'postgres';
59
62
  foundationConfigured: boolean;
60
63
  skillHotReload: boolean;
61
64
  };