@agentxm/extension-materialization 0.28.14-preview.1789139351.5cd4595aa
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/LICENSE +110 -0
- package/README.md +16 -0
- package/dist/src/desired-state/errors.d.ts +22 -0
- package/dist/src/desired-state/errors.js +22 -0
- package/dist/src/desired-state/retained-materialization.d.ts +58 -0
- package/dist/src/desired-state/retained-materialization.js +119 -0
- package/dist/src/errors.d.ts +42 -0
- package/dist/src/errors.js +14 -0
- package/dist/src/extensions/canonical-directory.d.ts +106 -0
- package/dist/src/extensions/canonical-directory.js +210 -0
- package/dist/src/extensions/canonical-reuse.d.ts +43 -0
- package/dist/src/extensions/canonical-reuse.js +38 -0
- package/dist/src/extensions/copy-directory.d.ts +50 -0
- package/dist/src/extensions/copy-directory.js +78 -0
- package/dist/src/extensions/errors.d.ts +97 -0
- package/dist/src/extensions/errors.js +42 -0
- package/dist/src/extensions/materializable-from-disk.d.ts +37 -0
- package/dist/src/extensions/materializable-from-disk.js +89 -0
- package/dist/src/extensions/operations.d.ts +252 -0
- package/dist/src/extensions/operations.js +631 -0
- package/dist/src/hooks/errors.d.ts +33 -0
- package/dist/src/hooks/errors.js +18 -0
- package/dist/src/hooks/manager.d.ts +13 -0
- package/dist/src/hooks/manager.js +778 -0
- package/dist/src/index.d.ts +44 -0
- package/dist/src/index.js +56 -0
- package/dist/src/knowledge/errors.d.ts +75 -0
- package/dist/src/knowledge/errors.js +36 -0
- package/dist/src/knowledge/manager.d.ts +9 -0
- package/dist/src/knowledge/manager.js +709 -0
- package/dist/src/live.d.ts +20 -0
- package/dist/src/live.js +20 -0
- package/dist/src/manager-contract.d.ts +136 -0
- package/dist/src/manager-contract.js +19 -0
- package/dist/src/manager-registry-live.d.ts +11 -0
- package/dist/src/manager-registry-live.js +23 -0
- package/dist/src/manager-registry.d.ts +37 -0
- package/dist/src/manager-registry.js +22 -0
- package/dist/src/managers.d.ts +147 -0
- package/dist/src/managers.js +32 -0
- package/dist/src/mcps/artifact.d.ts +24 -0
- package/dist/src/mcps/artifact.js +74 -0
- package/dist/src/mcps/authored-materialization.d.ts +28 -0
- package/dist/src/mcps/authored-materialization.js +34 -0
- package/dist/src/mcps/errors.d.ts +105 -0
- package/dist/src/mcps/errors.js +40 -0
- package/dist/src/mcps/install-operation.d.ts +92 -0
- package/dist/src/mcps/install-operation.js +557 -0
- package/dist/src/mcps/manager.d.ts +15 -0
- package/dist/src/mcps/manager.js +253 -0
- package/dist/src/mcps/native-entry.d.ts +51 -0
- package/dist/src/mcps/native-entry.js +100 -0
- package/dist/src/mcps/secret-store-live.d.ts +14 -0
- package/dist/src/mcps/secret-store-live.js +58 -0
- package/dist/src/mcps/secret-store.d.ts +55 -0
- package/dist/src/mcps/secret-store.js +33 -0
- package/dist/src/packs/errors.d.ts +50 -0
- package/dist/src/packs/errors.js +24 -0
- package/dist/src/packs/manager.d.ts +16 -0
- package/dist/src/packs/manager.js +189 -0
- package/dist/src/projection-participants-live.d.ts +17 -0
- package/dist/src/projection-participants-live.js +66 -0
- package/dist/src/projection-step-failure.d.ts +19 -0
- package/dist/src/projection-step-failure.js +76 -0
- package/dist/src/registry-materialization.d.ts +52 -0
- package/dist/src/registry-materialization.js +67 -0
- package/dist/src/rules/errors.d.ts +31 -0
- package/dist/src/rules/errors.js +17 -0
- package/dist/src/rules/manager.d.ts +18 -0
- package/dist/src/rules/manager.js +492 -0
- package/dist/src/skills/errors.d.ts +42 -0
- package/dist/src/skills/errors.js +21 -0
- package/dist/src/skills/manager.d.ts +17 -0
- package/dist/src/skills/manager.js +339 -0
- package/dist/src/skills/materialization.d.ts +52 -0
- package/dist/src/skills/materialization.js +168 -0
- package/dist/src/skills/skill-artifact.d.ts +38 -0
- package/dist/src/skills/skill-artifact.js +70 -0
- package/dist/src/skills/source-hash.d.ts +2 -0
- package/dist/src/skills/source-hash.js +6 -0
- package/dist/src/subagents/errors.d.ts +43 -0
- package/dist/src/subagents/errors.js +20 -0
- package/dist/src/subagents/lock-entry-builder.d.ts +20 -0
- package/dist/src/subagents/lock-entry-builder.js +63 -0
- package/dist/src/subagents/manager.d.ts +17 -0
- package/dist/src/subagents/manager.js +736 -0
- package/dist/src/testing.d.ts +87 -0
- package/dist/src/testing.js +116 -0
- package/package.json +70 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical package directory machinery: sibling staging/swap replacement,
|
|
3
|
+
* interrupted-swap recovery, create-only publication, reuse decisions, and
|
|
4
|
+
* external (non-registry) package materialization.
|
|
5
|
+
*
|
|
6
|
+
* @experimental This API is unstable and may change without notice.
|
|
7
|
+
*/
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
9
|
+
import { stripFileProtocol } from "@agentxm/registry-client";
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import { protectCreatedAncestors, protectWorkspacePath, } from "@agentxm/workspace-transactions";
|
|
13
|
+
import { recordFootprint } from "@agentxm/workspace-transactions";
|
|
14
|
+
import { shouldReuseCanonicalInstall } from "./canonical-reuse.js";
|
|
15
|
+
import { copyExtensionDirectory } from "./copy-directory.js";
|
|
16
|
+
import { validatePathSafety } from "@agentxm/workspace-state";
|
|
17
|
+
import { CanonicalPackageProbeFailed, CreateDestinationExists, PackageCopyFailed, PackageMaterializationFailed, StagedPackageInvalid, } from "./errors.js";
|
|
18
|
+
import { PathTraversalDetected } from "@agentxm/workspace-state";
|
|
19
|
+
import { computeMaterializedTreeIntegrity, } from "@agentxm/workspace-state";
|
|
20
|
+
export const canonicalMaterializationPaths = (canonicalPath) => ({
|
|
21
|
+
stagingPath: `${canonicalPath}.axm-staging`,
|
|
22
|
+
backupPath: `${canonicalPath}.axm-backup`,
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the sibling replacement state left by an interrupted swap.
|
|
26
|
+
*
|
|
27
|
+
* `replaceCanonicalDirectory` never populates the canonical path in place: it
|
|
28
|
+
* fills a sibling staging directory, then commits with two same-parent renames.
|
|
29
|
+
* So the canonical path only ever holds a whole tree, and the pair of
|
|
30
|
+
* (canonical present, backup present) names the interruption point exactly:
|
|
31
|
+
*
|
|
32
|
+
* - backup, no canonical — died between the two renames; restore the backup.
|
|
33
|
+
* - backup and canonical — the second rename landed; the backup is superseded.
|
|
34
|
+
* - no backup — nothing was swapped; only staging needs discarding.
|
|
35
|
+
*/
|
|
36
|
+
const recoverInterruptedReplacement = (canonicalPath, fs) => Effect.gen(function* () {
|
|
37
|
+
const { stagingPath, backupPath } = canonicalMaterializationPaths(canonicalPath);
|
|
38
|
+
const canonicalExists = yield* fs.exists(canonicalPath);
|
|
39
|
+
const backupExists = yield* fs.exists(backupPath);
|
|
40
|
+
if (backupExists && !canonicalExists) {
|
|
41
|
+
yield* fs.rename(backupPath, canonicalPath);
|
|
42
|
+
}
|
|
43
|
+
else if (backupExists) {
|
|
44
|
+
yield* fs.remove(backupPath, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
yield* fs.remove(stagingPath, { recursive: true, force: true });
|
|
47
|
+
}).pipe(Effect.mapError((cause) => new PackageMaterializationFailed({ path: canonicalPath, step: "recover", cause })));
|
|
48
|
+
/** Recover or clean sibling replacement state before any fallible source work. */
|
|
49
|
+
export const recoverCanonicalDirectory = (args) => Effect.gen(function* () {
|
|
50
|
+
const fs = yield* FileSystem.FileSystem;
|
|
51
|
+
const path = yield* Path.Path;
|
|
52
|
+
const { stagingPath, backupPath } = canonicalMaterializationPaths(args.canonicalPath);
|
|
53
|
+
yield* validatePathSafety(path, args.baseDir, args.canonicalPath);
|
|
54
|
+
yield* validatePathSafety(path, args.baseDir, stagingPath);
|
|
55
|
+
yield* validatePathSafety(path, args.baseDir, backupPath);
|
|
56
|
+
yield* recoverInterruptedReplacement(args.canonicalPath, fs);
|
|
57
|
+
});
|
|
58
|
+
const validateRequiredPackageFiles = (stagingPath, requiredFiles) => Effect.gen(function* () {
|
|
59
|
+
const fs = yield* FileSystem.FileSystem;
|
|
60
|
+
const path = yield* Path.Path;
|
|
61
|
+
yield* Effect.forEach(requiredFiles, (relativePath) => Effect.gen(function* () {
|
|
62
|
+
const filePath = path.join(stagingPath, relativePath);
|
|
63
|
+
yield* validatePathSafety(path, stagingPath, filePath);
|
|
64
|
+
const info = yield* fs
|
|
65
|
+
.stat(filePath)
|
|
66
|
+
.pipe(Effect.mapError((cause) => new StagedPackageInvalid({ file: relativePath, kind: "missing", cause })));
|
|
67
|
+
if (info.type !== "File") {
|
|
68
|
+
return yield* new StagedPackageInvalid({ file: relativePath, kind: "not-file" });
|
|
69
|
+
}
|
|
70
|
+
}), { discard: true });
|
|
71
|
+
});
|
|
72
|
+
/**
|
|
73
|
+
* Publish a complete canonical tree from a sibling staging directory. Recovery
|
|
74
|
+
* restores a prior tree left in the sibling backup by abrupt process death;
|
|
75
|
+
* incomplete staging is never made eligible for reuse.
|
|
76
|
+
*/
|
|
77
|
+
export const replaceCanonicalDirectoryWithInspection = (args) => Effect.gen(function* () {
|
|
78
|
+
const fs = yield* FileSystem.FileSystem;
|
|
79
|
+
const path = yield* Path.Path;
|
|
80
|
+
const { stagingPath, backupPath } = canonicalMaterializationPaths(args.canonicalPath);
|
|
81
|
+
yield* recoverCanonicalDirectory(args);
|
|
82
|
+
yield* protectCreatedAncestors(fs, path, path.dirname(args.canonicalPath));
|
|
83
|
+
yield* fs.makeDirectory(path.dirname(args.canonicalPath), { recursive: true }).pipe(Effect.mapError((cause) => new PackageMaterializationFailed({
|
|
84
|
+
path: args.canonicalPath,
|
|
85
|
+
step: "prepare-parent",
|
|
86
|
+
cause,
|
|
87
|
+
})));
|
|
88
|
+
const inspection = yield* Effect.gen(function* () {
|
|
89
|
+
yield* fs.makeDirectory(stagingPath, { recursive: true }).pipe(Effect.mapError((cause) => new PackageMaterializationFailed({
|
|
90
|
+
path: stagingPath,
|
|
91
|
+
step: "prepare-staging",
|
|
92
|
+
cause,
|
|
93
|
+
})));
|
|
94
|
+
yield* args.populate(stagingPath);
|
|
95
|
+
if (args.validate !== undefined)
|
|
96
|
+
yield* args.validate(stagingPath);
|
|
97
|
+
return yield* args.inspect(stagingPath);
|
|
98
|
+
}).pipe(Effect.tapError(() => fs.remove(stagingPath, { recursive: true, force: true }).pipe(Effect.ignore)));
|
|
99
|
+
yield* protectWorkspacePath(args.canonicalPath);
|
|
100
|
+
const hadCanonical = yield* fs
|
|
101
|
+
.exists(args.canonicalPath)
|
|
102
|
+
.pipe(Effect.mapError((cause) => new PackageMaterializationFailed({ path: args.canonicalPath, step: "inspect", cause })));
|
|
103
|
+
yield* Effect.uninterruptible(Effect.gen(function* () {
|
|
104
|
+
if (hadCanonical)
|
|
105
|
+
yield* fs.rename(args.canonicalPath, backupPath);
|
|
106
|
+
yield* fs
|
|
107
|
+
.rename(stagingPath, args.canonicalPath)
|
|
108
|
+
.pipe(Effect.tapError(() => hadCanonical
|
|
109
|
+
? fs.rename(backupPath, args.canonicalPath).pipe(Effect.ignore)
|
|
110
|
+
: Effect.void));
|
|
111
|
+
yield* fs.remove(backupPath, { recursive: true, force: true });
|
|
112
|
+
})).pipe(Effect.mapError((cause) => new PackageMaterializationFailed({ path: args.canonicalPath, step: "replace", cause })));
|
|
113
|
+
yield* recordFootprint({
|
|
114
|
+
path: args.canonicalPath,
|
|
115
|
+
change: hadCanonical ? "modified" : "created",
|
|
116
|
+
});
|
|
117
|
+
return { canonicalPath: args.canonicalPath, inspection };
|
|
118
|
+
});
|
|
119
|
+
export const replaceCanonicalDirectory = (args) => replaceCanonicalDirectoryWithInspection({
|
|
120
|
+
...args,
|
|
121
|
+
inspect: () => Effect.void,
|
|
122
|
+
}).pipe(Effect.map(({ canonicalPath }) => canonicalPath));
|
|
123
|
+
/**
|
|
124
|
+
* Publish one create-only authored package without ever populating its
|
|
125
|
+
* canonical directory in place. Interrupted sibling state is resolved before
|
|
126
|
+
* the collision check while the caller holds the workspace mutation lock.
|
|
127
|
+
*/
|
|
128
|
+
export const createCanonicalDirectory = (args) => Effect.gen(function* () {
|
|
129
|
+
const fs = yield* FileSystem.FileSystem;
|
|
130
|
+
yield* recoverCanonicalDirectory(args);
|
|
131
|
+
const exists = yield* fs.exists(args.canonicalPath).pipe(Effect.mapError((cause) => new PackageMaterializationFailed({
|
|
132
|
+
path: args.canonicalPath,
|
|
133
|
+
step: "inspect-create-destination",
|
|
134
|
+
cause,
|
|
135
|
+
})));
|
|
136
|
+
if (exists) {
|
|
137
|
+
return yield* new CreateDestinationExists({
|
|
138
|
+
subject: args.subject,
|
|
139
|
+
path: args.canonicalPath,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return yield* replaceCanonicalDirectory({
|
|
143
|
+
baseDir: args.baseDir,
|
|
144
|
+
canonicalPath: args.canonicalPath,
|
|
145
|
+
populate: args.populate,
|
|
146
|
+
validate: (stagingPath) => validateRequiredPackageFiles(stagingPath, args.requiredFiles ?? []).pipe(Effect.andThen(args.validate === undefined ? Effect.void : args.validate(stagingPath))),
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
/** Preserve an existing external canonical tree unless refresh was explicitly requested. */
|
|
150
|
+
export const canReuseExternalPackage = (args) => Effect.gen(function* () {
|
|
151
|
+
const fs = yield* FileSystem.FileSystem;
|
|
152
|
+
yield* recoverInterruptedReplacement(args.installedPath, fs);
|
|
153
|
+
const canonicalExists = yield* fs.exists(args.installedPath).pipe(Effect.mapError((cause) => new CanonicalPackageProbeFailed({
|
|
154
|
+
detail: args.existsFailureDetail(args.installedPath),
|
|
155
|
+
cause,
|
|
156
|
+
})));
|
|
157
|
+
return canonicalExists && !args.force;
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* Decide whether the installed tree already satisfies the requested ref, so no
|
|
161
|
+
* archive needs to be fetched or written.
|
|
162
|
+
*
|
|
163
|
+
* Callers that stage into a temporary directory must call this against the
|
|
164
|
+
* canonical installed path before staging: the decision is about the installed
|
|
165
|
+
* tree, while the registry materialization only answers where bytes go.
|
|
166
|
+
*/
|
|
167
|
+
export const canReuseInstalledPackage = (args) => Effect.gen(function* () {
|
|
168
|
+
const fs = yield* FileSystem.FileSystem;
|
|
169
|
+
yield* recoverInterruptedReplacement(args.installedPath, fs);
|
|
170
|
+
const canonicalExists = yield* fs.exists(args.installedPath).pipe(Effect.mapError((cause) => new CanonicalPackageProbeFailed({
|
|
171
|
+
detail: args.existsFailureDetail(args.installedPath),
|
|
172
|
+
cause,
|
|
173
|
+
})));
|
|
174
|
+
return shouldReuseCanonicalInstall({
|
|
175
|
+
canonicalExists,
|
|
176
|
+
force: args.force,
|
|
177
|
+
hasIntegrity: args.hasIntegrity,
|
|
178
|
+
refVersion: args.refVersion,
|
|
179
|
+
lockedVersion: args.lockedVersion,
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
export const materializeExternalPackageWithTreeIntegrity = (args) => Effect.gen(function* () {
|
|
183
|
+
const path = yield* Path.Path;
|
|
184
|
+
yield* validatePathSafety(path, args.baseDir, args.canonicalPath);
|
|
185
|
+
const sourcePath = stripFileProtocol(args.sourceLocation);
|
|
186
|
+
const isSelfCopy = path.resolve(sourcePath) === path.resolve(args.canonicalPath);
|
|
187
|
+
if (isSelfCopy) {
|
|
188
|
+
return {
|
|
189
|
+
canonicalPath: args.canonicalPath,
|
|
190
|
+
treeIntegrity: yield* computeMaterializedTreeIntegrity(args.canonicalPath),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
const result = yield* replaceCanonicalDirectoryWithInspection({
|
|
194
|
+
baseDir: args.baseDir,
|
|
195
|
+
canonicalPath: args.canonicalPath,
|
|
196
|
+
populate: (stagingPath) => copyExtensionDirectory(sourcePath, stagingPath).pipe(Effect.mapError((cause) => new PackageCopyFailed({
|
|
197
|
+
severity: args.copyFailureCode,
|
|
198
|
+
detail: args.copyFailureDetail(args.canonicalPath),
|
|
199
|
+
cause,
|
|
200
|
+
}))),
|
|
201
|
+
...(args.validate === undefined ? {} : { validate: args.validate }),
|
|
202
|
+
inspect: computeMaterializedTreeIntegrity,
|
|
203
|
+
});
|
|
204
|
+
return {
|
|
205
|
+
canonicalPath: result.canonicalPath,
|
|
206
|
+
treeIntegrity: result.inspection,
|
|
207
|
+
};
|
|
208
|
+
});
|
|
209
|
+
export const materializeExternalPackage = (args) => materializeExternalPackageWithTreeIntegrity(args).pipe(Effect.map(({ canonicalPath }) => canonicalPath));
|
|
210
|
+
//# sourceMappingURL=canonical-directory.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reuse decision for registry canonical trees.
|
|
3
|
+
*
|
|
4
|
+
* Archive integrity is a download-time supply-chain guarantee: the SRI hash
|
|
5
|
+
* in the lockfile is verified against fetched archive bytes before
|
|
6
|
+
* extraction. After install, canonical content is workspace-owned —
|
|
7
|
+
* content-preserving workspace tools (formatters, line-ending
|
|
8
|
+
* normalization) may rewrite installed files, and a no-op install must not
|
|
9
|
+
* revert them by re-extracting the archive.
|
|
10
|
+
*
|
|
11
|
+
* @experimental This API is unstable and may change without notice.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Decide whether a registry install may reuse the existing canonical tree
|
|
15
|
+
* instead of re-downloading and re-extracting the archive.
|
|
16
|
+
*
|
|
17
|
+
* `canonicalExists` must describe the canonical installed tree. Probing a
|
|
18
|
+
* staging destination instead makes it permanently false, which silently
|
|
19
|
+
* defeats the reuse contract above; prefer `canReuseInstalledPackage`, which
|
|
20
|
+
* takes the installed path by name.
|
|
21
|
+
*
|
|
22
|
+
* - `force` always re-materializes (the internal repair path for `--reinstall` installs
|
|
23
|
+
* and lint autofix reinstalls).
|
|
24
|
+
* - Refs without integrity (synthetic refs from publish) reuse an existing
|
|
25
|
+
* tree, preserving the historical publish behavior.
|
|
26
|
+
* - Refs with integrity reuse the tree only when the lockfile already pins
|
|
27
|
+
* the requested version, so version changes still re-materialize.
|
|
28
|
+
*
|
|
29
|
+
* @experimental This API is unstable and may change without notice.
|
|
30
|
+
*/
|
|
31
|
+
export declare const shouldReuseCanonicalInstall: (args: {
|
|
32
|
+
/** The canonical extension directory already exists on disk. */
|
|
33
|
+
readonly canonicalExists: boolean;
|
|
34
|
+
/** Caller demanded an unconditional re-materialization. */
|
|
35
|
+
readonly force: boolean;
|
|
36
|
+
/** The ref carries a pinned archive integrity (registry-resolved). */
|
|
37
|
+
readonly hasIntegrity: boolean;
|
|
38
|
+
/** Exact version requested by the ref being installed. */
|
|
39
|
+
readonly refVersion: string | undefined;
|
|
40
|
+
/** Resolved version recorded in the current lockfile entry, when any. */
|
|
41
|
+
readonly lockedVersion: string | undefined;
|
|
42
|
+
}) => boolean;
|
|
43
|
+
//# sourceMappingURL=canonical-reuse.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reuse decision for registry canonical trees.
|
|
3
|
+
*
|
|
4
|
+
* Archive integrity is a download-time supply-chain guarantee: the SRI hash
|
|
5
|
+
* in the lockfile is verified against fetched archive bytes before
|
|
6
|
+
* extraction. After install, canonical content is workspace-owned —
|
|
7
|
+
* content-preserving workspace tools (formatters, line-ending
|
|
8
|
+
* normalization) may rewrite installed files, and a no-op install must not
|
|
9
|
+
* revert them by re-extracting the archive.
|
|
10
|
+
*
|
|
11
|
+
* @experimental This API is unstable and may change without notice.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Decide whether a registry install may reuse the existing canonical tree
|
|
15
|
+
* instead of re-downloading and re-extracting the archive.
|
|
16
|
+
*
|
|
17
|
+
* `canonicalExists` must describe the canonical installed tree. Probing a
|
|
18
|
+
* staging destination instead makes it permanently false, which silently
|
|
19
|
+
* defeats the reuse contract above; prefer `canReuseInstalledPackage`, which
|
|
20
|
+
* takes the installed path by name.
|
|
21
|
+
*
|
|
22
|
+
* - `force` always re-materializes (the internal repair path for `--reinstall` installs
|
|
23
|
+
* and lint autofix reinstalls).
|
|
24
|
+
* - Refs without integrity (synthetic refs from publish) reuse an existing
|
|
25
|
+
* tree, preserving the historical publish behavior.
|
|
26
|
+
* - Refs with integrity reuse the tree only when the lockfile already pins
|
|
27
|
+
* the requested version, so version changes still re-materialize.
|
|
28
|
+
*
|
|
29
|
+
* @experimental This API is unstable and may change without notice.
|
|
30
|
+
*/
|
|
31
|
+
export const shouldReuseCanonicalInstall = (args) => {
|
|
32
|
+
if (!args.canonicalExists || args.force)
|
|
33
|
+
return false;
|
|
34
|
+
if (!args.hasIntegrity)
|
|
35
|
+
return true;
|
|
36
|
+
return args.refVersion !== undefined && args.refVersion === args.lockedVersion;
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=canonical-reuse.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared extension utilities.
|
|
3
|
+
*
|
|
4
|
+
* Cross-cutting utilities used by multiple extension types.
|
|
5
|
+
* Promoted from skills-specific modules to avoid cross-feature dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import type { PlatformError } from "effect/PlatformError";
|
|
11
|
+
import * as FileSystem from "effect/FileSystem";
|
|
12
|
+
import * as Path from "effect/Path";
|
|
13
|
+
import * as Effect from "effect/Effect";
|
|
14
|
+
/**
|
|
15
|
+
* Options for {@link copyExtensionDirectory}.
|
|
16
|
+
*/
|
|
17
|
+
export type CopyExtensionDirectoryOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* When true, omit entries that should not appear in a fanned-out agent
|
|
20
|
+
* artifact: `README.md`, `metadata.json`, and `_`-prefixed names (`.git` is
|
|
21
|
+
* always omitted).
|
|
22
|
+
*
|
|
23
|
+
* Defaults to `false` — a faithful copy of the source, matching what
|
|
24
|
+
* `publish` packages into the archive. Canonical materialization must use
|
|
25
|
+
* the faithful copy so realigning a lockfile does not strip authored files
|
|
26
|
+
* (e.g. `README.md`) that the published package contains.
|
|
27
|
+
*/
|
|
28
|
+
readonly forAgentArtifact?: boolean;
|
|
29
|
+
};
|
|
30
|
+
export type CopyExtensionDirectoryFailureDetails = {
|
|
31
|
+
readonly sourcePath: string;
|
|
32
|
+
readonly targetPath: string;
|
|
33
|
+
readonly subject?: string;
|
|
34
|
+
readonly sourceExists?: boolean;
|
|
35
|
+
};
|
|
36
|
+
export declare const formatCopyExtensionDirectoryFailure: ({ sourcePath, targetPath, subject, sourceExists, }: CopyExtensionDirectoryFailureDetails) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Recursively copies an extension directory from `src` to `dest`.
|
|
39
|
+
*
|
|
40
|
+
* By default this is a faithful copy of every entry except `.git`, used to
|
|
41
|
+
* materialize the canonical extension store from a package archive or local
|
|
42
|
+
* source. Pass `{ forAgentArtifact: true }` to also omit non-artifact entries
|
|
43
|
+
* when fanning the canonical copy out to an agent directory (see
|
|
44
|
+
* {@link CopyExtensionDirectoryOptions}).
|
|
45
|
+
*
|
|
46
|
+
* Symlinks are dereferenced (file content is copied, not the link).
|
|
47
|
+
* Directory entries are copied concurrently.
|
|
48
|
+
*/
|
|
49
|
+
export declare const copyExtensionDirectory: (src: string, dest: string, options?: CopyExtensionDirectoryOptions) => Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
50
|
+
//# sourceMappingURL=copy-directory.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared extension utilities.
|
|
3
|
+
*
|
|
4
|
+
* Cross-cutting utilities used by multiple extension types.
|
|
5
|
+
* Promoted from skills-specific modules to avoid cross-feature dependencies.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import * as FileSystem from "effect/FileSystem";
|
|
11
|
+
import * as Path from "effect/Path";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
// Path Safety Validation
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// -----------------------------------------------------------------------------
|
|
17
|
+
// Extension Directory Copy
|
|
18
|
+
// -----------------------------------------------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* VCS state is never copied in any copy mode. It does not belong to authored
|
|
21
|
+
* package content, and copying a `.git` directory from a git-hosted or local
|
|
22
|
+
* source would only bloat the canonical copy.
|
|
23
|
+
*/
|
|
24
|
+
const ALWAYS_EXCLUDED_NAMES = new Set([".git"]);
|
|
25
|
+
/**
|
|
26
|
+
* Entries additionally omitted from an agent-facing artifact: human files,
|
|
27
|
+
* AXM-managed install metadata, and authoring-private (`_`-prefixed) files.
|
|
28
|
+
* These belong in the canonical copy (they are part of the published package)
|
|
29
|
+
* but are trimmed when fanning out to an agent directory.
|
|
30
|
+
*/
|
|
31
|
+
const AGENT_ARTIFACT_EXCLUDED_NAMES = new Set(["README.md", "metadata.json"]);
|
|
32
|
+
const isExcluded = (name, forAgentArtifact) => {
|
|
33
|
+
if (ALWAYS_EXCLUDED_NAMES.has(name))
|
|
34
|
+
return true;
|
|
35
|
+
if (!forAgentArtifact)
|
|
36
|
+
return false;
|
|
37
|
+
return AGENT_ARTIFACT_EXCLUDED_NAMES.has(name) || name.startsWith("_");
|
|
38
|
+
};
|
|
39
|
+
const copyEntry = (src, dest, fs, path, forAgentArtifact) => Effect.gen(function* () {
|
|
40
|
+
// Read through symlinks (stat follows symlinks by default)
|
|
41
|
+
const info = yield* fs.stat(src);
|
|
42
|
+
if (info.type === "Directory") {
|
|
43
|
+
yield* copyDir(src, dest, fs, path, forAgentArtifact);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Copy file content — readFile follows symlinks, giving us dereferenced content
|
|
47
|
+
const content = yield* fs.readFile(src);
|
|
48
|
+
yield* fs.makeDirectory(path.dirname(dest), { recursive: true });
|
|
49
|
+
yield* fs.writeFile(dest, content);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const copyDir = (src, dest, fs, path, forAgentArtifact) => Effect.gen(function* () {
|
|
53
|
+
yield* fs.makeDirectory(dest, { recursive: true });
|
|
54
|
+
const entries = yield* fs.readDirectory(src);
|
|
55
|
+
yield* Effect.forEach(entries.filter((name) => !isExcluded(name, forAgentArtifact)), (name) => copyEntry(path.join(src, name), path.join(dest, name), fs, path, forAgentArtifact), { concurrency: "unbounded" });
|
|
56
|
+
});
|
|
57
|
+
export const formatCopyExtensionDirectoryFailure = ({ sourcePath, targetPath, subject = "extension files", sourceExists, }) => {
|
|
58
|
+
const missingSource = sourceExists === false ? "; source does not exist" : "";
|
|
59
|
+
return `Failed to copy ${subject} from ${sourcePath} to ${targetPath}${missingSource}`;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Recursively copies an extension directory from `src` to `dest`.
|
|
63
|
+
*
|
|
64
|
+
* By default this is a faithful copy of every entry except `.git`, used to
|
|
65
|
+
* materialize the canonical extension store from a package archive or local
|
|
66
|
+
* source. Pass `{ forAgentArtifact: true }` to also omit non-artifact entries
|
|
67
|
+
* when fanning the canonical copy out to an agent directory (see
|
|
68
|
+
* {@link CopyExtensionDirectoryOptions}).
|
|
69
|
+
*
|
|
70
|
+
* Symlinks are dereferenced (file content is copied, not the link).
|
|
71
|
+
* Directory entries are copied concurrently.
|
|
72
|
+
*/
|
|
73
|
+
export const copyExtensionDirectory = (src, dest, options) => Effect.gen(function* () {
|
|
74
|
+
const fs = yield* FileSystem.FileSystem;
|
|
75
|
+
const path = yield* Path.Path;
|
|
76
|
+
yield* copyDir(src, dest, fs, path, options?.forAgentArtifact ?? false);
|
|
77
|
+
});
|
|
78
|
+
//# sourceMappingURL=copy-directory.js.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failure family for canonical package materialization: staging, swap,
|
|
3
|
+
* copy, integrity, and the shared lifecycle closure postconditions. Fields are
|
|
4
|
+
* domain facts; the application error boundary owns rendering, codes, and
|
|
5
|
+
* suggestions.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import { PathTraversalDetected } from "@agentxm/workspace-state";
|
|
10
|
+
declare const PackageMaterializationFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
11
|
+
readonly _tag: "PackageMaterializationFailed";
|
|
12
|
+
} & Readonly<A>;
|
|
13
|
+
/** Canonical package staging/swap machinery failed at a filesystem step. */
|
|
14
|
+
export declare class PackageMaterializationFailed extends PackageMaterializationFailed_base<{
|
|
15
|
+
/** The canonical path for most steps; the staging path for `prepare-staging`. */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly step: "recover" | "prepare-parent" | "prepare-staging" | "inspect" | "replace" | "inspect-create-destination";
|
|
18
|
+
readonly cause: unknown;
|
|
19
|
+
}> {
|
|
20
|
+
}
|
|
21
|
+
declare const StagedPackageInvalid_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
22
|
+
readonly _tag: "StagedPackageInvalid";
|
|
23
|
+
} & Readonly<A>;
|
|
24
|
+
/** A staged package tree is missing or misshapes a type-required file. */
|
|
25
|
+
export declare class StagedPackageInvalid extends StagedPackageInvalid_base<{
|
|
26
|
+
readonly file: string;
|
|
27
|
+
readonly kind: "missing" | "not-file";
|
|
28
|
+
readonly cause?: unknown;
|
|
29
|
+
}> {
|
|
30
|
+
}
|
|
31
|
+
declare const CanonicalPackageProbeFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
32
|
+
readonly _tag: "CanonicalPackageProbeFailed";
|
|
33
|
+
} & Readonly<A>;
|
|
34
|
+
/**
|
|
35
|
+
* Probing installed canonical state failed. `detail` carries the caller's
|
|
36
|
+
* fact sentence verbatim; each call site owns its subject wording.
|
|
37
|
+
*/
|
|
38
|
+
export declare class CanonicalPackageProbeFailed extends CanonicalPackageProbeFailed_base<{
|
|
39
|
+
readonly detail: string;
|
|
40
|
+
readonly cause: unknown;
|
|
41
|
+
}> {
|
|
42
|
+
}
|
|
43
|
+
declare const PackageCopyFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
44
|
+
readonly _tag: "PackageCopyFailed";
|
|
45
|
+
} & Readonly<A>;
|
|
46
|
+
/**
|
|
47
|
+
* Copying package content into staging failed. `severity` records the caller's
|
|
48
|
+
* decision about whether the failure indicts the source content (`validation`)
|
|
49
|
+
* or the machinery (`internal`).
|
|
50
|
+
*/
|
|
51
|
+
export declare class PackageCopyFailed extends PackageCopyFailed_base<{
|
|
52
|
+
readonly severity: "internal" | "validation";
|
|
53
|
+
readonly detail: string;
|
|
54
|
+
readonly cause: unknown;
|
|
55
|
+
}> {
|
|
56
|
+
}
|
|
57
|
+
declare const ArchiveIntegrityMismatch_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
58
|
+
readonly _tag: "ArchiveIntegrityMismatch";
|
|
59
|
+
} & Readonly<A>;
|
|
60
|
+
/** The fetched archive did not match the accepted integrity pin. */
|
|
61
|
+
export declare class ArchiveIntegrityMismatch extends ArchiveIntegrityMismatch_base<{
|
|
62
|
+
/** The caller's subject sentence, e.g. `Integrity mismatch for name@1.0.0`. */
|
|
63
|
+
readonly subject: string;
|
|
64
|
+
}> {
|
|
65
|
+
}
|
|
66
|
+
declare const LifecyclePostconditionViolated_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
67
|
+
readonly _tag: "LifecyclePostconditionViolated";
|
|
68
|
+
} & Readonly<A>;
|
|
69
|
+
/** A lifecycle transition committed but its observable postcondition failed. */
|
|
70
|
+
export declare class LifecyclePostconditionViolated extends LifecyclePostconditionViolated_base<{
|
|
71
|
+
readonly postcondition: "install-observable" | "install-declared" | "new-observable" | "new-declared" | "materialize-observable" | "uninstall-remains-declared" | "uninstall-observed-state";
|
|
72
|
+
readonly targetType: string;
|
|
73
|
+
readonly targetName: string;
|
|
74
|
+
}> {
|
|
75
|
+
}
|
|
76
|
+
declare const ScaffoldedExtensionUnresolved_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
77
|
+
readonly _tag: "ScaffoldedExtensionUnresolved";
|
|
78
|
+
} & Readonly<A>;
|
|
79
|
+
/** A newly scaffolded extension could not be resolved from its workspace source. */
|
|
80
|
+
export declare class ScaffoldedExtensionUnresolved extends ScaffoldedExtensionUnresolved_base<{
|
|
81
|
+
readonly targetType: string;
|
|
82
|
+
readonly targetName: string;
|
|
83
|
+
}> {
|
|
84
|
+
}
|
|
85
|
+
declare const CreateDestinationExists_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
86
|
+
readonly _tag: "CreateDestinationExists";
|
|
87
|
+
} & Readonly<A>;
|
|
88
|
+
/** A create-only operation found existing state at its destination path. */
|
|
89
|
+
export declare class CreateDestinationExists extends CreateDestinationExists_base<{
|
|
90
|
+
readonly subject: string;
|
|
91
|
+
readonly path: string;
|
|
92
|
+
}> {
|
|
93
|
+
}
|
|
94
|
+
/** Every failure canonical materialization and the closure recipes construct. */
|
|
95
|
+
export type MaterializationError = PackageMaterializationFailed | StagedPackageInvalid | CanonicalPackageProbeFailed | PackageCopyFailed | ArchiveIntegrityMismatch | CreateDestinationExists | PathTraversalDetected | LifecyclePostconditionViolated | ScaffoldedExtensionUnresolved;
|
|
96
|
+
export {};
|
|
97
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed failure family for canonical package materialization: staging, swap,
|
|
3
|
+
* copy, integrity, and the shared lifecycle closure postconditions. Fields are
|
|
4
|
+
* domain facts; the application error boundary owns rendering, codes, and
|
|
5
|
+
* suggestions.
|
|
6
|
+
*
|
|
7
|
+
* @experimental This API is unstable and may change without notice.
|
|
8
|
+
*/
|
|
9
|
+
import { PathTraversalDetected } from "@agentxm/workspace-state";
|
|
10
|
+
import * as Data from "effect/Data";
|
|
11
|
+
/** Canonical package staging/swap machinery failed at a filesystem step. */
|
|
12
|
+
export class PackageMaterializationFailed extends Data.TaggedError("PackageMaterializationFailed") {
|
|
13
|
+
}
|
|
14
|
+
/** A staged package tree is missing or misshapes a type-required file. */
|
|
15
|
+
export class StagedPackageInvalid extends Data.TaggedError("StagedPackageInvalid") {
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Probing installed canonical state failed. `detail` carries the caller's
|
|
19
|
+
* fact sentence verbatim; each call site owns its subject wording.
|
|
20
|
+
*/
|
|
21
|
+
export class CanonicalPackageProbeFailed extends Data.TaggedError("CanonicalPackageProbeFailed") {
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Copying package content into staging failed. `severity` records the caller's
|
|
25
|
+
* decision about whether the failure indicts the source content (`validation`)
|
|
26
|
+
* or the machinery (`internal`).
|
|
27
|
+
*/
|
|
28
|
+
export class PackageCopyFailed extends Data.TaggedError("PackageCopyFailed") {
|
|
29
|
+
}
|
|
30
|
+
/** The fetched archive did not match the accepted integrity pin. */
|
|
31
|
+
export class ArchiveIntegrityMismatch extends Data.TaggedError("ArchiveIntegrityMismatch") {
|
|
32
|
+
}
|
|
33
|
+
/** A lifecycle transition committed but its observable postcondition failed. */
|
|
34
|
+
export class LifecyclePostconditionViolated extends Data.TaggedError("LifecyclePostconditionViolated") {
|
|
35
|
+
}
|
|
36
|
+
/** A newly scaffolded extension could not be resolved from its workspace source. */
|
|
37
|
+
export class ScaffoldedExtensionUnresolved extends Data.TaggedError("ScaffoldedExtensionUnresolved") {
|
|
38
|
+
}
|
|
39
|
+
/** A create-only operation found existing state at its destination path. */
|
|
40
|
+
export class CreateDestinationExists extends Data.TaggedError("CreateDestinationExists") {
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as FileSystem from "effect/FileSystem";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Path from "effect/Path";
|
|
5
|
+
import { CanonicalPackageProbeFailed } from "./errors.js";
|
|
6
|
+
import type { PathTraversalDetected } from "@agentxm/workspace-state";
|
|
7
|
+
import type { PackageContentHashFailed, WorkspaceSourceInvalid } from "@agentxm/workspace-state";
|
|
8
|
+
import type { LockEntryToRefError } from "@agentxm/workspace-state";
|
|
9
|
+
import type { WorkspaceSettingsReadFailure } from "@agentxm/workspace-state";
|
|
10
|
+
import type { SkillLockEntry } from "@agentxm/workspace-state";
|
|
11
|
+
import type { ConfiguredRecordRow } from "@agentxm/workspace-state";
|
|
12
|
+
import type { McpServerExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/mcp-server";
|
|
13
|
+
import type { PackRef } from "@agentxm/extension-model/unstable/extensions/refs/pack";
|
|
14
|
+
import type { SourceHostConfig } from "@agentxm/workspace-state";
|
|
15
|
+
import type { SkillExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/skill";
|
|
16
|
+
import type { SubagentExtensionRef } from "@agentxm/extension-model/unstable/extensions/refs/subagent";
|
|
17
|
+
import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
|
|
18
|
+
import type { WorkspaceLayout } from "@agentxm/workspace-state";
|
|
19
|
+
type DiskRefError = LockEntryToRefError | WorkspaceSourceInvalid | PathTraversalDetected | PackageContentHashFailed | CanonicalPackageProbeFailed;
|
|
20
|
+
interface DiskRefEnv {
|
|
21
|
+
readonly fs: FileSystem.FileSystem;
|
|
22
|
+
readonly path: Path.Path;
|
|
23
|
+
readonly baseDir: string;
|
|
24
|
+
readonly scope: WorkspaceScope;
|
|
25
|
+
readonly layout: WorkspaceLayout;
|
|
26
|
+
}
|
|
27
|
+
interface SkillDiskAcceptedResolutionContext {
|
|
28
|
+
readonly lockEntries: Readonly<Record<string, SkillLockEntry>>;
|
|
29
|
+
readonly getConfiguredSources: () => Effect.Effect<ReadonlyArray<SourceHostConfig>, WorkspaceSettingsReadFailure>;
|
|
30
|
+
readonly getConfiguredSourceByName: (name: string) => Effect.Effect<Option.Option<SourceHostConfig>, WorkspaceSettingsReadFailure>;
|
|
31
|
+
}
|
|
32
|
+
export declare const configuredSkillsToDiskRefs: (env: DiskRefEnv, configured: Readonly<Record<string, ConfiguredRecordRow>>, accepted?: SkillDiskAcceptedResolutionContext) => Effect.Effect<ReadonlyArray<SkillExtensionRef>, DiskRefError>;
|
|
33
|
+
export declare const configuredMcpServersToDiskRefs: (env: DiskRefEnv, configured: Readonly<Record<string, ConfiguredRecordRow>>) => Effect.Effect<ReadonlyArray<McpServerExtensionRef>, DiskRefError>;
|
|
34
|
+
export declare const configuredSubagentsToDiskRefs: (env: DiskRefEnv, configured: Readonly<Record<string, ConfiguredRecordRow>>) => Effect.Effect<ReadonlyArray<SubagentExtensionRef>, DiskRefError>;
|
|
35
|
+
export declare const configuredPacksToDiskRefs: (env: DiskRefEnv, configured: Readonly<Record<string, ConfiguredRecordRow>>) => Effect.Effect<ReadonlyArray<PackRef>, DiskRefError>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=materializable-from-disk.d.ts.map
|