@actiondock/core 2.0.8 → 2.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.
@@ -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
+ ## 业务操作规程
48
102
 
49
- Playbook 为复杂任务提供逐步操作规程。详细 SOP 请阅读对应 Markdown 文档:
103
+ > [!IMPORTANT]
104
+ > **规程优先准则**:当处理复合业务任务时,智能体必须优先检查是否存在匹配场景的 Playbook。若存在规程,必须优先查阅并严格遵循规程界定的步骤时序与校验逻辑推进,严禁无序拼凑调用底层 Action。
105
+
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
 
@@ -89,36 +146,65 @@ ad link "<skill_root>"
89
146
 
90
147
  ### 执行 Action
91
148
 
92
- 为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID
149
+ 为避免多技能之间的 Action ID 命名冲突,建议统一使用带有 Package 前缀的完全限定 ID
150
+
151
+ 推荐最佳实践:使用文件传递参数,杜绝终端引号转义问题:
152
+
153
+ \`\`\`bash
154
+ # 写入参数到临时文件并通过 --input-file 传递
155
+ cat << 'EOF' > /tmp/input.json
156
+ {
157
+ "param": "value"
158
+ }
159
+ EOF
160
+ ad run ${pkgId}/${firstAction} --input-file /tmp/input.json
161
+ \`\`\`
162
+
163
+ 亦可通过内联参数进行简易命令调用:
93
164
 
94
165
  \`\`\`bash
95
- # 格式:ad run <package-id>/<action-id> --input '<json>'
96
166
  ad run ${pkgId}/${firstAction} --input '{"param": "value"}'
97
167
  \`\`\`
98
168
 
99
169
  > **免注册本地执行**:
100
- > Agent 工作目录已位于本 Skill 根目录,亦可直接免 link 执行:
170
+ > 若工作目录已位于本技能根目录,亦可直接免 link 执行:
101
171
  > \`\`\`bash
102
172
  > cd <skill_root>
103
- > ad run <action-id> --input '<json>'
173
+ > ad run <action-id> --input-file /tmp/input.json
104
174
  > \`\`\`
105
175
 
106
- 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON Envelope:
176
+ ### 结构化响应解析
177
+
178
+ 所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 信封:
179
+
107
180
  \`\`\`json
181
+ // 执行成功响应 (ok 为 true)
108
182
  {
109
183
  "ok": true,
110
184
  "runId": "01J...",
111
185
  "data": { ... }
112
186
  }
187
+
188
+ // 执行失败响应 (ok 为 false)
189
+ {
190
+ "ok": false,
191
+ "runId": "01J...",
192
+ "error": {
193
+ "code": "ACTION_EXECUTION_FAILED",
194
+ "message": "错误详细描述信息"
195
+ }
196
+ }
113
197
  \`\`\`
114
- 日志与诊断信息输出至 \`stderr\`。
115
198
 
199
+ - \`stdout\`:标准 JSON 信封结果。当 \`ok\` 为 \`true\` 时,从 \`data\` 提取业务返回值推进后续步骤;当 \`ok\` 为 \`false\` 时,从 \`error\` 提取错误码与信息以判定自愈策略或上报。
200
+ - \`stderr\`:执行日志与诊断跟踪信息。
201
+ ${playbookSection}
116
202
  ---
117
203
 
118
204
  ## Action 目录
119
205
 
120
206
  ${actionListMd}
121
- ${playbookSection}
207
+
122
208
  ---
123
209
 
124
210
  ## 运行时配置与持久化状态
@@ -140,39 +226,26 @@ ad state get KEY --package ${pkgId}
140
226
  ## 故障排查与环境安装指引(按需查阅)
141
227
 
142
228
  > [!NOTE]
143
- > **按需排查原则**:默认宿主环境中已预置 \`ad\` 命令行工具。正常执行流程直接调用上述 Action 即可,**严禁在任务启动前盲目进行前置环境检查或体检**;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)时,方可按本节指引安装初始化。
229
+ > **按需排查原则**:默认宿主环境中已预置 \`ad\` 命令行工具与 Node.js 运行环境。正常执行流程直接调用上述 Action 即可,**严禁在任务启动前盲目进行前置环境检查或体检**;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)时,方可按本节指引安装初始化。
144
230
 
145
231
  ### 命令行工具未找到时的安装指引
146
232
 
147
233
  若宿主环境未安装 \`ad\` 命令行工具,请依次按如下步骤完成安装:
148
234
 
149
- - **安装 Bun 运行时**(若系统未安装):
150
- \`\`\`bash
151
- npm install -g bun
152
- \`\`\`
153
-
235
+ - **环境要求**:Node.js 版本大于等于 22.12.0(执行 \`node -v\` 确认)。
154
236
  - **全局安装 ActionDock 命令行工具**:
155
237
  \`\`\`bash
156
238
  npm install -g @actiondock/cli
157
239
  \`\`\`
158
-
159
240
  - **验证工具就绪**:
160
241
  \`\`\`bash
161
242
  ad --version
162
243
  \`\`\`
163
-
164
- - **源码本地开发模式**(若在本地源码仓库中贡献或开发):
165
- \`\`\`bash
166
- cd packages/cli && npm link
167
- cd ../sdk && bun link
168
- \`\`\`
169
-
170
244
  - **环境诊断与体检**:
171
245
  安装完成后若仍遇到异常,执行体检命令排查:
172
246
  \`\`\`bash
173
247
  ad doctor
174
248
  \`\`\`
175
-
176
249
  - **完成安装后重新链接本技能**:
177
250
  \`\`\`bash
178
251
  ad link "<skill_root>"
@@ -189,8 +262,8 @@ export function generateStandaloneSkillMd(
189
262
  const { cleanName, desc } = getCleanSkillMetadata(config);
190
263
  const firstAction = actions[0]?.id || "sample.greet";
191
264
 
192
- const actionListMd = renderActionListMarkdown(actions);
193
265
  const playbookSection = renderPlaybookSectionMarkdown(playbooks);
266
+ const actionListMd = renderActionListMarkdown(actions);
194
267
 
195
268
  return `---
