@actiondock/core 2.0.9 → 2.0.11-beta.0

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.
@@ -15,24 +15,78 @@ function renderActionListMarkdown(
15
15
  return actions
16
16
  .map((a) => {
17
17
  const aDesc = a.description ? ` - ${a.description}` : "";
18
- let params = "";
18
+ const idLabel = options.packageId
19
+ ? `\`${options.packageId}/${a.id}\` (或 \`${a.id}\`)`
20
+ : `\`${a.id}\``;
21
+
22
+ const lines: string[] = [`- ${idLabel}${aDesc}`];
23
+
24
+ // 标注元数据解析
25
+ if (a.annotations && typeof a.annotations === "object") {
26
+ const annoList: string[] = [];
27
+ if ((a.annotations as any).readOnly === true) {
28
+ annoList.push("只读操作");
29
+ }
30
+ if ((a.annotations as any).destructive === true) {
31
+ annoList.push("破坏性操作(执行前须向用户确认)");
32
+ }
33
+ if (annoList.length > 0) {
34
+ lines.push(` - 属性标注: ${annoList.join(", ")}`);
35
+ }
36
+ }
37
+
38
+ // 输入参数模式解析
19
39
  if (
20
40
  a.inputSchema &&
21
41
  typeof a.inputSchema === "object" &&
22
42
  (a.inputSchema as any).properties
23
43
  ) {
24
- const props = Object.keys((a.inputSchema as any).properties);
25
- const req = (a.inputSchema as any).required || [];
26
- params = `\n - 参数列表: ${props
27
- .map((p) => (req.includes(p) ? `\`${p}\` (必填)` : `\`${p}\``))
28
- .join(", ")}`;
44
+ const props = (a.inputSchema as any).properties as Record<string, any>;
45
+ const req = ((a.inputSchema as any).required || []) as string[];
46
+ const propKeys = Object.keys(props);
47
+
48
+ if (propKeys.length > 0) {
49
+ lines.push(" - 输入参数:");
50
+ for (const k of propKeys) {
51
+ const p = props[k] || {};
52
+ const typeStr = p.type ? `\`${p.type}\`` : "`any`";
53
+ const reqStr = req.includes(k) ? ", 必填" : "";
54
+ const descStr = p.description ? `: ${p.description}` : "";
55
+ const defStr =
56
+ p.default !== undefined
57
+ ? ` (默认值: \`${JSON.stringify(p.default)}\`)`
58
+ : "";
59
+ lines.push(` - \`${k}\` (${typeStr}${reqStr})${descStr}${defStr}`);
60
+ }
61
+ } else {
62
+ lines.push(" - 输入参数: 无");
63
+ }
64
+ } else {
65
+ lines.push(" - 输入参数: 无");
29
66
  }
30
- const idLabel = options.packageId
31
- ? `\`${options.packageId}/${a.id}\` (或 \`${a.id}\`)`
32
- : `\`${a.id}\``;
33
- return `- ${idLabel}${aDesc}${params}`;
67
+
68
+ // 输出字段模式解析
69
+ if (
70
+ a.outputSchema &&
71
+ typeof a.outputSchema === "object" &&
72
+ (a.outputSchema as any).properties
73
+ ) {
74
+ const outProps = (a.outputSchema as any).properties as Record<string, any>;
75
+ const outKeys = Object.keys(outProps);
76
+ if (outKeys.length > 0) {
77
+ lines.push(" - 输出字段:");
78
+ for (const k of outKeys) {
79
+ const p = outProps[k] || {};
80
+ const typeStr = p.type ? `\`${p.type}\`` : "`any`";
81
+ const descStr = p.description ? `: ${p.description}` : "";
82
+ lines.push(` - \`${k}\` (${typeStr})${descStr}`);
83
+ }
84
+ }
85
+ }
86
+
87
+ return lines.join("\n");
34
88
  })
35
- .join("\n");
89
+ .join("\n\n");
36
90
  }
37
91
 
