@actiondock/core 2.14.0 → 2.16.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/graph.d.ts +4 -2
- package/dist/catalog/graph.js +32 -182
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/dist/project/index.d.ts +0 -1
- package/dist/project/index.js +0 -1
- package/dist/project/init.js +2 -2
- package/dist/project/types.d.ts +0 -2
- package/dist/registry/registry.js +15 -27
- package/dist/runtime/standalone-commands.js +2 -8
- package/dist/runtime/standalone.d.ts +0 -2
- package/dist/utils/semver.d.ts +1 -2
- package/dist/utils/semver.js +5 -11
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/project/skill-dir.d.ts +0 -56
- package/dist/project/skill-dir.js +0 -214
package/dist/catalog/graph.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ import { parseSemVer, type SemVer } from "../utils/index.js";
|
|
|
6
6
|
import { type DiscoveredPackage, type PackageDiscoveryOptions } from "./discovery.js";
|
|
7
7
|
export type { SemVer };
|
|
8
8
|
export { parseSemVer };
|
|
9
|
+
/**
|
|
10
|
+
* 校验两个语义化版本范围是否兼容并能收敛为单一解析版本。
|
|
11
|
+
* 遵循设计规范:直接或传递版本范围无法收敛时拒绝并抛出 ACTION_PACKAGE_VERSION_CONFLICT。
|
|
12
|
+
*/
|
|
9
13
|
export declare function areVersionsCompatible(v1: string, v2: string): boolean;
|
|
10
14
|
/**
|
|
11
15
|
* 版本冲突错误。
|
|
@@ -141,8 +145,6 @@ export declare class DefaultPackageGraph implements PackageGraph {
|
|
|
141
145
|
export interface PackageGraphBuilderOptions {
|
|
142
146
|
/** 预先发现的包集合,若未提供则通过 PackageDiscovery 自动发现 */
|
|
143
147
|
packages?: DiscoveredPackage[];
|
|
144
|
-
/** 完整显式集合的依赖存在性与选定版本校验(Host 增量注册时不启用)。 */
|
|
145
|
-
validateDependencies?: boolean;
|
|
146
148
|
/** 根工程根目录或根包标识(与 projectRoot 互为别名) */
|
|
147
149
|
root?: string;
|
|
148
150
|
/** 当前工程根目录绝对或相对物理路径 */
|
package/dist/catalog/graph.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
-
import { ActionDockError, ACTION_PACKAGE_VERSION_CONFLICT, MANIFEST_DIGEST_MISMATCH, MANIFEST_NOT_FOUND, MANIFEST_READ_FAILED, PACKAGE_NOT_FOUND,
|
|
3
|
+
import { ActionDockError, ACTION_PACKAGE_VERSION_CONFLICT, MANIFEST_DIGEST_MISMATCH, MANIFEST_NOT_FOUND, MANIFEST_READ_FAILED, PACKAGE_NOT_FOUND, UNDECLARED_ACTION_DEPENDENCY, } from "../errors.js";
|
|
4
4
|
import { computeManifestDigest, parseJsonWithoutDuplicates } from "../project/digest.js";
|
|
5
5
|
import { loadLockfile } from "../project/lockfile.js";
|
|
6
6
|
import { loadPlaybooks, loadProjectConfig } from "../project/loader.js";
|
|
@@ -10,124 +10,30 @@ import { createPackageIdentity } from "../runtime/identity.js";
|
|
|
10
10
|
import { parseSemVer } from "../utils/index.js";
|
|
11
11
|
import { PackageDiscovery } from "./discovery.js";
|
|
12
12
|
export { parseSemVer };
|
|
13
|
-
/** 有限版本契约:严格三段版本(允许 v/= 前缀)、^、~、*;不猜测其他范围。 */
|
|
14
|
-
function parseVersionRange(value) {
|
|
15
|
-
const text = value.trim();
|
|
16
|
-
if (!text || text === "*")
|
|
17
|
-
return {};
|
|
18
|
-
const prefix = text[0];
|
|
19
|
-
const minimum = parseSemVer(prefix === "^" || prefix === "~" ? text.slice(1) : text);
|
|
20
|
-
if (!minimum)
|
|
21
|
-
return undefined;
|
|
22
|
-
if (prefix !== "^" && prefix !== "~")
|
|
23
|
-
return { minimum, exact: true };
|
|
24
|
-
const maximum = prefix === "~"
|
|
25
|
-
? { major: minimum.major, minor: minimum.minor + 1, patch: 0 }
|
|
26
|
-
: minimum.major > 0 ? { major: minimum.major + 1, minor: 0, patch: 0 }
|
|
27
|
-
: minimum.minor > 0 ? { major: 0, minor: minimum.minor + 1, patch: 0 }
|
|
28
|
-
: { major: 0, minor: 0, patch: minimum.patch + 1 };
|
|
29
|
-
return { minimum, maximum };
|
|
30
|
-
}
|
|
31
|
-
function compareVersions(a, b) {
|
|
32
|
-
const core = a.major - b.major || a.minor - b.minor || a.patch - b.patch;
|
|
33
|
-
if (core)
|
|
34
|
-
return core;
|
|
35
|
-
if (a.prerelease === b.prerelease)
|
|
36
|
-
return 0;
|
|
37
|
-
if (a.prerelease === undefined)
|
|
38
|
-
return 1;
|
|
39
|
-
if (b.prerelease === undefined)
|
|
40
|
-
return -1;
|
|
41
|
-
const left = a.prerelease.split(".");
|
|
42
|
-
const right = b.prerelease.split(".");
|
|
43
|
-
for (let i = 0; i < Math.max(left.length, right.length); i++) {
|
|
44
|
-
if (left[i] === undefined)
|
|
45
|
-
return -1;
|
|
46
|
-
if (right[i] === undefined)
|
|
47
|
-
return 1;
|
|
48
|
-
if (left[i] === right[i])
|
|
49
|
-
continue;
|
|
50
|
-
const numericLeft = /^\d+$/.test(left[i]);
|
|
51
|
-
const numericRight = /^\d+$/.test(right[i]);
|
|
52
|
-
if (numericLeft && numericRight)
|
|
53
|
-
return BigInt(left[i]) < BigInt(right[i]) ? -1 : 1;
|
|
54
|
-
if (numericLeft !== numericRight)
|
|
55
|
-
return numericLeft ? -1 : 1;
|
|
56
|
-
return left[i] < right[i] ? -1 : 1;
|
|
57
|
-
}
|
|
58
|
-
return 0;
|
|
59
|
-
}
|
|
60
|
-
function satisfiesRange(version, range) {
|
|
61
|
-
if (range.exact)
|
|
62
|
-
return compareVersions(version, range.minimum) === 0;
|
|
63
|
-
if (range.minimum && compareVersions(version, range.minimum) < 0)
|
|
64
|
-
return false;
|
|
65
|
-
if (range.maximum && compareVersions(version, range.maximum) >= 0)
|
|
66
|
-
return false;
|
|
67
|
-
// 预发布版本仅在范围下界显式点名同一核心版本的预发布时纳入。
|
|
68
|
-
if (version.prerelease !== undefined) {
|
|
69
|
-
const min = range.minimum;
|
|
70
|
-
return Boolean(min?.prerelease && version.major === min.major && version.minor === min.minor && version.patch === min.patch);
|
|
71
|
-
}
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
export function areVersionsCompatible(v1, v2) {
|
|
75
|
-
const left = parseVersionRange(v1);
|
|
76
|
-
const right = parseVersionRange(v2);
|
|
77
|
-
if (!left || !right)
|
|
78
|
-
return false;
|
|
79
|
-
const candidate = !left.minimum ? right.minimum : !right.minimum ? left.minimum
|
|
80
|
-
: compareVersions(left.minimum, right.minimum) >= 0 ? left.minimum : right.minimum;
|
|
81
|
-
if (!candidate)
|
|
82
|
-
return true;
|
|
83
|
-
if (satisfiesRange(candidate, left) && satisfiesRange(candidate, right))
|
|
84
|
-
return true;
|
|
85
|
-
// 两个范围可以在预发布下界之后的稳定版本交汇,但不能改变精确版本。
|
|
86
|
-
const stable = { major: candidate.major, minor: candidate.minor, patch: candidate.patch };
|
|
87
|
-
return satisfiesRange(stable, left) && satisfiesRange(stable, right);
|
|
88
|
-
}
|
|
89
13
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* 规则:
|
|
93
|
-
* - 范围操作符开头:^, ~, <, >, =, *;
|
|
94
|
-
* - 复合范围操作符:|| 或 - 范围;
|
|
95
|
-
* - X 范围与通配:如 1.x, 1.2.x, 1.*, 1.2.*, v1.x 等;
|
|
96
|
-
* - 不完整两段或一段版本:如 1, 1.2, v1, v1.2 等;
|
|
97
|
-
* - 版本三段式开头但未被严格解析器支持的非法形态。
|
|
14
|
+
* 校验两个语义化版本范围是否兼容并能收敛为单一解析版本。
|
|
15
|
+
* 遵循设计规范:直接或传递版本范围无法收敛时拒绝并抛出 ACTION_PACKAGE_VERSION_CONFLICT。
|
|
98
16
|
*/
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
if (!text)
|
|
102
|
-
return false;
|
|
103
|
-
if (/^[~^<>=*]/.test(text))
|
|
104
|
-
return true;
|
|
105
|
-
if (/\|\||\s+-\s+/.test(text))
|
|
106
|
-
return true;
|
|
107
|
-
if (/^v?\d+\.(?:[xX*]|\d+\.[xX*])(?:$|[-+])/.test(text))
|
|
108
|
-
return true;
|
|
109
|
-
if (/^v?\d+(?:\.\d+)?$/.test(text))
|
|
110
|
-
return true;
|
|
111
|
-
if (/^v?\d+\.\d+\.\d+/.test(text))
|
|
17
|
+
export function areVersionsCompatible(v1, v2) {
|
|
18
|
+
if (v1 === v2 || v1 === "*" || v2 === "*" || !v1 || !v2) {
|
|
112
19
|
return true;
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
/** 获取描述不是版本约束;仅真实范围及锁定版本参与版本兼容校验。 */
|
|
116
|
-
function dependencyVersionRequirements(spec, locked) {
|
|
117
|
-
const requirements = [];
|
|
118
|
-
const range = spec === undefined ? undefined : parseVersionRange(spec);
|
|
119
|
-
if (spec !== undefined && range)
|
|
120
|
-
requirements.push(spec);
|
|
121
|
-
else if (spec !== undefined && isUnsupportedVersionRange(spec)) {
|
|
122
|
-
throw new ActionDockError(INVALID_ARGUMENT, `Unsupported Action package version range '${spec}'; supported forms are exact versions, ^, ~ and *`);
|
|
123
20
|
}
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
21
|
+
const clean1 = v1.trim().replace(/^[\^~=]/, "");
|
|
22
|
+
const clean2 = v2.trim().replace(/^[\^~=]/, "");
|
|
23
|
+
const sem1 = parseSemVer(clean1);
|
|
24
|
+
const sem2 = parseSemVer(clean2);
|
|
25
|
+
if (!sem1 || !sem2) {
|
|
26
|
+
return v1 === v2;
|
|
27
|
+
}
|
|
28
|
+
if (sem1.major !== sem2.major) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (v1.startsWith("~") || v2.startsWith("~")) {
|
|
32
|
+
if (sem1.minor !== sem2.minor) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
129
35
|
}
|
|
130
|
-
return
|
|
36
|
+
return true;
|
|
131
37
|
}
|
|
132
38
|
/**
|
|
133
39
|
* 版本冲突错误。
|
|
@@ -374,9 +280,6 @@ export class PackageGraphBuilder {
|
|
|
374
280
|
*/
|
|
375
281
|
buildFromPackages(nodes, generation) {
|
|
376
282
|
for (const pkg of this.options.packages) {
|
|
377
|
-
if (nodes.has(pkg.id)) {
|
|
378
|
-
throw new ActionDockError(PACKAGE_ID_CONFLICT, `Package ID '${pkg.id}' occurs more than once in explicit package graph`);
|
|
379
|
-
}
|
|
380
283
|
const fields = createPackageNodeFields({
|
|
381
284
|
id: pkg.id,
|
|
382
285
|
root: pkg.root,
|
|
@@ -419,39 +322,6 @@ export class PackageGraphBuilder {
|
|
|
419
322
|
rootNode.isDirect = true;
|
|
420
323
|
}
|
|
421
324
|
}
|
|
422
|
-
if (this.options.validateDependencies) {
|
|
423
|
-
for (const node of nodes.values()) {
|
|
424
|
-
const lockfile = node.root ? loadLockfile(node.root) : null;
|
|
425
|
-
for (const [depId, spec] of Object.entries(node.manifest.dependencies || {})) {
|
|
426
|
-
const dep = nodes.get(depId);
|
|
427
|
-
if (!dep) {
|
|
428
|
-
throw new ActionDockError(PACKAGE_NOT_FOUND, `Package '${depId}' required by '${node.packageId}' is missing from explicit graph`);
|
|
429
|
-
}
|
|
430
|
-
for (const requirement of dependencyVersionRequirements(spec)) {
|
|
431
|
-
if (!areVersionsCompatible(requirement, dep.version)) {
|
|
432
|
-
throw new ActionPackageVersionConflictError(depId, [
|
|
433
|
-
{ requester: node.packageId, rangeOrVersion: requirement },
|
|
434
|
-
{ requester: depId, rangeOrVersion: dep.version },
|
|
435
|
-
]);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// 根锁文件也可能锁定传递依赖,不能只检查直接 dependencies。
|
|
440
|
-
for (const [depId, locked] of Object.entries(lockfile?.packages || {})) {
|
|
441
|
-
const dep = nodes.get(depId);
|
|
442
|
-
if (!dep)
|
|
443
|
-
continue;
|
|
444
|
-
for (const requirement of dependencyVersionRequirements(undefined, locked)) {
|
|
445
|
-
if (!areVersionsCompatible(requirement, dep.version)) {
|
|
446
|
-
throw new ActionPackageVersionConflictError(depId, [
|
|
447
|
-
{ requester: node.packageId, rangeOrVersion: requirement },
|
|
448
|
-
{ requester: depId, rangeOrVersion: dep.version },
|
|
449
|
-
]);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
325
|
linkDirectDependencies(nodes);
|
|
456
326
|
computeTransitiveDependencies(nodes);
|
|
457
327
|
const effectiveRootNode = rootIdentity ? nodes.get(rootIdentity.id) : undefined;
|
|
@@ -545,13 +415,6 @@ export class PackageGraphBuilder {
|
|
|
545
415
|
// 忽略扫描外部链接包异常
|
|
546
416
|
}
|
|
547
417
|
}
|
|
548
|
-
const requesterLocks = new Map([[rootPackageId, lockfile]]);
|
|
549
|
-
const getRequesterLock = (requester) => {
|
|
550
|
-
if (!requesterLocks.has(requester)) {
|
|
551
|
-
requesterLocks.set(requester, loadLockfile(nodes.get(requester).root));
|
|
552
|
-
}
|
|
553
|
-
return requesterLocks.get(requester);
|
|
554
|
-
};
|
|
555
418
|
const versionRequirements = new Map();
|
|
556
419
|
function recordRequirement(pkgId, requester, rangeOrVersion) {
|
|
557
420
|
const existing = versionRequirements.get(pkgId) || [];
|
|
@@ -566,22 +429,10 @@ export class PackageGraphBuilder {
|
|
|
566
429
|
existing.push({ requester, rangeOrVersion });
|
|
567
430
|
versionRequirements.set(pkgId, existing);
|
|
568
431
|
}
|
|
569
|
-
const recordDependency = (pkgId, requester, spec) => {
|
|
570
|
-
const requesterLocked = getRequesterLock(requester)?.packages[pkgId];
|
|
571
|
-
for (const requirement of dependencyVersionRequirements(spec, requesterLocked)) {
|
|
572
|
-
recordRequirement(pkgId, requester, requirement);
|
|
573
|
-
}
|
|
574
|
-
const rootLocked = lockfile?.packages[pkgId];
|
|
575
|
-
if (rootLocked && rootLocked !== requesterLocked) {
|
|
576
|
-
for (const requirement of dependencyVersionRequirements(undefined, rootLocked)) {
|
|
577
|
-
recordRequirement(pkgId, rootPackageId, requirement);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
};
|
|
581
432
|
const queue = [];
|
|
582
433
|
for (const [depId, spec] of Object.entries(rootManifest.dependencies || {})) {
|
|
583
434
|
rootNode.directDependencies.add(depId);
|
|
584
|
-
|
|
435
|
+
recordRequirement(depId, rootPackageId, spec);
|
|
585
436
|
queue.push({
|
|
586
437
|
pkgId: depId,
|
|
587
438
|
requester: rootPackageId,
|
|
@@ -609,7 +460,7 @@ export class PackageGraphBuilder {
|
|
|
609
460
|
continue;
|
|
610
461
|
}
|
|
611
462
|
let pkgDir = null;
|
|
612
|
-
const locked =
|
|
463
|
+
const locked = lockfile?.packages?.[item.pkgId];
|
|
613
464
|
if (packageRootsRecord?.[item.pkgId]) {
|
|
614
465
|
pkgDir = packageRootsRecord[item.pkgId];
|
|
615
466
|
}
|
|
@@ -623,7 +474,7 @@ export class PackageGraphBuilder {
|
|
|
623
474
|
}
|
|
624
475
|
candidateNames.push(item.rangeOrSpec);
|
|
625
476
|
candidateNames.push(item.pkgId);
|
|
626
|
-
let cur =
|
|
477
|
+
let cur = absProjectRoot;
|
|
627
478
|
while (true) {
|
|
628
479
|
for (const candName of candidateNames) {
|
|
629
480
|
const candPath = join(cur, "node_modules", candName);
|
|
@@ -655,9 +506,6 @@ export class PackageGraphBuilder {
|
|
|
655
506
|
throw new ActionDockError(MANIFEST_READ_FAILED, `Failed to read manifest for '${item.pkgId}': ${err.message}`);
|
|
656
507
|
}
|
|
657
508
|
const depManifest = parseJsonWithoutDuplicates(manifestRaw);
|
|
658
|
-
if (depManifest.id !== item.pkgId) {
|
|
659
|
-
throw new ActionDockError(PACKAGE_ID_CONFLICT, `Expected package '${item.pkgId}' at '${pkgDir}', found '${depManifest.id}'`);
|
|
660
|
-
}
|
|
661
509
|
const actualDigest = computeManifestDigest(depManifest);
|
|
662
510
|
if (locked?.manifestDigest && locked.manifestDigest !== actualDigest) {
|
|
663
511
|
throw new ActionDockError(MANIFEST_DIGEST_MISMATCH, `Manifest digest mismatch for package '${item.pkgId}': expected '${locked.manifestDigest}', got '${actualDigest}'. Re-resolution required.`);
|
|
@@ -690,13 +538,15 @@ export class PackageGraphBuilder {
|
|
|
690
538
|
};
|
|
691
539
|
nodes.set(item.pkgId, newNode);
|
|
692
540
|
for (const [subDepId, subSpec] of Object.entries(depManifest.dependencies || {})) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
541
|
+
recordRequirement(subDepId, item.pkgId, subSpec);
|
|
542
|
+
if (!nodes.has(subDepId)) {
|
|
543
|
+
queue.push({
|
|
544
|
+
pkgId: subDepId,
|
|
545
|
+
requester: item.pkgId,
|
|
546
|
+
rangeOrSpec: subSpec,
|
|
547
|
+
isDirect: false,
|
|
548
|
+
});
|
|
549
|
+
}
|
|
700
550
|
}
|
|
701
551
|
}
|
|
702
552
|
if (scanLinked) {
|
package/dist/index.d.ts
CHANGED
|
@@ -22,8 +22,6 @@ export { startActionDockServer, } from "./server/server.js";
|
|
|
22
22
|
export type { ServerOptions, ServerOptions as ActionDockServerOptions, ActionDockServerInstance, } from "./server/types.js";
|
|
23
23
|
export { initProject } from "./project/init.js";
|
|
24
24
|
export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
|
|
25
|
-
export { inspectLocalPackageDirectory, validateLocalSkillPackages, isExplicitPath, } from "./project/skill-dir.js";
|
|
26
25
|
export type { ProjectConfig, } from "./project/types.js";
|
|
27
|
-
export type { LocalSkillDirectory, LocalPackageEntry, } from "./project/skill-dir.js";
|
|
28
26
|
export type { PackageGraph, } from "./catalog/graph.js";
|
|
29
27
|
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
package/dist/index.js
CHANGED
|
@@ -19,6 +19,5 @@ export { startActionDockServer, } from "./server/server.js";
|
|
|
19
19
|
// 6. 核心工程辅助
|
|
20
20
|
export { initProject } from "./project/init.js";
|
|
21
21
|
export { loadProjectConfig, findProjectRoot, } from "./project/loader.js";
|
|
22
|
-
export { inspectLocalPackageDirectory, validateLocalSkillPackages, isExplicitPath, } from "./project/skill-dir.js";
|
|
23
22
|
// 8. 统一错误模型与核心标准错误码常量
|
|
24
23
|
export { ActionDockError, ProcessError, PACKAGE_NOT_FOUND, ACTION_PACKAGE_VERSION_CONFLICT, ACTION_NOT_FOUND, ACTION_LOAD_FAILED, ACTION_FAILED, ACTION_TIMEOUT, ACTION_CANCELLED, INPUT_VALIDATION_FAILED, OUTPUT_VALIDATION_FAILED, INPUT_NOT_JSON, OUTPUT_NOT_JSON, ACTION_SUBRUN_LIMIT, ACTION_CALL_CYCLE, UNDECLARED_ACTION_DEPENDENCY, INVALID_ACTION_REF, IDEMPOTENCY_CONFLICT, EXECUTION_FAILED, UNHANDLED_EXECUTION_ERROR, RUN_REPOSITORY_UNAVAILABLE, RUN_PERSISTENCE_FAILED, STANDALONE_ASYNC_UNSUPPORTED, HOST_PROCESS_EXITED, EXECUTION_ABORTED, TIMEOUT, NETWORK_ERROR, CAPABILITY_UNAVAILABLE, STATE_KEY_NOT_FOUND, STORAGE_BUSY, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED, EVENT_BACKPRESSURE_LIMIT, EVENT_CURSOR_EXPIRED, PROCESS_OUTPUT_LIMIT, PROCESS_SPAWN_ERROR, PROCESS_CANCELLED, PROCESS_TIMEOUT, UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, PACKAGE_FORBIDDEN, ACTION_FORBIDDEN, PLAYBOOK_NOT_FOUND, REQUEST_TOO_LARGE, ACTION_START_FAILED, ACTION_EXECUTION_ERROR, DATA_DIR_IN_USE, DATA_DIR_RECOVERY_REQUIRED, PROCESS_FAILED, MCP_TOOL_NAME_COLLISION, ACCESS_DENIED, INVALID_FLAT_ARGUMENT, INVALID_JSON, INVALID_JSON_LITERAL, INPUT_PATH_CONFLICT, FLAT_INPUT_LIMIT_EXCEEDED, INPUT_LIMIT_EXCEEDED, INPUT_POLICY_VIOLATION, INPUT_CONFLICT, INPUT_FILE_NOT_FOUND, INPUT_FILE_READ_FAILED, INVALID_ARGUMENT, PACKAGE_NOT_ALLOWED, INVALID_PACKAGE_ID, PATH_TRAVERSAL, AMBIGUOUS_STATE_KEY, RUN_NOT_FOUND, RUN_ALREADY_FINISHED, PROJECT_ROOT_NOT_FOUND, describeActionLoadFailure, createErrorEnvelope, resolveExecutionHint, } from "./errors.js";
|
package/dist/project/index.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ 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";
|
|
7
6
|
export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
8
7
|
export * from "./types.js";
|
|
9
8
|
export * from "./types-generator.js";
|
package/dist/project/index.js
CHANGED
|
@@ -3,7 +3,6 @@ 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";
|
|
7
6
|
export { SNAPSHOT_TRACKED_FILES, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
8
7
|
export * from "./types.js";
|
|
9
8
|
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.16.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.16.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
package/dist/project/types.d.ts
CHANGED
|
@@ -75,8 +75,6 @@ export interface ActionDockManifest {
|
|
|
75
75
|
actions?: Record<string, ActionManifestEntry>;
|
|
76
76
|
/** 声明的 Playbooks 集合(唯一事实源) */
|
|
77
77
|
playbooks?: Record<string, PlaybookManifestEntry>;
|
|
78
|
-
/** 包级静态依赖引用,与 Action 的 uses 共用引用语义。 */
|
|
79
|
-
uses?: string[];
|
|
80
78
|
/** 跨包外部依赖映射(逻辑包 ID 到 npm 包名) */
|
|
81
79
|
dependencies?: Record<string, string>;
|
|
82
80
|
/** 框架导出与分发边界文件列表 */
|
|
@@ -2,7 +2,6 @@ import { existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:
|
|
|
2
2
|
import { readFile, rename, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { findProjectRoot, loadProjectConfig } from "../project/loader.js";
|
|
5
|
-
import { inspectLocalPackageDirectory, isExplicitPath } from "../project/skill-dir.js";
|
|
6
5
|
import { getActionDockHome, getPackageSlug } from "../utils/index.js";
|
|
7
6
|
import { ActionDockError, MANIFEST_NOT_FOUND, REGISTRY_CORRUPTED } from "../errors.js";
|
|
8
7
|
import { withRegistryLock } from "./lock.js";
|
|
@@ -400,42 +399,31 @@ export function resolvePackageRoot(packageIdOrPath, cwd, customHome) {
|
|
|
400
399
|
const resolvedPath = resolve(baseDir, packageIdOrPath);
|
|
401
400
|
// 检查 packageIdOrPath 是否为磁盘上现存的目录或文件路径
|
|
402
401
|
if (existsSync(resolvedPath)) {
|
|
403
|
-
let info;
|
|
404
402
|
try {
|
|
405
|
-
info = statSync(resolvedPath);
|
|
406
|
-
}
|
|
407
|
-
catch (err) {
|
|
408
|
-
if (err?.code !== "ENOENT" && err?.code !== "ENOTDIR") {
|
|
409
|
-
throw err;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
if (info) {
|
|
403
|
+
const info = statSync(resolvedPath);
|
|
413
404
|
const targetDir = info.isDirectory() ? resolvedPath : dirname(resolvedPath);
|
|
414
|
-
// 普通开发包根目录:直接包含 actiondock.json 清单。
|
|
415
|
-
// 普通清单根目录定位与封闭 Skill 执行校验分离,不能被当成只含一个包的 Skill 闭包拒绝。
|
|
416
405
|
if (existsSync(join(targetDir, "actiondock.json"))) {
|
|
417
|
-
loadProjectConfig(targetDir);
|
|
418
406
|
return targetDir;
|
|
419
407
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
return localSkill.root;
|
|
424
|
-
}
|
|
425
|
-
// 具体文件查找所属工程根目录:仅当 packageIdOrPath 指向具体文件(非目录)时,
|
|
426
|
-
// 才尝试向上查找所属工程根目录;目录形态绝不向上回退,防止显式目录越界命中父级工程。
|
|
427
|
-
if (!info.isDirectory()) {
|
|
428
|
-
const parentRoot = findProjectRoot(targetDir);
|
|
429
|
-
if (parentRoot) {
|
|
430
|
-
loadProjectConfig(parentRoot);
|
|
431
|
-
return parentRoot;
|
|
432
|
-
}
|
|
408
|
+
const parentRoot = findProjectRoot(targetDir);
|
|
409
|
+
if (parentRoot) {
|
|
410
|
+
return parentRoot;
|
|
433
411
|
}
|
|
434
412
|
}
|
|
413
|
+
catch {
|
|
414
|
+
// stat 失败(如并发删除):忽略并继续走标识符解析路径
|
|
415
|
+
}
|
|
435
416
|
}
|
|
436
417
|
// 显式路径形态但未解析成功的场景:
|
|
437
418
|
// scoped package 标识符(如 @team/tools)虽含 '/' 但属于包 ID 而非文件路径
|
|
438
|
-
|
|
419
|
+
const isScopedPackage = packageIdOrPath.startsWith("@");
|
|
420
|
+
const isExplicitPath = !isScopedPackage &&
|
|
421
|
+
(packageIdOrPath.startsWith(".") ||
|
|
422
|
+
packageIdOrPath.startsWith("/") ||
|
|
423
|
+
packageIdOrPath.startsWith("~") ||
|
|
424
|
+
packageIdOrPath.includes("/") ||
|
|
425
|
+
packageIdOrPath.includes("\\"));
|
|
426
|
+
if (isExplicitPath) {
|
|
439
427
|
// 显式路径不存在或不是 ActionDock 项目时必须失败,禁止静默回退
|
|
440
428
|
return null;
|
|
441
429
|
}
|
|
@@ -29,7 +29,7 @@ export async function handleList(ctx, subArgs) {
|
|
|
29
29
|
const effectiveIntent = intent || (positionalPatterns.length > 0 ? positionalPatterns.join("|") : undefined);
|
|
30
30
|
const actions = await ctx.service.discovery.listActions();
|
|
31
31
|
const list = actions.map((a) => ({
|
|
32
|
-
id:
|
|
32
|
+
id: a.id,
|
|
33
33
|
description: a.description || "",
|
|
34
34
|
}));
|
|
35
35
|
const filterRes = filterWithFallbackInfo(list, effectiveIntent, [(a) => a.id, (a) => a.description], !noFallback);
|
|
@@ -66,7 +66,7 @@ export async function handleDescribe(ctx, subArgs) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
const payload = buildActionDescribePayload(action, {
|
|
69
|
-
packageId:
|
|
69
|
+
packageId: ctx.options.packageId,
|
|
70
70
|
});
|
|
71
71
|
if (isJson) {
|
|
72
72
|
ctx.writeOut(JSON.stringify(payload, null, 2));
|
|
@@ -274,9 +274,6 @@ function isServiceCapabilityError(err) {
|
|
|
274
274
|
code === "TARGET_CAPABILITY_UNAVAILABLE");
|
|
275
275
|
}
|
|
276
276
|
export async function handleConfig(ctx, subArgs) {
|
|
277
|
-
if (ctx.options.multiPackage) {
|
|
278
|
-
return ctx.emitError(subArgs.includes("--json"), "CAPABILITY_UNAVAILABLE", "Multi-package entries do not support config/state management; configure actions using --config and --data-dir.", ExitCode.INVALID_ARGUMENT);
|
|
279
|
-
}
|
|
280
277
|
const sub = subArgs[0] || "list";
|
|
281
278
|
if (!ctx.service.management) {
|
|
282
279
|
ctx.writeErr("Error: management port is not enabled on this service");
|
|
@@ -353,9 +350,6 @@ function decodeStateKeyArg(key) {
|
|
|
353
350
|
}
|
|
354
351
|
}
|
|
355
352
|
export async function handleState(ctx, subArgs) {
|
|
356
|
-
if (ctx.options.multiPackage) {
|
|
357
|
-
return ctx.emitError(subArgs.includes("--json"), "CAPABILITY_UNAVAILABLE", "Multi-package entries do not support config/state management; configure actions using --config and --data-dir.", ExitCode.INVALID_ARGUMENT);
|
|
358
|
-
}
|
|
359
353
|
const sub = subArgs[0] || "list";
|
|
360
354
|
let namespace;
|
|
361
355
|
let isAll = false;
|
package/dist/utils/semver.d.ts
CHANGED
|
@@ -6,11 +6,10 @@ export interface SemVer {
|
|
|
6
6
|
minor: number;
|
|
7
7
|
patch: number;
|
|
8
8
|
prerelease?: string;
|
|
9
|
-
build?: string;
|
|
10
9
|
}
|
|
11
10
|
/**
|
|
12
11
|
* 解析基础 SemVer 版本字符串(单一事实源)。
|
|
13
|
-
* 仅接受可选 v/=
|
|
12
|
+
* 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
|
|
14
13
|
* 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
|
|
15
14
|
*
|
|
16
15
|
* 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
|
package/dist/utils/semver.js
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 解析基础 SemVer 版本字符串(单一事实源)。
|
|
3
|
-
* 仅接受可选 v/=
|
|
3
|
+
* 仅接受可选 v/= 前缀的严格三段式语义化版本,可携带预发布后缀;
|
|
4
4
|
* 其余形态(两段式、非数值等)返回 null,由调用方自行兜底。
|
|
5
5
|
*
|
|
6
6
|
* 本模块不依赖任何 Node 内建与 core 领域模块,可在无 loader 的原生
|
|
7
7
|
* Node 直接以 .ts 形态导入(工程脚本域复用此处单一事实源)。
|
|
8
8
|
*/
|
|
9
9
|
export function parseSemVer(v) {
|
|
10
|
-
const match = v.trim().replace(/^[v=]/, "").match(/^(
|
|
10
|
+
const match = v.trim().replace(/^[v=]/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
11
11
|
if (!match)
|
|
12
12
|
return null;
|
|
13
|
-
const [major, minor, patch] = match.slice(1, 4).map(Number);
|
|
14
|
-
if (![major, minor, patch].every(Number.isSafeInteger))
|
|
15
|
-
return null;
|
|
16
|
-
if (match[4]?.split(".").some((id) => /^\d+$/.test(id) && id.length > 1 && id.startsWith("0")))
|
|
17
|
-
return null;
|
|
18
13
|
return {
|
|
19
|
-
major,
|
|
20
|
-
minor,
|
|
21
|
-
patch,
|
|
14
|
+
major: parseInt(match[1], 10),
|
|
15
|
+
minor: parseInt(match[2], 10),
|
|
16
|
+
patch: parseInt(match[3], 10),
|
|
22
17
|
prerelease: match[4],
|
|
23
|
-
...(match[5] === undefined ? {} : { build: match[5] }),
|
|
24
18
|
};
|
|
25
19
|
}
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.16.0",
|
|
4
4
|
"description": "ActionDock Core Engine - Project loader, runtime execution, SQLite storage, standalone builder, and skill exporter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"test": "node ../../scripts/run-tests.ts"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@actiondock/sdk": "^2.
|
|
62
|
+
"@actiondock/sdk": "^2.16.0",
|
|
63
63
|
"ajv": "^8.17.1",
|
|
64
64
|
"ajv-formats": "^3.0.1",
|
|
65
65
|
"undici": "^7.29.1"
|
|
@@ -1,56 +0,0 @@
|
|
|
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
|
-
* - 边界安全防护:packages 目录、各子包目录与 actiondock.json 清单真实路径必须在 Skill 根边界内,禁止越界符号链接;
|
|
47
|
-
* - 单包布局:目录根部存在 actiondock.json,解析为包含单一包的 LocalSkillDirectory;
|
|
48
|
-
* - 复合布局:目录根部存在 packages/ 目录,遍历其直接子目录,收集包含 actiondock.json 的子包;
|
|
49
|
-
* - 集合一致性校验:通过 validateLocalSkillPackages 严格核查依赖闭包、版本兼容与 uses 目标;
|
|
50
|
-
* - 异常透传:坏清单或权限异常原样抛出,严禁静默回退 Registry;
|
|
51
|
-
* - 若非候选技能目录,返回 null。
|
|
52
|
-
*
|
|
53
|
-
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
54
|
-
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
55
|
-
*/
|
|
56
|
-
export declare function inspectLocalPackageDirectory(directoryPath: string, cwd?: string): LocalSkillDirectory | null;
|
|
@@ -1,214 +0,0 @@
|
|
|
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
|
-
* - 边界安全防护:packages 目录、各子包目录与 actiondock.json 清单真实路径必须在 Skill 根边界内,禁止越界符号链接;
|
|
131
|
-
* - 单包布局:目录根部存在 actiondock.json,解析为包含单一包的 LocalSkillDirectory;
|
|
132
|
-
* - 复合布局:目录根部存在 packages/ 目录,遍历其直接子目录,收集包含 actiondock.json 的子包;
|
|
133
|
-
* - 集合一致性校验:通过 validateLocalSkillPackages 严格核查依赖闭包、版本兼容与 uses 目标;
|
|
134
|
-
* - 异常透传:坏清单或权限异常原样抛出,严禁静默回退 Registry;
|
|
135
|
-
* - 若非候选技能目录,返回 null。
|
|
136
|
-
*
|
|
137
|
-
* @param directoryPath 待检查的目录路径(相对路径将基于 cwd 解析)
|
|
138
|
-
* @param cwd 解析基准工作目录(默认为 process.cwd())
|
|
139
|
-
*/
|
|
140
|
-
export function inspectLocalPackageDirectory(directoryPath, cwd) {
|
|
141
|
-
if (!directoryPath || typeof directoryPath !== "string") {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
const absDir = resolve(cwd || process.cwd(), directoryPath);
|
|
145
|
-
if (!existsSync(absDir)) {
|
|
146
|
-
return null;
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
const stat = statSync(absDir);
|
|
150
|
-
if (!stat.isDirectory()) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
// 单包布局:根目录包含 actiondock.json
|
|
158
|
-
const rootManifest = join(absDir, "actiondock.json");
|
|
159
|
-
if (existsSync(rootManifest)) {
|
|
160
|
-
const config = loadProjectConfig(absDir);
|
|
161
|
-
const packages = [
|
|
162
|
-
{
|
|
163
|
-
packageId: config.id,
|
|
164
|
-
packageRoot: absDir,
|
|
165
|
-
projectConfig: config,
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
validateLocalSkillPackages(packages);
|
|
169
|
-
return {
|
|
170
|
-
type: "single",
|
|
171
|
-
root: absDir,
|
|
172
|
-
packages,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
// 复合 Skill 布局:存在 packages/ 目录且包含至少一个有效子包
|
|
176
|
-
const packagesDir = join(absDir, "packages");
|
|
177
|
-
if (existsSync(packagesDir)) {
|
|
178
|
-
const pStat = statSync(packagesDir);
|
|
179
|
-
if (pStat.isDirectory()) {
|
|
180
|
-
const entries = readdirSync(packagesDir, { withFileTypes: true });
|
|
181
|
-
const packages = [];
|
|
182
|
-
const seenIds = new Set();
|
|
183
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
184
|
-
for (const entry of entries) {
|
|
185
|
-
const subDir = join(packagesDir, entry.name);
|
|
186
|
-
const sStat = statSync(subDir);
|
|
187
|
-
if (sStat.isDirectory()) {
|
|
188
|
-
const subManifest = join(subDir, "actiondock.json");
|
|
189
|
-
if (existsSync(subManifest)) {
|
|
190
|
-
const childConfig = loadProjectConfig(subDir);
|
|
191
|
-
if (seenIds.has(childConfig.id)) {
|
|
192
|
-
throw new ActionDockError(PACKAGE_ID_CONFLICT, `Package ID conflict: package '${childConfig.id}' is declared more than once in skill directory '${absDir}'`);
|
|
193
|
-
}
|
|
194
|
-
seenIds.add(childConfig.id);
|
|
195
|
-
packages.push({
|
|
196
|
-
packageId: childConfig.id,
|
|
197
|
-
packageRoot: subDir,
|
|
198
|
-
projectConfig: childConfig,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (packages.length > 0) {
|
|
204
|
-
validateLocalSkillPackages(packages);
|
|
205
|
-
return {
|
|
206
|
-
type: "composite",
|
|
207
|
-
root: absDir,
|
|
208
|
-
packages,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return null;
|
|
214
|
-
}
|