196
269
  name: ${cleanName}
@@ -203,48 +276,78 @@ ${desc}
203
276
 
204
277
  ## 如何调用 Action
205
278
 
206
- 使用 Skill 目录中自带的独立可执行文件 \`${binaryRelPath}\` 即可完成工具发现与调用。
279
+ 使用技能目录中自带的独立可执行程序 \`${binaryRelPath}\` 即可完成工具发现与调用。
207
280
  **该工具无需在系统预先安装任何依赖**(无需安装 Node.js、Bun、Python 或 Java)。
208
281
 
209
282
  ### 发现可用 Action 清单
283
+
210
284
  \`\`\`bash
211
285
  ${binaryRelPath} list --json
212
286
  \`\`\`
213
287
 
214
- ### 查看 Action 结构与入参 Schema
288
+ ### 查看 Action 结构与入参规范
289
+
215
290
  \`\`\`bash
216
291
  ${binaryRelPath} describe <action-id> --json
217
292
  \`\`\`
218
293
 
219
294
  ### 执行 Action
295
+
296
+ 推荐最佳实践:使用文件传递参数,杜绝终端引号转义问题:
297
+
220
298
  \`\`\`bash
221
- ${binaryRelPath} run <action-id> --input '{"param": "value"}'
299
+ # 写入参数到临时文件并通过 --input-file 传递
300
+ cat << 'EOF' > /tmp/input.json
301
+ {
302
+ "param": "value"
303
+ }
304
+ EOF
305
+ ${binaryRelPath} run <action-id> --input-file /tmp/input.json
306
+ \`\`\`
307
+
308
+ 亦可通过内联参数进行简易命令调用:
222
309
 
223
- # 示例:
310
+ \`\`\`bash
224
311
  ${binaryRelPath} run ${firstAction} --input '{"param": "value"}'
225
312
  \`\`\`
226
313
 
314
+ ### 结构化响应解析
315
+
227
316
  所有 Action 执行结果均在 \`stdout\` 输出标准格式的 JSON 结果:
317
+
228
318
  \`\`\`json
319
+ // 执行成功响应 (ok 为 true)
229
320
  {
230
321
  "ok": true,
231
322
  "runId": "01J...",
232
323
  "data": { ... }
233
324
  }
325
+
326
+ // 执行失败响应 (ok 为 false)
327
+ {
328
+ "ok": false,
329
+ "runId": "01J...",
330
+ "error": {
331
+ "code": "ACTION_EXECUTION_FAILED",
332
+ "message": "错误详细描述信息"
333
+ }
334
+ }
234
335
  \`\`\`
235
- 日志与诊断信息输出至 \`stderr\`。
236
336
 
337
+ - \`stdout\`:标准 JSON 结果信封。当 \`ok\` 为 \`true\` 时,从 \`data\` 提取业务数据;当 \`ok\` 为 \`false\` 时,从 \`error\` 读取错误原因以处理异常。
338
+ - \`stderr\`:执行日志与诊断信息。
339
+ ${playbookSection}
237
340
  ---
238
341
 
239
342
  ## Action 目录
240
343
 
241
344
  ${actionListMd}
242
- ${playbookSection}
345
+
243
346
  ---
244
347
 
245
348
  ## 运行时配置与持久化状态
246
349
 
247
- 独立二进制会自动管理其本地 SQLite 数据库。如需检查或配置:
350
+ 独立二进制程序会自动管理其本地 SQLite 数据库。如需检查或配置:
248
351
 
249
352
  \`\`\`bash
250
353
  # 查看与设置配置项
@@ -273,29 +376,88 @@ export function generateSkillMd(
273
376
  return generateStandaloneSkillMd(config, actions, playbooks, optionsOrBinaryPath.binaryRelPath || "./bin/action-bin");
274
377
  }
275
378
 
379
+ export interface GenerateSkillJsonOptions {
380
+ mode?: "source" | "standalone";
381
+ executable?: string;
382
+ target?: string;
383
+ playbooks?: PlaybookDefinition[];
384
+ }
385
+
276
386
  export function generateSkillJson(
277
387
  config: ProjectConfig,
278
388
  actions: ActionDefinition[],
279
- binaryName: string,
280
- target: string
389
+ binaryNameOrOptions?: string | GenerateSkillJsonOptions,
390
+ target = "host",
391
+ playbooksList: PlaybookDefinition[] = []
281
392
  ): string {
282
- const manifest = {
393
+ let mode: "source" | "standalone" = "source";
394
+ let executable: string | undefined;
395
+ let targetPlatform = target;
396
+ let playbooks = playbooksList;
397
+
398
+ if (typeof binaryNameOrOptions === "string") {
399
+ mode = "standalone";
400
+ executable = `./bin/${binaryNameOrOptions}`;
401
+ } else if (binaryNameOrOptions && typeof binaryNameOrOptions === "object") {
402
+ mode = binaryNameOrOptions.mode || (binaryNameOrOptions.executable ? "standalone" : "source");
403
+ executable = binaryNameOrOptions.executable;
404
+ targetPlatform = binaryNameOrOptions.target || target;
405
+ if (binaryNameOrOptions.playbooks) {
406
+ playbooks = binaryNameOrOptions.playbooks;
407
+ }
408
+ }
409
+
410
+ const manifest: Record<string, unknown> = {
283
411
  schemaVersion: "2.0.0",
284
412
  packageId: config.id,
285
413
  name: config.name,
286
414
  version: config.version,
287
415
  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(),
416
+ mode,
297
417
  };
298
418
 
419
+ if (mode === "standalone" && executable) {
420
+ manifest.target = targetPlatform;
421
+ manifest.executable = executable;
422
+ }
423
+
424
+ manifest.actions = actions.map((a: any) => {
425
+ const item: Record<string, unknown> = {
426
+ id: a.id,
427
+ };
428
+ if (a.entry) {
429
+ item.entry = a.entry;
430
+ }
431
+ if (a.description) {
432
+ item.description = a.description;
433
+ }
434
+ if (a.inputSchema !== undefined) {
435
+ item.inputSchema = a.inputSchema;
436
+ }
437
+ if (a.outputSchema !== undefined) {
438
+ item.outputSchema = a.outputSchema;
439
+ }
440
+ if (a.uses) {
441
+ item.uses = a.uses;
442
+ }
443
+ if (a.tags) {
444
+ item.tags = a.tags;
445
+ }
446
+ if (a.annotations) {
447
+ item.annotations = a.annotations;
448
+ }
449
+ return item;
450
+ });
451
+
452
+ if (playbooks && playbooks.length > 0) {
453
+ manifest.playbooks = playbooks.map((p) => ({
454
+ id: p.id,
455
+ description: p.description,
456
+ entry: `playbooks/${basename(p.filePath)}`,
457
+ }));
458
+ }
459
+
460
+ manifest.exportedAt = new Date().toISOString();
461
+
299
462
  return JSON.stringify(manifest, null, 2) + "\n";
300
463
  }
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.8",
111
+ "@actiondock/sdk": "^2.0.10",
112
112
  },
113
113
  devDependencies: {
114
- "@actiondock/testing": "^2.0.8",
114
+ "@actiondock/testing": "^2.0.10",
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",
@@ -1,9 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
3
4
  import { basename, dirname, join, resolve } from "node:path";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import YAML from "yaml";
6
7
  import type { ActionDefinition } from "@actiondock/sdk";
8
+ import { getModuleLoader } from "../runtime/module-loader";
7
9
  import type { PlaybookDefinition, PlaybookFrontmatter, ProjectConfig } from "./types";
8
10
 
9
11
  /**
@@ -80,8 +82,15 @@ export function loadProjectConfig(projectRoot: string): ProjectConfig {
80
82
 
81
83
  /**
82
84
  * 探测宿主系统中可用的包管理工具(优先级:pnpm > npm > yarn > bun)。
85
+ * 可通过环境变量 ACTIONDOCK_INSTALLER 强制指定(如 npm、pnpm、yarn、bun)。
86
+ * 探测必须经 shell 执行:Windows 下 npm/pnpm/yarn 均为 .cmd 垫片,
87
+ * 不经 shell 的 spawnSync 无法解析,将错误地回退到原生 exe 的 bun。
83
88
  */
84
89
  function getInstallCommand(): string[] {
90
+ const preferred = process.env.ACTIONDOCK_INSTALLER?.trim();
91
+ if (preferred) {
92
+ return [preferred, "install"];
93
+ }
85
94
  const candidates: [string, string][] = [
86
95
  ["pnpm", "install"],
87
96
  ["npm", "install"],
@@ -90,8 +99,9 @@ function getInstallCommand(): string[] {
90
99
  ];
91
100
  for (const [pm, action] of candidates) {
92
101
  try {
93
- const check = spawnSync(pm, ["--version"], {
102
+ const check = spawnSync(`${pm} --version`, {
94
103
  stdio: "pipe",
104
+ shell: true,
95
105
  });
96
106
  if (check.status === 0) {
97
107
  return [pm, action];
@@ -103,6 +113,30 @@ function getInstallCommand(): string[] {
103
113
  return ["npm", "install"];
104
114
  }
105
115
 
116
+ /**
117
+ * 解析 npm 风格 .npmrc 中的 strict-ssl 配置(项目级优先于用户级)。
118
+ * 返回 undefined 表示各级配置均未声明该键。
119
+ */
120
+ function resolveNpmStrictSsl(projectRoot: string): boolean | undefined {
121
+ const configPaths = [join(projectRoot, ".npmrc"), join(homedir(), ".npmrc")];
122
+ for (const configPath of configPaths) {
123
+ try {
124
+ if (!existsSync(configPath)) continue;
125
+ const raw = readFileSync(configPath, "utf-8");
126
+ for (const line of raw.split(/\r?\n/)) {
127
+ const matched = line.match(/^\s*strict-ssl\s*=\s*(\S+)\s*$/i);
128
+ if (matched) {
129
+ const falsy = ["false", "0", "no", "off"];
130
+ return !falsy.includes(matched[1].toLowerCase());
131
+ }
132
+ }
133
+ } catch {
134
+ // 配置不可读时继续检查下一级
135
+ }
136
+ }
137
+ return undefined;
138
+ }
139
+
106
140
  /**
107
141
  * 确保项目依赖(node_modules)已正确安装。
108
142
  * 若尚未安装或加载失败时,自动触发包管理器执行依赖安装。
@@ -141,14 +175,30 @@ export function ensureProjectDependencies(projectRoot: string, force = false): b
141
175
  `[actiondock] Installing dependencies using ${installCmd[0]} for '${pkg.name || basename(projectRoot)}'...\n`
142
176
  );
143
177
 
144
- const proc = spawnSync(installCmd[0], installCmd.slice(1), {
178
+ // bun 不读取 .npmrc 的 strict-ssl 配置;当 npm 侧已声明 strict-ssl=false 时,
179
+ // 桥接为 bun 子进程的 NODE_TLS_REJECT_UNAUTHORIZED=0,保证内网自签名证书源下行为一致
180
+ const childEnv = { ...process.env };
181
+ if (installCmd[0] === "bun" && resolveNpmStrictSsl(projectRoot) === false) {
182
+ childEnv.NODE_TLS_REJECT_UNAUTHORIZED = "0";
183
+ }
184
+
185
+ // shell 模式下须传入单一命令字符串;候选命令均来自内置白名单,无注入面
186
+ const proc = spawnSync(installCmd.join(" "), {
145
187
  cwd: projectRoot,
146
188
  stdio: "pipe",
189
+ shell: true,
190
+ env: childEnv,
147
191
  });
148
192
 
149
193
  if (proc.status !== 0) {
150
194
  const errText = proc.stderr?.toString() || `Unknown error during ${installCmd[0]} install`;
151
195
  process.stderr.write(`[actiondock] Warning: Dependency installation failed: ${errText}\n`);
196
+ if (installCmd[0] === "bun" && /SELF_SIGNED_CERT|CERT_|UNABLE_TO_VERIFY|ERR_TLS/i.test(errText)) {
197
+ process.stderr.write(
198
+ `[actiondock] Hint: bun ignores 'strict-ssl=false' from .npmrc. ` +
199
+ `Add 'strict-ssl=false' to the project or user .npmrc, or set ACTIONDOCK_INSTALLER=npm.\n`
200
+ );
201
+ }
152
202
  return false;
153
203
  }
154
204
 
@@ -224,13 +274,14 @@ export async function loadActions(
224
274
 
225
275
  const files = discoverActionFiles(projectRoot, actionsDir);
226
276
  const actions = new Map<string, ActionDefinition>();
277
+ const loader = getModuleLoader();
227
278
 
228
279
  for (const file of files) {
229
280
  try {
230
281
  // 动态导入,若缺失模块则自动触发依赖重装与二次重试
231
282
  let imported: any;
232
283
  try {
233
- imported = await import(toImportSpecifier(file));
284
+ imported = await loader.load(file);
234
285
  } catch (err: any) {
235
286
  const msg = String(err.message || "");
236
287
  if (
@@ -242,7 +293,7 @@ export async function loadActions(
242
293
  ) {
243
294
  const installed = ensureProjectDependencies(projectRoot, true);
244
295
  if (installed) {
245
- imported = await import(toImportSpecifier(file));
296
+ imported = await loader.load(file);
246
297
  } else {
247
298
  throw err;
248
299
  }
@@ -282,28 +333,65 @@ export interface ActionFileEntry {
282
333
  }
283
334
 
284
335
  /**
285
- * 加载并建立 Action ID 与其物理源码文件路径之间的映射关系(供构建打包器使用)。
336
+ * 加载并建立 Action ID 与其物理源码文件路径之间的映射关系(供构建打包器及清单同步使用)。
286
337
  */
287
338
  export async function loadActionFileMap(
288
339
  projectRoot: string,
289
- actionsDir = "actions"
340
+ actionsDir = "actions",
341
+ options: { autoInstall?: boolean; strict?: boolean } = { autoInstall: true, strict: false }
290
342
  ): Promise<Map<string, ActionFileEntry>> {
343
+ if (options.autoInstall !== false) {
344
+ ensureProjectDependencies(projectRoot);
345
+ }
346
+
291
347
  const files = discoverActionFiles(projectRoot, actionsDir);
292
348
  const map = new Map<string, ActionFileEntry>();
349
+ const loader = getModuleLoader();
293
350
 
294
351
  for (const file of files) {
295
352
  try {
296
- 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
+
297
376
  const act = imported.default || imported.action;
298
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
+ }
299
383
  map.set(act.id, {
300
384
  id: act.id,
301
385
  filePath: resolve(file),
302
386
  action: act,
303
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}`);
304
394
  }
305
- } catch {
306
- // 忽略非 Action 导出的辅助模块
307
395
  }
308
396
  }
309
397