@actiondock/core 2.13.0 → 2.15.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/catalog/discovery.d.ts +3 -3
- package/dist/catalog/graph.d.ts +6 -8
- package/dist/catalog/graph.js +197 -62
- package/dist/catalog/resolve-playbook.d.ts +2 -2
- package/dist/catalog/resolve-playbook.js +12 -53
- package/dist/execution/types.d.ts +2 -2
- package/dist/host/routing.d.ts +1 -1
- package/dist/host/routing.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/package/runtime.d.ts +2 -2
- package/dist/package/runtime.js +3 -1
- package/dist/package/static-index.d.ts +4 -6
- package/dist/package/static-index.js +12 -60
- package/dist/package/types.d.ts +5 -5
- package/dist/project/index.d.ts +1 -0
- package/dist/project/index.js +1 -0
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +4 -2
- package/dist/project/loader.js +38 -34
- package/dist/project/manifest.d.ts +2 -0
- package/dist/project/manifest.js +17 -1
- package/dist/project/skill-dir.d.ts +72 -0
- package/dist/project/skill-dir.js +244 -0
- package/dist/project/types.d.ts +31 -0
- package/dist/registry/registry.js +27 -15
- package/dist/runtime/context.d.ts +3 -3
- package/dist/runtime/standalone-commands.js +8 -2
- package/dist/runtime/standalone.d.ts +2 -0
- package/dist/storage/sqlite-driver.d.ts +0 -1
- package/dist/storage/sqlite-driver.js +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/semver.d.ts +2 -1
- package/dist/utils/semver.js +11 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -7
- package/dist/utils/warning.d.ts +0 -1
- package/dist/utils/warning.js +0 -52
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, resolve } from "node:path";
|
|
1
|
+
import { resolve } from "node:path";
|
|
3
2
|
import { loadPlaybooks } from "../project/loader.js";
|
|
4
|
-
import { MANIFEST_FILE_NAME } from "../project/manifest.js";
|
|
5
3
|
/**
|
|
6
4
|
* 将增量声明合并到既有 Action 规范之上(纯函数,单一事实源)。
|
|
7
5
|
*
|
|
@@ -86,67 +84,21 @@ export function buildStaticActionMap(input) {
|
|
|
86
84
|
}
|
|
87
85
|
/**
|
|
88
86
|
* 静态读取并聚合指定包的 Playbook 规范索引。
|
|
89
|
-
*
|
|
90
|
-
* 聚合来源:磁盘规程目录扫描(以 actiondock.json 声明为事实源)与
|
|
91
|
-
* 项目配置中声明的 playbooks;同键时配置声明覆盖磁盘扫描结果。
|
|
87
|
+
* 统一复用 project/loader 的 loadPlaybooks(单一事实源),不再自行读文件或吞异常。
|
|
92
88
|
*/
|
|
93
89
|
export function buildStaticPlaybookMap(input) {
|
|
94
90
|
const { packageRoot, packageId, projectConfig } = input;
|
|
95
91
|
const map = new Map();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
id: def.id,
|
|
107
|
-
packageId,
|
|
108
|
-
description: def.description,
|
|
109
|
-
actions: def.actions,
|
|
110
|
-
content: def.content,
|
|
111
|
-
filePath: def.filePath,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
catch (err) {
|
|
116
|
-
// 规程加载失败(清单声明非法、规程文件损坏等)输出告警并跳过磁盘部分,保持返回可用列表
|
|
117
|
-
console.warn(`[App] Failed to load playbooks for package '${packageId}' from '${dirPath}': ${err?.message || String(err)}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
// 读取项目配置文件中的 playbooks
|
|
122
|
-
if (projectConfig && projectConfig.playbooks) {
|
|
123
|
-
const rawPlaybooks = projectConfig.playbooks;
|
|
124
|
-
if (typeof rawPlaybooks === "object" && rawPlaybooks !== null) {
|
|
125
|
-
for (const [id, item] of Object.entries(rawPlaybooks)) {
|
|
126
|
-
const existing = map.get(id);
|
|
127
|
-
let content = item.content ?? existing?.content ?? "";
|
|
128
|
-
let filePath = item.entry && packageRoot
|
|
129
|
-
? resolve(packageRoot, item.entry)
|
|
130
|
-
: existing?.filePath;
|
|
131
|
-
if (!content && filePath && existsSync(filePath)) {
|
|
132
|
-
try {
|
|
133
|
-
content = readFileSync(filePath, "utf-8");
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
// 读取失败(权限、IO 错误等)输出告警并保持空正文,不再无声吞没
|
|
137
|
-
console.warn(`[App] Failed to read playbook '${id}' content from '${filePath}' in package '${packageId}': ${err?.message || String(err)}`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
map.set(id, {
|
|
141
|
-
id,
|
|
142
|
-
packageId,
|
|
143
|
-
description: item.description ?? existing?.description,
|
|
144
|
-
actions: item.actions ?? existing?.actions,
|
|
145
|
-
content,
|
|
146
|
-
filePath,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
92
|
+
const loaded = loadPlaybooks(packageRoot || "", projectConfig.playbooksDir, projectConfig);
|
|
93
|
+
for (const [id, def] of loaded) {
|
|
94
|
+
map.set(id, {
|
|
95
|
+
id: def.id,
|
|
96
|
+
packageId,
|
|
97
|
+
description: def.description,
|
|
98
|
+
actions: def.actions,
|
|
99
|
+
content: def.content,
|
|
100
|
+
filePath: def.filePath,
|
|
101
|
+
});
|
|
150
102
|
}
|
|
151
103
|
return map;
|
|
152
104
|
}
|
package/dist/package/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ActionDefinition, ExecutionEvent, ExecutionResult, JsonValue, Logger, ProcessAPI, RunRecord } from "@actiondock/sdk";
|
|
2
2
|
import type { ActionInvoker, CancelResult, ExecutionTicket, LocalActionResolver } from "../execution/types.js";
|
|
3
3
|
import type { InvocationContext, RunOptions } from "../invocation/types.js";
|
|
4
|
-
import type { ConfigItemDefinition, ProjectConfig } from "../project/types.js";
|
|
4
|
+
import type { ConfigItemDefinition, ProjectConfig, RuntimeProjectConfig } from "../project/types.js";
|
|
5
5
|
import type { EventSink } from "../runtime/events.js";
|
|
6
6
|
import type { RuntimePlatform, StorageFactory, StorageFactoryOptions } from "../platform/types.js";
|
|
7
7
|
import type { RuntimeStorage, StateEntry } from "../storage/types.js";
|
|
@@ -156,8 +156,8 @@ export interface StateOptions {
|
|
|
156
156
|
export interface PackageRuntimeOptions {
|
|
157
157
|
/** 项目根目录绝对物理路径 */
|
|
158
158
|
packageRoot?: string;
|
|
159
|
-
/** 项目配置文件 (actiondock.json)
|
|
160
|
-
projectConfig?: ProjectConfig;
|
|
159
|
+
/** 项目配置文件 (actiondock.json) 对象或运行时规程来源配置 */
|
|
160
|
+
projectConfig?: ProjectConfig | RuntimeProjectConfig;
|
|
161
161
|
/** 数据持久化存储目录 */
|
|
162
162
|
dataDir?: string;
|
|
163
163
|
/** 自定义家目录绝对路径 */
|
|
@@ -241,8 +241,8 @@ export interface PackageRuntime {
|
|
|
241
241
|
readonly generationId?: string;
|
|
242
242
|
/** 包根目录绝对物理路径 */
|
|
243
243
|
readonly packageRoot?: string;
|
|
244
|
-
/** 项目配置对象(actiondock.json
|
|
245
|
-
readonly projectConfig: ProjectConfig;
|
|
244
|
+
/** 项目配置对象(actiondock.json 解析结果或运行时配置) */
|
|
245
|
+
readonly projectConfig: ProjectConfig | RuntimeProjectConfig;
|
|
246
246
|
/** 是否采用纯内存运行模式 */
|
|
247
247
|
readonly inMemory?: boolean;
|
|
248
248
|
/**
|
package/dist/project/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./init.js";
|
|
|
3
3
|
export * from "./loader.js";
|
|
4
4
|
export * from "./lockfile.js";
|
|
5
5
|
export * from "./manifest.js";
|
|
6
|
+
export * from "./skill-dir.js";
|
|
6
7
|
export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
7
8
|
export * from "./types.js";
|
|
8
9
|
export * from "./types-generator.js";
|
package/dist/project/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export * from "./init.js";
|
|
|
3
3
|
export * from "./loader.js";
|
|
4
4
|
export * from "./lockfile.js";
|
|
5
5
|
export * from "./manifest.js";
|
|
6
|
+
export * from "./skill-dir.js";
|
|
6
7
|
export { SNAPSHOT_TRACKED_FILES, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
7
8
|
export * from "./types.js";
|
|
8
9
|
export * from "./types-generator.js";
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.
|
|
89
|
+
"@actiondock/sdk": "^2.15.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.15.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
package/dist/project/loader.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActionDefinition } from "@actiondock/sdk";
|
|
2
2
|
import { type ModuleLoader } from "../platform/module-loader.js";
|
|
3
|
-
import type { ActionDockManifest, PlaybookDefinition, PlaybookManifestEntry, ProjectConfig } from "./types.js";
|
|
3
|
+
import type { ActionDockManifest, PlaybookDefinition, PlaybookManifestEntry, PlaybookSourceManifest, ProjectConfig, RuntimeProjectConfig } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
|
6
6
|
*
|
|
@@ -16,6 +16,8 @@ export declare function findProjectRoot(cwd?: string): string | null;
|
|
|
16
16
|
* @throws {Error} 若文件不存在或 JSON 格式错误、缺失必要字段
|
|
17
17
|
*/
|
|
18
18
|
export declare function loadProjectConfig(projectRoot: string): ProjectConfig;
|
|
19
|
+
/** 在运行时装配边界补全未声明的磁盘规程;显式规程来源(包括空集合)始终优先。 */
|
|
20
|
+
export declare function completeRuntimeProjectConfig(projectRoot: string | undefined, config: RuntimeProjectConfig): RuntimeProjectConfig;
|
|
19
21
|
export declare const ALLOWED_INSTALLERS: Set<string>;
|
|
20
22
|
/**
|
|
21
23
|
* 探测宿主系统中可用的包管理工具。
|
|
@@ -71,4 +73,4 @@ export declare function parsePlaybookContent(content: string, filePath: string,
|
|
|
71
73
|
* @param customManifest 可选的显式清单对象(若未提供则从磁盘加载)
|
|
72
74
|
* @returns Map<PlaybookId, PlaybookDefinition> 映射
|
|
73
75
|
*/
|
|
74
|
-
export declare function loadPlaybooks(projectRoot: string, _playbooksDir?: string, customManifest?: ActionDockManifest | null): Map<string, PlaybookDefinition>;
|
|
76
|
+
export declare function loadPlaybooks(projectRoot: string, _playbooksDir?: string, customManifest?: ActionDockManifest | PlaybookSourceManifest | null): Map<string, PlaybookDefinition>;
|
package/dist/project/loader.js
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
4
|
import { NodeModuleLoader, unwrapDefaultExport } from "../platform/module-loader.js";
|
|
5
5
|
import { ActionDockError, ACTION_LOAD_FAILED, describeActionLoadFailure, INVALID_ACTION_ID, INVALID_JSON, INVALID_PACKAGE_ID, INVALID_PLAYBOOK_ID, MANIFEST_LOAD_FAILED, PLAYBOOK_NOT_FOUND, PROJECT_CONFIG_NOT_FOUND, } from "../errors.js";
|
|
6
|
-
import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
|
|
6
|
+
import { loadManifest, normalizeFilePlaybookEntries, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
|
|
7
7
|
/**
|
|
8
8
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
|
9
9
|
*
|
|
@@ -68,7 +68,7 @@ export function loadProjectConfig(projectRoot) {
|
|
|
68
68
|
if (parsed.playbooksDir) {
|
|
69
69
|
assertWithinProjectRoot(projectRoot, parsed.playbooksDir, "playbooksDir");
|
|
70
70
|
}
|
|
71
|
-
return parsed;
|
|
71
|
+
return normalizeFilePlaybookEntries(parsed);
|
|
72
72
|
}
|
|
73
73
|
catch (err) {
|
|
74
74
|
if (err instanceof ActionDockError) {
|
|
@@ -77,6 +77,15 @@ export function loadProjectConfig(projectRoot) {
|
|
|
77
77
|
throw new ActionDockError(INVALID_JSON, `Failed to parse actiondock.json: ${err.message}`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
/** 在运行时装配边界补全未声明的磁盘规程;显式规程来源(包括空集合)始终优先。 */
|
|
81
|
+
export function completeRuntimeProjectConfig(projectRoot, config) {
|
|
82
|
+
if (!projectRoot || config.playbooks !== undefined)
|
|
83
|
+
return config;
|
|
84
|
+
const diskManifest = loadManifest(projectRoot);
|
|
85
|
+
return diskManifest?.playbooks === undefined
|
|
86
|
+
? config
|
|
87
|
+
: { ...config, playbooks: diskManifest.playbooks };
|
|
88
|
+
}
|
|
80
89
|
export const ALLOWED_INSTALLERS = new Set(["npm", "bun"]);
|
|
81
90
|
/**
|
|
82
91
|
* 异步探测包管理器可用性(通过执行 --version 并检查退出码)。
|
|
@@ -308,43 +317,38 @@ export function parsePlaybookContent(content, filePath, metadata) {
|
|
|
308
317
|
*/
|
|
309
318
|
export function loadPlaybooks(projectRoot, _playbooksDir = "playbooks", customManifest) {
|
|
310
319
|
const playbooks = new Map();
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
manifest
|
|
320
|
+
const isExplicitSource = customManifest !== undefined;
|
|
321
|
+
const manifest = isExplicitSource
|
|
322
|
+
? customManifest
|
|
323
|
+
: projectRoot ? loadManifest(projectRoot) : null;
|
|
324
|
+
for (const [playbookId, entry] of Object.entries(manifest?.playbooks || {})) {
|
|
325
|
+
if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
|
|
326
|
+
throw new ActionDockError(INVALID_PLAYBOOK_ID, `Invalid playbook ID '${playbookId}' in manifest. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
|
|
318
327
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
//
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return playbooks;
|
|
328
|
+
let content;
|
|
329
|
+
let filePath;
|
|
330
|
+
if (isExplicitSource && "content" in entry && typeof entry.content === "string") {
|
|
331
|
+
// 真实内联来源(包括空字符串)无需访问磁盘。
|
|
332
|
+
content = entry.content;
|
|
333
|
+
filePath = entry.entry ? (projectRoot ? resolve(projectRoot, entry.entry) : entry.entry) : "";
|
|
326
334
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
for (const [playbookId, pbEntry] of Object.entries(manifest.playbooks)) {
|
|
331
|
-
if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
|
|
332
|
-
throw new ActionDockError(INVALID_PLAYBOOK_ID, `Invalid playbook ID '${playbookId}' in actiondock.json. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
|
|
335
|
+
else {
|
|
336
|
+
if (!entry.entry || typeof entry.entry !== "string") {
|
|
337
|
+
throw new ActionDockError(PLAYBOOK_NOT_FOUND, `Playbook entry for '${playbookId}' must specify a valid file path or explicit inline content`);
|
|
333
338
|
}
|
|
334
|
-
|
|
335
|
-
assertPathWithinRoot(projectRoot,
|
|
336
|
-
if (!existsSync(
|
|
337
|
-
throw new ActionDockError(PLAYBOOK_NOT_FOUND, `Playbook file '${
|
|
339
|
+
filePath = resolve(projectRoot, entry.entry);
|
|
340
|
+
assertPathWithinRoot(projectRoot, filePath, `playbook entry '${entry.entry}'`);
|
|
341
|
+
if (!existsSync(filePath)) {
|
|
342
|
+
throw new ActionDockError(PLAYBOOK_NOT_FOUND, `Playbook file '${entry.entry}' for '${playbookId}' not found in ${projectRoot}`);
|
|
338
343
|
}
|
|
339
|
-
|
|
340
|
-
const def = parsePlaybookContent(content, fullPath, {
|
|
341
|
-
id: playbookId,
|
|
342
|
-
name: pbEntry?.name,
|
|
343
|
-
description: pbEntry.description,
|
|
344
|
-
actions: pbEntry.actions,
|
|
345
|
-
});
|
|
346
|
-
playbooks.set(playbookId, def);
|
|
344
|
+
content = readFileSync(filePath, "utf-8");
|
|
347
345
|
}
|
|
346
|
+
playbooks.set(playbookId, parsePlaybookContent(content, filePath, {
|
|
347
|
+
id: playbookId,
|
|
348
|
+
name: entry.name,
|
|
349
|
+
description: entry.description,
|
|
350
|
+
actions: entry.actions,
|
|
351
|
+
}));
|
|
348
352
|
}
|
|
349
353
|
return playbooks;
|
|
350
354
|
}
|
|
@@ -2,6 +2,8 @@ import type { ActionDockManifest } from "./types.js";
|
|
|
2
2
|
export declare const MANIFEST_FILE_NAME = "actiondock.json";
|
|
3
3
|
export declare const ACTION_ID_REGEX: RegExp;
|
|
4
4
|
export declare const PLAYBOOK_ID_REGEX: RegExp;
|
|
5
|
+
/** 磁盘规程始终采用文件声明,不能把非契约 content 字段带入运行时内联来源。 */
|
|
6
|
+
export declare function normalizeFilePlaybookEntries(manifest: ActionDockManifest): ActionDockManifest;
|
|
5
7
|
/**
|
|
6
8
|
* 读取并解析项目的声明式清单文件(actiondock.json 为唯一事实源)。
|
|
7
9
|
* 若文件不存在则返回 null。
|
package/dist/project/manifest.js
CHANGED
|
@@ -5,6 +5,22 @@ import { ActionDockError, MANIFEST_CORRUPTED, MANIFEST_INVALID, MANIFEST_READ_FA
|
|
|
5
5
|
export const MANIFEST_FILE_NAME = "actiondock.json";
|
|
6
6
|
export const ACTION_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
7
7
|
export const PLAYBOOK_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
|
|
8
|
+
/** 磁盘规程始终采用文件声明,不能把非契约 content 字段带入运行时内联来源。 */
|
|
9
|
+
export function normalizeFilePlaybookEntries(manifest) {
|
|
10
|
+
if (!manifest.playbooks || typeof manifest.playbooks !== "object" || Array.isArray(manifest.playbooks)) {
|
|
11
|
+
return manifest;
|
|
12
|
+
}
|
|
13
|
+
let changed = false;
|
|
14
|
+
const playbooks = Object.fromEntries(Object.entries(manifest.playbooks).map(([id, entry]) => {
|
|
15
|
+
if (entry && typeof entry === "object" && "content" in entry) {
|
|
16
|
+
const { content: _inlineContent, ...fileEntry } = entry;
|
|
17
|
+
changed = true;
|
|
18
|
+
return [id, fileEntry];
|
|
19
|
+
}
|
|
20
|
+
return [id, entry];
|
|
21
|
+
}));
|
|
22
|
+
return changed ? { ...manifest, playbooks } : manifest;
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* 读取并解析项目的声明式清单文件(actiondock.json 为唯一事实源)。
|
|
10
26
|
* 若文件不存在则返回 null。
|
|
@@ -38,7 +54,7 @@ export function loadManifest(projectRoot) {
|
|
|
38
54
|
if (!parsed.id) {
|
|
39
55
|
parsed.id = basename(projectRoot);
|
|
40
56
|
}
|
|
41
|
-
return parsed;
|
|
57
|
+
return normalizeFilePlaybookEntries(parsed);
|
|
42
58
|
}
|
|
43
59
|
/**
|
|
44
60
|
* 保存声明式清单文件至项目根目录(actiondock.json)。
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { ProjectConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 本地包条目;是否构成封闭集合由校验入口保证。
|
|
4
|
+
*/
|
|
5
|
+
export interface LocalPackageEntry {
|
|
6
|
+
packageId: string;
|
|
7
|
+
packageRoot: string;
|
|
8
|
+
projectConfig: ProjectConfig;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 本地技能 / 包目录发现视图,不代表已经通过封闭集合校验。
|
|
12
|
+
*/
|
|
13
|
+
export interface LocalSkillDirectory {
|
|
14
|
+
type: "single" | "composite";
|
|
15
|
+
root: string;
|
|
16
|
+
packages: LocalPackageEntry[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 判定路径字符串是否为显式文件/目录路径形态(而非包标识符)。
|
|
20
|
+
*
|
|
21
|
+
* 规则:
|
|
22
|
+
* - 以 .、/、~ 开头;
|
|
23
|
+
* - 包含 / 或 \\,且不是 scoped 包(@scope/name 仅含一个 /);
|
|
24
|
+
* - scoped 包若包含反斜杠或多于一个 /,亦视作路径。
|
|
25
|
+
*/
|
|
26
|
+
export declare function isExplicitPath(p: string): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 对明确的本地封闭包集合实施一致性校验。
|
|
29
|
+
*
|
|
30
|
+
* 校验规则:
|
|
31
|
+
* - 重复包标识防护:集合内不得存在相同 packageId;
|
|
32
|
+
* - 依赖存在性与版本兼容性:声明的直接依赖必须在集合内存在且满足语义版本约束(复用 PackageGraphBuilder);
|
|
33
|
+
* - 跨包 uses 目标完整性:uses 声明引用的目标包与目标 Action 必须在集合内存在。
|
|
34
|
+
*
|
|
35
|
+
* @param packages 封闭包条目集合
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateLocalSkillPackages(packages: Array<{
|
|
38
|
+
packageId: string;
|
|
39
|
+
packageRoot: string;
|
|
40
|
+
projectConfig: ProjectConfig;
|
|
41
|
+
}>): void;
|
|
42
|
+
/**
|
|
43
|
+
* 发现本地 Skill / 包目录结构(支持单包根目录与 packages/* 复合套件布局)。
|
|
44
|
+
* 仅执行物理布局发现与清单加载,不执行跨包封闭依赖集合校验。
|
|
45
|
+
*
|
|
46
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
47
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
48
|
+
*/
|
|
49
|
+
export declare function discoverLocalSkillDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
|
|
50
|
+
/**
|
|
51
|
+
* 查询指定路径的本地包或技能目录(供 info / 查询命令使用)。
|
|
52
|
+
* 单包不要求 node_modules 依赖闭包;复合目录仍要求封闭集合完整。
|
|
53
|
+
*
|
|
54
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
55
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
56
|
+
*/
|
|
57
|
+
export declare function queryLocalPackageDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
|
|
58
|
+
/**
|
|
59
|
+
* 探测指定路径是否为本地 Skill / 包目录(支持单包根目录与 packages/* 复合套件布局)。
|
|
60
|
+
*
|
|
61
|
+
* 封闭包装配与校验规则:
|
|
62
|
+
* - 符号链接按目录布局正常解析;Action 与规程入口的文件边界由相应加载器校验;
|
|
63
|
+
* - 单包布局:目录根部存在 actiondock.json,解析为包含单一包的 LocalSkillDirectory;
|
|
64
|
+
* - 复合布局:目录根部存在 packages/ 目录,遍历其直接子目录,收集包含 actiondock.json 的子包;
|
|
65
|
+
* - 集合一致性校验:通过 validateLocalSkillPackages 严格核查依赖闭包、版本兼容与 uses 目标;
|
|
66
|
+
* - 异常透传:坏清单或权限异常原样抛出,严禁静默回退 Registry;
|
|
67
|
+
* - 若非候选技能目录,返回 null。
|
|
68
|
+
*
|
|
69
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
70
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
71
|
+
*/
|
|
72
|
+
export declare function inspectLocalPackageDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { ActionDockError, ACTION_NOT_FOUND, PACKAGE_NOT_FOUND, PACKAGE_ID_CONFLICT, } from "../errors.js";
|
|
4
|
+
import { loadProjectConfig } from "./loader.js";
|
|
5
|
+
import { PackageGraphBuilder } from "../catalog/graph.js";
|
|
6
|
+
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
7
|
+
import { createPackageIdentity } from "../runtime/identity.js";
|
|
8
|
+
/**
|
|
9
|
+
* 判定路径字符串是否为显式文件/目录路径形态(而非包标识符)。
|
|
10
|
+
*
|
|
11
|
+
* 规则:
|
|
12
|
+
* - 以 .、/、~ 开头;
|
|
13
|
+
* - 包含 / 或 \\,且不是 scoped 包(@scope/name 仅含一个 /);
|
|
14
|
+
* - scoped 包若包含反斜杠或多于一个 /,亦视作路径。
|
|
15
|
+
*/
|
|
16
|
+
export function isExplicitPath(p) {
|
|
17
|
+
if (!p)
|
|
18
|
+
return false;
|
|
19
|
+
if (p.startsWith("@")) {
|
|
20
|
+
const slashCount = (p.match(/\//g) || []).length;
|
|
21
|
+
return slashCount > 1 || p.includes("\\");
|
|
22
|
+
}
|
|
23
|
+
return (p.startsWith(".") ||
|
|
24
|
+
p.startsWith("/") ||
|
|
25
|
+
p.startsWith("~") ||
|
|
26
|
+
p.includes("/") ||
|
|
27
|
+
p.includes("\\"));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 对明确的本地封闭包集合实施一致性校验。
|
|
31
|
+
*
|
|
32
|
+
* 校验规则:
|
|
33
|
+
* - 重复包标识防护:集合内不得存在相同 packageId;
|
|
34
|
+
* - 依赖存在性与版本兼容性:声明的直接依赖必须在集合内存在且满足语义版本约束(复用 PackageGraphBuilder);
|
|
35
|
+
* - 跨包 uses 目标完整性:uses 声明引用的目标包与目标 Action 必须在集合内存在。
|
|
36
|
+
*
|
|
37
|
+
* @param packages 封闭包条目集合
|
|
38
|
+
*/
|
|
39
|
+
export function validateLocalSkillPackages(packages) {
|
|
40
|
+
if (!packages || packages.length === 0)
|
|
41
|
+
return;
|
|
42
|
+
// 1. 重复 packageId 校验
|
|
43
|
+
const seenIds = new Set();
|
|
44
|
+
for (const pkg of packages) {
|
|
45
|
+
if (seenIds.has(pkg.packageId)) {
|
|
46
|
+
throw new ActionDockError(PACKAGE_ID_CONFLICT, `Package ID conflict: package '${pkg.packageId}' is declared more than once in skill packages`);
|
|
47
|
+
}
|
|
48
|
+
seenIds.add(pkg.packageId);
|
|
49
|
+
}
|
|
50
|
+
// 2. 构造包发现视图并交由 PackageGraphBuilder 校验依赖与版本
|
|
51
|
+
const discovered = packages.map((pkg) => ({
|
|
52
|
+
id: pkg.packageId,
|
|
53
|
+
root: pkg.packageRoot,
|
|
54
|
+
manifest: pkg.projectConfig,
|
|
55
|
+
identity: createPackageIdentity({
|
|
56
|
+
id: pkg.packageId,
|
|
57
|
+
instanceId: `${pkg.packageId}:${pkg.packageRoot}`,
|
|
58
|
+
generation: "skill-local",
|
|
59
|
+
}),
|
|
60
|
+
isCurrentProject: true,
|
|
61
|
+
}));
|
|
62
|
+
const builder = new PackageGraphBuilder({
|
|
63
|
+
packages: discovered,
|
|
64
|
+
validateDependencies: true,
|
|
65
|
+
});
|
|
66
|
+
builder.buildSync();
|
|
67
|
+
// 3. uses 声明目标存在性校验
|
|
68
|
+
const pkgMap = new Map();
|
|
69
|
+
for (const pkg of packages) {
|
|
70
|
+
pkgMap.set(pkg.packageId, pkg.projectConfig);
|
|
71
|
+
}
|
|
72
|
+
const validateUseRef = (rawRef, caller, currentPkg) => {
|
|
73
|
+
if (typeof rawRef !== "string" || !rawRef.trim())
|
|
74
|
+
return;
|
|
75
|
+
const trimmed = rawRef.trim();
|
|
76
|
+
// 先按完整包身份识别裸包引用,包括 scoped 包。
|
|
77
|
+
if (pkgMap.has(trimmed)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// 裸 scoped 包只有一个斜杠,不能按限定动作拆分。
|
|
81
|
+
if (trimmed.startsWith("@") && (trimmed.match(/\//g) || []).length === 1) {
|
|
82
|
+
throw new ActionDockError(PACKAGE_NOT_FOUND, `Package '${trimmed}' referenced by uses in '${caller}' was not found in skill directory`);
|
|
83
|
+
}
|
|
84
|
+
// 限定动作与包级通配符复用统一引用解析器。
|
|
85
|
+
if (trimmed.includes("/")) {
|
|
86
|
+
const ref = parseActionRef(trimmed);
|
|
87
|
+
const targetPkgId = ref.packageId;
|
|
88
|
+
const targetActionId = ref.actionId;
|
|
89
|
+
const targetPkg = pkgMap.get(targetPkgId);
|
|
90
|
+
if (!targetPkg) {
|
|
91
|
+
throw new ActionDockError(PACKAGE_NOT_FOUND, `Package '${targetPkgId}' referenced by uses in '${caller}' was not found in skill directory`);
|
|
92
|
+
}
|
|
93
|
+
if (targetActionId !== "*") {
|
|
94
|
+
const hasAction = Boolean(targetPkg.actions && targetPkg.actions[targetActionId]);
|
|
95
|
+
if (!hasAction) {
|
|
96
|
+
throw new ActionDockError(ACTION_NOT_FOUND, `Action '${targetActionId}' referenced by uses in '${caller}' was not found in package '${targetPkgId}'`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// 本包短动作引用。
|
|
102
|
+
if (currentPkg.actions && currentPkg.actions[trimmed]) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// 目标不存在。
|
|
106
|
+
throw new ActionDockError(PACKAGE_NOT_FOUND, `Package or action '${trimmed}' referenced by uses in '${caller}' was not found in skill directory`);
|
|
107
|
+
};
|
|
108
|
+
for (const pkg of packages) {
|
|
109
|
+
const pkgUses = pkg.projectConfig.uses;
|
|
110
|
+
if (Array.isArray(pkgUses)) {
|
|
111
|
+
for (const ref of pkgUses) {
|
|
112
|
+
validateUseRef(ref, `package '${pkg.packageId}'`, pkg.projectConfig);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (pkg.projectConfig.actions && typeof pkg.projectConfig.actions === "object") {
|
|
116
|
+
for (const [actionId, actionEntry] of Object.entries(pkg.projectConfig.actions)) {
|
|
117
|
+
if (actionEntry && Array.isArray(actionEntry.uses)) {
|
|
118
|
+
for (const ref of actionEntry.uses) {
|
|
119
|
+
validateUseRef(ref, `action '${pkg.packageId}/${actionId}'`, pkg.projectConfig);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 发现本地 Skill / 包目录结构(支持单包根目录与 packages/* 复合套件布局)。
|
|
128
|
+
* 仅执行物理布局发现与清单加载,不执行跨包封闭依赖集合校验。
|
|
129
|
+
*
|
|
130
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
131
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
132
|
+
*/
|
|
133
|
+
export function discoverLocalSkillDirectory(directoryPath, cwd) {
|
|
134
|
+
if (!directoryPath || typeof directoryPath !== "string") {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const absDir = resolve(cwd || process.cwd(), directoryPath);
|
|
138
|
+
if (!existsSync(absDir)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const stat = statSync(absDir);
|
|
143
|
+
if (!stat.isDirectory()) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
// 单包布局:根目录包含 actiondock.json
|
|
151
|
+
const rootManifest = join(absDir, "actiondock.json");
|
|
152
|
+
if (existsSync(rootManifest)) {
|
|
153
|
+
const config = loadProjectConfig(absDir);
|
|
154
|
+
const packages = [
|
|
155
|
+
{
|
|
156
|
+
packageId: config.id,
|
|
157
|
+
packageRoot: absDir,
|
|
158
|
+
projectConfig: config,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
return {
|
|
162
|
+
type: "single",
|
|
163
|
+
root: absDir,
|
|
164
|
+
packages,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// 复合 Skill 布局:存在 packages/ 目录且包含至少一个有效子包
|
|
168
|
+
const packagesDir = join(absDir, "packages");
|
|
169
|
+
if (existsSync(packagesDir)) {
|
|
170
|
+
const pStat = statSync(packagesDir);
|
|
171
|
+
if (pStat.isDirectory()) {
|
|
172
|
+
const entries = readdirSync(packagesDir, { withFileTypes: true });
|
|
173
|
+
const packages = [];
|
|
174
|
+
const seenIds = new Set();
|
|
175
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
176
|
+
for (const entry of entries) {
|
|
177
|
+
const subDir = join(packagesDir, entry.name);
|
|
178
|
+
const sStat = statSync(subDir);
|
|
179
|
+
if (sStat.isDirectory()) {
|
|
180
|
+
const subManifest = join(subDir, "actiondock.json");
|
|
181
|
+
if (existsSync(subManifest)) {
|
|
182
|
+
const childConfig = loadProjectConfig(subDir);
|
|
183
|
+
if (seenIds.has(childConfig.id)) {
|
|
184
|
+
throw new ActionDockError(PACKAGE_ID_CONFLICT, `Package ID conflict: package '${childConfig.id}' is declared more than once in skill directory '${absDir}'`);
|
|
185
|
+
}
|
|
186
|
+
seenIds.add(childConfig.id);
|
|
187
|
+
packages.push({
|
|
188
|
+
packageId: childConfig.id,
|
|
189
|
+
packageRoot: subDir,
|
|
190
|
+
projectConfig: childConfig,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (packages.length > 0) {
|
|
196
|
+
return {
|
|
197
|
+
type: "composite",
|
|
198
|
+
root: absDir,
|
|
199
|
+
packages,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 查询指定路径的本地包或技能目录(供 info / 查询命令使用)。
|
|
208
|
+
* 单包不要求 node_modules 依赖闭包;复合目录仍要求封闭集合完整。
|
|
209
|
+
*
|
|
210
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
211
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
212
|
+
*/
|
|
213
|
+
export function queryLocalPackageDirectory(directoryPath, cwd) {
|
|
214
|
+
const discovered = discoverLocalSkillDirectory(directoryPath, cwd);
|
|
215
|
+
if (!discovered) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (discovered.type === "composite") {
|
|
219
|
+
validateLocalSkillPackages(discovered.packages);
|
|
220
|
+
}
|
|
221
|
+
return discovered;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 探测指定路径是否为本地 Skill / 包目录(支持单包根目录与 packages/* 复合套件布局)。
|
|
225
|
+
*
|
|
226
|
+
* 封闭包装配与校验规则:
|
|
227
|
+
* - 符号链接按目录布局正常解析;Action 与规程入口的文件边界由相应加载器校验;
|
|
228
|
+
* - 单包布局:目录根部存在 actiondock.json,解析为包含单一包的 LocalSkillDirectory;
|
|
229
|
+
* - 复合布局:目录根部存在 packages/ 目录,遍历其直接子目录,收集包含 actiondock.json 的子包;
|
|
230
|
+
* - 集合一致性校验:通过 validateLocalSkillPackages 严格核查依赖闭包、版本兼容与 uses 目标;
|
|
231
|
+
* - 异常透传:坏清单或权限异常原样抛出,严禁静默回退 Registry;
|
|
232
|
+
* - 若非候选技能目录,返回 null。
|
|
233
|
+
*
|
|
234
|
+
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
235
|
+
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
236
|
+
*/
|
|
237
|
+
export function inspectLocalPackageDirectory(directoryPath, cwd) {
|
|
238
|
+
const discovered = discoverLocalSkillDirectory(directoryPath, cwd);
|
|
239
|
+
if (!discovered) {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
validateLocalSkillPackages(discovered.packages);
|
|
243
|
+
return discovered;
|
|
244
|
+
}
|