@actiondock/builder 2.19.0 → 2.21.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.
- package/dist/dependency-resolver.d.ts +2 -0
- package/dist/dependency-resolver.js +36 -1
- package/dist/npm-pack.d.ts +4 -3
- package/dist/npm-pack.js +35 -57
- package/dist/planner.js +142 -59
- package/dist/skill/templates.js +11 -6
- package/dist/types.d.ts +2 -0
- package/package.json +3 -3
|
@@ -3,6 +3,8 @@ import type { ExternalDependency } from "./types.js";
|
|
|
3
3
|
* 测试与类型声明文件后缀常量(供 fallback 清单过滤与 isIgnoredPath 共享同一口径)。
|
|
4
4
|
*/
|
|
5
5
|
export declare const TEST_FILE_SUFFIXES: string[];
|
|
6
|
+
/** 运行环境配置不得进入分发产物;普通业务 bindings.json 不受影响。 */
|
|
7
|
+
export declare function isRuntimeConfigFile(relPath: string): boolean;
|
|
6
8
|
/**
|
|
7
9
|
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
8
10
|
*/
|
|
@@ -15,6 +15,41 @@ export const TEST_FILE_SUFFIXES = [
|
|
|
15
15
|
".spec.jsx",
|
|
16
16
|
".d.ts",
|
|
17
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* ActionDock 配置加密主密钥与身份标记的规范文件名(位于密钥目录 keys 内)。
|
|
20
|
+
*
|
|
21
|
+
* 命名事实源为 core/security/config-key.ts 的 resolveKeyPaths:
|
|
22
|
+
* 密钥文件 config.key、身份标记 config.key.id。
|
|
23
|
+
*/
|
|
24
|
+
const CONFIG_KEY_FILE_NAMES = ["config.key", "config.key.id"];
|
|
25
|
+
/**
|
|
26
|
+
* 判断文件名是否为 ActionDock 配置加密密钥资产。
|
|
27
|
+
*
|
|
28
|
+
* 密钥发布采用原子硬链接,临时文件名为 .{原名}.tmp.{pid}.{uuid}
|
|
29
|
+
* (core/security/config-key.ts 的 publishAtomically);失败清理为尽力而为,
|
|
30
|
+
* 残留临时名同样可能出现在密钥目录内,须一并识别。
|
|
31
|
+
*/
|
|
32
|
+
function isConfigKeyArtifactName(fileName) {
|
|
33
|
+
for (const name of CONFIG_KEY_FILE_NAMES) {
|
|
34
|
+
if (fileName === name || fileName.startsWith(`.${name}.tmp.`)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
/** 运行环境配置不得进入分发产物;普通业务 bindings.json 不受影响。 */
|
|
41
|
+
export function isRuntimeConfigFile(relPath) {
|
|
42
|
+
const segments = relPath.replace(/\\/g, "/").split("/");
|
|
43
|
+
const fileName = segments.at(-1) ?? "";
|
|
44
|
+
if (segments.includes(".actiondock") || fileName === "profiles.json") {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
// 配置加密主密钥仅落在密钥目录 keys 内:dataDir 定位为 <dataDir>/keys,
|
|
48
|
+
// 主目录定位为 .actiondock/keys(后者已被上方 .actiondock 分支覆盖)。
|
|
49
|
+
// 仅在 keys 目录上下文下精确排除,普通业务 keys 目录内的其他文件
|
|
50
|
+
// (如 keys/data.json、keys/api.key)与密钥目录外的同名业务资产不受影响。
|
|
51
|
+
return segments.at(-2) === "keys" && isConfigKeyArtifactName(fileName);
|
|
52
|
+
}
|
|
18
53
|
/**
|
|
19
54
|
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
20
55
|
*/
|
|
@@ -24,7 +59,7 @@ export function isIgnoredPath(relPath) {
|
|
|
24
59
|
normalized.includes("/node_modules/") ||
|
|
25
60
|
normalized.startsWith(".git/") ||
|
|
26
61
|
normalized.startsWith("dist/") ||
|
|
27
|
-
normalized
|
|
62
|
+
isRuntimeConfigFile(normalized) ||
|
|
28
63
|
TEST_FILE_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
|
|
29
64
|
}
|
|
30
65
|
/**
|
package/dist/npm-pack.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type RunPackageManagerOptions } from "@actiondock/core/project";
|
|
1
2
|
/**
|
|
2
3
|
* 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz)。
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* 使用 --json=false 确保标准输出仅输出单行压缩包文件名(显式覆盖 npm_config_json 等环境配置)。
|
|
5
|
+
* 复用统一包管理器启动解析与驱动底座,以 shell: false 执行,完整保留路径参数与特殊字符边界。
|
|
5
6
|
*/
|
|
6
|
-
export declare function runNpmPack(stagingPkgDir: string, packDestination: string,
|
|
7
|
+
export declare function runNpmPack(stagingPkgDir: string, packDestination: string, _fallbackName?: string, options?: RunPackageManagerOptions): Promise<string>;
|
package/dist/npm-pack.js
CHANGED
|
@@ -1,69 +1,47 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { join } from "node:path";
|
|
3
|
+
import { runPackageManager } from "@actiondock/core/project";
|
|
4
4
|
import { BuilderError } from "./errors.js";
|
|
5
5
|
/**
|
|
6
6
|
* 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz)。
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* 使用 --json=false 确保标准输出仅输出单行压缩包文件名(显式覆盖 npm_config_json 等环境配置)。
|
|
8
|
+
* 复用统一包管理器启动解析与驱动底座,以 shell: false 执行,完整保留路径参数与特殊字符边界。
|
|
9
9
|
*/
|
|
10
|
-
export async function runNpmPack(stagingPkgDir, packDestination,
|
|
11
|
-
const
|
|
12
|
-
|
|
10
|
+
export async function runNpmPack(stagingPkgDir, packDestination, _fallbackName, options) {
|
|
11
|
+
const args = [
|
|
12
|
+
"pack",
|
|
13
|
+
"--ignore-scripts",
|
|
14
|
+
"--json=false",
|
|
15
|
+
"--pack-destination",
|
|
16
|
+
packDestination,
|
|
17
|
+
];
|
|
18
|
+
let procResult;
|
|
19
|
+
try {
|
|
20
|
+
procResult = await runPackageManager("npm", args, {
|
|
21
|
+
...options,
|
|
13
22
|
cwd: stagingPkgDir,
|
|
14
|
-
shell: process.platform === "win32",
|
|
15
23
|
});
|
|
16
|
-
let stdoutBuf = "";
|
|
17
|
-
let stderrBuf = "";
|
|
18
|
-
child.stdout.on("data", (chunk) => (stdoutBuf += chunk));
|
|
19
|
-
child.stderr.on("data", (chunk) => (stderrBuf += chunk));
|
|
20
|
-
child.on("error", rejectPromise);
|
|
21
|
-
child.on("close", (code) => {
|
|
22
|
-
if (code === 0) {
|
|
23
|
-
resolvePromise({ stdout: stdoutBuf, stderr: stderrBuf });
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
rejectPromise(new BuilderError(`npm pack exited with code ${code}. Output:\n${stdoutBuf}\n${stderrBuf}`));
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
// 优先解析 --json 结构化输出中的压缩包文件名
|
|
31
|
-
let generatedName;
|
|
32
|
-
const trimmed = stdout.trim();
|
|
33
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
34
|
-
try {
|
|
35
|
-
const parsed = JSON.parse(trimmed);
|
|
36
|
-
let items;
|
|
37
|
-
if (Array.isArray(parsed)) {
|
|
38
|
-
items = parsed;
|
|
39
|
-
}
|
|
40
|
-
else if (typeof parsed === "object" && parsed !== null) {
|
|
41
|
-
// npm >= 11 的 pack --json 输出为按包名索引的对象,取全部包含 filename 的条目
|
|
42
|
-
items = Object.values(parsed).filter((entry) => typeof entry === "object" && entry !== null && "filename" in entry);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
items = [];
|
|
46
|
-
}
|
|
47
|
-
// 多包输出时取最后一个 filename(npm 按依赖顺序逐包输出,末尾为当前项目产物)
|
|
48
|
-
const filenames = items
|
|
49
|
-
.map((item) => item.filename)
|
|
50
|
-
.filter((name) => typeof name === "string" && name.length > 0);
|
|
51
|
-
const filename = filenames[filenames.length - 1];
|
|
52
|
-
if (filename !== undefined) {
|
|
53
|
-
generatedName = filename;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
// 回退到尾部行解析
|
|
58
|
-
}
|
|
59
24
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new BuilderError(`npm pack failed to execute: ${err?.message || String(err)}`);
|
|
27
|
+
}
|
|
28
|
+
if (procResult.status !== 0) {
|
|
29
|
+
throw new BuilderError(`npm pack exited with code ${procResult.status}. Output:\n${procResult.stdout}\n${procResult.stderr}`);
|
|
30
|
+
}
|
|
31
|
+
const stdout = procResult.stdout;
|
|
32
|
+
const lines = stdout
|
|
33
|
+
.split(/\r?\n/)
|
|
34
|
+
.map((line) => line.trim())
|
|
35
|
+
.filter((line) => line.length > 0);
|
|
36
|
+
if (lines.length === 0) {
|
|
37
|
+
throw new BuilderError(`npm pack produced empty stdout; expected tarball filename. Output:\n${stdout}\nStderr:\n${procResult.stderr}`);
|
|
38
|
+
}
|
|
39
|
+
if (lines.length > 1) {
|
|
40
|
+
throw new BuilderError(`npm pack produced unexpected multi-line output; expected single tarball filename. Output:\n${stdout}`);
|
|
41
|
+
}
|
|
42
|
+
const generatedName = lines[0];
|
|
43
|
+
if (!generatedName.endsWith(".tgz")) {
|
|
44
|
+
throw new BuilderError(`npm pack produced unexpected output line '${generatedName}'; expected .tgz filename. Output:\n${stdout}`);
|
|
67
45
|
}
|
|
68
46
|
const generatedTarball = join(packDestination, generatedName);
|
|
69
47
|
if (!existsSync(generatedTarball)) {
|
package/dist/planner.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, join, relative, resolve } from "node:path";
|
|
3
|
-
import { ActionDockError, ACTION_NOT_FOUND, UNDECLARED_ACTION_DEPENDENCY, loadProjectConfig, } from "@actiondock/core";
|
|
4
|
-
import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, } from "@actiondock/core/project";
|
|
3
|
+
import { ActionDockError, ACTION_NOT_FOUND, PACKAGE_NOT_FOUND, UNDECLARED_ACTION_DEPENDENCY, loadProjectConfig, } from "@actiondock/core";
|
|
4
|
+
import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, loadActionBindings, hasRemoteActionBinding, validateCanonicalActionRef, } from "@actiondock/core/project";
|
|
5
5
|
import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, } from "@actiondock/core/graph";
|
|
6
6
|
import { PlannerError } from "./errors.js";
|
|
7
7
|
import { assertRelativeDependenciesIntegrity } from "./dependency-check.js";
|
|
8
8
|
import { computeLockfileInfo } from "./lockfile-resolver.js";
|
|
9
|
-
import { extractExternalDependencies, isIgnoredPath } from "./dependency-resolver.js";
|
|
9
|
+
import { extractExternalDependencies, isIgnoredPath, isRuntimeConfigFile } from "./dependency-resolver.js";
|
|
10
10
|
/**
|
|
11
11
|
* 递归扫描指定目录下的文件,返回绝对路径列表。
|
|
12
12
|
* 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
|
|
@@ -15,6 +15,19 @@ import { extractExternalDependencies, isIgnoredPath } from "./dependency-resolve
|
|
|
15
15
|
function walkDirectory(dir) {
|
|
16
16
|
return traverseDirectory(dir).map((entry) => entry.fullPath);
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* 判断动作引用是否为完全限定标识符(包含包命名空间与动作名,且不含通配符与非法字符)。
|
|
20
|
+
* 统一复用 core 的 validateCanonicalActionRef 单一事实源。
|
|
21
|
+
*/
|
|
22
|
+
function isFullyQualifiedActionRef(ref) {
|
|
23
|
+
try {
|
|
24
|
+
validateCanonicalActionRef(ref);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
/**
|
|
19
32
|
* 构造备用清单映射,仅读取文件系统条目与配置声明,杜绝 AST 源码分析与动态代码执行。
|
|
20
33
|
*/
|
|
@@ -181,81 +194,138 @@ export class SelectionPlanner {
|
|
|
181
194
|
*/
|
|
182
195
|
resolveClosure(root, manifest, config, initialActionIds) {
|
|
183
196
|
const resolvedActionIds = new Set();
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
const
|
|
197
|
+
const remoteActionRefs = new Set();
|
|
198
|
+
const visitedEdges = new Set();
|
|
199
|
+
const queuedEdges = new Set();
|
|
200
|
+
const queue = [];
|
|
201
|
+
const enqueueDependency = (ref, callerRoot, callerPkgId) => {
|
|
202
|
+
const edgeKey = `${callerRoot}:::${ref}`;
|
|
203
|
+
if (!visitedEdges.has(edgeKey) && !queuedEdges.has(edgeKey)) {
|
|
204
|
+
queuedEdges.add(edgeKey);
|
|
205
|
+
queue.push({ ref, callerRoot, callerPkgId });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
for (const id of initialActionIds) {
|
|
209
|
+
enqueueDependency(id, root, config?.id);
|
|
210
|
+
}
|
|
187
211
|
const externalActionRoots = new Map();
|
|
188
212
|
const externalActionEntries = new Map();
|
|
189
213
|
const manifestActions = manifest.actions || {};
|
|
190
214
|
const graph = new PackageGraphBuilder({ root, allowDevLinks: true }).buildSync();
|
|
191
215
|
const catalog = new DefaultActionCatalog(graph);
|
|
192
216
|
while (queue.length > 0) {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
217
|
+
const currentItem = queue.shift();
|
|
218
|
+
const currentId = currentItem.ref;
|
|
219
|
+
const currentCallerRoot = currentItem.callerRoot;
|
|
220
|
+
const currentCallerPkgId = currentItem.callerPkgId;
|
|
221
|
+
const currentEdgeKey = `${currentCallerRoot}:::${currentId}`;
|
|
222
|
+
queuedEdges.delete(currentEdgeKey);
|
|
223
|
+
visitedEdges.add(currentEdgeKey);
|
|
224
|
+
let entry = currentCallerRoot === root
|
|
225
|
+
? (manifestActions[currentId] || config?.actions?.[currentId])
|
|
226
|
+
: undefined;
|
|
227
|
+
let entryRoot = currentCallerRoot;
|
|
228
|
+
let targetPkgId = currentCallerRoot === root ? config?.id : undefined;
|
|
200
229
|
if (!entry) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
let localResolved = null;
|
|
231
|
+
let resolveError = null;
|
|
232
|
+
try {
|
|
233
|
+
localResolved = resolveAction(currentId, {
|
|
234
|
+
graph,
|
|
235
|
+
catalog,
|
|
236
|
+
caller: currentCallerPkgId || config?.id,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
resolveError = err;
|
|
241
|
+
}
|
|
242
|
+
if (localResolved) {
|
|
243
|
+
const resolvedPkgId = localResolved.package.id;
|
|
244
|
+
const resolvedActionId = localResolved.ref.actionId;
|
|
245
|
+
const extNode = graph.packages.get(resolvedPkgId);
|
|
246
|
+
if (extNode && existsSync(extNode.root)) {
|
|
247
|
+
const extRoot = extNode.root;
|
|
248
|
+
let extConfig;
|
|
249
|
+
try {
|
|
250
|
+
extConfig = loadProjectConfig(extRoot);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
extConfig = {
|
|
254
|
+
id: resolvedPkgId,
|
|
255
|
+
name: resolvedPkgId,
|
|
256
|
+
version: "0.1.0",
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
const externalManifest = loadManifest(extRoot);
|
|
260
|
+
const externalEntry = externalManifest?.actions?.[resolvedActionId] ||
|
|
261
|
+
extConfig?.actions?.[resolvedActionId];
|
|
262
|
+
if (!externalEntry) {
|
|
263
|
+
resolveError = new ActionDockError(UNDECLARED_ACTION_DEPENDENCY, `UNDECLARED_ACTION_DEPENDENCY: Action '${resolvedActionId}' is not declared in package '${resolvedPkgId}' manifest`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
entry = externalEntry;
|
|
267
|
+
entryRoot = extRoot;
|
|
268
|
+
targetPkgId = resolvedPkgId;
|
|
269
|
+
// 依赖包 actiondock.json 中声明的传递依赖闭包(按外部包自身作用域入队,不继承根配置)
|
|
270
|
+
const extPkgUses = extConfig?.uses;
|
|
271
|
+
if (Array.isArray(extPkgUses)) {
|
|
272
|
+
for (const u of extPkgUses) {
|
|
273
|
+
const trimmedUse = typeof u === "string" ? u.trim() : "";
|
|
274
|
+
if (trimmedUse) {
|
|
275
|
+
enqueueDependency(trimmedUse, extRoot, resolvedPkgId);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
217
280
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
281
|
+
}
|
|
282
|
+
if (!entry) {
|
|
283
|
+
// 仅在明确为包未找到或动作未找到时允许回退至远端绑定检查;严禁掩盖损坏清单、无效引用或版本冲突等非缺失异常
|
|
284
|
+
const isMissing = !resolveError ||
|
|
285
|
+
(resolveError instanceof ActionDockError &&
|
|
286
|
+
(resolveError.code === PACKAGE_NOT_FOUND ||
|
|
287
|
+
resolveError.code === ACTION_NOT_FOUND));
|
|
288
|
+
if (!isMissing) {
|
|
289
|
+
throw resolveError;
|
|
223
290
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const trimmedUse = typeof u === "string" ? u.trim() : "";
|
|
231
|
-
if (trimmedUse &&
|
|
232
|
-
!resolvedActionIds.has(trimmedUse) &&
|
|
233
|
-
!queued.has(trimmedUse)) {
|
|
234
|
-
queued.add(trimmedUse);
|
|
235
|
-
queue.push(trimmedUse);
|
|
236
|
-
}
|
|
291
|
+
// 仅对完全限定依赖(pkg/action),且发起调用的当前包根目录绑定配置中包含精确 remote 条目时,识别为远端依赖
|
|
292
|
+
if (isFullyQualifiedActionRef(currentId)) {
|
|
293
|
+
const bindings = loadActionBindings(currentCallerRoot);
|
|
294
|
+
if (hasRemoteActionBinding(bindings, currentId)) {
|
|
295
|
+
remoteActionRefs.add(currentId);
|
|
296
|
+
continue;
|
|
237
297
|
}
|
|
238
298
|
}
|
|
299
|
+
if (resolveError) {
|
|
300
|
+
throw resolveError;
|
|
301
|
+
}
|
|
302
|
+
throw new ActionDockError(ACTION_NOT_FOUND, `Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`);
|
|
239
303
|
}
|
|
240
304
|
}
|
|
241
|
-
|
|
242
|
-
|
|
305
|
+
// 验证本地 entry 文件物理存在,不得跳过本地 entry 缺失
|
|
306
|
+
const resolvedPath = resolve(entryRoot, entry.entry);
|
|
307
|
+
if (!existsSync(resolvedPath)) {
|
|
308
|
+
throw new PlannerError(`Action '${currentId}' entry file not found on disk: ${entry.entry}`, "ENTRY_FILE_NOT_FOUND");
|
|
243
309
|
}
|
|
244
310
|
if (entryRoot !== root) {
|
|
245
311
|
externalActionRoots.set(currentId, entryRoot);
|
|
246
312
|
externalActionEntries.set(currentId, entry);
|
|
247
313
|
}
|
|
314
|
+
// 物化 action 按既有唯一集合去重,若已物化则跳过重复 uses 展开
|
|
315
|
+
if (resolvedActionIds.has(currentId)) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
248
318
|
resolvedActionIds.add(currentId);
|
|
249
319
|
if (entry.uses && Array.isArray(entry.uses)) {
|
|
250
320
|
for (const depId of entry.uses) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
321
|
+
const trimmedDep = typeof depId === "string" ? depId.trim() : "";
|
|
322
|
+
if (trimmedDep) {
|
|
323
|
+
enqueueDependency(trimmedDep, entryRoot, targetPkgId || currentCallerPkgId);
|
|
254
324
|
}
|
|
255
325
|
}
|
|
256
326
|
}
|
|
257
327
|
}
|
|
258
|
-
return { resolvedActionIds, externalActionRoots, externalActionEntries };
|
|
328
|
+
return { resolvedActionIds, externalActionRoots, externalActionEntries, remoteActionRefs };
|
|
259
329
|
}
|
|
260
330
|
/**
|
|
261
331
|
* 构造 Action 依赖结构(闭包结果物化为 ActionDependency 列表)。
|
|
@@ -312,6 +382,9 @@ export class SelectionPlanner {
|
|
|
312
382
|
declaredFiles.add(f);
|
|
313
383
|
}
|
|
314
384
|
for (const declaredRel of declaredFiles) {
|
|
385
|
+
if (isRuntimeConfigFile(declaredRel)) {
|
|
386
|
+
throw new PlannerError(`Runtime configuration file '${declaredRel}' cannot be explicitly declared as build asset or file`, "RESTRICTED_CONFIG_ASSET");
|
|
387
|
+
}
|
|
315
388
|
const resolvedFile = resolve(root, declaredRel);
|
|
316
389
|
assertPathWithinRoot(root, resolvedFile, "files");
|
|
317
390
|
if (!existsSync(resolvedFile)) {
|
|
@@ -322,7 +395,10 @@ export class SelectionPlanner {
|
|
|
322
395
|
const walked = walkDirectory(resolvedFile);
|
|
323
396
|
for (const f of walked) {
|
|
324
397
|
const rel = relative(root, f).replace(/\\/g, "/");
|
|
325
|
-
if (!isIgnoredPath(rel) &&
|
|
398
|
+
if (!isIgnoredPath(rel) &&
|
|
399
|
+
!isRuntimeConfigFile(rel) &&
|
|
400
|
+
!actionPathSet.has(f) &&
|
|
401
|
+
!filePathSet.has(rel)) {
|
|
326
402
|
filePathSet.add(rel);
|
|
327
403
|
modulesAndAssets.push({
|
|
328
404
|
path: rel,
|
|
@@ -334,7 +410,10 @@ export class SelectionPlanner {
|
|
|
334
410
|
}
|
|
335
411
|
else if (stat.isFile()) {
|
|
336
412
|
const rel = relative(root, resolvedFile).replace(/\\/g, "/");
|
|
337
|
-
if (!
|
|
413
|
+
if (!isIgnoredPath(rel) &&
|
|
414
|
+
!isRuntimeConfigFile(rel) &&
|
|
415
|
+
!actionPathSet.has(resolvedFile) &&
|
|
416
|
+
!filePathSet.has(rel)) {
|
|
338
417
|
filePathSet.add(rel);
|
|
339
418
|
modulesAndAssets.push({
|
|
340
419
|
path: rel,
|
|
@@ -359,6 +438,9 @@ export class SelectionPlanner {
|
|
|
359
438
|
declaredAssets.add(a);
|
|
360
439
|
}
|
|
361
440
|
for (const declaredRel of declaredAssets) {
|
|
441
|
+
if (isRuntimeConfigFile(declaredRel)) {
|
|
442
|
+
throw new PlannerError(`Runtime configuration file '${declaredRel}' cannot be explicitly declared as build asset or file`, "RESTRICTED_CONFIG_ASSET");
|
|
443
|
+
}
|
|
362
444
|
const resolvedAsset = resolve(root, declaredRel);
|
|
363
445
|
assertPathWithinRoot(root, resolvedAsset, "assets");
|
|
364
446
|
if (!existsSync(resolvedAsset)) {
|
|
@@ -369,7 +451,7 @@ export class SelectionPlanner {
|
|
|
369
451
|
const walked = walkDirectory(resolvedAsset);
|
|
370
452
|
for (const f of walked) {
|
|
371
453
|
const rel = relative(root, f).replace(/\\/g, "/");
|
|
372
|
-
if (!assetPathSet.has(rel)) {
|
|
454
|
+
if (!isIgnoredPath(rel) && !isRuntimeConfigFile(rel) && !assetPathSet.has(rel)) {
|
|
373
455
|
assetPathSet.add(rel);
|
|
374
456
|
modulesAndAssets.push({
|
|
375
457
|
path: rel,
|
|
@@ -381,7 +463,7 @@ export class SelectionPlanner {
|
|
|
381
463
|
}
|
|
382
464
|
else if (stat.isFile()) {
|
|
383
465
|
const rel = relative(root, resolvedAsset).replace(/\\/g, "/");
|
|
384
|
-
if (!assetPathSet.has(rel)) {
|
|
466
|
+
if (!isIgnoredPath(rel) && !isRuntimeConfigFile(rel) && !assetPathSet.has(rel)) {
|
|
385
467
|
assetPathSet.add(rel);
|
|
386
468
|
modulesAndAssets.push({
|
|
387
469
|
path: rel,
|
|
@@ -397,7 +479,7 @@ export class SelectionPlanner {
|
|
|
397
479
|
const assetFiles = walkDirectory(defaultAssetsDir);
|
|
398
480
|
for (const file of assetFiles) {
|
|
399
481
|
const rel = relative(root, file).replace(/\\/g, "/");
|
|
400
|
-
if (!assetPathSet.has(rel)) {
|
|
482
|
+
if (!isIgnoredPath(rel) && !isRuntimeConfigFile(rel) && !assetPathSet.has(rel)) {
|
|
401
483
|
assetPathSet.add(rel);
|
|
402
484
|
modulesAndAssets.push({
|
|
403
485
|
path: rel,
|
|
@@ -458,7 +540,7 @@ export class SelectionPlanner {
|
|
|
458
540
|
finalPlaybooks = selectedPlaybooks.filter((pb) => {
|
|
459
541
|
if (!pb.actions || pb.actions.length === 0)
|
|
460
542
|
return true;
|
|
461
|
-
return pb.actions.every((a) => closure.resolvedActionIds.has(a));
|
|
543
|
+
return pb.actions.every((a) => closure.resolvedActionIds.has(a) || closure.remoteActionRefs.has(a));
|
|
462
544
|
});
|
|
463
545
|
}
|
|
464
546
|
const actionDependencies = this.buildActionDependencies(root, closure, manifest);
|
|
@@ -498,6 +580,7 @@ export class SelectionPlanner {
|
|
|
498
580
|
configDefs: config.config,
|
|
499
581
|
lockfile: lockfileInfo,
|
|
500
582
|
externalProjectRoots: externalRoots,
|
|
583
|
+
remoteActionRefs: Array.from(closure.remoteActionRefs || []).sort(),
|
|
501
584
|
metadata: {
|
|
502
585
|
plannedAt: new Date().toISOString(),
|
|
503
586
|
schemaVersion: 2,
|
package/dist/skill/templates.js
CHANGED
|
@@ -220,19 +220,24 @@ ${desc}`;
|
|
|
220
220
|
|
|
221
221
|
本技能为 ActionDock 源码型技能包。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
|
|
222
222
|
|
|
223
|
-
###
|
|
223
|
+
### 注册链接至全局路由表
|
|
224
224
|
|
|
225
|
-
本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root
|
|
225
|
+
本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\`。标准流程为执行注册链接,将其登记至全局路由表:
|
|
226
226
|
|
|
227
227
|
\`\`\`bash
|
|
228
|
-
# 安装生产依赖
|
|
229
|
-
cd "<skill_root>" && npm install --omit=dev
|
|
230
|
-
|
|
231
228
|
# 注册并链接到 ActionDock
|
|
232
229
|
ad link "<skill_root>"
|
|
233
230
|
\`\`\`
|
|
234
231
|
|
|
235
|
-
> \`ad
|
|
232
|
+
> 登记后,在任意工作目录通过完全限定 ID 本地调用 \`ad run\` 时,主动作与宿主快照中已有的本地子动作均会在实际调用前按需准备依赖。\`ad link\` 天然具备幂等性,可安全重复调用。
|
|
233
|
+
|
|
234
|
+
### 显式手动预装依赖(可选)
|
|
235
|
+
|
|
236
|
+
使用者明确要求预先准备环境或修复已有依赖时,按项目实际使用的包管理器手动安装生产依赖。若本次调用指定了 \`--no-install\` 或 \`ACTIONDOCK_AUTO_INSTALL=false\`,整条调用链均保持禁装策略并报告依赖未就绪,智能体不能擅自执行安装命令绕过策略。以下为 npm 项目的预装示例:
|
|
237
|
+
|
|
238
|
+
\`\`\`bash
|
|
239
|
+
cd "<skill_root>" && npm install --omit=dev
|
|
240
|
+
\`\`\``;
|
|
236
241
|
const sDescribe = `### 动作参数契约按需调阅
|
|
237
242
|
|
|
238
243
|
在调用未知参数的 Action 前,可在终端执行命令查阅该 Action 的输入输出模式与详细说明:
|
package/dist/types.d.ts
CHANGED
|
@@ -131,6 +131,8 @@ export interface BuildPlan {
|
|
|
131
131
|
configDefs?: Record<string, unknown>;
|
|
132
132
|
/** 依赖闭包包含的外部已链接包项目根目录列表(去重) */
|
|
133
133
|
externalProjectRoots?: string[];
|
|
134
|
+
/** 经由远端绑定配置解析的外部依赖 Action 引用列表(只读展示,不加入本地源码构建闭包) */
|
|
135
|
+
remoteActionRefs?: string[];
|
|
134
136
|
/** 检测到的锁文件信息 */
|
|
135
137
|
lockfile?: LockfileInfo;
|
|
136
138
|
/** 规划元数据 */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"description": "ActionDock Build Planner, distribution packaging, and Skill exporter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"test": "node ../../scripts/run-tests.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@actiondock/core": "^2.
|
|
33
|
-
"@actiondock/sdk": "^2.
|
|
32
|
+
"@actiondock/core": "^2.21.0",
|
|
33
|
+
"@actiondock/sdk": "^2.21.0"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"actiondock",
|