@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.
- package/package.json +9 -3
- package/src/build/index.ts +0 -1
- package/src/build/templates.ts +1 -1
- package/src/catalog/types.ts +1 -9
- package/src/doctor/doctor.ts +99 -38
- package/src/execution/service.ts +47 -7
- package/src/export/index.ts +0 -1
- package/src/export/templates.ts +221 -59
- package/src/index.ts +1 -0
- package/src/project/init.ts +3 -2
- package/src/project/loader.ts +97 -9
- package/src/project/manifest.ts +205 -2
- package/src/project/types.ts +49 -0
- package/src/registry/registry.ts +126 -32
- package/src/runtime/context.ts +2 -1
- package/src/runtime/index.ts +1 -0
- package/src/runtime/module-loader.ts +67 -0
- package/src/runtime/process.ts +12 -11
- package/src/runtime/runner.ts +23 -36
- package/src/server/index.ts +1 -0
- package/src/server/routes/actions.ts +262 -0
- package/src/server/routes/common.ts +126 -0
- package/src/server/routes/config.ts +147 -0
- package/src/server/routes/doctor.ts +29 -0
- package/src/server/routes/health.ts +40 -0
- package/src/server/routes/index.ts +9 -0
- package/src/server/routes/info.ts +221 -0
- package/src/server/routes/playbooks.ts +109 -0
- package/src/server/routes/runs.ts +301 -0
- package/src/server/routes/state.ts +133 -0
- package/src/server/server.ts +82 -1323
- package/src/storage/index.ts +20 -4
- package/src/storage/sqlite.ts +60 -31
- package/src/storage/types.ts +8 -0
- package/src/utils/index.ts +2 -0
- package/src/build/builder.ts +0 -218
- package/src/export/skill.ts +0 -350
package/src/export/templates.ts
CHANGED
|
@@ -15,24 +15,78 @@ function renderActionListMarkdown(
|
|
|
15
15
|
return actions
|
|
16
16
|
.map((a) => {
|
|
17
17
|
const aDesc = a.description ? ` - ${a.description}` : "";
|
|
18
|
-
|
|
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 =
|
|
25
|
-
const req = (a.inputSchema as any).required || [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
##
|
|
101
|
+
## 业务操作规程
|
|
48
102
|
|
|
49
|
-
|
|
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
|
|
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
|
-
>
|
|
170
|
+
> 若工作目录已位于本技能根目录,亦可直接免 link 执行:
|
|
101
171
|
> \`\`\`bash
|
|
102
172
|
> cd <skill_root>
|
|
103
|
-
> ad run <action-id> --input
|
|
173
|
+
> ad run <action-id> --input-file /tmp/input.json
|
|
104
174
|
> \`\`\`
|
|
105
175
|
|
|
106
|
-
|
|
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
|
-
|
|
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\`
|
|
229
|
+
> **按需排查原则**:默认宿主环境中已预置 \`ad\` 命令行工具与 Node.js 运行环境。正常执行流程直接调用上述 Action 即可,**严禁在任务启动前盲目进行前置环境检查或体检**;仅在终端明确报错提示命令不存在(如 \`ad: command not found\`)时,方可按本节指引安装初始化。
|
|
144
230
|
|
|
145
231
|
### 命令行工具未找到时的安装指引
|
|
146
232
|
|
|
147
233
|
若宿主环境未安装 \`ad\` 命令行工具,请依次按如下步骤完成安装:
|
|
148
234
|
|
|
149
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
345
|
+
|
|
243
346
|
---
|
|
244
347
|
|
|
245
348
|
## 运行时配置与持久化状态
|
|
246
349
|
|
|
247
|
-
|
|
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
|
-
|
|
280
|
-
target
|
|
389
|
+
binaryNameOrOptions?: string | GenerateSkillJsonOptions,
|
|
390
|
+
target = "host",
|
|
391
|
+
playbooksList: PlaybookDefinition[] = []
|
|
281
392
|
): string {
|
|
282
|
-
|
|
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
|
-
|
|
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
package/src/project/init.ts
CHANGED
|
@@ -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.
|
|
111
|
+
"@actiondock/sdk": "^2.0.10",
|
|
112
112
|
},
|
|
113
113
|
devDependencies: {
|
|
114
|
-
"@actiondock/testing": "^2.0.
|
|
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",
|
package/src/project/loader.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|