38
92
  function renderPlaybookSectionMarkdown(playbooks: PlaybookDefinition[]): string {
@@ -40,13 +94,16 @@ function renderPlaybookSectionMarkdown(playbooks: PlaybookDefinition[]): string
40
94
  const list = playbooks
41
95
  .map((p) => {
42
96
  const rel = `./playbooks/${basename(p.filePath)}`;
43
- return `- **${p.id}** (\`${rel}\`): ${p.description || "任务指南"}`;
97
+ return `- **${p.id}** (\`${rel}\`): ${p.description || "业务操作指南"}`;
44
98
  })
45
99
  .join("\n");
46
100
  return `
47
- ## 任务指南 (Playbook SOPs)
101
+ ## 业务操作规程
102
+
103
+ > [!IMPORTANT]
104
+ > **规程优先准则**:当处理复合业务任务时,智能体必须优先检查是否存在匹配场景的 Playbook。若存在规程,必须优先查阅并严格遵循规程界定的步骤时序与校验逻辑推进,严禁无序拼凑调用底层 Action。
48
105
 
49
- Playbook 为复杂任务提供逐步操作规程。详细 SOP 请阅读对应 Markdown 文档:
106
+ Playbook SOPs 为复杂业务任务提供逐步指导规程。详细规程请查阅对应文档:
50
107
 
51
108
  ${list}
52
109
  `;
@@ -61,8 +118,8 @@ export function generateSourceSkillMd(
61
118
  const pkgId = config.id;
62
119
  const firstAction = actions[0]?.id || "sample.greet";
63
120
 
64
- const actionListMd = renderActionListMarkdown(actions, { packageId: pkgId });
65
121
  const playbookSection = renderPlaybookSectionMarkdown(playbooks);
122
+ const actionListMd = renderActionListMarkdown(actions, { packageId: pkgId });
66
123
 
67
124
  return `---
68
125
  name: ${cleanName}
@@ -75,7 +132,7 @@ ${desc}
75
132
 
76
133
  ## ActionDock 运行时
77
134
 
78
- 本技能为 **ActionDock 源码型技能包**。AI Agent 可直接通过已安装的 ActionDock 命令行工具 (\`ad\`) 执行其中的 Action。
135
+ 本技能为 **ActionDock 源码型技能包**。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
79
136
 
80
137
  ### 注册与链接
81
138
 
@@ -87,38 +144,75 @@ ad link "<skill_root>"
87
144
 
88
145
  > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。
89
146
 
147
+ ### 动作参数契约按需调阅
148
+
149
+ 在调用未知参数的 Action 前,可在终端执行命令按需查阅该 Action 的输入输出模式与详细说明:
150
+
151
+ \`\`\`bash
152
+ ad action show ${pkgId}/${firstAction}
153
+ \`\`\`
154
+
90
155
  ### 执行 Action
91
156
 
92
- 为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID
157
+ 为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID
158
+
159
+ 推荐最佳实践:使用文件传递参数,杜绝终端引号转义问题:
160
+
161
+ \`\`\`bash
162
+ # 写入参数到临时文件并通过 --input-file 传递
163
+ cat << 'EOF' > /tmp/input.json
164
+ {
165
+ "param": "value"
166
+ }
167
+ EOF
168
+ ad run ${pkgId}/${firstAction} --input-file /tmp/input.json
169
+ \`\`\`
170
+
171
+ 亦可通过内联参数进行简易命令调用:
93
172
 
94
173
  \`\`\`bash
95
- # 格式:ad run <package-id>/<action-id> --input '<json>'
96
174
  ad run ${pkgId}/${firstAction} --input '{"param": "value"}'
97
175
  \`\`\`
98
176
 
99
177
  > **免注册本地执行**:
100
- > Agent 工作目录已位于本 Skill 根目录,亦可直接免 link 执行:
178
+ > 若工作目录已位于本技能根目录,亦可直接免 link 执行:
101
179
  > \`\`\`bash
102
180
  > cd <skill_root>
103
- > ad run <action-id> --input '<json>'
181
+ > ad run <action-id> --input-file /tmp/input.json
104
182
  > \`\`\`
105
183
 
106
- 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON Envelope:
184
+ ### 结构化响应解析
185
+
186
+ 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 信封:
187
+
107
188
  \`\`\`json
189
+ // 执行成功响应 (ok 为 true)
108
190
  {
109
191
  "ok": true,
110
192
  "runId": "01J...",
111
193
  "data": { ... }
112
194
  }
195
+
196
+ // 执行失败响应 (ok 为 false)
197
+ {
198
+ "ok": false,
199
+ "runId": "01J...",
200
+ "error": {
201
+ "code": "ACTION_EXECUTION_FAILED",
202
+ "message": "错误详细描述信息"
203
+ }
204
+ }
113
205
  \`\`\`
114
- 日志与诊断信息输出至 \`stderr\`。
115
206
 
207
+ - \`stdout\`:标准 JSON 信封结果。当 \`ok\` 为 \`true\` 时,从 \`data\` 提取业务返回值推进后续步骤;当 \`ok\` 为 \`false\` 时,从 \`error\` 提取错误码与信息以判定自愈策略或上报。
208
+ - \`stderr\`:执行日志与诊断跟踪信息。
209
+ ${playbookSection}
116
210
  ---
117
211
 
118
212
  ## Action 目录
119
213
 
120
214
  ${actionListMd}
121
- ${playbookSection}
215
+
122
216
  ---
123
217
 
124
218
  ## 运行时配置与持久化状态
@@ -140,39 +234,26 @@ ad state get KEY --package ${pkgId}
140
234
  ## 故障排查与环境安装指引(按需查阅)
141
235
 
142
236
  > [!NOTE]
143
- > **按需排查原则**:默认宿主环境中已预置 \`ad\` 命令行工具。正常执行流程直接调用上述 Action 即可,**严禁在任务启动前盲目进行前置环境检查或体检**;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)时,方可按本节指引安装初始化。
237
+ > **按需排查原则**:默认宿主环境中已预置 \`ad\` 命令行工具与 Node.js 运行环境。正常执行流程直接调用上述 Action 即可,**严禁在任务启动前盲目进行前置环境检查或体检**;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)时,方可按本节指引安装初始化。
144
238
 
145
239
  ### 命令行工具未找到时的安装指引
146
240
 
147
241
  若宿主环境未安装 \`ad\` 命令行工具,请依次按如下步骤完成安装:
148
242
 
149
- - **安装 Bun 运行时**(若系统未安装):
150
- \`\`\`bash
151
- npm install -g bun
152
- \`\`\`
153
-
243
+ - **环境要求**:Node.js 版本大于等于 22.12.0(执行 \`node -v\` 确认)。
154
244
  - **全局安装 ActionDock 命令行工具**:
155
245
  \`\`\`bash
156
246
  npm install -g @actiondock/cli
157
247
  \`\`\`
158
-
159
248
  - **验证工具就绪**:
160
249
  \`\`\`bash
161
250
  ad --version
162
251
  \`\`\`
163
-
164
- - **源码本地开发模式**(若在本地源码仓库中贡献或开发):
165
- \`\`\`bash
166
- cd packages/cli && npm link
167
- cd ../sdk && bun link
168
- \`\`\`
169
-
170
252
  - **环境诊断与体检**:
171
253
  安装完成后若仍遇到异常,执行体检命令排查:
172
254
  \`\`\`bash
173
255
  ad doctor
174
256
  \`\`\`
175
-
176
257
  - **完成安装后重新链接本技能**:
177
258
  \`\`\`bash
178
259
  ad link "<skill_root>"
@@ -189,8 +270,8 @@ export function generateStandaloneSkillMd(
189
270
  const { cleanName, desc } = getCleanSkillMetadata(config);
190
271
  const firstAction = actions[0]?.id || "sample.greet";
191
272
 
192
- const actionListMd = renderActionListMarkdown(actions);
193
273
  const playbookSection = renderPlaybookSectionMarkdown(playbooks);
274
+ const actionListMd = renderActionListMarkdown(actions);
194
275
 
195
276
  return `---
196
277
  name: ${cleanName}
@@ -203,48 +284,78 @@ ${desc}
203
284
 
204
285
  ## 如何调用 Action
205
286
 
206
- 使用 Skill 目录中自带的独立可执行文件 \`${binaryRelPath}\` 即可完成工具发现与调用。
287
+ 使用技能目录中自带的独立可执行程序 \`${binaryRelPath}\` 即可完成工具发现与调用。
207
288
  **该工具无需在系统预先安装任何依赖**(无需安装 Node.js、Bun、Python 或 Java)。
208
289
 
209
290
  ### 发现可用 Action 清单
291
+
210
292
  \`\`\`bash
211
293
  ${binaryRelPath} list --json
212
294
  \`\`\`
213
295
 
214
- ### 查看 Action 结构与入参 Schema
296
+ ### 查看 Action 结构与入参规范
297
+
215
298
  \`\`\`bash
216
299
  ${binaryRelPath} describe <action-id> --json
217
300
  \`\`\`
218
301
 
219
302
  ### 执行 Action
303
+
304
+ 推荐最佳实践:使用文件传递参数,杜绝终端引号转义问题:
305
+
220
306
  \`\`\`bash
221
- ${binaryRelPath} run <action-id> --input '{"param": "value"}'
307
+ # 写入参数到临时文件并通过 --input-file 传递
308
+ cat << 'EOF' > /tmp/input.json
309
+ {
310
+ "param": "value"
311
+ }
312
+ EOF
313
+ ${binaryRelPath} run <action-id> --input-file /tmp/input.json
314
+ \`\`\`
315
+
316
+ 亦可通过内联参数进行简易命令调用:
222
317
 
223
- # 示例:
318
+ \`\`\`bash
224
319
  ${binaryRelPath} run ${firstAction} --input '{"param": "value"}'
225
320
  \`\`\`
226
321
 
322
+ ### 结构化响应解析
323
+
227
324
  所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 结果:
325
+
228
326
  \`\`\`json
327
+ // 执行成功响应 (ok 为 true)
229
328
  {
230
329
  "ok": true,
231
330
  "runId": "01J...",
232
331
  "data": { ... }
233
332
  }
333
+
334
+ // 执行失败响应 (ok 为 false)
335
+ {
336
+ "ok": false,
337
+ "runId": "01J...",
338
+ "error": {
339
+ "code": "ACTION_EXECUTION_FAILED",
340
+ "message": "错误详细描述信息"
341
+ }
342
+ }
234
343
  \`\`\`
235
- 日志与诊断信息输出至 \`stderr\`。
236
344
 
345
+ - \`stdout\`:标准 JSON 结果信封。当 \`ok\` 为 \`true\` 时,从 \`data\` 提取业务数据;当 \`ok\` 为 \`false\` 时,从 \`error\` 读取错误原因以处理异常。
346
+ - \`stderr\`:执行日志与诊断信息。
347
+ ${playbookSection}
237
348
  ---
238
349
 
239
350
  ## Action 目录
240
351
 
241
352
  ${actionListMd}
242
- ${playbookSection}
353
+
243
354
  ---
244
355
 
245
356
  ## 运行时配置与持久化状态
246
357
 
247
- 独立二进制会自动管理其本地 SQLite 数据库。如需检查或配置:
358
+ 独立二进制程序会自动管理其本地 SQLite 数据库。如需检查或配置:
248
359
 
249
360
  \`\`\`bash
250
361
  # 查看与设置配置项
@@ -273,29 +384,207 @@ export function generateSkillMd(
273
384
  return generateStandaloneSkillMd(config, actions, playbooks, optionsOrBinaryPath.binaryRelPath || "./bin/action-bin");
274
385
  }
275
386
 
387
+ export interface CompositeSkillPackageInfo {
388
+ config: ProjectConfig;
389
+ actions: Array<{ id: string; description?: string }>;
390
+ playbooks: Array<{ id: string; name?: string; description?: string; filePath: string }>;
391
+ packageDir: string;
392
+ }
393
+
394
+ /**
395
+ * 生成多包聚合的复合模式 SKILL.md 文档。
396
+ */
397
+ export function generateCompositeSkillMd(
398
+ bundleName: string,
399
+ description: string,
400
+ packages: CompositeSkillPackageInfo[]
401
+ ): string {
402
+ const cleanName = bundleName.replace(/[^a-zA-Z0-9-_]/g, "-").toLowerCase();
403
+ const samplePkg = packages.find((p) => p.actions && p.actions.length > 0);
404
+ const sampleActionId = samplePkg
405
+ ? `${samplePkg.config.id}/${samplePkg.actions[0].id}`
406
+ : "sample.greet";
407
+
408
+ const actionSections = packages
409
+ .map((pkg) => {
410
+ const actList = pkg.actions
411
+ .map((a) => {
412
+ const desc = a.description ? `: ${a.description}` : "";
413
+ return `- \`${pkg.config.id}/${a.id}\`${desc}`;
414
+ })
415
+ .join("\n");
416
+ return `### ${pkg.config.name} (${pkg.config.id})\n${actList || "- 无可用 Action"}`;
417
+ })
418
+ .join("\n\n");
419
+
420
+ const playbookEntries: string[] = [];
421
+ for (const pkg of packages) {
422
+ for (const pb of pkg.playbooks) {
423
+ const relPath = `packages/${pkg.packageDir}/playbooks/${basename(pb.filePath)}`;
424
+ playbookEntries.push(`- [${pb.name || pb.id}](${relPath}): ${pb.description || "标准操作规程"}`);
425
+ }
426
+ }
427
+
428
+ const playbookSection =
429
+ playbookEntries.length > 0
430
+ ? `## 推荐操作规程\n\n涉及多步骤或业务流程时,优先遵循以下原位规程:\n\n${playbookEntries.join("\n")}\n\n---\n`
431
+ : "";
432
+
433
+ return `---
434
+ name: ${cleanName}
435
+ description: ${description}
436
+ ---
437
+
438
+ # ${bundleName} 复合技能套件
439
+
440
+ ${description}
441
+
442
+ ## ActionDock 运行时初始化
443
+
444
+ 本技能为 **ActionDock 复合工作区技能包**,聚合了多个功能包。智能体在初次调用或初始化时,在当前技能根目录执行注册命令:
445
+
446
+ \`\`\`bash
447
+ ad link "<skill_root>"
448
+ \`\`\`
449
+
450
+ > \`ad link\` 会自动识别并注册工作区下的所有子包,使其中的 Action 随时可以通过完全限定标识调用。
451
+
452
+ ## 动作参数契约按需调阅
453
+
454
+ 为节省上下文开销,各 Action 的详细参数结构不静态内嵌在说明书中。在调用未知参数的 Action 前,可在终端执行命令查阅输入输出约束:
455
+
456
+ \`\`\`bash
457
+ ad action show ${sampleActionId}
458
+ \`\`\`
459
+
460
+ ## 可用 Action 工具清单
461
+
462
+ ${actionSections}
463
+
464
+ ---
465
+
466
+ ${playbookSection}
467
+ ## 标准调用命令
468
+
469
+ 推荐使用参数文件传递内容,杜绝终端引号转义问题:
470
+
471
+ \`\`\`bash
472
+ cat << 'EOF' > /tmp/input.json
473
+ {
474
+ "param": "value"
475
+ }
476
+ EOF
477
+ ad run ${sampleActionId} --input-file /tmp/input.json
478
+ \`\`\`
479
+
480
+ ### 结构化响应解析
481
+
482
+ 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 信封:
483
+
484
+ \`\`\`json
485
+ // 执行成功响应 (ok 为 true)
486
+ {
487
+ "ok": true,
488
+ "runId": "01J...",
489
+ "data": { ... }
490
+ }
491
+
492
+ // 执行失败响应 (ok 为 false)
493
+ {
494
+ "ok": false,
495
+ "runId": "01J...",
496
+ "error": {
497
+ "code": "ACTION_EXECUTION_FAILED",
498
+ "message": "错误详细描述信息"
499
+ }
500
+ }
501
+ \`\`\`
502
+ `;
503
+ }
504
+
505
+
506
+ export interface GenerateSkillJsonOptions {
507
+ mode?: "source" | "standalone";
508
+ executable?: string;
509
+ target?: string;
510
+ playbooks?: PlaybookDefinition[];
511
+ }
512
+
276
513
  export function generateSkillJson(
277
514
  config: ProjectConfig,
278
515
  actions: ActionDefinition[],
279
- binaryName: string,
280
- target: string
516
+ binaryNameOrOptions?: string | GenerateSkillJsonOptions,
517
+ target = "host",
518
+ playbooksList: PlaybookDefinition[] = []
281
519
  ): string {
282
- const manifest = {
520
+ let mode: "source" | "standalone" = "source";
521
+ let executable: string | undefined;
522
+ let targetPlatform = target;
523
+ let playbooks = playbooksList;
524
+
525
+ if (typeof binaryNameOrOptions === "string") {
526
+ mode = "standalone";
527
+ executable = `./bin/${binaryNameOrOptions}`;
528
+ } else if (binaryNameOrOptions && typeof binaryNameOrOptions === "object") {
529
+ mode = binaryNameOrOptions.mode || (binaryNameOrOptions.executable ? "standalone" : "source");
530
+ executable = binaryNameOrOptions.executable;
531
+ targetPlatform = binaryNameOrOptions.target || target;
532
+ if (binaryNameOrOptions.playbooks) {
533
+ playbooks = binaryNameOrOptions.playbooks;
534
+ }
535
+ }
536
+
537
+ const manifest: Record<string, unknown> = {
283
538
  schemaVersion: "2.0.0",
284
539
  packageId: config.id,
285
540
  name: config.name,
286
541
  version: config.version,
287
542
  description: config.description,
288
- target,
289
- executable: `./bin/${binaryName}`,
290
- actions: actions.map((a) => ({
291
- id: a.id,
292
- description: a.description,
293
- inputSchema: a.inputSchema,
294
- outputSchema: a.outputSchema,
295
- })),
296
- exportedAt: new Date().toISOString(),
543
+ mode,
297
544
  };
298
545
 
546
+ if (mode === "standalone" && executable) {
547
+ manifest.target = targetPlatform;
548
+ manifest.executable = executable;
549
+ }
550
+
551
+ manifest.actions = actions.map((a: any) => {
552
+ const item: Record<string, unknown> = {
553
+ id: a.id,
554
+ };
555
+ if (a.entry) {
556
+ item.entry = a.entry;
557
+ }
558
+ if (a.description) {
559
+ item.description = a.description;
560
+ }
561
+ if (a.inputSchema !== undefined) {
562
+ item.inputSchema = a.inputSchema;
563
+ }
564
+ if (a.outputSchema !== undefined) {
565
+ item.outputSchema = a.outputSchema;
566
+ }
567
+ if (a.uses) {
568
+ item.uses = a.uses;
569
+ }
570
+ if (a.tags) {
571
+ item.tags = a.tags;
572
+ }
573
+ if (a.annotations) {
574
+ item.annotations = a.annotations;
575
+ }
576
+ return item;
577
+ });
578
+
579
+ if (playbooks && playbooks.length > 0) {
580
+ manifest.playbooks = playbooks.map((p) => ({
581
+ id: p.id,
582
+ description: p.description,
583
+ entry: `playbooks/${basename(p.filePath)}`,
584
+ }));
585
+ }
586
+
587
+ manifest.exportedAt = new Date().toISOString();
588
+
299
589
  return JSON.stringify(manifest, null, 2) + "\n";
300
590
  }
301
-
package/src/index.ts CHANGED
@@ -12,4 +12,5 @@ export * from "./schema/validator";
12
12
  export * from "./server";
13
13
  export * from "./storage";
14
14
  export * from "./utils";
15
+ export type { ModuleLoader } from "./runtime";
15
16
 
@@ -108,10 +108,10 @@ export function initProject(targetDir: string, options: InitOptions = {}): void
108
108
  node: ">=22.12.0",
109
109
  },
