agentwheel 0.20.8 → 0.20.10
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/README.md +31 -0
- package/dist/{apply-OEKVIYBV.js → apply-ZNEUBJU3.js} +2 -2
- package/dist/{chunk-JXX6EDIP.js → chunk-J7DKPDS7.js} +550 -309
- package/dist/{chunk-4AM3LNUQ.js → chunk-P2JK34W7.js} +119 -6
- package/dist/index.js +1366 -309
- package/dist/{transaction-D7VRZZX5.js → transaction-4ERCJXG7.js} +1 -1
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +14 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
MergeAdoptionMismatchError,
|
|
4
4
|
applyCombinedInstallPlan,
|
|
5
5
|
assertExactMergeContribution,
|
|
6
|
+
assertMergedSourceContribution,
|
|
6
7
|
assertOperationContained,
|
|
7
8
|
assertSafeInstallName,
|
|
8
9
|
canonicalGraphLockJson,
|
|
@@ -12,10 +13,12 @@ import {
|
|
|
12
13
|
commitManifestMetadataJournal,
|
|
13
14
|
computeInstallManifestInventoryRevision,
|
|
14
15
|
computeManifestRevision,
|
|
16
|
+
computeSourceLockRevision,
|
|
15
17
|
computeTargetFingerprint,
|
|
16
18
|
desiredManagedInstructionBlockHash,
|
|
17
19
|
hasMergeRemovalContent,
|
|
18
20
|
installManifestSchema,
|
|
21
|
+
installManifestV2Schema,
|
|
19
22
|
legacyUnownedWorkspaceOwner,
|
|
20
23
|
listInstallManifests,
|
|
21
24
|
managedInstructionBlockMode,
|
|
@@ -29,11 +32,12 @@ import {
|
|
|
29
32
|
readGraphLock,
|
|
30
33
|
readInstallManifest,
|
|
31
34
|
readManagedInstructionBlockState,
|
|
35
|
+
readSourceLock,
|
|
32
36
|
recoverPendingApply,
|
|
33
37
|
uninstall,
|
|
34
38
|
withManifestRevision,
|
|
35
39
|
writeInstallManifest
|
|
36
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-P2JK34W7.js";
|
|
37
41
|
import {
|
|
38
42
|
CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
39
43
|
GovernedMutation,
|
|
@@ -94,7 +98,7 @@ import {
|
|
|
94
98
|
workspaceSelectionImportSchema,
|
|
95
99
|
writeApplyJournal,
|
|
96
100
|
writeWorkspaceConfig
|
|
97
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-J7DKPDS7.js";
|
|
98
102
|
import {
|
|
99
103
|
inferSourceDriverName
|
|
100
104
|
} from "./chunk-EMDIG24I.js";
|
|
@@ -109,11 +113,11 @@ import {
|
|
|
109
113
|
} from "./chunk-7VPI5J5Y.js";
|
|
110
114
|
|
|
111
115
|
// src/cli/index.ts
|
|
112
|
-
import { createHash as
|
|
116
|
+
import { createHash as createHash15 } from "crypto";
|
|
113
117
|
import { existsSync } from "fs";
|
|
114
|
-
import { mkdir as mkdir16, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
115
|
-
import { homedir as homedir12 } from "os";
|
|
116
|
-
import { dirname as
|
|
118
|
+
import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
119
|
+
import { homedir as homedir12, tmpdir as tmpdir5 } from "os";
|
|
120
|
+
import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute4, join as join46, relative as relative10, resolve as resolve26 } from "path";
|
|
117
121
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
118
122
|
import { Command } from "commander";
|
|
119
123
|
|
|
@@ -917,11 +921,11 @@ function normalizeArtifactSelectors(select, legacySkills) {
|
|
|
917
921
|
if (selected.length === 0) return void 0;
|
|
918
922
|
return [...new Set(selected.map(parseArtifactSelector))];
|
|
919
923
|
}
|
|
920
|
-
function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
924
|
+
function filterArtifactsBySelection(artifacts, selectors, legacySkills, options = {}) {
|
|
921
925
|
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
922
926
|
if (!selected?.length) return artifacts;
|
|
923
927
|
const selectedSet = new Set(selected);
|
|
924
|
-
const available = new Set(artifacts.flatMap(artifactSelectorAliases));
|
|
928
|
+
const available = new Set((options.validationArtifacts ?? artifacts).flatMap(artifactSelectorAliases));
|
|
925
929
|
const missing = selected.filter((selector) => !available.has(selector));
|
|
926
930
|
if (missing.length > 0) {
|
|
927
931
|
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
@@ -1706,6 +1710,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1706
1710
|
hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
|
|
1707
1711
|
baseRevision: options.baseRevision ?? manifest?.revision ?? null,
|
|
1708
1712
|
runtimeStateRevision,
|
|
1713
|
+
stateMigration: options.stateMigration,
|
|
1709
1714
|
migrationReport: migration.report,
|
|
1710
1715
|
graphLockDigest: options.graphLockDigest,
|
|
1711
1716
|
adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
|
|
@@ -2388,6 +2393,7 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
|
|
|
2388
2393
|
graphNodeId: entry.graphNodeId,
|
|
2389
2394
|
dependencyRole: entry.dependencyRole,
|
|
2390
2395
|
owners: ownersOverride ?? entry.owners,
|
|
2396
|
+
workspaceOwner: entry.workspaceOwner,
|
|
2391
2397
|
graphLockDigest: entry.graphLockDigest,
|
|
2392
2398
|
mergeRemoval: entry.mergeRemoval,
|
|
2393
2399
|
mergeCreatedDestination: entry.mergeCreatedDestination
|
|
@@ -3107,14 +3113,14 @@ async function writeMermaidAsset(response) {
|
|
|
3107
3113
|
"content-type": "application/javascript; charset=utf-8",
|
|
3108
3114
|
"cache-control": "public, max-age=3600"
|
|
3109
3115
|
});
|
|
3110
|
-
await new Promise((
|
|
3116
|
+
await new Promise((resolve27) => {
|
|
3111
3117
|
const stream = createReadStream(assetPath);
|
|
3112
3118
|
stream.on("error", () => {
|
|
3113
3119
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
3114
3120
|
else response.destroy();
|
|
3115
|
-
|
|
3121
|
+
resolve27();
|
|
3116
3122
|
});
|
|
3117
|
-
stream.on("end",
|
|
3123
|
+
stream.on("end", resolve27);
|
|
3118
3124
|
stream.pipe(response);
|
|
3119
3125
|
});
|
|
3120
3126
|
}
|
|
@@ -3141,14 +3147,14 @@ function escapeHtml2(value) {
|
|
|
3141
3147
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3142
3148
|
}
|
|
3143
3149
|
function listen(server, port, bind) {
|
|
3144
|
-
return new Promise((
|
|
3150
|
+
return new Promise((resolve27, reject) => {
|
|
3145
3151
|
const onError = (error) => {
|
|
3146
3152
|
server.off("listening", onListening);
|
|
3147
3153
|
reject(error);
|
|
3148
3154
|
};
|
|
3149
3155
|
const onListening = () => {
|
|
3150
3156
|
server.off("error", onError);
|
|
3151
|
-
|
|
3157
|
+
resolve27();
|
|
3152
3158
|
};
|
|
3153
3159
|
server.once("error", onError);
|
|
3154
3160
|
server.once("listening", onListening);
|
|
@@ -3156,7 +3162,7 @@ function listen(server, port, bind) {
|
|
|
3156
3162
|
});
|
|
3157
3163
|
}
|
|
3158
3164
|
function waitForShutdown(server, beforeClose) {
|
|
3159
|
-
return new Promise((
|
|
3165
|
+
return new Promise((resolve27) => {
|
|
3160
3166
|
let closing = false;
|
|
3161
3167
|
const shutdown = () => {
|
|
3162
3168
|
if (closing) return;
|
|
@@ -3164,7 +3170,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3164
3170
|
process.off("SIGINT", shutdown);
|
|
3165
3171
|
process.off("SIGTERM", shutdown);
|
|
3166
3172
|
beforeClose();
|
|
3167
|
-
server.close(() =>
|
|
3173
|
+
server.close(() => resolve27());
|
|
3168
3174
|
};
|
|
3169
3175
|
process.once("SIGINT", shutdown);
|
|
3170
3176
|
process.once("SIGTERM", shutdown);
|
|
@@ -3174,7 +3180,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3174
3180
|
process.off("SIGINT", shutdown);
|
|
3175
3181
|
process.off("SIGTERM", shutdown);
|
|
3176
3182
|
beforeClose();
|
|
3177
|
-
|
|
3183
|
+
resolve27();
|
|
3178
3184
|
});
|
|
3179
3185
|
});
|
|
3180
3186
|
}
|
|
@@ -3727,7 +3733,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
3727
3733
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
3728
3734
|
}
|
|
3729
3735
|
async function delay(ms) {
|
|
3730
|
-
await new Promise((
|
|
3736
|
+
await new Promise((resolve27) => setTimeout(resolve27, ms));
|
|
3731
3737
|
}
|
|
3732
3738
|
function parseClawHubSource(source) {
|
|
3733
3739
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -3904,7 +3910,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
|
|
|
3904
3910
|
if (Date.now() - started > timeoutMs) {
|
|
3905
3911
|
throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
|
|
3906
3912
|
}
|
|
3907
|
-
await new Promise((
|
|
3913
|
+
await new Promise((resolve27) => setTimeout(resolve27, 50));
|
|
3908
3914
|
}
|
|
3909
3915
|
}
|
|
3910
3916
|
try {
|
|
@@ -6570,7 +6576,7 @@ async function collectIncludeSelector(raw, optional, artifactsByRelativePath, sc
|
|
|
6570
6576
|
}
|
|
6571
6577
|
function artifactTargetsRuntime(runtimes, runtime, nodeId, selector, warn) {
|
|
6572
6578
|
if (!runtimes?.length || !runtime || runtimes.includes(runtime)) return true;
|
|
6573
|
-
warn?.(`skip artifact ${nodeId}:${selector} (not targeted: runtimes=[${runtimes.join(",")}])`);
|
|
6579
|
+
warn?.(`skip artifact ${nodeId}:${selector} (selected but not targeted: runtimes=[${runtimes.join(",")}])`);
|
|
6574
6580
|
return false;
|
|
6575
6581
|
}
|
|
6576
6582
|
function requirementTargetsRuntime(runtimes, runtime, label, warn) {
|
|
@@ -6770,8 +6776,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
6770
6776
|
async function withCachePathLock(path, fn) {
|
|
6771
6777
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
6772
6778
|
let release = () => void 0;
|
|
6773
|
-
const current = previous.then(() => new Promise((
|
|
6774
|
-
release =
|
|
6779
|
+
const current = previous.then(() => new Promise((resolve27) => {
|
|
6780
|
+
release = resolve27;
|
|
6775
6781
|
}));
|
|
6776
6782
|
cacheLocks.set(path, current);
|
|
6777
6783
|
await previous;
|
|
@@ -6833,6 +6839,11 @@ function detectDirectCollisions(nodes) {
|
|
|
6833
6839
|
for (const [selector, owners] of bySelector) {
|
|
6834
6840
|
const uniqueOwners = [...new Map(owners.map((owner) => [owner.node.id, owner])).values()];
|
|
6835
6841
|
if (uniqueOwners.length <= 1) continue;
|
|
6842
|
+
const selectedArtifacts = uniqueOwners.map((owner) => ({
|
|
6843
|
+
owner,
|
|
6844
|
+
artifact: owner.artifacts.find((candidate) => artifactSelectorKey(candidate) === selector)
|
|
6845
|
+
}));
|
|
6846
|
+
if (new Set(selectedArtifacts.map(({ artifact }) => artifact.hash)).size === 1) continue;
|
|
6836
6847
|
const replacements = uniqueOwners.filter((owner) => {
|
|
6837
6848
|
const artifact = owner.artifacts.find((candidate) => artifactSelectorKey(candidate) === selector);
|
|
6838
6849
|
return uniqueOwners.every((other) => {
|
|
@@ -6842,7 +6853,7 @@ function detectDirectCollisions(nodes) {
|
|
|
6842
6853
|
});
|
|
6843
6854
|
if (replacements.length === 1) continue;
|
|
6844
6855
|
throw new Error(
|
|
6845
|
-
`Direct dependency artifact collision for ${selector}: ${uniqueOwners.map((owner) => `${owner.node.id} required by ${owner.node.requiredBy.join(", ")}`).join("; ")}. Resolve by aliasing, deselecting one artifact, or overriding the dependency selection.`
|
|
6856
|
+
`Direct dependency artifact collision for ${selector}: ${uniqueOwners.map((owner) => `${owner.node.id} required by ${owner.node.requiredBy.join(", ")}`).join("; ")}. Content hashes differ: ${selectedArtifacts.map(({ owner, artifact }) => `${owner.node.id}=sha256:${artifact.hash}`).join("; ")}. Resolve by aliasing, deselecting one artifact, or overriding the dependency selection.`
|
|
6846
6857
|
);
|
|
6847
6858
|
}
|
|
6848
6859
|
}
|
|
@@ -7027,8 +7038,8 @@ import { rm as rm7 } from "fs/promises";
|
|
|
7027
7038
|
|
|
7028
7039
|
// src/lifecycle/source-plan.ts
|
|
7029
7040
|
import { createHash as createHash8 } from "crypto";
|
|
7030
|
-
import { mkdir as mkdir14 } from "fs/promises";
|
|
7031
|
-
import { dirname as dirname17, join as join33, resolve as
|
|
7041
|
+
import { mkdir as mkdir14, readdir as readdir9 } from "fs/promises";
|
|
7042
|
+
import { basename as basename18, dirname as dirname17, join as join33, posix as posix2, resolve as resolve16 } from "path";
|
|
7032
7043
|
|
|
7033
7044
|
// src/resolve/graph-diff.ts
|
|
7034
7045
|
function diffGraphLocks(previous, next) {
|
|
@@ -7226,8 +7237,10 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7226
7237
|
for (const staged of [...stagedNodes.values()].sort((a, b) => a.rawNode.node.id.localeCompare(b.rawNode.node.id))) {
|
|
7227
7238
|
const rawNode = staged.rawNode;
|
|
7228
7239
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
|
|
7229
|
-
const
|
|
7230
|
-
const
|
|
7240
|
+
const selectionValidatedArtifacts = filterArtifactsBySelection(staged.artifacts, rawNode.node.selected);
|
|
7241
|
+
const preRuntimeArtifacts = targetContext.noDeps ? selectionValidatedArtifacts : staged.artifacts;
|
|
7242
|
+
const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(preRuntimeArtifacts, targetContext.adapter.name, runtimeSelectedSet) : preRuntimeArtifacts;
|
|
7243
|
+
const compositionArtifacts = runtimeArtifacts;
|
|
7231
7244
|
const expandedArtifacts = await expandMarkdownIncludes(compositionArtifacts, staged.root, {
|
|
7232
7245
|
nodeId: rawNode.node.id,
|
|
7233
7246
|
originNodeId: rawNode.node.id,
|
|
@@ -7275,7 +7288,12 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7275
7288
|
};
|
|
7276
7289
|
}
|
|
7277
7290
|
});
|
|
7278
|
-
const selectedArtifacts = filterArtifactsBySelection(
|
|
7291
|
+
const selectedArtifacts = filterArtifactsBySelection(
|
|
7292
|
+
expandedArtifacts,
|
|
7293
|
+
rawNode.node.selected,
|
|
7294
|
+
void 0,
|
|
7295
|
+
{ validationArtifacts: staged.artifacts }
|
|
7296
|
+
);
|
|
7279
7297
|
const claudeRenderedArtifacts = await renderClaudeSubagents(selectedArtifacts, staged.root, targetContext.adapter);
|
|
7280
7298
|
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, staged.root, targetContext.adapter);
|
|
7281
7299
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
@@ -7389,8 +7407,9 @@ function sha2563(content) {
|
|
|
7389
7407
|
return createHash7("sha256").update(content).digest("hex");
|
|
7390
7408
|
}
|
|
7391
7409
|
function assignInstallNames(graph, artifacts) {
|
|
7392
|
-
const
|
|
7393
|
-
const
|
|
7410
|
+
const equivalentArtifacts = coalesceEquivalentDirectArtifacts(artifacts);
|
|
7411
|
+
const superseded = supersededLogicalSelectors(equivalentArtifacts);
|
|
7412
|
+
const effectiveArtifacts = equivalentArtifacts.filter((artifact) => !superseded.has(artifact.logicalSelector));
|
|
7394
7413
|
const aliases = workspaceAliases(graph);
|
|
7395
7414
|
validateAliasScopes(graph, effectiveArtifacts, aliases);
|
|
7396
7415
|
const decisions = /* @__PURE__ */ new Map();
|
|
@@ -7433,6 +7452,37 @@ function assignInstallNames(graph, artifacts) {
|
|
|
7433
7452
|
}));
|
|
7434
7453
|
return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
|
|
7435
7454
|
}
|
|
7455
|
+
function coalesceEquivalentDirectArtifacts(artifacts) {
|
|
7456
|
+
const equivalent = /* @__PURE__ */ new Map();
|
|
7457
|
+
const passthrough = [];
|
|
7458
|
+
for (const artifact of artifacts) {
|
|
7459
|
+
if (artifact.dependencyRole !== "direct") {
|
|
7460
|
+
passthrough.push(artifact);
|
|
7461
|
+
continue;
|
|
7462
|
+
}
|
|
7463
|
+
const key = `${artifact.type}\0${artifact.name}\0${artifact.hash}`;
|
|
7464
|
+
const group = equivalent.get(key) ?? [];
|
|
7465
|
+
group.push(artifact);
|
|
7466
|
+
equivalent.set(key, group);
|
|
7467
|
+
}
|
|
7468
|
+
for (const group of equivalent.values()) {
|
|
7469
|
+
const sorted = [...group].sort((left, right) => left.logicalSelector.localeCompare(right.logicalSelector));
|
|
7470
|
+
const canonical = sorted[0];
|
|
7471
|
+
passthrough.push({
|
|
7472
|
+
...canonical,
|
|
7473
|
+
owners: [...new Set(sorted.flatMap((artifact) => artifact.owners))].sort((left, right) => left.localeCompare(right)),
|
|
7474
|
+
composedFrom: mergeComposedFrom(sorted)
|
|
7475
|
+
});
|
|
7476
|
+
}
|
|
7477
|
+
return passthrough;
|
|
7478
|
+
}
|
|
7479
|
+
function mergeComposedFrom(artifacts) {
|
|
7480
|
+
const entries = /* @__PURE__ */ new Map();
|
|
7481
|
+
for (const entry of artifacts.flatMap((artifact) => artifact.composedFrom ?? [])) {
|
|
7482
|
+
entries.set(`${entry.selector}\0${entry.hash}`, entry);
|
|
7483
|
+
}
|
|
7484
|
+
return entries.size > 0 ? [...entries.values()].sort((left, right) => `${left.selector}\0${left.hash}`.localeCompare(`${right.selector}\0${right.hash}`)) : void 0;
|
|
7485
|
+
}
|
|
7436
7486
|
function supersededLogicalSelectors(artifacts) {
|
|
7437
7487
|
const removed = /* @__PURE__ */ new Set();
|
|
7438
7488
|
for (const replacement of artifacts) {
|
|
@@ -7595,7 +7645,8 @@ function reachableNodeIds(graph, rootNodeId) {
|
|
|
7595
7645
|
return reachable;
|
|
7596
7646
|
}
|
|
7597
7647
|
function installNameCollisionError(group) {
|
|
7598
|
-
|
|
7648
|
+
const details = group.map((artifact) => `${artifact.logicalSelector}=sha256:${artifact.hash}`).sort().join("; ");
|
|
7649
|
+
return new Error(`Install name collision for ${group[0]?.type}/${group[0]?.installName}. Artifact content hashes: ${details}`);
|
|
7599
7650
|
}
|
|
7600
7651
|
function namespaceDecision(artifact, reason) {
|
|
7601
7652
|
return {
|
|
@@ -8029,11 +8080,71 @@ function containsPath(parent, candidate) {
|
|
|
8029
8080
|
return path === "" || !path.startsWith("..") && !isAbsolute2(path);
|
|
8030
8081
|
}
|
|
8031
8082
|
|
|
8083
|
+
// src/model/target-state.ts
|
|
8084
|
+
import { posix, resolve as resolve15 } from "path";
|
|
8085
|
+
var targetEvolutionFields = /* @__PURE__ */ new Set([
|
|
8086
|
+
"adapterCodeHash",
|
|
8087
|
+
"adapterConfig",
|
|
8088
|
+
"adapterModule",
|
|
8089
|
+
"targetRoot",
|
|
8090
|
+
"transportDescription",
|
|
8091
|
+
"ssh"
|
|
8092
|
+
]);
|
|
8093
|
+
function resolveTargetStateIdentity(options) {
|
|
8094
|
+
return {
|
|
8095
|
+
targetFingerprint: computeTargetFingerprint(options.targetFingerprintParts),
|
|
8096
|
+
stateFingerprint: computeTargetFingerprint({
|
|
8097
|
+
identityVersion: 1,
|
|
8098
|
+
contributionScope: {
|
|
8099
|
+
workspaceRoot: resolve15(options.workspaceRoot),
|
|
8100
|
+
targetKey: options.targetKey
|
|
8101
|
+
},
|
|
8102
|
+
target: {
|
|
8103
|
+
...persistentTargetParts(options.targetFingerprintParts),
|
|
8104
|
+
installRoot: normalizeInstallRoot(options.resolvedInstallRoot, options.transportKind),
|
|
8105
|
+
endpoint: targetEndpoint(options.targetFingerprintParts, options.transportKind)
|
|
8106
|
+
}
|
|
8107
|
+
})
|
|
8108
|
+
};
|
|
8109
|
+
}
|
|
8110
|
+
function persistentTargetParts(value) {
|
|
8111
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { value };
|
|
8112
|
+
return Object.fromEntries(
|
|
8113
|
+
Object.entries(value).filter(([key]) => !targetEvolutionFields.has(key))
|
|
8114
|
+
);
|
|
8115
|
+
}
|
|
8116
|
+
function normalizeInstallRoot(path, transportKind) {
|
|
8117
|
+
return transportKind === "ssh" ? posix.normalize(path) : resolve15(path);
|
|
8118
|
+
}
|
|
8119
|
+
function targetEndpoint(value, transportKind) {
|
|
8120
|
+
if (transportKind !== "ssh") return { kind: "local" };
|
|
8121
|
+
const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8122
|
+
const ssh = record.ssh && typeof record.ssh === "object" && !Array.isArray(record.ssh) ? record.ssh : {};
|
|
8123
|
+
if (typeof ssh.host !== "string" || ssh.host.trim().length === 0) {
|
|
8124
|
+
throw new Error("SSH endpoint host is required for target state identity; transport description is not an identity source.");
|
|
8125
|
+
}
|
|
8126
|
+
if (ssh.user !== void 0 && (typeof ssh.user !== "string" || ssh.user.trim().length === 0)) {
|
|
8127
|
+
throw new Error("SSH target state identity requires a non-empty endpoint user when one is provided.");
|
|
8128
|
+
}
|
|
8129
|
+
if (ssh.port !== void 0 && (!Number.isInteger(ssh.port) || ssh.port <= 0)) {
|
|
8130
|
+
throw new Error("SSH target state identity requires a positive integer endpoint port.");
|
|
8131
|
+
}
|
|
8132
|
+
return {
|
|
8133
|
+
kind: "ssh",
|
|
8134
|
+
host: ssh.host.trim(),
|
|
8135
|
+
user: ssh.user,
|
|
8136
|
+
port: ssh.port ?? 22
|
|
8137
|
+
};
|
|
8138
|
+
}
|
|
8139
|
+
|
|
8032
8140
|
// src/lifecycle/source-plan.ts
|
|
8033
8141
|
async function createGraphSourcePlan(options) {
|
|
8034
8142
|
if (options.roots.length === 0) {
|
|
8035
8143
|
throw new Error("At least one source is required for a graph plan.");
|
|
8036
8144
|
}
|
|
8145
|
+
if (options.freshGraphOnly && options.readOnly !== true) {
|
|
8146
|
+
throw new Error("Fresh graph recovery is read-only and cannot be used for install planning.");
|
|
8147
|
+
}
|
|
8037
8148
|
const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
|
|
8038
8149
|
const transport = options.transport ?? localTransport;
|
|
8039
8150
|
const warnings = [];
|
|
@@ -8042,22 +8153,62 @@ async function createGraphSourcePlan(options) {
|
|
|
8042
8153
|
options.warn?.(message);
|
|
8043
8154
|
};
|
|
8044
8155
|
const installationType = options.installationType ?? resolveInstallationTypeForAdapterTarget(options.adapter);
|
|
8045
|
-
const
|
|
8156
|
+
const targetFingerprintParts2 = options.targetFingerprintParts ?? {
|
|
8046
8157
|
adapter: options.adapter.name,
|
|
8047
8158
|
fleetId: options.fleetId,
|
|
8048
8159
|
installationType,
|
|
8049
8160
|
targetRoot: options.targetRoot,
|
|
8050
8161
|
transport: transport.kind,
|
|
8051
8162
|
transportDescription: transport.description
|
|
8163
|
+
};
|
|
8164
|
+
const installRoot = installRootForAdapterInstallationType(options.adapter, options.targetRoot, installationType, transport.kind === "ssh");
|
|
8165
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
8166
|
+
targetFingerprintParts: targetFingerprintParts2,
|
|
8167
|
+
workspaceRoot,
|
|
8168
|
+
targetKey: options.targetKey ?? "default",
|
|
8169
|
+
resolvedInstallRoot: installRoot,
|
|
8170
|
+
transportKind: transport.kind
|
|
8052
8171
|
});
|
|
8172
|
+
const targetFingerprint = targetIdentity.targetFingerprint;
|
|
8053
8173
|
const stateKey = stateKeyFor(options.adapter.name, {
|
|
8054
8174
|
installationType,
|
|
8055
8175
|
stateKey: options.stateKey,
|
|
8056
|
-
targetFingerprint,
|
|
8176
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
8057
8177
|
fleetId: options.fleetId
|
|
8058
8178
|
});
|
|
8059
|
-
const
|
|
8060
|
-
|
|
8179
|
+
const graphLockPath = pathForGraphLock(
|
|
8180
|
+
workspaceRoot,
|
|
8181
|
+
options.targetKey ?? "default",
|
|
8182
|
+
options.adapter.name,
|
|
8183
|
+
targetIdentity.stateFingerprint
|
|
8184
|
+
);
|
|
8185
|
+
const ownership = await resolveWorkspaceOwnershipScope(workspaceRoot, {
|
|
8186
|
+
fleetId: options.fleetId,
|
|
8187
|
+
globalRoot: options.globalRoot
|
|
8188
|
+
});
|
|
8189
|
+
const workspaceOwner = workspaceOwnerForRoot(ownership.root, ownership.fleetId);
|
|
8190
|
+
let recoveredPendingApply = false;
|
|
8191
|
+
if (options.readOnly !== true) {
|
|
8192
|
+
const pendingJournal = await discoverTargetApplyJournal({
|
|
8193
|
+
adapter: options.adapter.name,
|
|
8194
|
+
installationType,
|
|
8195
|
+
stateKey,
|
|
8196
|
+
explicitStateKey: options.stateKey,
|
|
8197
|
+
fleetId: options.fleetId,
|
|
8198
|
+
installRoot,
|
|
8199
|
+
workspaceOwner,
|
|
8200
|
+
graphLockPath,
|
|
8201
|
+
transport
|
|
8202
|
+
});
|
|
8203
|
+
if (pendingJournal) {
|
|
8204
|
+
recoveredPendingApply = await recoverPendingApplyIfSafe(
|
|
8205
|
+
installRoot,
|
|
8206
|
+
options.adapter.name,
|
|
8207
|
+
transport,
|
|
8208
|
+
{ installationType, stateKey: pendingJournal.stateKey }
|
|
8209
|
+
);
|
|
8210
|
+
}
|
|
8211
|
+
}
|
|
8061
8212
|
const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
|
|
8062
8213
|
const trustPolicy = {
|
|
8063
8214
|
...normalizeTrustPolicy(workspaceConfig.trust),
|
|
@@ -8065,12 +8216,33 @@ async function createGraphSourcePlan(options) {
|
|
|
8065
8216
|
};
|
|
8066
8217
|
const lockMode = options.frozenLock === true || options.offline === true;
|
|
8067
8218
|
const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
|
|
8068
|
-
const
|
|
8069
|
-
|
|
8070
|
-
|
|
8219
|
+
const readPriorState = async () => {
|
|
8220
|
+
const [stableManifest, stableLock2, stableSourceLock2] = await Promise.all([
|
|
8221
|
+
readInstallManifest(installRoot, options.adapter.name, transport, { installationType, stateKey }),
|
|
8222
|
+
readExistingGraphLock(graphLockPath),
|
|
8223
|
+
readSourceLock(installRoot, options.adapter.name, transport, { installationType, stateKey })
|
|
8224
|
+
]);
|
|
8225
|
+
const priorState2 = await resolvePriorTargetState({
|
|
8226
|
+
adapter: options.adapter.name,
|
|
8227
|
+
installationType,
|
|
8228
|
+
stateKey,
|
|
8229
|
+
explicitStateKey: options.stateKey,
|
|
8230
|
+
fleetId: options.fleetId,
|
|
8231
|
+
installRoot,
|
|
8232
|
+
workspaceOwner,
|
|
8233
|
+
graphLockPath,
|
|
8234
|
+
stableManifest,
|
|
8235
|
+
stableLock: stableLock2,
|
|
8236
|
+
transport
|
|
8237
|
+
});
|
|
8238
|
+
return { priorState: priorState2, stableLock: stableLock2, stableSourceLock: stableSourceLock2 };
|
|
8239
|
+
};
|
|
8240
|
+
const { priorState, stableLock, stableSourceLock } = options.freshGraphOnly ? { priorState: {}, stableLock: void 0, stableSourceLock: void 0 } : await readPriorState();
|
|
8241
|
+
const previousLock = priorState.graphLock;
|
|
8242
|
+
const registryClient = new RegistryClient({ workspaceRoot, cachePath: options.registryCachePath, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
8071
8243
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
8072
8244
|
workspaceRoot,
|
|
8073
|
-
cacheRoot: join33(workspaceRoot, ".agentwheel", "cache"),
|
|
8245
|
+
cacheRoot: options.cacheRoot ?? join33(workspaceRoot, ".agentwheel", "cache"),
|
|
8074
8246
|
registryClient,
|
|
8075
8247
|
noDeps: options.noDeps,
|
|
8076
8248
|
includeSuggestions: options.includeSuggestions,
|
|
@@ -8107,13 +8279,13 @@ async function createGraphSourcePlan(options) {
|
|
|
8107
8279
|
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
8108
8280
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
8109
8281
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
}
|
|
8115
|
-
const
|
|
8116
|
-
const
|
|
8282
|
+
if (normalizeInstallRoot2(resolvedInstallRoot, transport.kind) !== normalizeInstallRoot2(installRoot, transport.kind)) {
|
|
8283
|
+
throw new Error(
|
|
8284
|
+
`Adapter ${options.adapter.name} resolved a different install root after rendering; target state identity cannot be proven.`
|
|
8285
|
+
);
|
|
8286
|
+
}
|
|
8287
|
+
const manifest = priorState.manifest;
|
|
8288
|
+
const basePlan = options.retireExactMcp ? await createExactMcpRetirementPlan(
|
|
8117
8289
|
desiredArtifacts,
|
|
8118
8290
|
options.adapter,
|
|
8119
8291
|
options.targetRoot,
|
|
@@ -8135,10 +8307,18 @@ async function createGraphSourcePlan(options) {
|
|
|
8135
8307
|
forceDrift: options.forceDrift,
|
|
8136
8308
|
forceConflict: options.forceConflict,
|
|
8137
8309
|
replaceConflict: options.replaceConflict,
|
|
8310
|
+
stateMigration: priorState.migration,
|
|
8138
8311
|
warn
|
|
8139
8312
|
});
|
|
8313
|
+
const plan = priorState.migration && !basePlan.stateMigration ? { ...basePlan, stateMigration: priorState.migration } : basePlan;
|
|
8314
|
+
plan.targetStateFilePreconditions = {
|
|
8315
|
+
graphLockPath,
|
|
8316
|
+
graphLockRevision: stableLock ? digestGraphLock(stableLock) : null,
|
|
8317
|
+
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
8318
|
+
};
|
|
8319
|
+
let foreignStateObservations = [];
|
|
8140
8320
|
if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
|
|
8141
|
-
await assertNoForeignWorkspaceState({
|
|
8321
|
+
foreignStateObservations = await assertNoForeignWorkspaceState({
|
|
8142
8322
|
installRoot: resolvedInstallRoot,
|
|
8143
8323
|
adapter: options.adapter.name,
|
|
8144
8324
|
transport,
|
|
@@ -8146,9 +8326,11 @@ async function createGraphSourcePlan(options) {
|
|
|
8146
8326
|
workspaceOwner,
|
|
8147
8327
|
globalRoot: options.globalRoot,
|
|
8148
8328
|
stateKey,
|
|
8329
|
+
migratingStateKey: priorState.migration?.fromStateKey,
|
|
8149
8330
|
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8150
8331
|
plannedOperations: plan.operations
|
|
8151
8332
|
});
|
|
8333
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8152
8334
|
}
|
|
8153
8335
|
return {
|
|
8154
8336
|
plan,
|
|
@@ -8161,7 +8343,10 @@ async function createGraphSourcePlan(options) {
|
|
|
8161
8343
|
warnings,
|
|
8162
8344
|
newTransitiveSources: trustEvaluation.promptSources,
|
|
8163
8345
|
graphDiff,
|
|
8164
|
-
recoveredPendingApply
|
|
8346
|
+
recoveredPendingApply,
|
|
8347
|
+
previousManifest: priorState.manifest,
|
|
8348
|
+
previousGraphLock: priorState.graphLock,
|
|
8349
|
+
foreignStateObservations
|
|
8165
8350
|
};
|
|
8166
8351
|
}
|
|
8167
8352
|
async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
@@ -8174,12 +8359,15 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
|
8174
8359
|
workspaceOwner: options.workspaceOwner,
|
|
8175
8360
|
globalRoot: options.globalRoot,
|
|
8176
8361
|
stateKey: plan.stateKey,
|
|
8362
|
+
migratingStateKey: plan.stateMigration?.fromStateKey,
|
|
8177
8363
|
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
|
|
8178
8364
|
plannedOperations: plan.operations
|
|
8179
8365
|
});
|
|
8366
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8180
8367
|
}
|
|
8181
|
-
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
|
|
8182
|
-
|
|
8368
|
+
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2, context) {
|
|
8369
|
+
const identity = resolveTargetStateIdentity({ targetFingerprintParts: targetFingerprintParts2, workspaceRoot, targetKey: targetKey2, ...context });
|
|
8370
|
+
return pathForGraphLock(workspaceRoot, targetKey2, adapter, identity.stateFingerprint);
|
|
8183
8371
|
}
|
|
8184
8372
|
function desiredArtifactsFromGraphBundle(bundle) {
|
|
8185
8373
|
return bundle.artifacts.map((artifact) => desiredArtifactFromResolved(artifact));
|
|
@@ -8221,10 +8409,278 @@ async function readExistingGraphLock(path) {
|
|
|
8221
8409
|
if (!await pathExists(path)) return void 0;
|
|
8222
8410
|
return readGraphLock(path);
|
|
8223
8411
|
}
|
|
8412
|
+
async function discoverTargetApplyJournal(options) {
|
|
8413
|
+
const journals = await listApplyJournals(
|
|
8414
|
+
options.installRoot,
|
|
8415
|
+
options.adapter,
|
|
8416
|
+
options.transport,
|
|
8417
|
+
{ installationType: options.installationType }
|
|
8418
|
+
);
|
|
8419
|
+
const correlated = [];
|
|
8420
|
+
for (const item of journals) {
|
|
8421
|
+
const journal = item.journal;
|
|
8422
|
+
const journalStateKey = journal.stateKey ?? stateKeyFor(options.adapter, { installationType: options.installationType });
|
|
8423
|
+
if (normalizeInstallRoot2(journal.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8424
|
+
continue;
|
|
8425
|
+
}
|
|
8426
|
+
if (journalStateKey === options.stateKey) {
|
|
8427
|
+
validateJournalManifestEnvelope(journal.manifest, options, journalStateKey, item.path);
|
|
8428
|
+
correlated.push({ path: item.path, stateKey: journalStateKey });
|
|
8429
|
+
continue;
|
|
8430
|
+
}
|
|
8431
|
+
if (!journal.graphLockPath || !journal.graphLock || !journal.graphLockDigest) continue;
|
|
8432
|
+
if (resolve16(dirname17(journal.graphLockPath)) !== resolve16(dirname17(options.graphLockPath))) continue;
|
|
8433
|
+
if (options.transport.kind === "ssh") {
|
|
8434
|
+
throw new Error(
|
|
8435
|
+
`Legacy SSH apply journal at ${item.path} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the journal was preserved.`
|
|
8436
|
+
);
|
|
8437
|
+
}
|
|
8438
|
+
const suffix = ".graph-lock.json";
|
|
8439
|
+
const graphName = basename18(journal.graphLockPath);
|
|
8440
|
+
if (!graphName.endsWith(suffix)) {
|
|
8441
|
+
throw new Error(`Legacy apply journal at ${item.path} has an invalid graph-lock path.`);
|
|
8442
|
+
}
|
|
8443
|
+
const fingerprint = graphName.slice(0, -suffix.length);
|
|
8444
|
+
if (!/^[a-f0-9]{64}$/u.test(fingerprint) || journal.graphLock.canonical.targetFingerprint !== fingerprint) {
|
|
8445
|
+
throw new Error(`Legacy apply journal graph-lock fingerprint does not match its path at ${item.path}.`);
|
|
8446
|
+
}
|
|
8447
|
+
const expectedStateKey = stateKeyFor(options.adapter, {
|
|
8448
|
+
installationType: options.installationType,
|
|
8449
|
+
stateKey: options.explicitStateKey,
|
|
8450
|
+
targetFingerprint: fingerprint,
|
|
8451
|
+
fleetId: options.fleetId
|
|
8452
|
+
});
|
|
8453
|
+
if (journalStateKey !== expectedStateKey) {
|
|
8454
|
+
throw new Error(`Legacy apply journal state key does not match its graph lock at ${item.path}.`);
|
|
8455
|
+
}
|
|
8456
|
+
let actualGraphLock;
|
|
8457
|
+
try {
|
|
8458
|
+
actualGraphLock = await readGraphLock(journal.graphLockPath);
|
|
8459
|
+
} catch (error) {
|
|
8460
|
+
throw new Error(
|
|
8461
|
+
`Legacy apply journal graph lock is missing or invalid at ${journal.graphLockPath}: ` + (error instanceof Error ? error.message : String(error))
|
|
8462
|
+
);
|
|
8463
|
+
}
|
|
8464
|
+
const embeddedDigest = digestGraphLock(journal.graphLock);
|
|
8465
|
+
const actualDigest = digestGraphLock(actualGraphLock);
|
|
8466
|
+
if (embeddedDigest !== journal.graphLockDigest || actualDigest !== journal.graphLockDigest) {
|
|
8467
|
+
throw new Error(`Legacy apply journal graph-lock digest mismatch at ${item.path}.`);
|
|
8468
|
+
}
|
|
8469
|
+
const manifest = validateJournalManifestEnvelope(journal.manifest, options, journalStateKey, item.path);
|
|
8470
|
+
validateLegacyManifestCandidate(manifest, {
|
|
8471
|
+
...options,
|
|
8472
|
+
candidateStateKey: journalStateKey,
|
|
8473
|
+
graphLockDigest: journal.graphLockDigest,
|
|
8474
|
+
graphLockPath: journal.graphLockPath
|
|
8475
|
+
});
|
|
8476
|
+
correlated.push({ path: item.path, stateKey: journalStateKey });
|
|
8477
|
+
}
|
|
8478
|
+
if (correlated.length > 1) {
|
|
8479
|
+
throw new Error(`Ambiguous apply journals for ${options.adapter}: multiple journals match the requested target state.`);
|
|
8480
|
+
}
|
|
8481
|
+
return correlated[0];
|
|
8482
|
+
}
|
|
8483
|
+
function validateJournalManifestEnvelope(value, options, stateKey, journalPath) {
|
|
8484
|
+
let manifest;
|
|
8485
|
+
try {
|
|
8486
|
+
manifest = installManifestV2Schema.parse(value);
|
|
8487
|
+
} catch (error) {
|
|
8488
|
+
throw new Error(
|
|
8489
|
+
`Apply journal at ${journalPath} has an invalid install manifest: ` + (error instanceof Error ? error.message : String(error))
|
|
8490
|
+
);
|
|
8491
|
+
}
|
|
8492
|
+
if (manifest.adapter !== options.adapter || manifest.installationType !== options.installationType || manifest.stateKey !== stateKey || normalizeInstallRoot2(manifest.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8493
|
+
throw new Error(`Apply journal target identity does not match its manifest at ${journalPath}.`);
|
|
8494
|
+
}
|
|
8495
|
+
return manifest;
|
|
8496
|
+
}
|
|
8497
|
+
async function resolvePriorTargetState(options) {
|
|
8498
|
+
const candidates = await discoverLegacyStateCandidates(options);
|
|
8499
|
+
const correlated = candidates.filter((candidate) => candidate.manifest);
|
|
8500
|
+
if (options.stableManifest) {
|
|
8501
|
+
for (const candidate of correlated) {
|
|
8502
|
+
if (!sameManifestContributions(options.stableManifest, candidate.manifest)) {
|
|
8503
|
+
throw new Error(
|
|
8504
|
+
`Stable and legacy target states coexist with disagreeing contributions for ${options.adapter}; reconcile them explicitly.`
|
|
8505
|
+
);
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8508
|
+
const sameKeyCandidates = correlated.filter((candidate) => candidate.stateKey === options.stateKey);
|
|
8509
|
+
if (!options.stableLock && sameKeyCandidates.length > 1) {
|
|
8510
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple correlated graph locks use the explicit state key.`);
|
|
8511
|
+
}
|
|
8512
|
+
if (!options.stableLock && sameKeyCandidates.length === 1) {
|
|
8513
|
+
const candidate = sameKeyCandidates[0];
|
|
8514
|
+
return {
|
|
8515
|
+
graphLock: candidate.graphLock,
|
|
8516
|
+
manifest: options.stableManifest,
|
|
8517
|
+
migration: migrationFromCandidate(candidate)
|
|
8518
|
+
};
|
|
8519
|
+
}
|
|
8520
|
+
return { graphLock: options.stableLock, manifest: options.stableManifest };
|
|
8521
|
+
}
|
|
8522
|
+
if (options.stableLock && correlated.length > 0) {
|
|
8523
|
+
throw new Error(
|
|
8524
|
+
`Stable graph state and legacy manifest state coexist for ${options.adapter} without a stable manifest; reconcile them explicitly.`
|
|
8525
|
+
);
|
|
8526
|
+
}
|
|
8527
|
+
if (correlated.length > 1) {
|
|
8528
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple correlated manifests were found.`);
|
|
8529
|
+
}
|
|
8530
|
+
if (correlated.length === 1) {
|
|
8531
|
+
const candidate = correlated[0];
|
|
8532
|
+
return {
|
|
8533
|
+
graphLock: candidate.graphLock,
|
|
8534
|
+
manifest: candidate.manifest,
|
|
8535
|
+
migration: migrationFromCandidate(candidate)
|
|
8536
|
+
};
|
|
8537
|
+
}
|
|
8538
|
+
if (options.stableLock) return { graphLock: options.stableLock };
|
|
8539
|
+
if (candidates.length > 1) {
|
|
8540
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple graph-only candidates were found.`);
|
|
8541
|
+
}
|
|
8542
|
+
if (candidates.length === 1) {
|
|
8543
|
+
throw new Error(
|
|
8544
|
+
`Legacy graph-only target state for ${options.adapter} has no correlated install manifest; runtime identity and ownership cannot be proven, so the graph lock was preserved.`
|
|
8545
|
+
);
|
|
8546
|
+
}
|
|
8547
|
+
return {};
|
|
8548
|
+
}
|
|
8549
|
+
async function discoverLegacyStateCandidates(options) {
|
|
8550
|
+
const directory = dirname17(options.graphLockPath);
|
|
8551
|
+
let names;
|
|
8552
|
+
try {
|
|
8553
|
+
names = await readdir9(directory);
|
|
8554
|
+
} catch (error) {
|
|
8555
|
+
if (error.code === "ENOENT") return [];
|
|
8556
|
+
throw error;
|
|
8557
|
+
}
|
|
8558
|
+
const stableName = basename18(options.graphLockPath);
|
|
8559
|
+
const suffix = ".graph-lock.json";
|
|
8560
|
+
const candidates = [];
|
|
8561
|
+
for (const name of names.sort((left, right) => left.localeCompare(right))) {
|
|
8562
|
+
if (name === stableName || !name.endsWith(suffix)) continue;
|
|
8563
|
+
const fingerprint = name.slice(0, -suffix.length);
|
|
8564
|
+
if (!/^[a-f0-9]{64}$/u.test(fingerprint)) continue;
|
|
8565
|
+
const graphLockPath = join33(directory, name);
|
|
8566
|
+
let graphLock;
|
|
8567
|
+
try {
|
|
8568
|
+
graphLock = await readGraphLock(graphLockPath);
|
|
8569
|
+
} catch (error) {
|
|
8570
|
+
throw new Error(
|
|
8571
|
+
`Legacy graph lock at ${graphLockPath} is invalid or unsupported: ` + (error instanceof Error ? error.message : String(error))
|
|
8572
|
+
);
|
|
8573
|
+
}
|
|
8574
|
+
if (graphLock.canonical.targetFingerprint !== fingerprint) {
|
|
8575
|
+
throw new Error(
|
|
8576
|
+
`Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
|
|
8577
|
+
);
|
|
8578
|
+
}
|
|
8579
|
+
if (options.transport.kind === "ssh") {
|
|
8580
|
+
throw new Error(
|
|
8581
|
+
`Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
|
|
8582
|
+
);
|
|
8583
|
+
}
|
|
8584
|
+
const graphLockDigest = digestGraphLock(graphLock);
|
|
8585
|
+
const candidateStateKey = stateKeyFor(options.adapter, {
|
|
8586
|
+
installationType: options.installationType,
|
|
8587
|
+
stateKey: options.explicitStateKey,
|
|
8588
|
+
targetFingerprint: fingerprint,
|
|
8589
|
+
fleetId: options.fleetId
|
|
8590
|
+
});
|
|
8591
|
+
let manifest;
|
|
8592
|
+
let sourceLock;
|
|
8593
|
+
try {
|
|
8594
|
+
manifest = await readInstallManifest(options.installRoot, options.adapter, options.transport, {
|
|
8595
|
+
installationType: options.installationType,
|
|
8596
|
+
stateKey: candidateStateKey
|
|
8597
|
+
});
|
|
8598
|
+
sourceLock = await readSourceLock(options.installRoot, options.adapter, options.transport, {
|
|
8599
|
+
installationType: options.installationType,
|
|
8600
|
+
stateKey: candidateStateKey
|
|
8601
|
+
});
|
|
8602
|
+
} catch (error) {
|
|
8603
|
+
throw new Error(
|
|
8604
|
+
`Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error))
|
|
8605
|
+
);
|
|
8606
|
+
}
|
|
8607
|
+
if (!manifest) {
|
|
8608
|
+
if (sourceLock) {
|
|
8609
|
+
throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
|
|
8610
|
+
}
|
|
8611
|
+
candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
|
|
8612
|
+
continue;
|
|
8613
|
+
}
|
|
8614
|
+
const validated = validateLegacyManifestCandidate(manifest, {
|
|
8615
|
+
...options,
|
|
8616
|
+
candidateStateKey,
|
|
8617
|
+
graphLockDigest,
|
|
8618
|
+
graphLockPath
|
|
8619
|
+
});
|
|
8620
|
+
candidates.push({
|
|
8621
|
+
stateKey: candidateStateKey,
|
|
8622
|
+
graphLockPath,
|
|
8623
|
+
graphLockDigest,
|
|
8624
|
+
graphLock,
|
|
8625
|
+
manifest: validated,
|
|
8626
|
+
sourceLock
|
|
8627
|
+
});
|
|
8628
|
+
}
|
|
8629
|
+
return candidates;
|
|
8630
|
+
}
|
|
8631
|
+
function validateLegacyManifestCandidate(manifest, options) {
|
|
8632
|
+
if (manifest.version !== 2) {
|
|
8633
|
+
throw new Error(`Legacy target state for ${options.graphLockPath} requires an install manifest v2.`);
|
|
8634
|
+
}
|
|
8635
|
+
if (manifest.adapter !== options.adapter) {
|
|
8636
|
+
throw new Error(`Legacy target state adapter mismatch at ${options.graphLockPath}.`);
|
|
8637
|
+
}
|
|
8638
|
+
if (manifest.installationType !== options.installationType) {
|
|
8639
|
+
throw new Error(`Legacy target state installation type mismatch at ${options.graphLockPath}.`);
|
|
8640
|
+
}
|
|
8641
|
+
if (manifest.stateKey !== options.candidateStateKey) {
|
|
8642
|
+
throw new Error(`Legacy target state key mismatch at ${options.graphLockPath}.`);
|
|
8643
|
+
}
|
|
8644
|
+
if (normalizeInstallRoot2(manifest.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8645
|
+
throw new Error(`Legacy target state target root mismatch at ${options.graphLockPath}.`);
|
|
8646
|
+
}
|
|
8647
|
+
const correlated = manifest.entries.filter((entry) => entry.graphLockDigest === options.graphLockDigest);
|
|
8648
|
+
if (correlated.length === 0) {
|
|
8649
|
+
throw new Error(`Legacy install manifest graph-lock digest does not match ${options.graphLockPath}.`);
|
|
8650
|
+
}
|
|
8651
|
+
if (correlated.some((entry) => entry.workspaceOwner !== options.workspaceOwner)) {
|
|
8652
|
+
throw new Error(`Legacy target state contribution scope does not match ${options.graphLockPath}.`);
|
|
8653
|
+
}
|
|
8654
|
+
return manifest;
|
|
8655
|
+
}
|
|
8656
|
+
function migrationFromCandidate(candidate) {
|
|
8657
|
+
return {
|
|
8658
|
+
fromStateKey: candidate.manifest ? candidate.stateKey : void 0,
|
|
8659
|
+
fromGraphLockPath: candidate.graphLockPath,
|
|
8660
|
+
fromGraphLockDigest: candidate.graphLockDigest,
|
|
8661
|
+
sourceLock: candidate.manifest ? candidate.sourceLock ?? null : void 0
|
|
8662
|
+
};
|
|
8663
|
+
}
|
|
8664
|
+
function sameManifestContributions(left, right) {
|
|
8665
|
+
if (left.version !== 2) return false;
|
|
8666
|
+
return stableJson2(manifestContributionState(left)) === stableJson2(manifestContributionState(right));
|
|
8667
|
+
}
|
|
8668
|
+
function manifestContributionState(manifest) {
|
|
8669
|
+
return manifest.entries.map(({ updatedAt: _updatedAt, graphLockDigest: _graphLockDigest, ...entry }) => entry).sort((left, right) => left.path.localeCompare(right.path));
|
|
8670
|
+
}
|
|
8671
|
+
function stableJson2(value) {
|
|
8672
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
8673
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
8674
|
+
const record = value;
|
|
8675
|
+
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort((left, right) => left.localeCompare(right)).map((key) => `${JSON.stringify(key)}:${stableJson2(record[key])}`).join(",")}}`;
|
|
8676
|
+
}
|
|
8677
|
+
function normalizeInstallRoot2(path, transportKind) {
|
|
8678
|
+
return transportKind === "ssh" ? posix2.normalize(path) : resolve16(path);
|
|
8679
|
+
}
|
|
8224
8680
|
var workspaceOwnerPrefix2 = "workspace-root:";
|
|
8225
8681
|
async function assertNoForeignWorkspaceState(check) {
|
|
8226
8682
|
const planned = new Set(check.plannedPaths);
|
|
8227
|
-
if (planned.size === 0) return;
|
|
8683
|
+
if (planned.size === 0) return [];
|
|
8228
8684
|
const plannedOperations = /* @__PURE__ */ new Map();
|
|
8229
8685
|
for (const operation of check.plannedOperations) {
|
|
8230
8686
|
if (!planned.has(operation.relativeDestPath)) continue;
|
|
@@ -8233,18 +8689,22 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8233
8689
|
plannedOperations.set(operation.relativeDestPath, operations);
|
|
8234
8690
|
}
|
|
8235
8691
|
const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
|
|
8236
|
-
const root =
|
|
8692
|
+
const root = resolve16(check.workspaceRoot);
|
|
8237
8693
|
const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
|
|
8238
|
-
const
|
|
8694
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
8239
8695
|
for (const entry of manifests) {
|
|
8240
|
-
if (entry.stateKey === check.stateKey) continue;
|
|
8241
|
-
const
|
|
8696
|
+
if (entry.stateKey === check.stateKey || entry.stateKey === check.migratingStateKey) continue;
|
|
8697
|
+
const entryCounts = /* @__PURE__ */ new Map();
|
|
8698
|
+
for (const manifestEntry of entry.manifest.entries) {
|
|
8699
|
+
const owner = "workspaceOwner" in manifestEntry ? manifestEntry.workspaceOwner : void 0;
|
|
8700
|
+
if (typeof owner === "string" && owner.startsWith(workspaceOwnerPrefix2)) {
|
|
8701
|
+
entryCounts.set(owner, (entryCounts.get(owner) ?? 0) + 1);
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8242
8704
|
for (const manifestEntry of entry.manifest.entries) {
|
|
8243
8705
|
const owner = "workspaceOwner" in manifestEntry ? manifestEntry.workspaceOwner : void 0;
|
|
8244
8706
|
if (typeof owner !== "string" || !owner.startsWith(workspaceOwnerPrefix2)) continue;
|
|
8245
8707
|
if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
|
|
8246
|
-
const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
|
|
8247
|
-
bucket.entryCount += 1;
|
|
8248
8708
|
if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
|
|
8249
8709
|
check,
|
|
8250
8710
|
manifestEntry.path,
|
|
@@ -8252,16 +8712,46 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8252
8712
|
manifestEntry.mergeRemoval,
|
|
8253
8713
|
plannedOperations.get(manifestEntry.path) ?? []
|
|
8254
8714
|
)) {
|
|
8255
|
-
|
|
8715
|
+
const ownedEntry = manifestEntry;
|
|
8716
|
+
const pathCollisions = collisions.get(manifestEntry.path) ?? [];
|
|
8717
|
+
pathCollisions.push({
|
|
8718
|
+
owner,
|
|
8719
|
+
stateKey: entry.stateKey,
|
|
8720
|
+
fileName: entry.fileName,
|
|
8721
|
+
manifestRevision: entry.manifest.revision,
|
|
8722
|
+
entryCount: entryCounts.get(owner) ?? 1,
|
|
8723
|
+
entry: ownedEntry
|
|
8724
|
+
});
|
|
8725
|
+
collisions.set(manifestEntry.path, pathCollisions);
|
|
8256
8726
|
}
|
|
8257
|
-
byOwner.set(owner, bucket);
|
|
8258
8727
|
}
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8728
|
+
}
|
|
8729
|
+
const foreignByOwner = /* @__PURE__ */ new Map();
|
|
8730
|
+
const observations = [];
|
|
8731
|
+
for (const [path, pathCollisions] of collisions) {
|
|
8732
|
+
const operations = plannedOperations.get(path) ?? [];
|
|
8733
|
+
const exact = pathCollisions.length === 1 ? await exactForeignKeepOperation(check, pathCollisions[0], operations) : void 0;
|
|
8734
|
+
if (exact) {
|
|
8735
|
+
const index = check.plannedOperations.indexOf(operations[0]);
|
|
8736
|
+
if (index < 0) throw new Error(`Foreign-state reconciliation lost planned operation for ${path}.`);
|
|
8737
|
+
check.plannedOperations[index] = exact.operation;
|
|
8738
|
+
observations.push(exact.observation);
|
|
8739
|
+
continue;
|
|
8740
|
+
}
|
|
8741
|
+
for (const collision of pathCollisions) {
|
|
8742
|
+
const key = `${collision.owner}\0${collision.fileName}`;
|
|
8743
|
+
const bucket = foreignByOwner.get(key) ?? {
|
|
8744
|
+
owner: collision.owner,
|
|
8745
|
+
fileName: collision.fileName,
|
|
8746
|
+
entryCount: collision.entryCount,
|
|
8747
|
+
collidingPaths: []
|
|
8748
|
+
};
|
|
8749
|
+
if (!bucket.collidingPaths.includes(path)) bucket.collidingPaths.push(path);
|
|
8750
|
+
foreignByOwner.set(key, bucket);
|
|
8262
8751
|
}
|
|
8263
8752
|
}
|
|
8264
|
-
|
|
8753
|
+
const foreign = [...foreignByOwner.values()];
|
|
8754
|
+
if (foreign.length === 0) return observations.sort((left, right) => left.path.localeCompare(right.path));
|
|
8265
8755
|
throw new Error([
|
|
8266
8756
|
`Refusing to plan ${check.adapter} at ${check.installRoot}.`,
|
|
8267
8757
|
"This runtime root already carries Agentwheel state owned by another workspace, at paths this run would install:",
|
|
@@ -8275,6 +8765,36 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8275
8765
|
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."
|
|
8276
8766
|
].join("\n"));
|
|
8277
8767
|
}
|
|
8768
|
+
async function exactForeignKeepOperation(check, collision, operations) {
|
|
8769
|
+
if (operations.length !== 1) return void 0;
|
|
8770
|
+
const operation = operations[0];
|
|
8771
|
+
const entry = collision.entry;
|
|
8772
|
+
if (operation.semanticPlugin || operation.programmaticOperation || operation.mergeStrategy || operation.mode || entry.semanticPlugin || entry.mergeStrategy || entry.mode) return void 0;
|
|
8773
|
+
if (!operation.desiredHash || operation.artifactType !== entry.artifactType || operation.artifactName !== entry.artifactName || operation.kind !== entry.kind || operation.desiredHash !== entry.sourceHash) return void 0;
|
|
8774
|
+
const path = join33(check.installRoot, entry.path);
|
|
8775
|
+
if (!await check.transport.pathExists(path)) return void 0;
|
|
8776
|
+
const currentHash = await check.transport.hashPath(path);
|
|
8777
|
+
if (currentHash !== entry.hash || currentHash !== operation.desiredHash || operation.currentHash !== void 0 && operation.currentHash !== currentHash) return void 0;
|
|
8778
|
+
return {
|
|
8779
|
+
operation: {
|
|
8780
|
+
...operation,
|
|
8781
|
+
action: "keep",
|
|
8782
|
+
currentHash,
|
|
8783
|
+
manifestHash: entry.hash,
|
|
8784
|
+
reason: `exact foreign artifact owned by ${collision.owner}; kept outside workspace ${check.workspaceOwner}`,
|
|
8785
|
+
preserveInManifest: false
|
|
8786
|
+
},
|
|
8787
|
+
observation: {
|
|
8788
|
+
stateKey: collision.stateKey,
|
|
8789
|
+
fileName: collision.fileName,
|
|
8790
|
+
manifestRevision: collision.manifestRevision,
|
|
8791
|
+
workspaceOwner: collision.owner,
|
|
8792
|
+
path: entry.path,
|
|
8793
|
+
graphNodeId: entry.graphNodeId,
|
|
8794
|
+
logicalSelector: entry.logicalSelector
|
|
8795
|
+
}
|
|
8796
|
+
};
|
|
8797
|
+
}
|
|
8278
8798
|
async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
|
|
8279
8799
|
if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
|
|
8280
8800
|
const operation = operations[0];
|
|
@@ -8446,7 +8966,7 @@ function targetLabel(target) {
|
|
|
8446
8966
|
}
|
|
8447
8967
|
|
|
8448
8968
|
// src/runtime/target.ts
|
|
8449
|
-
import { basename as
|
|
8969
|
+
import { basename as basename19, dirname as dirname18, join as join34, resolve as resolve17 } from "path";
|
|
8450
8970
|
var runtimeMarkers = [
|
|
8451
8971
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
8452
8972
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -8455,9 +8975,9 @@ var runtimeMarkers = [
|
|
|
8455
8975
|
{ adapter: "copilot", dirs: [".github"] }
|
|
8456
8976
|
];
|
|
8457
8977
|
async function resolveRuntimeTarget(request = {}) {
|
|
8458
|
-
const cwd =
|
|
8978
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8459
8979
|
if (request.targetRoot) {
|
|
8460
|
-
const targetRoot =
|
|
8980
|
+
const targetRoot = resolve17(request.targetRoot);
|
|
8461
8981
|
return {
|
|
8462
8982
|
adapter: request.adapter ?? "openclaw",
|
|
8463
8983
|
installationType: request.installationType,
|
|
@@ -8489,7 +9009,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
8489
9009
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
8490
9010
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
8491
9011
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
8492
|
-
const cwd =
|
|
9012
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8493
9013
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
8494
9014
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8495
9015
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
|
|
@@ -8499,8 +9019,8 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
8499
9019
|
return targets;
|
|
8500
9020
|
}
|
|
8501
9021
|
async function resolveProfileRuntimeTargets(request) {
|
|
8502
|
-
const cwd =
|
|
8503
|
-
const workspaceRoot = request.targetRoot ?
|
|
9022
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
9023
|
+
const workspaceRoot = request.targetRoot ? resolve17(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
8504
9024
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8505
9025
|
const profile = config.profiles[request.profile];
|
|
8506
9026
|
if (!profile) {
|
|
@@ -8566,12 +9086,12 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
8566
9086
|
return unique[0];
|
|
8567
9087
|
}
|
|
8568
9088
|
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
8569
|
-
const root =
|
|
9089
|
+
const root = resolve17(cwd);
|
|
8570
9090
|
const matches = [];
|
|
8571
9091
|
for (const marker of runtimeMarkers) {
|
|
8572
9092
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
8573
9093
|
for (const dir of marker.dirs) {
|
|
8574
|
-
if (
|
|
9094
|
+
if (basename19(root) === dir) {
|
|
8575
9095
|
matches.push({ adapter: marker.adapter, targetRoot: dirname18(root) });
|
|
8576
9096
|
} else if (await pathExists(join34(root, dir))) {
|
|
8577
9097
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -8615,9 +9135,9 @@ function dedupeTargets(matches) {
|
|
|
8615
9135
|
return [...byKey.values()];
|
|
8616
9136
|
}
|
|
8617
9137
|
function runtimeScanRoot(request) {
|
|
8618
|
-
const root =
|
|
9138
|
+
const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
8619
9139
|
if (request.targetRoot) return root;
|
|
8620
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
9140
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename19(root))) ? dirname18(root) : root;
|
|
8621
9141
|
}
|
|
8622
9142
|
|
|
8623
9143
|
// src/lifecycle/profile.ts
|
|
@@ -9004,9 +9524,9 @@ function normalizeVersion(version) {
|
|
|
9004
9524
|
|
|
9005
9525
|
// src/model/package-validate.ts
|
|
9006
9526
|
import { stat as stat12 } from "fs/promises";
|
|
9007
|
-
import { resolve as
|
|
9527
|
+
import { resolve as resolve18 } from "path";
|
|
9008
9528
|
async function validatePackage(root) {
|
|
9009
|
-
const packageRoot =
|
|
9529
|
+
const packageRoot = resolve18(root);
|
|
9010
9530
|
const findings = [];
|
|
9011
9531
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
9012
9532
|
if (!manifestPath) {
|
|
@@ -9107,7 +9627,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
9107
9627
|
try {
|
|
9108
9628
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
9109
9629
|
if (isCrossPackageSelector(selector)) return;
|
|
9110
|
-
const full =
|
|
9630
|
+
const full = resolve18(packageRoot, selector);
|
|
9111
9631
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
9112
9632
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
9113
9633
|
return;
|
|
@@ -9161,10 +9681,10 @@ function isCrossPackageSelector(value) {
|
|
|
9161
9681
|
|
|
9162
9682
|
// src/model/package-migrate.ts
|
|
9163
9683
|
import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
|
|
9164
|
-
import { join as join37, resolve as
|
|
9684
|
+
import { join as join37, resolve as resolve19 } from "path";
|
|
9165
9685
|
import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
|
|
9166
9686
|
async function migratePackageManifest(root) {
|
|
9167
|
-
const packageRoot =
|
|
9687
|
+
const packageRoot = resolve19(root);
|
|
9168
9688
|
for (const name of openPackManifestNames) {
|
|
9169
9689
|
const path = join37(packageRoot, name);
|
|
9170
9690
|
if (await pathExists(path)) {
|
|
@@ -9226,7 +9746,7 @@ function resolveCliVersion() {
|
|
|
9226
9746
|
}
|
|
9227
9747
|
|
|
9228
9748
|
// src/lifecycle/ownership.ts
|
|
9229
|
-
import { resolve as
|
|
9749
|
+
import { resolve as resolve20 } from "path";
|
|
9230
9750
|
async function planArtifactOwnershipHandoff(request) {
|
|
9231
9751
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
9232
9752
|
}
|
|
@@ -9352,8 +9872,8 @@ function containedArtifactPath(targetRoot, relativePath) {
|
|
|
9352
9872
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
9353
9873
|
throw new Error(`Unsafe managed artifact path: ${relativePath}`);
|
|
9354
9874
|
}
|
|
9355
|
-
const root =
|
|
9356
|
-
const candidate =
|
|
9875
|
+
const root = resolve20(targetRoot);
|
|
9876
|
+
const candidate = resolve20(root, relativePath);
|
|
9357
9877
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
9358
9878
|
throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
|
|
9359
9879
|
}
|
|
@@ -9386,7 +9906,7 @@ function assertManifestIdentity(manifest, request) {
|
|
|
9386
9906
|
|
|
9387
9907
|
// src/lifecycle/ownership-retire-stale.ts
|
|
9388
9908
|
import { createHash as createHash9 } from "crypto";
|
|
9389
|
-
import { resolve as
|
|
9909
|
+
import { resolve as resolve21 } from "path";
|
|
9390
9910
|
async function planRetireStaleOwnership(request) {
|
|
9391
9911
|
return observe(request, request.transport ?? localTransport);
|
|
9392
9912
|
}
|
|
@@ -9519,7 +10039,7 @@ async function observe(request, transport) {
|
|
|
9519
10039
|
return { ...withoutDigest, planDigest: digest(withoutDigest) };
|
|
9520
10040
|
}
|
|
9521
10041
|
function normalizeRequest(request) {
|
|
9522
|
-
const targetRoot =
|
|
10042
|
+
const targetRoot = resolve21(request.targetRoot);
|
|
9523
10043
|
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, request.installationType, "source");
|
|
9524
10044
|
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
|
|
9525
10045
|
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
@@ -9530,8 +10050,8 @@ function normalizeRequest(request) {
|
|
|
9530
10050
|
installationType: request.installationType.trim(),
|
|
9531
10051
|
sourceStateKey,
|
|
9532
10052
|
destinationStateKey,
|
|
9533
|
-
fromWorkspaceRoot:
|
|
9534
|
-
toWorkspaceRoot:
|
|
10053
|
+
fromWorkspaceRoot: resolve21(request.fromWorkspaceRoot),
|
|
10054
|
+
toWorkspaceRoot: resolve21(request.toWorkspaceRoot),
|
|
9535
10055
|
toFleetId: request.toFleetId.trim()
|
|
9536
10056
|
};
|
|
9537
10057
|
}
|
|
@@ -9552,7 +10072,7 @@ async function requireManifest(request, stateKey, transport, label) {
|
|
|
9552
10072
|
installationType: manifest.installationType,
|
|
9553
10073
|
stateKey: manifest.stateKey
|
|
9554
10074
|
});
|
|
9555
|
-
if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType ||
|
|
10075
|
+
if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType || resolve21(manifest.targetRoot) !== request.targetRoot || actualStateKey !== stateKey) {
|
|
9556
10076
|
throw new Error(`${label} install manifest identity does not match the requested adapter, target, installation type, and state key.`);
|
|
9557
10077
|
}
|
|
9558
10078
|
return manifest;
|
|
@@ -9633,10 +10153,262 @@ function canonicalJson(value) {
|
|
|
9633
10153
|
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
9634
10154
|
}
|
|
9635
10155
|
|
|
10156
|
+
// src/lifecycle/legacy-recovery-plan.ts
|
|
10157
|
+
import { createHash as createHash10 } from "crypto";
|
|
10158
|
+
import { readdir as readdir10 } from "fs/promises";
|
|
10159
|
+
import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
|
|
10160
|
+
async function inventoryHistoricalLocks(fleetRoot) {
|
|
10161
|
+
const root = join39(fleetRoot, ".agentwheel", "locks");
|
|
10162
|
+
const found = [];
|
|
10163
|
+
async function visit(dir) {
|
|
10164
|
+
for (const entry of await readdir10(dir, { withFileTypes: true }).catch((error) => {
|
|
10165
|
+
if (error.code === "ENOENT") return [];
|
|
10166
|
+
throw error;
|
|
10167
|
+
})) {
|
|
10168
|
+
const path = join39(dir, entry.name);
|
|
10169
|
+
if (entry.isDirectory()) await visit(path);
|
|
10170
|
+
else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) {
|
|
10171
|
+
const lock = await readGraphLock(path);
|
|
10172
|
+
found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
|
|
10173
|
+
}
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10176
|
+
await visit(root);
|
|
10177
|
+
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
10178
|
+
}
|
|
10179
|
+
async function planLegacyRecovery(input) {
|
|
10180
|
+
const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
|
|
10181
|
+
assertCompleteGraphPlan(graphPlan);
|
|
10182
|
+
const binding = {
|
|
10183
|
+
targetLockDir: dirname21(graphPlan.graphLockPath),
|
|
10184
|
+
stableLockPath: graphPlan.graphLockPath,
|
|
10185
|
+
stableStateKey: graphPlan.plan.stateKey,
|
|
10186
|
+
currentFingerprint: graphPlan.targetFingerprint,
|
|
10187
|
+
expectedOwner: workspaceOwnerForRoot(input.workspaceRoot, input.fleetId),
|
|
10188
|
+
installRoot,
|
|
10189
|
+
adapter,
|
|
10190
|
+
installationType,
|
|
10191
|
+
fleetId: input.fleetId,
|
|
10192
|
+
explicitStateKey: input.explicitStateKey,
|
|
10193
|
+
transportKind: transport.kind
|
|
10194
|
+
};
|
|
10195
|
+
const manifests = await listInstallManifests(installRoot, adapter, transport);
|
|
10196
|
+
const paths = /* @__PURE__ */ new Set();
|
|
10197
|
+
const desired = /* @__PURE__ */ new Map();
|
|
10198
|
+
const nodeHashes = new Map(graphPlan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node.sourceHash]));
|
|
10199
|
+
for (const op of graphPlan.plan.operations) {
|
|
10200
|
+
if (!op.desiredHash || op.action === "remove" || op.action === "keep") continue;
|
|
10201
|
+
const path = relative8(installRoot, op.destPath);
|
|
10202
|
+
if (path.startsWith("..") || path === "") throw new Error(`Planner destination escapes target: ${op.destPath}`);
|
|
10203
|
+
paths.add(path);
|
|
10204
|
+
const items = desired.get(path) ?? [];
|
|
10205
|
+
items.push(op);
|
|
10206
|
+
desired.set(path, items);
|
|
10207
|
+
}
|
|
10208
|
+
for (const item of manifests) for (const entry of item.manifest.entries) paths.add(entry.path);
|
|
10209
|
+
const resolutionWarnings = graphPlan.warnings.filter((warning) => !/^skip (?:artifact|dependency) .+ \((?:selected but )?not targeted: runtimes=\[[^\]]+\]\)$/.test(warning));
|
|
10210
|
+
const complete = resolutionWarnings.length === 0;
|
|
10211
|
+
const output = [];
|
|
10212
|
+
for (const path of [...paths].sort()) {
|
|
10213
|
+
const allClaims = manifests.flatMap((item) => item.manifest.entries.filter((entry) => entry.path === path).map((entry) => claimFor(item, entry, historicalLocks, binding)));
|
|
10214
|
+
const claims = allClaims.filter((claim) => claim.scope !== "foreign");
|
|
10215
|
+
const foreignClaims = allClaims.filter((claim) => claim.scope === "foreign");
|
|
10216
|
+
const current = desired.get(path) ?? [];
|
|
10217
|
+
const currentHashes = [...new Set(current.map((item) => item.desiredHash))];
|
|
10218
|
+
const currentSourceHashes = [...new Set(current.map((item) => item.graphNodeId ? nodeHashes.get(item.graphNodeId) : void 0).filter((value) => Boolean(value)))].sort();
|
|
10219
|
+
const currentNodeIds = [...new Set(current.map((item) => item.graphNodeId).filter((value) => Boolean(value)))].sort();
|
|
10220
|
+
const currentSelectors = [...new Set(current.map((item) => item.logicalSelector).filter((value) => Boolean(value)))].sort();
|
|
10221
|
+
const currentOwners = [...new Set(current.flatMap((item) => item.owners ?? []))].sort();
|
|
10222
|
+
const livePath = resolve22(installRoot, path);
|
|
10223
|
+
if (relative8(installRoot, livePath).startsWith("..")) throw new Error(`Manifest path escapes target: ${path}`);
|
|
10224
|
+
const liveHash = await transport.pathExists(livePath) ? await transport.hashPath(livePath) : null;
|
|
10225
|
+
const reasons = [];
|
|
10226
|
+
const currentResults = await Promise.all(current.map((op) => verifyCurrentOperation(op, liveHash, transport)));
|
|
10227
|
+
const currentGraphStatus = current.length === 0 ? "absent" : currentResults.every((result) => result === "exact-match") ? "exact-match" : currentResults.some((result) => result === "unverifiable") ? "unverifiable" : "mismatch";
|
|
10228
|
+
if (currentHashes.length > 1 && current.some((op) => !op.mergeStrategy && op.mode !== "managed-block")) {
|
|
10229
|
+
reasons.push("current graph has divergent hashes for the destination");
|
|
10230
|
+
}
|
|
10231
|
+
if (currentGraphStatus === "mismatch") reasons.push("live contribution differs from current source");
|
|
10232
|
+
if (currentGraphStatus === "unverifiable") reasons.push("current contribution could not be verified");
|
|
10233
|
+
if (current.some((op) => op.action === "conflict" || op.action === "drift")) reasons.push("install planner has a blocking operation");
|
|
10234
|
+
if (!complete) reasons.push("current source graph has resolution warnings; completeness unproven");
|
|
10235
|
+
if (claims.some((claim) => claim.scope === "unbound" || claim.evidence.length)) reasons.push("historical provenance unresolved");
|
|
10236
|
+
if (foreignClaims.length) reasons.push("foreign manifest claim conflicts with destination");
|
|
10237
|
+
if (new Set(claims.map((claim) => `${claim.owner}:${claim.hash}:${claim.sourceHash}:${claim.graphLockDigest ?? ""}`)).size > 1) {
|
|
10238
|
+
reasons.push("competing same-target claims diverge");
|
|
10239
|
+
}
|
|
10240
|
+
if (current.length === 0 && claims.length === 0) reasons.push("no same-target historical claim");
|
|
10241
|
+
if (current.length === 0) {
|
|
10242
|
+
for (const claim of claims) {
|
|
10243
|
+
if (claim.scope !== "same-target") continue;
|
|
10244
|
+
const item = manifests.find((manifest) => manifest.path === claim.manifestPath);
|
|
10245
|
+
const entry = item.manifest.entries.find((candidate) => candidate.path === path && candidate.sourceHash === claim.sourceHash);
|
|
10246
|
+
if (!await verifyHistoricalEntry(entry, livePath, liveHash, transport)) {
|
|
10247
|
+
reasons.push("live contribution differs from same-target historical claim");
|
|
10248
|
+
break;
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
}
|
|
10252
|
+
const category = reasons.length ? "unresolved" : currentGraphStatus === "exact-match" ? "current-graph-exact-match" : "retirement-candidate";
|
|
10253
|
+
output.push({
|
|
10254
|
+
path,
|
|
10255
|
+
category,
|
|
10256
|
+
reasons,
|
|
10257
|
+
currentHash: currentHashes.length === 1 ? currentHashes[0] : null,
|
|
10258
|
+
currentHashes: currentHashes.sort(),
|
|
10259
|
+
currentSourceHashes,
|
|
10260
|
+
currentNodeIds,
|
|
10261
|
+
currentSelectors,
|
|
10262
|
+
currentOwners,
|
|
10263
|
+
currentGraphStatus,
|
|
10264
|
+
liveHash,
|
|
10265
|
+
claims,
|
|
10266
|
+
foreignClaims
|
|
10267
|
+
});
|
|
10268
|
+
}
|
|
10269
|
+
const counts = { "current-graph-exact-match": 0, "retirement-candidate": 0, unresolved: 0 };
|
|
10270
|
+
const currentGraphCounts = { "exact-match": 0, mismatch: 0, absent: 0, unverifiable: 0 };
|
|
10271
|
+
for (const path of output) counts[path.category]++;
|
|
10272
|
+
for (const path of output) currentGraphCounts[path.currentGraphStatus]++;
|
|
10273
|
+
return {
|
|
10274
|
+
targetRoot: installRoot,
|
|
10275
|
+
targetKey: targetKey2,
|
|
10276
|
+
adapter,
|
|
10277
|
+
installationType,
|
|
10278
|
+
currentGraphLockDigest: graphPlan.graphLockDigest,
|
|
10279
|
+
roots: graphPlan.graph.roots.map((root) => ({ rootId: root.rootId, source: root.source, graphNodeId: root.graphNodeId, selected: root.selected })),
|
|
10280
|
+
resolutionWarnings,
|
|
10281
|
+
complete,
|
|
10282
|
+
paths: output,
|
|
10283
|
+
counts,
|
|
10284
|
+
currentGraphCounts
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
function claimFor(item, entry, locks, binding) {
|
|
10288
|
+
const digest2 = "graphLockDigest" in entry ? entry.graphLockDigest : void 0;
|
|
10289
|
+
const nodeId = "graphNodeId" in entry ? entry.graphNodeId : void 0;
|
|
10290
|
+
const selector = "logicalSelector" in entry ? entry.logicalSelector : void 0;
|
|
10291
|
+
const matches = locks.filter((candidate) => candidate.digest === digest2);
|
|
10292
|
+
const evidence = [];
|
|
10293
|
+
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
10294
|
+
const sameRoot = binding.transportKind === "ssh" ? posix3.normalize(item.manifest.targetRoot) === posix3.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
|
|
10295
|
+
const inTargetDir = matches.filter((candidate) => resolve22(dirname21(candidate.path)) === resolve22(binding.targetLockDir));
|
|
10296
|
+
const boundLocks = inTargetDir.filter((candidate) => lockBindsState(candidate, item, binding));
|
|
10297
|
+
if (owner !== binding.expectedOwner) evidence.push("workspace owner differs from selected Fleet target");
|
|
10298
|
+
if (item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType) evidence.push("installation type differs or manifest is legacy v1");
|
|
10299
|
+
if (!sameRoot) evidence.push("manifest target root differs from selected Fleet target");
|
|
10300
|
+
if (item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter)) {
|
|
10301
|
+
evidence.push("manifest state key or adapter differs from filename");
|
|
10302
|
+
}
|
|
10303
|
+
if (!digest2) evidence.push("historical graph-lock digest missing");
|
|
10304
|
+
else if (matches.length === 0) evidence.push("historical graph-lock digest has no matching lock");
|
|
10305
|
+
if (matches.length && !inTargetDir.length) evidence.push("claim belongs to another Fleet target graph");
|
|
10306
|
+
if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
|
|
10307
|
+
if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
|
|
10308
|
+
evidence.push("historical graph-lock selector/node does not cover claim");
|
|
10309
|
+
} else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector && artifact.hash === entry.sourceHash))) {
|
|
10310
|
+
evidence.push("historical graph-lock artifact hash differs from manifest source hash");
|
|
10311
|
+
}
|
|
10312
|
+
const foreign = owner !== binding.expectedOwner || !sameRoot || item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType || item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter) || matches.length > 0 && inTargetDir.length === 0;
|
|
10313
|
+
const scope = foreign ? "foreign" : boundLocks.length && evidence.length === 0 ? "same-target" : "unbound";
|
|
10314
|
+
return {
|
|
10315
|
+
scope,
|
|
10316
|
+
manifestPath: item.path,
|
|
10317
|
+
stateKey: item.stateKey,
|
|
10318
|
+
manifestStateKey: item.manifest.version === 2 ? item.manifest.stateKey : void 0,
|
|
10319
|
+
installationType: item.manifest.version === 2 ? item.manifest.installationType : void 0,
|
|
10320
|
+
targetRoot: item.manifest.targetRoot,
|
|
10321
|
+
owner,
|
|
10322
|
+
hash: entry.hash,
|
|
10323
|
+
sourceHash: entry.sourceHash,
|
|
10324
|
+
graphLockDigest: digest2,
|
|
10325
|
+
graphNodeId: nodeId,
|
|
10326
|
+
logicalSelector: selector,
|
|
10327
|
+
owners: "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"],
|
|
10328
|
+
lockPaths: matches.map((candidate) => candidate.path),
|
|
10329
|
+
evidence
|
|
10330
|
+
};
|
|
10331
|
+
}
|
|
10332
|
+
function lockBindsState(candidate, manifest, binding) {
|
|
10333
|
+
const name = basename20(candidate.path);
|
|
10334
|
+
if (!name.endsWith(".graph-lock.json")) return false;
|
|
10335
|
+
const fingerprint = name.slice(0, -".graph-lock.json".length);
|
|
10336
|
+
if (!/^[a-f0-9]{64}$/u.test(fingerprint)) return false;
|
|
10337
|
+
const stable = resolve22(candidate.path) === resolve22(binding.stableLockPath);
|
|
10338
|
+
if (stable) {
|
|
10339
|
+
return manifest.stateKey === binding.stableStateKey && candidate.lock.canonical.targetFingerprint === binding.currentFingerprint;
|
|
10340
|
+
}
|
|
10341
|
+
return candidate.lock.canonical.targetFingerprint === fingerprint && manifest.stateKey === stateKeyFor(binding.adapter, {
|
|
10342
|
+
installationType: binding.installationType,
|
|
10343
|
+
stateKey: binding.explicitStateKey,
|
|
10344
|
+
targetFingerprint: fingerprint,
|
|
10345
|
+
fleetId: binding.fleetId
|
|
10346
|
+
});
|
|
10347
|
+
}
|
|
10348
|
+
async function verifyCurrentOperation(op, liveHash, transport) {
|
|
10349
|
+
if (liveHash === null || !op.desiredHash) return "mismatch";
|
|
10350
|
+
if (op.semanticPlugin || op.programmaticOperation) return "unverifiable";
|
|
10351
|
+
if (op.mode === "managed-block") {
|
|
10352
|
+
const selector = managedInstructionSelector(op.logicalSelector, op.artifactType, op.artifactName);
|
|
10353
|
+
const state = await readManagedInstructionBlockState(op.destPath, selector, transport);
|
|
10354
|
+
return state.hasBlock && !state.drifted && state.hash === op.desiredHash ? "exact-match" : "mismatch";
|
|
10355
|
+
}
|
|
10356
|
+
if (op.mergeStrategy) {
|
|
10357
|
+
if (!op.sourcePath) return "unverifiable";
|
|
10358
|
+
try {
|
|
10359
|
+
await assertMergedSourceContribution(op.sourcePath, op.mergeStrategy, await transport.readFile(op.destPath));
|
|
10360
|
+
return "exact-match";
|
|
10361
|
+
} catch {
|
|
10362
|
+
return "mismatch";
|
|
10363
|
+
}
|
|
10364
|
+
}
|
|
10365
|
+
return liveHash === op.desiredHash ? "exact-match" : "mismatch";
|
|
10366
|
+
}
|
|
10367
|
+
async function verifyHistoricalEntry(entry, livePath, liveHash, transport) {
|
|
10368
|
+
if (liveHash === null || entry.semanticPlugin) return false;
|
|
10369
|
+
if (entry.mode === "managed-block") {
|
|
10370
|
+
const selector = managedInstructionSelector(
|
|
10371
|
+
"logicalSelector" in entry ? entry.logicalSelector : void 0,
|
|
10372
|
+
entry.artifactType,
|
|
10373
|
+
entry.artifactName
|
|
10374
|
+
);
|
|
10375
|
+
const state = await readManagedInstructionBlockState(livePath, selector, transport);
|
|
10376
|
+
return state.hasBlock && !state.drifted && state.hash === entry.hash;
|
|
10377
|
+
}
|
|
10378
|
+
if (entry.mergeStrategy) {
|
|
10379
|
+
if (!hasMergeRemovalContent(entry.mergeRemoval)) return false;
|
|
10380
|
+
try {
|
|
10381
|
+
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, await transport.readFile(livePath));
|
|
10382
|
+
return true;
|
|
10383
|
+
} catch {
|
|
10384
|
+
return false;
|
|
10385
|
+
}
|
|
10386
|
+
}
|
|
10387
|
+
return entry.hash === liveHash;
|
|
10388
|
+
}
|
|
10389
|
+
function assertCompleteGraphPlan(plan) {
|
|
10390
|
+
const sourceNodes = plan.graph.nodes.filter((node) => node.selected.length > 0);
|
|
10391
|
+
const lockedNodes = new Map(plan.bundle.graphLock.canonical.nodes.map((node) => [node.id, node]));
|
|
10392
|
+
for (const node of sourceNodes) {
|
|
10393
|
+
const locked = lockedNodes.get(node.id);
|
|
10394
|
+
if (!locked || locked.sourceHash !== node.sourceHash || node.selected.some((selector) => !locked.selected.includes(selector))) {
|
|
10395
|
+
throw new Error(`Current source graph omitted selected node or dependency: ${node.id}`);
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
for (const edge of plan.graph.edges) {
|
|
10399
|
+
if (!plan.bundle.graphLock.canonical.edges.some((locked) => locked.from === edge.from && locked.to === edge.to && locked.alias === edge.alias)) {
|
|
10400
|
+
throw new Error(`Current source graph omitted dependency edge: ${edge.from} -> ${edge.to}`);
|
|
10401
|
+
}
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
function sha2564(value) {
|
|
10405
|
+
return createHash10("sha256").update(value).digest("hex");
|
|
10406
|
+
}
|
|
10407
|
+
|
|
9636
10408
|
// src/version/policy.ts
|
|
9637
10409
|
import { execFile as execFile3 } from "child_process";
|
|
9638
10410
|
import { readFile as readFile23 } from "fs/promises";
|
|
9639
|
-
import { join as
|
|
10411
|
+
import { join as join40, resolve as resolve23 } from "path";
|
|
9640
10412
|
import { promisify as promisify3 } from "util";
|
|
9641
10413
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
9642
10414
|
import { z as z5 } from "zod";
|
|
@@ -9730,7 +10502,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9730
10502
|
if (tagged.length > 0) return tagged;
|
|
9731
10503
|
}
|
|
9732
10504
|
if (driverName === "local") {
|
|
9733
|
-
const root =
|
|
10505
|
+
const root = resolve23(workspaceRoot, pkg.source);
|
|
9734
10506
|
const manifest2 = await readPackageManifest(root);
|
|
9735
10507
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
9736
10508
|
try {
|
|
@@ -9745,7 +10517,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9745
10517
|
}
|
|
9746
10518
|
const driver = getSourceDriver(driverName);
|
|
9747
10519
|
const resolved = await driver.resolve(pkg.source, {
|
|
9748
|
-
cacheRoot:
|
|
10520
|
+
cacheRoot: join40(workspaceRoot, ".agentwheel", "cache"),
|
|
9749
10521
|
mode: "tracking",
|
|
9750
10522
|
ref: pkg.requestedRef
|
|
9751
10523
|
});
|
|
@@ -9839,7 +10611,7 @@ function gitUrlFromSource(source) {
|
|
|
9839
10611
|
throw new Error(`Version discovery does not support Git source: ${source}`);
|
|
9840
10612
|
}
|
|
9841
10613
|
function versionCachePath(workspaceRoot) {
|
|
9842
|
-
return
|
|
10614
|
+
return join40(workspaceRoot, ".agentwheel", "cache", "version-index.json");
|
|
9843
10615
|
}
|
|
9844
10616
|
async function readVersionCache(path) {
|
|
9845
10617
|
if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
|
|
@@ -9853,7 +10625,7 @@ async function readVersionCache(path) {
|
|
|
9853
10625
|
// src/profile/members.ts
|
|
9854
10626
|
import { execFile as execFile4 } from "child_process";
|
|
9855
10627
|
import { readFile as readFile24 } from "fs/promises";
|
|
9856
|
-
import { join as
|
|
10628
|
+
import { join as join41, resolve as resolve24 } from "path";
|
|
9857
10629
|
import { promisify as promisify4 } from "util";
|
|
9858
10630
|
import { z as z7 } from "zod";
|
|
9859
10631
|
|
|
@@ -10028,7 +10800,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
10028
10800
|
let stderr = "";
|
|
10029
10801
|
try {
|
|
10030
10802
|
if (member.transport === "local") {
|
|
10031
|
-
const workspace =
|
|
10803
|
+
const workspace = resolve24(parentWorkspace, member.workspace);
|
|
10032
10804
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
10033
10805
|
cwd: workspace,
|
|
10034
10806
|
env: env2,
|
|
@@ -10074,7 +10846,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
10074
10846
|
process.execPath,
|
|
10075
10847
|
[process.argv[1], ...memberCommandArgs(member, args)],
|
|
10076
10848
|
{
|
|
10077
|
-
cwd:
|
|
10849
|
+
cwd: resolve24(parentWorkspace, member.workspace),
|
|
10078
10850
|
env: env2,
|
|
10079
10851
|
maxBuffer: 20 * 1024 * 1024
|
|
10080
10852
|
}
|
|
@@ -10154,7 +10926,7 @@ function memberFailure(member, health, error) {
|
|
|
10154
10926
|
};
|
|
10155
10927
|
}
|
|
10156
10928
|
function memberCachePath(workspaceRoot, profileName, memberId) {
|
|
10157
|
-
return
|
|
10929
|
+
return join41(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
|
|
10158
10930
|
}
|
|
10159
10931
|
async function readMemberCache(path) {
|
|
10160
10932
|
if (!await pathExists(path)) return void 0;
|
|
@@ -10180,7 +10952,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
10180
10952
|
}
|
|
10181
10953
|
}
|
|
10182
10954
|
function compositeKey(workspaceRoot, profileName) {
|
|
10183
|
-
return `${
|
|
10955
|
+
return `${resolve24(workspaceRoot)}#${profileName}`;
|
|
10184
10956
|
}
|
|
10185
10957
|
|
|
10186
10958
|
// src/status/repository.ts
|
|
@@ -10227,10 +10999,10 @@ function valueAfter(lines, prefix) {
|
|
|
10227
10999
|
}
|
|
10228
11000
|
|
|
10229
11001
|
// src/catalogue/client.ts
|
|
10230
|
-
import { createHash as
|
|
11002
|
+
import { createHash as createHash11 } from "crypto";
|
|
10231
11003
|
import { readFile as readFile25, rm as rm8 } from "fs/promises";
|
|
10232
11004
|
import { homedir as homedir9 } from "os";
|
|
10233
|
-
import { join as
|
|
11005
|
+
import { join as join42 } from "path";
|
|
10234
11006
|
|
|
10235
11007
|
// src/model/catalogue.ts
|
|
10236
11008
|
import { z as z8 } from "zod";
|
|
@@ -10502,18 +11274,18 @@ var CatalogueClient = class {
|
|
|
10502
11274
|
}
|
|
10503
11275
|
return {
|
|
10504
11276
|
value: schema.parse(value),
|
|
10505
|
-
digest:
|
|
11277
|
+
digest: createHash11("sha256").update(bytes).digest("hex")
|
|
10506
11278
|
};
|
|
10507
11279
|
}
|
|
10508
11280
|
};
|
|
10509
11281
|
function defaultCatalogueCachePath() {
|
|
10510
|
-
return
|
|
11282
|
+
return join42(homedir9(), ".agentwheel", "catalogue-cache.json");
|
|
10511
11283
|
}
|
|
10512
11284
|
function sameSources2(a, b) {
|
|
10513
11285
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
10514
11286
|
}
|
|
10515
11287
|
function catalogueContentHash(enriched, vercel) {
|
|
10516
|
-
return
|
|
11288
|
+
return createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
|
|
10517
11289
|
}
|
|
10518
11290
|
|
|
10519
11291
|
// src/search/index.ts
|
|
@@ -10875,9 +11647,9 @@ function includesToken(normalizedText, token) {
|
|
|
10875
11647
|
}
|
|
10876
11648
|
|
|
10877
11649
|
// src/semantic/index.ts
|
|
10878
|
-
import { createHash as
|
|
11650
|
+
import { createHash as createHash12 } from "crypto";
|
|
10879
11651
|
import { homedir as homedir10 } from "os";
|
|
10880
|
-
import { join as
|
|
11652
|
+
import { join as join43 } from "path";
|
|
10881
11653
|
import { env, pipeline } from "@huggingface/transformers";
|
|
10882
11654
|
var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
|
|
10883
11655
|
var CONTRACT = {
|
|
@@ -10965,13 +11737,13 @@ var SemanticSearchClient = class {
|
|
|
10965
11737
|
if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
|
|
10966
11738
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
10967
11739
|
if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
|
|
10968
|
-
const digest2 =
|
|
11740
|
+
const digest2 = createHash12("sha256").update(bytes).digest("hex");
|
|
10969
11741
|
if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
|
|
10970
11742
|
return bytes;
|
|
10971
11743
|
}
|
|
10972
11744
|
};
|
|
10973
11745
|
async function embedQuery(query) {
|
|
10974
|
-
env.cacheDir =
|
|
11746
|
+
env.cacheDir = join43(homedir10(), ".agentwheel", "semantic-models");
|
|
10975
11747
|
const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
|
|
10976
11748
|
revision: CONTRACT.model.revision,
|
|
10977
11749
|
dtype: CONTRACT.model.dtype,
|
|
@@ -11055,9 +11827,9 @@ function ensureTrailingSlash(value) {
|
|
|
11055
11827
|
}
|
|
11056
11828
|
|
|
11057
11829
|
// src/trial/skill.ts
|
|
11058
|
-
import { createHash as
|
|
11830
|
+
import { createHash as createHash13 } from "crypto";
|
|
11059
11831
|
import { readFile as readFile26, stat as stat13 } from "fs/promises";
|
|
11060
|
-
import { join as
|
|
11832
|
+
import { join as join44 } from "path";
|
|
11061
11833
|
import { parse as parseYaml2 } from "yaml";
|
|
11062
11834
|
var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
|
|
11063
11835
|
async function createSkillTrial(driver, resolved, selectors) {
|
|
@@ -11069,7 +11841,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
11069
11841
|
throw new Error("Skill trial requires exactly one selected skill. Use --skill <name> or --select skills/<name>.");
|
|
11070
11842
|
}
|
|
11071
11843
|
const artifact = skills[0];
|
|
11072
|
-
const path = artifact.kind === "dir" ?
|
|
11844
|
+
const path = artifact.kind === "dir" ? join44(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
|
|
11073
11845
|
const info = await stat13(path);
|
|
11074
11846
|
if (info.size > MAX_TRIAL_SKILL_BYTES) {
|
|
11075
11847
|
throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
|
|
@@ -11085,7 +11857,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
11085
11857
|
skill: {
|
|
11086
11858
|
name: artifact.name,
|
|
11087
11859
|
relativePath: artifact.relativePath,
|
|
11088
|
-
sha256:
|
|
11860
|
+
sha256: createHash13("sha256").update(content).digest("hex"),
|
|
11089
11861
|
frontmatter,
|
|
11090
11862
|
content
|
|
11091
11863
|
}
|
|
@@ -11104,10 +11876,10 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
11104
11876
|
}
|
|
11105
11877
|
|
|
11106
11878
|
// src/lifecycle/fleet-normalize.ts
|
|
11107
|
-
import { createHash as
|
|
11108
|
-
import { readFile as readFile27, readdir as
|
|
11879
|
+
import { createHash as createHash14 } from "crypto";
|
|
11880
|
+
import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
|
|
11109
11881
|
import { homedir as homedir11 } from "os";
|
|
11110
|
-
import {
|
|
11882
|
+
import { dirname as dirname22, isAbsolute as isAbsolute3, join as join45, relative as relative9, resolve as resolve25 } from "path";
|
|
11111
11883
|
async function planFleetNormalization(request) {
|
|
11112
11884
|
const normalizedRequest = normalizeRequest2(request);
|
|
11113
11885
|
const destinationScope = await resolveWorkspaceScope({
|
|
@@ -11137,7 +11909,7 @@ async function planFleetNormalization(request) {
|
|
|
11137
11909
|
`Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
|
|
11138
11910
|
);
|
|
11139
11911
|
}
|
|
11140
|
-
packages.push({ name, declarationDigest:
|
|
11912
|
+
packages.push({ name, declarationDigest: sha2565(sourceDeclaration) });
|
|
11141
11913
|
}
|
|
11142
11914
|
if (!legacySelfNormalization) await assertSourceFleetPostcondition(normalizedRequest, sourceScope, candidates);
|
|
11143
11915
|
if ((normalizedRequest.profile || normalizedRequest.agent || normalizedRequest.artifacts || normalizedRequest.orphanedOwnerRoots) && !legacySelfNormalization) {
|
|
@@ -11168,7 +11940,7 @@ async function planFleetNormalization(request) {
|
|
|
11168
11940
|
packages,
|
|
11169
11941
|
installedState
|
|
11170
11942
|
};
|
|
11171
|
-
return { ...planWithoutDigest, planDigest:
|
|
11943
|
+
return { ...planWithoutDigest, planDigest: sha2565(canonicalJson2(planWithoutDigest)) };
|
|
11172
11944
|
}
|
|
11173
11945
|
async function applyFleetNormalization(request, hooks = {}) {
|
|
11174
11946
|
if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
|
|
@@ -11306,7 +12078,7 @@ async function assertNormalizationPostconditions(journal) {
|
|
|
11306
12078
|
}
|
|
11307
12079
|
for (const graphLock of journal.graphLocks) {
|
|
11308
12080
|
const raw = await readOptionalRecord(graphLock.path);
|
|
11309
|
-
const digest2 = raw ?
|
|
12081
|
+
const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
|
|
11310
12082
|
if (digest2 !== graphLock.afterDigest) {
|
|
11311
12083
|
throw new Error(`Fleet normalization graph-lock postcheck failed: ${graphLock.path}`);
|
|
11312
12084
|
}
|
|
@@ -11378,17 +12150,17 @@ function normalizeRequest2(request) {
|
|
|
11378
12150
|
...profile ? { profile } : {},
|
|
11379
12151
|
...agent ? { agent } : {},
|
|
11380
12152
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
11381
|
-
...request.globalRoot ? { globalRoot:
|
|
12153
|
+
...request.globalRoot ? { globalRoot: resolve25(request.globalRoot) } : {}
|
|
11382
12154
|
};
|
|
11383
12155
|
}
|
|
11384
12156
|
function normalizeOrphanedOwnerRoot(value) {
|
|
11385
12157
|
const root = value.trim();
|
|
11386
12158
|
if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
11387
|
-
return
|
|
12159
|
+
return resolve25(root);
|
|
11388
12160
|
}
|
|
11389
12161
|
async function normalizationRoots(request) {
|
|
11390
12162
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
11391
|
-
const source = request.from === "user" ?
|
|
12163
|
+
const source = request.from === "user" ? resolve25(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
11392
12164
|
return { source, destination: destination.root };
|
|
11393
12165
|
}
|
|
11394
12166
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -11396,7 +12168,7 @@ async function withOrderedLocks(roots, fn) {
|
|
|
11396
12168
|
if (index === roots.length) return fn();
|
|
11397
12169
|
const root = roots[index];
|
|
11398
12170
|
return withFilesystemLock(
|
|
11399
|
-
|
|
12171
|
+
join45(root, ".agentwheel", "fleet-normalize.lock"),
|
|
11400
12172
|
5e3,
|
|
11401
12173
|
() => run(index + 1),
|
|
11402
12174
|
"fleet normalization"
|
|
@@ -11454,11 +12226,28 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11454
12226
|
const coveredDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11455
12227
|
const plannedDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11456
12228
|
for (const sourceGraph of sourceGraphs) {
|
|
11457
|
-
const sourceState = await
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
12229
|
+
const sourceState = await installedTargetStateForGraph(
|
|
12230
|
+
source,
|
|
12231
|
+
sourceGraph,
|
|
12232
|
+
manifests,
|
|
12233
|
+
sourceOwners,
|
|
12234
|
+
selected
|
|
12235
|
+
);
|
|
12236
|
+
const destinationStableState = await targetStateForGraph(destination, sourceGraph);
|
|
12237
|
+
const matchingDestinationGraphs = destinationGraphs.filter((candidate) => candidate.targetKey === sourceGraph.targetKey && candidate.adapter === sourceGraph.adapter);
|
|
12238
|
+
if (matchingDestinationGraphs.length > 1) {
|
|
12239
|
+
throw new Error(
|
|
12240
|
+
`Ambiguous destination graph state for target '${sourceGraph.targetKey}': ` + matchingDestinationGraphs.map((candidate) => candidate.path).join(", ")
|
|
12241
|
+
);
|
|
11461
12242
|
}
|
|
12243
|
+
const destinationGraph = matchingDestinationGraphs[0];
|
|
12244
|
+
const destinationState = destinationGraph ? await installedTargetStateForGraph(
|
|
12245
|
+
destination,
|
|
12246
|
+
destinationGraph,
|
|
12247
|
+
manifests,
|
|
12248
|
+
destinationOwners,
|
|
12249
|
+
selected
|
|
12250
|
+
) : destinationStableState;
|
|
11462
12251
|
if (sourceState.adapter !== destinationState.adapter) {
|
|
11463
12252
|
throw new Error(`Source and destination adapters diverge for target '${sourceGraph.targetKey}'.`);
|
|
11464
12253
|
}
|
|
@@ -11467,7 +12256,6 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11467
12256
|
throw new Error(`Source graph lock is not covered by its canonical install manifest: ${sourceState.manifestPath}`);
|
|
11468
12257
|
}
|
|
11469
12258
|
const sourceGraphArtifacts = new Set(sourceGraph.artifactIdentities);
|
|
11470
|
-
const destinationGraph = destinationGraphs.find((candidate) => candidate.path === destinationState.graphLockPath);
|
|
11471
12259
|
if (destinationGraph && destinationGraph.digest !== sourceGraph.digest) {
|
|
11472
12260
|
throw new Error("Source and destination graph declarations, selections, versions, identities, or rendered paths diverge.");
|
|
11473
12261
|
}
|
|
@@ -11479,10 +12267,10 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11479
12267
|
const destinationGraphAfter = destinationGraph?.lock ?? destinationGraphFromSource(sourceGraph.lock, destinationState.targetFingerprint);
|
|
11480
12268
|
graphTransfers.push({
|
|
11481
12269
|
sourceGraphLockPath: sourceGraph.path,
|
|
11482
|
-
sourceGraphLockDigest:
|
|
12270
|
+
sourceGraphLockDigest: sha2565(canonicalJson2(sourceGraph.lock)),
|
|
11483
12271
|
destinationGraphLockPath: destinationState.graphLockPath,
|
|
11484
|
-
destinationGraphLockDigest: destinationGraph ?
|
|
11485
|
-
destinationGraphLockAfterDigest:
|
|
12272
|
+
destinationGraphLockDigest: destinationGraph ? sha2565(canonicalJson2(destinationGraph.lock)) : null,
|
|
12273
|
+
destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraphAfter)),
|
|
11486
12274
|
targetKey: sourceGraph.targetKey,
|
|
11487
12275
|
adapter: destinationState.adapter,
|
|
11488
12276
|
targetFingerprint: destinationState.targetFingerprint
|
|
@@ -11585,16 +12373,30 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11585
12373
|
const transferableOwners = orphanedOwners.size > 0 ? orphanedOwners : /* @__PURE__ */ new Set([legacyOwner]);
|
|
11586
12374
|
let normalizedGraphCount = 0;
|
|
11587
12375
|
for (const graph of graphCandidates) {
|
|
12376
|
+
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
|
|
12377
|
+
if (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
|
|
12378
|
+
const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
|
|
12379
|
+
if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
12380
|
+
assertCorrelatedGraphManifest(
|
|
12381
|
+
graph,
|
|
12382
|
+
destinationState,
|
|
12383
|
+
destinationManifest,
|
|
12384
|
+
/* @__PURE__ */ new Set([fleetOwner]),
|
|
12385
|
+
selected
|
|
12386
|
+
);
|
|
12387
|
+
normalizedGraphCount += 1;
|
|
12388
|
+
continue;
|
|
12389
|
+
}
|
|
12390
|
+
}
|
|
11588
12391
|
const legacyState = await legacyTargetStateForGraph(fleet, graph);
|
|
12392
|
+
if (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
|
|
12393
|
+
throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12394
|
+
}
|
|
11589
12395
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
11590
12396
|
const relevantManifestEntries2 = legacyManifest?.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected) && entryMatchesArtifacts(entry, selectedArtifacts)) ?? [];
|
|
11591
12397
|
if (relevantManifestEntries2.length > 0) {
|
|
11592
12398
|
graphs.push(graph);
|
|
11593
12399
|
}
|
|
11594
|
-
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
|
|
11595
|
-
if (destinationState.graphLockPath === graph.path && legacyManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
11596
|
-
normalizedGraphCount += 1;
|
|
11597
|
-
}
|
|
11598
12400
|
}
|
|
11599
12401
|
if (graphs.length === 0 && normalizedGraphCount > 0) {
|
|
11600
12402
|
throw new Error(`Fleet '${fleet.fleetId}' installed state is already normalized to fleet-qualified ownership.`);
|
|
@@ -11695,7 +12497,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11695
12497
|
)
|
|
11696
12498
|
});
|
|
11697
12499
|
if (!selectedArtifacts) {
|
|
11698
|
-
const fullDigest =
|
|
12500
|
+
const fullDigest = sha2565(canonicalJson2(graph.lock));
|
|
11699
12501
|
const existingDestinationGraph = graphCandidates.find((candidate) => candidate.path === destinationState.graphLockPath);
|
|
11700
12502
|
if (existingDestinationGraph) assertGraphIsSubset(graph, existingDestinationGraph);
|
|
11701
12503
|
const destinationGraph = destinationGraphFromSource(graph.lock, destinationState.targetFingerprint);
|
|
@@ -11703,8 +12505,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11703
12505
|
sourceGraphLockPath: graph.path,
|
|
11704
12506
|
sourceGraphLockDigest: fullDigest,
|
|
11705
12507
|
destinationGraphLockPath: destinationState.graphLockPath,
|
|
11706
|
-
destinationGraphLockDigest: existingDestinationGraph ?
|
|
11707
|
-
destinationGraphLockAfterDigest:
|
|
12508
|
+
destinationGraphLockDigest: existingDestinationGraph ? sha2565(canonicalJson2(existingDestinationGraph.lock)) : null,
|
|
12509
|
+
destinationGraphLockAfterDigest: sha2565(canonicalJson2(destinationGraph)),
|
|
11708
12510
|
targetKey: graph.targetKey,
|
|
11709
12511
|
adapter: graph.adapter,
|
|
11710
12512
|
targetFingerprint: destinationState.targetFingerprint
|
|
@@ -11762,7 +12564,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
11762
12564
|
async function runtimeRoots(scope) {
|
|
11763
12565
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
11764
12566
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
11765
|
-
roots.add(
|
|
12567
|
+
roots.add(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
11766
12568
|
}
|
|
11767
12569
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
11768
12570
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -11780,17 +12582,17 @@ async function runtimeRoots(scope) {
|
|
|
11780
12582
|
async function collectManifests(roots) {
|
|
11781
12583
|
const found = /* @__PURE__ */ new Map();
|
|
11782
12584
|
for (const root of roots) {
|
|
11783
|
-
const dir =
|
|
12585
|
+
const dir = join45(root, ".agentwheel");
|
|
11784
12586
|
let names;
|
|
11785
12587
|
try {
|
|
11786
|
-
names = await
|
|
12588
|
+
names = await readdir11(dir);
|
|
11787
12589
|
} catch (error) {
|
|
11788
12590
|
if (isMissing(error)) continue;
|
|
11789
12591
|
throw error;
|
|
11790
12592
|
}
|
|
11791
12593
|
for (const name of names.sort((a, b) => a.localeCompare(b))) {
|
|
11792
12594
|
if (!name.endsWith(".install-manifest.json")) continue;
|
|
11793
|
-
const path =
|
|
12595
|
+
const path = join45(dir, name);
|
|
11794
12596
|
const raw = JSON.parse(await readFile27(path, "utf8"));
|
|
11795
12597
|
const parsed = installManifestSchema.parse(raw);
|
|
11796
12598
|
if (parsed.version !== 2) {
|
|
@@ -11799,14 +12601,14 @@ async function collectManifests(roots) {
|
|
|
11799
12601
|
}
|
|
11800
12602
|
continue;
|
|
11801
12603
|
}
|
|
11802
|
-
if (
|
|
12604
|
+
if (resolve25(parsed.targetRoot) !== resolve25(root)) {
|
|
11803
12605
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
11804
12606
|
}
|
|
11805
12607
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
11806
12608
|
installationType: parsed.installationType,
|
|
11807
12609
|
stateKey: parsed.stateKey
|
|
11808
12610
|
});
|
|
11809
|
-
if (
|
|
12611
|
+
if (resolve25(expectedPath) !== resolve25(path)) {
|
|
11810
12612
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11811
12613
|
}
|
|
11812
12614
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11827,7 +12629,7 @@ async function collectManifestPaths(paths) {
|
|
|
11827
12629
|
installationType: parsed.installationType,
|
|
11828
12630
|
stateKey: parsed.stateKey
|
|
11829
12631
|
});
|
|
11830
|
-
if (
|
|
12632
|
+
if (resolve25(expectedPath) !== resolve25(path)) {
|
|
11831
12633
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11832
12634
|
}
|
|
11833
12635
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11846,21 +12648,17 @@ function assertNoForeignSamePathOwnership(manifests, renderedPaths, allowedOwner
|
|
|
11846
12648
|
}
|
|
11847
12649
|
}
|
|
11848
12650
|
async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include) {
|
|
11849
|
-
const root =
|
|
12651
|
+
const root = join45(workspaceRoot, ".agentwheel", "locks");
|
|
11850
12652
|
if (!await pathExists(root)) return [];
|
|
11851
12653
|
const results = [];
|
|
11852
12654
|
for (const path of await listFiles(root)) {
|
|
11853
12655
|
if (!path.endsWith(".graph-lock.json")) continue;
|
|
11854
12656
|
const lock = await readGraphLock(path);
|
|
11855
12657
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
11856
|
-
const parts =
|
|
12658
|
+
const parts = relative9(root, path).split(/[\\/]/);
|
|
11857
12659
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
11858
|
-
const [targetKey2, adapter
|
|
12660
|
+
const [targetKey2, adapter] = parts;
|
|
11859
12661
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
11860
|
-
const pathFingerprint = basename19(fileName, ".graph-lock.json");
|
|
11861
|
-
if (!lock.canonical.targetFingerprint || lock.canonical.targetFingerprint !== pathFingerprint) {
|
|
11862
|
-
throw new Error(`Graph lock fingerprint does not match its canonical path: ${path}`);
|
|
11863
|
-
}
|
|
11864
12662
|
const candidate = {
|
|
11865
12663
|
path,
|
|
11866
12664
|
lock,
|
|
@@ -11874,7 +12672,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11874
12672
|
const projection = relevantGraphProjection(lock, selected);
|
|
11875
12673
|
results.push({
|
|
11876
12674
|
...candidate,
|
|
11877
|
-
digest:
|
|
12675
|
+
digest: sha2565(canonicalJson2(projection)),
|
|
11878
12676
|
artifactIdentities: lock.canonical.artifacts.filter((artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(lock, owner, selected))).map(graphArtifactIdentity).sort()
|
|
11879
12677
|
});
|
|
11880
12678
|
}
|
|
@@ -11889,9 +12687,19 @@ function isCurrentLocalTarget(scope, graph) {
|
|
|
11889
12687
|
}
|
|
11890
12688
|
async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
11891
12689
|
if (!isCurrentLocalTarget(scope, graph)) return false;
|
|
11892
|
-
const
|
|
11893
|
-
|
|
11894
|
-
|
|
12690
|
+
const configuredLegacyPath = await legacyManifestPathForConfiguredTarget(scope, graph);
|
|
12691
|
+
if (configuredLegacyPath) {
|
|
12692
|
+
const manifest = (await collectManifestPaths([configuredLegacyPath]))[0];
|
|
12693
|
+
if (manifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12694
|
+
}
|
|
12695
|
+
if (graph.lock.canonical.roots.some((root) => !scope.config.packages.some((pkg) => pkg.name === root.rootId))) return false;
|
|
12696
|
+
const legacyState = await legacyTargetStateForGraph(scope, graph);
|
|
12697
|
+
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
12698
|
+
if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12699
|
+
const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
|
|
12700
|
+
if (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
|
|
12701
|
+
const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
|
|
12702
|
+
return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
|
|
11895
12703
|
}
|
|
11896
12704
|
async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
11897
12705
|
const agent = scope.config.agents[graph.targetKey];
|
|
@@ -11910,9 +12718,10 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
11910
12718
|
baseDir: scope.root
|
|
11911
12719
|
});
|
|
11912
12720
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
11913
|
-
const installRoot =
|
|
12721
|
+
const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
11914
12722
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11915
12723
|
installationType: agent.installationType,
|
|
12724
|
+
stateKey: agent.stateKey,
|
|
11916
12725
|
targetFingerprint: graph.lock.canonical.targetFingerprint
|
|
11917
12726
|
});
|
|
11918
12727
|
return installManifestPath(installRoot, adapter.name, { installationType: agent.installationType, stateKey });
|
|
@@ -11977,14 +12786,21 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11977
12786
|
ssh: void 0,
|
|
11978
12787
|
...agent?.stateKey ? { stateKey: agent.stateKey } : {}
|
|
11979
12788
|
};
|
|
11980
|
-
const
|
|
12789
|
+
const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
12790
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
12791
|
+
targetFingerprintParts: fingerprintParts,
|
|
12792
|
+
workspaceRoot: scope.root,
|
|
12793
|
+
targetKey: graph.targetKey,
|
|
12794
|
+
resolvedInstallRoot: installRoot,
|
|
12795
|
+
transportKind: "local"
|
|
12796
|
+
});
|
|
12797
|
+
const targetFingerprint = targetIdentity.targetFingerprint;
|
|
11981
12798
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11982
12799
|
installationType,
|
|
11983
12800
|
stateKey: agent?.stateKey,
|
|
11984
|
-
targetFingerprint,
|
|
12801
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
11985
12802
|
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
11986
12803
|
});
|
|
11987
|
-
const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
11988
12804
|
return {
|
|
11989
12805
|
adapter: adapter.name,
|
|
11990
12806
|
installationType,
|
|
@@ -11992,28 +12808,81 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11992
12808
|
installRoot,
|
|
11993
12809
|
stateKey,
|
|
11994
12810
|
targetFingerprint,
|
|
11995
|
-
graphLockPath: graphLockPathForTarget(scope.root, graph.targetKey, adapter.name, fingerprintParts
|
|
12811
|
+
graphLockPath: graphLockPathForTarget(scope.root, graph.targetKey, adapter.name, fingerprintParts, {
|
|
12812
|
+
resolvedInstallRoot: installRoot,
|
|
12813
|
+
transportKind: "local"
|
|
12814
|
+
}),
|
|
11996
12815
|
manifestPath: installManifestPath(installRoot, adapter.name, { installationType, stateKey })
|
|
11997
12816
|
};
|
|
11998
12817
|
}
|
|
11999
|
-
async function
|
|
12000
|
-
const
|
|
12818
|
+
async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
|
|
12819
|
+
const stable = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12820
|
+
let state;
|
|
12821
|
+
if (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
|
|
12822
|
+
state = stable;
|
|
12823
|
+
} else {
|
|
12824
|
+
const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
|
|
12825
|
+
if (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
|
|
12826
|
+
throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12827
|
+
}
|
|
12828
|
+
state = released;
|
|
12829
|
+
}
|
|
12830
|
+
const manifest = manifests.find((candidate) => resolve25(candidate.path) === resolve25(state.manifestPath));
|
|
12831
|
+
if (!manifest) {
|
|
12832
|
+
throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
|
|
12833
|
+
}
|
|
12834
|
+
assertCorrelatedGraphManifest(graph, state, manifest, owners, selected);
|
|
12835
|
+
return state;
|
|
12836
|
+
}
|
|
12837
|
+
async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null) {
|
|
12838
|
+
const current = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12001
12839
|
const targetFingerprint = graph.lock.canonical.targetFingerprint;
|
|
12840
|
+
if (!targetFingerprint || !/^[a-f0-9]{64}$/u.test(targetFingerprint)) {
|
|
12841
|
+
throw new Error(`Released graph lock has an invalid target fingerprint: ${graph.path}`);
|
|
12842
|
+
}
|
|
12843
|
+
const agent = scope.config.agents[graph.targetKey];
|
|
12002
12844
|
const stateKey = stateKeyFor(current.adapter, {
|
|
12003
12845
|
installationType: current.installationType,
|
|
12004
|
-
|
|
12846
|
+
stateKey: agent?.stateKey,
|
|
12847
|
+
targetFingerprint,
|
|
12848
|
+
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
12005
12849
|
});
|
|
12006
12850
|
return {
|
|
12007
12851
|
...current,
|
|
12008
12852
|
stateKey,
|
|
12009
12853
|
targetFingerprint,
|
|
12010
|
-
graphLockPath: graph.path,
|
|
12854
|
+
graphLockPath: join45(dirname22(graph.path), `${targetFingerprint}.graph-lock.json`),
|
|
12011
12855
|
manifestPath: installManifestPath(current.installRoot, current.adapter, {
|
|
12012
12856
|
installationType: current.installationType,
|
|
12013
12857
|
stateKey
|
|
12014
12858
|
})
|
|
12015
12859
|
};
|
|
12016
12860
|
}
|
|
12861
|
+
function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
|
|
12862
|
+
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve25(manifest.manifest.targetRoot) !== resolve25(state.installRoot)) {
|
|
12863
|
+
throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
|
|
12864
|
+
}
|
|
12865
|
+
const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
|
|
12866
|
+
if (entries.length === 0) {
|
|
12867
|
+
throw new Error(`Graph lock is not covered by selected install-manifest contributions: ${graph.path}`);
|
|
12868
|
+
}
|
|
12869
|
+
const graphArtifacts = new Set(graph.artifactIdentities);
|
|
12870
|
+
const graphLockDigest = sha2565(canonicalJson2(graph.lock));
|
|
12871
|
+
for (const entry of entries) {
|
|
12872
|
+
if (!owners.has(entry.workspaceOwner)) {
|
|
12873
|
+
throw new Error(`Graph lock manifest owner mismatch at ${manifest.path}: ${entry.workspaceOwner}.`);
|
|
12874
|
+
}
|
|
12875
|
+
if (!graphArtifacts.has(graphEntryIdentity(entry))) {
|
|
12876
|
+
throw new Error(`Install manifest entry is not covered by its graph lock: ${renderedEntryPath(manifest.manifest, entry)}`);
|
|
12877
|
+
}
|
|
12878
|
+
if (entry.graphLockDigest !== void 0 && entry.graphLockDigest !== graphLockDigest) {
|
|
12879
|
+
throw new Error(`Install manifest graph-lock digest does not match ${graph.path}.`);
|
|
12880
|
+
}
|
|
12881
|
+
}
|
|
12882
|
+
}
|
|
12883
|
+
async function legacyTargetStateForGraph(scope, graph) {
|
|
12884
|
+
return releasedTargetStateForGraph(scope, graph, null);
|
|
12885
|
+
}
|
|
12017
12886
|
function assertGraphIsSubset(source, destination) {
|
|
12018
12887
|
const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
|
|
12019
12888
|
for (const root of destination.lock.canonical.roots) {
|
|
@@ -12047,7 +12916,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
12047
12916
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
12048
12917
|
if (existing) {
|
|
12049
12918
|
const manifest2 = existing.manifest;
|
|
12050
|
-
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey ||
|
|
12919
|
+
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve25(manifest2.targetRoot) !== state.installRoot) {
|
|
12051
12920
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
12052
12921
|
}
|
|
12053
12922
|
}
|
|
@@ -12107,7 +12976,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12107
12976
|
const content = await readFile27(path, "utf8");
|
|
12108
12977
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
12109
12978
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
12110
|
-
} else if (
|
|
12979
|
+
} else if (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
12111
12980
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
12112
12981
|
} else {
|
|
12113
12982
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -12118,8 +12987,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12118
12987
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
12119
12988
|
}
|
|
12120
12989
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
12121
|
-
const normalizedRoot =
|
|
12122
|
-
const candidate =
|
|
12990
|
+
const normalizedRoot = resolve25(root);
|
|
12991
|
+
const candidate = resolve25(normalizedRoot, entryPath);
|
|
12123
12992
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
12124
12993
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
12125
12994
|
}
|
|
@@ -12210,8 +13079,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
12210
13079
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
12211
13080
|
}
|
|
12212
13081
|
function renderedEntryPath(manifest, entry) {
|
|
12213
|
-
const root =
|
|
12214
|
-
const candidate =
|
|
13082
|
+
const root = resolve25(manifest.targetRoot);
|
|
13083
|
+
const candidate = resolve25(root, entry.path);
|
|
12215
13084
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
12216
13085
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
12217
13086
|
}
|
|
@@ -12366,7 +13235,7 @@ async function buildJournalGraphLockStates(plan) {
|
|
|
12366
13235
|
for (const transfer of plan.installedState.graphTransfers) {
|
|
12367
13236
|
const sourceBefore = JSON.parse(await readFile27(transfer.sourceGraphLockPath, "utf8"));
|
|
12368
13237
|
const sourceLock = await readGraphLock(transfer.sourceGraphLockPath);
|
|
12369
|
-
const sourceDigest =
|
|
13238
|
+
const sourceDigest = sha2565(canonicalJson2(sourceLock));
|
|
12370
13239
|
if (sourceDigest !== transfer.sourceGraphLockDigest) {
|
|
12371
13240
|
throw new Error(`Source graph lock changed after planning: ${transfer.sourceGraphLockPath}`);
|
|
12372
13241
|
}
|
|
@@ -12384,9 +13253,9 @@ async function buildJournalGraphLockStates(plan) {
|
|
|
12384
13253
|
continue;
|
|
12385
13254
|
}
|
|
12386
13255
|
const destinationBefore = await readOptionalRecord(transfer.destinationGraphLockPath);
|
|
12387
|
-
const destinationBeforeDigest = destinationBefore ?
|
|
13256
|
+
const destinationBeforeDigest = destinationBefore ? sha2565(canonicalJson2(await readGraphLock(transfer.destinationGraphLockPath))) : null;
|
|
12388
13257
|
const destinationAfter = destinationBefore ? await readGraphLock(transfer.destinationGraphLockPath) : destinationGraphFromSource(sourceLock, transfer.targetFingerprint);
|
|
12389
|
-
const destinationAfterDigest =
|
|
13258
|
+
const destinationAfterDigest = sha2565(canonicalJson2(destinationAfter));
|
|
12390
13259
|
if (destinationBeforeDigest !== transfer.destinationGraphLockDigest || destinationAfterDigest !== transfer.destinationGraphLockAfterDigest) {
|
|
12391
13260
|
throw new Error(`Destination graph lock changed after planning: ${transfer.destinationGraphLockPath}`);
|
|
12392
13261
|
}
|
|
@@ -12496,7 +13365,7 @@ async function assertJournalRecoverable(journal) {
|
|
|
12496
13365
|
}
|
|
12497
13366
|
for (const graphLock of journal.graphLocks) {
|
|
12498
13367
|
const raw = await readOptionalRecord(graphLock.path);
|
|
12499
|
-
const digest2 = raw ?
|
|
13368
|
+
const digest2 = raw ? sha2565(canonicalJson2(await readGraphLock(graphLock.path))) : null;
|
|
12500
13369
|
if (digest2 !== graphLock.beforeDigest && digest2 !== graphLock.afterDigest) {
|
|
12501
13370
|
throw new Error(`Cannot recover fleet normalization because graph lock changed outside the transaction: ${graphLock.path}`);
|
|
12502
13371
|
}
|
|
@@ -12549,10 +13418,10 @@ function packageMap(config) {
|
|
|
12549
13418
|
return result;
|
|
12550
13419
|
}
|
|
12551
13420
|
function configRevision(config) {
|
|
12552
|
-
return
|
|
13421
|
+
return sha2565(canonicalJson2(config));
|
|
12553
13422
|
}
|
|
12554
13423
|
function normalizationJournalPath(sourceRoot, destinationFleet) {
|
|
12555
|
-
return
|
|
13424
|
+
return join45(sourceRoot, ".agentwheel", `fleet-normalize-${destinationFleet}.journal.json`);
|
|
12556
13425
|
}
|
|
12557
13426
|
function canonicalJson2(value) {
|
|
12558
13427
|
return JSON.stringify(sortValue(value));
|
|
@@ -12564,8 +13433,8 @@ function sortValue(value) {
|
|
|
12564
13433
|
}
|
|
12565
13434
|
return value;
|
|
12566
13435
|
}
|
|
12567
|
-
function
|
|
12568
|
-
return
|
|
13436
|
+
function sha2565(value) {
|
|
13437
|
+
return createHash14("sha256").update(value).digest("hex");
|
|
12569
13438
|
}
|
|
12570
13439
|
function assertDigest(value) {
|
|
12571
13440
|
if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
|
|
@@ -12603,6 +13472,7 @@ var commandGovernance = {
|
|
|
12603
13472
|
"package migrate": "governed-always",
|
|
12604
13473
|
remember: "governed-always",
|
|
12605
13474
|
"ownership handoff": "governed-unless-dry-run",
|
|
13475
|
+
"ownership recovery-plan": "incidental-cache",
|
|
12606
13476
|
"ownership retire-stale": "governed-apply",
|
|
12607
13477
|
"mcp retire": "governed-apply",
|
|
12608
13478
|
eject: "governed-always",
|
|
@@ -12709,7 +13579,7 @@ fleetCommand.command("normalize").description("plan or apply duplicate desired-s
|
|
|
12709
13579
|
});
|
|
12710
13580
|
cacheCommand.command("prune").description("remove old Git source snapshots while preserving locked commits").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 workspace root").option("--keep <count>", "newest snapshots to retain per source", parsePositiveInteger, 3).option("--apply", "delete the selected snapshots; without this flag only preview", false).action(async (options) => {
|
|
12711
13581
|
const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
|
|
12712
|
-
const cacheRoot =
|
|
13582
|
+
const cacheRoot = join46(targetRoot, ".agentwheel", "cache");
|
|
12713
13583
|
const result = await pruneGitCache(cacheRoot, { keepSnapshots: options.keep, dryRun: !options.apply });
|
|
12714
13584
|
const verb = options.apply ? "Removed" : "Would remove";
|
|
12715
13585
|
for (const path of result.removedPaths) console.log(`${verb} ${path}`);
|
|
@@ -12727,7 +13597,7 @@ program.command("list").description("list artifacts exposed by a package source"
|
|
|
12727
13597
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12728
13598
|
const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
|
|
12729
13599
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12730
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
13600
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
|
|
12731
13601
|
try {
|
|
12732
13602
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
12733
13603
|
for (const artifact of artifacts) {
|
|
@@ -12796,7 +13666,7 @@ program.command("try").description("read and validate one skill for the current
|
|
|
12796
13666
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12797
13667
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12798
13668
|
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, {
|
|
12799
|
-
cacheRoot:
|
|
13669
|
+
cacheRoot: join46(targetRoot, ".agentwheel", "cache")
|
|
12800
13670
|
}))));
|
|
12801
13671
|
try {
|
|
12802
13672
|
const trial = await createSkillTrial(driver, resolved, selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry));
|
|
@@ -12818,7 +13688,7 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
12818
13688
|
const targetRoot = await workspaceContextRootFromOptions(normalizeRuntimeScopeOptions(options));
|
|
12819
13689
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
12820
13690
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
12821
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
13691
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
|
|
12822
13692
|
try {
|
|
12823
13693
|
const result = await driver.scan(resolved);
|
|
12824
13694
|
if (result.findings.length === 0) {
|
|
@@ -12918,11 +13788,11 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
12918
13788
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12919
13789
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
12920
13790
|
for (const target of targets) {
|
|
12921
|
-
const
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
console.log(formatDepsWhy(
|
|
13791
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13792
|
+
if (!state.graphLock) {
|
|
13793
|
+
throw new Error(`No graph lock for ${state.adapter.name} at ${target.targetRoot}: ${state.stableGraphLockPath}`);
|
|
13794
|
+
}
|
|
13795
|
+
console.log(formatDepsWhy(state.graphLock, state.manifest, selector));
|
|
12926
13796
|
}
|
|
12927
13797
|
})
|
|
12928
13798
|
);
|
|
@@ -13013,19 +13883,23 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13013
13883
|
throw new Error(`Ownership handoff selector must be exactly type/name: ${selector}`);
|
|
13014
13884
|
}
|
|
13015
13885
|
const target = targets[0];
|
|
13016
|
-
const
|
|
13017
|
-
const adapter
|
|
13018
|
-
|
|
13019
|
-
|
|
13886
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13887
|
+
const { adapter, installationType } = state;
|
|
13888
|
+
if (state.migration) {
|
|
13889
|
+
throw new Error(
|
|
13890
|
+
"Migrate the target state before ownership handoff by running a full agentwheel install for this target."
|
|
13891
|
+
);
|
|
13892
|
+
}
|
|
13020
13893
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13021
13894
|
if (options.toFleet) {
|
|
13022
13895
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
13023
|
-
if (
|
|
13896
|
+
if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
|
|
13024
13897
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13025
13898
|
}
|
|
13026
13899
|
}
|
|
13027
13900
|
const request = {
|
|
13028
|
-
|
|
13901
|
+
installationType,
|
|
13902
|
+
stateKey: state.stableStateKey,
|
|
13029
13903
|
targetRoot: state.installRoot,
|
|
13030
13904
|
adapter: adapter.name,
|
|
13031
13905
|
artifactType,
|
|
@@ -13045,11 +13919,85 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13045
13919
|
console.log(`Manifest revision: ${result.manifestRevision}`);
|
|
13046
13920
|
console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
|
|
13047
13921
|
});
|
|
13922
|
+
ownershipCommand.command("recovery-plan").description("write a read-only per-path legacy ownership evidence report").requiredOption("--fleet <id>", "registered Fleet containing the current source graph").requiredOption("--report <path>", "new private JSON report path").option("--agent <name>", "one named Fleet target").option("--profile <name>", "Fleet profile resolving to one target").action(async (options) => {
|
|
13923
|
+
const targets = await resolveCliTargets({ fleet: options.fleet, agent: options.agent, profile: options.profile });
|
|
13924
|
+
if (targets.length !== 1) throw new Error(`Recovery planning requires one Fleet target, found ${targets.length}.`);
|
|
13925
|
+
const target = targets[0];
|
|
13926
|
+
if (target.fleetId !== options.fleet) throw new Error("Resolved target does not belong to the selected Fleet.");
|
|
13927
|
+
const adapter = await resolveAdapterForTarget(target, {});
|
|
13928
|
+
const transport = transportForTarget(target);
|
|
13929
|
+
const requestedReport = normalizeCliPath(options.report);
|
|
13930
|
+
const reportPath = join46(await realpath2(dirname23(requestedReport)), basename21(requestedReport));
|
|
13931
|
+
const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
|
|
13932
|
+
if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute4(reportInsideTarget)) {
|
|
13933
|
+
throw new Error("Recovery report must be outside the runtime target root.");
|
|
13934
|
+
}
|
|
13935
|
+
const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
13936
|
+
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
13937
|
+
if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
|
|
13938
|
+
const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
|
|
13939
|
+
const priorTmpdir = process.env.TMPDIR;
|
|
13940
|
+
try {
|
|
13941
|
+
process.env.TMPDIR = stage;
|
|
13942
|
+
const graphPlan = await createGraphSourcePlan({
|
|
13943
|
+
roots: config.packages.map((pkg) => ({
|
|
13944
|
+
rootId: pkg.name,
|
|
13945
|
+
source: pkg.source,
|
|
13946
|
+
mode: pkg.mode,
|
|
13947
|
+
version: pkg.version,
|
|
13948
|
+
ref: pkg.requestedRef,
|
|
13949
|
+
select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
13950
|
+
selection: pkg.selection,
|
|
13951
|
+
aliases: pkg.aliases,
|
|
13952
|
+
overrides: pkg.overrides,
|
|
13953
|
+
includeSuggestions: pkg.withSuggestions,
|
|
13954
|
+
suggestionAliases: pkg.suggestions,
|
|
13955
|
+
useLock: false
|
|
13956
|
+
})),
|
|
13957
|
+
targetRoot: target.targetRoot,
|
|
13958
|
+
workspaceRoot: target.workspaceRoot,
|
|
13959
|
+
fleetId: target.fleetId,
|
|
13960
|
+
adapter,
|
|
13961
|
+
transport,
|
|
13962
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
13963
|
+
targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
|
|
13964
|
+
installationType,
|
|
13965
|
+
stateKey: target.stateKey,
|
|
13966
|
+
readOnly: true,
|
|
13967
|
+
yes: true,
|
|
13968
|
+
freshGraphOnly: true,
|
|
13969
|
+
deferForeignStateCheck: true,
|
|
13970
|
+
cacheRoot: join46(stage, "source-cache"),
|
|
13971
|
+
registryCachePath: join46(stage, "registry.json")
|
|
13972
|
+
});
|
|
13973
|
+
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
|
|
13974
|
+
const report = await planLegacyRecovery({
|
|
13975
|
+
graphPlan,
|
|
13976
|
+
installRoot,
|
|
13977
|
+
workspaceRoot: target.workspaceRoot,
|
|
13978
|
+
fleetId: target.fleetId,
|
|
13979
|
+
explicitStateKey: target.stateKey,
|
|
13980
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
13981
|
+
adapter: adapter.name,
|
|
13982
|
+
installationType,
|
|
13983
|
+
historicalLocks: await inventoryHistoricalLocks(target.workspaceRoot),
|
|
13984
|
+
transport
|
|
13985
|
+
});
|
|
13986
|
+
await writeFile13(reportPath, `${JSON.stringify(report, null, 2)}
|
|
13987
|
+
`, { mode: 384, flag: "wx" });
|
|
13988
|
+
console.log(`Recovery report: ${reportPath}`);
|
|
13989
|
+
console.log(JSON.stringify(report.counts));
|
|
13990
|
+
} finally {
|
|
13991
|
+
if (priorTmpdir === void 0) delete process.env.TMPDIR;
|
|
13992
|
+
else process.env.TMPDIR = priorTmpdir;
|
|
13993
|
+
await rm10(stage, { recursive: true, force: true });
|
|
13994
|
+
}
|
|
13995
|
+
});
|
|
13048
13996
|
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("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
13049
13997
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
13050
13998
|
const fleet = await showRegisteredFleet(options.fleet);
|
|
13051
13999
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13052
|
-
if (
|
|
14000
|
+
if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
|
|
13053
14001
|
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13054
14002
|
}
|
|
13055
14003
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
@@ -13057,19 +14005,22 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
13057
14005
|
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
13058
14006
|
}
|
|
13059
14007
|
const target = targets[0];
|
|
13060
|
-
if (target.fleetId !== options.fleet ||
|
|
14008
|
+
if (target.fleetId !== options.fleet || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
|
|
13061
14009
|
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
13062
14010
|
}
|
|
13063
|
-
const
|
|
13064
|
-
const adapter
|
|
13065
|
-
|
|
13066
|
-
|
|
14011
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
14012
|
+
const { adapter, installationType } = state;
|
|
14013
|
+
if (state.migration) {
|
|
14014
|
+
throw new Error(
|
|
14015
|
+
"Migrate the target state before ownership retirement by running a full agentwheel install for this target."
|
|
14016
|
+
);
|
|
14017
|
+
}
|
|
13067
14018
|
const request = {
|
|
13068
14019
|
targetRoot: state.installRoot,
|
|
13069
14020
|
adapter: adapter.name,
|
|
13070
14021
|
installationType,
|
|
13071
14022
|
sourceStateKey: options.sourceStateKey,
|
|
13072
|
-
destinationStateKey: state.
|
|
14023
|
+
destinationStateKey: state.stableStateKey,
|
|
13073
14024
|
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
13074
14025
|
toWorkspaceRoot,
|
|
13075
14026
|
toFleetId: fleet.id,
|
|
@@ -13154,13 +14105,18 @@ program.command("uninstall").description("remove configured packages or managed
|
|
|
13154
14105
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
13155
14106
|
const transport = transportForTarget(target);
|
|
13156
14107
|
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
13157
|
-
const state =
|
|
13158
|
-
const manifest =
|
|
14108
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
14109
|
+
const manifest = state.manifest;
|
|
13159
14110
|
if (!manifest) {
|
|
13160
14111
|
console.log(`No install manifest for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
13161
14112
|
continue;
|
|
13162
14113
|
}
|
|
13163
|
-
const plan =
|
|
14114
|
+
const plan = {
|
|
14115
|
+
...filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options)),
|
|
14116
|
+
stateKey: state.stableStateKey,
|
|
14117
|
+
stateMigration: state.migration,
|
|
14118
|
+
targetStateFilePreconditions: state.targetStateFilePreconditions
|
|
14119
|
+
};
|
|
13164
14120
|
console.log(formatPlan(plan));
|
|
13165
14121
|
const result = await uninstall(plan, { dryRun: normalizedOptions.dryRun, force: normalizedOptions.force, transport });
|
|
13166
14122
|
if (!normalizedOptions.dryRun) {
|
|
@@ -13207,7 +14163,7 @@ journalCommand.command("list").description("show pending apply journals for reso
|
|
|
13207
14163
|
if (!journal) continue;
|
|
13208
14164
|
pending += 1;
|
|
13209
14165
|
console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
|
|
13210
|
-
console.log(` journal: ${
|
|
14166
|
+
console.log(` journal: ${join46(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
|
|
13211
14167
|
console.log(` stateKey: ${state.state.stateKey}`);
|
|
13212
14168
|
console.log(` createdAt: ${journal.createdAt}`);
|
|
13213
14169
|
console.log(` updatedAt: ${journal.updatedAt}`);
|
|
@@ -13543,14 +14499,14 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
13543
14499
|
);
|
|
13544
14500
|
}
|
|
13545
14501
|
const bundle = options.deferGraphRendering ? await stageSourceRaw(driver, resolvedSource, {
|
|
13546
|
-
cacheRoot:
|
|
14502
|
+
cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
|
|
13547
14503
|
mode: options.mode,
|
|
13548
14504
|
ref: initialVersion?.ref,
|
|
13549
14505
|
frozenLock: lockMode
|
|
13550
14506
|
}) : await stageSource(driver, resolvedSource, {
|
|
13551
14507
|
workspaceRoot: targetRoot,
|
|
13552
14508
|
adapter,
|
|
13553
|
-
cacheRoot:
|
|
14509
|
+
cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
|
|
13554
14510
|
mode: options.mode,
|
|
13555
14511
|
ref: initialVersion?.ref,
|
|
13556
14512
|
frozenLock: lockMode,
|
|
@@ -13622,15 +14578,12 @@ async function packageSelectsSkillForTarget(target, pkg, selector, options) {
|
|
|
13622
14578
|
if (explicitSelection && !explicitSelection.some((candidate) => candidate === selector)) return false;
|
|
13623
14579
|
const groups = /* @__PURE__ */ new Map();
|
|
13624
14580
|
const group = graphGroupForPackage(groups, target, pkg, options);
|
|
13625
|
-
const
|
|
13626
|
-
|
|
13627
|
-
group.
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
);
|
|
13632
|
-
if (await pathExists(graphLockPath)) {
|
|
13633
|
-
const lock = await readGraphLock(graphLockPath);
|
|
14581
|
+
const state = await resolveCliTargetState(group.target, {
|
|
14582
|
+
...options,
|
|
14583
|
+
installationType: group.installationType
|
|
14584
|
+
});
|
|
14585
|
+
if (state.graphLock) {
|
|
14586
|
+
const lock = state.graphLock;
|
|
13634
14587
|
const root = lock.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
13635
14588
|
if (root?.selected.includes(selector)) return true;
|
|
13636
14589
|
}
|
|
@@ -13933,13 +14886,30 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
13933
14886
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
13934
14887
|
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
13935
14888
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
13936
|
-
|
|
13937
|
-
group.target
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
14889
|
+
if (behavior.readOnly !== true && targetOptions.dryRun !== true) {
|
|
14890
|
+
const pendingState = await journalStateForTarget(group.target, {
|
|
14891
|
+
...targetOptions,
|
|
14892
|
+
installationType: group.installationType
|
|
14893
|
+
});
|
|
14894
|
+
if (await readApplyJournal(
|
|
14895
|
+
pendingState.installRoot,
|
|
14896
|
+
pendingState.adapter.name,
|
|
14897
|
+
pendingState.transport,
|
|
14898
|
+
pendingState.state
|
|
14899
|
+
)) {
|
|
14900
|
+
await recoverPendingApply(
|
|
14901
|
+
pendingState.installRoot,
|
|
14902
|
+
pendingState.adapter.name,
|
|
14903
|
+
pendingState.transport,
|
|
14904
|
+
pendingState.state
|
|
14905
|
+
);
|
|
14906
|
+
}
|
|
14907
|
+
}
|
|
14908
|
+
const priorGroupState = await resolveCliTargetState(group.target, {
|
|
14909
|
+
...targetOptions,
|
|
14910
|
+
installationType: group.installationType
|
|
14911
|
+
});
|
|
14912
|
+
const previousGroupLock = priorGroupState.graphLock;
|
|
13943
14913
|
const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
|
|
13944
14914
|
if (scopedDependencyUpdate) {
|
|
13945
14915
|
for (const pkg of group.packages) {
|
|
@@ -14049,14 +15019,14 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14049
15019
|
expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner
|
|
14050
15020
|
});
|
|
14051
15021
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
14052
|
-
const
|
|
14053
|
-
const
|
|
14054
|
-
const scopedResult = targetOptions.focusedArtifact ?
|
|
15022
|
+
const manifest = result.previousManifest;
|
|
15023
|
+
const previousLock = result.previousGraphLock;
|
|
15024
|
+
const scopedResult = targetOptions.focusedArtifact ? previousLock ? scopeUpdatePlanToArtifact(result, scopedRootId, targetOptions.focusedArtifact, previousLock, manifest) : scopeInstallPlanToArtifact(result, scopedRootId, targetOptions.focusedArtifact, manifest) : previousLock ? scopeUpdatePlanToRoot(result, scopedRootId, previousLock, manifest) : scopeInstallPlanToRoot(result, scopedRootId, manifest);
|
|
14055
15025
|
if (targetOptions.focusedArtifact && targetOptions.forceForeignState !== true) {
|
|
14056
15026
|
const focusedPaths = scopedResult.plan.operations.filter((operation) => operationMatchesFocusedArtifact(
|
|
14057
15027
|
operation,
|
|
14058
15028
|
targetOptions.focusedArtifact,
|
|
14059
|
-
focusedArtifactOwnerKeys(result.bundle.graphLock,
|
|
15029
|
+
focusedArtifactOwnerKeys(result.bundle.graphLock, previousLock, scopedRootId, targetOptions.focusedArtifact)
|
|
14060
15030
|
)).map((operation) => operation.relativeDestPath);
|
|
14061
15031
|
const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
|
|
14062
15032
|
fleetId: group.target.fleetId
|
|
@@ -14070,9 +15040,11 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14070
15040
|
}
|
|
14071
15041
|
results.push(scopedResult);
|
|
14072
15042
|
} else if (scopedDependencyUpdate) {
|
|
14073
|
-
const
|
|
14074
|
-
const
|
|
14075
|
-
|
|
15043
|
+
const manifest = result.previousManifest;
|
|
15044
|
+
const previousLock = result.previousGraphLock;
|
|
15045
|
+
if (!previousLock) {
|
|
15046
|
+
throw new Error(`Dependency update requires an existing graph lock for ${adapter.name} at ${group.target.targetRoot}.`);
|
|
15047
|
+
}
|
|
14076
15048
|
results.push(scopeUpdatePlanToDependencies(result, dependencyUpdateSelectors, previousLock, manifest));
|
|
14077
15049
|
} else {
|
|
14078
15050
|
results.push(result);
|
|
@@ -14115,7 +15087,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
14115
15087
|
selectedPreviousNodeIds,
|
|
14116
15088
|
selectedRootIds
|
|
14117
15089
|
);
|
|
14118
|
-
const graphLockDigest =
|
|
15090
|
+
const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
14119
15091
|
return {
|
|
14120
15092
|
...result,
|
|
14121
15093
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -14241,7 +15213,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
|
|
|
14241
15213
|
function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
14242
15214
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
|
|
14243
15215
|
const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
|
|
14244
|
-
const graphLockDigest =
|
|
15216
|
+
const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
14245
15217
|
return {
|
|
14246
15218
|
...scoped,
|
|
14247
15219
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -14252,7 +15224,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
|
14252
15224
|
function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
|
|
14253
15225
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
|
|
14254
15226
|
const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
|
|
14255
|
-
const graphLockDigest =
|
|
15227
|
+
const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
14256
15228
|
return {
|
|
14257
15229
|
...scoped,
|
|
14258
15230
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -14547,7 +15519,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
|
14547
15519
|
selectedPreviousNodeIds,
|
|
14548
15520
|
/* @__PURE__ */ new Set([rootId])
|
|
14549
15521
|
);
|
|
14550
|
-
const graphLockDigest =
|
|
15522
|
+
const graphLockDigest = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
14551
15523
|
return {
|
|
14552
15524
|
...scoped,
|
|
14553
15525
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -14619,7 +15591,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
14619
15591
|
artifactType: entry.artifactType,
|
|
14620
15592
|
artifactName: entry.artifactName,
|
|
14621
15593
|
kind: entry.kind,
|
|
14622
|
-
destPath: operation?.destPath ??
|
|
15594
|
+
destPath: operation?.destPath ?? join46(targetRoot, entry.path),
|
|
14623
15595
|
relativeDestPath: entry.path,
|
|
14624
15596
|
desiredHash: entry.sourceHash,
|
|
14625
15597
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -14665,8 +15637,12 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14665
15637
|
const removedInstallationType = options.installationType ?? pkg.installationType ?? removedTarget.installationType ?? "local";
|
|
14666
15638
|
const adapter = await resolveAdapterForTarget(removedTarget, removedAdapterOptions);
|
|
14667
15639
|
const transport = transportForTarget(removedTarget);
|
|
14668
|
-
const removedState =
|
|
14669
|
-
|
|
15640
|
+
const removedState = await resolveCliTargetState(removedTarget, {
|
|
15641
|
+
...options,
|
|
15642
|
+
...removedAdapterOptions,
|
|
15643
|
+
installationType: removedInstallationType
|
|
15644
|
+
});
|
|
15645
|
+
const manifest = removedState.manifest;
|
|
14670
15646
|
if (!manifest) {
|
|
14671
15647
|
console.log(`No install manifest for ${adapter.name} at ${removedTarget.targetRoot}`);
|
|
14672
15648
|
continue;
|
|
@@ -14714,25 +15690,24 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14714
15690
|
remainingDesired = desiredArtifactsFromGraphBundle(result2.bundle);
|
|
14715
15691
|
renderedRoot = result2.bundle.root;
|
|
14716
15692
|
}
|
|
14717
|
-
const
|
|
15693
|
+
const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
|
|
15694
|
+
graphLockDigest: remainingGraphPlan?.graphLockDigest
|
|
15695
|
+
});
|
|
15696
|
+
const transitionPlan = remainingGraphPlan?.plan;
|
|
15697
|
+
const plan = {
|
|
15698
|
+
...uninstallPlan,
|
|
15699
|
+
stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
|
|
15700
|
+
stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
|
|
15701
|
+
targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
|
|
15702
|
+
};
|
|
14718
15703
|
console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
|
|
14719
15704
|
console.log(formatPlan(plan));
|
|
14720
15705
|
const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
|
|
14721
|
-
removeGraphLockPath:
|
|
14722
|
-
removedTarget.workspaceRoot,
|
|
14723
|
-
targetKeyForTarget(removedTarget, adapter.name),
|
|
14724
|
-
adapter.name,
|
|
14725
|
-
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions, removedInstallationType)
|
|
14726
|
-
)
|
|
15706
|
+
removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
|
|
14727
15707
|
};
|
|
14728
15708
|
if (!options.dryRun) {
|
|
14729
15709
|
declareMutationPath(workspaceConfigPath(target.workspaceRoot));
|
|
14730
|
-
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath :
|
|
14731
|
-
removedTarget.workspaceRoot,
|
|
14732
|
-
targetKeyForTarget(removedTarget, adapter.name),
|
|
14733
|
-
adapter.name,
|
|
14734
|
-
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions, removedInstallationType)
|
|
14735
|
-
);
|
|
15710
|
+
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
|
|
14736
15711
|
declareMutationPath(declarativeGraphPath);
|
|
14737
15712
|
}
|
|
14738
15713
|
const result = await uninstall(plan, {
|
|
@@ -14807,36 +15782,92 @@ function targetFingerprintParts(target, adapter, options, installationType) {
|
|
|
14807
15782
|
};
|
|
14808
15783
|
}
|
|
14809
15784
|
function installStateForTarget(target, adapter, options, installationType) {
|
|
14810
|
-
const
|
|
15785
|
+
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
|
|
15786
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
15787
|
+
targetFingerprintParts: targetFingerprintParts(target, adapter, options, installationType),
|
|
15788
|
+
workspaceRoot: target.workspaceRoot,
|
|
15789
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
15790
|
+
resolvedInstallRoot: installRoot,
|
|
15791
|
+
transportKind: target.transport
|
|
15792
|
+
});
|
|
14811
15793
|
return {
|
|
14812
15794
|
installationType,
|
|
14813
15795
|
stateKey: stateKeyFor(adapter.name, {
|
|
14814
15796
|
installationType,
|
|
14815
15797
|
stateKey: target.stateKey,
|
|
14816
|
-
targetFingerprint,
|
|
15798
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
14817
15799
|
fleetId: target.fleetId
|
|
14818
15800
|
}),
|
|
14819
|
-
installRoot
|
|
15801
|
+
installRoot
|
|
14820
15802
|
};
|
|
14821
15803
|
}
|
|
14822
|
-
function
|
|
14823
|
-
|
|
14824
|
-
|
|
15804
|
+
function targetIdentityContext(target, adapter, installationType) {
|
|
15805
|
+
return {
|
|
15806
|
+
resolvedInstallRoot: installRootForAdapterInstallationType(
|
|
15807
|
+
adapter,
|
|
15808
|
+
target.targetRoot,
|
|
15809
|
+
installationType,
|
|
15810
|
+
target.transport === "ssh"
|
|
15811
|
+
),
|
|
15812
|
+
transportKind: target.transport
|
|
15813
|
+
};
|
|
14825
15814
|
}
|
|
14826
|
-
async function
|
|
15815
|
+
async function resolveCliTargetState(target, options) {
|
|
14827
15816
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
14828
15817
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15818
|
+
const transport = transportForTarget(target);
|
|
14829
15819
|
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
14830
|
-
const
|
|
15820
|
+
const stable = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
15821
|
+
const stableGraphLockPath = graphLockPathForTarget(
|
|
14831
15822
|
target.workspaceRoot,
|
|
14832
15823
|
targetKeyForTarget(target, adapter.name),
|
|
14833
15824
|
adapter.name,
|
|
14834
|
-
targetFingerprintParts(target, adapter, adapterOptions, installationType)
|
|
15825
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
15826
|
+
targetIdentityContext(target, adapter, installationType)
|
|
14835
15827
|
);
|
|
14836
|
-
|
|
14837
|
-
|
|
15828
|
+
const [stableManifest, stableLock, stableSourceLock, ownership] = await Promise.all([
|
|
15829
|
+
readInstallManifest(stable.installRoot, adapter.name, transport, stable),
|
|
15830
|
+
pathExists(stableGraphLockPath).then((exists) => exists ? readGraphLock(stableGraphLockPath) : void 0),
|
|
15831
|
+
readSourceLock(stable.installRoot, adapter.name, transport, stable),
|
|
15832
|
+
resolveWorkspaceOwnershipScope(target.workspaceRoot, { fleetId: target.fleetId })
|
|
15833
|
+
]);
|
|
15834
|
+
const prior = await resolvePriorTargetState({
|
|
15835
|
+
adapter: adapter.name,
|
|
15836
|
+
installationType,
|
|
15837
|
+
stateKey: stable.stateKey,
|
|
15838
|
+
explicitStateKey: target.stateKey,
|
|
15839
|
+
fleetId: target.fleetId,
|
|
15840
|
+
installRoot: stable.installRoot,
|
|
15841
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
15842
|
+
graphLockPath: stableGraphLockPath,
|
|
15843
|
+
stableManifest,
|
|
15844
|
+
stableLock,
|
|
15845
|
+
transport
|
|
15846
|
+
});
|
|
15847
|
+
return {
|
|
15848
|
+
adapter,
|
|
15849
|
+
installationType,
|
|
15850
|
+
installRoot: stable.installRoot,
|
|
15851
|
+
stateKey: prior.migration?.fromStateKey ?? stable.stateKey,
|
|
15852
|
+
stableStateKey: stable.stateKey,
|
|
15853
|
+
stableGraphLockPath,
|
|
15854
|
+
manifest: prior.manifest,
|
|
15855
|
+
graphLock: prior.graphLock,
|
|
15856
|
+
graphLockPath: prior.graphLock ? prior.migration?.fromGraphLockPath ?? stableGraphLockPath : null,
|
|
15857
|
+
migration: prior.migration,
|
|
15858
|
+
targetStateFilePreconditions: {
|
|
15859
|
+
graphLockPath: stableGraphLockPath,
|
|
15860
|
+
graphLockRevision: stableLock ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
|
|
15861
|
+
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
15862
|
+
}
|
|
15863
|
+
};
|
|
15864
|
+
}
|
|
15865
|
+
async function readTargetGraphLock(target, options) {
|
|
15866
|
+
const state = await resolveCliTargetState(target, options);
|
|
15867
|
+
if (!state.graphLock || !state.graphLockPath) {
|
|
15868
|
+
throw new Error(`No graph lock for ${state.adapter.name} at ${target.targetRoot}: ${state.stableGraphLockPath}`);
|
|
14838
15869
|
}
|
|
14839
|
-
return { adapter, path, lock:
|
|
15870
|
+
return { adapter: state.adapter, path: state.graphLockPath, lock: state.graphLock };
|
|
14840
15871
|
}
|
|
14841
15872
|
async function printStatus(target, options) {
|
|
14842
15873
|
const report = await collectTargetStatus(target, options);
|
|
@@ -14844,30 +15875,24 @@ async function printStatus(target, options) {
|
|
|
14844
15875
|
}
|
|
14845
15876
|
async function collectTargetStatus(target, options) {
|
|
14846
15877
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14847
|
-
const
|
|
14848
|
-
const adapter
|
|
14849
|
-
const
|
|
14850
|
-
const
|
|
14851
|
-
const
|
|
14852
|
-
const
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
const result = await readTargetGraphLock(target, adapterOptions);
|
|
14857
|
-
graphLockPath = result.path;
|
|
14858
|
-
graphLock = result.lock;
|
|
14859
|
-
} catch {
|
|
14860
|
-
graphLock = void 0;
|
|
14861
|
-
}
|
|
15878
|
+
const state = await resolveCliTargetState(target, { ...options, warn: () => void 0 });
|
|
15879
|
+
const { adapter, installationType, manifest, graphLock, graphLockPath } = state;
|
|
15880
|
+
const pending = await collectPendingInstallWork(target, options);
|
|
15881
|
+
const foreignObservations = pending.foreignStateObservations;
|
|
15882
|
+
const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
|
|
15883
|
+
const observedManifestRevision = foreignObservations.length > 0 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
|
|
15884
|
+
observation.fileName,
|
|
15885
|
+
observation.manifestRevision
|
|
15886
|
+
]).sort())).digest("hex") : null;
|
|
14862
15887
|
const packages = [];
|
|
14863
15888
|
for (const pkg of config.packages) {
|
|
14864
|
-
const root =
|
|
14865
|
-
const node = root ?
|
|
15889
|
+
const root = effectiveGraphLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
15890
|
+
const node = root ? effectiveGraphLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId) : void 0;
|
|
14866
15891
|
const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
|
|
14867
15892
|
forceRefresh: options.refresh,
|
|
14868
15893
|
offline: options.offline
|
|
14869
15894
|
});
|
|
14870
|
-
const installed = node && manifest?.entries.some((entry) => "graphNodeId" in entry && entry.graphNodeId === node.id) ? node.version : null;
|
|
15895
|
+
const installed = node && (manifest?.entries.some((entry) => "graphNodeId" in entry && entry.graphNodeId === node.id) || foreignObservations.some((observation) => observation.graphNodeId === node.id)) ? node.version : null;
|
|
14871
15896
|
const locked = node?.version ?? null;
|
|
14872
15897
|
const baseline = installed ?? locked;
|
|
14873
15898
|
packages.push({
|
|
@@ -14890,14 +15915,13 @@ async function collectTargetStatus(target, options) {
|
|
|
14890
15915
|
)
|
|
14891
15916
|
});
|
|
14892
15917
|
}
|
|
14893
|
-
const
|
|
14894
|
-
|
|
14895
|
-
const
|
|
14896
|
-
const installed = manifest?.entries.some((entry) => {
|
|
15918
|
+
const artifacts = (effectiveGraphLock?.canonical.artifacts ?? []).map((artifact) => {
|
|
15919
|
+
const node = effectiveGraphLock?.canonical.nodes.find((candidate) => candidate.id === artifact.graphNodeId);
|
|
15920
|
+
const installed = (manifest?.entries.some((entry) => {
|
|
14897
15921
|
if (!("graphNodeId" in entry) || entry.graphNodeId !== artifact.graphNodeId) return false;
|
|
14898
15922
|
if ("logicalSelector" in entry && entry.logicalSelector) return entry.logicalSelector === artifact.logicalSelector;
|
|
14899
15923
|
return entry.artifactType === artifact.type && entry.artifactName === artifact.name;
|
|
14900
|
-
}) ?? false;
|
|
15924
|
+
}) ?? false) || foreignObservations.some((observation) => observation.graphNodeId === artifact.graphNodeId && (!observation.logicalSelector || observation.logicalSelector === artifact.logicalSelector));
|
|
14901
15925
|
return {
|
|
14902
15926
|
selector: artifact.logicalSelector,
|
|
14903
15927
|
type: artifact.type,
|
|
@@ -14910,7 +15934,7 @@ async function collectTargetStatus(target, options) {
|
|
|
14910
15934
|
};
|
|
14911
15935
|
});
|
|
14912
15936
|
const health = [];
|
|
14913
|
-
if (!manifest || !
|
|
15937
|
+
if (!manifest && foreignObservations.length === 0 || !effectiveGraphLock) health.push("FAIL");
|
|
14914
15938
|
if (pending.error) health.push("DEGRADED");
|
|
14915
15939
|
if (pending.driftCount > 0 || pending.conflictCount > 0) health.push("FAIL");
|
|
14916
15940
|
else if (pending.pendingCount > 0) health.push("WARN");
|
|
@@ -14921,11 +15945,11 @@ async function collectTargetStatus(target, options) {
|
|
|
14921
15945
|
installationType,
|
|
14922
15946
|
targetRoot: state.installRoot,
|
|
14923
15947
|
health: worstStatusHealth(health),
|
|
14924
|
-
manifestRevision: manifest?.revision ??
|
|
14925
|
-
manifestEntryCount: manifest?.entries.length ?? 0,
|
|
15948
|
+
manifestRevision: manifest?.revision ?? observedManifestRevision,
|
|
15949
|
+
manifestEntryCount: (manifest?.entries.length ?? 0) + foreignObservations.length,
|
|
14926
15950
|
graphLockPath,
|
|
14927
15951
|
packageCount: packages.length,
|
|
14928
|
-
artifactCount:
|
|
15952
|
+
artifactCount: effectiveGraphLock?.canonical.artifacts.length ?? 0,
|
|
14929
15953
|
pendingCount: pending.pendingCount,
|
|
14930
15954
|
driftCount: pending.driftCount,
|
|
14931
15955
|
conflictCount: pending.conflictCount,
|
|
@@ -15103,7 +16127,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
15103
16127
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
15104
16128
|
);
|
|
15105
16129
|
}
|
|
15106
|
-
const memberRoot =
|
|
16130
|
+
const memberRoot = resolve26(workspaceRoot, member.workspace);
|
|
15107
16131
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
15108
16132
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
15109
16133
|
throw new Error(
|
|
@@ -15260,9 +16284,34 @@ async function journalStateForTarget(target, options) {
|
|
|
15260
16284
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
15261
16285
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15262
16286
|
const transport = transportForTarget(target);
|
|
15263
|
-
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
15264
|
-
const
|
|
15265
|
-
|
|
16287
|
+
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
16288
|
+
const stable = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
16289
|
+
const graphLockPath = graphLockPathForTarget(
|
|
16290
|
+
target.workspaceRoot,
|
|
16291
|
+
targetKeyForTarget(target, adapter.name),
|
|
16292
|
+
adapter.name,
|
|
16293
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
16294
|
+
targetIdentityContext(target, adapter, installationType)
|
|
16295
|
+
);
|
|
16296
|
+
const ownership = await resolveWorkspaceOwnershipScope(target.workspaceRoot, { fleetId: target.fleetId });
|
|
16297
|
+
const pending = await discoverTargetApplyJournal({
|
|
16298
|
+
adapter: adapter.name,
|
|
16299
|
+
installationType,
|
|
16300
|
+
stateKey: stable.stateKey,
|
|
16301
|
+
explicitStateKey: target.stateKey,
|
|
16302
|
+
fleetId: target.fleetId,
|
|
16303
|
+
installRoot: stable.installRoot,
|
|
16304
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
16305
|
+
graphLockPath,
|
|
16306
|
+
transport
|
|
16307
|
+
});
|
|
16308
|
+
return {
|
|
16309
|
+
adapter,
|
|
16310
|
+
transport,
|
|
16311
|
+
installationType,
|
|
16312
|
+
installRoot: stable.installRoot,
|
|
16313
|
+
state: { installationType, stateKey: pending?.stateKey ?? stable.stateKey }
|
|
16314
|
+
};
|
|
15266
16315
|
}
|
|
15267
16316
|
async function collectPendingInstallWork(target, options) {
|
|
15268
16317
|
let results = [];
|
|
@@ -15283,11 +16332,20 @@ async function collectPendingInstallWork(target, options) {
|
|
|
15283
16332
|
pendingCount: pending.length,
|
|
15284
16333
|
driftCount: counts.drift ?? 0,
|
|
15285
16334
|
conflictCount: counts.conflict ?? 0,
|
|
15286
|
-
counts
|
|
16335
|
+
counts,
|
|
16336
|
+
foreignStateObservations: results.flatMap((result) => result.foreignStateObservations),
|
|
16337
|
+
observedGraphLock: results.length === 1 ? results[0].bundle.graphLock : void 0
|
|
15287
16338
|
};
|
|
15288
16339
|
} catch (error) {
|
|
15289
16340
|
const message = error instanceof Error ? error.message : String(error);
|
|
15290
|
-
return {
|
|
16341
|
+
return {
|
|
16342
|
+
pendingCount: 0,
|
|
16343
|
+
driftCount: 0,
|
|
16344
|
+
conflictCount: 0,
|
|
16345
|
+
counts: {},
|
|
16346
|
+
foreignStateObservations: [],
|
|
16347
|
+
error: message
|
|
16348
|
+
};
|
|
15291
16349
|
} finally {
|
|
15292
16350
|
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
15293
16351
|
}
|
|
@@ -15300,18 +16358,17 @@ async function printDoctor(target, options) {
|
|
|
15300
16358
|
if (!targetMapping?.enabled) {
|
|
15301
16359
|
throw new Error(`Adapter ${adapter.name} does not support skills for installation type '${installationType}'.`);
|
|
15302
16360
|
}
|
|
15303
|
-
const
|
|
15304
|
-
const
|
|
15305
|
-
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
16361
|
+
const state = await resolveCliTargetState(target, { ...options, installationType });
|
|
16362
|
+
const manifest = state.manifest;
|
|
15306
16363
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
15307
16364
|
const skills = [];
|
|
15308
16365
|
for (const request of requestedSkills) {
|
|
15309
|
-
const skillPath =
|
|
16366
|
+
const skillPath = join46(state.installRoot, targetMapping.dest, request.name);
|
|
15310
16367
|
const exists = await pathExists(skillPath);
|
|
15311
16368
|
const manifestEntry = manifest?.entries.find((entry) => {
|
|
15312
16369
|
if (entry.artifactType !== "skills") return false;
|
|
15313
16370
|
const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
|
|
15314
|
-
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path ===
|
|
16371
|
+
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join46(targetMapping.dest, request.name);
|
|
15315
16372
|
});
|
|
15316
16373
|
const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
|
|
15317
16374
|
skills.push({
|
|
@@ -15391,7 +16448,7 @@ function doctorSkillLabel(name) {
|
|
|
15391
16448
|
return `${name} skill`;
|
|
15392
16449
|
}
|
|
15393
16450
|
function isSyncwheelWorkspace(targetRoot) {
|
|
15394
|
-
return existsSync(
|
|
16451
|
+
return existsSync(join46(targetRoot, ".syncwheel", "manifest.json"));
|
|
15395
16452
|
}
|
|
15396
16453
|
function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
|
|
15397
16454
|
const args = [
|
|
@@ -15554,14 +16611,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
15554
16611
|
}
|
|
15555
16612
|
function normalizeCliPath(value) {
|
|
15556
16613
|
if (value === "~") return homedir12();
|
|
15557
|
-
if (value.startsWith("~/")) return
|
|
15558
|
-
return
|
|
16614
|
+
if (value.startsWith("~/")) return resolve26(homedir12(), value.slice(2));
|
|
16615
|
+
return resolve26(value);
|
|
15559
16616
|
}
|
|
15560
16617
|
function shellQuoteArgument(value) {
|
|
15561
16618
|
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
15562
16619
|
}
|
|
15563
16620
|
function isHomePath(path) {
|
|
15564
|
-
return
|
|
16621
|
+
return resolve26(path) === resolve26(homedir12());
|
|
15565
16622
|
}
|
|
15566
16623
|
function adapterListFromOption(adapter) {
|
|
15567
16624
|
if (!adapter) return [];
|
|
@@ -15616,11 +16673,11 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
15616
16673
|
};
|
|
15617
16674
|
}
|
|
15618
16675
|
async function initPackage(root) {
|
|
15619
|
-
await mkdir16(
|
|
15620
|
-
await mkdir16(
|
|
15621
|
-
await mkdir16(
|
|
15622
|
-
const manifestPath =
|
|
15623
|
-
const instructionsPath =
|
|
16676
|
+
await mkdir16(join46(root, "instructions"), { recursive: true });
|
|
16677
|
+
await mkdir16(join46(root, "rules"), { recursive: true });
|
|
16678
|
+
await mkdir16(join46(root, "skills"), { recursive: true });
|
|
16679
|
+
const manifestPath = join46(root, "openpack.json");
|
|
16680
|
+
const instructionsPath = join46(root, "instructions", "AGENTS.md");
|
|
15624
16681
|
const manifest = {
|
|
15625
16682
|
schemaVersion: CURRENT_OPENPACK_SCHEMA_VERSION,
|
|
15626
16683
|
name: "example/agentwheel-package",
|
|
@@ -15638,7 +16695,7 @@ async function initPackage(root) {
|
|
|
15638
16695
|
await writeFile13(instructionsPath, "# Agent Instructions\n", "utf8");
|
|
15639
16696
|
}
|
|
15640
16697
|
async function defaultBootstrapPackage(_root) {
|
|
15641
|
-
const packageRoot = await findAgentwheelPackageRoot(
|
|
16698
|
+
const packageRoot = await findAgentwheelPackageRoot(dirname23(fileURLToPath3(import.meta.url)));
|
|
15642
16699
|
if (!packageRoot) return void 0;
|
|
15643
16700
|
return {
|
|
15644
16701
|
name: "agentwheel",
|
|
@@ -15682,10 +16739,10 @@ function withFleetExample(config) {
|
|
|
15682
16739
|
};
|
|
15683
16740
|
}
|
|
15684
16741
|
async function findAgentwheelPackageRoot(start) {
|
|
15685
|
-
let current =
|
|
16742
|
+
let current = resolve26(start);
|
|
15686
16743
|
while (true) {
|
|
15687
16744
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
15688
|
-
const parent =
|
|
16745
|
+
const parent = dirname23(current);
|
|
15689
16746
|
if (parent === current) return void 0;
|
|
15690
16747
|
current = parent;
|
|
15691
16748
|
}
|
|
@@ -15810,14 +16867,14 @@ async function governedCommandContext(command, options) {
|
|
|
15810
16867
|
const kind = String(command.args[0] ?? "workspace");
|
|
15811
16868
|
return {
|
|
15812
16869
|
workspaceRoot: workspaceRoot2,
|
|
15813
|
-
anticipatedPaths: kind === "package" ? [
|
|
16870
|
+
anticipatedPaths: kind === "package" ? [join46(targetRoot, "openpack.json"), join46(targetRoot, "instructions", "AGENTS.md")] : [workspaceConfigPath(targetRoot)]
|
|
15814
16871
|
};
|
|
15815
16872
|
}
|
|
15816
16873
|
if (path === "package migrate") {
|
|
15817
16874
|
const packageRoot = normalizeTargetRoot(String(command.args[0] ?? "."));
|
|
15818
16875
|
return {
|
|
15819
16876
|
workspaceRoot: await findExistingWorkspaceRoot(packageRoot) ?? packageRoot,
|
|
15820
|
-
anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) =>
|
|
16877
|
+
anticipatedPaths: ["agentwheel.json", "agentwheel.jsonc", "openpack.json", "openpack.jsonc"].map((name) => join46(packageRoot, name))
|
|
15821
16878
|
};
|
|
15822
16879
|
}
|
|
15823
16880
|
const source = typeof command.args[0] === "string" ? command.args[0] : void 0;
|
|
@@ -15829,7 +16886,7 @@ async function governedCommandContext(command, options) {
|
|
|
15829
16886
|
const anticipatedPaths = [];
|
|
15830
16887
|
if (path === "add") anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
|
|
15831
16888
|
if (path === "remember" && options.runtime) {
|
|
15832
|
-
anticipatedPaths.push(
|
|
16889
|
+
anticipatedPaths.push(join46(workspaceRoot, ".agentwheel", "overlays", options.runtime, "instructions.local.md"));
|
|
15833
16890
|
}
|
|
15834
16891
|
if (path === "uninstall" && source) anticipatedPaths.push(workspaceConfigPath(workspaceRoot));
|
|
15835
16892
|
return {
|