agentwheel 0.20.7 → 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 +967 -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,11 @@ 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;
|
|
7243
|
+
const expandedArtifacts = await expandMarkdownIncludes(compositionArtifacts, staged.root, {
|
|
7231
7244
|
nodeId: rawNode.node.id,
|
|
7232
7245
|
originNodeId: rawNode.node.id,
|
|
7233
7246
|
additionalComposeEntries: (artifact) => matchingCompositionEntries(
|
|
@@ -7274,7 +7287,12 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7274
7287
|
};
|
|
7275
7288
|
}
|
|
7276
7289
|
});
|
|
7277
|
-
const selectedArtifacts = filterArtifactsBySelection(
|
|
7290
|
+
const selectedArtifacts = filterArtifactsBySelection(
|
|
7291
|
+
expandedArtifacts,
|
|
7292
|
+
rawNode.node.selected,
|
|
7293
|
+
void 0,
|
|
7294
|
+
{ validationArtifacts: staged.artifacts }
|
|
7295
|
+
);
|
|
7278
7296
|
const claudeRenderedArtifacts = await renderClaudeSubagents(selectedArtifacts, staged.root, targetContext.adapter);
|
|
7279
7297
|
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, staged.root, targetContext.adapter);
|
|
7280
7298
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
@@ -7388,8 +7406,9 @@ function sha2563(content) {
|
|
|
7388
7406
|
return createHash7("sha256").update(content).digest("hex");
|
|
7389
7407
|
}
|
|
7390
7408
|
function assignInstallNames(graph, artifacts) {
|
|
7391
|
-
const
|
|
7392
|
-
const
|
|
7409
|
+
const equivalentArtifacts = coalesceEquivalentDirectArtifacts(artifacts);
|
|
7410
|
+
const superseded = supersededLogicalSelectors(equivalentArtifacts);
|
|
7411
|
+
const effectiveArtifacts = equivalentArtifacts.filter((artifact) => !superseded.has(artifact.logicalSelector));
|
|
7393
7412
|
const aliases = workspaceAliases(graph);
|
|
7394
7413
|
validateAliasScopes(graph, effectiveArtifacts, aliases);
|
|
7395
7414
|
const decisions = /* @__PURE__ */ new Map();
|
|
@@ -7432,6 +7451,37 @@ function assignInstallNames(graph, artifacts) {
|
|
|
7432
7451
|
}));
|
|
7433
7452
|
return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
|
|
7434
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
|
+
}
|
|
7435
7485
|
function supersededLogicalSelectors(artifacts) {
|
|
7436
7486
|
const removed = /* @__PURE__ */ new Set();
|
|
7437
7487
|
for (const replacement of artifacts) {
|
|
@@ -7594,7 +7644,8 @@ function reachableNodeIds(graph, rootNodeId) {
|
|
|
7594
7644
|
return reachable;
|
|
7595
7645
|
}
|
|
7596
7646
|
function installNameCollisionError(group) {
|
|
7597
|
-
|
|
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}`);
|
|
7598
7649
|
}
|
|
7599
7650
|
function namespaceDecision(artifact, reason) {
|
|
7600
7651
|
return {
|
|
@@ -8028,6 +8079,63 @@ function containsPath(parent, candidate) {
|
|
|
8028
8079
|
return path === "" || !path.startsWith("..") && !isAbsolute2(path);
|
|
8029
8080
|
}
|
|
8030
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
|
+
|
|
8031
8139
|
// src/lifecycle/source-plan.ts
|
|
8032
8140
|
async function createGraphSourcePlan(options) {
|
|
8033
8141
|
if (options.roots.length === 0) {
|
|
@@ -8041,22 +8149,62 @@ async function createGraphSourcePlan(options) {
|
|
|
8041
8149
|
options.warn?.(message);
|
|
8042
8150
|
};
|
|
8043
8151
|
const installationType = options.installationType ?? resolveInstallationTypeForAdapterTarget(options.adapter);
|
|
8044
|
-
const
|
|
8152
|
+
const targetFingerprintParts2 = options.targetFingerprintParts ?? {
|
|
8045
8153
|
adapter: options.adapter.name,
|
|
8046
8154
|
fleetId: options.fleetId,
|
|
8047
8155
|
installationType,
|
|
8048
8156
|
targetRoot: options.targetRoot,
|
|
8049
8157
|
transport: transport.kind,
|
|
8050
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
|
|
8051
8167
|
});
|
|
8168
|
+
const targetFingerprint = targetIdentity.targetFingerprint;
|
|
8052
8169
|
const stateKey = stateKeyFor(options.adapter.name, {
|
|
8053
8170
|
installationType,
|
|
8054
8171
|
stateKey: options.stateKey,
|
|
8055
|
-
targetFingerprint,
|
|
8172
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
8056
8173
|
fleetId: options.fleetId
|
|
8057
8174
|
});
|
|
8058
|
-
const
|
|
8059
|
-
|
|
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
|
+
}
|
|
8060
8208
|
const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
|
|
8061
8209
|
const trustPolicy = {
|
|
8062
8210
|
...normalizeTrustPolicy(workspaceConfig.trust),
|
|
@@ -8064,8 +8212,29 @@ async function createGraphSourcePlan(options) {
|
|
|
8064
8212
|
};
|
|
8065
8213
|
const lockMode = options.frozenLock === true || options.offline === true;
|
|
8066
8214
|
const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
|
|
8067
|
-
const
|
|
8068
|
-
|
|
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;
|
|
8069
8238
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
8070
8239
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
8071
8240
|
workspaceRoot,
|
|
@@ -8093,6 +8262,7 @@ async function createGraphSourcePlan(options) {
|
|
|
8093
8262
|
adapter: options.adapter,
|
|
8094
8263
|
installationType,
|
|
8095
8264
|
targetFingerprint,
|
|
8265
|
+
noDeps: options.noDeps,
|
|
8096
8266
|
warn
|
|
8097
8267
|
});
|
|
8098
8268
|
const desiredArtifacts = filterArtifactsByAdapterTargets(
|
|
@@ -8105,13 +8275,13 @@ async function createGraphSourcePlan(options) {
|
|
|
8105
8275
|
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
8106
8276
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
8107
8277
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
}
|
|
8113
|
-
const
|
|
8114
|
-
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(
|
|
8115
8285
|
desiredArtifacts,
|
|
8116
8286
|
options.adapter,
|
|
8117
8287
|
options.targetRoot,
|
|
@@ -8133,10 +8303,18 @@ async function createGraphSourcePlan(options) {
|
|
|
8133
8303
|
forceDrift: options.forceDrift,
|
|
8134
8304
|
forceConflict: options.forceConflict,
|
|
8135
8305
|
replaceConflict: options.replaceConflict,
|
|
8306
|
+
stateMigration: priorState.migration,
|
|
8136
8307
|
warn
|
|
8137
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 = [];
|
|
8138
8316
|
if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
|
|
8139
|
-
await assertNoForeignWorkspaceState({
|
|
8317
|
+
foreignStateObservations = await assertNoForeignWorkspaceState({
|
|
8140
8318
|
installRoot: resolvedInstallRoot,
|
|
8141
8319
|
adapter: options.adapter.name,
|
|
8142
8320
|
transport,
|
|
@@ -8144,9 +8322,11 @@ async function createGraphSourcePlan(options) {
|
|
|
8144
8322
|
workspaceOwner,
|
|
8145
8323
|
globalRoot: options.globalRoot,
|
|
8146
8324
|
stateKey,
|
|
8325
|
+
migratingStateKey: priorState.migration?.fromStateKey,
|
|
8147
8326
|
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8148
8327
|
plannedOperations: plan.operations
|
|
8149
8328
|
});
|
|
8329
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8150
8330
|
}
|
|
8151
8331
|
return {
|
|
8152
8332
|
plan,
|
|
@@ -8159,7 +8339,10 @@ async function createGraphSourcePlan(options) {
|
|
|
8159
8339
|
warnings,
|
|
8160
8340
|
newTransitiveSources: trustEvaluation.promptSources,
|
|
8161
8341
|
graphDiff,
|
|
8162
|
-
recoveredPendingApply
|
|
8342
|
+
recoveredPendingApply,
|
|
8343
|
+
previousManifest: priorState.manifest,
|
|
8344
|
+
previousGraphLock: priorState.graphLock,
|
|
8345
|
+
foreignStateObservations
|
|
8163
8346
|
};
|
|
8164
8347
|
}
|
|
8165
8348
|
async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
@@ -8172,12 +8355,15 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
|
8172
8355
|
workspaceOwner: options.workspaceOwner,
|
|
8173
8356
|
globalRoot: options.globalRoot,
|
|
8174
8357
|
stateKey: plan.stateKey,
|
|
8358
|
+
migratingStateKey: plan.stateMigration?.fromStateKey,
|
|
8175
8359
|
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
|
|
8176
8360
|
plannedOperations: plan.operations
|
|
8177
8361
|
});
|
|
8362
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8178
8363
|
}
|
|
8179
|
-
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
|
|
8180
|
-
|
|
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);
|
|
8181
8367
|
}
|
|
8182
8368
|
function desiredArtifactsFromGraphBundle(bundle) {
|
|
8183
8369
|
return bundle.artifacts.map((artifact) => desiredArtifactFromResolved(artifact));
|
|
@@ -8219,10 +8405,278 @@ async function readExistingGraphLock(path) {
|
|
|
8219
8405
|
if (!await pathExists(path)) return void 0;
|
|
8220
8406
|
return readGraphLock(path);
|
|
8221
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
|
+
}
|
|
8222
8676
|
var workspaceOwnerPrefix2 = "workspace-root:";
|
|
8223
8677
|
async function assertNoForeignWorkspaceState(check) {
|
|
8224
8678
|
const planned = new Set(check.plannedPaths);
|
|
8225
|
-
if (planned.size === 0) return;
|
|
8679
|
+
if (planned.size === 0) return [];
|
|
8226
8680
|
const plannedOperations = /* @__PURE__ */ new Map();
|
|
8227
8681
|
for (const operation of check.plannedOperations) {
|
|
8228
8682
|
if (!planned.has(operation.relativeDestPath)) continue;
|
|
@@ -8231,18 +8685,22 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8231
8685
|
plannedOperations.set(operation.relativeDestPath, operations);
|
|
8232
8686
|
}
|
|
8233
8687
|
const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
|
|
8234
|
-
const root =
|
|
8688
|
+
const root = resolve16(check.workspaceRoot);
|
|
8235
8689
|
const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
|
|
8236
|
-
const
|
|
8690
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
8237
8691
|
for (const entry of manifests) {
|
|
8238
|
-
if (entry.stateKey === check.stateKey) continue;
|
|
8239
|
-
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
|
+
}
|
|
8240
8700
|
for (const manifestEntry of entry.manifest.entries) {
|
|
8241
8701
|
const owner = "workspaceOwner" in manifestEntry ? manifestEntry.workspaceOwner : void 0;
|
|
8242
8702
|
if (typeof owner !== "string" || !owner.startsWith(workspaceOwnerPrefix2)) continue;
|
|
8243
8703
|
if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
|
|
8244
|
-
const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
|
|
8245
|
-
bucket.entryCount += 1;
|
|
8246
8704
|
if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
|
|
8247
8705
|
check,
|
|
8248
8706
|
manifestEntry.path,
|
|
@@ -8250,16 +8708,46 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8250
8708
|
manifestEntry.mergeRemoval,
|
|
8251
8709
|
plannedOperations.get(manifestEntry.path) ?? []
|
|
8252
8710
|
)) {
|
|
8253
|
-
|
|
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);
|
|
8254
8722
|
}
|
|
8255
|
-
byOwner.set(owner, bucket);
|
|
8256
8723
|
}
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
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);
|
|
8260
8747
|
}
|
|
8261
8748
|
}
|
|
8262
|
-
|
|
8749
|
+
const foreign = [...foreignByOwner.values()];
|
|
8750
|
+
if (foreign.length === 0) return observations.sort((left, right) => left.path.localeCompare(right.path));
|
|
8263
8751
|
throw new Error([
|
|
8264
8752
|
`Refusing to plan ${check.adapter} at ${check.installRoot}.`,
|
|
8265
8753
|
"This runtime root already carries Agentwheel state owned by another workspace, at paths this run would install:",
|
|
@@ -8273,6 +8761,36 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8273
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."
|
|
8274
8762
|
].join("\n"));
|
|
8275
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
|
+
}
|
|
8276
8794
|
async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
|
|
8277
8795
|
if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
|
|
8278
8796
|
const operation = operations[0];
|
|
@@ -8444,7 +8962,7 @@ function targetLabel(target) {
|
|
|
8444
8962
|
}
|
|
8445
8963
|
|
|
8446
8964
|
// src/runtime/target.ts
|
|
8447
|
-
import { basename as
|
|
8965
|
+
import { basename as basename19, dirname as dirname18, join as join34, resolve as resolve17 } from "path";
|
|
8448
8966
|
var runtimeMarkers = [
|
|
8449
8967
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
8450
8968
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -8453,9 +8971,9 @@ var runtimeMarkers = [
|
|
|
8453
8971
|
{ adapter: "copilot", dirs: [".github"] }
|
|
8454
8972
|
];
|
|
8455
8973
|
async function resolveRuntimeTarget(request = {}) {
|
|
8456
|
-
const cwd =
|
|
8974
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8457
8975
|
if (request.targetRoot) {
|
|
8458
|
-
const targetRoot =
|
|
8976
|
+
const targetRoot = resolve17(request.targetRoot);
|
|
8459
8977
|
return {
|
|
8460
8978
|
adapter: request.adapter ?? "openclaw",
|
|
8461
8979
|
installationType: request.installationType,
|
|
@@ -8487,7 +9005,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
8487
9005
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
8488
9006
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
8489
9007
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
8490
|
-
const cwd =
|
|
9008
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
8491
9009
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
8492
9010
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8493
9011
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
|
|
@@ -8497,8 +9015,8 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
8497
9015
|
return targets;
|
|
8498
9016
|
}
|
|
8499
9017
|
async function resolveProfileRuntimeTargets(request) {
|
|
8500
|
-
const cwd =
|
|
8501
|
-
const workspaceRoot = request.targetRoot ?
|
|
9018
|
+
const cwd = resolve17(request.cwd ?? process.cwd());
|
|
9019
|
+
const workspaceRoot = request.targetRoot ? resolve17(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
8502
9020
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8503
9021
|
const profile = config.profiles[request.profile];
|
|
8504
9022
|
if (!profile) {
|
|
@@ -8564,12 +9082,12 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
8564
9082
|
return unique[0];
|
|
8565
9083
|
}
|
|
8566
9084
|
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
8567
|
-
const root =
|
|
9085
|
+
const root = resolve17(cwd);
|
|
8568
9086
|
const matches = [];
|
|
8569
9087
|
for (const marker of runtimeMarkers) {
|
|
8570
9088
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
8571
9089
|
for (const dir of marker.dirs) {
|
|
8572
|
-
if (
|
|
9090
|
+
if (basename19(root) === dir) {
|
|
8573
9091
|
matches.push({ adapter: marker.adapter, targetRoot: dirname18(root) });
|
|
8574
9092
|
} else if (await pathExists(join34(root, dir))) {
|
|
8575
9093
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
@@ -8613,9 +9131,9 @@ function dedupeTargets(matches) {
|
|
|
8613
9131
|
return [...byKey.values()];
|
|
8614
9132
|
}
|
|
8615
9133
|
function runtimeScanRoot(request) {
|
|
8616
|
-
const root =
|
|
9134
|
+
const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
8617
9135
|
if (request.targetRoot) return root;
|
|
8618
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
9136
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename19(root))) ? dirname18(root) : root;
|
|
8619
9137
|
}
|
|
8620
9138
|
|
|
8621
9139
|
// src/lifecycle/profile.ts
|
|
@@ -9002,9 +9520,9 @@ function normalizeVersion(version) {
|
|
|
9002
9520
|
|
|
9003
9521
|
// src/model/package-validate.ts
|
|
9004
9522
|
import { stat as stat12 } from "fs/promises";
|
|
9005
|
-
import { resolve as
|
|
9523
|
+
import { resolve as resolve18 } from "path";
|
|
9006
9524
|
async function validatePackage(root) {
|
|
9007
|
-
const packageRoot =
|
|
9525
|
+
const packageRoot = resolve18(root);
|
|
9008
9526
|
const findings = [];
|
|
9009
9527
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
9010
9528
|
if (!manifestPath) {
|
|
@@ -9105,7 +9623,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
9105
9623
|
try {
|
|
9106
9624
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
9107
9625
|
if (isCrossPackageSelector(selector)) return;
|
|
9108
|
-
const full =
|
|
9626
|
+
const full = resolve18(packageRoot, selector);
|
|
9109
9627
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
9110
9628
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
9111
9629
|
return;
|
|
@@ -9159,10 +9677,10 @@ function isCrossPackageSelector(value) {
|
|
|
9159
9677
|
|
|
9160
9678
|
// src/model/package-migrate.ts
|
|
9161
9679
|
import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
|
|
9162
|
-
import { join as join37, resolve as
|
|
9680
|
+
import { join as join37, resolve as resolve19 } from "path";
|
|
9163
9681
|
import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
|
|
9164
9682
|
async function migratePackageManifest(root) {
|
|
9165
|
-
const packageRoot =
|
|
9683
|
+
const packageRoot = resolve19(root);
|
|
9166
9684
|
for (const name of openPackManifestNames) {
|
|
9167
9685
|
const path = join37(packageRoot, name);
|
|
9168
9686
|
if (await pathExists(path)) {
|
|
@@ -9224,7 +9742,7 @@ function resolveCliVersion() {
|
|
|
9224
9742
|
}
|
|
9225
9743
|
|
|
9226
9744
|
// src/lifecycle/ownership.ts
|
|
9227
|
-
import { resolve as
|
|
9745
|
+
import { resolve as resolve20 } from "path";
|
|
9228
9746
|
async function planArtifactOwnershipHandoff(request) {
|
|
9229
9747
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
9230
9748
|
}
|
|
@@ -9350,8 +9868,8 @@ function containedArtifactPath(targetRoot, relativePath) {
|
|
|
9350
9868
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
9351
9869
|
throw new Error(`Unsafe managed artifact path: ${relativePath}`);
|
|
9352
9870
|
}
|
|
9353
|
-
const root =
|
|
9354
|
-
const candidate =
|
|
9871
|
+
const root = resolve20(targetRoot);
|
|
9872
|
+
const candidate = resolve20(root, relativePath);
|
|
9355
9873
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
9356
9874
|
throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
|
|
9357
9875
|
}
|
|
@@ -9384,7 +9902,7 @@ function assertManifestIdentity(manifest, request) {
|
|
|
9384
9902
|
|
|
9385
9903
|
// src/lifecycle/ownership-retire-stale.ts
|
|
9386
9904
|
import { createHash as createHash9 } from "crypto";
|
|
9387
|
-
import { resolve as
|
|
9905
|
+
import { resolve as resolve21 } from "path";
|
|
9388
9906
|
async function planRetireStaleOwnership(request) {
|
|
9389
9907
|
return observe(request, request.transport ?? localTransport);
|
|
9390
9908
|
}
|
|
@@ -9517,7 +10035,7 @@ async function observe(request, transport) {
|
|
|
9517
10035
|
return { ...withoutDigest, planDigest: digest(withoutDigest) };
|
|
9518
10036
|
}
|
|
9519
10037
|
function normalizeRequest(request) {
|
|
9520
|
-
const targetRoot =
|
|
10038
|
+
const targetRoot = resolve21(request.targetRoot);
|
|
9521
10039
|
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, request.installationType, "source");
|
|
9522
10040
|
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, request.installationType, "destination");
|
|
9523
10041
|
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
@@ -9528,8 +10046,8 @@ function normalizeRequest(request) {
|
|
|
9528
10046
|
installationType: request.installationType.trim(),
|
|
9529
10047
|
sourceStateKey,
|
|
9530
10048
|
destinationStateKey,
|
|
9531
|
-
fromWorkspaceRoot:
|
|
9532
|
-
toWorkspaceRoot:
|
|
10049
|
+
fromWorkspaceRoot: resolve21(request.fromWorkspaceRoot),
|
|
10050
|
+
toWorkspaceRoot: resolve21(request.toWorkspaceRoot),
|
|
9533
10051
|
toFleetId: request.toFleetId.trim()
|
|
9534
10052
|
};
|
|
9535
10053
|
}
|
|
@@ -9550,7 +10068,7 @@ async function requireManifest(request, stateKey, transport, label) {
|
|
|
9550
10068
|
installationType: manifest.installationType,
|
|
9551
10069
|
stateKey: manifest.stateKey
|
|
9552
10070
|
});
|
|
9553
|
-
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) {
|
|
9554
10072
|
throw new Error(`${label} install manifest identity does not match the requested adapter, target, installation type, and state key.`);
|
|
9555
10073
|
}
|
|
9556
10074
|
return manifest;
|
|
@@ -9634,7 +10152,7 @@ function canonicalJson(value) {
|
|
|
9634
10152
|
// src/version/policy.ts
|
|
9635
10153
|
import { execFile as execFile3 } from "child_process";
|
|
9636
10154
|
import { readFile as readFile23 } from "fs/promises";
|
|
9637
|
-
import { join as join39, resolve as
|
|
10155
|
+
import { join as join39, resolve as resolve22 } from "path";
|
|
9638
10156
|
import { promisify as promisify3 } from "util";
|
|
9639
10157
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
9640
10158
|
import { z as z5 } from "zod";
|
|
@@ -9728,7 +10246,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9728
10246
|
if (tagged.length > 0) return tagged;
|
|
9729
10247
|
}
|
|
9730
10248
|
if (driverName === "local") {
|
|
9731
|
-
const root =
|
|
10249
|
+
const root = resolve22(workspaceRoot, pkg.source);
|
|
9732
10250
|
const manifest2 = await readPackageManifest(root);
|
|
9733
10251
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
9734
10252
|
try {
|
|
@@ -9851,7 +10369,7 @@ async function readVersionCache(path) {
|
|
|
9851
10369
|
// src/profile/members.ts
|
|
9852
10370
|
import { execFile as execFile4 } from "child_process";
|
|
9853
10371
|
import { readFile as readFile24 } from "fs/promises";
|
|
9854
|
-
import { join as join40, resolve as
|
|
10372
|
+
import { join as join40, resolve as resolve23 } from "path";
|
|
9855
10373
|
import { promisify as promisify4 } from "util";
|
|
9856
10374
|
import { z as z7 } from "zod";
|
|
9857
10375
|
|
|
@@ -10026,7 +10544,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
10026
10544
|
let stderr = "";
|
|
10027
10545
|
try {
|
|
10028
10546
|
if (member.transport === "local") {
|
|
10029
|
-
const workspace =
|
|
10547
|
+
const workspace = resolve23(parentWorkspace, member.workspace);
|
|
10030
10548
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
10031
10549
|
cwd: workspace,
|
|
10032
10550
|
env: env2,
|
|
@@ -10072,7 +10590,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
10072
10590
|
process.execPath,
|
|
10073
10591
|
[process.argv[1], ...memberCommandArgs(member, args)],
|
|
10074
10592
|
{
|
|
10075
|
-
cwd:
|
|
10593
|
+
cwd: resolve23(parentWorkspace, member.workspace),
|
|
10076
10594
|
env: env2,
|
|
10077
10595
|
maxBuffer: 20 * 1024 * 1024
|
|
10078
10596
|
}
|
|
@@ -10178,7 +10696,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
10178
10696
|
}
|
|
10179
10697
|
}
|
|
10180
10698
|
function compositeKey(workspaceRoot, profileName) {
|
|
10181
|
-
return `${
|
|
10699
|
+
return `${resolve23(workspaceRoot)}#${profileName}`;
|
|
10182
10700
|
}
|
|
10183
10701
|
|
|
10184
10702
|
// src/status/repository.ts
|
|
@@ -11103,9 +11621,9 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
11103
11621
|
|
|
11104
11622
|
// src/lifecycle/fleet-normalize.ts
|
|
11105
11623
|
import { createHash as createHash13 } from "crypto";
|
|
11106
|
-
import { readFile as readFile27, readdir as
|
|
11624
|
+
import { readFile as readFile27, readdir as readdir10, rm as rm9 } from "fs/promises";
|
|
11107
11625
|
import { homedir as homedir11 } from "os";
|
|
11108
|
-
import {
|
|
11626
|
+
import { dirname as dirname21, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve24 } from "path";
|
|
11109
11627
|
async function planFleetNormalization(request) {
|
|
11110
11628
|
const normalizedRequest = normalizeRequest2(request);
|
|
11111
11629
|
const destinationScope = await resolveWorkspaceScope({
|
|
@@ -11376,17 +11894,17 @@ function normalizeRequest2(request) {
|
|
|
11376
11894
|
...profile ? { profile } : {},
|
|
11377
11895
|
...agent ? { agent } : {},
|
|
11378
11896
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
11379
|
-
...request.globalRoot ? { globalRoot:
|
|
11897
|
+
...request.globalRoot ? { globalRoot: resolve24(request.globalRoot) } : {}
|
|
11380
11898
|
};
|
|
11381
11899
|
}
|
|
11382
11900
|
function normalizeOrphanedOwnerRoot(value) {
|
|
11383
11901
|
const root = value.trim();
|
|
11384
11902
|
if (!root || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
11385
|
-
return
|
|
11903
|
+
return resolve24(root);
|
|
11386
11904
|
}
|
|
11387
11905
|
async function normalizationRoots(request) {
|
|
11388
11906
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
11389
|
-
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;
|
|
11390
11908
|
return { source, destination: destination.root };
|
|
11391
11909
|
}
|
|
11392
11910
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -11452,11 +11970,28 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11452
11970
|
const coveredDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11453
11971
|
const plannedDestinationGraphs = /* @__PURE__ */ new Set();
|
|
11454
11972
|
for (const sourceGraph of sourceGraphs) {
|
|
11455
|
-
const sourceState = await
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
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
|
+
);
|
|
11459
11986
|
}
|
|
11987
|
+
const destinationGraph = matchingDestinationGraphs[0];
|
|
11988
|
+
const destinationState = destinationGraph ? await installedTargetStateForGraph(
|
|
11989
|
+
destination,
|
|
11990
|
+
destinationGraph,
|
|
11991
|
+
manifests,
|
|
11992
|
+
destinationOwners,
|
|
11993
|
+
selected
|
|
11994
|
+
) : destinationStableState;
|
|
11460
11995
|
if (sourceState.adapter !== destinationState.adapter) {
|
|
11461
11996
|
throw new Error(`Source and destination adapters diverge for target '${sourceGraph.targetKey}'.`);
|
|
11462
11997
|
}
|
|
@@ -11465,7 +12000,6 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
11465
12000
|
throw new Error(`Source graph lock is not covered by its canonical install manifest: ${sourceState.manifestPath}`);
|
|
11466
12001
|
}
|
|
11467
12002
|
const sourceGraphArtifacts = new Set(sourceGraph.artifactIdentities);
|
|
11468
|
-
const destinationGraph = destinationGraphs.find((candidate) => candidate.path === destinationState.graphLockPath);
|
|
11469
12003
|
if (destinationGraph && destinationGraph.digest !== sourceGraph.digest) {
|
|
11470
12004
|
throw new Error("Source and destination graph declarations, selections, versions, identities, or rendered paths diverge.");
|
|
11471
12005
|
}
|
|
@@ -11583,16 +12117,30 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
11583
12117
|
const transferableOwners = orphanedOwners.size > 0 ? orphanedOwners : /* @__PURE__ */ new Set([legacyOwner]);
|
|
11584
12118
|
let normalizedGraphCount = 0;
|
|
11585
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
|
+
}
|
|
11586
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
|
+
}
|
|
11587
12139
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
11588
12140
|
const relevantManifestEntries2 = legacyManifest?.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected) && entryMatchesArtifacts(entry, selectedArtifacts)) ?? [];
|
|
11589
12141
|
if (relevantManifestEntries2.length > 0) {
|
|
11590
12142
|
graphs.push(graph);
|
|
11591
12143
|
}
|
|
11592
|
-
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
|
|
11593
|
-
if (destinationState.graphLockPath === graph.path && legacyManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
11594
|
-
normalizedGraphCount += 1;
|
|
11595
|
-
}
|
|
11596
12144
|
}
|
|
11597
12145
|
if (graphs.length === 0 && normalizedGraphCount > 0) {
|
|
11598
12146
|
throw new Error(`Fleet '${fleet.fleetId}' installed state is already normalized to fleet-qualified ownership.`);
|
|
@@ -11760,7 +12308,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
11760
12308
|
async function runtimeRoots(scope) {
|
|
11761
12309
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
11762
12310
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
11763
|
-
roots.add(
|
|
12311
|
+
roots.add(resolve24(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
11764
12312
|
}
|
|
11765
12313
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
11766
12314
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -11781,7 +12329,7 @@ async function collectManifests(roots) {
|
|
|
11781
12329
|
const dir = join44(root, ".agentwheel");
|
|
11782
12330
|
let names;
|
|
11783
12331
|
try {
|
|
11784
|
-
names = await
|
|
12332
|
+
names = await readdir10(dir);
|
|
11785
12333
|
} catch (error) {
|
|
11786
12334
|
if (isMissing(error)) continue;
|
|
11787
12335
|
throw error;
|
|
@@ -11797,14 +12345,14 @@ async function collectManifests(roots) {
|
|
|
11797
12345
|
}
|
|
11798
12346
|
continue;
|
|
11799
12347
|
}
|
|
11800
|
-
if (
|
|
12348
|
+
if (resolve24(parsed.targetRoot) !== resolve24(root)) {
|
|
11801
12349
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
11802
12350
|
}
|
|
11803
12351
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
11804
12352
|
installationType: parsed.installationType,
|
|
11805
12353
|
stateKey: parsed.stateKey
|
|
11806
12354
|
});
|
|
11807
|
-
if (
|
|
12355
|
+
if (resolve24(expectedPath) !== resolve24(path)) {
|
|
11808
12356
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11809
12357
|
}
|
|
11810
12358
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11825,7 +12373,7 @@ async function collectManifestPaths(paths) {
|
|
|
11825
12373
|
installationType: parsed.installationType,
|
|
11826
12374
|
stateKey: parsed.stateKey
|
|
11827
12375
|
});
|
|
11828
|
-
if (
|
|
12376
|
+
if (resolve24(expectedPath) !== resolve24(path)) {
|
|
11829
12377
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
11830
12378
|
}
|
|
11831
12379
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -11853,12 +12401,8 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11853
12401
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
11854
12402
|
const parts = relative8(root, path).split(/[\\/]/);
|
|
11855
12403
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
11856
|
-
const [targetKey2, adapter
|
|
12404
|
+
const [targetKey2, adapter] = parts;
|
|
11857
12405
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
11858
|
-
const pathFingerprint = basename19(fileName, ".graph-lock.json");
|
|
11859
|
-
if (!lock.canonical.targetFingerprint || lock.canonical.targetFingerprint !== pathFingerprint) {
|
|
11860
|
-
throw new Error(`Graph lock fingerprint does not match its canonical path: ${path}`);
|
|
11861
|
-
}
|
|
11862
12406
|
const candidate = {
|
|
11863
12407
|
path,
|
|
11864
12408
|
lock,
|
|
@@ -11887,9 +12431,19 @@ function isCurrentLocalTarget(scope, graph) {
|
|
|
11887
12431
|
}
|
|
11888
12432
|
async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
11889
12433
|
if (!isCurrentLocalTarget(scope, graph)) return false;
|
|
11890
|
-
const
|
|
11891
|
-
|
|
11892
|
-
|
|
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;
|
|
11893
12447
|
}
|
|
11894
12448
|
async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
11895
12449
|
const agent = scope.config.agents[graph.targetKey];
|
|
@@ -11908,9 +12462,10 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
11908
12462
|
baseDir: scope.root
|
|
11909
12463
|
});
|
|
11910
12464
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
11911
|
-
const installRoot =
|
|
12465
|
+
const installRoot = resolve24(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
11912
12466
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11913
12467
|
installationType: agent.installationType,
|
|
12468
|
+
stateKey: agent.stateKey,
|
|
11914
12469
|
targetFingerprint: graph.lock.canonical.targetFingerprint
|
|
11915
12470
|
});
|
|
11916
12471
|
return installManifestPath(installRoot, adapter.name, { installationType: agent.installationType, stateKey });
|
|
@@ -11975,14 +12530,21 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11975
12530
|
ssh: void 0,
|
|
11976
12531
|
...agent?.stateKey ? { stateKey: agent.stateKey } : {}
|
|
11977
12532
|
};
|
|
11978
|
-
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;
|
|
11979
12542
|
const stateKey = stateKeyFor(adapter.name, {
|
|
11980
12543
|
installationType,
|
|
11981
12544
|
stateKey: agent?.stateKey,
|
|
11982
|
-
targetFingerprint,
|
|
12545
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
11983
12546
|
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
11984
12547
|
});
|
|
11985
|
-
const installRoot = resolve23(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
11986
12548
|
return {
|
|
11987
12549
|
adapter: adapter.name,
|
|
11988
12550
|
installationType,
|
|
@@ -11990,28 +12552,81 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
11990
12552
|
installRoot,
|
|
11991
12553
|
stateKey,
|
|
11992
12554
|
targetFingerprint,
|
|
11993
|
-
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
|
+
}),
|
|
11994
12559
|
manifestPath: installManifestPath(installRoot, adapter.name, { installationType, stateKey })
|
|
11995
12560
|
};
|
|
11996
12561
|
}
|
|
11997
|
-
async function
|
|
11998
|
-
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);
|
|
11999
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];
|
|
12000
12588
|
const stateKey = stateKeyFor(current.adapter, {
|
|
12001
12589
|
installationType: current.installationType,
|
|
12002
|
-
|
|
12590
|
+
stateKey: agent?.stateKey,
|
|
12591
|
+
targetFingerprint,
|
|
12592
|
+
...identityFleetId ? { fleetId: identityFleetId } : {}
|
|
12003
12593
|
});
|
|
12004
12594
|
return {
|
|
12005
12595
|
...current,
|
|
12006
12596
|
stateKey,
|
|
12007
12597
|
targetFingerprint,
|
|
12008
|
-
graphLockPath: graph.path,
|
|
12598
|
+
graphLockPath: join44(dirname21(graph.path), `${targetFingerprint}.graph-lock.json`),
|
|
12009
12599
|
manifestPath: installManifestPath(current.installRoot, current.adapter, {
|
|
12010
12600
|
installationType: current.installationType,
|
|
12011
12601
|
stateKey
|
|
12012
12602
|
})
|
|
12013
12603
|
};
|
|
12014
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
|
+
}
|
|
12015
12630
|
function assertGraphIsSubset(source, destination) {
|
|
12016
12631
|
const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
|
|
12017
12632
|
for (const root of destination.lock.canonical.roots) {
|
|
@@ -12045,7 +12660,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
12045
12660
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
12046
12661
|
if (existing) {
|
|
12047
12662
|
const manifest2 = existing.manifest;
|
|
12048
|
-
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) {
|
|
12049
12664
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
12050
12665
|
}
|
|
12051
12666
|
}
|
|
@@ -12105,7 +12720,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12105
12720
|
const content = await readFile27(path, "utf8");
|
|
12106
12721
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
12107
12722
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
12108
|
-
} else if (
|
|
12723
|
+
} else if (resolve24(sourcePath) === resolve24(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
12109
12724
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
12110
12725
|
} else {
|
|
12111
12726
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -12116,8 +12731,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
12116
12731
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
12117
12732
|
}
|
|
12118
12733
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
12119
|
-
const normalizedRoot =
|
|
12120
|
-
const candidate =
|
|
12734
|
+
const normalizedRoot = resolve24(root);
|
|
12735
|
+
const candidate = resolve24(normalizedRoot, entryPath);
|
|
12121
12736
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
12122
12737
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
12123
12738
|
}
|
|
@@ -12208,8 +12823,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
12208
12823
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
12209
12824
|
}
|
|
12210
12825
|
function renderedEntryPath(manifest, entry) {
|
|
12211
|
-
const root =
|
|
12212
|
-
const candidate =
|
|
12826
|
+
const root = resolve24(manifest.targetRoot);
|
|
12827
|
+
const candidate = resolve24(root, entry.path);
|
|
12213
12828
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
12214
12829
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
12215
12830
|
}
|
|
@@ -12916,11 +13531,11 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
12916
13531
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
12917
13532
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
12918
13533
|
for (const target of targets) {
|
|
12919
|
-
const
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
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));
|
|
12924
13539
|
}
|
|
12925
13540
|
})
|
|
12926
13541
|
);
|
|
@@ -13011,19 +13626,23 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13011
13626
|
throw new Error(`Ownership handoff selector must be exactly type/name: ${selector}`);
|
|
13012
13627
|
}
|
|
13013
13628
|
const target = targets[0];
|
|
13014
|
-
const
|
|
13015
|
-
const adapter
|
|
13016
|
-
|
|
13017
|
-
|
|
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
|
+
}
|
|
13018
13636
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13019
13637
|
if (options.toFleet) {
|
|
13020
13638
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
13021
|
-
if (
|
|
13639
|
+
if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
|
|
13022
13640
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13023
13641
|
}
|
|
13024
13642
|
}
|
|
13025
13643
|
const request = {
|
|
13026
|
-
|
|
13644
|
+
installationType,
|
|
13645
|
+
stateKey: state.stableStateKey,
|
|
13027
13646
|
targetRoot: state.installRoot,
|
|
13028
13647
|
adapter: adapter.name,
|
|
13029
13648
|
artifactType,
|
|
@@ -13047,7 +13666,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
13047
13666
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
13048
13667
|
const fleet = await showRegisteredFleet(options.fleet);
|
|
13049
13668
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13050
|
-
if (
|
|
13669
|
+
if (resolve25(fleet.root) !== resolve25(toWorkspaceRoot)) {
|
|
13051
13670
|
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13052
13671
|
}
|
|
13053
13672
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
@@ -13055,19 +13674,22 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
13055
13674
|
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
13056
13675
|
}
|
|
13057
13676
|
const target = targets[0];
|
|
13058
|
-
if (target.fleetId !== options.fleet ||
|
|
13677
|
+
if (target.fleetId !== options.fleet || resolve25(target.workspaceRoot) !== resolve25(fleet.root)) {
|
|
13059
13678
|
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
13060
13679
|
}
|
|
13061
|
-
const
|
|
13062
|
-
const adapter
|
|
13063
|
-
|
|
13064
|
-
|
|
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
|
+
}
|
|
13065
13687
|
const request = {
|
|
13066
13688
|
targetRoot: state.installRoot,
|
|
13067
13689
|
adapter: adapter.name,
|
|
13068
13690
|
installationType,
|
|
13069
13691
|
sourceStateKey: options.sourceStateKey,
|
|
13070
|
-
destinationStateKey: state.
|
|
13692
|
+
destinationStateKey: state.stableStateKey,
|
|
13071
13693
|
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
13072
13694
|
toWorkspaceRoot,
|
|
13073
13695
|
toFleetId: fleet.id,
|
|
@@ -13152,13 +13774,18 @@ program.command("uninstall").description("remove configured packages or managed
|
|
|
13152
13774
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
13153
13775
|
const transport = transportForTarget(target);
|
|
13154
13776
|
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
13155
|
-
const state =
|
|
13156
|
-
const manifest =
|
|
13777
|
+
const state = await resolveCliTargetState(target, normalizedOptions);
|
|
13778
|
+
const manifest = state.manifest;
|
|
13157
13779
|
if (!manifest) {
|
|
13158
13780
|
console.log(`No install manifest for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
13159
13781
|
continue;
|
|
13160
13782
|
}
|
|
13161
|
-
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
|
+
};
|
|
13162
13789
|
console.log(formatPlan(plan));
|
|
13163
13790
|
const result = await uninstall(plan, { dryRun: normalizedOptions.dryRun, force: normalizedOptions.force, transport });
|
|
13164
13791
|
if (!normalizedOptions.dryRun) {
|
|
@@ -13620,15 +14247,12 @@ async function packageSelectsSkillForTarget(target, pkg, selector, options) {
|
|
|
13620
14247
|
if (explicitSelection && !explicitSelection.some((candidate) => candidate === selector)) return false;
|
|
13621
14248
|
const groups = /* @__PURE__ */ new Map();
|
|
13622
14249
|
const group = graphGroupForPackage(groups, target, pkg, options);
|
|
13623
|
-
const
|
|
13624
|
-
|
|
13625
|
-
group.
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
);
|
|
13630
|
-
if (await pathExists(graphLockPath)) {
|
|
13631
|
-
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;
|
|
13632
14256
|
const root = lock.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
13633
14257
|
if (root?.selected.includes(selector)) return true;
|
|
13634
14258
|
}
|
|
@@ -13931,13 +14555,30 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
13931
14555
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
13932
14556
|
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
13933
14557
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
13934
|
-
|
|
13935
|
-
group.target
|
|
13936
|
-
|
|
13937
|
-
|
|
13938
|
-
|
|
13939
|
-
|
|
13940
|
-
|
|
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;
|
|
13941
14582
|
const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
|
|
13942
14583
|
if (scopedDependencyUpdate) {
|
|
13943
14584
|
for (const pkg of group.packages) {
|
|
@@ -14047,14 +14688,14 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14047
14688
|
expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner
|
|
14048
14689
|
});
|
|
14049
14690
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
14050
|
-
const
|
|
14051
|
-
const
|
|
14052
|
-
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);
|
|
14053
14694
|
if (targetOptions.focusedArtifact && targetOptions.forceForeignState !== true) {
|
|
14054
14695
|
const focusedPaths = scopedResult.plan.operations.filter((operation) => operationMatchesFocusedArtifact(
|
|
14055
14696
|
operation,
|
|
14056
14697
|
targetOptions.focusedArtifact,
|
|
14057
|
-
focusedArtifactOwnerKeys(result.bundle.graphLock,
|
|
14698
|
+
focusedArtifactOwnerKeys(result.bundle.graphLock, previousLock, scopedRootId, targetOptions.focusedArtifact)
|
|
14058
14699
|
)).map((operation) => operation.relativeDestPath);
|
|
14059
14700
|
const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
|
|
14060
14701
|
fleetId: group.target.fleetId
|
|
@@ -14068,9 +14709,11 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
14068
14709
|
}
|
|
14069
14710
|
results.push(scopedResult);
|
|
14070
14711
|
} else if (scopedDependencyUpdate) {
|
|
14071
|
-
const
|
|
14072
|
-
const
|
|
14073
|
-
|
|
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
|
+
}
|
|
14074
14717
|
results.push(scopeUpdatePlanToDependencies(result, dependencyUpdateSelectors, previousLock, manifest));
|
|
14075
14718
|
} else {
|
|
14076
14719
|
results.push(result);
|
|
@@ -14663,8 +15306,12 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14663
15306
|
const removedInstallationType = options.installationType ?? pkg.installationType ?? removedTarget.installationType ?? "local";
|
|
14664
15307
|
const adapter = await resolveAdapterForTarget(removedTarget, removedAdapterOptions);
|
|
14665
15308
|
const transport = transportForTarget(removedTarget);
|
|
14666
|
-
const removedState =
|
|
14667
|
-
|
|
15309
|
+
const removedState = await resolveCliTargetState(removedTarget, {
|
|
15310
|
+
...options,
|
|
15311
|
+
...removedAdapterOptions,
|
|
15312
|
+
installationType: removedInstallationType
|
|
15313
|
+
});
|
|
15314
|
+
const manifest = removedState.manifest;
|
|
14668
15315
|
if (!manifest) {
|
|
14669
15316
|
console.log(`No install manifest for ${adapter.name} at ${removedTarget.targetRoot}`);
|
|
14670
15317
|
continue;
|
|
@@ -14712,25 +15359,24 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
14712
15359
|
remainingDesired = desiredArtifactsFromGraphBundle(result2.bundle);
|
|
14713
15360
|
renderedRoot = result2.bundle.root;
|
|
14714
15361
|
}
|
|
14715
|
-
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
|
+
};
|
|
14716
15372
|
console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
|
|
14717
15373
|
console.log(formatPlan(plan));
|
|
14718
15374
|
const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
|
|
14719
|
-
removeGraphLockPath:
|
|
14720
|
-
removedTarget.workspaceRoot,
|
|
14721
|
-
targetKeyForTarget(removedTarget, adapter.name),
|
|
14722
|
-
adapter.name,
|
|
14723
|
-
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions, removedInstallationType)
|
|
14724
|
-
)
|
|
15375
|
+
removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
|
|
14725
15376
|
};
|
|
14726
15377
|
if (!options.dryRun) {
|
|
14727
15378
|
declareMutationPath(workspaceConfigPath(target.workspaceRoot));
|
|
14728
|
-
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath :
|
|
14729
|
-
removedTarget.workspaceRoot,
|
|
14730
|
-
targetKeyForTarget(removedTarget, adapter.name),
|
|
14731
|
-
adapter.name,
|
|
14732
|
-
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions, removedInstallationType)
|
|
14733
|
-
);
|
|
15379
|
+
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
|
|
14734
15380
|
declareMutationPath(declarativeGraphPath);
|
|
14735
15381
|
}
|
|
14736
15382
|
const result = await uninstall(plan, {
|
|
@@ -14805,36 +15451,92 @@ function targetFingerprintParts(target, adapter, options, installationType) {
|
|
|
14805
15451
|
};
|
|
14806
15452
|
}
|
|
14807
15453
|
function installStateForTarget(target, adapter, options, installationType) {
|
|
14808
|
-
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
|
+
});
|
|
14809
15462
|
return {
|
|
14810
15463
|
installationType,
|
|
14811
15464
|
stateKey: stateKeyFor(adapter.name, {
|
|
14812
15465
|
installationType,
|
|
14813
15466
|
stateKey: target.stateKey,
|
|
14814
|
-
targetFingerprint,
|
|
15467
|
+
targetFingerprint: targetIdentity.stateFingerprint,
|
|
14815
15468
|
fleetId: target.fleetId
|
|
14816
15469
|
}),
|
|
14817
|
-
installRoot
|
|
15470
|
+
installRoot
|
|
14818
15471
|
};
|
|
14819
15472
|
}
|
|
14820
|
-
function
|
|
14821
|
-
|
|
14822
|
-
|
|
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
|
+
};
|
|
14823
15483
|
}
|
|
14824
|
-
async function
|
|
15484
|
+
async function resolveCliTargetState(target, options) {
|
|
14825
15485
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
14826
15486
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15487
|
+
const transport = transportForTarget(target);
|
|
14827
15488
|
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
14828
|
-
const
|
|
15489
|
+
const stable = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
15490
|
+
const stableGraphLockPath = graphLockPathForTarget(
|
|
14829
15491
|
target.workspaceRoot,
|
|
14830
15492
|
targetKeyForTarget(target, adapter.name),
|
|
14831
15493
|
adapter.name,
|
|
14832
|
-
targetFingerprintParts(target, adapter, adapterOptions, installationType)
|
|
15494
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
15495
|
+
targetIdentityContext(target, adapter, installationType)
|
|
14833
15496
|
);
|
|
14834
|
-
|
|
14835
|
-
|
|
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}`);
|
|
14836
15538
|
}
|
|
14837
|
-
return { adapter, path, lock:
|
|
15539
|
+
return { adapter: state.adapter, path: state.graphLockPath, lock: state.graphLock };
|
|
14838
15540
|
}
|
|
14839
15541
|
async function printStatus(target, options) {
|
|
14840
15542
|
const report = await collectTargetStatus(target, options);
|
|
@@ -14842,30 +15544,24 @@ async function printStatus(target, options) {
|
|
|
14842
15544
|
}
|
|
14843
15545
|
async function collectTargetStatus(target, options) {
|
|
14844
15546
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14845
|
-
const
|
|
14846
|
-
const adapter
|
|
14847
|
-
const
|
|
14848
|
-
const
|
|
14849
|
-
const
|
|
14850
|
-
const
|
|
14851
|
-
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
const result = await readTargetGraphLock(target, adapterOptions);
|
|
14855
|
-
graphLockPath = result.path;
|
|
14856
|
-
graphLock = result.lock;
|
|
14857
|
-
} catch {
|
|
14858
|
-
graphLock = void 0;
|
|
14859
|
-
}
|
|
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;
|
|
14860
15556
|
const packages = [];
|
|
14861
15557
|
for (const pkg of config.packages) {
|
|
14862
|
-
const root =
|
|
14863
|
-
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;
|
|
14864
15560
|
const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
|
|
14865
15561
|
forceRefresh: options.refresh,
|
|
14866
15562
|
offline: options.offline
|
|
14867
15563
|
});
|
|
14868
|
-
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;
|
|
14869
15565
|
const locked = node?.version ?? null;
|
|
14870
15566
|
const baseline = installed ?? locked;
|
|
14871
15567
|
packages.push({
|
|
@@ -14888,14 +15584,13 @@ async function collectTargetStatus(target, options) {
|
|
|
14888
15584
|
)
|
|
14889
15585
|
});
|
|
14890
15586
|
}
|
|
14891
|
-
const
|
|
14892
|
-
|
|
14893
|
-
const
|
|
14894
|
-
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) => {
|
|
14895
15590
|
if (!("graphNodeId" in entry) || entry.graphNodeId !== artifact.graphNodeId) return false;
|
|
14896
15591
|
if ("logicalSelector" in entry && entry.logicalSelector) return entry.logicalSelector === artifact.logicalSelector;
|
|
14897
15592
|
return entry.artifactType === artifact.type && entry.artifactName === artifact.name;
|
|
14898
|
-
}) ?? false;
|
|
15593
|
+
}) ?? false) || foreignObservations.some((observation) => observation.graphNodeId === artifact.graphNodeId && (!observation.logicalSelector || observation.logicalSelector === artifact.logicalSelector));
|
|
14899
15594
|
return {
|
|
14900
15595
|
selector: artifact.logicalSelector,
|
|
14901
15596
|
type: artifact.type,
|
|
@@ -14908,7 +15603,7 @@ async function collectTargetStatus(target, options) {
|
|
|
14908
15603
|
};
|
|
14909
15604
|
});
|
|
14910
15605
|
const health = [];
|
|
14911
|
-
if (!manifest || !
|
|
15606
|
+
if (!manifest && foreignObservations.length === 0 || !effectiveGraphLock) health.push("FAIL");
|
|
14912
15607
|
if (pending.error) health.push("DEGRADED");
|
|
14913
15608
|
if (pending.driftCount > 0 || pending.conflictCount > 0) health.push("FAIL");
|
|
14914
15609
|
else if (pending.pendingCount > 0) health.push("WARN");
|
|
@@ -14919,11 +15614,11 @@ async function collectTargetStatus(target, options) {
|
|
|
14919
15614
|
installationType,
|
|
14920
15615
|
targetRoot: state.installRoot,
|
|
14921
15616
|
health: worstStatusHealth(health),
|
|
14922
|
-
manifestRevision: manifest?.revision ??
|
|
14923
|
-
manifestEntryCount: manifest?.entries.length ?? 0,
|
|
15617
|
+
manifestRevision: manifest?.revision ?? observedManifestRevision,
|
|
15618
|
+
manifestEntryCount: (manifest?.entries.length ?? 0) + foreignObservations.length,
|
|
14924
15619
|
graphLockPath,
|
|
14925
15620
|
packageCount: packages.length,
|
|
14926
|
-
artifactCount:
|
|
15621
|
+
artifactCount: effectiveGraphLock?.canonical.artifacts.length ?? 0,
|
|
14927
15622
|
pendingCount: pending.pendingCount,
|
|
14928
15623
|
driftCount: pending.driftCount,
|
|
14929
15624
|
conflictCount: pending.conflictCount,
|
|
@@ -15101,7 +15796,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
15101
15796
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
15102
15797
|
);
|
|
15103
15798
|
}
|
|
15104
|
-
const memberRoot =
|
|
15799
|
+
const memberRoot = resolve25(workspaceRoot, member.workspace);
|
|
15105
15800
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
15106
15801
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
15107
15802
|
throw new Error(
|
|
@@ -15258,9 +15953,34 @@ async function journalStateForTarget(target, options) {
|
|
|
15258
15953
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
15259
15954
|
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
15260
15955
|
const transport = transportForTarget(target);
|
|
15261
|
-
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
15262
|
-
const
|
|
15263
|
-
|
|
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
|
+
};
|
|
15264
15984
|
}
|
|
15265
15985
|
async function collectPendingInstallWork(target, options) {
|
|
15266
15986
|
let results = [];
|
|
@@ -15281,11 +16001,20 @@ async function collectPendingInstallWork(target, options) {
|
|
|
15281
16001
|
pendingCount: pending.length,
|
|
15282
16002
|
driftCount: counts.drift ?? 0,
|
|
15283
16003
|
conflictCount: counts.conflict ?? 0,
|
|
15284
|
-
counts
|
|
16004
|
+
counts,
|
|
16005
|
+
foreignStateObservations: results.flatMap((result) => result.foreignStateObservations),
|
|
16006
|
+
observedGraphLock: results.length === 1 ? results[0].bundle.graphLock : void 0
|
|
15285
16007
|
};
|
|
15286
16008
|
} catch (error) {
|
|
15287
16009
|
const message = error instanceof Error ? error.message : String(error);
|
|
15288
|
-
return {
|
|
16010
|
+
return {
|
|
16011
|
+
pendingCount: 0,
|
|
16012
|
+
driftCount: 0,
|
|
16013
|
+
conflictCount: 0,
|
|
16014
|
+
counts: {},
|
|
16015
|
+
foreignStateObservations: [],
|
|
16016
|
+
error: message
|
|
16017
|
+
};
|
|
15289
16018
|
} finally {
|
|
15290
16019
|
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
15291
16020
|
}
|
|
@@ -15298,9 +16027,8 @@ async function printDoctor(target, options) {
|
|
|
15298
16027
|
if (!targetMapping?.enabled) {
|
|
15299
16028
|
throw new Error(`Adapter ${adapter.name} does not support skills for installation type '${installationType}'.`);
|
|
15300
16029
|
}
|
|
15301
|
-
const
|
|
15302
|
-
const
|
|
15303
|
-
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
16030
|
+
const state = await resolveCliTargetState(target, { ...options, installationType });
|
|
16031
|
+
const manifest = state.manifest;
|
|
15304
16032
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
15305
16033
|
const skills = [];
|
|
15306
16034
|
for (const request of requestedSkills) {
|
|
@@ -15552,14 +16280,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
15552
16280
|
}
|
|
15553
16281
|
function normalizeCliPath(value) {
|
|
15554
16282
|
if (value === "~") return homedir12();
|
|
15555
|
-
if (value.startsWith("~/")) return
|
|
15556
|
-
return
|
|
16283
|
+
if (value.startsWith("~/")) return resolve25(homedir12(), value.slice(2));
|
|
16284
|
+
return resolve25(value);
|
|
15557
16285
|
}
|
|
15558
16286
|
function shellQuoteArgument(value) {
|
|
15559
16287
|
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
15560
16288
|
}
|
|
15561
16289
|
function isHomePath(path) {
|
|
15562
|
-
return
|
|
16290
|
+
return resolve25(path) === resolve25(homedir12());
|
|
15563
16291
|
}
|
|
15564
16292
|
function adapterListFromOption(adapter) {
|
|
15565
16293
|
if (!adapter) return [];
|
|
@@ -15680,7 +16408,7 @@ function withFleetExample(config) {
|
|
|
15680
16408
|
};
|
|
15681
16409
|
}
|
|
15682
16410
|
async function findAgentwheelPackageRoot(start) {
|
|
15683
|
-
let current =
|
|
16411
|
+
let current = resolve25(start);
|
|
15684
16412
|
while (true) {
|
|
15685
16413
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
15686
16414
|
const parent = dirname22(current);
|