agentwheel 0.20.0 → 0.20.2
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/{apply-7HJQQOCI.js → apply-OGC6E2Y4.js} +4 -2
- package/dist/{chunk-USHT7FGV.js → chunk-BAJEWUI6.js} +84 -6
- package/dist/{chunk-HOYIIUL5.js → chunk-Z4N7TB6Q.js} +37 -4
- package/dist/index.js +730 -298
- package/dist/{transaction-IM5MZS4B.js → transaction-7CNV5MAQ.js} +5 -1
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +1 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
canonicalizeGraphLock,
|
|
10
10
|
claudeInstructionBridgesAgents,
|
|
11
11
|
combineMergeRemovals,
|
|
12
|
+
commitManifestMetadataJournal,
|
|
13
|
+
computeInstallManifestInventoryRevision,
|
|
12
14
|
computeManifestRevision,
|
|
13
15
|
computeTargetFingerprint,
|
|
14
16
|
desiredManagedInstructionBlockHash,
|
|
@@ -19,6 +21,7 @@ import {
|
|
|
19
21
|
managedInstructionBlockMode,
|
|
20
22
|
managedInstructionPhysicalKey,
|
|
21
23
|
managedInstructionSelector,
|
|
24
|
+
mergeContributionAbsent,
|
|
22
25
|
mergeRemovalForInstall,
|
|
23
26
|
normalizeImmutableCacheIdentity,
|
|
24
27
|
normalizeOwners,
|
|
@@ -30,7 +33,7 @@ import {
|
|
|
30
33
|
uninstall,
|
|
31
34
|
withManifestRevision,
|
|
32
35
|
writeInstallManifest
|
|
33
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-BAJEWUI6.js";
|
|
34
37
|
import {
|
|
35
38
|
CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
36
39
|
GovernedMutation,
|
|
@@ -38,6 +41,7 @@ import {
|
|
|
38
41
|
acquireApplyLock,
|
|
39
42
|
adapterSchema,
|
|
40
43
|
adapterTargetSupport,
|
|
44
|
+
applyLockPath,
|
|
41
45
|
artifactFormatSchema,
|
|
42
46
|
artifactTypeSchema,
|
|
43
47
|
assertGovernedRuntimeTransportSupported,
|
|
@@ -53,9 +57,12 @@ import {
|
|
|
53
57
|
installRootForAdapterInstallationType,
|
|
54
58
|
installRootForArtifacts,
|
|
55
59
|
isCompositeWorkspaceProfile,
|
|
60
|
+
listAllApplyJournals,
|
|
61
|
+
listApplyJournals,
|
|
56
62
|
listMutationReceipts,
|
|
57
63
|
loadAdapterConfig,
|
|
58
64
|
localTransport,
|
|
65
|
+
mutationMetadataForApplyJournal,
|
|
59
66
|
mutationPolicyForWorkspace,
|
|
60
67
|
packageAssetSchema,
|
|
61
68
|
packageComposeEntrySchema,
|
|
@@ -85,8 +92,9 @@ import {
|
|
|
85
92
|
workspaceConfigSchema,
|
|
86
93
|
workspaceExportsSchema,
|
|
87
94
|
workspaceSelectionImportSchema,
|
|
95
|
+
writeApplyJournal,
|
|
88
96
|
writeWorkspaceConfig
|
|
89
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-Z4N7TB6Q.js";
|
|
90
98
|
import {
|
|
91
99
|
inferSourceDriverName
|
|
92
100
|
} from "./chunk-EMDIG24I.js";
|
|
@@ -101,11 +109,11 @@ import {
|
|
|
101
109
|
} from "./chunk-7VPI5J5Y.js";
|
|
102
110
|
|
|
103
111
|
// src/cli/index.ts
|
|
104
|
-
import { createHash as
|
|
112
|
+
import { createHash as createHash14 } from "crypto";
|
|
105
113
|
import { existsSync } from "fs";
|
|
106
114
|
import { mkdir as mkdir16, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
107
115
|
import { homedir as homedir12 } from "os";
|
|
108
|
-
import { dirname as dirname22, join as join45, resolve as
|
|
116
|
+
import { dirname as dirname22, join as join45, resolve as resolve24 } from "path";
|
|
109
117
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
110
118
|
import { Command } from "commander";
|
|
111
119
|
|
|
@@ -1449,6 +1457,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1449
1457
|
const installationType = resolveInstallationTypeForArtifacts(adapter, installableArtifacts.map((artifact) => artifact.type), requestedInstallationType);
|
|
1450
1458
|
const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, installableArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
1451
1459
|
await validateArtifactsForInstall(installableArtifacts, adapter, installationType);
|
|
1460
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(installRoot, adapter.name, transport);
|
|
1452
1461
|
for (const artifact of installableArtifacts) {
|
|
1453
1462
|
if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
|
|
1454
1463
|
throw new Error(`Dependency-provided ${artifact.type} artifacts cannot be installed until per-subentry ownership exists: ${artifact.type}/${artifact.name}`);
|
|
@@ -1462,7 +1471,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1462
1471
|
}
|
|
1463
1472
|
}
|
|
1464
1473
|
await addProgrammaticOperations(desired, adapter, installRoot);
|
|
1465
|
-
return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType });
|
|
1474
|
+
return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType, runtimeStateRevision });
|
|
1466
1475
|
}
|
|
1467
1476
|
async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
|
|
1468
1477
|
const workspaceOwner = options.workspaceOwner;
|
|
@@ -1512,7 +1521,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1512
1521
|
}
|
|
1513
1522
|
}
|
|
1514
1523
|
const incompleteMergeOwnership = existing2 && existing2.mergeStrategy && !("mergeCreatedDestination" in existing2 && existing2.mergeCreatedDestination === true) && !("mergeRemoval" in existing2 && hasMergeRemovalContent(existing2.mergeRemoval));
|
|
1515
|
-
const adoptExisting = exists2 && (
|
|
1524
|
+
const adoptExisting = exists2 && (incompleteMergeOwnership || !existing2 && options.forceConflict === true);
|
|
1516
1525
|
let mergeRemoval;
|
|
1517
1526
|
try {
|
|
1518
1527
|
mergeRemoval = await mergeRemovalForInstall(op.sourcePath, op.mergeStrategy, currentContent, {
|
|
@@ -1544,7 +1553,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1544
1553
|
mergeCreatedDestination: existing2?.mergeCreatedDestination,
|
|
1545
1554
|
currentHash: currentHash2,
|
|
1546
1555
|
manifestHash: existing2?.hash,
|
|
1547
|
-
reason: existing2 ? "
|
|
1556
|
+
reason: existing2 ? "repairing exact incomplete merge ownership" : "force adopting exact unmanaged merge contribution"
|
|
1548
1557
|
});
|
|
1549
1558
|
continue;
|
|
1550
1559
|
}
|
|
@@ -1684,6 +1693,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1684
1693
|
}
|
|
1685
1694
|
for (const operation of operations) assertOperationContained(operation, targetRoot);
|
|
1686
1695
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
1696
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(targetRoot, adapter.name, transport);
|
|
1697
|
+
if (options.runtimeStateRevision && runtimeStateRevision !== options.runtimeStateRevision) {
|
|
1698
|
+
throw new Error("Runtime manifest inventory changed during planning; replan needed.");
|
|
1699
|
+
}
|
|
1687
1700
|
return {
|
|
1688
1701
|
adapter: adapter.name,
|
|
1689
1702
|
installationType: options.installationType ?? defaultInstallationType,
|
|
@@ -1692,6 +1705,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1692
1705
|
operations,
|
|
1693
1706
|
hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
|
|
1694
1707
|
baseRevision: options.baseRevision ?? manifest?.revision ?? null,
|
|
1708
|
+
runtimeStateRevision,
|
|
1695
1709
|
migrationReport: migration.report,
|
|
1696
1710
|
graphLockDigest: options.graphLockDigest,
|
|
1697
1711
|
adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
|
|
@@ -2182,6 +2196,7 @@ function isPendingInstallOperation(operation) {
|
|
|
2182
2196
|
// src/install/uninstall.ts
|
|
2183
2197
|
import { join as join13 } from "path";
|
|
2184
2198
|
async function createUninstallPlan(manifest, transport = localTransport) {
|
|
2199
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
|
|
2185
2200
|
const operations = [];
|
|
2186
2201
|
for (const entry of manifest.entries) {
|
|
2187
2202
|
const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
|
|
@@ -2237,6 +2252,9 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2237
2252
|
}
|
|
2238
2253
|
}
|
|
2239
2254
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
2255
|
+
if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
|
|
2256
|
+
throw new Error("Runtime manifest inventory changed during uninstall planning; replan needed.");
|
|
2257
|
+
}
|
|
2240
2258
|
return {
|
|
2241
2259
|
adapter: manifest.adapter,
|
|
2242
2260
|
installationType: "installationType" in manifest ? manifest.installationType : defaultInstallationType,
|
|
@@ -2245,10 +2263,12 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2245
2263
|
operations,
|
|
2246
2264
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
2247
2265
|
baseRevision: manifest.revision,
|
|
2266
|
+
runtimeStateRevision,
|
|
2248
2267
|
adapterCode: manifest.adapterCode
|
|
2249
2268
|
};
|
|
2250
2269
|
}
|
|
2251
2270
|
async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport = localTransport, options = {}) {
|
|
2271
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
|
|
2252
2272
|
const installationType = "installationType" in manifest ? manifest.installationType : defaultInstallationType;
|
|
2253
2273
|
const stateKey = "stateKey" in manifest ? manifest.stateKey : void 0;
|
|
2254
2274
|
const desiredPlan = await createCombinedInstallPlan(remainingDesired, adapter, manifest.targetRoot, void 0, transport, { installationType, stateKey });
|
|
@@ -2338,6 +2358,9 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2338
2358
|
}
|
|
2339
2359
|
}
|
|
2340
2360
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
2361
|
+
if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
|
|
2362
|
+
throw new Error("Runtime manifest inventory changed during ownership uninstall planning; replan needed.");
|
|
2363
|
+
}
|
|
2341
2364
|
return {
|
|
2342
2365
|
adapter: manifest.adapter,
|
|
2343
2366
|
installationType,
|
|
@@ -2346,6 +2369,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2346
2369
|
operations,
|
|
2347
2370
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
2348
2371
|
baseRevision: manifest.revision,
|
|
2372
|
+
runtimeStateRevision,
|
|
2349
2373
|
adapterCode: manifest.adapterCode,
|
|
2350
2374
|
graphLockDigest: options.graphLockDigest
|
|
2351
2375
|
};
|
|
@@ -3083,14 +3107,14 @@ async function writeMermaidAsset(response) {
|
|
|
3083
3107
|
"content-type": "application/javascript; charset=utf-8",
|
|
3084
3108
|
"cache-control": "public, max-age=3600"
|
|
3085
3109
|
});
|
|
3086
|
-
await new Promise((
|
|
3110
|
+
await new Promise((resolve25) => {
|
|
3087
3111
|
const stream = createReadStream(assetPath);
|
|
3088
3112
|
stream.on("error", () => {
|
|
3089
3113
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
3090
3114
|
else response.destroy();
|
|
3091
|
-
|
|
3115
|
+
resolve25();
|
|
3092
3116
|
});
|
|
3093
|
-
stream.on("end",
|
|
3117
|
+
stream.on("end", resolve25);
|
|
3094
3118
|
stream.pipe(response);
|
|
3095
3119
|
});
|
|
3096
3120
|
}
|
|
@@ -3117,14 +3141,14 @@ function escapeHtml2(value) {
|
|
|
3117
3141
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3118
3142
|
}
|
|
3119
3143
|
function listen(server, port, bind) {
|
|
3120
|
-
return new Promise((
|
|
3144
|
+
return new Promise((resolve25, reject) => {
|
|
3121
3145
|
const onError = (error) => {
|
|
3122
3146
|
server.off("listening", onListening);
|
|
3123
3147
|
reject(error);
|
|
3124
3148
|
};
|
|
3125
3149
|
const onListening = () => {
|
|
3126
3150
|
server.off("error", onError);
|
|
3127
|
-
|
|
3151
|
+
resolve25();
|
|
3128
3152
|
};
|
|
3129
3153
|
server.once("error", onError);
|
|
3130
3154
|
server.once("listening", onListening);
|
|
@@ -3132,7 +3156,7 @@ function listen(server, port, bind) {
|
|
|
3132
3156
|
});
|
|
3133
3157
|
}
|
|
3134
3158
|
function waitForShutdown(server, beforeClose) {
|
|
3135
|
-
return new Promise((
|
|
3159
|
+
return new Promise((resolve25) => {
|
|
3136
3160
|
let closing = false;
|
|
3137
3161
|
const shutdown = () => {
|
|
3138
3162
|
if (closing) return;
|
|
@@ -3140,7 +3164,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3140
3164
|
process.off("SIGINT", shutdown);
|
|
3141
3165
|
process.off("SIGTERM", shutdown);
|
|
3142
3166
|
beforeClose();
|
|
3143
|
-
server.close(() =>
|
|
3167
|
+
server.close(() => resolve25());
|
|
3144
3168
|
};
|
|
3145
3169
|
process.once("SIGINT", shutdown);
|
|
3146
3170
|
process.once("SIGTERM", shutdown);
|
|
@@ -3150,7 +3174,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3150
3174
|
process.off("SIGINT", shutdown);
|
|
3151
3175
|
process.off("SIGTERM", shutdown);
|
|
3152
3176
|
beforeClose();
|
|
3153
|
-
|
|
3177
|
+
resolve25();
|
|
3154
3178
|
});
|
|
3155
3179
|
});
|
|
3156
3180
|
}
|
|
@@ -3703,7 +3727,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
3703
3727
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
3704
3728
|
}
|
|
3705
3729
|
async function delay(ms) {
|
|
3706
|
-
await new Promise((
|
|
3730
|
+
await new Promise((resolve25) => setTimeout(resolve25, ms));
|
|
3707
3731
|
}
|
|
3708
3732
|
function parseClawHubSource(source) {
|
|
3709
3733
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -3874,7 +3898,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
|
|
|
3874
3898
|
if (Date.now() - started > timeoutMs) {
|
|
3875
3899
|
throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
|
|
3876
3900
|
}
|
|
3877
|
-
await new Promise((
|
|
3901
|
+
await new Promise((resolve25) => setTimeout(resolve25, 50));
|
|
3878
3902
|
}
|
|
3879
3903
|
}
|
|
3880
3904
|
try {
|
|
@@ -6665,8 +6689,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
6665
6689
|
async function withCachePathLock(path, fn) {
|
|
6666
6690
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
6667
6691
|
let release = () => void 0;
|
|
6668
|
-
const current = previous.then(() => new Promise((
|
|
6669
|
-
release =
|
|
6692
|
+
const current = previous.then(() => new Promise((resolve25) => {
|
|
6693
|
+
release = resolve25;
|
|
6670
6694
|
}));
|
|
6671
6695
|
cacheLocks.set(path, current);
|
|
6672
6696
|
await previous;
|
|
@@ -6742,8 +6766,8 @@ function detectDirectCollisions(nodes) {
|
|
|
6742
6766
|
}
|
|
6743
6767
|
}
|
|
6744
6768
|
function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
|
|
6745
|
-
const
|
|
6746
|
-
return `${name}@${version}+${
|
|
6769
|
+
const digest2 = createHash6("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
|
|
6770
|
+
return `${name}@${version}+${digest2}`;
|
|
6747
6771
|
}
|
|
6748
6772
|
function sortedUnique2(values) {
|
|
6749
6773
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
@@ -6914,7 +6938,7 @@ import { rm as rm7 } from "fs/promises";
|
|
|
6914
6938
|
// src/lifecycle/source-plan.ts
|
|
6915
6939
|
import { createHash as createHash8 } from "crypto";
|
|
6916
6940
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
6917
|
-
import { dirname as dirname17, join as join33, resolve as
|
|
6941
|
+
import { dirname as dirname17, join as join33, resolve as resolve15 } from "path";
|
|
6918
6942
|
|
|
6919
6943
|
// src/resolve/graph-diff.ts
|
|
6920
6944
|
function diffGraphLocks(previous, next) {
|
|
@@ -7735,6 +7759,185 @@ async function createExactMcpRetirementPlan(desiredArtifacts, adapter, targetRoo
|
|
|
7735
7759
|
};
|
|
7736
7760
|
}
|
|
7737
7761
|
|
|
7762
|
+
// src/model/fleet.ts
|
|
7763
|
+
import { lstat as lstat2, readFile as readFile20, realpath } from "fs/promises";
|
|
7764
|
+
import { homedir as homedir7 } from "os";
|
|
7765
|
+
import { isAbsolute as isAbsolute2, relative as relative7, resolve as resolve14 } from "path";
|
|
7766
|
+
async function resolveWorkspaceScope(request = {}) {
|
|
7767
|
+
const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
|
|
7768
|
+
if (selected > 1) {
|
|
7769
|
+
throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
|
|
7770
|
+
}
|
|
7771
|
+
const globalRoot = resolve14(request.globalRoot ?? homedir7());
|
|
7772
|
+
if (request.user) {
|
|
7773
|
+
const config2 = await readWorkspaceConfig(globalRoot);
|
|
7774
|
+
assertNonFleetScope("user", config2);
|
|
7775
|
+
return { kind: "user", root: globalRoot, config: config2 };
|
|
7776
|
+
}
|
|
7777
|
+
if (request.fleet !== void 0) {
|
|
7778
|
+
const id = fleetIdSchema.parse(request.fleet);
|
|
7779
|
+
const registration = await showRegisteredFleet(id, { globalRoot });
|
|
7780
|
+
const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
7781
|
+
const config2 = await readRequiredConfig(root, `fleet '${id}'`);
|
|
7782
|
+
assertFleetContract(id, registration, config2);
|
|
7783
|
+
return { kind: "fleet", root, fleetId: id, config: config2 };
|
|
7784
|
+
}
|
|
7785
|
+
const cwd = resolve14(request.cwd ?? process.cwd());
|
|
7786
|
+
const discoveredRoot = await findExistingWorkspaceRoot(cwd);
|
|
7787
|
+
if (!discoveredRoot || discoveredRoot === globalRoot) {
|
|
7788
|
+
if (request.local === true && cwd !== globalRoot) {
|
|
7789
|
+
return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
|
|
7790
|
+
}
|
|
7791
|
+
throw missingScopeError(request.local === true ? "local" : "implicit local");
|
|
7792
|
+
}
|
|
7793
|
+
const config = await readRequiredConfig(discoveredRoot, "local");
|
|
7794
|
+
assertNonFleetScope("local", config);
|
|
7795
|
+
return { kind: "local", root: discoveredRoot, config };
|
|
7796
|
+
}
|
|
7797
|
+
async function registerFleet(request) {
|
|
7798
|
+
const id = fleetIdSchema.parse(request.id);
|
|
7799
|
+
const requiredPackages = sortedUnique4(request.requiredPackages);
|
|
7800
|
+
if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
|
|
7801
|
+
const globalRoot = resolve14(request.globalRoot ?? homedir7());
|
|
7802
|
+
const home = await readWorkspaceConfig(globalRoot);
|
|
7803
|
+
if (supportsFleetConfig(home) && home.fleetId) {
|
|
7804
|
+
throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
|
|
7805
|
+
}
|
|
7806
|
+
const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
|
|
7807
|
+
if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
|
|
7808
|
+
const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
|
|
7809
|
+
const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
|
|
7810
|
+
if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
|
|
7811
|
+
if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
|
|
7812
|
+
const target = await readRequiredConfig(root, `fleet '${id}'`);
|
|
7813
|
+
const registration = registeredFleetSchema.parse({ root, requiredPackages });
|
|
7814
|
+
assertFleetContract(id, registration, target);
|
|
7815
|
+
const upgraded = workspaceConfigSchema.parse({
|
|
7816
|
+
...home,
|
|
7817
|
+
schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
7818
|
+
exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
|
|
7819
|
+
fleets: { ...existing, [id]: registration }
|
|
7820
|
+
});
|
|
7821
|
+
const configPath = globalWorkspaceConfigPath(globalRoot);
|
|
7822
|
+
declareMutationPath(configPath);
|
|
7823
|
+
await writeJsonAtomic(configPath, upgraded);
|
|
7824
|
+
return { id, ...registration };
|
|
7825
|
+
}
|
|
7826
|
+
async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
|
|
7827
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
7828
|
+
const normalized = {};
|
|
7829
|
+
for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
|
|
7830
|
+
const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
7831
|
+
if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
|
|
7832
|
+
const incumbent = canonical.get(root);
|
|
7833
|
+
if (incumbent) {
|
|
7834
|
+
throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
|
|
7835
|
+
}
|
|
7836
|
+
canonical.set(root, id);
|
|
7837
|
+
normalized[id] = registeredFleetSchema.parse({ ...registration, root });
|
|
7838
|
+
}
|
|
7839
|
+
return normalized;
|
|
7840
|
+
}
|
|
7841
|
+
async function listRegisteredFleets(options = {}) {
|
|
7842
|
+
const globalRoot = resolve14(options.globalRoot ?? homedir7());
|
|
7843
|
+
const home = await readWorkspaceConfig(globalRoot);
|
|
7844
|
+
if (!supportsFleetConfig(home)) return [];
|
|
7845
|
+
return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
|
|
7846
|
+
}
|
|
7847
|
+
async function showRegisteredFleet(idInput, options = {}) {
|
|
7848
|
+
const id = fleetIdSchema.parse(idInput);
|
|
7849
|
+
const fleets = await listRegisteredFleets(options);
|
|
7850
|
+
const registration = fleets.find((candidate) => candidate.id === id);
|
|
7851
|
+
if (!registration) {
|
|
7852
|
+
throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
|
|
7853
|
+
}
|
|
7854
|
+
return registration;
|
|
7855
|
+
}
|
|
7856
|
+
async function resolveWorkspaceOwnershipScope(workspaceRootInput, options = {}) {
|
|
7857
|
+
const workspaceRoot = await canonicalizeDirectory(resolve14(workspaceRootInput), "Workspace ownership root");
|
|
7858
|
+
if (options.fleetId) {
|
|
7859
|
+
return { root: workspaceRoot, fleetId: fleetIdSchema.parse(options.fleetId) };
|
|
7860
|
+
}
|
|
7861
|
+
const syntacticallyContaining = (await listRegisteredFleets({ globalRoot: options.globalRoot })).filter((fleet2) => containsPath(resolve14(fleet2.root), workspaceRoot));
|
|
7862
|
+
const registered = await Promise.all(syntacticallyContaining.map(async (fleet2) => ({
|
|
7863
|
+
...fleet2,
|
|
7864
|
+
root: await canonicalizeDirectory(fleet2.root, `Registered fleet '${fleet2.id}' root`)
|
|
7865
|
+
})));
|
|
7866
|
+
const containing = registered.filter((fleet2) => containsPath(fleet2.root, workspaceRoot));
|
|
7867
|
+
if (containing.length > 1) {
|
|
7868
|
+
throw new Error(
|
|
7869
|
+
`Workspace ${workspaceRoot} is contained by multiple registered fleets: ` + containing.map((fleet2) => `'${fleet2.id}' at ${fleet2.root}`).join(", ") + ". Resolve the overlapping registrations before planning ownership."
|
|
7870
|
+
);
|
|
7871
|
+
}
|
|
7872
|
+
const fleet = containing[0];
|
|
7873
|
+
if (fleet) assertFleetContract(fleet.id, fleet, await readRequiredConfig(fleet.root, `fleet '${fleet.id}'`));
|
|
7874
|
+
return fleet ? { root: fleet.root, fleetId: fleet.id } : { root: workspaceRoot };
|
|
7875
|
+
}
|
|
7876
|
+
async function readRequiredConfig(root, label) {
|
|
7877
|
+
const path = workspaceConfigPath(root);
|
|
7878
|
+
if (!await pathExists(path)) throw missingScopeError(label);
|
|
7879
|
+
try {
|
|
7880
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
|
|
7881
|
+
} catch (error) {
|
|
7882
|
+
throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7883
|
+
}
|
|
7884
|
+
}
|
|
7885
|
+
function assertFleetContract(id, registration, config) {
|
|
7886
|
+
if (!supportsFleetConfig(config)) {
|
|
7887
|
+
throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
|
|
7888
|
+
}
|
|
7889
|
+
if (config.fleetId !== id) {
|
|
7890
|
+
throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
|
|
7891
|
+
}
|
|
7892
|
+
if (Object.keys(config.fleets).length > 0) {
|
|
7893
|
+
throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
|
|
7894
|
+
}
|
|
7895
|
+
const names = new Set(config.packages.map((pkg) => pkg.name));
|
|
7896
|
+
for (const packageName of registration.requiredPackages) {
|
|
7897
|
+
if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
|
|
7898
|
+
}
|
|
7899
|
+
}
|
|
7900
|
+
async function assertCanonicalDirectory(input, label) {
|
|
7901
|
+
const canonical = await canonicalizeDirectory(input, label);
|
|
7902
|
+
const normalized = resolve14(input);
|
|
7903
|
+
const stats = await lstat2(input);
|
|
7904
|
+
if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
|
|
7905
|
+
throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
|
|
7906
|
+
}
|
|
7907
|
+
return canonical;
|
|
7908
|
+
}
|
|
7909
|
+
async function canonicalizeDirectory(input, label) {
|
|
7910
|
+
if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
|
|
7911
|
+
let canonical;
|
|
7912
|
+
try {
|
|
7913
|
+
canonical = await realpath(input);
|
|
7914
|
+
} catch {
|
|
7915
|
+
throw new Error(`${label} does not exist: ${input}`);
|
|
7916
|
+
}
|
|
7917
|
+
const stats = await lstat2(canonical);
|
|
7918
|
+
if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
|
|
7919
|
+
return canonical;
|
|
7920
|
+
}
|
|
7921
|
+
function missingScopeError(label) {
|
|
7922
|
+
return new Error(
|
|
7923
|
+
`Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
|
|
7924
|
+
);
|
|
7925
|
+
}
|
|
7926
|
+
function assertNonFleetScope(kind, config) {
|
|
7927
|
+
if (supportsFleetConfig(config) && config.fleetId) {
|
|
7928
|
+
throw new Error(
|
|
7929
|
+
`The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
|
|
7930
|
+
);
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
function sortedUnique4(values) {
|
|
7934
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
7935
|
+
}
|
|
7936
|
+
function containsPath(parent, candidate) {
|
|
7937
|
+
const path = relative7(parent, candidate);
|
|
7938
|
+
return path === "" || !path.startsWith("..") && !isAbsolute2(path);
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7738
7941
|
// src/lifecycle/source-plan.ts
|
|
7739
7942
|
async function createGraphSourcePlan(options) {
|
|
7740
7943
|
if (options.roots.length === 0) {
|
|
@@ -7813,7 +8016,11 @@ async function createGraphSourcePlan(options) {
|
|
|
7813
8016
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
7814
8017
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
7815
8018
|
const manifest = await readInstallManifest(resolvedInstallRoot, options.adapter.name, transport, { installationType: resolvedInstallationType, stateKey });
|
|
7816
|
-
const
|
|
8019
|
+
const ownership = await resolveWorkspaceOwnershipScope(workspaceRoot, {
|
|
8020
|
+
fleetId: options.fleetId,
|
|
8021
|
+
globalRoot: options.globalRoot
|
|
8022
|
+
});
|
|
8023
|
+
const workspaceOwner = workspaceOwnerForRoot(ownership.root, ownership.fleetId);
|
|
7817
8024
|
const plan = options.retireExactMcp ? await createExactMcpRetirementPlan(
|
|
7818
8025
|
desiredArtifacts,
|
|
7819
8026
|
options.adapter,
|
|
@@ -7847,7 +8054,8 @@ async function createGraphSourcePlan(options) {
|
|
|
7847
8054
|
workspaceOwner,
|
|
7848
8055
|
globalRoot: options.globalRoot,
|
|
7849
8056
|
stateKey,
|
|
7850
|
-
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath)
|
|
8057
|
+
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8058
|
+
plannedOperations: plan.operations
|
|
7851
8059
|
});
|
|
7852
8060
|
}
|
|
7853
8061
|
return {
|
|
@@ -7874,7 +8082,8 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
|
7874
8082
|
workspaceOwner: options.workspaceOwner,
|
|
7875
8083
|
globalRoot: options.globalRoot,
|
|
7876
8084
|
stateKey: plan.stateKey,
|
|
7877
|
-
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath)
|
|
8085
|
+
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
|
|
8086
|
+
plannedOperations: plan.operations
|
|
7878
8087
|
});
|
|
7879
8088
|
}
|
|
7880
8089
|
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
|
|
@@ -7924,8 +8133,15 @@ var workspaceOwnerPrefix2 = "workspace-root:";
|
|
|
7924
8133
|
async function assertNoForeignWorkspaceState(check) {
|
|
7925
8134
|
const planned = new Set(check.plannedPaths);
|
|
7926
8135
|
if (planned.size === 0) return;
|
|
8136
|
+
const plannedOperations = /* @__PURE__ */ new Map();
|
|
8137
|
+
for (const operation of check.plannedOperations) {
|
|
8138
|
+
if (!planned.has(operation.relativeDestPath)) continue;
|
|
8139
|
+
const operations = plannedOperations.get(operation.relativeDestPath) ?? [];
|
|
8140
|
+
operations.push(operation);
|
|
8141
|
+
plannedOperations.set(operation.relativeDestPath, operations);
|
|
8142
|
+
}
|
|
7927
8143
|
const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
|
|
7928
|
-
const root =
|
|
8144
|
+
const root = resolve15(check.workspaceRoot);
|
|
7929
8145
|
const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
|
|
7930
8146
|
const foreign = [];
|
|
7931
8147
|
for (const entry of manifests) {
|
|
@@ -7937,7 +8153,15 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
7937
8153
|
if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
|
|
7938
8154
|
const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
|
|
7939
8155
|
bucket.entryCount += 1;
|
|
7940
|
-
if (planned.has(manifestEntry.path)
|
|
8156
|
+
if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
|
|
8157
|
+
check,
|
|
8158
|
+
manifestEntry.path,
|
|
8159
|
+
manifestEntry.mergeStrategy,
|
|
8160
|
+
manifestEntry.mergeRemoval,
|
|
8161
|
+
plannedOperations.get(manifestEntry.path) ?? []
|
|
8162
|
+
)) {
|
|
8163
|
+
bucket.collidingPaths.push(manifestEntry.path);
|
|
8164
|
+
}
|
|
7941
8165
|
byOwner.set(owner, bucket);
|
|
7942
8166
|
}
|
|
7943
8167
|
for (const [owner, bucket] of byOwner) {
|
|
@@ -7959,6 +8183,34 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
7959
8183
|
parseWorkspaceOwner(check.workspaceOwner)?.fleetId ? "Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet." : "Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."
|
|
7960
8184
|
].join("\n"));
|
|
7961
8185
|
}
|
|
8186
|
+
async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
|
|
8187
|
+
if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
|
|
8188
|
+
const operation = operations[0];
|
|
8189
|
+
if (operation.mergeStrategy !== foreignStrategy || !hasMergeRemovalContent(operation.mergeRemoval)) return false;
|
|
8190
|
+
if (!disjointMcpMergeContributions(foreignStrategy, foreignRemoval, operation.mergeRemoval)) return false;
|
|
8191
|
+
try {
|
|
8192
|
+
const content = await check.transport.readFile(join33(check.installRoot, relativePath));
|
|
8193
|
+
assertExactMergeContribution(foreignRemoval, foreignStrategy, content);
|
|
8194
|
+
assertExactMergeContribution(operation.mergeRemoval, foreignStrategy, content);
|
|
8195
|
+
return true;
|
|
8196
|
+
} catch {
|
|
8197
|
+
return false;
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
function disjointMcpMergeContributions(strategy, left, right) {
|
|
8201
|
+
const leftNames = mcpServerNames(strategy, left);
|
|
8202
|
+
const rightNames = mcpServerNames(strategy, right);
|
|
8203
|
+
if (!leftNames || !rightNames) return false;
|
|
8204
|
+
return [...leftNames].every((name) => !rightNames.has(name));
|
|
8205
|
+
}
|
|
8206
|
+
function mcpServerNames(strategy, removal) {
|
|
8207
|
+
if (strategy !== "codex-toml-mcp" && strategy !== "json-deep") return void 0;
|
|
8208
|
+
const keys = Object.keys(removal);
|
|
8209
|
+
if (strategy === "json-deep" && (keys.length !== 1 || keys[0] !== "mcpServers")) return void 0;
|
|
8210
|
+
const servers = removal.mcpServers ?? (strategy === "codex-toml-mcp" ? removal : void 0);
|
|
8211
|
+
if (!servers || Array.isArray(servers) || typeof servers !== "object") return void 0;
|
|
8212
|
+
return new Set(Object.keys(servers));
|
|
8213
|
+
}
|
|
7962
8214
|
function globalConfigRoot(globalRoot) {
|
|
7963
8215
|
return dirname17(dirname17(globalWorkspaceConfigPath(globalRoot)));
|
|
7964
8216
|
}
|
|
@@ -8031,17 +8283,17 @@ function selectionImportKey2(selection) {
|
|
|
8031
8283
|
exportHash: selection.exportHash,
|
|
8032
8284
|
exportName: selection.exportName,
|
|
8033
8285
|
extends: selection.extends,
|
|
8034
|
-
inherited:
|
|
8035
|
-
additions:
|
|
8036
|
-
exclusions:
|
|
8037
|
-
effective:
|
|
8286
|
+
inherited: sortedUnique5(selection.inherited),
|
|
8287
|
+
additions: sortedUnique5(selection.additions),
|
|
8288
|
+
exclusions: sortedUnique5(selection.exclusions),
|
|
8289
|
+
effective: sortedUnique5(selection.effective)
|
|
8038
8290
|
});
|
|
8039
8291
|
}
|
|
8040
|
-
function
|
|
8292
|
+
function sortedUnique5(values) {
|
|
8041
8293
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
8042
8294
|
}
|
|
8043
8295
|
function selectorKey(selectors) {
|
|
8044
|
-
return
|
|
8296
|
+
return sortedUnique5(selectors).join("\0");
|
|
8045
8297
|
}
|
|
8046
8298
|
async function assertTrusted(sources, options) {
|
|
8047
8299
|
if (sources.length === 0) return;
|
|
@@ -8102,7 +8354,7 @@ function targetLabel(target) {
|
|
|
8102
8354
|
}
|
|
8103
8355
|
|
|
8104
8356
|
// src/runtime/target.ts
|
|
8105
|
-
import { basename as basename18, dirname as dirname18, join as join34, resolve as
|
|
8357
|
+
import { basename as basename18, dirname as dirname18, join as join34, resolve as resolve16 } from "path";
|
|
8106
8358
|
var runtimeMarkers = [
|
|
8107
8359
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
8108
8360
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -8111,9 +8363,9 @@ var runtimeMarkers = [
|
|
|
8111
8363
|
{ adapter: "copilot", dirs: [".github"] }
|
|
8112
8364
|
];
|
|
8113
8365
|
async function resolveRuntimeTarget(request = {}) {
|
|
8114
|
-
const cwd =
|
|
8366
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8115
8367
|
if (request.targetRoot) {
|
|
8116
|
-
const targetRoot =
|
|
8368
|
+
const targetRoot = resolve16(request.targetRoot);
|
|
8117
8369
|
return {
|
|
8118
8370
|
adapter: request.adapter ?? "openclaw",
|
|
8119
8371
|
installationType: request.installationType,
|
|
@@ -8145,7 +8397,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
8145
8397
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
8146
8398
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
8147
8399
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
8148
|
-
const cwd =
|
|
8400
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8149
8401
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
8150
8402
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8151
8403
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
|
|
@@ -8155,8 +8407,8 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
8155
8407
|
return targets;
|
|
8156
8408
|
}
|
|
8157
8409
|
async function resolveProfileRuntimeTargets(request) {
|
|
8158
|
-
const cwd =
|
|
8159
|
-
const workspaceRoot = request.targetRoot ?
|
|
8410
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8411
|
+
const workspaceRoot = request.targetRoot ? resolve16(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
8160
8412
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8161
8413
|
const profile = config.profiles[request.profile];
|
|
8162
8414
|
if (!profile) {
|
|
@@ -8222,7 +8474,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
8222
8474
|
return unique[0];
|
|
8223
8475
|
}
|
|
8224
8476
|
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
8225
|
-
const root =
|
|
8477
|
+
const root = resolve16(cwd);
|
|
8226
8478
|
const matches = [];
|
|
8227
8479
|
for (const marker of runtimeMarkers) {
|
|
8228
8480
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
@@ -8271,7 +8523,7 @@ function dedupeTargets(matches) {
|
|
|
8271
8523
|
return [...byKey.values()];
|
|
8272
8524
|
}
|
|
8273
8525
|
function runtimeScanRoot(request) {
|
|
8274
|
-
const root =
|
|
8526
|
+
const root = resolve16(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
8275
8527
|
if (request.targetRoot) return root;
|
|
8276
8528
|
return runtimeMarkers.some((marker) => marker.dirs.includes(basename18(root))) ? dirname18(root) : root;
|
|
8277
8529
|
}
|
|
@@ -8581,8 +8833,8 @@ function shellQuoteArg(value) {
|
|
|
8581
8833
|
}
|
|
8582
8834
|
|
|
8583
8835
|
// src/cli/update-check.ts
|
|
8584
|
-
import { mkdir as mkdir15, readFile as
|
|
8585
|
-
import { homedir as
|
|
8836
|
+
import { mkdir as mkdir15, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
8837
|
+
import { homedir as homedir8 } from "os";
|
|
8586
8838
|
import { dirname as dirname19, join as join35 } from "path";
|
|
8587
8839
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8588
8840
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
@@ -8591,7 +8843,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
8591
8843
|
if (isDisabled(options)) return;
|
|
8592
8844
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
8593
8845
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
8594
|
-
const cachePath = options.cachePath ?? join35(
|
|
8846
|
+
const cachePath = options.cachePath ?? join35(homedir8(), ".agentwheel", "update-check.json");
|
|
8595
8847
|
try {
|
|
8596
8848
|
const cached = await readCache(cachePath);
|
|
8597
8849
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -8628,7 +8880,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
8628
8880
|
}
|
|
8629
8881
|
async function readCache(path) {
|
|
8630
8882
|
try {
|
|
8631
|
-
const parsed = JSON.parse(await
|
|
8883
|
+
const parsed = JSON.parse(await readFile21(path, "utf8"));
|
|
8632
8884
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
8633
8885
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
8634
8886
|
} catch {
|
|
@@ -8660,9 +8912,9 @@ function normalizeVersion(version) {
|
|
|
8660
8912
|
|
|
8661
8913
|
// src/model/package-validate.ts
|
|
8662
8914
|
import { stat as stat12 } from "fs/promises";
|
|
8663
|
-
import { resolve as
|
|
8915
|
+
import { resolve as resolve17 } from "path";
|
|
8664
8916
|
async function validatePackage(root) {
|
|
8665
|
-
const packageRoot =
|
|
8917
|
+
const packageRoot = resolve17(root);
|
|
8666
8918
|
const findings = [];
|
|
8667
8919
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
8668
8920
|
if (!manifestPath) {
|
|
@@ -8763,7 +9015,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
8763
9015
|
try {
|
|
8764
9016
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
8765
9017
|
if (isCrossPackageSelector(selector)) return;
|
|
8766
|
-
const full =
|
|
9018
|
+
const full = resolve17(packageRoot, selector);
|
|
8767
9019
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
8768
9020
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
8769
9021
|
return;
|
|
@@ -8816,11 +9068,11 @@ function isCrossPackageSelector(value) {
|
|
|
8816
9068
|
}
|
|
8817
9069
|
|
|
8818
9070
|
// src/model/package-migrate.ts
|
|
8819
|
-
import { readFile as
|
|
8820
|
-
import { join as join37, resolve as
|
|
9071
|
+
import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
|
|
9072
|
+
import { join as join37, resolve as resolve18 } from "path";
|
|
8821
9073
|
import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
|
|
8822
9074
|
async function migratePackageManifest(root) {
|
|
8823
|
-
const packageRoot =
|
|
9075
|
+
const packageRoot = resolve18(root);
|
|
8824
9076
|
for (const name of openPackManifestNames) {
|
|
8825
9077
|
const path = join37(packageRoot, name);
|
|
8826
9078
|
if (await pathExists(path)) {
|
|
@@ -8834,7 +9086,7 @@ async function migratePackageManifest(root) {
|
|
|
8834
9086
|
const from = join37(packageRoot, legacyName);
|
|
8835
9087
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
8836
9088
|
const to = join37(packageRoot, toName);
|
|
8837
|
-
const content = await
|
|
9089
|
+
const content = await readFile22(from, "utf8");
|
|
8838
9090
|
const updated = updateSchemaVersion(content);
|
|
8839
9091
|
declareMutationPath(from);
|
|
8840
9092
|
declareMutationPath(to);
|
|
@@ -8882,7 +9134,7 @@ function resolveCliVersion() {
|
|
|
8882
9134
|
}
|
|
8883
9135
|
|
|
8884
9136
|
// src/lifecycle/ownership.ts
|
|
8885
|
-
import { resolve as
|
|
9137
|
+
import { resolve as resolve19 } from "path";
|
|
8886
9138
|
async function planArtifactOwnershipHandoff(request) {
|
|
8887
9139
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
8888
9140
|
}
|
|
@@ -8895,7 +9147,9 @@ async function applyArtifactOwnershipHandoff(request) {
|
|
|
8895
9147
|
const scope = { installationType: request.installationType, stateKey: request.stateKey };
|
|
8896
9148
|
const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, scope);
|
|
8897
9149
|
try {
|
|
8898
|
-
if (await
|
|
9150
|
+
if ((await listApplyJournals(request.targetRoot, request.adapter, transport, {
|
|
9151
|
+
installationType: request.installationType
|
|
9152
|
+
})).length > 0) {
|
|
8899
9153
|
throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
|
|
8900
9154
|
}
|
|
8901
9155
|
const plan = await validateOwnershipHandoff(request, transport);
|
|
@@ -8997,12 +9251,17 @@ async function verifiedEntryHash(entry, destPath, transport) {
|
|
|
8997
9251
|
}
|
|
8998
9252
|
return currentHash;
|
|
8999
9253
|
}
|
|
9254
|
+
async function verifyManifestEntryRuntime(targetRoot, entry, transport = localTransport) {
|
|
9255
|
+
const path = containedArtifactPath(targetRoot, entry.path);
|
|
9256
|
+
if (!await transport.pathExists(path)) throw new Error(`Managed artifact is missing: ${entry.path}`);
|
|
9257
|
+
return verifiedEntryHash(entry, path, transport);
|
|
9258
|
+
}
|
|
9000
9259
|
function containedArtifactPath(targetRoot, relativePath) {
|
|
9001
9260
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
9002
9261
|
throw new Error(`Unsafe managed artifact path: ${relativePath}`);
|
|
9003
9262
|
}
|
|
9004
|
-
const root =
|
|
9005
|
-
const candidate =
|
|
9263
|
+
const root = resolve19(targetRoot);
|
|
9264
|
+
const candidate = resolve19(root, relativePath);
|
|
9006
9265
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
9007
9266
|
throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
|
|
9008
9267
|
}
|
|
@@ -9033,10 +9292,246 @@ function assertManifestIdentity(manifest, request) {
|
|
|
9033
9292
|
}
|
|
9034
9293
|
}
|
|
9035
9294
|
|
|
9295
|
+
// src/lifecycle/ownership-retire-stale.ts
|
|
9296
|
+
import { createHash as createHash9 } from "crypto";
|
|
9297
|
+
import { resolve as resolve20 } from "path";
|
|
9298
|
+
async function planRetireStaleOwnership(request) {
|
|
9299
|
+
return observe(request, request.transport ?? localTransport);
|
|
9300
|
+
}
|
|
9301
|
+
async function applyRetireStaleOwnership(request) {
|
|
9302
|
+
assertApplyPreconditions(request);
|
|
9303
|
+
const transport = request.transport ?? localTransport;
|
|
9304
|
+
const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, {
|
|
9305
|
+
installationType: request.installationType,
|
|
9306
|
+
stateKey: request.destinationStateKey
|
|
9307
|
+
});
|
|
9308
|
+
try {
|
|
9309
|
+
const pending = await listAllApplyJournals(request.targetRoot, request.adapter, transport);
|
|
9310
|
+
if (pending.length > 0) {
|
|
9311
|
+
throw new Error(`Cannot retire stale ownership while runtime apply journal(s) are pending: ${pending.map((item) => item.path).join(", ")}`);
|
|
9312
|
+
}
|
|
9313
|
+
const current = await observe(request, transport);
|
|
9314
|
+
assertExpected(request.planDigest, current.planDigest, "plan digest");
|
|
9315
|
+
assertExpected(request.expectedSourceRevision, current.source.revision, "source manifest revision");
|
|
9316
|
+
assertExpected(request.expectedDestinationRevision, current.destination.revision, "destination manifest revision");
|
|
9317
|
+
assertExpected(request.expectedInventoryRevision, current.manifestInventoryRevision, "manifest inventory revision");
|
|
9318
|
+
const source = await requireManifest(request, request.sourceStateKey, transport, "source");
|
|
9319
|
+
const selectedDigests = new Set(current.selected.map((entry) => entry.sourceEntryDigest));
|
|
9320
|
+
const retained = source.entries.filter((entry) => !selectedDigests.has(entryDigest(entry)));
|
|
9321
|
+
if (source.entries.length - retained.length !== current.selected.length) {
|
|
9322
|
+
throw new Error("Source manifest selection changed while locked; replan required.");
|
|
9323
|
+
}
|
|
9324
|
+
declareMutationPath(current.source.manifestPath);
|
|
9325
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9326
|
+
const mutation = mutationMetadataForApplyJournal();
|
|
9327
|
+
const journal = {
|
|
9328
|
+
version: mutation ? 2 : 1,
|
|
9329
|
+
...mutation ? { mutation } : {},
|
|
9330
|
+
mode: "uninstall",
|
|
9331
|
+
adapter: request.adapter,
|
|
9332
|
+
installationType: request.installationType,
|
|
9333
|
+
stateKey: request.sourceStateKey,
|
|
9334
|
+
targetRoot: request.targetRoot,
|
|
9335
|
+
baseRevision: source.revision,
|
|
9336
|
+
createdAt: now,
|
|
9337
|
+
updatedAt: now,
|
|
9338
|
+
operations: [],
|
|
9339
|
+
completed: [],
|
|
9340
|
+
manifest: { ...source, entries: retained }
|
|
9341
|
+
};
|
|
9342
|
+
await writeApplyJournal(journal, transport);
|
|
9343
|
+
await commitManifestMetadataJournal(journal, transport);
|
|
9344
|
+
return {
|
|
9345
|
+
...current,
|
|
9346
|
+
applied: true,
|
|
9347
|
+
sourceManifestRemoved: retained.length === 0,
|
|
9348
|
+
retainedSourceEntries: retained.length
|
|
9349
|
+
};
|
|
9350
|
+
} finally {
|
|
9351
|
+
await lock.release();
|
|
9352
|
+
}
|
|
9353
|
+
}
|
|
9354
|
+
async function observe(request, transport) {
|
|
9355
|
+
const normalized = normalizeRequest(request);
|
|
9356
|
+
const source = await requireManifest(normalized, normalized.sourceStateKey, transport, "source");
|
|
9357
|
+
const destination = await requireManifest(normalized, normalized.destinationStateKey, transport, "destination");
|
|
9358
|
+
const sourceOwner = workspaceOwnerForRoot(normalized.fromWorkspaceRoot);
|
|
9359
|
+
const destinationOwner = workspaceOwnerForRoot(normalized.toWorkspaceRoot, normalized.toFleetId);
|
|
9360
|
+
const destinationByPath = /* @__PURE__ */ new Map();
|
|
9361
|
+
for (const entry of destination.entries) {
|
|
9362
|
+
if (entry.workspaceOwner !== destinationOwner) continue;
|
|
9363
|
+
const entries = destinationByPath.get(entry.path) ?? [];
|
|
9364
|
+
entries.push(entry);
|
|
9365
|
+
destinationByPath.set(entry.path, entries);
|
|
9366
|
+
}
|
|
9367
|
+
const selected = [];
|
|
9368
|
+
for (const sourceEntry of source.entries) {
|
|
9369
|
+
if (sourceEntry.workspaceOwner !== sourceOwner) continue;
|
|
9370
|
+
const candidates = destinationByPath.get(sourceEntry.path) ?? [];
|
|
9371
|
+
if (candidates.length === 0) continue;
|
|
9372
|
+
if (candidates.length !== 1) {
|
|
9373
|
+
throw new Error(`Destination Fleet manifest has ambiguous ownership for ${sourceEntry.path}.`);
|
|
9374
|
+
}
|
|
9375
|
+
assertRetirableSourceEntry(sourceEntry);
|
|
9376
|
+
const destinationEntry = candidates[0];
|
|
9377
|
+
await assertContributionCoverage(sourceEntry, destinationEntry, normalized.targetRoot, transport);
|
|
9378
|
+
const runtimeHash = await verifyManifestEntryRuntime(normalized.targetRoot, destinationEntry, transport);
|
|
9379
|
+
selected.push({
|
|
9380
|
+
path: sourceEntry.path,
|
|
9381
|
+
sourceEntryDigest: entryDigest(sourceEntry),
|
|
9382
|
+
destinationEntryDigest: entryDigest(destinationEntry),
|
|
9383
|
+
runtimeHash
|
|
9384
|
+
});
|
|
9385
|
+
}
|
|
9386
|
+
selected.sort((a, b) => a.path.localeCompare(b.path) || a.sourceEntryDigest.localeCompare(b.sourceEntryDigest) || a.destinationEntryDigest.localeCompare(b.destinationEntryDigest));
|
|
9387
|
+
if (selected.length === 0) {
|
|
9388
|
+
throw new Error("No stale source ownership entries match an exact path in the destination Fleet manifest.");
|
|
9389
|
+
}
|
|
9390
|
+
const withoutDigest = {
|
|
9391
|
+
adapter: normalized.adapter,
|
|
9392
|
+
installationType: normalized.installationType,
|
|
9393
|
+
targetRoot: normalized.targetRoot,
|
|
9394
|
+
source: {
|
|
9395
|
+
stateKey: normalized.sourceStateKey,
|
|
9396
|
+
manifestPath: installManifestPath(normalized.targetRoot, normalized.adapter, {
|
|
9397
|
+
installationType: normalized.installationType,
|
|
9398
|
+
stateKey: normalized.sourceStateKey
|
|
9399
|
+
}),
|
|
9400
|
+
revision: source.revision,
|
|
9401
|
+
owner: sourceOwner
|
|
9402
|
+
},
|
|
9403
|
+
destination: {
|
|
9404
|
+
stateKey: normalized.destinationStateKey,
|
|
9405
|
+
manifestPath: installManifestPath(normalized.targetRoot, normalized.adapter, {
|
|
9406
|
+
installationType: normalized.installationType,
|
|
9407
|
+
stateKey: normalized.destinationStateKey
|
|
9408
|
+
}),
|
|
9409
|
+
revision: destination.revision,
|
|
9410
|
+
owner: destinationOwner,
|
|
9411
|
+
fleetId: normalized.toFleetId
|
|
9412
|
+
},
|
|
9413
|
+
manifestInventoryRevision: await computeInstallManifestInventoryRevision(
|
|
9414
|
+
normalized.targetRoot,
|
|
9415
|
+
normalized.adapter,
|
|
9416
|
+
transport
|
|
9417
|
+
),
|
|
9418
|
+
selected
|
|
9419
|
+
};
|
|
9420
|
+
return { ...withoutDigest, planDigest: digest(withoutDigest) };
|
|
9421
|
+
}
|
|
9422
|
+
function normalizeRequest(request) {
|
|
9423
|
+
const targetRoot = resolve20(request.targetRoot);
|
|
9424
|
+
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, request.installationType, "source");
|
|
9425
|
+
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
|
|
9426
|
+
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
9427
|
+
if (!request.toFleetId.trim()) throw new Error("Destination Fleet id is required.");
|
|
9428
|
+
return {
|
|
9429
|
+
...request,
|
|
9430
|
+
targetRoot,
|
|
9431
|
+
installationType: request.installationType.trim(),
|
|
9432
|
+
sourceStateKey,
|
|
9433
|
+
destinationStateKey,
|
|
9434
|
+
fromWorkspaceRoot: resolve20(request.fromWorkspaceRoot),
|
|
9435
|
+
toWorkspaceRoot: resolve20(request.toWorkspaceRoot),
|
|
9436
|
+
toFleetId: request.toFleetId.trim()
|
|
9437
|
+
};
|
|
9438
|
+
}
|
|
9439
|
+
function exactStateKey(adapter, value, installationType, label) {
|
|
9440
|
+
if (!value || stateKeyFor(adapter, { installationType, stateKey: value }) !== value) {
|
|
9441
|
+
throw new Error(`The ${label} state key must be an exact canonical state key.`);
|
|
9442
|
+
}
|
|
9443
|
+
return value;
|
|
9444
|
+
}
|
|
9445
|
+
async function requireManifest(request, stateKey, transport, label) {
|
|
9446
|
+
const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, {
|
|
9447
|
+
installationType: request.installationType,
|
|
9448
|
+
stateKey
|
|
9449
|
+
});
|
|
9450
|
+
if (!manifest) throw new Error(`No ${label} install manifest for state key ${stateKey}.`);
|
|
9451
|
+
if (manifest.version !== 2) throw new Error(`${label} ownership retirement requires an Agentwheel v2 install manifest.`);
|
|
9452
|
+
const actualStateKey = stateKeyFor(manifest.adapter, {
|
|
9453
|
+
installationType: manifest.installationType,
|
|
9454
|
+
stateKey: manifest.stateKey
|
|
9455
|
+
});
|
|
9456
|
+
if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType || resolve20(manifest.targetRoot) !== request.targetRoot || actualStateKey !== stateKey) {
|
|
9457
|
+
throw new Error(`${label} install manifest identity does not match the requested adapter, target, installation type, and state key.`);
|
|
9458
|
+
}
|
|
9459
|
+
return manifest;
|
|
9460
|
+
}
|
|
9461
|
+
function assertRetirableSourceEntry(entry) {
|
|
9462
|
+
if (entry.semanticPlugin || entry.semanticCommand || entry.executed) {
|
|
9463
|
+
throw new Error(`Cannot retire semantic source ownership at ${entry.path}.`);
|
|
9464
|
+
}
|
|
9465
|
+
if (entry.mergeStrategy && !entry.mergeRemoval) {
|
|
9466
|
+
throw new Error(`Cannot retire incomplete merge ownership at ${entry.path}.`);
|
|
9467
|
+
}
|
|
9468
|
+
}
|
|
9469
|
+
async function assertContributionCoverage(source, destination, targetRoot, transport) {
|
|
9470
|
+
const sourceManagedBlock = source.mode === "managed-block";
|
|
9471
|
+
const destinationManagedBlock = destination.mode === "managed-block";
|
|
9472
|
+
const sourceMerge = source.mergeStrategy !== void 0;
|
|
9473
|
+
const destinationMerge = destination.mergeStrategy !== void 0;
|
|
9474
|
+
const sourceCategory = sourceManagedBlock ? "managed-block" : sourceMerge ? "merge" : "plain";
|
|
9475
|
+
const destinationCategory = destinationManagedBlock ? "managed-block" : destinationMerge ? "merge" : "plain";
|
|
9476
|
+
if (sourceCategory !== destinationCategory) {
|
|
9477
|
+
throw new Error(
|
|
9478
|
+
`Destination Fleet ownership category ${destinationCategory} cannot replace stale source category ${sourceCategory} at ${source.path}.`
|
|
9479
|
+
);
|
|
9480
|
+
}
|
|
9481
|
+
if (sourceCategory === "plain") return;
|
|
9482
|
+
if (sourceManagedBlock) {
|
|
9483
|
+
const sourceSelector = managedInstructionSelector(source.logicalSelector, source.artifactType, source.artifactName);
|
|
9484
|
+
const destinationSelector = managedInstructionSelector(destination.logicalSelector, destination.artifactType, destination.artifactName);
|
|
9485
|
+
if (!sourceManagedBlock || !destinationManagedBlock || sourceSelector !== destinationSelector) {
|
|
9486
|
+
throw new Error(`Destination Fleet does not exactly cover stale source managed block contribution at ${source.path}.`);
|
|
9487
|
+
}
|
|
9488
|
+
return;
|
|
9489
|
+
}
|
|
9490
|
+
if (source.mergeStrategy && destination.mergeStrategy) {
|
|
9491
|
+
if (!source.mergeRemoval) throw new Error(`Cannot retire incomplete merge ownership at ${source.path}.`);
|
|
9492
|
+
const exactlyCovered = destination.mergeStrategy === source.mergeStrategy && destination.mergeRemoval && contributionSelector(source) === contributionSelector(destination) && canonicalJson(source.mergeRemoval) === canonicalJson(destination.mergeRemoval);
|
|
9493
|
+
if (exactlyCovered) return;
|
|
9494
|
+
const current = await transport.readFile(containedArtifactPath(targetRoot, source.path));
|
|
9495
|
+
if (!mergeContributionAbsent(source.mergeRemoval, source.mergeStrategy, current)) {
|
|
9496
|
+
throw new Error(`Destination Fleet does not exactly cover or replace stale source merge contribution at ${source.path}.`);
|
|
9497
|
+
}
|
|
9498
|
+
}
|
|
9499
|
+
}
|
|
9500
|
+
function contributionSelector(entry) {
|
|
9501
|
+
return entry.logicalSelector ?? `${entry.artifactType}/${entry.artifactName}`;
|
|
9502
|
+
}
|
|
9503
|
+
function assertApplyPreconditions(request) {
|
|
9504
|
+
for (const [label, value] of [
|
|
9505
|
+
["plan digest", request.planDigest],
|
|
9506
|
+
["source manifest revision", request.expectedSourceRevision],
|
|
9507
|
+
["destination manifest revision", request.expectedDestinationRevision],
|
|
9508
|
+
["manifest inventory revision", request.expectedInventoryRevision]
|
|
9509
|
+
]) {
|
|
9510
|
+
if (!value || !/^[a-f0-9]{64}$/.test(value)) {
|
|
9511
|
+
throw new Error(`Applying stale ownership retirement requires a reviewed lowercase SHA-256 ${label}.`);
|
|
9512
|
+
}
|
|
9513
|
+
}
|
|
9514
|
+
}
|
|
9515
|
+
function assertExpected(expected, current, label) {
|
|
9516
|
+
if (expected !== current) throw new Error(`Stale ${label}: expected ${expected}, found ${current}; replan required.`);
|
|
9517
|
+
}
|
|
9518
|
+
function entryDigest(entry) {
|
|
9519
|
+
return digest(entry);
|
|
9520
|
+
}
|
|
9521
|
+
function digest(value) {
|
|
9522
|
+
return createHash9("sha256").update(canonicalJson(value)).digest("hex");
|
|
9523
|
+
}
|
|
9524
|
+
function canonicalJson(value) {
|
|
9525
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
9526
|
+
if (!value || typeof value !== "object") return JSON.stringify(value);
|
|
9527
|
+
const record = value;
|
|
9528
|
+
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
9529
|
+
}
|
|
9530
|
+
|
|
9036
9531
|
// src/version/policy.ts
|
|
9037
9532
|
import { execFile as execFile3 } from "child_process";
|
|
9038
|
-
import { readFile as
|
|
9039
|
-
import { join as join39, resolve as
|
|
9533
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
9534
|
+
import { join as join39, resolve as resolve21 } from "path";
|
|
9040
9535
|
import { promisify as promisify3 } from "util";
|
|
9041
9536
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
9042
9537
|
import { z as z5 } from "zod";
|
|
@@ -9130,7 +9625,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9130
9625
|
if (tagged.length > 0) return tagged;
|
|
9131
9626
|
}
|
|
9132
9627
|
if (driverName === "local") {
|
|
9133
|
-
const root =
|
|
9628
|
+
const root = resolve21(workspaceRoot, pkg.source);
|
|
9134
9629
|
const manifest2 = await readPackageManifest(root);
|
|
9135
9630
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
9136
9631
|
try {
|
|
@@ -9244,7 +9739,7 @@ function versionCachePath(workspaceRoot) {
|
|
|
9244
9739
|
async function readVersionCache(path) {
|
|
9245
9740
|
if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
|
|
9246
9741
|
try {
|
|
9247
|
-
return versionCacheSchema.parse(JSON.parse(await
|
|
9742
|
+
return versionCacheSchema.parse(JSON.parse(await readFile23(path, "utf8")));
|
|
9248
9743
|
} catch {
|
|
9249
9744
|
return { schemaVersion: 1, sources: {} };
|
|
9250
9745
|
}
|
|
@@ -9252,8 +9747,8 @@ async function readVersionCache(path) {
|
|
|
9252
9747
|
|
|
9253
9748
|
// src/profile/members.ts
|
|
9254
9749
|
import { execFile as execFile4 } from "child_process";
|
|
9255
|
-
import { readFile as
|
|
9256
|
-
import { join as join40, resolve as
|
|
9750
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
9751
|
+
import { join as join40, resolve as resolve22 } from "path";
|
|
9257
9752
|
import { promisify as promisify4 } from "util";
|
|
9258
9753
|
import { z as z7 } from "zod";
|
|
9259
9754
|
|
|
@@ -9428,7 +9923,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
9428
9923
|
let stderr = "";
|
|
9429
9924
|
try {
|
|
9430
9925
|
if (member.transport === "local") {
|
|
9431
|
-
const workspace =
|
|
9926
|
+
const workspace = resolve22(parentWorkspace, member.workspace);
|
|
9432
9927
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
9433
9928
|
cwd: workspace,
|
|
9434
9929
|
env: env2,
|
|
@@ -9474,7 +9969,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
9474
9969
|
process.execPath,
|
|
9475
9970
|
[process.argv[1], "--no-update-check", ...args],
|
|
9476
9971
|
{
|
|
9477
|
-
cwd:
|
|
9972
|
+
cwd: resolve22(parentWorkspace, member.workspace),
|
|
9478
9973
|
env: env2,
|
|
9479
9974
|
maxBuffer: 20 * 1024 * 1024
|
|
9480
9975
|
}
|
|
@@ -9554,7 +10049,7 @@ function memberCachePath(workspaceRoot, profileName, memberId) {
|
|
|
9554
10049
|
async function readMemberCache(path) {
|
|
9555
10050
|
if (!await pathExists(path)) return void 0;
|
|
9556
10051
|
try {
|
|
9557
|
-
return memberCacheSchema.parse(JSON.parse(await
|
|
10052
|
+
return memberCacheSchema.parse(JSON.parse(await readFile24(path, "utf8")));
|
|
9558
10053
|
} catch {
|
|
9559
10054
|
return void 0;
|
|
9560
10055
|
}
|
|
@@ -9575,7 +10070,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
9575
10070
|
}
|
|
9576
10071
|
}
|
|
9577
10072
|
function compositeKey(workspaceRoot, profileName) {
|
|
9578
|
-
return `${
|
|
10073
|
+
return `${resolve22(workspaceRoot)}#${profileName}`;
|
|
9579
10074
|
}
|
|
9580
10075
|
|
|
9581
10076
|
// src/status/repository.ts
|
|
@@ -9622,9 +10117,9 @@ function valueAfter(lines, prefix) {
|
|
|
9622
10117
|
}
|
|
9623
10118
|
|
|
9624
10119
|
// src/catalogue/client.ts
|
|
9625
|
-
import { createHash as
|
|
9626
|
-
import { readFile as
|
|
9627
|
-
import { homedir as
|
|
10120
|
+
import { createHash as createHash10 } from "crypto";
|
|
10121
|
+
import { readFile as readFile25, rm as rm8 } from "fs/promises";
|
|
10122
|
+
import { homedir as homedir9 } from "os";
|
|
9628
10123
|
import { join as join41 } from "path";
|
|
9629
10124
|
|
|
9630
10125
|
// src/model/catalogue.ts
|
|
@@ -9841,7 +10336,7 @@ var CatalogueClient = class {
|
|
|
9841
10336
|
async readCache() {
|
|
9842
10337
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
9843
10338
|
try {
|
|
9844
|
-
const value = JSON.parse(await
|
|
10339
|
+
const value = JSON.parse(await readFile25(this.cachePath, "utf8"));
|
|
9845
10340
|
const envelope = catalogueCacheEnvelopeSchema.parse(value);
|
|
9846
10341
|
if (envelope.contentHash) {
|
|
9847
10342
|
const contentHash = catalogueContentHash(envelope.enriched, envelope.vercel);
|
|
@@ -9897,18 +10392,18 @@ var CatalogueClient = class {
|
|
|
9897
10392
|
}
|
|
9898
10393
|
return {
|
|
9899
10394
|
value: schema.parse(value),
|
|
9900
|
-
digest:
|
|
10395
|
+
digest: createHash10("sha256").update(bytes).digest("hex")
|
|
9901
10396
|
};
|
|
9902
10397
|
}
|
|
9903
10398
|
};
|
|
9904
10399
|
function defaultCatalogueCachePath() {
|
|
9905
|
-
return join41(
|
|
10400
|
+
return join41(homedir9(), ".agentwheel", "catalogue-cache.json");
|
|
9906
10401
|
}
|
|
9907
10402
|
function sameSources2(a, b) {
|
|
9908
10403
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
9909
10404
|
}
|
|
9910
10405
|
function catalogueContentHash(enriched, vercel) {
|
|
9911
|
-
return
|
|
10406
|
+
return createHash10("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
|
|
9912
10407
|
}
|
|
9913
10408
|
|
|
9914
10409
|
// src/search/index.ts
|
|
@@ -10270,8 +10765,8 @@ function includesToken(normalizedText, token) {
|
|
|
10270
10765
|
}
|
|
10271
10766
|
|
|
10272
10767
|
// src/semantic/index.ts
|
|
10273
|
-
import { createHash as
|
|
10274
|
-
import { homedir as
|
|
10768
|
+
import { createHash as createHash11 } from "crypto";
|
|
10769
|
+
import { homedir as homedir10 } from "os";
|
|
10275
10770
|
import { join as join42 } from "path";
|
|
10276
10771
|
import { env, pipeline } from "@huggingface/transformers";
|
|
10277
10772
|
var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
|
|
@@ -10360,13 +10855,13 @@ var SemanticSearchClient = class {
|
|
|
10360
10855
|
if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
|
|
10361
10856
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
10362
10857
|
if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
|
|
10363
|
-
const
|
|
10364
|
-
if (
|
|
10858
|
+
const digest2 = createHash11("sha256").update(bytes).digest("hex");
|
|
10859
|
+
if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
|
|
10365
10860
|
return bytes;
|
|
10366
10861
|
}
|
|
10367
10862
|
};
|
|
10368
10863
|
async function embedQuery(query) {
|
|
10369
|
-
env.cacheDir = join42(
|
|
10864
|
+
env.cacheDir = join42(homedir10(), ".agentwheel", "semantic-models");
|
|
10370
10865
|
const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
|
|
10371
10866
|
revision: CONTRACT.model.revision,
|
|
10372
10867
|
dtype: CONTRACT.model.dtype,
|
|
@@ -10450,8 +10945,8 @@ function ensureTrailingSlash(value) {
|
|
|
10450
10945
|
}
|
|
10451
10946
|
|
|
10452
10947
|
// src/trial/skill.ts
|
|
10453
|
-
import { createHash as
|
|
10454
|
-
import { readFile as
|
|
10948
|
+
import { createHash as createHash12 } from "crypto";
|
|
10949
|
+
import { readFile as readFile26, stat as stat13 } from "fs/promises";
|
|
10455
10950
|
import { join as join43 } from "path";
|
|
10456
10951
|
import { parse as parseYaml2 } from "yaml";
|
|
10457
10952
|
var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
|
|
@@ -10469,7 +10964,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
10469
10964
|
if (info.size > MAX_TRIAL_SKILL_BYTES) {
|
|
10470
10965
|
throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
|
|
10471
10966
|
}
|
|
10472
|
-
const content = await
|
|
10967
|
+
const content = await readFile26(path, "utf8");
|
|
10473
10968
|
const frontmatter = readSkillFrontmatter(content, artifact);
|
|
10474
10969
|
return {
|
|
10475
10970
|
schemaVersion: 1,
|
|
@@ -10480,7 +10975,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
10480
10975
|
skill: {
|
|
10481
10976
|
name: artifact.name,
|
|
10482
10977
|
relativePath: artifact.relativePath,
|
|
10483
|
-
sha256:
|
|
10978
|
+
sha256: createHash12("sha256").update(content).digest("hex"),
|
|
10484
10979
|
frontmatter,
|
|
10485
10980
|
content
|
|
10486
10981
|
}
|
|
@@ -10498,168 +10993,13 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
10498
10993
|
return { name: value.name, description: value.description };
|
|
10499
10994
|
}
|
|
10500
10995
|
|
|
10501
|
-
// src/model/fleet.ts
|
|
10502
|
-
import { lstat as lstat2, readFile as readFile26, realpath } from "fs/promises";
|
|
10503
|
-
import { homedir as homedir10 } from "os";
|
|
10504
|
-
import { isAbsolute as isAbsolute2, resolve as resolve21 } from "path";
|
|
10505
|
-
async function resolveWorkspaceScope(request = {}) {
|
|
10506
|
-
const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
|
|
10507
|
-
if (selected > 1) {
|
|
10508
|
-
throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
|
|
10509
|
-
}
|
|
10510
|
-
const globalRoot = resolve21(request.globalRoot ?? homedir10());
|
|
10511
|
-
if (request.user) {
|
|
10512
|
-
const config2 = await readWorkspaceConfig(globalRoot);
|
|
10513
|
-
assertNonFleetScope("user", config2);
|
|
10514
|
-
return { kind: "user", root: globalRoot, config: config2 };
|
|
10515
|
-
}
|
|
10516
|
-
if (request.fleet !== void 0) {
|
|
10517
|
-
const id = fleetIdSchema.parse(request.fleet);
|
|
10518
|
-
const registration = await showRegisteredFleet(id, { globalRoot });
|
|
10519
|
-
const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
10520
|
-
const config2 = await readRequiredConfig(root, `fleet '${id}'`);
|
|
10521
|
-
assertFleetContract(id, registration, config2);
|
|
10522
|
-
return { kind: "fleet", root, fleetId: id, config: config2 };
|
|
10523
|
-
}
|
|
10524
|
-
const cwd = resolve21(request.cwd ?? process.cwd());
|
|
10525
|
-
const discoveredRoot = await findExistingWorkspaceRoot(cwd);
|
|
10526
|
-
if (!discoveredRoot || discoveredRoot === globalRoot) {
|
|
10527
|
-
if (request.local === true && cwd !== globalRoot) {
|
|
10528
|
-
return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
|
|
10529
|
-
}
|
|
10530
|
-
throw missingScopeError(request.local === true ? "local" : "implicit local");
|
|
10531
|
-
}
|
|
10532
|
-
const config = await readRequiredConfig(discoveredRoot, "local");
|
|
10533
|
-
assertNonFleetScope("local", config);
|
|
10534
|
-
return { kind: "local", root: discoveredRoot, config };
|
|
10535
|
-
}
|
|
10536
|
-
async function registerFleet(request) {
|
|
10537
|
-
const id = fleetIdSchema.parse(request.id);
|
|
10538
|
-
const requiredPackages = sortedUnique5(request.requiredPackages);
|
|
10539
|
-
if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
|
|
10540
|
-
const globalRoot = resolve21(request.globalRoot ?? homedir10());
|
|
10541
|
-
const home = await readWorkspaceConfig(globalRoot);
|
|
10542
|
-
if (supportsFleetConfig(home) && home.fleetId) {
|
|
10543
|
-
throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
|
|
10544
|
-
}
|
|
10545
|
-
const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
|
|
10546
|
-
if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
|
|
10547
|
-
const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
|
|
10548
|
-
const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
|
|
10549
|
-
if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
|
|
10550
|
-
if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
|
|
10551
|
-
const target = await readRequiredConfig(root, `fleet '${id}'`);
|
|
10552
|
-
const registration = registeredFleetSchema.parse({ root, requiredPackages });
|
|
10553
|
-
assertFleetContract(id, registration, target);
|
|
10554
|
-
const upgraded = workspaceConfigSchema.parse({
|
|
10555
|
-
...home,
|
|
10556
|
-
schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
10557
|
-
exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
|
|
10558
|
-
fleets: { ...existing, [id]: registration }
|
|
10559
|
-
});
|
|
10560
|
-
const configPath = globalWorkspaceConfigPath(globalRoot);
|
|
10561
|
-
declareMutationPath(configPath);
|
|
10562
|
-
await writeJsonAtomic(configPath, upgraded);
|
|
10563
|
-
return { id, ...registration };
|
|
10564
|
-
}
|
|
10565
|
-
async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
|
|
10566
|
-
const canonical = /* @__PURE__ */ new Map();
|
|
10567
|
-
const normalized = {};
|
|
10568
|
-
for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
|
|
10569
|
-
const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
10570
|
-
if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
|
|
10571
|
-
const incumbent = canonical.get(root);
|
|
10572
|
-
if (incumbent) {
|
|
10573
|
-
throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
|
|
10574
|
-
}
|
|
10575
|
-
canonical.set(root, id);
|
|
10576
|
-
normalized[id] = registeredFleetSchema.parse({ ...registration, root });
|
|
10577
|
-
}
|
|
10578
|
-
return normalized;
|
|
10579
|
-
}
|
|
10580
|
-
async function listRegisteredFleets(options = {}) {
|
|
10581
|
-
const globalRoot = resolve21(options.globalRoot ?? homedir10());
|
|
10582
|
-
const home = await readWorkspaceConfig(globalRoot);
|
|
10583
|
-
if (!supportsFleetConfig(home)) return [];
|
|
10584
|
-
return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
|
|
10585
|
-
}
|
|
10586
|
-
async function showRegisteredFleet(idInput, options = {}) {
|
|
10587
|
-
const id = fleetIdSchema.parse(idInput);
|
|
10588
|
-
const fleets = await listRegisteredFleets(options);
|
|
10589
|
-
const registration = fleets.find((candidate) => candidate.id === id);
|
|
10590
|
-
if (!registration) {
|
|
10591
|
-
throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
|
|
10592
|
-
}
|
|
10593
|
-
return registration;
|
|
10594
|
-
}
|
|
10595
|
-
async function readRequiredConfig(root, label) {
|
|
10596
|
-
const path = workspaceConfigPath(root);
|
|
10597
|
-
if (!await pathExists(path)) throw missingScopeError(label);
|
|
10598
|
-
try {
|
|
10599
|
-
return workspaceConfigSchema.parse(JSON.parse(await readFile26(path, "utf8")));
|
|
10600
|
-
} catch (error) {
|
|
10601
|
-
throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10602
|
-
}
|
|
10603
|
-
}
|
|
10604
|
-
function assertFleetContract(id, registration, config) {
|
|
10605
|
-
if (!supportsFleetConfig(config)) {
|
|
10606
|
-
throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
|
|
10607
|
-
}
|
|
10608
|
-
if (config.fleetId !== id) {
|
|
10609
|
-
throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
|
|
10610
|
-
}
|
|
10611
|
-
if (Object.keys(config.fleets).length > 0) {
|
|
10612
|
-
throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
|
|
10613
|
-
}
|
|
10614
|
-
const names = new Set(config.packages.map((pkg) => pkg.name));
|
|
10615
|
-
for (const packageName of registration.requiredPackages) {
|
|
10616
|
-
if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
|
|
10617
|
-
}
|
|
10618
|
-
}
|
|
10619
|
-
async function assertCanonicalDirectory(input, label) {
|
|
10620
|
-
const canonical = await canonicalizeDirectory(input, label);
|
|
10621
|
-
const normalized = resolve21(input);
|
|
10622
|
-
const stats = await lstat2(input);
|
|
10623
|
-
if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
|
|
10624
|
-
throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
|
|
10625
|
-
}
|
|
10626
|
-
return canonical;
|
|
10627
|
-
}
|
|
10628
|
-
async function canonicalizeDirectory(input, label) {
|
|
10629
|
-
if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
|
|
10630
|
-
let canonical;
|
|
10631
|
-
try {
|
|
10632
|
-
canonical = await realpath(input);
|
|
10633
|
-
} catch {
|
|
10634
|
-
throw new Error(`${label} does not exist: ${input}`);
|
|
10635
|
-
}
|
|
10636
|
-
const stats = await lstat2(canonical);
|
|
10637
|
-
if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
|
|
10638
|
-
return canonical;
|
|
10639
|
-
}
|
|
10640
|
-
function missingScopeError(label) {
|
|
10641
|
-
return new Error(
|
|
10642
|
-
`Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
|
|
10643
|
-
);
|
|
10644
|
-
}
|
|
10645
|
-
function assertNonFleetScope(kind, config) {
|
|
10646
|
-
if (supportsFleetConfig(config) && config.fleetId) {
|
|
10647
|
-
throw new Error(
|
|
10648
|
-
`The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
|
|
10649
|
-
);
|
|
10650
|
-
}
|
|
10651
|
-
}
|
|
10652
|
-
function sortedUnique5(values) {
|
|
10653
|
-
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
10654
|
-
}
|
|
10655
|
-
|
|
10656
10996
|
// src/lifecycle/fleet-normalize.ts
|
|
10657
|
-
import { createHash as
|
|
10997
|
+
import { createHash as createHash13 } from "crypto";
|
|
10658
10998
|
import { readFile as readFile27, readdir as readdir9, rm as rm9 } from "fs/promises";
|
|
10659
10999
|
import { homedir as homedir11 } from "os";
|
|
10660
|
-
import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as
|
|
11000
|
+
import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve23 } from "path";
|
|
10661
11001
|
async function planFleetNormalization(request) {
|
|
10662
|
-
const normalizedRequest =
|
|
11002
|
+
const normalizedRequest = normalizeRequest2(request);
|
|
10663
11003
|
const destinationScope = await resolveWorkspaceScope({
|
|
10664
11004
|
fleet: normalizedRequest.destinationFleet,
|
|
10665
11005
|
globalRoot: normalizedRequest.globalRoot
|
|
@@ -10680,8 +11020,8 @@ async function planFleetNormalization(request) {
|
|
|
10680
11020
|
if (!sourcePackage || !destinationPackage) {
|
|
10681
11021
|
throw new Error(`Package '${name}' must be an existing duplicate in both source and destination fleets.`);
|
|
10682
11022
|
}
|
|
10683
|
-
const sourceDeclaration =
|
|
10684
|
-
const destinationDeclaration =
|
|
11023
|
+
const sourceDeclaration = canonicalJson2(sourcePackage);
|
|
11024
|
+
const destinationDeclaration = canonicalJson2(destinationPackage);
|
|
10685
11025
|
if (sourceDeclaration !== destinationDeclaration) {
|
|
10686
11026
|
throw new Error(
|
|
10687
11027
|
`Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
|
|
@@ -10718,13 +11058,13 @@ async function planFleetNormalization(request) {
|
|
|
10718
11058
|
packages,
|
|
10719
11059
|
installedState
|
|
10720
11060
|
};
|
|
10721
|
-
return { ...planWithoutDigest, planDigest: sha2564(
|
|
11061
|
+
return { ...planWithoutDigest, planDigest: sha2564(canonicalJson2(planWithoutDigest)) };
|
|
10722
11062
|
}
|
|
10723
11063
|
async function applyFleetNormalization(request, hooks = {}) {
|
|
10724
11064
|
if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
|
|
10725
11065
|
if (!request.planDigest) throw new Error("Applying fleet normalization requires --plan-digest <digest> from a reviewed dry-run.");
|
|
10726
11066
|
assertDigest(request.planDigest);
|
|
10727
|
-
const normalizedRequest =
|
|
11067
|
+
const normalizedRequest = normalizeRequest2(request);
|
|
10728
11068
|
const roots = await normalizationRoots(normalizedRequest);
|
|
10729
11069
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
10730
11070
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -10856,14 +11196,14 @@ async function assertNormalizationPostconditions(journal) {
|
|
|
10856
11196
|
}
|
|
10857
11197
|
for (const graphLock of journal.graphLocks) {
|
|
10858
11198
|
const raw = await readOptionalRecord(graphLock.path);
|
|
10859
|
-
const
|
|
10860
|
-
if (
|
|
11199
|
+
const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
|
|
11200
|
+
if (digest2 !== graphLock.afterDigest) {
|
|
10861
11201
|
throw new Error(`Fleet normalization graph-lock postcheck failed: ${graphLock.path}`);
|
|
10862
11202
|
}
|
|
10863
11203
|
}
|
|
10864
11204
|
}
|
|
10865
11205
|
async function recoverFleetNormalization(request) {
|
|
10866
|
-
const normalizedRequest =
|
|
11206
|
+
const normalizedRequest = normalizeRequest2(request);
|
|
10867
11207
|
const roots = await normalizationRoots(normalizedRequest);
|
|
10868
11208
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
10869
11209
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -10906,7 +11246,7 @@ async function assertSourceFleetPostcondition(request, source, selectedPackages)
|
|
|
10906
11246
|
);
|
|
10907
11247
|
}
|
|
10908
11248
|
}
|
|
10909
|
-
function
|
|
11249
|
+
function normalizeRequest2(request) {
|
|
10910
11250
|
const packages = request.packages === void 0 ? void 0 : sortedUnique6(request.packages);
|
|
10911
11251
|
if (request.packages !== void 0 && packages?.length === 0) throw new Error("--package requires at least one package name.");
|
|
10912
11252
|
const artifacts = request.artifacts === void 0 ? void 0 : sortedUnique6(request.artifacts.map((artifact) => normalizeArtifactSelector(artifact)));
|
|
@@ -10928,17 +11268,17 @@ function normalizeRequest(request) {
|
|
|
10928
11268
|
...profile ? { profile } : {},
|
|
10929
11269
|
...agent ? { agent } : {},
|
|
10930
11270
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
10931
|
-
...request.globalRoot ? { globalRoot:
|
|
11271
|
+
...request.globalRoot ? { globalRoot: resolve23(request.globalRoot) } : {}
|
|
10932
11272
|
};
|
|
10933
11273
|
}
|
|
10934
11274
|
function normalizeOrphanedOwnerRoot(value) {
|
|
10935
11275
|
const root = value.trim();
|
|
10936
11276
|
if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
10937
|
-
return
|
|
11277
|
+
return resolve23(root);
|
|
10938
11278
|
}
|
|
10939
11279
|
async function normalizationRoots(request) {
|
|
10940
11280
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
10941
|
-
const source = request.from === "user" ?
|
|
11281
|
+
const source = request.from === "user" ? resolve23(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
10942
11282
|
return { source, destination: destination.root };
|
|
10943
11283
|
}
|
|
10944
11284
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -11029,10 +11369,10 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11029
11369
|
const destinationGraphAfter = destinationGraph?.lock ?? destinationGraphFromSource(sourceGraph.lock, destinationState.targetFingerprint);
|
|
11030
11370
|
graphTransfers.push({
|
|
11031
11371
|
sourceGraphLockPath: sourceGraph.path,
|
|
11032
|
-
sourceGraphLockDigest: sha2564(
|
|
11372
|
+
sourceGraphLockDigest: sha2564(canonicalJson2(sourceGraph.lock)),
|
|
11033
11373
|
destinationGraphLockPath: destinationState.graphLockPath,
|
|
11034
|
-
destinationGraphLockDigest: destinationGraph ? sha2564(
|
|
11035
|
-
destinationGraphLockAfterDigest: sha2564(
|
|
11374
|
+
destinationGraphLockDigest: destinationGraph ? sha2564(canonicalJson2(destinationGraph.lock)) : null,
|
|
11375
|
+
destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraphAfter)),
|
|
11036
11376
|
targetKey: sourceGraph.targetKey,
|
|
11037
11377
|
adapter: destinationState.adapter,
|
|
11038
11378
|
targetFingerprint: destinationState.targetFingerprint
|
|
@@ -11245,7 +11585,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11245
11585
|
)
|
|
11246
11586
|
});
|
|
11247
11587
|
if (!selectedArtifacts) {
|
|
11248
|
-
const fullDigest = sha2564(
|
|
11588
|
+
const fullDigest = sha2564(canonicalJson2(graph.lock));
|
|
11249
11589
|
const existingDestinationGraph = graphCandidates.find((candidate) => candidate.path === destinationState.graphLockPath);
|
|
11250
11590
|
if (existingDestinationGraph) assertGraphIsSubset(graph, existingDestinationGraph);
|
|
11251
11591
|
const destinationGraph = destinationGraphFromSource(graph.lock, destinationState.targetFingerprint);
|
|
@@ -11253,8 +11593,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11253
11593
|
sourceGraphLockPath: graph.path,
|
|
11254
11594
|
sourceGraphLockDigest: fullDigest,
|
|
11255
11595
|
destinationGraphLockPath: destinationState.graphLockPath,
|
|
11256
|
-
destinationGraphLockDigest: existingDestinationGraph ? sha2564(
|
|
11257
|
-
destinationGraphLockAfterDigest: sha2564(
|
|
11596
|
+
destinationGraphLockDigest: existingDestinationGraph ? sha2564(canonicalJson2(existingDestinationGraph.lock)) : null,
|
|
11597
|
+
destinationGraphLockAfterDigest: sha2564(canonicalJson2(destinationGraph)),
|
|
11258
11598
|
targetKey: graph.targetKey,
|
|
11259
11599
|
adapter: graph.adapter,
|
|
11260
11600
|
targetFingerprint: destinationState.targetFingerprint
|
|
@@ -11312,7 +11652,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
11312
11652
|
async function runtimeRoots(scope) {
|
|
11313
11653
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
11314
11654
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
11315
|
-
roots.add(
|
|
11655
|
+
roots.add(resolve23(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
11316
11656
|
}
|
|
11317
11657
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
11318
11658
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -11349,14 +11689,14 @@ async function collectManifests(roots) {
|
|
|
11349
11689
|
}
|
|
11350
11690
|
continue;
|
|
11351
11691
|
}
|
|
11352
|
-
if (
|
|
11692
|
+
if (resolve23(parsed.targetRoot) !== resolve23(root)) {
|
|
11353
11693
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
11354
11694
|
}
|
|
11355
11695
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
11356
11696
|
installationType: parsed.installationType,
|
|
11357
11697
|
stateKey: parsed.stateKey
|
|
11358
11698
|
});
|
|
11359
|
-
if (
|
|
11699
|
+
if (resolve23(expectedPath) !== resolve23(path)) {
|
|
11360
11700
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11361
11701
|
}
|
|
11362
11702
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11377,7 +11717,7 @@ async function collectManifestPaths(paths) {
|
|
|
11377
11717
|
installationType: parsed.installationType,
|
|
11378
11718
|
stateKey: parsed.stateKey
|
|
11379
11719
|
});
|
|
11380
|
-
if (
|
|
11720
|
+
if (resolve23(expectedPath) !== resolve23(path)) {
|
|
11381
11721
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11382
11722
|
}
|
|
11383
11723
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11403,7 +11743,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11403
11743
|
if (!path.endsWith(".graph-lock.json")) continue;
|
|
11404
11744
|
const lock = await readGraphLock(path);
|
|
11405
11745
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
11406
|
-
const parts =
|
|
11746
|
+
const parts = relative8(root, path).split(/[\\/]/);
|
|
11407
11747
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
11408
11748
|
const [targetKey2, adapter, fileName] = parts;
|
|
11409
11749
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
@@ -11424,7 +11764,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11424
11764
|
const projection = relevantGraphProjection(lock, selected);
|
|
11425
11765
|
results.push({
|
|
11426
11766
|
...candidate,
|
|
11427
|
-
digest: sha2564(
|
|
11767
|
+
digest: sha2564(canonicalJson2(projection)),
|
|
11428
11768
|
artifactIdentities: lock.canonical.artifacts.filter((artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(lock, owner, selected))).map(graphArtifactIdentity).sort()
|
|
11429
11769
|
});
|
|
11430
11770
|
}
|
|
@@ -11460,7 +11800,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
11460
11800
|
baseDir: scope.root
|
|
11461
11801
|
});
|
|
11462
11802
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
11463
|
-
const installRoot =
|
|
11803
|
+
const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
11464
11804
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11465
11805
|
installationType: agent.installationType,
|
|
11466
11806
|
targetFingerprint: graph.lock.canonical.targetFingerprint
|
|
@@ -11534,7 +11874,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11534
11874
|
targetFingerprint,
|
|
11535
11875
|
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
11536
11876
|
});
|
|
11537
|
-
const installRoot =
|
|
11877
|
+
const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
11538
11878
|
return {
|
|
11539
11879
|
adapter: adapter.name,
|
|
11540
11880
|
installationType,
|
|
@@ -11565,9 +11905,9 @@ async function legacyTargetStateForGraph(scope, graph) {
|
|
|
11565
11905
|
};
|
|
11566
11906
|
}
|
|
11567
11907
|
function assertGraphIsSubset(source, destination) {
|
|
11568
|
-
const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId,
|
|
11908
|
+
const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
|
|
11569
11909
|
for (const root of destination.lock.canonical.roots) {
|
|
11570
|
-
if (sourceRoots.get(root.rootId) !==
|
|
11910
|
+
if (sourceRoots.get(root.rootId) !== canonicalJson2(root)) {
|
|
11571
11911
|
throw new Error(
|
|
11572
11912
|
`Partially normalized destination graph diverges at root '${root.rootId}': ${destination.path}`
|
|
11573
11913
|
);
|
|
@@ -11597,7 +11937,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
11597
11937
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
11598
11938
|
if (existing) {
|
|
11599
11939
|
const manifest2 = existing.manifest;
|
|
11600
|
-
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey ||
|
|
11940
|
+
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve23(manifest2.targetRoot) !== state.installRoot) {
|
|
11601
11941
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
11602
11942
|
}
|
|
11603
11943
|
}
|
|
@@ -11657,7 +11997,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
11657
11997
|
const content = await readFile27(path, "utf8");
|
|
11658
11998
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
11659
11999
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
11660
|
-
} else if (
|
|
12000
|
+
} else if (resolve23(sourcePath) === resolve23(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
11661
12001
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
11662
12002
|
} else {
|
|
11663
12003
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -11668,8 +12008,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
11668
12008
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
11669
12009
|
}
|
|
11670
12010
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
11671
|
-
const normalizedRoot =
|
|
11672
|
-
const candidate =
|
|
12011
|
+
const normalizedRoot = resolve23(root);
|
|
12012
|
+
const candidate = resolve23(normalizedRoot, entryPath);
|
|
11673
12013
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
11674
12014
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
11675
12015
|
}
|
|
@@ -11760,8 +12100,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
11760
12100
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
11761
12101
|
}
|
|
11762
12102
|
function renderedEntryPath(manifest, entry) {
|
|
11763
|
-
const root =
|
|
11764
|
-
const candidate =
|
|
12103
|
+
const root = resolve23(manifest.targetRoot);
|
|
12104
|
+
const candidate = resolve23(root, entry.path);
|
|
11765
12105
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
11766
12106
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
11767
12107
|
}
|
|
@@ -11780,7 +12120,7 @@ function assertSimpleVerifiableEntry(entry, path) {
|
|
|
11780
12120
|
if (entry.kind !== "file" && entry.kind !== "dir") throw new Error(`Unsupported installed entry kind at ${path}.`);
|
|
11781
12121
|
}
|
|
11782
12122
|
function entryIdentity(entry) {
|
|
11783
|
-
return
|
|
12123
|
+
return canonicalJson2({
|
|
11784
12124
|
path: entry.path,
|
|
11785
12125
|
artifactType: entry.artifactType,
|
|
11786
12126
|
artifactName: entry.artifactName,
|
|
@@ -11798,7 +12138,7 @@ function entryIdentity(entry) {
|
|
|
11798
12138
|
});
|
|
11799
12139
|
}
|
|
11800
12140
|
function graphArtifactIdentity(artifact) {
|
|
11801
|
-
return
|
|
12141
|
+
return canonicalJson2({
|
|
11802
12142
|
graphNodeId: artifact.graphNodeId,
|
|
11803
12143
|
type: artifact.type,
|
|
11804
12144
|
name: artifact.name,
|
|
@@ -11812,7 +12152,7 @@ function graphArtifactIdentity(artifact) {
|
|
|
11812
12152
|
});
|
|
11813
12153
|
}
|
|
11814
12154
|
function graphEntryIdentity(entry) {
|
|
11815
|
-
return
|
|
12155
|
+
return canonicalJson2({
|
|
11816
12156
|
graphNodeId: entry.graphNodeId,
|
|
11817
12157
|
type: entry.artifactType,
|
|
11818
12158
|
name: entry.artifactName,
|
|
@@ -11916,7 +12256,7 @@ async function buildJournalGraphLockStates(plan) {
|
|
|
11916
12256
|
for (const transfer of plan.installedState.graphTransfers) {
|
|
11917
12257
|
const sourceBefore = JSON.parse(await readFile27(transfer.sourceGraphLockPath, "utf8"));
|
|
11918
12258
|
const sourceLock = await readGraphLock(transfer.sourceGraphLockPath);
|
|
11919
|
-
const sourceDigest = sha2564(
|
|
12259
|
+
const sourceDigest = sha2564(canonicalJson2(sourceLock));
|
|
11920
12260
|
if (sourceDigest !== transfer.sourceGraphLockDigest) {
|
|
11921
12261
|
throw new Error(`Source graph lock changed after planning: ${transfer.sourceGraphLockPath}`);
|
|
11922
12262
|
}
|
|
@@ -11934,9 +12274,9 @@ async function buildJournalGraphLockStates(plan) {
|
|
|
11934
12274
|
continue;
|
|
11935
12275
|
}
|
|
11936
12276
|
const destinationBefore = await readOptionalRecord(transfer.destinationGraphLockPath);
|
|
11937
|
-
const destinationBeforeDigest = destinationBefore ? sha2564(
|
|
12277
|
+
const destinationBeforeDigest = destinationBefore ? sha2564(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
|
|
11938
12278
|
const destinationAfter = destinationBefore ? await readGraphLock(transfer.destinationGraphLockPath) : destinationGraphFromSource(sourceLock, transfer.targetFingerprint);
|
|
11939
|
-
const destinationAfterDigest = sha2564(
|
|
12279
|
+
const destinationAfterDigest = sha2564(canonicalJson2(destinationAfter));
|
|
11940
12280
|
if (destinationBeforeDigest !== transfer.destinationGraphLockDigest || destinationAfterDigest !== transfer.destinationGraphLockAfterDigest) {
|
|
11941
12281
|
throw new Error(`Destination graph lock changed after planning: ${transfer.destinationGraphLockPath}`);
|
|
11942
12282
|
}
|
|
@@ -11976,6 +12316,7 @@ async function acquireJournalManifestLocks(manifests) {
|
|
|
11976
12316
|
try {
|
|
11977
12317
|
const unique = /* @__PURE__ */ new Map();
|
|
11978
12318
|
for (const item of manifests) unique.set(item.path, item);
|
|
12319
|
+
const acquiredPaths = /* @__PURE__ */ new Set();
|
|
11979
12320
|
for (const item of [...unique.values()].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
11980
12321
|
const raw = await readOptionalRecord(item.path) ?? item.before ?? item.after;
|
|
11981
12322
|
if (!raw && !(item.targetRoot && item.adapter && item.installationType && item.stateKey)) {
|
|
@@ -11988,9 +12329,13 @@ async function acquireJournalManifestLocks(manifests) {
|
|
|
11988
12329
|
const installationType = manifest?.installationType ?? item.installationType;
|
|
11989
12330
|
const stateKey = manifest?.stateKey ?? item.stateKey;
|
|
11990
12331
|
const scope = { installationType, stateKey };
|
|
11991
|
-
const
|
|
11992
|
-
|
|
11993
|
-
|
|
12332
|
+
const lockPath = applyLockPath(targetRoot, adapter, scope);
|
|
12333
|
+
if (!acquiredPaths.has(lockPath)) {
|
|
12334
|
+
const lock = await acquireApplyLock(targetRoot, adapter, void 0, {}, scope);
|
|
12335
|
+
locks.push(lock);
|
|
12336
|
+
acquiredPaths.add(lockPath);
|
|
12337
|
+
}
|
|
12338
|
+
if ((await listApplyJournals(targetRoot, adapter, void 0, { installationType })).length > 0) {
|
|
11994
12339
|
throw new Error(`Cannot normalize while an Agentwheel apply journal is pending for ${item.path}.`);
|
|
11995
12340
|
}
|
|
11996
12341
|
}
|
|
@@ -12041,8 +12386,8 @@ async function assertJournalRecoverable(journal) {
|
|
|
12041
12386
|
}
|
|
12042
12387
|
for (const graphLock of journal.graphLocks) {
|
|
12043
12388
|
const raw = await readOptionalRecord(graphLock.path);
|
|
12044
|
-
const
|
|
12045
|
-
if (
|
|
12389
|
+
const digest2 = raw ? sha2564(canonicalJson2(await readGraphLock(graphLock.path))) : null;
|
|
12390
|
+
if (digest2 !== graphLock.beforeDigest && digest2 !== graphLock.afterDigest) {
|
|
12046
12391
|
throw new Error(`Cannot recover fleet normalization because graph lock changed outside the transaction: ${graphLock.path}`);
|
|
12047
12392
|
}
|
|
12048
12393
|
}
|
|
@@ -12094,12 +12439,12 @@ function packageMap(config) {
|
|
|
12094
12439
|
return result;
|
|
12095
12440
|
}
|
|
12096
12441
|
function configRevision(config) {
|
|
12097
|
-
return sha2564(
|
|
12442
|
+
return sha2564(canonicalJson2(config));
|
|
12098
12443
|
}
|
|
12099
12444
|
function normalizationJournalPath(sourceRoot, destinationFleet) {
|
|
12100
12445
|
return join44(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
|
|
12101
12446
|
}
|
|
12102
|
-
function
|
|
12447
|
+
function canonicalJson2(value) {
|
|
12103
12448
|
return JSON.stringify(sortValue(value));
|
|
12104
12449
|
}
|
|
12105
12450
|
function sortValue(value) {
|
|
@@ -12110,7 +12455,7 @@ function sortValue(value) {
|
|
|
12110
12455
|
return value;
|
|
12111
12456
|
}
|
|
12112
12457
|
function sha2564(value) {
|
|
12113
|
-
return
|
|
12458
|
+
return createHash13("sha256").update(value).digest("hex");
|
|
12114
12459
|
}
|
|
12115
12460
|
function assertDigest(value) {
|
|
12116
12461
|
if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
|
|
@@ -12148,6 +12493,7 @@ var commandGovernance = {
|
|
|
12148
12493
|
"package migrate": "governed-always",
|
|
12149
12494
|
remember: "governed-always",
|
|
12150
12495
|
"ownership handoff": "governed-unless-dry-run",
|
|
12496
|
+
"ownership retire-stale": "governed-apply",
|
|
12151
12497
|
"mcp retire": "governed-apply",
|
|
12152
12498
|
eject: "governed-always",
|
|
12153
12499
|
uninstall: "governed-unless-dry-run",
|
|
@@ -12564,7 +12910,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
12564
12910
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
12565
12911
|
if (options.toFleet) {
|
|
12566
12912
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
12567
|
-
if (
|
|
12913
|
+
if (resolve24(fleet.root) !== resolve24(toWorkspaceRoot)) {
|
|
12568
12914
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
12569
12915
|
}
|
|
12570
12916
|
}
|
|
@@ -12589,6 +12935,84 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
12589
12935
|
console.log(`Manifest revision: ${result.manifestRevision}`);
|
|
12590
12936
|
console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
|
|
12591
12937
|
});
|
|
12938
|
+
ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
12939
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12940
|
+
const fleet = await showRegisteredFleet(options.fleet);
|
|
12941
|
+
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
12942
|
+
if (resolve24(fleet.root) !== resolve24(toWorkspaceRoot)) {
|
|
12943
|
+
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
12944
|
+
}
|
|
12945
|
+
const targets = await resolveCliTargets(normalizedOptions);
|
|
12946
|
+
if (targets.length !== 1) {
|
|
12947
|
+
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
12948
|
+
}
|
|
12949
|
+
const target = targets[0];
|
|
12950
|
+
if (target.fleetId !== options.fleet || resolve24(target.workspaceRoot) !== resolve24(fleet.root)) {
|
|
12951
|
+
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
12952
|
+
}
|
|
12953
|
+
const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
|
|
12954
|
+
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
12955
|
+
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
12956
|
+
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
12957
|
+
const request = {
|
|
12958
|
+
targetRoot: state.installRoot,
|
|
12959
|
+
adapter: adapter.name,
|
|
12960
|
+
installationType,
|
|
12961
|
+
sourceStateKey: options.sourceStateKey,
|
|
12962
|
+
destinationStateKey: state.stateKey,
|
|
12963
|
+
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
12964
|
+
toWorkspaceRoot,
|
|
12965
|
+
toFleetId: fleet.id,
|
|
12966
|
+
planDigest: options.planDigest,
|
|
12967
|
+
expectedSourceRevision: options.expectedSourceRevision,
|
|
12968
|
+
expectedDestinationRevision: options.expectedDestinationRevision,
|
|
12969
|
+
expectedInventoryRevision: options.expectedInventoryRevision,
|
|
12970
|
+
transport: transportForTarget(target)
|
|
12971
|
+
};
|
|
12972
|
+
const result = options.apply ? await applyRetireStaleOwnership(request) : await planRetireStaleOwnership(request);
|
|
12973
|
+
const targetArgs = options.profile ? ["--profile", options.profile] : target.agentName ? ["--agent", target.agentName] : ["--adapter", adapter.name, "--target-root", target.targetRoot];
|
|
12974
|
+
const applyArgs = [
|
|
12975
|
+
"agentwheel",
|
|
12976
|
+
"ownership",
|
|
12977
|
+
"retire-stale",
|
|
12978
|
+
"--from-workspace-root",
|
|
12979
|
+
request.fromWorkspaceRoot,
|
|
12980
|
+
"--to-workspace-root",
|
|
12981
|
+
request.toWorkspaceRoot,
|
|
12982
|
+
"--source-state-key",
|
|
12983
|
+
request.sourceStateKey,
|
|
12984
|
+
"--fleet",
|
|
12985
|
+
request.toFleetId,
|
|
12986
|
+
...targetArgs,
|
|
12987
|
+
"--installation-type",
|
|
12988
|
+
installationType,
|
|
12989
|
+
...options.adapterConfig ? ["--adapter-config", options.adapterConfig] : [],
|
|
12990
|
+
...options.adapterModule ? ["--adapter-module", options.adapterModule] : [],
|
|
12991
|
+
...options.allowAdapterCode ? ["--allow-adapter-code"] : [],
|
|
12992
|
+
"--plan-digest",
|
|
12993
|
+
result.planDigest,
|
|
12994
|
+
"--expected-source-revision",
|
|
12995
|
+
result.source.revision,
|
|
12996
|
+
"--expected-destination-revision",
|
|
12997
|
+
result.destination.revision,
|
|
12998
|
+
"--expected-inventory-revision",
|
|
12999
|
+
result.manifestInventoryRevision,
|
|
13000
|
+
"--apply"
|
|
13001
|
+
];
|
|
13002
|
+
const applyCommand = applyArgs.map(shellQuoteArgument).join(" ");
|
|
13003
|
+
if (options.json) {
|
|
13004
|
+
console.log(JSON.stringify({ ...result, applyCommand }, null, 2));
|
|
13005
|
+
return;
|
|
13006
|
+
}
|
|
13007
|
+
console.log(`${options.apply ? "Retired stale ownership" : "Stale ownership retirement plan"}: ${result.selected.length} entr${result.selected.length === 1 ? "y" : "ies"}`);
|
|
13008
|
+
console.log(`Target: ${result.adapter}/${result.installationType} at ${result.targetRoot}`);
|
|
13009
|
+
console.log(`Source: ${result.source.stateKey} revision ${result.source.revision}`);
|
|
13010
|
+
console.log(`Destination: ${result.destination.stateKey} revision ${result.destination.revision}`);
|
|
13011
|
+
console.log(`Manifest inventory revision: ${result.manifestInventoryRevision}`);
|
|
13012
|
+
for (const entry of result.selected) console.log(`- ${entry.path}: ${entry.sourceEntryDigest} -> ${entry.destinationEntryDigest}`);
|
|
13013
|
+
console.log(`Plan digest: ${result.planDigest}`);
|
|
13014
|
+
if (!options.apply) console.log(`Apply: ${applyCommand}`);
|
|
13015
|
+
});
|
|
12592
13016
|
var mcpCommand = program.command("mcp").description("operate on MCP runtime configuration");
|
|
12593
13017
|
mcpCommand.command("retire").description("remove one exact legacy MCP contribution with explicit state ownership").argument("<package>", "configured package containing exactly one legacy MCP artifact").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit runtime/project root").option("--agent <name>", "one named agent from merged config").option("--profile <name>", "workspace runtime profile resolving to one target").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--from-workspace-root <path>", "required previous owner when managed legacy state exists").option("--dry-run", "show the retirement plan without writing (default)", false).option("--apply", "apply the exact reviewed retirement plan", false).option("--json", "print the install plan as JSON", false).action(async (packageName, options) => {
|
|
12594
13018
|
if (options.apply && options.dryRun) throw new Error("--apply cannot be combined with --dry-run.");
|
|
@@ -13522,10 +13946,13 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
13522
13946
|
targetOptions.focusedArtifact,
|
|
13523
13947
|
focusedArtifactOwnerKeys(result.bundle.graphLock, previousGroupLock, scopedRootId, targetOptions.focusedArtifact)
|
|
13524
13948
|
)).map((operation) => operation.relativeDestPath);
|
|
13949
|
+
const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
|
|
13950
|
+
fleetId: group.target.fleetId
|
|
13951
|
+
});
|
|
13525
13952
|
await assertNoForeignWorkspaceStateForPlan(scopedResult.plan, {
|
|
13526
13953
|
transport,
|
|
13527
13954
|
workspaceRoot: group.target.workspaceRoot,
|
|
13528
|
-
workspaceOwner: workspaceOwnerForRoot(
|
|
13955
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
13529
13956
|
plannedPaths: focusedPaths
|
|
13530
13957
|
});
|
|
13531
13958
|
}
|
|
@@ -13576,7 +14003,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
13576
14003
|
selectedPreviousNodeIds,
|
|
13577
14004
|
selectedRootIds
|
|
13578
14005
|
);
|
|
13579
|
-
const graphLockDigest =
|
|
14006
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13580
14007
|
return {
|
|
13581
14008
|
...result,
|
|
13582
14009
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -13702,7 +14129,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
|
|
|
13702
14129
|
function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
13703
14130
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
|
|
13704
14131
|
const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
|
|
13705
|
-
const graphLockDigest =
|
|
14132
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13706
14133
|
return {
|
|
13707
14134
|
...scoped,
|
|
13708
14135
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -13713,7 +14140,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
|
13713
14140
|
function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
|
|
13714
14141
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
|
|
13715
14142
|
const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
|
|
13716
|
-
const graphLockDigest =
|
|
14143
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
13717
14144
|
return {
|
|
13718
14145
|
...scoped,
|
|
13719
14146
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -14008,7 +14435,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
|
14008
14435
|
selectedPreviousNodeIds,
|
|
14009
14436
|
/* @__PURE__ */ new Set([rootId])
|
|
14010
14437
|
);
|
|
14011
|
-
const graphLockDigest =
|
|
14438
|
+
const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
14012
14439
|
return {
|
|
14013
14440
|
...scoped,
|
|
14014
14441
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -14092,6 +14519,8 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
14092
14519
|
semanticPlugin: entry.semanticPlugin,
|
|
14093
14520
|
execute: entry.executed,
|
|
14094
14521
|
mergeStrategy: entry.mergeStrategy,
|
|
14522
|
+
mergeRemoval: "mergeRemoval" in entry ? entry.mergeRemoval : void 0,
|
|
14523
|
+
mergeCreatedDestination: "mergeCreatedDestination" in entry ? entry.mergeCreatedDestination : void 0,
|
|
14095
14524
|
composedFrom: entry.composedFrom,
|
|
14096
14525
|
installName: fresh?.installName ?? ("installName" in entry ? entry.installName : entry.artifactName),
|
|
14097
14526
|
logicalSelector: fresh?.logicalSelector ?? ("logicalSelector" in entry ? entry.logicalSelector : `${entry.artifactType}/${entry.artifactName}`),
|
|
@@ -14562,7 +14991,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
14562
14991
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
14563
14992
|
);
|
|
14564
14993
|
}
|
|
14565
|
-
const memberRoot =
|
|
14994
|
+
const memberRoot = resolve24(workspaceRoot, member.workspace);
|
|
14566
14995
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
14567
14996
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
14568
14997
|
throw new Error(
|
|
@@ -15013,11 +15442,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
15013
15442
|
}
|
|
15014
15443
|
function normalizeCliPath(value) {
|
|
15015
15444
|
if (value === "~") return homedir12();
|
|
15016
|
-
if (value.startsWith("~/")) return
|
|
15017
|
-
return
|
|
15445
|
+
if (value.startsWith("~/")) return resolve24(homedir12(), value.slice(2));
|
|
15446
|
+
return resolve24(value);
|
|
15447
|
+
}
|
|
15448
|
+
function shellQuoteArgument(value) {
|
|
15449
|
+
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
15018
15450
|
}
|
|
15019
15451
|
function isHomePath(path) {
|
|
15020
|
-
return
|
|
15452
|
+
return resolve24(path) === resolve24(homedir12());
|
|
15021
15453
|
}
|
|
15022
15454
|
function adapterListFromOption(adapter) {
|
|
15023
15455
|
if (!adapter) return [];
|
|
@@ -15138,7 +15570,7 @@ function withFleetExample(config) {
|
|
|
15138
15570
|
};
|
|
15139
15571
|
}
|
|
15140
15572
|
async function findAgentwheelPackageRoot(start) {
|
|
15141
|
-
let current =
|
|
15573
|
+
let current = resolve24(start);
|
|
15142
15574
|
while (true) {
|
|
15143
15575
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
15144
15576
|
const parent = dirname22(current);
|