110
110
  dependencies: {
111
- "@actiondock/sdk": "^2.0.9",
111
+ "@actiondock/sdk": "2.0.11-beta.0",
112
112
  },
113
113
  devDependencies: {
114
- "@actiondock/testing": "^2.0.9",
114
+ "@actiondock/testing": "2.0.11-beta.0",
115
115
  "@types/node": "^22.12.0",
116
116
  "tsx": "^4.19.0",
117
117
  "typescript": "^5.7.0",
@@ -157,6 +157,7 @@ build/
157
157
  export default defineAction({
158
158
  id: "sample.greet",
159
159
  description: "Greeting action demonstrating basic input, config, and state usage",
160
+ tags: ["sample"],
160
161
 
161
162
  inputSchema: {
162
163
  type: "object",
@@ -5,6 +5,7 @@ import { basename, dirname, join, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import YAML from "yaml";
7
7
  import type { ActionDefinition } from "@actiondock/sdk";
8
+ import { getModuleLoader } from "../runtime/module-loader";
8
9
  import type { PlaybookDefinition, PlaybookFrontmatter, ProjectConfig } from "./types";
9
10
 
10
11
  /**
@@ -273,13 +274,14 @@ export async function loadActions(
273
274
 
274
275
  const files = discoverActionFiles(projectRoot, actionsDir);
275
276
  const actions = new Map<string, ActionDefinition>();
277
+ const loader = getModuleLoader();
276
278
 
277
279
  for (const file of files) {
278
280
  try {
279
281
  // 动态导入,若缺失模块则自动触发依赖重装与二次重试
280
282
  let imported: any;
281
283
  try {
282
- imported = await import(toImportSpecifier(file));
284
+ imported = await loader.load(file);
283
285
  } catch (err: any) {
284
286
  const msg = String(err.message || "");
285
287
  if (
@@ -291,7 +293,7 @@ export async function loadActions(
291
293
  ) {
292
294
  const installed = ensureProjectDependencies(projectRoot, true);
293
295
  if (installed) {
294
- imported = await import(toImportSpecifier(file));
296
+ imported = await loader.load(file);
295
297
  } else {
296
298
  throw err;
297
299
  }
@@ -331,28 +333,65 @@ export interface ActionFileEntry {
331
333
  }
332
334
 
333
335
  /**
334
- * 加载并建立 Action ID 与其物理源码文件路径之间的映射关系(供构建打包器使用)。
336
+ * 加载并建立 Action ID 与其物理源码文件路径之间的映射关系(供构建打包器及清单同步使用)。
335
337
  */
336
338
  export async function loadActionFileMap(
337
339
  projectRoot: string,
338
- actionsDir = "actions"
340
+ actionsDir = "actions",
341
+ options: { autoInstall?: boolean; strict?: boolean } = { autoInstall: true, strict: false }
339
342
  ): Promise<Map<string, ActionFileEntry>> {
343
+ if (options.autoInstall !== false) {
344
+ ensureProjectDependencies(projectRoot);
345
+ }
346
+
340
347
  const files = discoverActionFiles(projectRoot, actionsDir);
341
348
  const map = new Map<string, ActionFileEntry>();
349
+ const loader = getModuleLoader();
342
350
 
343
351
  for (const file of files) {
344
352
  try {
345
- const imported = await import(toImportSpecifier(file));
353
+ let imported: any;
354
+ try {
355
+ imported = await loader.load(file);
356
+ } catch (err: any) {
357
+ const msg = String(err.message || "");
358
+ if (
359
+ options.autoInstall !== false &&
360
+ (msg.includes("Cannot find package") ||
361
+ msg.includes("Cannot find module") ||
362
+ msg.includes("ERR_MODULE_NOT_FOUND") ||
363
+ msg.includes("Could not resolve"))
364
+ ) {
365
+ const installed = ensureProjectDependencies(projectRoot, true);
366
+ if (installed) {
367
+ imported = await loader.load(file);
368
+ } else {
369
+ throw err;
370
+ }
371
+ } else {
372
+ throw err;
373
+ }
374
+ }
375
+
346
376
  const act = imported.default || imported.action;
347
377
  if (act && typeof act === "object" && typeof act.id === "string") {
378
+ if (map.has(act.id)) {
379
+ throw new Error(
380
+ `Duplicate action ID '${act.id}' found in ${file} (previously loaded from ${map.get(act.id)!.filePath})`
381
+ );
382
+ }
348
383
  map.set(act.id, {
349
384
  id: act.id,
350
385
  filePath: resolve(file),
351
386
  action: act,
352
387
  });
388
+ } else if (options.strict) {
389
+ console.warn(`[WARN] File ${file} does not export a valid default ActionDefinition`);
390
+ }
391
+ } catch (err: any) {
392
+ if (options.strict) {
393
+ throw new Error(`Failed to load action from ${file}: ${err.message}`);
353
394
  }
354
- } catch {
355
- // 忽略非 Action 导出的辅助模块
356
395
  }
357
396
  }
358
397