@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
@@ -7,16 +7,63 @@
7
7
  * - tool_calls 解析(同步 & 流式)
8
8
  */
9
9
  import { resolveLlmModelName } from './resolve-llm-model.js';
10
+ import { createFileLlmTurnLogFromEnv, maskApiKey } from '../debug/llm-turn-log.js';
10
11
  export class HttpLLMProvider {
11
12
  apiKey;
12
13
  baseUrl;
13
14
  defaultModel;
14
15
  timeoutMs;
16
+ turnLog;
15
17
  constructor(config) {
16
18
  this.apiKey = config.apiKey;
17
19
  this.baseUrl = (config.baseUrl ?? 'https://api.openai.com/v1').replace(/\/$/, '');
18
20
  this.defaultModel = config.defaultModel ?? 'DeepSeek-V4-Pro';
19
21
  this.timeoutMs = config.timeoutMs ?? 60000;
22
+ this.turnLog = 'turnLog' in config ? config.turnLog : createFileLlmTurnLogFromEnv();
23
+ }
24
+ async writeTurn(args) {
25
+ if (!this.turnLog)
26
+ return;
27
+ const model = resolveLlmModelName(args.config.model, this.defaultModel);
28
+ try {
29
+ await this.turnLog.record({
30
+ ...args.config.turn,
31
+ url: args.url,
32
+ model,
33
+ temperature: args.config.temperature,
34
+ maxTokens: args.config.maxTokens,
35
+ apiKey: maskApiKey(this.apiKey),
36
+ systemPrompt: args.config.systemPrompt,
37
+ systemSections: args.config.systemSections?.map((section) => ({
38
+ id: section.id,
39
+ title: section.title,
40
+ chars: section.body.length,
41
+ body: section.body,
42
+ })),
43
+ tools: args.config.tools?.map((tool) => tool.function.name),
44
+ messages: args.messages.map((message) => ({
45
+ role: message.role,
46
+ content: message.content,
47
+ toolCallId: message.toolCallId,
48
+ toolCalls: message.toolCalls,
49
+ })),
50
+ response: {
51
+ content: args.response.content,
52
+ toolCalls: args.response.toolCalls,
53
+ reasoningContent: args.response.reasoningContent,
54
+ inputTokens: args.response.inputTokens,
55
+ outputTokens: args.response.outputTokens,
56
+ finishReason: args.response.finishReason,
57
+ error: args.response.error,
58
+ },
59
+ latencyMs: Date.now() - args.startedAt,
60
+ });
61
+ }
62
+ catch (err) {
63
+ if (process.env.EAP_LLM_DEBUG === '1') {
64
+ console.warn('[LLM-TURN] write failed', err);
65
+ }
66
+ }
20
67
  }
21
68
  async invoke(messages, config) {
22
69
  const body = this.buildRequestBody(messages, config, false);
@@ -52,6 +99,7 @@ export class HttpLLMProvider {
52
99
  const controller = new AbortController();
53
100
  const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
54
101
  const url = `${this.baseUrl}/chat/completions`;
102
+ const startedAt = Date.now();
55
103
  try {
56
104
  let response;
57
105
  try {
@@ -90,7 +138,7 @@ export class HttpLLMProvider {
90
138
  usage: data.usage,
91
139
  }, null, 2));
92
140
  }
93
- return {
141
+ const result = {
94
142
  content,
95
143
  toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
96
144
  ...(reasoningContent ? { reasoningContent } : {}),
@@ -99,6 +147,37 @@ export class HttpLLMProvider {
99
147
  // model 字段优先级:config(用户显式配置) > data.model(网关返回,可能是路由别名如 "/mnt/fast") > defaultModel
100
148
  model: config.model ?? (typeof data.model === 'string' && !data.model.includes('/') ? data.model : undefined) ?? this.defaultModel,
101
149
  };
150
+ await this.writeTurn({
151
+ url,
152
+ messages,
153
+ config,
154
+ startedAt,
155
+ response: {
156
+ content: result.content,
157
+ toolCalls: result.toolCalls,
158
+ reasoningContent: result.reasoningContent,
159
+ inputTokens: result.inputTokens,
160
+ outputTokens: result.outputTokens,
161
+ finishReason: choice?.finish_reason,
162
+ },
163
+ });
164
+ return result;
165
+ }
166
+ catch (err) {
167
+ await this.writeTurn({
168
+ url,
169
+ messages,
170
+ config,
171
+ startedAt,
172
+ response: {
173
+ content: '',
174
+ inputTokens: 0,
175
+ outputTokens: 0,
176
+ finishReason: 'error',
177
+ error: err instanceof Error ? err.message : String(err),
178
+ },
179
+ });
180
+ throw err;
102
181
  }
103
182
  finally {
104
183
  clearTimeout(timeout);
@@ -136,6 +215,13 @@ export class HttpLLMProvider {
136
215
  const controller = new AbortController();
137
216
  const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
138
217
  const url = `${this.baseUrl}/chat/completions`;
218
+ const startedAt = Date.now();
219
+ const contentChunks = [];
220
+ const reasoningChunks = [];
221
+ const toolCallAccumulators = new Map();
222
+ let inputTokens = 0;
223
+ let outputTokens = 0;
224
+ let lastFinishReason;
139
225
  try {
140
226
  let response;
141
227
  try {
@@ -163,9 +249,6 @@ export class HttpLLMProvider {
163
249
  const reader = response.body.getReader();
164
250
  const decoder = new TextDecoder();
165
251
  let buffer = '';
166
- const toolCallAccumulators = new Map();
167
- let inputTokens = 0;
168
- let outputTokens = 0;
169
252
  let usageEmitted = false;
170
253
  let model = config.model ?? this.defaultModel;
171
254
  while (true) {
@@ -200,6 +283,8 @@ export class HttpLLMProvider {
200
283
  }
201
284
  const delta = chunk.choices?.[0]?.delta;
202
285
  const finishReason = chunk.choices?.[0]?.finish_reason;
286
+ if (finishReason)
287
+ lastFinishReason = finishReason;
203
288
  // 累积 tool_calls delta
204
289
  if (delta?.tool_calls) {
205
290
  for (const tc of delta.tool_calls) {
@@ -223,6 +308,10 @@ export class HttpLLMProvider {
223
308
  // 产出内容 / reasoning chunk
224
309
  const content = delta?.content ?? '';
225
310
  const reasoningContent = typeof delta?.reasoning_content === 'string' ? delta.reasoning_content : '';
311
+ if (content)
312
+ contentChunks.push(content);
313
+ if (reasoningContent)
314
+ reasoningChunks.push(reasoningContent);
226
315
  const isEnd = finishReason === 'stop' || finishReason === 'tool_calls';
227
316
  const hasUsage = Boolean(chunk.usage);
228
317
  if (content || reasoningContent || isEnd) {
@@ -281,6 +370,42 @@ export class HttpLLMProvider {
281
370
  outputTokens,
282
371
  }, null, 2));
283
372
  }
373
+ await this.writeTurn({
374
+ url,
375
+ messages,
376
+ config,
377
+ startedAt,
378
+ response: {
379
+ content: contentChunks.join(''),
380
+ toolCalls: toolCallAccumulators.size > 0
381
+ ? this.finalizeToolCalls(toolCallAccumulators)
382
+ : undefined,
383
+ reasoningContent: reasoningChunks.length > 0 ? reasoningChunks.join('') : undefined,
384
+ inputTokens,
385
+ outputTokens,
386
+ finishReason: lastFinishReason,
387
+ },
388
+ });
389
+ }
390
+ catch (err) {
391
+ await this.writeTurn({
392
+ url,
393
+ messages,
394
+ config,
395
+ startedAt,
396
+ response: {
397
+ content: contentChunks.join(''),
398
+ toolCalls: toolCallAccumulators.size > 0
399
+ ? this.finalizeToolCalls(toolCallAccumulators)
400
+ : undefined,
401
+ reasoningContent: reasoningChunks.length > 0 ? reasoningChunks.join('') : undefined,
402
+ inputTokens,
403
+ outputTokens,
404
+ finishReason: 'error',
405
+ error: err instanceof Error ? err.message : String(err),
406
+ },
407
+ });
408
+ throw err;
284
409
  }
285
410
  finally {
286
411
  clearTimeout(timeout);
@@ -3,13 +3,17 @@
3
3
  * 连接表里只带 apiKeyEnv,密钥从 process.env 读取,不进 JSON。
4
4
  */
5
5
  import type { ILLMProvider } from '../graph/nodes/llm-node.js';
6
+ import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
7
+ import { type ModelCallDefaults } from '@gt-fe/eap-contracts';
6
8
  export interface LocalLlmConnection {
7
9
  baseUrl: string;
8
10
  apiKey: string;
11
+ defaults?: ModelCallDefaults;
9
12
  }
10
13
  export interface LocalLlmConnectionRef {
11
14
  baseUrl: string;
12
15
  apiKeyEnv: string;
16
+ defaults?: ModelCallDefaults;
13
17
  }
14
18
  export interface LocalLlmConnectionRefs {
15
19
  default?: LocalLlmConnectionRef;
@@ -24,4 +28,4 @@ export declare function parseLocalLlmConnectionRefs(env?: NodeJS.ProcessEnv): Lo
24
28
  export declare function hydrateLocalLlmConnections(refs: LocalLlmConnectionRefs, env?: NodeJS.ProcessEnv): LocalLlmConnections;
25
29
  export declare function requireLocalLlmConnections(env?: NodeJS.ProcessEnv): LocalLlmConnections;
26
30
  export declare function resolveLocalLlmConnection(connections: LocalLlmConnections, model?: string): LocalLlmConnection;
27
- export declare function createLocalLlmProvider(connections?: LocalLlmConnections): ILLMProvider;
31
+ export declare function createLocalLlmProvider(connections?: LocalLlmConnections, turnLog?: ILlmTurnLog): ILLMProvider;
@@ -2,6 +2,7 @@
2
2
  * 本地多模型 LLM:按节点 model 选网关。仅 dev。
3
3
  * 连接表里只带 apiKeyEnv,密钥从 process.env 读取,不进 JSON。
4
4
  */
5
+ import { mergeModelCallParams } from '@gt-fe/eap-contracts';
5
6
  import { HttpLLMProvider } from './http-llm-provider.js';
6
7
  export const LLM_CONNECTIONS_ENV = 'EAP_LLM_CONNECTIONS';
7
8
  export function parseLocalLlmConnectionRefs(env = process.env) {
@@ -41,7 +42,7 @@ export function hydrateLocalLlmConnections(refs, env = process.env) {
41
42
  const apiKey = env[ref.apiKeyEnv]?.trim();
42
43
  if (!apiKey)
43
44
  return undefined;
44
- return { baseUrl: ref.baseUrl, apiKey };
45
+ return { baseUrl: ref.baseUrl, apiKey, defaults: ref.defaults };
45
46
  };
46
47
  const byModel = {};
47
48
  for (const [name, ref] of Object.entries(refs.byModel ?? {})) {
@@ -73,20 +74,26 @@ export function resolveLocalLlmConnection(connections, model) {
73
74
  }
74
75
  return connection;
75
76
  }
76
- export function createLocalLlmProvider(connections = requireLocalLlmConnections()) {
77
- return new LocalLlmProvider(connections);
77
+ export function createLocalLlmProvider(connections = requireLocalLlmConnections(), turnLog) {
78
+ return new LocalLlmProvider(connections, turnLog);
78
79
  }
79
80
  class LocalLlmProvider {
80
81
  connections;
82
+ turnLog;
81
83
  cache = new Map();
82
- constructor(connections) {
84
+ constructor(connections, turnLog) {
83
85
  this.connections = connections;
86
+ this.turnLog = turnLog;
84
87
  }
85
88
  invoke(messages, config) {
86
- return this.providerFor(config.model).invoke(messages, config);
89
+ const connection = resolveLocalLlmConnection(this.connections, config.model);
90
+ const merged = mergeModelCallParams(config, connection.defaults);
91
+ return this.providerFor(config.model).invoke(messages, { ...config, ...merged });
87
92
  }
88
93
  stream(messages, config) {
89
- return this.providerFor(config.model).stream(messages, config);
94
+ const connection = resolveLocalLlmConnection(this.connections, config.model);
95
+ const merged = mergeModelCallParams(config, connection.defaults);
96
+ return this.providerFor(config.model).stream(messages, { ...config, ...merged });
90
97
  }
91
98
  providerFor(model) {
92
99
  const connection = resolveLocalLlmConnection(this.connections, model);
@@ -98,6 +105,7 @@ class LocalLlmProvider {
98
105
  apiKey: connection.apiKey,
99
106
  baseUrl: connection.baseUrl,
100
107
  defaultModel: model,
108
+ turnLog: this.turnLog,
101
109
  });
102
110
  this.cache.set(cacheKey, provider);
103
111
  return provider;
@@ -13,16 +13,20 @@
13
13
  */
14
14
  import type { GraphMessage } from '../graph/contracts/executable-graph.js';
15
15
  import type { ILLMProvider, LLMNodeConfig, LLMResponse, LLMChunk } from '../graph/nodes/llm-node.js';
16
+ import type { ILlmTurnLog } from '../debug/llm-turn-log.js';
16
17
  export interface RoutingLLMProviderConfig {
17
18
  foundationUrl: string;
18
19
  fallbackProvider?: ILLMProvider;
20
+ turnLog?: ILlmTurnLog;
19
21
  }
20
22
  export declare class RoutingLLMProvider implements ILLMProvider {
21
23
  private readonly foundationUrl;
22
24
  private readonly fallback;
25
+ private readonly turnLog?;
23
26
  constructor(config: RoutingLLMProviderConfig);
24
27
  invoke(messages: GraphMessage[], config: LLMNodeConfig): Promise<LLMResponse>;
25
28
  stream(messages: GraphMessage[], config: LLMNodeConfig): AsyncIterableIterator<LLMChunk>;
29
+ private withCallDefaults;
26
30
  private selectModel;
27
31
  private createProviderForModel;
28
32
  }
@@ -11,24 +11,31 @@
11
11
  * - 支持任意 OpenAI 兼容 API(OpenAI / DeepSeek / 本地 Ollama 等)
12
12
  * - 连接配置由 Foundation 统一管理,Runtime 无感知
13
13
  */
14
+ import { mergeModelCallParams } from '@gt-fe/eap-contracts';
14
15
  import { EchoLLMProvider } from './echo-llm-provider.js';
15
16
  import { HttpLLMProvider } from './http-llm-provider.js';
16
17
  export class RoutingLLMProvider {
17
18
  foundationUrl;
18
19
  fallback;
20
+ turnLog;
19
21
  constructor(config) {
20
22
  this.foundationUrl = config.foundationUrl.replace(/\/$/, '');
21
23
  this.fallback = config.fallbackProvider ?? new EchoLLMProvider();
24
+ this.turnLog = config.turnLog;
22
25
  }
23
26
  async invoke(messages, config) {
24
27
  const selected = await this.selectModel(config);
25
28
  const provider = this.createProviderForModel(selected);
26
- return provider.invoke(messages, { ...config, model: selected.name });
29
+ return provider.invoke(messages, this.withCallDefaults(config, selected));
27
30
  }
28
31
  async *stream(messages, config) {
29
32
  const selected = await this.selectModel(config);
30
33
  const provider = this.createProviderForModel(selected);
31
- yield* provider.stream(messages, { ...config, model: selected.name });
34
+ yield* provider.stream(messages, this.withCallDefaults(config, selected));
35
+ }
36
+ withCallDefaults(config, selected) {
37
+ const merged = mergeModelCallParams(config, selected.defaults);
38
+ return { ...config, ...merged, model: selected.name };
32
39
  }
33
40
  async selectModel(config) {
34
41
  try {
@@ -47,6 +54,7 @@ export class RoutingLLMProvider {
47
54
  name: body.data.name,
48
55
  provider: body.data.provider,
49
56
  connection: body.data.connection ?? null,
57
+ defaults: body.data.defaults ?? null,
50
58
  };
51
59
  }
52
60
  }
@@ -64,6 +72,7 @@ export class RoutingLLMProvider {
64
72
  baseUrl: selected.connection.baseUrl,
65
73
  defaultModel: selected.name,
66
74
  timeoutMs: selected.connection.timeoutMs,
75
+ turnLog: this.turnLog,
67
76
  });
68
77
  }
69
78
  // Fallback: 兼容旧逻辑(直接读 env)—— 开发环境快速启动用
@@ -72,6 +81,7 @@ export class RoutingLLMProvider {
72
81
  apiKey: process.env.OPENAI_API_KEY,
73
82
  baseUrl: process.env.OPENAI_BASE_URL,
74
83
  defaultModel: selected.name,
84
+ turnLog: this.turnLog,
75
85
  });
76
86
  }
77
87
  return this.fallback;
@@ -1 +1 @@
1
- export { resolveDeclaredModels, resolveAgentPrimaryModel, resolveAgentModelAllowlist, } from '@gt-fe/eap-contracts';
1
+ export { resolveDeclaredModels, resolveAgentDefaultModel, resolveAgentModelAllowlist, } from '@gt-fe/eap-contracts';
@@ -1 +1 @@
1
- export { resolveDeclaredModels, resolveAgentPrimaryModel, resolveAgentModelAllowlist, } from '@gt-fe/eap-contracts';
1
+ export { resolveDeclaredModels, resolveAgentDefaultModel, resolveAgentModelAllowlist, } from '@gt-fe/eap-contracts';
@@ -12,8 +12,8 @@ export interface AgentManifest {
12
12
  skills: string[];
13
13
  prompts: Record<string, string>;
14
14
  models?: {
15
- primary: string;
16
- alternatives?: string[];
15
+ default: string;
16
+ allowed?: string[];
17
17
  };
18
18
  config?: {
19
19
  timeout?: number;
@@ -7,7 +7,7 @@
7
7
  * {root}/agents/<agentCode>/<version>/users/<userId>/skills/<skillCode>/
8
8
  *
9
9
  * 命名:agentCode / toolsetCode / skillCode / toolName 均为语义化小写标识;
10
- * Skill 目录无版本层(维护形态为 ZIP 上传整包替换,包内必含 skill.md)。
10
+ * Skill 目录无版本层:包内必含 SKILL.md(Agent Skills)。
11
11
  */
12
12
  export interface SnapshotToolRef {
13
13
  toolId?: string;
@@ -17,7 +17,7 @@ export interface SnapshotToolRef {
17
17
  sha256?: string;
18
18
  /**
19
19
  * 可选白名单:列出则仅启用所列子工具;省略 = 启用该 toolset 下全部子工具。
20
- * @see agent.manifest toolRefs[].enabledTools
20
+ * @see agent.manifest toolsets[].enabled
21
21
  */
22
22
  enabledTools?: string[];
23
23
  /** 免 HITL 确认的子工具名(覆盖子工具 requireConfirmation) */
@@ -74,12 +74,12 @@ export interface IDeploymentSnapshot {
74
74
  /** 规范化摘要:去 sha256: 前缀、空白,转小写 hex */
75
75
  export declare function normalizeDigest(value: unknown): string | null;
76
76
  /**
77
- * 装配引用(toolRefs / 简写 tools[])vs 内联完整 ToolManifest。
77
+ * 装配引用 vs 内联完整 ToolManifest。
78
78
  * 含 type/execution 的视为内联定义;仅 toolset/name/version/enabledTools 等视为引用。
79
79
  */
80
80
  export declare function isInlineToolDeclaration(item: unknown): boolean;
81
81
  /**
82
- * 将 agent.manifest.toolRefs 上的 enabledTools/autoApprove 叠到 snapshot.tools[](snapshot 未带时)。
82
+ * 将 agent.manifest.toolsets 上的 enabled/autoApprove 叠到 snapshot.tools[](snapshot 未带时)。
83
83
  */
84
84
  export declare function overlayToolRefPolicies(snapTools: SnapshotToolRef[], manifest?: Record<string, unknown>): SnapshotToolRef[];
85
85
  export declare class FileSystemDeploymentSnapshot implements IDeploymentSnapshot {
@@ -7,12 +7,14 @@
7
7
  * {root}/agents/<agentCode>/<version>/users/<userId>/skills/<skillCode>/
8
8
  *
9
9
  * 命名:agentCode / toolsetCode / skillCode / toolName 均为语义化小写标识;
10
- * Skill 目录无版本层(维护形态为 ZIP 上传整包替换,包内必含 skill.md)。
10
+ * Skill 目录无版本层:包内必含 SKILL.md(Agent Skills)。
11
11
  */
12
12
  import { readdir, readFile, access } from 'node:fs/promises';
13
13
  import { createHash } from 'node:crypto';
14
14
  import path from 'node:path';
15
15
  import { constants as fsConstants } from 'node:fs';
16
+ import { parseSkillMd } from '@gt-fe/eap-contracts';
17
+ import { findSkillEntryFile } from './skill-tree.js';
16
18
  import { preloadMissing, preloadPackageCorrupt, } from './preload-errors.js';
17
19
  import { isRequireChecksum, resolvePreloadBehaviorFlags, } from './preload-flags.js';
18
20
  async function pathExists(p) {
@@ -139,7 +141,7 @@ async function pickLatestVersionDir(parent) {
139
141
  return path.join(parent, dirs[dirs.length - 1]);
140
142
  }
141
143
  function skillRefsFromManifest(manifest) {
142
- const raw = manifest.skillRefs ?? manifest.skills;
144
+ const raw = manifest.skills;
143
145
  if (!Array.isArray(raw))
144
146
  return [];
145
147
  const out = [];
@@ -150,7 +152,7 @@ function skillRefsFromManifest(manifest) {
150
152
  }
151
153
  if (item && typeof item === 'object') {
152
154
  const o = item;
153
- const skillId = String(o.skill ?? o.skillId ?? o.skillCode ?? o.id ?? '');
155
+ const skillId = String(o.code ?? '');
154
156
  if (!skillId)
155
157
  continue;
156
158
  out.push({
@@ -171,7 +173,7 @@ function parseStringList(value) {
171
173
  return out.length > 0 ? out : undefined;
172
174
  }
173
175
  /**
174
- * 装配引用(toolRefs / 简写 tools[])vs 内联完整 ToolManifest。
176
+ * 装配引用 vs 内联完整 ToolManifest。
175
177
  * 含 type/execution 的视为内联定义;仅 toolset/name/version/enabledTools 等视为引用。
176
178
  */
177
179
  export function isInlineToolDeclaration(item) {
@@ -189,7 +191,7 @@ function parseToolAssemblyRef(item) {
189
191
  if (isInlineToolDeclaration(item))
190
192
  return null;
191
193
  const o = item;
192
- const name = String(o.toolset ?? o.name ?? o.toolId ?? o.id ?? '');
194
+ const name = String(o.code ?? o.name ?? '');
193
195
  if (!name)
194
196
  return null;
195
197
  return {
@@ -197,14 +199,13 @@ function parseToolAssemblyRef(item) {
197
199
  toolId: o.toolId != null ? String(o.toolId) : undefined,
198
200
  version: o.version != null ? String(o.version) : undefined,
199
201
  sha256: typeof o.sha256 === 'string' ? o.sha256 : undefined,
200
- enabledTools: parseStringList(o.enabledTools),
202
+ enabledTools: parseStringList(o.enabled),
201
203
  autoApprove: parseStringList(o.autoApprove),
202
204
  };
203
205
  }
204
206
  /**
205
- * 从 agent.manifest 收集工具集装配引用:优先 toolRefs[],再合并简写 tools[](非内联项)。
206
- */
207
- function toolRefsFromManifest(manifest) {
207
+ /** 从 agent.manifest.toolsets 收集工具集装配引用。 */
208
+ function toolsetsFromManifest(manifest) {
208
209
  const out = [];
209
210
  const byName = new Map();
210
211
  const upsert = (ref) => {
@@ -214,7 +215,6 @@ function toolRefsFromManifest(manifest) {
214
215
  out.push(ref);
215
216
  return;
216
217
  }
217
- // 后写补全白名单等字段,不覆盖已有显式值
218
218
  if (!prev.enabledTools && ref.enabledTools)
219
219
  prev.enabledTools = ref.enabledTools;
220
220
  if (!prev.autoApprove && ref.autoApprove)
@@ -226,12 +226,7 @@ function toolRefsFromManifest(manifest) {
226
226
  if (!prev.toolId && ref.toolId)
227
227
  prev.toolId = ref.toolId;
228
228
  };
229
- for (const item of Array.isArray(manifest.toolRefs) ? manifest.toolRefs : []) {
230
- const ref = parseToolAssemblyRef(item);
231
- if (ref)
232
- upsert(ref);
233
- }
234
- for (const item of Array.isArray(manifest.tools) ? manifest.tools : []) {
229
+ for (const item of Array.isArray(manifest.toolsets) ? manifest.toolsets : []) {
235
230
  const ref = parseToolAssemblyRef(item);
236
231
  if (ref)
237
232
  upsert(ref);
@@ -239,12 +234,12 @@ function toolRefsFromManifest(manifest) {
239
234
  return out;
240
235
  }
241
236
  /**
242
- * 将 agent.manifest.toolRefs 上的 enabledTools/autoApprove 叠到 snapshot.tools[](snapshot 未带时)。
237
+ * 将 agent.manifest.toolsets 上的 enabled/autoApprove 叠到 snapshot.tools[](snapshot 未带时)。
243
238
  */
244
239
  export function overlayToolRefPolicies(snapTools, manifest) {
245
240
  if (!manifest)
246
241
  return snapTools;
247
- const fromManifest = toolRefsFromManifest(manifest);
242
+ const fromManifest = toolsetsFromManifest(manifest);
248
243
  if (fromManifest.length === 0)
249
244
  return snapTools;
250
245
  const byName = new Map(fromManifest.map((r) => [r.name, r]));
@@ -259,6 +254,12 @@ export function overlayToolRefPolicies(snapTools, manifest) {
259
254
  };
260
255
  });
261
256
  }
257
+ function nonemptyRefs(refs) {
258
+ return refs && refs.length > 0 ? refs : undefined;
259
+ }
260
+ function nonemptySkillRefs(refs) {
261
+ return refs && refs.length > 0 ? refs : undefined;
262
+ }
262
263
  function normalizeSnapshotToolRefs(raw) {
263
264
  if (!Array.isArray(raw))
264
265
  return undefined;
@@ -281,7 +282,7 @@ function normalizeSnapshotSkillRefs(raw) {
281
282
  }
282
283
  if (item && typeof item === 'object') {
283
284
  const o = item;
284
- const skillId = String(o.skill ?? o.skillId ?? o.code ?? o.skillCode ?? o.id ?? '');
285
+ const skillId = String(o.code ?? o.skillId ?? '');
285
286
  if (!skillId)
286
287
  continue;
287
288
  out.push({
@@ -417,8 +418,8 @@ export class FileSystemDeploymentSnapshot {
417
418
  return {
418
419
  agentCode,
419
420
  version: String(snap.version ?? resolvedVersion),
420
- tools: normalizeSnapshotToolRefs(snap.tools) ?? toolRefsFromManifest(manifest ?? {}),
421
- skills: normalizeSnapshotSkillRefs(snap.skills) ?? skillRefsFromManifest(manifest ?? {}),
421
+ tools: nonemptyRefs(normalizeSnapshotToolRefs(snap.tools)) ?? toolsetsFromManifest(manifest ?? {}),
422
+ skills: nonemptySkillRefs(normalizeSnapshotSkillRefs(snap.skills)) ?? skillRefsFromManifest(manifest ?? {}),
422
423
  graph,
423
424
  agentDir,
424
425
  manifest: manifest ?? undefined,
@@ -428,7 +429,7 @@ export class FileSystemDeploymentSnapshot {
428
429
  return {
429
430
  agentCode,
430
431
  version: resolvedVersion,
431
- tools: toolRefsFromManifest(manifest ?? {}),
432
+ tools: toolsetsFromManifest(manifest ?? {}),
432
433
  skills: skillRefsFromManifest(manifest ?? {}),
433
434
  graph,
434
435
  agentDir,
@@ -497,10 +498,9 @@ export class FileSystemDeploymentSnapshot {
497
498
  if (!(await pathExists(bundlePath)))
498
499
  continue;
499
500
  await this.assertReadyIfRequired(bundlePath, `skill ${ref.skillId}`);
501
+ const entryFile = await findSkillEntryFile(bundlePath);
500
502
  const manifestPathCandidates = [
501
503
  path.join(bundlePath, 'skill.manifest.json'),
502
- path.join(bundlePath, 'skill.definition.json'),
503
- path.join(bundlePath, 'manifest.json'),
504
504
  ];
505
505
  let manifest = null;
506
506
  let manifestPath = null;
@@ -512,22 +512,44 @@ export class FileSystemDeploymentSnapshot {
512
512
  break;
513
513
  }
514
514
  }
515
+ if (!manifest && entryFile) {
516
+ const parsed = parseSkillMd(await readFile(entryFile, 'utf8'));
517
+ if (!parsed)
518
+ continue;
519
+ manifest = {
520
+ code: parsed.frontmatter.name,
521
+ name: parsed.frontmatter.name,
522
+ description: parsed.frontmatter.description,
523
+ ...(parsed.frontmatter.license ? { license: parsed.frontmatter.license } : {}),
524
+ };
525
+ manifestPath = entryFile;
526
+ }
515
527
  if (!manifest || !manifestPath)
516
528
  continue;
517
- // 用户装配层跳过 sha256 校验(用户覆盖版本与预装快照散列不同是正常行为)
529
+ if (!manifest.description && entryFile) {
530
+ const parsed = parseSkillMd(await readFile(entryFile, 'utf8'));
531
+ if (parsed?.frontmatter.description) {
532
+ manifest = { ...manifest, description: parsed.frontmatter.description };
533
+ if (!manifest.name)
534
+ manifest.name = parsed.frontmatter.name;
535
+ }
536
+ }
518
537
  const isUserOverridePath = bundlePath.includes('/users/') || bundlePath.includes('\\users\\');
519
538
  const isAuthorOverlay = extraRoots.some((root) => root && isPathInside(root, bundlePath));
520
- if (ref.sha256 && !isUserOverridePath && !isAuthorOverlay) {
539
+ if (ref.sha256 && !isUserOverridePath && !isAuthorOverlay && manifestPath.endsWith('.json')) {
521
540
  const actual = await sha256OfFile(manifestPath);
522
541
  await assertDigestMatch(ref.sha256, actual, `skill ${ref.skillId}`, { path: manifestPath });
523
542
  }
524
- const skillId = String(manifest.code ?? manifest.skillId ?? manifest.skillCode ?? manifest.id ?? ref.skillId);
543
+ const skillId = String(manifest.code ?? ref.skillId);
525
544
  return {
526
545
  skillId,
527
546
  name: String(manifest.name ?? ref.name ?? skillId),
528
547
  version: String(manifest.version ?? ref.version ?? path.basename(bundlePath)),
529
548
  description: manifest.description ?? ref.description,
530
- manifest,
549
+ manifest: {
550
+ ...manifest,
551
+ ...(entryFile ? { skillEntryFile: entryFile } : {}),
552
+ },
531
553
  bundlePath,
532
554
  };
533
555
  }
@@ -111,7 +111,7 @@ export declare class GraphLoader {
111
111
  loadToolDefinitions(agentCode: string, options?: LoadToolDefinitionsOptions): Promise<LoadedToolDefinition[]>;
112
112
  private loadToolDefinitionsFromSource;
113
113
  /**
114
- * 从预装快照加载已装配 Skill 完整定义(workflow / prompt / toolRefs)
114
+ * 加载已装配 Skill:SKILL.md 目录(Agent Skills)或可选 skill.manifest.json。
115
115
  * @param options.userId 用于解析 users/<userId>/skills(优先于 preinstall/skills)
116
116
  * @param options.allowMissingSkills 缺失时跳过而非抛错(warm 预热用)
117
117
  */