agentwheel 0.20.8 → 0.20.9
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-JBULQGW5.js} +2 -2
- package/dist/{chunk-4AM3LNUQ.js → chunk-E7Z2YB2C.js} +118 -6
- package/dist/{chunk-JXX6EDIP.js → chunk-SHPNLNBR.js} +550 -309
- package/dist/index.js +965 -239
- package/dist/{transaction-D7VRZZX5.js → transaction-EPP7GHXD.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
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
commitManifestMetadataJournal,
|
|
13
13
|
computeInstallManifestInventoryRevision,
|
|
14
14
|
computeManifestRevision,
|
|
15
|
+
computeSourceLockRevision,
|
|
15
16
|
computeTargetFingerprint,
|
|
16
17
|
desiredManagedInstructionBlockHash,
|
|
17
18
|
hasMergeRemovalContent,
|
|
18
19
|
installManifestSchema,
|
|
20
|
+
installManifestV2Schema,
|
|
19
21
|
legacyUnownedWorkspaceOwner,
|
|
20
22
|
listInstallManifests,
|
|
21
23
|
managedInstructionBlockMode,
|
|
@@ -29,11 +31,12 @@ import {
|
|
|
29
31
|
readGraphLock,
|
|
30
32
|
readInstallManifest,
|
|
31
33
|
readManagedInstructionBlockState,
|
|
34
|
+
readSourceLock,
|
|
32
35
|
recoverPendingApply,
|
|
33
36
|
uninstall,
|
|
34
37
|
withManifestRevision,
|
|
35
38
|
writeInstallManifest
|
|
36
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-E7Z2YB2C.js";
|
|
37
40
|
import {
|
|
38
41
|
CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
39
42
|
GovernedMutation,
|
|
@@ -94,7 +97,7 @@ import {
|
|
|
94
97
|
workspaceSelectionImportSchema,
|
|
95
98
|
writeApplyJournal,
|
|
96
99
|
writeWorkspaceConfig
|
|
97
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-SHPNLNBR.js";
|
|
98
101
|
import {
|
|
99
102
|
inferSourceDriverName
|
|
100
103
|
} from "./chunk-EMDIG24I.js";
|
|
@@ -113,7 +116,7 @@ import { createHash as createHash14 } from "crypto";
|
|
|
113
116
|
import { existsSync } from "fs";
|
|
114
117
|
import { mkdir as mkdir16, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
115
118
|
import { homedir as homedir12 } from "os";
|
|
116
|
-
import { dirname as dirname22, join as join45, resolve as
|
|
119
|
+
import { dirname as dirname22, join as join45, resolve as resolve25 } from "path";
|
|
117
120
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
118
121
|
import { Command } from "commander";
|
|
119
122
|
|
|
@@ -917,11 +920,11 @@ function normalizeArtifactSelectors(select, legacySkills) {
|
|
|
917
920
|
if (selected.length === 0) return void 0;
|
|
918
921
|
return [...new Set(selected.map(parseArtifactSelector))];
|
|
919
922
|
}
|
|
920
|
-
function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
923
|
+
function filterArtifactsBySelection(artifacts, selectors, legacySkills, options = {}) {
|
|
921
924
|
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
922
925
|
if (!selected?.length) return artifacts;
|
|
923
926
|
const selectedSet = new Set(selected);
|
|
924
|
-
const available = new Set(artifacts.flatMap(artifactSelectorAliases));
|
|
927
|
+
const available = new Set((options.validationArtifacts ?? artifacts).flatMap(artifactSelectorAliases));
|
|
925
928
|
const missing = selected.filter((selector) => !available.has(selector));
|
|
926
929
|
if (missing.length > 0) {
|
|
927
930
|
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
@@ -1706,6 +1709,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1706
1709
|
hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
|
|
1707
1710
|
baseRevision: options.baseRevision ?? manifest?.revision ?? null,
|
|
1708
1711
|
runtimeStateRevision,
|
|
1712
|
+
stateMigration: options.stateMigration,
|
|
1709
1713
|
migrationReport: migration.report,
|
|
1710
1714
|
graphLockDigest: options.graphLockDigest,
|
|
1711
1715
|
adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
|
|
@@ -2388,6 +2392,7 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
|
|
|
2388
2392
|
graphNodeId: entry.graphNodeId,
|
|
2389
2393
|
dependencyRole: entry.dependencyRole,
|
|
2390
2394
|
owners: ownersOverride ?? entry.owners,
|
|
2395
|
+
workspaceOwner: entry.workspaceOwner,
|
|
2391
2396
|
graphLockDigest: entry.graphLockDigest,
|
|
2392
2397
|
mergeRemoval: entry.mergeRemoval,
|
|
2393
2398
|
mergeCreatedDestination: entry.mergeCreatedDestination
|
|
@@ -3107,14 +3112,14 @@ async function writeMermaidAsset(response) {
|
|
|
3107
3112
|
"content-type": "application/javascript; charset=utf-8",
|
|
3108
3113
|
"cache-control": "public, max-age=3600"
|
|
3109
3114
|
});
|
|
3110
|
-
await new Promise((
|
|
3115
|
+
await new Promise((resolve26) => {
|
|
3111
3116
|
const stream = createReadStream(assetPath);
|
|
3112
3117
|
stream.on("error", () => {
|
|
3113
3118
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
3114
3119
|
else response.destroy();
|
|
3115
|
-
|
|
3120
|
+
resolve26();
|
|
3116
3121
|
});
|
|
3117
|
-
stream.on("end",
|
|
3122
|
+
stream.on("end", resolve26);
|
|
3118
3123
|
stream.pipe(response);
|
|
3119
3124
|
});
|
|
3120
3125
|
}
|
|
@@ -3141,14 +3146,14 @@ function escapeHtml2(value) {
|
|
|
3141
3146
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3142
3147
|
}
|
|
3143
3148
|
function listen(server, port, bind) {
|
|
3144
|
-
return new Promise((
|
|
3149
|
+
return new Promise((resolve26, reject) => {
|
|
3145
3150
|
const onError = (error) => {
|
|
3146
3151
|
server.off("listening", onListening);
|
|
3147
3152
|
reject(error);
|
|
3148
3153
|
};
|
|
3149
3154
|
const onListening = () => {
|
|
3150
3155
|
server.off("error", onError);
|
|
3151
|
-
|
|
3156
|
+
resolve26();
|
|
3152
3157
|
};
|
|
3153
3158
|
server.once("error", onError);
|
|
3154
3159
|
server.once("listening", onListening);
|
|
@@ -3156,7 +3161,7 @@ function listen(server, port, bind) {
|
|
|
3156
3161
|
});
|
|
3157
3162
|
}
|
|
3158
3163
|
function waitForShutdown(server, beforeClose) {
|
|
3159
|
-
return new Promise((
|
|
3164
|
+
return new Promise((resolve26) => {
|
|
3160
3165
|
let closing = false;
|
|
3161
3166
|
const shutdown = () => {
|
|
3162
3167
|
if (closing) return;
|
|
@@ -3164,7 +3169,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3164
3169
|
process.off("SIGINT", shutdown);
|
|
3165
3170
|
process.off("SIGTERM", shutdown);
|
|
3166
3171
|
beforeClose();
|
|
3167
|
-
server.close(() =>
|
|
3172
|
+
server.close(() => resolve26());
|
|
3168
3173
|
};
|
|
3169
3174
|
process.once("SIGINT", shutdown);
|
|
3170
3175
|
process.once("SIGTERM", shutdown);
|
|
@@ -3174,7 +3179,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3174
3179
|
process.off("SIGINT", shutdown);
|
|
3175
3180
|
process.off("SIGTERM", shutdown);
|
|
3176
3181
|
beforeClose();
|
|
3177
|
-
|
|
3182
|
+
resolve26();
|
|
3178
3183
|
});
|
|
3179
3184
|
});
|
|
3180
3185
|
}
|
|
@@ -3727,7 +3732,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
3727
3732
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
3728
3733
|
}
|
|
3729
3734
|
async function delay(ms) {
|
|
3730
|
-
await new Promise((
|
|
3735
|
+
await new Promise((resolve26) => setTimeout(resolve26, ms));
|
|
3731
3736
|
}
|
|
3732
3737
|
function parseClawHubSource(source) {
|
|
3733
3738
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -3904,7 +3909,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
|
|
|
3904
3909
|
if (Date.now() - started > timeoutMs) {
|
|
3905
3910
|
throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
|
|
3906
3911
|
}
|
|
3907
|
-
await new Promise((
|
|
3912
|
+
await new Promise((resolve26) => setTimeout(resolve26, 50));
|
|
3908
3913
|
}
|
|
3909
3914
|
}
|
|
3910
3915
|
try {
|
|
@@ -6570,7 +6575,7 @@ async function collectIncludeSelector(raw, optional, artifactsByRelativePath, sc
|
|
|
6570
6575
|
}
|
|
6571
6576
|
function artifactTargetsRuntime(runtimes, runtime, nodeId, selector, warn) {
|
|
6572
6577
|
if (!runtimes?.length || !runtime || runtimes.includes(runtime)) return true;
|
|
6573
|
-
warn?.(`skip artifact ${nodeId}:${selector} (not targeted: runtimes=[${runtimes.join(",")}])`);
|
|
6578
|
+
warn?.(`skip artifact ${nodeId}:${selector} (selected but not targeted: runtimes=[${runtimes.join(",")}])`);
|
|
6574
6579
|
return false;
|
|
6575
6580
|
}
|
|
6576
6581
|
function requirementTargetsRuntime(runtimes, runtime, label, warn) {
|
|
@@ -6770,8 +6775,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
6770
6775
|
async function withCachePathLock(path, fn) {
|
|
6771
6776
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
6772
6777
|
let release = () => void 0;
|
|
6773
|
-
const current = previous.then(() => new Promise((
|
|
6774
|
-
release =
|
|
6778
|
+
const current = previous.then(() => new Promise((resolve26) => {
|
|
6779
|
+
release = resolve26;
|
|
6775
6780
|
}));
|
|
6776
6781
|
cacheLocks.set(path, current);
|
|
6777
6782
|
await previous;
|
|
@@ -6833,6 +6838,11 @@ function detectDirectCollisions(nodes) {
|
|
|
6833
6838
|
for (const [selector, owners] of bySelector) {
|
|
6834
6839
|
const uniqueOwners = [...new Map(owners.map((owner) => [owner.node.id, owner])).values()];
|
|
6835
6840
|
if (uniqueOwners.length <= 1) continue;
|
|
6841
|
+
const selectedArtifacts = uniqueOwners.map((owner) => ({
|
|
6842
|
+
owner,
|
|
6843
|
+
artifact: owner.artifacts.find((candidate) => artifactSelectorKey(candidate) === selector)
|
|
6844
|
+
}));
|
|
6845
|
+
if (new Set(selectedArtifacts.map(({ artifact }) => artifact.hash)).size === 1) continue;
|
|
6836
6846
|
const replacements = uniqueOwners.filter((owner) => {
|
|
6837
6847
|
const artifact = owner.artifacts.find((candidate) => artifactSelectorKey(candidate) === selector);
|
|
6838
6848
|
return uniqueOwners.every((other) => {
|
|
@@ -6842,7 +6852,7 @@ function detectDirectCollisions(nodes) {
|
|
|
6842
6852
|
});
|
|
6843
6853
|
if (replacements.length === 1) continue;
|
|
6844
6854
|
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.`
|
|
6855
|
+
`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
6856
|
);
|
|
6847
6857
|
}
|
|
6848
6858
|
}
|
|
@@ -7027,8 +7037,8 @@ import { rm as rm7 } from "fs/promises";
|
|
|
7027
7037
|
|
|
7028
7038
|
// src/lifecycle/source-plan.ts
|
|
7029
7039
|
import { createHash as createHash8 } from "crypto";
|
|
7030
|
-
import { mkdir as mkdir14 } from "fs/promises";
|
|
7031
|
-
import { dirname as dirname17, join as join33, resolve as
|
|
7040
|
+
import { mkdir as mkdir14, readdir as readdir9 } from "fs/promises";
|
|
7041
|
+
import { basename as basename18, dirname as dirname17, join as join33, posix as posix2, resolve as resolve16 } from "path";
|
|
7032
7042
|
|
|
7033
7043
|
// src/resolve/graph-diff.ts
|
|
7034
7044
|
function diffGraphLocks(previous, next) {
|
|
@@ -7226,8 +7236,10 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7226
7236
|
for (const staged of [...stagedNodes.values()].sort((a, b) => a.rawNode.node.id.localeCompare(b.rawNode.node.id))) {
|
|
7227
7237
|
const rawNode = staged.rawNode;
|
|
7228
7238
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
|
|
7229
|
-
const
|
|
7230
|
-
const
|
|
7239
|
+
const selectionValidatedArtifacts = filterArtifactsBySelection(staged.artifacts, rawNode.node.selected);
|
|
7240
|
+
const preRuntimeArtifacts = targetContext.noDeps ? selectionValidatedArtifacts : staged.artifacts;
|
|
7241
|
+
const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(preRuntimeArtifacts, targetContext.adapter.name, runtimeSelectedSet) : preRuntimeArtifacts;
|
|
7242
|
+
const compositionArtifacts = runtimeArtifacts;
|
|
7231
7243
|
const expandedArtifacts = await expandMarkdownIncludes(compositionArtifacts, staged.root, {
|
|
7232
7244
|
nodeId: rawNode.node.id,
|
|
7233
7245
|
originNodeId: rawNode.node.id,
|
|
@@ -7275,7 +7287,12 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7275
7287
|
};
|
|
7276
7288
|
}
|
|
7277
7289
|
});
|
|
7278
|
-
const selectedArtifacts = filterArtifactsBySelection(
|
|
7290
|
+
const selectedArtifacts = filterArtifactsBySelection(
|
|
7291
|
+
expandedArtifacts,
|
|
7292
|
+
rawNode.node.selected,
|
|
7293
|
+
void 0,
|
|
7294
|
+
{ validationArtifacts: staged.artifacts }
|
|
7295
|
+
);
|
|
7279
7296
|
const claudeRenderedArtifacts = await renderClaudeSubagents(selectedArtifacts, staged.root, targetContext.adapter);
|
|
7280
7297
|
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, staged.root, targetContext.adapter);
|
|
7281
7298
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
@@ -7389,8 +7406,9 @@ function sha2563(content) {
|
|
|
7389
7406
|
return createHash7("sha256").update(content).digest("hex");
|
|
7390
7407
|
}
|
|
7391
7408
|
function assignInstallNames(graph, artifacts) {
|
|
7392
|
-
const
|
|
7393
|
-
const
|
|
7409
|
+
const equivalentArtifacts = coalesceEquivalentDirectArtifacts(artifacts);
|
|
7410
|
+
const superseded = supersededLogicalSelectors(equivalentArtifacts);
|
|
7411
|
+
const effectiveArtifacts = equivalentArtifacts.filter((artifact) => !superseded.has(artifact.logicalSelector));
|
|
7394
7412
|
const aliases = workspaceAliases(graph);
|
|
7395
7413
|
validateAliasScopes(graph, effectiveArtifacts, aliases);
|
|
7396
7414
|
const decisions = /* @__PURE__ */ new Map();
|
|
@@ -7433,6 +7451,37 @@ function assignInstallNames(graph, artifacts) {
|
|
|
7433
7451
|
}));
|
|
7434
7452
|
return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
|
|
7435
7453
|
}
|
|
7454
|
+
function coalesceEquivalentDirectArtifacts(artifacts) {
|
|
7455
|
+
const equivalent = /* @__PURE__ */ new Map();
|
|
7456
|
+
const passthrough = [];
|
|
7457
|
+
for (const artifact of artifacts) {
|
|
7458
|
+
if (artifact.dependencyRole !== "direct") {
|
|
7459
|
+
passthrough.push(artifact);
|
|
7460
|
+
continue;
|
|
7461
|
+
}
|
|
7462
|
+
const key = `${artifact.type}\0${artifact.name}\0${artifact.hash}`;
|
|
7463
|
+
const group = equivalent.get(key) ?? [];
|
|
7464
|
+
group.push(artifact);
|
|
7465
|
+
equivalent.set(key, group);
|
|
7466
|
+
}
|
|
7467
|
+
for (const group of equivalent.values()) {
|
|
7468
|
+
const sorted = [...group].sort((left, right) => left.logicalSelector.localeCompare(right.logicalSelector));
|
|
7469
|
+
const canonical = sorted[0];
|
|
7470
|
+
passthrough.push({
|
|
7471
|
+
...canonical,
|
|
7472
|
+
owners: [...new Set(sorted.flatMap((artifact) => artifact.owners))].sort((left, right) => left.localeCompare(right)),
|
|
7473
|
+
composedFrom: mergeComposedFrom(sorted)
|
|
7474
|
+
});
|
|
7475
|
+
}
|
|
7476
|
+
return passthrough;
|
|
7477
|
+
}
|
|
7478
|
+
function mergeComposedFrom(artifacts) {
|
|
7479
|
+
const entries = /* @__PURE__ */ new Map();
|
|
7480
|
+
for (const entry of artifacts.flatMap((artifact) => artifact.composedFrom ?? [])) {
|
|
7481
|
+
entries.set(`${entry.selector}\0${entry.hash}`, entry);
|
|
7482
|
+
}
|
|
7483
|
+
return entries.size > 0 ? [...entries.values()].sort((left, right) => `${left.selector}\0${left.hash}`.localeCompare(`${right.selector}\0${right.hash}`)) : void 0;
|
|
7484
|
+
}
|
|
7436
7485
|
function supersededLogicalSelectors(artifacts) {
|
|
7437
7486
|
const removed = /* @__PURE__ */ new Set();
|
|
7438
7487
|
for (const replacement of artifacts) {
|
|
@@ -7595,7 +7644,8 @@ function reachableNodeIds(graph, rootNodeId) {
|
|
|
7595
7644
|
return reachable;
|
|
7596
7645
|
}
|
|
7597
7646
|
function installNameCollisionError(group) {
|
|
7598
|
-
|
|
7647
|
+
const details = group.map((artifact) => `${artifact.logicalSelector}=sha256:${artifact.hash}`).sort().join("; ");
|
|
7648
|
+
return new Error(`Install name collision for ${group[0]?.type}/${group[0]?.installName}. Artifact content hashes: ${details}`);
|
|
7599
7649
|
}
|
|
7600
7650
|
function namespaceDecision(artifact, reason) {
|
|
7601
7651
|
return {
|
|
@@ -8029,6 +8079,63 @@ function containsPath(parent, candidate) {
|
|
|
8029
8079
|
return path === "" || !path.startsWith("..") && !isAbsolute2(path);
|
|
8030
8080
|
}
|
|
8031
8081
|
|
|
8082
|
+
// src/model/target-state.ts
|
|
8083
|
+
import { posix, resolve as resolve15 } from "path";
|
|
8084
|
+
var targetEvolutionFields = /* @__PURE__ */ new Set([
|
|
8085
|
+
"adapterCodeHash",
|
|
8086
|
+
"adapterConfig",
|
|
8087
|
+
"adapterModule",
|
|
8088
|
+
"targetRoot",
|
|
8089
|
+
"transportDescription",
|
|
8090
|
+
"ssh"
|
|
8091
|
+
]);
|
|
8092
|
+
function resolveTargetStateIdentity(options) {
|
|
8093
|
+
return {
|
|
8094
|
+
targetFingerprint: computeTargetFingerprint(options.targetFingerprintParts),
|
|
8095
|
+
stateFingerprint: computeTargetFingerprint({
|
|
8096
|
+
identityVersion: 1,
|
|
8097
|
+
contributionScope: {
|
|
8098
|
+
workspaceRoot: resolve15(options.workspaceRoot),
|
|
8099
|
+
targetKey: options.targetKey
|
|
8100
|
+
},
|
|
8101
|
+
target: {
|
|
8102
|
+
...persistentTargetParts(options.targetFingerprintParts),
|
|
8103
|
+
installRoot: normalizeInstallRoot(options.resolvedInstallRoot, options.transportKind),
|
|
8104
|
+
endpoint: targetEndpoint(options.targetFingerprintParts, options.transportKind)
|
|
8105
|
+
}
|
|
8106
|
+
})
|
|
8107
|
+
};
|
|
8108
|
+
}
|
|
8109
|
+
function persistentTargetParts(value) {
|
|
8110
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return { value };
|
|
8111
|
+
return Object.fromEntries(
|
|
8112
|
+
Object.entries(value).filter(([key]) => !targetEvolutionFields.has(key))
|
|
8113
|
+
);
|
|
8114
|
+
}
|
|
8115
|
+
function normalizeInstallRoot(path, transportKind) {
|
|
8116
|
+
return transportKind === "ssh" ? posix.normalize(path) : resolve15(path);
|
|
8117
|
+
}
|
|
8118
|
+
function targetEndpoint(value, transportKind) {
|
|
8119
|
+
if (transportKind !== "ssh") return { kind: "local" };
|
|
8120
|
+
const record = value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
8121
|
+
const ssh = record.ssh && typeof record.ssh === "object" && !Array.isArray(record.ssh) ? record.ssh : {};
|
|
8122
|
+
if (typeof ssh.host !== "string" || ssh.host.trim().length === 0) {
|
|
8123
|
+
throw new Error("SSH endpoint host is required for target state identity; transport description is not an identity source.");
|
|
8124
|
+
}
|
|
8125
|
+
if (ssh.user !== void 0 && (typeof ssh.user !== "string" || ssh.user.trim().length === 0)) {
|
|
8126
|
+
throw new Error("SSH target state identity requires a non-empty endpoint user when one is provided.");
|
|
8127
|
+
}
|
|
8128
|
+
if (ssh.port !== void 0 && (!Number.isInteger(ssh.port) || ssh.port <= 0)) {
|
|
8129
|
+
throw new Error("SSH target state identity requires a positive integer endpoint port.");
|
|
8130
|
+
}
|
|
8131
|
+
return {
|
|
8132
|
+
kind: "ssh",
|
|
8133
|
+
host: ssh.host.trim(),
|
|
8134
|
+
user: ssh.user,
|
|
8135
|
+
port: ssh.port ?? 22
|
|
8136
|
+
};
|
|
8137
|
+
}
|
|
8138
|
+
|
|
8032
8139
|
// src/lifecycle/source-plan.ts
|
|
8033
8140
|
async function createGraphSourcePlan(options) {
|
|
8034
8141
|
if (options.roots.length === 0) {
|
|
@@ -8042,22 +8149,62 @@ async function createGraphSourcePlan(options) {
|
|
|
8042
8149
|
options.warn?.(message);
|
|
8043
8150
|
};
|
|
8044
8151
|
const installationType = options.installationType ?? resolveInstallationTypeForAdapterTarget(options.adapter);
|
|
8045
|
-
const
|
|
8152
|
+
const targetFingerprintParts2 = options.targetFingerprintParts ?? {
|
|
8046
8153
|
adapter: options.adapter.name,
|
|
8047
8154
|
fleetId: options.fleetId,
|
|
8048
8155
|
installationType,
|
|
8049
8156
|
targetRoot: options.targetRoot,
|
|
8050
8157
|
transport: transport.kind,
|
|
8051
8158
|
transportDescription: transport.description
|
|
8159
|
+
};
|
|
8160
|
+
const installRoot = installRootForAdapterInstallationType(options.adapter, options.targetRoot, installationType, transport.kind === "ssh");
|
|
8161
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
8162
|
+
targetFingerprintParts: targetFingerprintParts2,
|
|
8163
|
+
workspaceRoot,
|
|
8164
|
+
targetKey: options.targetKey ?? "default",
|
|
8165
|
+
resolvedInstallRoot: installRoot,
|
|
8166
|
+
transportKind: transport.kind
|
|
8052
8167
|
});
|
|
8168
|
+
const targetFingerprint = targetIdentity.targetFingerprint;
|
|
8053
8169
|
const stateKey = stateKeyFor(options.adapter.name, {
|
|
8054
8170
|
installationType,
|
|
8055
8171
|
stateKey: options.stateKey,
|
|
8056
|
-
targetFingerprint,
|
|
8172
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
8057
8173
|
fleetId: options.fleetId
|
|
8058
8174
|
});
|
|
8059
|
-
const
|
|
8060
|
-
|
|
8175
|
+
const graphLockPath = pathForGraphLock(
|
|
8176
|
+
workspaceRoot,
|
|
8177
|
+
options.targetKey ?? "default",
|
|
8178
|
+
options.adapter.name,
|
|
8179
|
+
targetIdentity.stateFingerprint
|
|
8180
|
+
);
|
|
8181
|
+
const ownership = await resolveWorkspaceOwnershipScope(workspaceRoot, {
|
|
8182
|
+
fleetId: options.fleetId,
|
|
8183
|
+
globalRoot: options.globalRoot
|
|
8184
|
+
});
|
|
8185
|
+
const workspaceOwner = workspaceOwnerForRoot(ownership.root, ownership.fleetId);
|
|
8186
|
+
let recoveredPendingApply = false;
|
|
8187
|
+
if (options.readOnly !== true) {
|
|
8188
|
+
const pendingJournal = await discoverTargetApplyJournal({
|
|
8189
|
+
adapter: options.adapter.name,
|
|
8190
|
+
installationType,
|
|
8191
|
+
stateKey,
|
|
8192
|
+
explicitStateKey: options.stateKey,
|
|
8193
|
+
fleetId: options.fleetId,
|
|
8194
|
+
installRoot,
|
|
8195
|
+
workspaceOwner,
|
|
8196
|
+
graphLockPath,
|
|
8197
|
+
transport
|
|
8198
|
+
});
|
|
8199
|
+
if (pendingJournal) {
|
|
8200
|
+
recoveredPendingApply = await recoverPendingApplyIfSafe(
|
|
8201
|
+
installRoot,
|
|
8202
|
+
options.adapter.name,
|
|
8203
|
+
transport,
|
|
8204
|
+
{ installationType, stateKey: pendingJournal.stateKey }
|
|
8205
|
+
);
|
|
8206
|
+
}
|
|
8207
|
+
}
|
|
8061
8208
|
const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
|
|
8062
8209
|
const trustPolicy = {
|
|
8063
8210
|
...normalizeTrustPolicy(workspaceConfig.trust),
|
|
@@ -8065,8 +8212,29 @@ async function createGraphSourcePlan(options) {
|
|
|
8065
8212
|
};
|
|
8066
8213
|
const lockMode = options.frozenLock === true || options.offline === true;
|
|
8067
8214
|
const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
|
|
8068
|
-
const
|
|
8069
|
-
|
|
8215
|
+
const readPriorState = async () => {
|
|
8216
|
+
const [stableManifest, stableLock2, stableSourceLock2] = await Promise.all([
|
|
8217
|
+
readInstallManifest(installRoot, options.adapter.name, transport, { installationType, stateKey }),
|
|
8218
|
+
readExistingGraphLock(graphLockPath),
|
|
8219
|
+
readSourceLock(installRoot, options.adapter.name, transport, { installationType, stateKey })
|
|
8220
|
+
]);
|
|
8221
|
+
const priorState2 = await resolvePriorTargetState({
|
|
8222
|
+
adapter: options.adapter.name,
|
|
8223
|
+
installationType,
|
|
8224
|
+
stateKey,
|
|
8225
|
+
explicitStateKey: options.stateKey,
|
|
8226
|
+
fleetId: options.fleetId,
|
|
8227
|
+
installRoot,
|
|
8228
|
+
workspaceOwner,
|
|
8229
|
+
graphLockPath,
|
|
8230
|
+
stableManifest,
|
|
8231
|
+
stableLock: stableLock2,
|
|
8232
|
+
transport
|
|
8233
|
+
});
|
|
8234
|
+
return { priorState: priorState2, stableLock: stableLock2, stableSourceLock: stableSourceLock2 };
|
|
8235
|
+
};
|
|
8236
|
+
const { priorState, stableLock, stableSourceLock } = await readPriorState();
|
|
8237
|
+
const previousLock = priorState.graphLock;
|
|
8070
8238
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
8071
8239
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
8072
8240
|
workspaceRoot,
|
|
@@ -8107,13 +8275,13 @@ async function createGraphSourcePlan(options) {
|
|
|
8107
8275
|
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
8108
8276
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
8109
8277
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
}
|
|
8115
|
-
const
|
|
8116
|
-
const
|
|
8278
|
+
if (normalizeInstallRoot2(resolvedInstallRoot, transport.kind) !== normalizeInstallRoot2(installRoot, transport.kind)) {
|
|
8279
|
+
throw new Error(
|
|
8280
|
+
`Adapter ${options.adapter.name} resolved a different install root after rendering; target state identity cannot be proven.`
|
|
8281
|
+
);
|
|
8282
|
+
}
|
|
8283
|
+
const manifest = priorState.manifest;
|
|
8284
|
+
const basePlan = options.retireExactMcp ? await createExactMcpRetirementPlan(
|
|
8117
8285
|
desiredArtifacts,
|
|
8118
8286
|
options.adapter,
|
|
8119
8287
|
options.targetRoot,
|
|
@@ -8135,10 +8303,18 @@ async function createGraphSourcePlan(options) {
|
|
|
8135
8303
|
forceDrift: options.forceDrift,
|
|
8136
8304
|
forceConflict: options.forceConflict,
|
|
8137
8305
|
replaceConflict: options.replaceConflict,
|
|
8306
|
+
stateMigration: priorState.migration,
|
|
8138
8307
|
warn
|
|
8139
8308
|
});
|
|
8309
|
+
const plan = priorState.migration && !basePlan.stateMigration ? { ...basePlan, stateMigration: priorState.migration } : basePlan;
|
|
8310
|
+
plan.targetStateFilePreconditions = {
|
|
8311
|
+
graphLockPath,
|
|
8312
|
+
graphLockRevision: stableLock ? digestGraphLock(stableLock) : null,
|
|
8313
|
+
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
8314
|
+
};
|
|
8315
|
+
let foreignStateObservations = [];
|
|
8140
8316
|
if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
|
|
8141
|
-
await assertNoForeignWorkspaceState({
|
|
8317
|
+
foreignStateObservations = await assertNoForeignWorkspaceState({
|
|
8142
8318
|
installRoot: resolvedInstallRoot,
|
|
8143
8319
|
adapter: options.adapter.name,
|
|
8144
8320
|
transport,
|
|
@@ -8146,9 +8322,11 @@ async function createGraphSourcePlan(options) {
|
|
|
8146
8322
|
workspaceOwner,
|
|
8147
8323
|
globalRoot: options.globalRoot,
|
|
8148
8324
|
stateKey,
|
|
8325
|
+
migratingStateKey: priorState.migration?.fromStateKey,
|
|
8149
8326
|
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8150
8327
|
plannedOperations: plan.operations
|
|
8151
8328
|
});
|
|
8329
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8152
8330
|
}
|
|
8153
8331
|
return {
|
|
8154
8332
|
plan,
|
|
@@ -8161,7 +8339,10 @@ async function createGraphSourcePlan(options) {
|
|
|
8161
8339
|
warnings,
|
|
8162
8340
|
newTransitiveSources: trustEvaluation.promptSources,
|
|
8163
8341
|
graphDiff,
|
|
8164
|
-
recoveredPendingApply
|
|
8342
|
+
recoveredPendingApply,
|
|
8343
|
+
previousManifest: priorState.manifest,
|
|
8344
|
+
previousGraphLock: priorState.graphLock,
|
|
8345
|
+
foreignStateObservations
|
|
8165
8346
|
};
|
|
8166
8347
|
}
|
|
8167
8348
|
async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
@@ -8174,12 +8355,15 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
|
8174
8355
|
workspaceOwner: options.workspaceOwner,
|
|
8175
8356
|
globalRoot: options.globalRoot,
|
|
8176
8357
|
stateKey: plan.stateKey,
|
|
8358
|
+
migratingStateKey: plan.stateMigration?.fromStateKey,
|
|
8177
8359
|
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
|
|
8178
8360
|
plannedOperations: plan.operations
|
|
8179
8361
|
});
|
|
8362
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8180
8363
|
}
|
|
8181
|
-
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
|
|
8182
|
-
|
|
8364
|
+
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2, context) {
|
|
8365
|
+
const identity = resolveTargetStateIdentity({ targetFingerprintParts: targetFingerprintParts2, workspaceRoot, targetKey: targetKey2, ...context });
|
|
8366
|
+
return pathForGraphLock(workspaceRoot, targetKey2, adapter, identity.stateFingerprint);
|
|
8183
8367
|
}
|
|
8184
8368
|
function desiredArtifactsFromGraphBundle(bundle) {
|
|
8185
8369
|
return bundle.artifacts.map((artifact) => desiredArtifactFromResolved(artifact));
|
|
@@ -8221,10 +8405,278 @@ async function readExistingGraphLock(path) {
|
|
|
8221
8405
|
if (!await pathExists(path)) return void 0;
|
|
8222
8406
|
return readGraphLock(path);
|
|
8223
8407
|
}
|
|
8408
|
+
async function discoverTargetApplyJournal(options) {
|
|
8409
|
+
const journals = await listApplyJournals(
|
|
8410
|
+
options.installRoot,
|
|
8411
|
+
options.adapter,
|
|
8412
|
+
options.transport,
|
|
8413
|
+
{ installationType: options.installationType }
|
|
8414
|
+
);
|
|
8415
|
+
const correlated = [];
|
|
8416
|
+
for (const item of journals) {
|
|
8417
|
+
const journal = item.journal;
|
|
8418
|
+
const journalStateKey = journal.stateKey ?? stateKeyFor(options.adapter, { installationType: options.installationType });
|
|
8419
|
+
if (normalizeInstallRoot2(journal.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8420
|
+
continue;
|
|
8421
|
+
}
|
|
8422
|
+
if (journalStateKey === options.stateKey) {
|
|
8423
|
+
validateJournalManifestEnvelope(journal.manifest, options, journalStateKey, item.path);
|
|
8424
|
+
correlated.push({ path: item.path, stateKey: journalStateKey });
|
|
8425
|
+
continue;
|
|
8426
|
+
}
|
|
8427
|
+
if (!journal.graphLockPath || !journal.graphLock || !journal.graphLockDigest) continue;
|
|
8428
|
+
if (resolve16(dirname17(journal.graphLockPath)) !== resolve16(dirname17(options.graphLockPath))) continue;
|
|
8429
|
+
if (options.transport.kind === "ssh") {
|
|
8430
|
+
throw new Error(
|
|
8431
|
+
`Legacy SSH apply journal at ${item.path} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the journal was preserved.`
|
|
8432
|
+
);
|
|
8433
|
+
}
|
|
8434
|
+
const suffix = ".graph-lock.json";
|
|
8435
|
+
const graphName = basename18(journal.graphLockPath);
|
|
8436
|
+
if (!graphName.endsWith(suffix)) {
|
|
8437
|
+
throw new Error(`Legacy apply journal at ${item.path} has an invalid graph-lock path.`);
|
|
8438
|
+
}
|
|
8439
|
+
const fingerprint = graphName.slice(0, -suffix.length);
|
|
8440
|
+
if (!/^[a-f0-9]{64}$/u.test(fingerprint) || journal.graphLock.canonical.targetFingerprint !== fingerprint) {
|
|
8441
|
+
throw new Error(`Legacy apply journal graph-lock fingerprint does not match its path at ${item.path}.`);
|
|
8442
|
+
}
|
|
8443
|
+
const expectedStateKey = stateKeyFor(options.adapter, {
|
|
8444
|
+
installationType: options.installationType,
|
|
8445
|
+
stateKey: options.explicitStateKey,
|
|
8446
|
+
targetFingerprint: fingerprint,
|
|
8447
|
+
fleetId: options.fleetId
|
|
8448
|
+
});
|
|
8449
|
+
if (journalStateKey !== expectedStateKey) {
|
|
8450
|
+
throw new Error(`Legacy apply journal state key does not match its graph lock at ${item.path}.`);
|
|
8451
|
+
}
|
|
8452
|
+
let actualGraphLock;
|
|
8453
|
+
try {
|
|
8454
|
+
actualGraphLock = await readGraphLock(journal.graphLockPath);
|
|
8455
|
+
} catch (error) {
|
|
8456
|
+
throw new Error(
|
|
8457
|
+
`Legacy apply journal graph lock is missing or invalid at ${journal.graphLockPath}: ` + (error instanceof Error ? error.message : String(error))
|
|
8458
|
+
);
|
|
8459
|
+
}
|
|
8460
|
+
const embeddedDigest = digestGraphLock(journal.graphLock);
|
|
8461
|
+
const actualDigest = digestGraphLock(actualGraphLock);
|
|
8462
|
+
if (embeddedDigest !== journal.graphLockDigest || actualDigest !== journal.graphLockDigest) {
|
|
8463
|
+
throw new Error(`Legacy apply journal graph-lock digest mismatch at ${item.path}.`);
|
|
8464
|
+
}
|
|
8465
|
+
const manifest = validateJournalManifestEnvelope(journal.manifest, options, journalStateKey, item.path);
|
|
8466
|
+
validateLegacyManifestCandidate(manifest, {
|
|
8467
|
+
...options,
|
|
8468
|
+
candidateStateKey: journalStateKey,
|
|
8469
|
+
graphLockDigest: journal.graphLockDigest,
|
|
8470
|
+
graphLockPath: journal.graphLockPath
|
|
8471
|
+
});
|
|
8472
|
+
correlated.push({ path: item.path, stateKey: journalStateKey });
|
|
8473
|
+
}
|
|
8474
|
+
if (correlated.length > 1) {
|
|
8475
|
+
throw new Error(`Ambiguous apply journals for ${options.adapter}: multiple journals match the requested target state.`);
|
|
8476
|
+
}
|
|
8477
|
+
return correlated[0];
|
|
8478
|
+
}
|
|
8479
|
+
function validateJournalManifestEnvelope(value, options, stateKey, journalPath) {
|
|
8480
|
+
let manifest;
|
|
8481
|
+
try {
|
|
8482
|
+
manifest = installManifestV2Schema.parse(value);
|
|
8483
|
+
} catch (error) {
|
|
8484
|
+
throw new Error(
|
|
8485
|
+
`Apply journal at ${journalPath} has an invalid install manifest: ` + (error instanceof Error ? error.message : String(error))
|
|
8486
|
+
);
|
|
8487
|
+
}
|
|
8488
|
+
if (manifest.adapter !== options.adapter || manifest.installationType !== options.installationType || manifest.stateKey !== stateKey || normalizeInstallRoot2(manifest.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8489
|
+
throw new Error(`Apply journal target identity does not match its manifest at ${journalPath}.`);
|
|
8490
|
+
}
|
|
8491
|
+
return manifest;
|
|
8492
|
+
}
|
|
8493
|
+
async function resolvePriorTargetState(options) {
|
|
8494
|
+
const candidates = await discoverLegacyStateCandidates(options);
|
|
8495
|
+
const correlated = candidates.filter((candidate) => candidate.manifest);
|
|
8496
|
+
if (options.stableManifest) {
|
|
8497
|
+
for (const candidate of correlated) {
|
|
8498
|
+
if (!sameManifestContributions(options.stableManifest, candidate.manifest)) {
|
|
8499
|
+
throw new Error(
|
|
8500
|
+
`Stable and legacy target states coexist with disagreeing contributions for ${options.adapter}; reconcile them explicitly.`
|
|
8501
|
+
);
|
|
8502
|
+
}
|
|
8503
|
+
}
|
|
8504
|
+
const sameKeyCandidates = correlated.filter((candidate) => candidate.stateKey === options.stateKey);
|
|
8505
|
+
if (!options.stableLock && sameKeyCandidates.length > 1) {
|
|
8506
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple correlated graph locks use the explicit state key.`);
|
|
8507
|
+
}
|
|
8508
|
+
if (!options.stableLock && sameKeyCandidates.length === 1) {
|
|
8509
|
+
const candidate = sameKeyCandidates[0];
|
|
8510
|
+
return {
|
|
8511
|
+
graphLock: candidate.graphLock,
|
|
8512
|
+
manifest: options.stableManifest,
|
|
8513
|
+
migration: migrationFromCandidate(candidate)
|
|
8514
|
+
};
|
|
8515
|
+
}
|
|
8516
|
+
return { graphLock: options.stableLock, manifest: options.stableManifest };
|
|
8517
|
+
}
|
|
8518
|
+
if (options.stableLock && correlated.length > 0) {
|
|
8519
|
+
throw new Error(
|
|
8520
|
+
`Stable graph state and legacy manifest state coexist for ${options.adapter} without a stable manifest; reconcile them explicitly.`
|
|
8521
|
+
);
|
|
8522
|
+
}
|
|
8523
|
+
if (correlated.length > 1) {
|
|
8524
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple correlated manifests were found.`);
|
|
8525
|
+
}
|
|
8526
|
+
if (correlated.length === 1) {
|
|
8527
|
+
const candidate = correlated[0];
|
|
8528
|
+
return {
|
|
8529
|
+
graphLock: candidate.graphLock,
|
|
8530
|
+
manifest: candidate.manifest,
|
|
8531
|
+
migration: migrationFromCandidate(candidate)
|
|
8532
|
+
};
|
|
8533
|
+
}
|
|
8534
|
+
if (options.stableLock) return { graphLock: options.stableLock };
|
|
8535
|
+
if (candidates.length > 1) {
|
|
8536
|
+
throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple graph-only candidates were found.`);
|
|
8537
|
+
}
|
|
8538
|
+
if (candidates.length === 1) {
|
|
8539
|
+
throw new Error(
|
|
8540
|
+
`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.`
|
|
8541
|
+
);
|
|
8542
|
+
}
|
|
8543
|
+
return {};
|
|
8544
|
+
}
|
|
8545
|
+
async function discoverLegacyStateCandidates(options) {
|
|
8546
|
+
const directory = dirname17(options.graphLockPath);
|
|
8547
|
+
let names;
|
|
8548
|
+
try {
|
|
8549
|
+
names = await readdir9(directory);
|
|
8550
|
+
} catch (error) {
|
|
8551
|
+
if (error.code === "ENOENT") return [];
|
|
8552
|
+
throw error;
|
|
8553
|
+
}
|
|
8554
|
+
const stableName = basename18(options.graphLockPath);
|
|
8555
|
+
const suffix = ".graph-lock.json";
|
|
8556
|
+
const candidates = [];
|
|
8557
|
+
for (const name of names.sort((left, right) => left.localeCompare(right))) {
|
|
8558
|
+
if (name === stableName || !name.endsWith(suffix)) continue;
|
|
8559
|
+
const fingerprint = name.slice(0, -suffix.length);
|
|
8560
|
+
if (!/^[a-f0-9]{64}$/u.test(fingerprint)) continue;
|
|
8561
|
+
const graphLockPath = join33(directory, name);
|
|
8562
|
+
let graphLock;
|
|
8563
|
+
try {
|
|
8564
|
+
graphLock = await readGraphLock(graphLockPath);
|
|
8565
|
+
} catch (error) {
|
|
8566
|
+
throw new Error(
|
|
8567
|
+
`Legacy graph lock at ${graphLockPath} is invalid or unsupported: ` + (error instanceof Error ? error.message : String(error))
|
|
8568
|
+
);
|
|
8569
|
+
}
|
|
8570
|
+
if (graphLock.canonical.targetFingerprint !== fingerprint) {
|
|
8571
|
+
throw new Error(
|
|
8572
|
+
`Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
|
|
8573
|
+
);
|
|
8574
|
+
}
|
|
8575
|
+
if (options.transport.kind === "ssh") {
|
|
8576
|
+
throw new Error(
|
|
8577
|
+
`Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
|
|
8578
|
+
);
|
|
8579
|
+
}
|
|
8580
|
+
const graphLockDigest = digestGraphLock(graphLock);
|
|
8581
|
+
const candidateStateKey = stateKeyFor(options.adapter, {
|
|
8582
|
+
installationType: options.installationType,
|
|
8583
|
+
stateKey: options.explicitStateKey,
|
|
8584
|
+
targetFingerprint: fingerprint,
|
|
8585
|
+
fleetId: options.fleetId
|
|
8586
|
+
});
|
|
8587
|
+
let manifest;
|
|
8588
|
+
let sourceLock;
|
|
8589
|
+
try {
|
|
8590
|
+
manifest = await readInstallManifest(options.installRoot, options.adapter, options.transport, {
|
|
8591
|
+
installationType: options.installationType,
|
|
8592
|
+
stateKey: candidateStateKey
|
|
8593
|
+
});
|
|
8594
|
+
sourceLock = await readSourceLock(options.installRoot, options.adapter, options.transport, {
|
|
8595
|
+
installationType: options.installationType,
|
|
8596
|
+
stateKey: candidateStateKey
|
|
8597
|
+
});
|
|
8598
|
+
} catch (error) {
|
|
8599
|
+
throw new Error(
|
|
8600
|
+
`Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error))
|
|
8601
|
+
);
|
|
8602
|
+
}
|
|
8603
|
+
if (!manifest) {
|
|
8604
|
+
if (sourceLock) {
|
|
8605
|
+
throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
|
|
8606
|
+
}
|
|
8607
|
+
candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
|
|
8608
|
+
continue;
|
|
8609
|
+
}
|
|
8610
|
+
const validated = validateLegacyManifestCandidate(manifest, {
|
|
8611
|
+
...options,
|
|
8612
|
+
candidateStateKey,
|
|
8613
|
+
graphLockDigest,
|
|
8614
|
+
graphLockPath
|
|
8615
|
+
});
|
|
8616
|
+
candidates.push({
|
|
8617
|
+
stateKey: candidateStateKey,
|
|
8618
|
+
graphLockPath,
|
|
8619
|
+
graphLockDigest,
|
|
8620
|
+
graphLock,
|
|
8621
|
+
manifest: validated,
|
|
8622
|
+
sourceLock
|
|
8623
|
+
});
|
|
8624
|
+
}
|
|
8625
|
+
return candidates;
|
|
8626
|
+
}
|
|
8627
|
+
function validateLegacyManifestCandidate(manifest, options) {
|
|
8628
|
+
if (manifest.version !== 2) {
|
|
8629
|
+
throw new Error(`Legacy target state for ${options.graphLockPath} requires an install manifest v2.`);
|
|
8630
|
+
}
|
|
8631
|
+
if (manifest.adapter !== options.adapter) {
|
|
8632
|
+
throw new Error(`Legacy target state adapter mismatch at ${options.graphLockPath}.`);
|
|
8633
|
+
}
|
|
8634
|
+
if (manifest.installationType !== options.installationType) {
|
|
8635
|
+
throw new Error(`Legacy target state installation type mismatch at ${options.graphLockPath}.`);
|
|
8636
|
+
}
|
|
8637
|
+
if (manifest.stateKey !== options.candidateStateKey) {
|
|
8638
|
+
throw new Error(`Legacy target state key mismatch at ${options.graphLockPath}.`);
|
|
8639
|
+
}
|
|
8640
|
+
if (normalizeInstallRoot2(manifest.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
|
|
8641
|
+
throw new Error(`Legacy target state target root mismatch at ${options.graphLockPath}.`);
|
|
8642
|
+
}
|
|
8643
|
+
const correlated = manifest.entries.filter((entry) => entry.graphLockDigest === options.graphLockDigest);
|
|
8644
|
+
if (correlated.length === 0) {
|
|
8645
|
+
throw new Error(`Legacy install manifest graph-lock digest does not match ${options.graphLockPath}.`);
|
|
8646
|
+
}
|
|
8647
|
+
if (correlated.some((entry) => entry.workspaceOwner !== options.workspaceOwner)) {
|
|
8648
|
+
throw new Error(`Legacy target state contribution scope does not match ${options.graphLockPath}.`);
|
|
8649
|
+
}
|
|
8650
|
+
return manifest;
|
|
8651
|
+
}
|
|
8652
|
+
function migrationFromCandidate(candidate) {
|
|
8653
|
+
return {
|
|
8654
|
+
fromStateKey: candidate.manifest ? candidate.stateKey : void 0,
|
|
8655
|
+
fromGraphLockPath: candidate.graphLockPath,
|
|
8656
|
+
fromGraphLockDigest: candidate.graphLockDigest,
|
|
8657
|
+
sourceLock: candidate.manifest ? candidate.sourceLock ?? null : void 0
|
|
8658
|
+
};
|
|
8659
|
+
}
|
|
8660
|
+
function sameManifestContributions(left, right) {
|
|
8661
|
+
if (left.version !== 2) return false;
|
|
8662
|
+
return stableJson2(manifestContributionState(left)) === stableJson2(manifestContributionState(right));
|
|
8663
|
+
}
|
|
8664
|
+
function manifestContributionState(manifest) {
|
|
8665
|
+
return manifest.entries.map(({ updatedAt: _updatedAt, graphLockDigest: _graphLockDigest, ...entry }) => entry).sort((left, right) => left.path.localeCompare(right.path));
|
|
8666
|
+
}
|
|
8667
|
+
function stableJson2(value) {
|
|
8668
|
+
if (Array.isArray(value)) return `[${value.map(stableJson2).join(",")}]`;
|
|
8669
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value) ?? "null";
|
|
8670
|
+
const record = value;
|
|
8671
|
+
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(",")}}`;
|
|
8672
|
+
}
|
|
8673
|
+
function normalizeInstallRoot2(path, transportKind) {
|
|
8674
|
+
return transportKind === "ssh" ? posix2.normalize(path) : resolve16(path);
|
|
8675
|
+
}
|
|
8224
8676
|
var workspaceOwnerPrefix2 = "workspace-root:";
|
|
8225
8677
|
async function assertNoForeignWorkspaceState(check) {
|
|
8226
8678
|
const planned = new Set(check.plannedPaths);
|
|
8227
|
-
if (planned.size === 0) return;
|
|
8679
|
+
if (planned.size === 0) return [];
|
|
8228
8680
|
const plannedOperations = /* @__PURE__ */ new Map();
|
|
8229
8681
|
for (const operation of check.plannedOperations) {
|
|
8230
8682
|
if (!planned.has(operation.relativeDestPath)) continue;
|
|
@@ -8233,18 +8685,22 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8233
8685
|
plannedOperations.set(operation.relativeDestPath, operations);
|
|
8234
8686
|
}
|
|
8235
8687
|
const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
|
|
8236
|
-
const root =
|
|
8688
|
+
const root = resolve16(check.workspaceRoot);
|
|
8237
8689
|
const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
|
|
8238
|
-
const
|
|
8690
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
8239
8691
|
for (const entry of manifests) {
|
|
8240
|
-
if (entry.stateKey === check.stateKey) continue;
|
|
8241
|
-
const
|
|
8692
|
+
if (entry.stateKey === check.stateKey || entry.stateKey === check.migratingStateKey) continue;
|
|
8693
|
+
const entryCounts = /* @__PURE__ */ new Map();
|
|
8694
|
+
for (const manifestEntry of entry.manifest.entries) {
|
|
8695
|
+
const owner = "workspaceOwner" in manifestEntry ? manifestEntry.workspaceOwner : void 0;
|
|
8696
|
+
if (typeof owner === "string" && owner.startsWith(workspaceOwnerPrefix2)) {
|
|
8697
|
+
entryCounts.set(owner, (entryCounts.get(owner) ?? 0) + 1);
|
|
8698
|
+
}
|
|
8699
|
+
}
|
|
8242
8700
|
for (const manifestEntry of entry.manifest.entries) {
|
|
8243
8701
|
const owner = "workspaceOwner" in manifestEntry ? manifestEntry.workspaceOwner : void 0;
|
|
8244
8702
|
if (typeof owner !== "string" || !owner.startsWith(workspaceOwnerPrefix2)) continue;
|
|
8245
8703
|
if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
|
|
8246
|
-
const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
|
|
8247
|
-
bucket.entryCount += 1;
|
|
8248
8704
|
if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
|
|
8249
8705
|
check,
|
|
8250
8706
|
manifestEntry.path,
|
|
@@ -8252,16 +8708,46 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8252
8708
|
manifestEntry.mergeRemoval,
|
|
8253
8709
|
plannedOperations.get(manifestEntry.path) ?? []
|
|
8254
8710
|
)) {
|
|
8255
|
-
|
|
8711
|
+
const ownedEntry = manifestEntry;
|
|
8712
|
+
const pathCollisions = collisions.get(manifestEntry.path) ?? [];
|
|
8713
|
+
pathCollisions.push({
|
|
8714
|
+
owner,
|
|
8715
|
+
stateKey: entry.stateKey,
|
|
8716
|
+
fileName: entry.fileName,
|
|
8717
|
+
manifestRevision: entry.manifest.revision,
|
|
8718
|
+
entryCount: entryCounts.get(owner) ?? 1,
|
|
8719
|
+
entry: ownedEntry
|
|
8720
|
+
});
|
|
8721
|
+
collisions.set(manifestEntry.path, pathCollisions);
|
|
8256
8722
|
}
|
|
8257
|
-
byOwner.set(owner, bucket);
|
|
8258
8723
|
}
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8724
|
+
}
|
|
8725
|
+
const foreignByOwner = /* @__PURE__ */ new Map();
|
|
8726
|
+
const observations = [];
|
|
8727
|
+
for (const [path, pathCollisions] of collisions) {
|
|
8728
|
+
const operations = plannedOperations.get(path) ?? [];
|
|
8729
|
+
const exact = pathCollisions.length === 1 ? await exactForeignKeepOperation(check, pathCollisions[0], operations) : void 0;
|
|
8730
|
+
if (exact) {
|
|
8731
|
+
const index = check.plannedOperations.indexOf(operations[0]);
|
|
8732
|
+
if (index < 0) throw new Error(`Foreign-state reconciliation lost planned operation for ${path}.`);
|
|
8733
|
+
check.plannedOperations[index] = exact.operation;
|
|
8734
|
+
observations.push(exact.observation);
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
for (const collision of pathCollisions) {
|
|
8738
|
+
const key = `${collision.owner}\0${collision.fileName}`;
|
|
8739
|
+
const bucket = foreignByOwner.get(key) ?? {
|
|
8740
|
+
owner: collision.owner,
|
|
8741
|
+
fileName: collision.fileName,
|
|
8742
|
+
entryCount: collision.entryCount,
|
|
8743
|
+
collidingPaths: []
|
|
8744
|
+
};
|
|
8745
|
+
if (!bucket.collidingPaths.includes(path)) bucket.collidingPaths.push(path);
|
|
8746
|
+
foreignByOwner.set(key, bucket);
|
|
8262
8747
|
}
|
|
8263
8748
|
}
|
|
8264
|
-
|
|
8749
|
+
const foreign = [...foreignByOwner.values()];
|
|
8750
|
+
if (foreign.length === 0) return observations.sort((left, right) => left.path.localeCompare(right.path));
|
|
8265
8751
|
throw new Error([
|
|
8266
8752
|
`Refusing to plan ${check.adapter} at ${check.installRoot}.`,
|
|
8267
8753
|
"This runtime root already carries Agentwheel state owned by another workspace, at paths this run would install:",
|
|
@@ -8275,6 +8761,36 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8275
8761
|
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
8762
|
].join("\n"));
|
|
8277
8763
|
}
|
|
8764
|
+
async function exactForeignKeepOperation(check, collision, operations) {
|
|
8765
|
+
if (operations.length !== 1) return void 0;
|
|
8766
|
+
const operation = operations[0];
|
|
8767
|
+
const entry = collision.entry;
|
|
8768
|
+
if (operation.semanticPlugin || operation.programmaticOperation || operation.mergeStrategy || operation.mode || entry.semanticPlugin || entry.mergeStrategy || entry.mode) return void 0;
|
|
8769
|
+
if (!operation.desiredHash || operation.artifactType !== entry.artifactType || operation.artifactName !== entry.artifactName || operation.kind !== entry.kind || operation.desiredHash !== entry.sourceHash) return void 0;
|
|
8770
|
+
const path = join33(check.installRoot, entry.path);
|
|
8771
|
+
if (!await check.transport.pathExists(path)) return void 0;
|
|
8772
|
+
const currentHash = await check.transport.hashPath(path);
|
|
8773
|
+
if (currentHash !== entry.hash || currentHash !== operation.desiredHash || operation.currentHash !== void 0 && operation.currentHash !== currentHash) return void 0;
|
|
8774
|
+
return {
|
|
8775
|
+
operation: {
|
|
8776
|
+
...operation,
|
|
8777
|
+
action: "keep",
|
|
8778
|
+
currentHash,
|
|
8779
|
+
manifestHash: entry.hash,
|
|
8780
|
+
reason: `exact foreign artifact owned by ${collision.owner}; kept outside workspace ${check.workspaceOwner}`,
|
|
8781
|
+
preserveInManifest: false
|
|
8782
|
+
},
|
|
8783
|
+
observation: {
|
|
8784
|
+
stateKey: collision.stateKey,
|
|
8785
|
+
fileName: collision.fileName,
|
|
8786
|
+
manifestRevision: collision.manifestRevision,
|
|
8787
|
+
workspaceOwner: collision.owner,
|
|
8788
|
+
path: entry.path,
|
|
8789
|
+
graphNodeId: entry.graphNodeId,
|
|
8790
|
+
logicalSelector: entry.logicalSelector
|
|
8791
|
+
}
|
|
8792
|
+
};
|
|
8793
|
+
}
|
|
8278
8794
|
async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
|
|
8279
8795
|
if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
|
|
8280
8796
|
const operation = operations[0];
|
|
@@ -8446,7 +8962,7 @@ function targetLabel(target) {
|
|
|
8446
8962
|
}
|
|
8447
8963
|
|
|
8448
8964
|
// src/runtime/target.ts
|
|
8449
|
-
import { basename as
|
|
8965
|
+
import { basename as basename19, dirname as dirname18, join as join34, resolve as resolve17 } from "path";
|
|
8450
8966
|
var runtimeMarkers = [
|
|
8451
8967
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
8452
8968
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -8455,9 +8971,9 @@ var runtimeMarkers = [
|
|
|
8455
8971
|
{ adapter: "copilot", dirs: [".github"] }
|
|
8456
8972
|
];
|
|
8457
8973
|
async function resolveRuntimeTarget(request = {}) {
|
|
8458
|
-
const cwd =
|
|
8974
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8459
8975
|
if (request.targetRoot) {
|
|
8460
|
-
const targetRoot =
|
|
8976
|
+
const targetRoot = resolve17(request.targetRoot);
|
|
8461
8977
|
return {
|
|
8462
8978
|
adapter: request.adapter ?? "openclaw",
|
|
8463
8979
|
installationType: request.installationType,
|
|
@@ -8489,7 +9005,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
8489
9005
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
8490
9006
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
8491
9007
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
8492
|
-
const cwd =
|
|
9008
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8493
9009
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
8494
9010
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8495
9011
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
|
|
@@ -8499,8 +9015,8 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
8499
9015
|
return targets;
|
|
8500
9016
|
}
|
|
8501
9017
|
async function resolveProfileRuntimeTargets(request) {
|
|
8502
|
-
const cwd =
|
|
8503
|
-
const workspaceRoot = request.targetRoot ?
|
|
9018
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
9019
|
+
const workspaceRoot = request.targetRoot ? resolve17(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
8504
9020
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8505
9021
|
const profile = config.profiles[request.profile];
|
|
8506
9022
|
if (!profile) {
|
|
@@ -8566,12 +9082,12 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
8566
9082
|
return unique[0];
|
|
8567
9083
|
}
|
|
8568
9084
|
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
8569
|
-
const root =
|
|
9085
|
+
const root = resolve17(cwd);
|
|
8570
9086
|
const matches = [];
|
|
8571
9087
|
for (const marker of runtimeMarkers) {
|
|
8572
9088
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
8573
9089
|
for (const dir of marker.dirs) {
|
|
8574
|
-
if (
|
|
9090
|
+
if (basename19(root) === dir) {
|
|
8575
9091
|
matches.push({ adapter: marker.adapter, targetRoot: dirname18(root) });
|
|
8576
9092
|
} else if (await pathExists(join34(root, dir))) {
|
|
8577
9093
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -8615,9 +9131,9 @@ function dedupeTargets(matches) {
|
|
|
8615
9131
|
return [...byKey.values()];
|
|
8616
9132
|
}
|
|
8617
9133
|
function runtimeScanRoot(request) {
|
|
8618
|
-
const root =
|
|
9134
|
+
const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
8619
9135
|
if (request.targetRoot) return root;
|
|
8620
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
9136
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename19(root))) ? dirname18(root) : root;
|
|
8621
9137
|
}
|
|
8622
9138
|
|
|
8623
9139
|
// src/lifecycle/profile.ts
|
|
@@ -9004,9 +9520,9 @@ function normalizeVersion(version) {
|
|
|
9004
9520
|
|
|
9005
9521
|
// src/model/package-validate.ts
|
|
9006
9522
|
import { stat as stat12 } from "fs/promises";
|
|
9007
|
-
import { resolve as
|
|
9523
|
+
import { resolve as resolve18 } from "path";
|
|
9008
9524
|
async function validatePackage(root) {
|
|
9009
|
-
const packageRoot =
|
|
9525
|
+
const packageRoot = resolve18(root);
|
|
9010
9526
|
const findings = [];
|
|
9011
9527
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
9012
9528
|
if (!manifestPath) {
|
|
@@ -9107,7 +9623,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
9107
9623
|
try {
|
|
9108
9624
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
9109
9625
|
if (isCrossPackageSelector(selector)) return;
|
|
9110
|
-
const full =
|
|
9626
|
+
const full = resolve18(packageRoot, selector);
|
|
9111
9627
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
9112
9628
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
9113
9629
|
return;
|
|
@@ -9161,10 +9677,10 @@ function isCrossPackageSelector(value) {
|
|
|
9161
9677
|
|
|
9162
9678
|
// src/model/package-migrate.ts
|
|
9163
9679
|
import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
|
|
9164
|
-
import { join as join37, resolve as
|
|
9680
|
+
import { join as join37, resolve as resolve19 } from "path";
|
|
9165
9681
|
import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
|
|
9166
9682
|
async function migratePackageManifest(root) {
|
|
9167
|
-
const packageRoot =
|
|
9683
|
+
const packageRoot = resolve19(root);
|
|
9168
9684
|
for (const name of openPackManifestNames) {
|
|
9169
9685
|
const path = join37(packageRoot, name);
|
|
9170
9686
|
if (await pathExists(path)) {
|
|
@@ -9226,7 +9742,7 @@ function resolveCliVersion() {
|
|
|
9226
9742
|
}
|
|
9227
9743
|
|
|
9228
9744
|
// src/lifecycle/ownership.ts
|
|
9229
|
-
import { resolve as
|
|
9745
|
+
import { resolve as resolve20 } from "path";
|
|
9230
9746
|
async function planArtifactOwnershipHandoff(request) {
|
|
9231
9747
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
9232
9748
|
}
|
|
@@ -9352,8 +9868,8 @@ function containedArtifactPath(targetRoot, relativePath) {
|
|
|
9352
9868
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
9353
9869
|
throw new Error(`Unsafe managed artifact path: ${relativePath}`);
|
|
9354
9870
|
}
|
|
9355
|
-
const root =
|
|
9356
|
-
const candidate =
|
|
9871
|
+
const root = resolve20(targetRoot);
|
|
9872
|
+
const candidate = resolve20(root, relativePath);
|
|
9357
9873
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
9358
9874
|
throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
|
|
9359
9875
|
}
|
|
@@ -9386,7 +9902,7 @@ function assertManifestIdentity(manifest, request) {
|
|
|
9386
9902
|
|
|
9387
9903
|
// src/lifecycle/ownership-retire-stale.ts
|
|
9388
9904
|
import { createHash as createHash9 } from "crypto";
|
|
9389
|
-
import { resolve as
|
|
9905
|
+
import { resolve as resolve21 } from "path";
|
|
9390
9906
|
async function planRetireStaleOwnership(request) {
|
|
9391
9907
|
return observe(request, request.transport ?? localTransport);
|
|
9392
9908
|
}
|
|
@@ -9519,7 +10035,7 @@ async function observe(request, transport) {
|
|
|
9519
10035
|
return { ...withoutDigest, planDigest: digest(withoutDigest) };
|
|
9520
10036
|
}
|
|
9521
10037
|
function normalizeRequest(request) {
|
|
9522
|
-
const targetRoot =
|
|
10038
|
+
const targetRoot = resolve21(request.targetRoot);
|
|
9523
10039
|
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, request.installationType, "source");
|
|
9524
10040
|
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
|
|
9525
10041
|
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
@@ -9530,8 +10046,8 @@ function normalizeRequest(request) {
|
|
|
9530
10046
|
installationType: request.installationType.trim(),
|
|
9531
10047
|
sourceStateKey,
|
|
9532
10048
|
destinationStateKey,
|
|
9533
|
-
fromWorkspaceRoot:
|
|
9534
|
-
toWorkspaceRoot:
|
|
10049
|
+
fromWorkspaceRoot: resolve21(request.fromWorkspaceRoot),
|
|
10050
|
+
toWorkspaceRoot: resolve21(request.toWorkspaceRoot),
|
|
9535
10051
|
toFleetId: request.toFleetId.trim()
|
|
9536
10052
|
};
|
|
9537
10053
|
}
|
|
@@ -9552,7 +10068,7 @@ async function requireManifest(request, stateKey, transport, label) {
|
|
|
9552
10068
|
installationType: manifest.installationType,
|
|
9553
10069
|
stateKey: manifest.stateKey
|
|
9554
10070
|
});
|
|
9555
|
-
if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType ||
|
|
10071
|
+
if (manifest.adapter !== request.adapter || manifest.installationType !== request.installationType || resolve21(manifest.targetRoot) !== request.targetRoot || actualStateKey !== stateKey) {
|
|
9556
10072
|
throw new Error(`${label} install manifest identity does not match the requested adapter, target, installation type, and state key.`);
|
|
9557
10073
|
}
|
|
9558
10074
|
return manifest;
|
|
@@ -9636,7 +10152,7 @@ function canonicalJson(value) {
|
|
|
9636
10152
|
// src/version/policy.ts
|
|
9637
10153
|
import { execFile as execFile3 } from "child_process";
|
|
9638
10154
|
import { readFile as readFile23 } from "fs/promises";
|
|
9639
|
-
import { join as join39, resolve as
|
|
10155
|
+
import { join as join39, resolve as resolve22 } from "path";
|
|
9640
10156
|
import { promisify as promisify3 } from "util";
|
|
9641
10157
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
9642
10158
|
import { z as z5 } from "zod";
|
|
@@ -9730,7 +10246,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9730
10246
|
if (tagged.length > 0) return tagged;
|
|
9731
10247
|
}
|
|
9732
10248
|
if (driverName === "local") {
|
|
9733
|
-
const root =
|
|
10249
|
+
const root = resolve22(workspaceRoot, pkg.source);
|
|
9734
10250
|
const manifest2 = await readPackageManifest(root);
|
|
9735
10251
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
9736
10252
|
try {
|
|
@@ -9853,7 +10369,7 @@ async function readVersionCache(path) {
|
|
|
9853
10369
|
// src/profile/members.ts
|
|
9854
10370
|
import { execFile as execFile4 } from "child_process";
|
|
9855
10371
|
import { readFile as readFile24 } from "fs/promises";
|
|
9856
|
-
import { join as join40, resolve as
|
|
10372
|
+
import { join as join40, resolve as resolve23 } from "path";
|
|
9857
10373
|
import { promisify as promisify4 } from "util";
|
|
9858
10374
|
import { z as z7 } from "zod";
|
|
9859
10375
|
|
|
@@ -10028,7 +10544,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
10028
10544
|
let stderr = "";
|
|
10029
10545
|
try {
|
|
10030
10546
|
if (member.transport === "local") {
|
|
10031
|
-
const workspace =
|
|
10547
|
+
const workspace = resolve23(parentWorkspace, member.workspace);
|
|
10032
10548
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
10033
10549
|
cwd: workspace,
|
|
10034
10550
|
env: env2,
|
|
@@ -10074,7 +10590,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
10074
10590
|
process.execPath,
|
|
10075
10591
|
[process.argv[1], ...memberCommandArgs(member, args)],
|
|
10076
10592
|
{
|
|
10077
|
-
cwd:
|
|
10593
|
+
cwd: resolve23(parentWorkspace, member.workspace),
|
|
10078
10594
|
env: env2,
|
|
10079
10595
|
maxBuffer: 20 * 1024 * 1024
|
|
10080
10596
|
}
|
|
@@ -10180,7 +10696,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
10180
10696
|
}
|
|
10181
10697
|
}
|
|
10182
10698
|
function compositeKey(workspaceRoot, profileName) {
|
|
10183
|
-
return `${
|
|
10699
|
+
return `${resolve23(workspaceRoot)}#${profileName}`;
|
|
10184
10700
|
}
|
|
10185
10701
|
|
|
10186
10702
|
// src/status/repository.ts
|
|
@@ -11105,9 +11621,9 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
11105
11621
|
|
|
11106
11622
|
// src/lifecycle/fleet-normalize.ts
|
|
11107
11623
|
import { createHash as createHash13 } from "crypto";
|
|
11108
|
-
import { readFile as readFile27, readdir as
|
|
11624
|
+
import { readFile as readFile27, readdir as readdir10, rm as rm9 } from "fs/promises";
|
|
11109
11625
|
import { homedir as homedir11 } from "os";
|
|
11110
|
-
import {
|
|
11626
|
+
import { dirname as dirname21, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve24 } from "path";
|
|
11111
11627
|
async function planFleetNormalization(request) {
|
|
11112
11628
|
const normalizedRequest = normalizeRequest2(request);
|
|
11113
11629
|
const destinationScope = await resolveWorkspaceScope({
|
|
@@ -11378,17 +11894,17 @@ function normalizeRequest2(request) {
|
|
|
11378
11894
|
...profile ? { profile } : {},
|
|
11379
11895
|
...agent ? { agent } : {},
|
|
11380
11896
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
11381
|
-
...request.globalRoot ? { globalRoot:
|
|
11897
|
+
...request.globalRoot ? { globalRoot: resolve24(request.globalRoot) } : {}
|
|
11382
11898
|
};
|
|
11383
11899
|
}
|
|
11384
11900
|
function normalizeOrphanedOwnerRoot(value) {
|
|
11385
11901
|
const root = value.trim();
|
|
11386
11902
|
if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
11387
|
-
return
|
|
11903
|
+
return resolve24(root);
|
|
11388
11904
|
}
|
|
11389
11905
|
async function normalizationRoots(request) {
|
|
11390
11906
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
11391
|
-
const source = request.from === "user" ?
|
|
11907
|
+
const source = request.from === "user" ? resolve24(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
11392
11908
|
return { source, destination: destination.root };
|
|
11393
11909
|
}
|
|
11394
11910
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -11454,11 +11970,28 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11454
11970
|
const coveredDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11455
11971
|
const plannedDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11456
11972
|
for (const sourceGraph of sourceGraphs) {
|
|
11457
|
-
const sourceState = await
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11973
|
+
const sourceState = await installedTargetStateForGraph(
|
|
11974
|
+
source,
|
|
11975
|
+
sourceGraph,
|
|
11976
|
+
manifests,
|
|
11977
|
+
sourceOwners,
|
|
11978
|
+
selected
|
|
11979
|
+
);
|
|
11980
|
+
const destinationStableState = await targetStateForGraph(destination, sourceGraph);
|
|
11981
|
+
const matchingDestinationGraphs = destinationGraphs.filter((candidate) => candidate.targetKey === sourceGraph.targetKey && candidate.adapter === sourceGraph.adapter);
|
|
11982
|
+
if (matchingDestinationGraphs.length > 1) {
|
|
11983
|
+
throw new Error(
|
|
11984
|
+
`Ambiguous destination graph state for target '${sourceGraph.targetKey}': ` + matchingDestinationGraphs.map((candidate) => candidate.path).join(", ")
|
|
11985
|
+
);
|
|
11461
11986
|
}
|
|
11987
|
+
const destinationGraph = matchingDestinationGraphs[0];
|
|
11988
|
+
const destinationState = destinationGraph ? await installedTargetStateForGraph(
|
|
11989
|
+
destination,
|
|
11990
|
+
destinationGraph,
|
|
11991
|
+
manifests,
|
|
11992
|
+
destinationOwners,
|
|
11993
|
+
selected
|
|
11994
|
+
) : destinationStableState;
|
|
11462
11995
|
if (sourceState.adapter !== destinationState.adapter) {
|
|
11463
11996
|
throw new Error(`Source and destination adapters diverge for target '${sourceGraph.targetKey}'.`);
|
|
11464
11997
|
}
|
|
@@ -11467,7 +12000,6 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11467
12000
|
throw new Error(`Source graph lock is not covered by its canonical install manifest: ${sourceState.manifestPath}`);
|
|
11468
12001
|
}
|
|
11469
12002
|
const sourceGraphArtifacts = new Set(sourceGraph.artifactIdentities);
|
|
11470
|
-
const destinationGraph = destinationGraphs.find((candidate) => candidate.path === destinationState.graphLockPath);
|
|
11471
12003
|
if (destinationGraph && destinationGraph.digest !== sourceGraph.digest) {
|
|
11472
12004
|
throw new Error("Source and destination graph declarations, selections, versions, identities, or rendered paths diverge.");
|
|
11473
12005
|
}
|
|
@@ -11585,16 +12117,30 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11585
12117
|
const transferableOwners = orphanedOwners.size > 0 ? orphanedOwners : /* @__PURE__ */ new Set([legacyOwner]);
|
|
11586
12118
|
let normalizedGraphCount = 0;
|
|
11587
12119
|
for (const graph of graphCandidates) {
|
|
12120
|
+
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
|
|
12121
|
+
if (resolve24(destinationState.graphLockPath) === resolve24(graph.path)) {
|
|
12122
|
+
const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
|
|
12123
|
+
if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
12124
|
+
assertCorrelatedGraphManifest(
|
|
12125
|
+
graph,
|
|
12126
|
+
destinationState,
|
|
12127
|
+
destinationManifest,
|
|
12128
|
+
/* @__PURE__ */ new Set([fleetOwner]),
|
|
12129
|
+
selected
|
|
12130
|
+
);
|
|
12131
|
+
normalizedGraphCount += 1;
|
|
12132
|
+
continue;
|
|
12133
|
+
}
|
|
12134
|
+
}
|
|
11588
12135
|
const legacyState = await legacyTargetStateForGraph(fleet, graph);
|
|
12136
|
+
if (resolve24(legacyState.graphLockPath) !== resolve24(graph.path)) {
|
|
12137
|
+
throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12138
|
+
}
|
|
11589
12139
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
11590
12140
|
const relevantManifestEntries2 = legacyManifest?.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected) && entryMatchesArtifacts(entry, selectedArtifacts)) ?? [];
|
|
11591
12141
|
if (relevantManifestEntries2.length > 0) {
|
|
11592
12142
|
graphs.push(graph);
|
|
11593
12143
|
}
|
|
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
12144
|
}
|
|
11599
12145
|
if (graphs.length === 0 && normalizedGraphCount > 0) {
|
|
11600
12146
|
throw new Error(`Fleet '${fleet.fleetId}' installed state is already normalized to fleet-qualified ownership.`);
|
|
@@ -11762,7 +12308,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
11762
12308
|
async function runtimeRoots(scope) {
|
|
11763
12309
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
11764
12310
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
11765
|
-
roots.add(
|
|
12311
|
+
roots.add(resolve24(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
11766
12312
|
}
|
|
11767
12313
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
11768
12314
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -11783,7 +12329,7 @@ async function collectManifests(roots) {
|
|
|
11783
12329
|
const dir = join44(root, ".agentwheel");
|
|
11784
12330
|
let names;
|
|
11785
12331
|
try {
|
|
11786
|
-
names = await
|
|
12332
|
+
names = await readdir10(dir);
|
|
11787
12333
|
} catch (error) {
|
|
11788
12334
|
if (isMissing(error)) continue;
|
|
11789
12335
|
throw error;
|
|
@@ -11799,14 +12345,14 @@ async function collectManifests(roots) {
|
|
|
11799
12345
|
}
|
|
11800
12346
|
continue;
|
|
11801
12347
|
}
|
|
11802
|
-
if (
|
|
12348
|
+
if (resolve24(parsed.targetRoot) !== resolve24(root)) {
|
|
11803
12349
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
11804
12350
|
}
|
|
11805
12351
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
11806
12352
|
installationType: parsed.installationType,
|
|
11807
12353
|
stateKey: parsed.stateKey
|
|
11808
12354
|
});
|
|
11809
|
-
if (
|
|
12355
|
+
if (resolve24(expectedPath) !== resolve24(path)) {
|
|
11810
12356
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11811
12357
|
}
|
|
11812
12358
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11827,7 +12373,7 @@ async function collectManifestPaths(paths) {
|
|
|
11827
12373
|
installationType: parsed.installationType,
|
|
11828
12374
|
stateKey: parsed.stateKey
|
|
11829
12375
|
});
|
|
11830
|
-
if (
|
|
12376
|
+
if (resolve24(expectedPath) !== resolve24(path)) {
|
|
11831
12377
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11832
12378
|
}
|
|
11833
12379
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11855,12 +12401,8 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11855
12401
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
11856
12402
|
const parts = relative8(root, path).split(/[\\/]/);
|
|
11857
12403
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
11858
|
-
const [targetKey2, adapter
|
|
12404
|
+
const [targetKey2, adapter] = parts;
|
|
11859
12405
|
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
12406
|
const candidate = {
|
|
11865
12407
|
path,
|
|
11866
12408
|
lock,
|
|
@@ -11889,9 +12431,19 @@ function isCurrentLocalTarget(scope, graph) {
|
|
|
11889
12431
|
}
|
|
11890
12432
|
async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
11891
12433
|
if (!isCurrentLocalTarget(scope, graph)) return false;
|
|
11892
|
-
const
|
|
11893
|
-
|
|
11894
|
-
|
|
12434
|
+
const configuredLegacyPath = await legacyManifestPathForConfiguredTarget(scope, graph);
|
|
12435
|
+
if (configuredLegacyPath) {
|
|
12436
|
+
const manifest = (await collectManifestPaths([configuredLegacyPath]))[0];
|
|
12437
|
+
if (manifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12438
|
+
}
|
|
12439
|
+
if (graph.lock.canonical.roots.some((root) => !scope.config.packages.some((pkg) => pkg.name === root.rootId))) return false;
|
|
12440
|
+
const legacyState = await legacyTargetStateForGraph(scope, graph);
|
|
12441
|
+
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
12442
|
+
if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12443
|
+
const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
|
|
12444
|
+
if (resolve24(stableState.graphLockPath) !== resolve24(graph.path)) return false;
|
|
12445
|
+
const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
|
|
12446
|
+
return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
|
|
11895
12447
|
}
|
|
11896
12448
|
async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
11897
12449
|
const agent = scope.config.agents[graph.targetKey];
|
|
@@ -11910,9 +12462,10 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
11910
12462
|
baseDir: scope.root
|
|
11911
12463
|
});
|
|
11912
12464
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
11913
|
-
const installRoot =
|
|
12465
|
+
const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
11914
12466
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11915
12467
|
installationType: agent.installationType,
|
|
12468
|
+
stateKey: agent.stateKey,
|
|
11916
12469
|
targetFingerprint: graph.lock.canonical.targetFingerprint
|
|
11917
12470
|
});
|
|
11918
12471
|
return installManifestPath(installRoot, adapter.name, { installationType: agent.installationType, stateKey });
|
|
@@ -11977,14 +12530,21 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11977
12530
|
ssh: void 0,
|
|
11978
12531
|
...agent?.stateKey ? { stateKey: agent.stateKey } : {}
|
|
11979
12532
|
};
|
|
11980
|
-
const
|
|
12533
|
+
const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
12534
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
12535
|
+
targetFingerprintParts: fingerprintParts,
|
|
12536
|
+
workspaceRoot: scope.root,
|
|
12537
|
+
targetKey: graph.targetKey,
|
|
12538
|
+
resolvedInstallRoot: installRoot,
|
|
12539
|
+
transportKind: "local"
|
|
12540
|
+
});
|
|
12541
|
+
const targetFingerprint = targetIdentity.targetFingerprint;
|
|
11981
12542
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11982
12543
|
installationType,
|
|
11983
12544
|
stateKey: agent?.stateKey,
|
|
11984
|
-
targetFingerprint,
|
|
12545
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
11985
12546
|
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
11986
12547
|
});
|
|
11987
|
-
const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
11988
12548
|
return {
|
|
11989
12549
|
adapter: adapter.name,
|
|
11990
12550
|
installationType,
|
|
@@ -11992,28 +12552,81 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11992
12552
|
installRoot,
|
|
11993
12553
|
stateKey,
|
|
11994
12554
|
targetFingerprint,
|
|
11995
|
-
graphLockPath: graphLockPathForTarget(scope.root, graph.targetKey, adapter.name, fingerprintParts
|
|
12555
|
+
graphLockPath: graphLockPathForTarget(scope.root, graph.targetKey, adapter.name, fingerprintParts, {
|
|
12556
|
+
resolvedInstallRoot: installRoot,
|
|
12557
|
+
transportKind: "local"
|
|
12558
|
+
}),
|
|
11996
12559
|
manifestPath: installManifestPath(installRoot, adapter.name, { installationType, stateKey })
|
|
11997
12560
|
};
|
|
11998
12561
|
}
|
|
11999
|
-
async function
|
|
12000
|
-
const
|
|
12562
|
+
async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
|
|
12563
|
+
const stable = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12564
|
+
let state;
|
|
12565
|
+
if (resolve24(graph.path) === resolve24(stable.graphLockPath)) {
|
|
12566
|
+
state = stable;
|
|
12567
|
+
} else {
|
|
12568
|
+
const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
|
|
12569
|
+
if (resolve24(graph.path) !== resolve24(released.graphLockPath)) {
|
|
12570
|
+
throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12571
|
+
}
|
|
12572
|
+
state = released;
|
|
12573
|
+
}
|
|
12574
|
+
const manifest = manifests.find((candidate) => resolve24(candidate.path) === resolve24(state.manifestPath));
|
|
12575
|
+
if (!manifest) {
|
|
12576
|
+
throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
|
|
12577
|
+
}
|
|
12578
|
+
assertCorrelatedGraphManifest(graph, state, manifest, owners, selected);
|
|
12579
|
+
return state;
|
|
12580
|
+
}
|
|
12581
|
+
async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null) {
|
|
12582
|
+
const current = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12001
12583
|
const targetFingerprint = graph.lock.canonical.targetFingerprint;
|
|
12584
|
+
if (!targetFingerprint || !/^[a-f0-9]{64}$/u.test(targetFingerprint)) {
|
|
12585
|
+
throw new Error(`Released graph lock has an invalid target fingerprint: ${graph.path}`);
|
|
12586
|
+
}
|
|
12587
|
+
const agent = scope.config.agents[graph.targetKey];
|
|
12002
12588
|
const stateKey = stateKeyFor(current.adapter, {
|
|
12003
12589
|
installationType: current.installationType,
|
|
12004
|
-
|
|
12590
|
+
stateKey: agent?.stateKey,
|
|
12591
|
+
targetFingerprint,
|
|
12592
|
+
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
12005
12593
|
});
|
|
12006
12594
|
return {
|
|
12007
12595
|
...current,
|
|
12008
12596
|
stateKey,
|
|
12009
12597
|
targetFingerprint,
|
|
12010
|
-
graphLockPath: graph.path,
|
|
12598
|
+
graphLockPath: join44(dirname21(graph.path), `${targetFingerprint}.graph-lock.json`),
|
|
12011
12599
|
manifestPath: installManifestPath(current.installRoot, current.adapter, {
|
|
12012
12600
|
installationType: current.installationType,
|
|
12013
12601
|
stateKey
|
|
12014
12602
|
})
|
|
12015
12603
|
};
|
|
12016
12604
|
}
|
|
12605
|
+
function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
|
|
12606
|
+
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve24(manifest.manifest.targetRoot) !== resolve24(state.installRoot)) {
|
|
12607
|
+
throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
|
|
12608
|
+
}
|
|
12609
|
+
const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
|
|
12610
|
+
if (entries.length === 0) {
|
|
12611
|
+
throw new Error(`Graph lock is not covered by selected install-manifest contributions: ${graph.path}`);
|
|
12612
|
+
}
|
|
12613
|
+
const graphArtifacts = new Set(graph.artifactIdentities);
|
|
12614
|
+
const graphLockDigest = sha2564(canonicalJson2(graph.lock));
|
|
12615
|
+
for (const entry of entries) {
|
|
12616
|
+
if (!owners.has(entry.workspaceOwner)) {
|
|
12617
|
+
throw new Error(`Graph lock manifest owner mismatch at ${manifest.path}: ${entry.workspaceOwner}.`);
|
|
12618
|
+
}
|
|
12619
|
+
if (!graphArtifacts.has(graphEntryIdentity(entry))) {
|
|
12620
|
+
throw new Error(`Install manifest entry is not covered by its graph lock: ${renderedEntryPath(manifest.manifest, entry)}`);
|
|
12621
|
+
}
|
|
12622
|
+
if (entry.graphLockDigest !== void 0 && entry.graphLockDigest !== graphLockDigest) {
|
|
12623
|
+
throw new Error(`Install manifest graph-lock digest does not match ${graph.path}.`);
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
}
|
|
12627
|
+
async function legacyTargetStateForGraph(scope, graph) {
|
|
12628
|
+
return releasedTargetStateForGraph(scope, graph, null);
|
|
12629
|
+
}
|
|
12017
12630
|
function assertGraphIsSubset(source, destination) {
|
|
12018
12631
|
const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
|
|
12019
12632
|
for (const root of destination.lock.canonical.roots) {
|
|
@@ -12047,7 +12660,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
12047
12660
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
12048
12661
|
if (existing) {
|
|
12049
12662
|
const manifest2 = existing.manifest;
|
|
12050
|
-
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey ||
|
|
12663
|
+
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve24(manifest2.targetRoot) !== state.installRoot) {
|
|
12051
12664
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
12052
12665
|
}
|
|
12053
12666
|
}
|
|
@@ -12107,7 +12720,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12107
12720
|
const content = await readFile27(path, "utf8");
|
|
12108
12721
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
12109
12722
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
12110
|
-
} else if (
|
|
12723
|
+
} else if (resolve24(sourcePath) === resolve24(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
12111
12724
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
12112
12725
|
} else {
|
|
12113
12726
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -12118,8 +12731,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12118
12731
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
12119
12732
|
}
|
|
12120
12733
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
12121
|
-
const normalizedRoot =
|
|
12122
|
-
const candidate =
|
|
12734
|
+
const normalizedRoot = resolve24(root);
|
|
12735
|
+
const candidate = resolve24(normalizedRoot, entryPath);
|
|
12123
12736
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
12124
12737
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
12125
12738
|
}
|
|
@@ -12210,8 +12823,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
12210
12823
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
12211
12824
|
}
|
|
12212
12825
|
function renderedEntryPath(manifest, entry) {
|
|
12213
|
-
const root =
|
|
12214
|
-
const candidate =
|
|
12826
|
+
const root = resolve24(manifest.targetRoot);
|
|
12827
|
+
const candidate = resolve24(root, entry.path);
|
|
12215
12828
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
12216
12829
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
12217
12830
|
}
|
|
@@ -12918,11 +13531,11 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
12918
13531
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12919
13532
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
12920
13533
|
for (const target of targets) {
|
|
12921
|
-
const
|
|
12922
|
-
|
|
12923
|
-
|
|
12924
|
-
|
|
12925
|
-
console.log(formatDepsWhy(
|
|
13534
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13535
|
+
if (!state.graphLock) {
|
|
13536
|
+
throw new Error(`No graph lock for ${state.adapter.name} at ${target.targetRoot}: ${state.stableGraphLockPath}`);
|
|
13537
|
+
}
|
|
13538
|
+
console.log(formatDepsWhy(state.graphLock, state.manifest, selector));
|
|
12926
13539
|
}
|
|
12927
13540
|
})
|
|
12928
13541
|
);
|
|
@@ -13013,19 +13626,23 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13013
13626
|
throw new Error(`Ownership handoff selector must be exactly type/name: ${selector}`);
|
|
13014
13627
|
}
|
|
13015
13628
|
const target = targets[0];
|
|
13016
|
-
const
|
|
13017
|
-
const adapter
|
|
13018
|
-
|
|
13019
|
-
|
|
13629
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13630
|
+
const { adapter, installationType } = state;
|
|
13631
|
+
if (state.migration) {
|
|
13632
|
+
throw new Error(
|
|
13633
|
+
"Migrate the target state before ownership handoff by running a full agentwheel install for this target."
|
|
13634
|
+
);
|
|
13635
|
+
}
|
|
13020
13636
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13021
13637
|
if (options.toFleet) {
|
|
13022
13638
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
13023
|
-
if (
|
|
13639
|
+
if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
|
|
13024
13640
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13025
13641
|
}
|
|
13026
13642
|
}
|
|
13027
13643
|
const request = {
|
|
13028
|
-
|
|
13644
|
+
installationType,
|
|
13645
|
+
stateKey: state.stableStateKey,
|
|
13029
13646
|
targetRoot: state.installRoot,
|
|
13030
13647
|
adapter: adapter.name,
|
|
13031
13648
|
artifactType,
|
|
@@ -13049,7 +13666,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
13049
13666
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
13050
13667
|
const fleet = await showRegisteredFleet(options.fleet);
|
|
13051
13668
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13052
|
-
if (
|
|
13669
|
+
if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
|
|
13053
13670
|
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13054
13671
|
}
|
|
13055
13672
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
@@ -13057,19 +13674,22 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
13057
13674
|
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
13058
13675
|
}
|
|
13059
13676
|
const target = targets[0];
|
|
13060
|
-
if (target.fleetId !== options.fleet ||
|
|
13677
|
+
if (target.fleetId !== options.fleet || resolve25(target.workspaceRoot) !== resolve25(fleet.root)) {
|
|
13061
13678
|
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
13062
13679
|
}
|
|
13063
|
-
const
|
|
13064
|
-
const adapter
|
|
13065
|
-
|
|
13066
|
-
|
|
13680
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13681
|
+
const { adapter, installationType } = state;
|
|
13682
|
+
if (state.migration) {
|
|
13683
|
+
throw new Error(
|
|
13684
|
+
"Migrate the target state before ownership retirement by running a full agentwheel install for this target."
|
|
13685
|
+
);
|
|
13686
|
+
}
|
|
13067
13687
|
const request = {
|
|
13068
13688
|
targetRoot: state.installRoot,
|
|
13069
13689
|
adapter: adapter.name,
|
|
13070
13690
|
installationType,
|
|
13071
13691
|
sourceStateKey: options.sourceStateKey,
|
|
13072
|
-
destinationStateKey: state.
|
|
13692
|
+
destinationStateKey: state.stableStateKey,
|
|
13073
13693
|
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
13074
13694
|
toWorkspaceRoot,
|
|
13075
13695
|
toFleetId: fleet.id,
|
|
@@ -13154,13 +13774,18 @@ program.command("uninstall").description("remove configured packages or managed
|
|
|
13154
13774
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
13155
13775
|
const transport = transportForTarget(target);
|
|
13156
13776
|
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
13157
|
-
const state =
|
|
13158
|
-
const manifest =
|
|
13777
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13778
|
+
const manifest = state.manifest;
|
|
13159
13779
|
if (!manifest) {
|
|
13160
13780
|
console.log(`No install manifest for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
13161
13781
|
continue;
|
|
13162
13782
|
}
|
|
13163
|
-
const plan =
|
|
13783
|
+
const plan = {
|
|
13784
|
+
...filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options)),
|
|
13785
|
+
stateKey: state.stableStateKey,
|
|
13786
|
+
stateMigration: state.migration,
|
|
13787
|
+
targetStateFilePreconditions: state.targetStateFilePreconditions
|
|
13788
|
+
};
|
|
13164
13789
|
console.log(formatPlan(plan));
|
|
13165
13790
|
const result = await uninstall(plan, { dryRun: normalizedOptions.dryRun, force: normalizedOptions.force, transport });
|
|
13166
13791
|
if (!normalizedOptions.dryRun) {
|
|
@@ -13622,15 +14247,12 @@ async function packageSelectsSkillForTarget(target, pkg, selector, options) {
|
|
|
13622
14247
|
if (explicitSelection && !explicitSelection.some((candidate) => candidate === selector)) return false;
|
|
13623
14248
|
const groups = /* @__PURE__ */ new Map();
|
|
13624
14249
|
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);
|
|
14250
|
+
const state = await resolveCliTargetState(group.target, {
|
|
14251
|
+
...options,
|
|
14252
|
+
installationType: group.installationType
|
|
14253
|
+
});
|
|
14254
|
+
if (state.graphLock) {
|
|
14255
|
+
const lock = state.graphLock;
|
|
13634
14256
|
const root = lock.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
13635
14257
|
if (root?.selected.includes(selector)) return true;
|
|
13636
14258
|
}
|
|
@@ -13933,13 +14555,30 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
13933
14555
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
13934
14556
|
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
13935
14557
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
13936
|
-
|
|
13937
|
-
group.target
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
13941
|
-
|
|
13942
|
-
|
|
14558
|
+
if (behavior.readOnly !== true && targetOptions.dryRun !== true) {
|
|
14559
|
+
const pendingState = await journalStateForTarget(group.target, {
|
|
14560
|
+
...targetOptions,
|
|
14561
|
+
installationType: group.installationType
|
|
14562
|
+
});
|
|
14563
|
+
if (await readApplyJournal(
|
|
14564
|
+
pendingState.installRoot,
|
|
14565
|
+
pendingState.adapter.name,
|
|
14566
|
+
pendingState.transport,
|
|
14567
|
+
pendingState.state
|
|
14568
|
+
)) {
|
|
14569
|
+
await recoverPendingApply(
|
|
14570
|
+
pendingState.installRoot,
|
|
14571
|
+
pendingState.adapter.name,
|
|
14572
|
+
pendingState.transport,
|
|
14573
|
+
pendingState.state
|
|
14574
|
+
);
|
|
14575
|
+
}
|
|
14576
|
+
}
|
|
14577
|
+
const priorGroupState = await resolveCliTargetState(group.target, {
|
|
14578
|
+
...targetOptions,
|
|
14579
|
+
installationType: group.installationType
|
|
14580
|
+
});
|
|
14581
|
+
const previousGroupLock = priorGroupState.graphLock;
|
|
13943
14582
|
const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
|
|
13944
14583
|
if (scopedDependencyUpdate) {
|
|
13945
14584
|
for (const pkg of group.packages) {
|
|
@@ -14049,14 +14688,14 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14049
14688
|
expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner
|
|
14050
14689
|
});
|
|
14051
14690
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
14052
|
-
const
|
|
14053
|
-
const
|
|
14054
|
-
const scopedResult = targetOptions.focusedArtifact ?
|
|
14691
|
+
const manifest = result.previousManifest;
|
|
14692
|
+
const previousLock = result.previousGraphLock;
|
|
14693
|
+
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
14694
|
if (targetOptions.focusedArtifact && targetOptions.forceForeignState !== true) {
|
|
14056
14695
|
const focusedPaths = scopedResult.plan.operations.filter((operation) => operationMatchesFocusedArtifact(
|
|
14057
14696
|
operation,
|
|
14058
14697
|
targetOptions.focusedArtifact,
|
|
14059
|
-
focusedArtifactOwnerKeys(result.bundle.graphLock,
|
|
14698
|
+
focusedArtifactOwnerKeys(result.bundle.graphLock, previousLock, scopedRootId, targetOptions.focusedArtifact)
|
|
14060
14699
|
)).map((operation) => operation.relativeDestPath);
|
|
14061
14700
|
const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
|
|
14062
14701
|
fleetId: group.target.fleetId
|
|
@@ -14070,9 +14709,11 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14070
14709
|
}
|
|
14071
14710
|
results.push(scopedResult);
|
|
14072
14711
|
} else if (scopedDependencyUpdate) {
|
|
14073
|
-
const
|
|
14074
|
-
const
|
|
14075
|
-
|
|
14712
|
+
const manifest = result.previousManifest;
|
|
14713
|
+
const previousLock = result.previousGraphLock;
|
|
14714
|
+
if (!previousLock) {
|
|
14715
|
+
throw new Error(`Dependency update requires an existing graph lock for ${adapter.name} at ${group.target.targetRoot}.`);
|
|
14716
|
+
}
|
|
14076
14717
|
results.push(scopeUpdatePlanToDependencies(result, dependencyUpdateSelectors, previousLock, manifest));
|
|
14077
14718
|
} else {
|
|
14078
14719
|
results.push(result);
|
|
@@ -14665,8 +15306,12 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14665
15306
|
const removedInstallationType = options.installationType ?? pkg.installationType ?? removedTarget.installationType ?? "local";
|
|
14666
15307
|
const adapter = await resolveAdapterForTarget(removedTarget, removedAdapterOptions);
|
|
14667
15308
|
const transport = transportForTarget(removedTarget);
|
|
14668
|
-
const removedState =
|
|
14669
|
-
|
|
15309
|
+
const removedState = await resolveCliTargetState(removedTarget, {
|
|
15310
|
+
...options,
|
|
15311
|
+
...removedAdapterOptions,
|
|
15312
|
+
installationType: removedInstallationType
|
|
15313
|
+
});
|
|
15314
|
+
const manifest = removedState.manifest;
|
|
14670
15315
|
if (!manifest) {
|
|
14671
15316
|
console.log(`No install manifest for ${adapter.name} at ${removedTarget.targetRoot}`);
|
|
14672
15317
|
continue;
|
|
@@ -14714,25 +15359,24 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14714
15359
|
remainingDesired = desiredArtifactsFromGraphBundle(result2.bundle);
|
|
14715
15360
|
renderedRoot = result2.bundle.root;
|
|
14716
15361
|
}
|
|
14717
|
-
const
|
|
15362
|
+
const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
|
|
15363
|
+
graphLockDigest: remainingGraphPlan?.graphLockDigest
|
|
15364
|
+
});
|
|
15365
|
+
const transitionPlan = remainingGraphPlan?.plan;
|
|
15366
|
+
const plan = {
|
|
15367
|
+
...uninstallPlan,
|
|
15368
|
+
stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
|
|
15369
|
+
stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
|
|
15370
|
+
targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
|
|
15371
|
+
};
|
|
14718
15372
|
console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
|
|
14719
15373
|
console.log(formatPlan(plan));
|
|
14720
15374
|
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
|
-
)
|
|
15375
|
+
removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
|
|
14727
15376
|
};
|
|
14728
15377
|
if (!options.dryRun) {
|
|
14729
15378
|
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
|
-
);
|
|
15379
|
+
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
|
|
14736
15380
|
declareMutationPath(declarativeGraphPath);
|
|
14737
15381
|
}
|
|
14738
15382
|
const result = await uninstall(plan, {
|
|
@@ -14807,36 +15451,92 @@ function targetFingerprintParts(target, adapter, options, installationType) {
|
|
|
14807
15451
|
};
|
|
14808
15452
|
}
|
|
14809
15453
|
function installStateForTarget(target, adapter, options, installationType) {
|
|
14810
|
-
const
|
|
15454
|
+
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
|
|
15455
|
+
const targetIdentity = resolveTargetStateIdentity({
|
|
15456
|
+
targetFingerprintParts: targetFingerprintParts(target, adapter, options, installationType),
|
|
15457
|
+
workspaceRoot: target.workspaceRoot,
|
|
15458
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
15459
|
+
resolvedInstallRoot: installRoot,
|
|
15460
|
+
transportKind: target.transport
|
|
15461
|
+
});
|
|
14811
15462
|
return {
|
|
14812
15463
|
installationType,
|
|
14813
15464
|
stateKey: stateKeyFor(adapter.name, {
|
|
14814
15465
|
installationType,
|
|
14815
15466
|
stateKey: target.stateKey,
|
|
14816
|
-
targetFingerprint,
|
|
15467
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
14817
15468
|
fleetId: target.fleetId
|
|
14818
15469
|
}),
|
|
14819
|
-
installRoot
|
|
15470
|
+
installRoot
|
|
14820
15471
|
};
|
|
14821
15472
|
}
|
|
14822
|
-
function
|
|
14823
|
-
|
|
14824
|
-
|
|
15473
|
+
function targetIdentityContext(target, adapter, installationType) {
|
|
15474
|
+
return {
|
|
15475
|
+
resolvedInstallRoot: installRootForAdapterInstallationType(
|
|
15476
|
+
adapter,
|
|
15477
|
+
target.targetRoot,
|
|
15478
|
+
installationType,
|
|
15479
|
+
target.transport === "ssh"
|
|
15480
|
+
),
|
|
15481
|
+
transportKind: target.transport
|
|
15482
|
+
};
|
|
14825
15483
|
}
|
|
14826
|
-
async function
|
|
15484
|
+
async function resolveCliTargetState(target, options) {
|
|
14827
15485
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
14828
15486
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15487
|
+
const transport = transportForTarget(target);
|
|
14829
15488
|
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
14830
|
-
const
|
|
15489
|
+
const stable = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
15490
|
+
const stableGraphLockPath = graphLockPathForTarget(
|
|
14831
15491
|
target.workspaceRoot,
|
|
14832
15492
|
targetKeyForTarget(target, adapter.name),
|
|
14833
15493
|
adapter.name,
|
|
14834
|
-
targetFingerprintParts(target, adapter, adapterOptions, installationType)
|
|
15494
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
15495
|
+
targetIdentityContext(target, adapter, installationType)
|
|
14835
15496
|
);
|
|
14836
|
-
|
|
14837
|
-
|
|
15497
|
+
const [stableManifest, stableLock, stableSourceLock, ownership] = await Promise.all([
|
|
15498
|
+
readInstallManifest(stable.installRoot, adapter.name, transport, stable),
|
|
15499
|
+
pathExists(stableGraphLockPath).then((exists) => exists ? readGraphLock(stableGraphLockPath) : void 0),
|
|
15500
|
+
readSourceLock(stable.installRoot, adapter.name, transport, stable),
|
|
15501
|
+
resolveWorkspaceOwnershipScope(target.workspaceRoot, { fleetId: target.fleetId })
|
|
15502
|
+
]);
|
|
15503
|
+
const prior = await resolvePriorTargetState({
|
|
15504
|
+
adapter: adapter.name,
|
|
15505
|
+
installationType,
|
|
15506
|
+
stateKey: stable.stateKey,
|
|
15507
|
+
explicitStateKey: target.stateKey,
|
|
15508
|
+
fleetId: target.fleetId,
|
|
15509
|
+
installRoot: stable.installRoot,
|
|
15510
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
15511
|
+
graphLockPath: stableGraphLockPath,
|
|
15512
|
+
stableManifest,
|
|
15513
|
+
stableLock,
|
|
15514
|
+
transport
|
|
15515
|
+
});
|
|
15516
|
+
return {
|
|
15517
|
+
adapter,
|
|
15518
|
+
installationType,
|
|
15519
|
+
installRoot: stable.installRoot,
|
|
15520
|
+
stateKey: prior.migration?.fromStateKey ?? stable.stateKey,
|
|
15521
|
+
stableStateKey: stable.stateKey,
|
|
15522
|
+
stableGraphLockPath,
|
|
15523
|
+
manifest: prior.manifest,
|
|
15524
|
+
graphLock: prior.graphLock,
|
|
15525
|
+
graphLockPath: prior.graphLock ? prior.migration?.fromGraphLockPath ?? stableGraphLockPath : null,
|
|
15526
|
+
migration: prior.migration,
|
|
15527
|
+
targetStateFilePreconditions: {
|
|
15528
|
+
graphLockPath: stableGraphLockPath,
|
|
15529
|
+
graphLockRevision: stableLock ? createHash14("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
|
|
15530
|
+
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
15531
|
+
}
|
|
15532
|
+
};
|
|
15533
|
+
}
|
|
15534
|
+
async function readTargetGraphLock(target, options) {
|
|
15535
|
+
const state = await resolveCliTargetState(target, options);
|
|
15536
|
+
if (!state.graphLock || !state.graphLockPath) {
|
|
15537
|
+
throw new Error(`No graph lock for ${state.adapter.name} at ${target.targetRoot}: ${state.stableGraphLockPath}`);
|
|
14838
15538
|
}
|
|
14839
|
-
return { adapter, path, lock:
|
|
15539
|
+
return { adapter: state.adapter, path: state.graphLockPath, lock: state.graphLock };
|
|
14840
15540
|
}
|
|
14841
15541
|
async function printStatus(target, options) {
|
|
14842
15542
|
const report = await collectTargetStatus(target, options);
|
|
@@ -14844,30 +15544,24 @@ async function printStatus(target, options) {
|
|
|
14844
15544
|
}
|
|
14845
15545
|
async function collectTargetStatus(target, options) {
|
|
14846
15546
|
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
|
-
}
|
|
15547
|
+
const state = await resolveCliTargetState(target, { ...options, warn: () => void 0 });
|
|
15548
|
+
const { adapter, installationType, manifest, graphLock, graphLockPath } = state;
|
|
15549
|
+
const pending = await collectPendingInstallWork(target, options);
|
|
15550
|
+
const foreignObservations = pending.foreignStateObservations;
|
|
15551
|
+
const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
|
|
15552
|
+
const observedManifestRevision = foreignObservations.length > 0 ? createHash14("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
|
|
15553
|
+
observation.fileName,
|
|
15554
|
+
observation.manifestRevision
|
|
15555
|
+
]).sort())).digest("hex") : null;
|
|
14862
15556
|
const packages = [];
|
|
14863
15557
|
for (const pkg of config.packages) {
|
|
14864
|
-
const root =
|
|
14865
|
-
const node = root ?
|
|
15558
|
+
const root = effectiveGraphLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
15559
|
+
const node = root ? effectiveGraphLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId) : void 0;
|
|
14866
15560
|
const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
|
|
14867
15561
|
forceRefresh: options.refresh,
|
|
14868
15562
|
offline: options.offline
|
|
14869
15563
|
});
|
|
14870
|
-
const installed = node && manifest?.entries.some((entry) => "graphNodeId" in entry && entry.graphNodeId === node.id) ? node.version : null;
|
|
15564
|
+
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
15565
|
const locked = node?.version ?? null;
|
|
14872
15566
|
const baseline = installed ?? locked;
|
|
14873
15567
|
packages.push({
|
|
@@ -14890,14 +15584,13 @@ async function collectTargetStatus(target, options) {
|
|
|
14890
15584
|
)
|
|
14891
15585
|
});
|
|
14892
15586
|
}
|
|
14893
|
-
const
|
|
14894
|
-
|
|
14895
|
-
const
|
|
14896
|
-
const installed = manifest?.entries.some((entry) => {
|
|
15587
|
+
const artifacts = (effectiveGraphLock?.canonical.artifacts ?? []).map((artifact) => {
|
|
15588
|
+
const node = effectiveGraphLock?.canonical.nodes.find((candidate) => candidate.id === artifact.graphNodeId);
|
|
15589
|
+
const installed = (manifest?.entries.some((entry) => {
|
|
14897
15590
|
if (!("graphNodeId" in entry) || entry.graphNodeId !== artifact.graphNodeId) return false;
|
|
14898
15591
|
if ("logicalSelector" in entry && entry.logicalSelector) return entry.logicalSelector === artifact.logicalSelector;
|
|
14899
15592
|
return entry.artifactType === artifact.type && entry.artifactName === artifact.name;
|
|
14900
|
-
}) ?? false;
|
|
15593
|
+
}) ?? false) || foreignObservations.some((observation) => observation.graphNodeId === artifact.graphNodeId && (!observation.logicalSelector || observation.logicalSelector === artifact.logicalSelector));
|
|
14901
15594
|
return {
|
|
14902
15595
|
selector: artifact.logicalSelector,
|
|
14903
15596
|
type: artifact.type,
|
|
@@ -14910,7 +15603,7 @@ async function collectTargetStatus(target, options) {
|
|
|
14910
15603
|
};
|
|
14911
15604
|
});
|
|
14912
15605
|
const health = [];
|
|
14913
|
-
if (!manifest || !
|
|
15606
|
+
if (!manifest && foreignObservations.length === 0 || !effectiveGraphLock) health.push("FAIL");
|
|
14914
15607
|
if (pending.error) health.push("DEGRADED");
|
|
14915
15608
|
if (pending.driftCount > 0 || pending.conflictCount > 0) health.push("FAIL");
|
|
14916
15609
|
else if (pending.pendingCount > 0) health.push("WARN");
|
|
@@ -14921,11 +15614,11 @@ async function collectTargetStatus(target, options) {
|
|
|
14921
15614
|
installationType,
|
|
14922
15615
|
targetRoot: state.installRoot,
|
|
14923
15616
|
health: worstStatusHealth(health),
|
|
14924
|
-
manifestRevision: manifest?.revision ??
|
|
14925
|
-
manifestEntryCount: manifest?.entries.length ?? 0,
|
|
15617
|
+
manifestRevision: manifest?.revision ?? observedManifestRevision,
|
|
15618
|
+
manifestEntryCount: (manifest?.entries.length ?? 0) + foreignObservations.length,
|
|
14926
15619
|
graphLockPath,
|
|
14927
15620
|
packageCount: packages.length,
|
|
14928
|
-
artifactCount:
|
|
15621
|
+
artifactCount: effectiveGraphLock?.canonical.artifacts.length ?? 0,
|
|
14929
15622
|
pendingCount: pending.pendingCount,
|
|
14930
15623
|
driftCount: pending.driftCount,
|
|
14931
15624
|
conflictCount: pending.conflictCount,
|
|
@@ -15103,7 +15796,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
15103
15796
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
15104
15797
|
);
|
|
15105
15798
|
}
|
|
15106
|
-
const memberRoot =
|
|
15799
|
+
const memberRoot = resolve25(workspaceRoot, member.workspace);
|
|
15107
15800
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
15108
15801
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
15109
15802
|
throw new Error(
|
|
@@ -15260,9 +15953,34 @@ async function journalStateForTarget(target, options) {
|
|
|
15260
15953
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
15261
15954
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15262
15955
|
const transport = transportForTarget(target);
|
|
15263
|
-
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
15264
|
-
const
|
|
15265
|
-
|
|
15956
|
+
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
15957
|
+
const stable = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
15958
|
+
const graphLockPath = graphLockPathForTarget(
|
|
15959
|
+
target.workspaceRoot,
|
|
15960
|
+
targetKeyForTarget(target, adapter.name),
|
|
15961
|
+
adapter.name,
|
|
15962
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
15963
|
+
targetIdentityContext(target, adapter, installationType)
|
|
15964
|
+
);
|
|
15965
|
+
const ownership = await resolveWorkspaceOwnershipScope(target.workspaceRoot, { fleetId: target.fleetId });
|
|
15966
|
+
const pending = await discoverTargetApplyJournal({
|
|
15967
|
+
adapter: adapter.name,
|
|
15968
|
+
installationType,
|
|
15969
|
+
stateKey: stable.stateKey,
|
|
15970
|
+
explicitStateKey: target.stateKey,
|
|
15971
|
+
fleetId: target.fleetId,
|
|
15972
|
+
installRoot: stable.installRoot,
|
|
15973
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
15974
|
+
graphLockPath,
|
|
15975
|
+
transport
|
|
15976
|
+
});
|
|
15977
|
+
return {
|
|
15978
|
+
adapter,
|
|
15979
|
+
transport,
|
|
15980
|
+
installationType,
|
|
15981
|
+
installRoot: stable.installRoot,
|
|
15982
|
+
state: { installationType, stateKey: pending?.stateKey ?? stable.stateKey }
|
|
15983
|
+
};
|
|
15266
15984
|
}
|
|
15267
15985
|
async function collectPendingInstallWork(target, options) {
|
|
15268
15986
|
let results = [];
|
|
@@ -15283,11 +16001,20 @@ async function collectPendingInstallWork(target, options) {
|
|
|
15283
16001
|
pendingCount: pending.length,
|
|
15284
16002
|
driftCount: counts.drift ?? 0,
|
|
15285
16003
|
conflictCount: counts.conflict ?? 0,
|
|
15286
|
-
counts
|
|
16004
|
+
counts,
|
|
16005
|
+
foreignStateObservations: results.flatMap((result) => result.foreignStateObservations),
|
|
16006
|
+
observedGraphLock: results.length === 1 ? results[0].bundle.graphLock : void 0
|
|
15287
16007
|
};
|
|
15288
16008
|
} catch (error) {
|
|
15289
16009
|
const message = error instanceof Error ? error.message : String(error);
|
|
15290
|
-
return {
|
|
16010
|
+
return {
|
|
16011
|
+
pendingCount: 0,
|
|
16012
|
+
driftCount: 0,
|
|
16013
|
+
conflictCount: 0,
|
|
16014
|
+
counts: {},
|
|
16015
|
+
foreignStateObservations: [],
|
|
16016
|
+
error: message
|
|
16017
|
+
};
|
|
15291
16018
|
} finally {
|
|
15292
16019
|
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
15293
16020
|
}
|
|
@@ -15300,9 +16027,8 @@ async function printDoctor(target, options) {
|
|
|
15300
16027
|
if (!targetMapping?.enabled) {
|
|
15301
16028
|
throw new Error(`Adapter ${adapter.name} does not support skills for installation type '${installationType}'.`);
|
|
15302
16029
|
}
|
|
15303
|
-
const
|
|
15304
|
-
const
|
|
15305
|
-
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
16030
|
+
const state = await resolveCliTargetState(target, { ...options, installationType });
|
|
16031
|
+
const manifest = state.manifest;
|
|
15306
16032
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
15307
16033
|
const skills = [];
|
|
15308
16034
|
for (const request of requestedSkills) {
|
|
@@ -15554,14 +16280,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
15554
16280
|
}
|
|
15555
16281
|
function normalizeCliPath(value) {
|
|
15556
16282
|
if (value === "~") return homedir12();
|
|
15557
|
-
if (value.startsWith("~/")) return
|
|
15558
|
-
return
|
|
16283
|
+
if (value.startsWith("~/")) return resolve25(homedir12(), value.slice(2));
|
|
16284
|
+
return resolve25(value);
|
|
15559
16285
|
}
|
|
15560
16286
|
function shellQuoteArgument(value) {
|
|
15561
16287
|
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
15562
16288
|
}
|
|
15563
16289
|
function isHomePath(path) {
|
|
15564
|
-
return
|
|
16290
|
+
return resolve25(path) === resolve25(homedir12());
|
|
15565
16291
|
}
|
|
15566
16292
|
function adapterListFromOption(adapter) {
|
|
15567
16293
|
if (!adapter) return [];
|
|
@@ -15682,7 +16408,7 @@ function withFleetExample(config) {
|
|
|
15682
16408
|
};
|
|
15683
16409
|
}
|
|
15684
16410
|
async function findAgentwheelPackageRoot(start) {
|
|
15685
|
-
let current =
|
|
16411
|
+
let current = resolve25(start);
|
|
15686
16412
|
while (true) {
|
|
15687
16413
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
15688
16414
|
const parent = dirname22(current);
|