agentwheel 0.18.3 → 0.18.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +111 -34
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +1 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -13260,7 +13260,7 @@ function sortedUnique7(values) {
|
|
|
13260
13260
|
import { createHash as createHash15 } from "crypto";
|
|
13261
13261
|
import { readFile as readFile37, readdir as readdir9, rm as rm13 } from "fs/promises";
|
|
13262
13262
|
import { homedir as homedir13 } from "os";
|
|
13263
|
-
import { basename as basename22, join as join50, relative as relative8, resolve as resolve25 } from "path";
|
|
13263
|
+
import { basename as basename22, isAbsolute as isAbsolute4, join as join50, relative as relative8, resolve as resolve25 } from "path";
|
|
13264
13264
|
async function planFleetNormalization(request) {
|
|
13265
13265
|
const normalizedRequest = normalizeRequest(request);
|
|
13266
13266
|
const destinationScope = await resolveWorkspaceScope({
|
|
@@ -13293,10 +13293,17 @@ async function planFleetNormalization(request) {
|
|
|
13293
13293
|
packages.push({ name, declarationDigest: sha2564(sourceDeclaration) });
|
|
13294
13294
|
}
|
|
13295
13295
|
if (!legacySelfNormalization) await assertSourceFleetPostcondition(normalizedRequest, sourceScope, candidates);
|
|
13296
|
-
if ((normalizedRequest.profile || normalizedRequest.artifacts) && !legacySelfNormalization) {
|
|
13297
|
-
throw new Error("--profile
|
|
13298
|
-
}
|
|
13299
|
-
const installedState = legacySelfNormalization ? await inspectLegacySelfInstalledState(
|
|
13296
|
+
if ((normalizedRequest.profile || normalizedRequest.agent || normalizedRequest.artifacts || normalizedRequest.orphanedOwnerRoots) && !legacySelfNormalization) {
|
|
13297
|
+
throw new Error("--profile, --agent, --artifact, and --orphaned-owner are supported only for legacy same-fleet ownership normalization.");
|
|
13298
|
+
}
|
|
13299
|
+
const installedState = legacySelfNormalization ? await inspectLegacySelfInstalledState(
|
|
13300
|
+
destinationScope,
|
|
13301
|
+
candidates,
|
|
13302
|
+
normalizedRequest.profile,
|
|
13303
|
+
normalizedRequest.agent,
|
|
13304
|
+
normalizedRequest.artifacts,
|
|
13305
|
+
normalizedRequest.orphanedOwnerRoots
|
|
13306
|
+
) : await inspectInstalledState(sourceScope, destinationScope, candidates);
|
|
13300
13307
|
const planWithoutDigest = {
|
|
13301
13308
|
version: 1,
|
|
13302
13309
|
request: normalizedRequest,
|
|
@@ -13464,15 +13471,29 @@ function normalizeRequest(request) {
|
|
|
13464
13471
|
if (request.artifacts !== void 0 && artifacts?.length === 0) throw new Error("--artifact requires at least one type/name selector.");
|
|
13465
13472
|
const profile = request.profile?.trim();
|
|
13466
13473
|
if (request.profile !== void 0 && !profile) throw new Error("--profile requires a profile name.");
|
|
13474
|
+
const agent = request.agent?.trim();
|
|
13475
|
+
if (request.agent !== void 0 && !agent) throw new Error("--agent requires a configured agent name.");
|
|
13476
|
+
if (profile && agent) throw new Error("--profile and --agent cannot be combined.");
|
|
13477
|
+
const orphanedOwnerRoots = request.orphanedOwnerRoots === void 0 ? void 0 : sortedUnique8(request.orphanedOwnerRoots.map((root) => normalizeOrphanedOwnerRoot(root)));
|
|
13478
|
+
if (request.orphanedOwnerRoots !== void 0 && orphanedOwnerRoots?.length === 0) {
|
|
13479
|
+
throw new Error("--orphaned-owner requires at least one absolute workspace root.");
|
|
13480
|
+
}
|
|
13467
13481
|
return {
|
|
13468
13482
|
destinationFleet: request.destinationFleet.trim(),
|
|
13469
13483
|
from: request.from,
|
|
13470
13484
|
...packages ? { packages } : {},
|
|
13471
13485
|
...artifacts ? { artifacts } : {},
|
|
13472
13486
|
...profile ? { profile } : {},
|
|
13487
|
+
...agent ? { agent } : {},
|
|
13488
|
+
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
13473
13489
|
...request.globalRoot ? { globalRoot: resolve25(request.globalRoot) } : {}
|
|
13474
13490
|
};
|
|
13475
13491
|
}
|
|
13492
|
+
function normalizeOrphanedOwnerRoot(value) {
|
|
13493
|
+
const root = value.trim();
|
|
13494
|
+
if (!root || !isAbsolute4(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
13495
|
+
return resolve25(root);
|
|
13496
|
+
}
|
|
13476
13497
|
async function normalizationRoots(request) {
|
|
13477
13498
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
13478
13499
|
const source = request.from === "user" ? resolve25(request.globalRoot ?? homedir13()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
@@ -13519,6 +13540,7 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
13519
13540
|
sourceManifestCount: 0,
|
|
13520
13541
|
destinationManifestCount: 0,
|
|
13521
13542
|
renderedPathCount: 0,
|
|
13543
|
+
orphanedUnmanagedPaths: [],
|
|
13522
13544
|
sourceGraphLockPaths: [],
|
|
13523
13545
|
graphTransfers: [],
|
|
13524
13546
|
transfers: []
|
|
@@ -13616,7 +13638,8 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
13616
13638
|
installationType: destinationState.installationType,
|
|
13617
13639
|
adapter: destinationState.adapter,
|
|
13618
13640
|
renderedPaths: sortedUnique8(sourceRenderedPaths),
|
|
13619
|
-
destinationRenderedPaths: sortedUnique8(destinationRenderedPaths)
|
|
13641
|
+
destinationRenderedPaths: sortedUnique8(destinationRenderedPaths),
|
|
13642
|
+
unmanagedSourceRenderedPaths: []
|
|
13620
13643
|
});
|
|
13621
13644
|
}
|
|
13622
13645
|
if (coveredSourceEntries.size !== sourceEntries.length) {
|
|
@@ -13648,18 +13671,19 @@ async function inspectInstalledState(source, destination, packageNames) {
|
|
|
13648
13671
|
sourceManifestCount: new Set(sourceEntries.map((entry) => entry.manifest.path)).size,
|
|
13649
13672
|
destinationManifestCount: new Set(destinationEntries.map((entry) => entry.manifest.path)).size,
|
|
13650
13673
|
renderedPathCount: plannedDestinationPaths.size,
|
|
13674
|
+
orphanedUnmanagedPaths: [],
|
|
13651
13675
|
sourceGraphLockPaths: sourceGraphs.map((graph) => graph.path).sort((a, b) => a.localeCompare(b)),
|
|
13652
13676
|
graphTransfers: graphTransfers.sort((a, b) => a.sourceGraphLockPath.localeCompare(b.sourceGraphLockPath)),
|
|
13653
13677
|
transfers: [...transfers.values()].sort((a, b) => `${a.sourceManifestPath}:${a.destinationManifestPath}`.localeCompare(`${b.sourceManifestPath}:${b.destinationManifestPath}`))
|
|
13654
13678
|
};
|
|
13655
13679
|
}
|
|
13656
|
-
async function inspectLegacySelfInstalledState(fleet, packageNames, profileName, artifactNames) {
|
|
13680
|
+
async function inspectLegacySelfInstalledState(fleet, packageNames, profileName, agentName, artifactNames, orphanedOwnerRoots) {
|
|
13657
13681
|
if (fleet.kind !== "fleet" || !fleet.fleetId) {
|
|
13658
13682
|
throw new Error("Legacy self-normalization requires a registered named fleet destination.");
|
|
13659
13683
|
}
|
|
13660
13684
|
const selected = new Set(packageNames);
|
|
13661
13685
|
const selectedArtifacts = artifactNames ? new Set(artifactNames) : void 0;
|
|
13662
|
-
const targetKeys = profileName ? targetKeysForProfile(fleet, profileName) : void 0;
|
|
13686
|
+
const targetKeys = agentName ? targetKeysForAgent(fleet, agentName) : profileName ? targetKeysForProfile(fleet, profileName) : void 0;
|
|
13663
13687
|
const graphCandidates = await relevantGraphLocks(
|
|
13664
13688
|
fleet.root,
|
|
13665
13689
|
selected,
|
|
@@ -13669,6 +13693,9 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
13669
13693
|
const graphs = [];
|
|
13670
13694
|
const legacyOwner = workspaceOwnerForRoot(fleet.root);
|
|
13671
13695
|
const fleetOwner = workspaceOwnerForRoot(fleet.root, fleet.fleetId);
|
|
13696
|
+
const orphanedOwners = await orphanedWorkspaceOwners(orphanedOwnerRoots);
|
|
13697
|
+
const knownLegacyOwners = /* @__PURE__ */ new Set([legacyOwner, ...orphanedOwners]);
|
|
13698
|
+
const transferableOwners = orphanedOwners.size > 0 ? orphanedOwners : /* @__PURE__ */ new Set([legacyOwner]);
|
|
13672
13699
|
let normalizedGraphCount = 0;
|
|
13673
13700
|
for (const graph of graphCandidates) {
|
|
13674
13701
|
const legacyState = await legacyTargetStateForGraph(fleet, graph);
|
|
@@ -13692,14 +13719,19 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
13692
13719
|
}
|
|
13693
13720
|
const manifests = await collectManifestPaths([...manifestPaths]);
|
|
13694
13721
|
const relevantEntries = manifests.flatMap((manifest) => manifest.manifest.entries.filter((entry) => graphs.some((graph) => entryMatchesGraphPackages(entry, graph.lock, selected)) && entryMatchesArtifacts(entry, selectedArtifacts)).map((entry) => ({ manifest, entry, renderedPath: renderedEntryPath(manifest.manifest, entry) })));
|
|
13695
|
-
const legacyEntries = relevantEntries.filter((entry) => entry.entry.workspaceOwner
|
|
13722
|
+
const legacyEntries = relevantEntries.filter((entry) => transferableOwners.has(entry.entry.workspaceOwner));
|
|
13696
13723
|
const qualifiedEntries = relevantEntries.filter((entry) => entry.entry.workspaceOwner === fleetOwner);
|
|
13697
|
-
const foreignEntries = relevantEntries.filter((entry) => entry.entry.workspaceOwner
|
|
13724
|
+
const foreignEntries = relevantEntries.filter((entry) => !knownLegacyOwners.has(entry.entry.workspaceOwner) && entry.entry.workspaceOwner !== fleetOwner);
|
|
13698
13725
|
if (foreignEntries.length > 0) {
|
|
13699
13726
|
throw new Error(
|
|
13700
13727
|
`Legacy self-normalization owner mismatch at ${foreignEntries[0].renderedPath}: expected ${legacyOwner}, found foreign owner ${foreignEntries[0].entry.workspaceOwner}.`
|
|
13701
13728
|
);
|
|
13702
13729
|
}
|
|
13730
|
+
for (const orphanedOwner of orphanedOwners) {
|
|
13731
|
+
if (!legacyEntries.some((entry) => entry.entry.workspaceOwner === orphanedOwner)) {
|
|
13732
|
+
throw new Error(`Accepted orphaned owner '${orphanedOwner}' has no matching installed-state entries for this normalization.`);
|
|
13733
|
+
}
|
|
13734
|
+
}
|
|
13703
13735
|
if (legacyEntries.length === 0) {
|
|
13704
13736
|
if (qualifiedEntries.length > 0) {
|
|
13705
13737
|
throw new Error(`Fleet '${fleet.fleetId}' installed state is already normalized to fleet-qualified ownership.`);
|
|
@@ -13718,6 +13750,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
13718
13750
|
const graphTransfers = [];
|
|
13719
13751
|
const coveredEntries = /* @__PURE__ */ new Set();
|
|
13720
13752
|
const renderedPaths = /* @__PURE__ */ new Set();
|
|
13753
|
+
const orphanedUnmanagedPaths = /* @__PURE__ */ new Set();
|
|
13721
13754
|
for (const graph of graphs) {
|
|
13722
13755
|
const legacyState = await legacyTargetStateForGraph(fleet, graph);
|
|
13723
13756
|
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
|
|
@@ -13731,35 +13764,48 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
13731
13764
|
const graphArtifacts = new Set(graph.artifactIdentities);
|
|
13732
13765
|
const existingManifest = manifests.find((manifest) => manifest.path === destinationState.manifestPath);
|
|
13733
13766
|
const desiredEntries = [];
|
|
13767
|
+
const sourceRenderedPaths = [];
|
|
13768
|
+
const destinationRenderedPaths = [];
|
|
13734
13769
|
for (const entry of expectedEntries) {
|
|
13735
13770
|
coveredEntries.add(entry);
|
|
13736
13771
|
assertSimpleVerifiableEntry(entry.entry, entry.renderedPath);
|
|
13737
|
-
|
|
13772
|
+
const coveredByCurrentGraph = graphArtifacts.has(graphEntryIdentity(entry.entry));
|
|
13773
|
+
const matchesCurrentGraph = orphanedOwners.has(entry.entry.workspaceOwner) && orphanedEntryMatchesCurrentGraph(entry, graph, selected);
|
|
13774
|
+
if (!coveredByCurrentGraph && !matchesCurrentGraph && !orphanedOwners.has(entry.entry.workspaceOwner)) {
|
|
13738
13775
|
throw new Error(`Legacy manifest entry is not covered by its graph lock: ${entry.renderedPath}`);
|
|
13739
13776
|
}
|
|
13740
13777
|
await assertEquivalentRuntimeBytes(entry.renderedPath, entry.renderedPath, entry.entry.hash);
|
|
13741
13778
|
renderedPaths.add(entry.renderedPath);
|
|
13742
|
-
|
|
13779
|
+
sourceRenderedPaths.push(entry.renderedPath);
|
|
13780
|
+
if (coveredByCurrentGraph || matchesCurrentGraph) {
|
|
13781
|
+
desiredEntries.push({ ...entry.entry, workspaceOwner: fleetOwner });
|
|
13782
|
+
destinationRenderedPaths.push(entry.renderedPath);
|
|
13783
|
+
} else {
|
|
13784
|
+
orphanedUnmanagedPaths.add(entry.renderedPath);
|
|
13785
|
+
}
|
|
13743
13786
|
}
|
|
13744
|
-
const after = mergeDestinationManifest(
|
|
13787
|
+
const after = desiredEntries.length > 0 || existingManifest ? mergeDestinationManifest(
|
|
13745
13788
|
existingManifest,
|
|
13746
13789
|
destinationState,
|
|
13747
13790
|
expectedEntries[0].manifest.manifest.generatedAt,
|
|
13748
13791
|
desiredEntries,
|
|
13749
|
-
|
|
13750
|
-
);
|
|
13792
|
+
transferableOwners
|
|
13793
|
+
) : void 0;
|
|
13751
13794
|
transfers.set(legacyState.manifestPath, {
|
|
13752
13795
|
sourceManifestPath: legacyState.manifestPath,
|
|
13753
13796
|
sourceManifestRevision: expectedEntries[0].manifest.manifest.revision,
|
|
13754
13797
|
destinationManifestPath: destinationState.manifestPath,
|
|
13755
13798
|
destinationManifestRevision: existingManifest?.manifest.revision ?? null,
|
|
13756
|
-
destinationManifestAfterRevision: computeManifestRevision(after),
|
|
13799
|
+
destinationManifestAfterRevision: after ? computeManifestRevision(after) : null,
|
|
13757
13800
|
destinationTargetRoot: destinationState.installRoot,
|
|
13758
13801
|
destinationStateKey: destinationState.stateKey,
|
|
13759
13802
|
installationType: destinationState.installationType,
|
|
13760
13803
|
adapter: destinationState.adapter,
|
|
13761
|
-
renderedPaths: sortedUnique8(
|
|
13762
|
-
destinationRenderedPaths: sortedUnique8(
|
|
13804
|
+
renderedPaths: sortedUnique8(sourceRenderedPaths),
|
|
13805
|
+
destinationRenderedPaths: sortedUnique8(destinationRenderedPaths),
|
|
13806
|
+
unmanagedSourceRenderedPaths: sortedUnique8(
|
|
13807
|
+
sourceRenderedPaths.filter((path) => !destinationRenderedPaths.includes(path))
|
|
13808
|
+
)
|
|
13763
13809
|
});
|
|
13764
13810
|
if (!selectedArtifacts) {
|
|
13765
13811
|
const fullDigest = sha2564(canonicalJson2(graph.lock));
|
|
@@ -13786,11 +13832,22 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
13786
13832
|
sourceManifestCount: new Set(legacyEntries.map((entry) => entry.manifest.path)).size,
|
|
13787
13833
|
destinationManifestCount: new Set(qualifiedEntries.map((entry) => entry.manifest.path)).size,
|
|
13788
13834
|
renderedPathCount: renderedPaths.size,
|
|
13835
|
+
orphanedUnmanagedPaths: [...orphanedUnmanagedPaths].sort((a, b) => a.localeCompare(b)),
|
|
13789
13836
|
sourceGraphLockPaths: graphs.map((graph) => graph.path).sort((a, b) => a.localeCompare(b)),
|
|
13790
13837
|
graphTransfers: graphTransfers.sort((a, b) => a.sourceGraphLockPath.localeCompare(b.sourceGraphLockPath)),
|
|
13791
13838
|
transfers: [...transfers.values()].sort((a, b) => a.sourceManifestPath.localeCompare(b.sourceManifestPath))
|
|
13792
13839
|
};
|
|
13793
13840
|
}
|
|
13841
|
+
async function orphanedWorkspaceOwners(roots) {
|
|
13842
|
+
const owners = /* @__PURE__ */ new Set();
|
|
13843
|
+
for (const root of roots ?? []) {
|
|
13844
|
+
if (await pathExists(root)) {
|
|
13845
|
+
throw new Error(`Orphaned owner root still exists and cannot be adopted: ${root}. Recover or normalize it from that workspace instead.`);
|
|
13846
|
+
}
|
|
13847
|
+
owners.add(workspaceOwnerForRoot(root));
|
|
13848
|
+
}
|
|
13849
|
+
return owners;
|
|
13850
|
+
}
|
|
13794
13851
|
function targetKeysForProfile(scope, name) {
|
|
13795
13852
|
const profile = scope.config.profiles[name];
|
|
13796
13853
|
if (!profile) throw new Error(`Unknown fleet profile '${name}'.`);
|
|
@@ -13799,6 +13856,14 @@ function targetKeysForProfile(scope, name) {
|
|
|
13799
13856
|
}
|
|
13800
13857
|
return new Set(profile.runtimes.map((runtime) => runtime.agent ?? runtime.adapter));
|
|
13801
13858
|
}
|
|
13859
|
+
function targetKeysForAgent(scope, name) {
|
|
13860
|
+
const agent = scope.config.agents[name];
|
|
13861
|
+
if (!agent) throw new Error(`Unknown fleet agent '${name}'.`);
|
|
13862
|
+
if (agent.transport === "ssh") {
|
|
13863
|
+
throw new Error(`Installed-state normalization cannot hand off SSH agent '${name}' locally.`);
|
|
13864
|
+
}
|
|
13865
|
+
return /* @__PURE__ */ new Set([name]);
|
|
13866
|
+
}
|
|
13802
13867
|
async function runtimeRoots(scope) {
|
|
13803
13868
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
13804
13869
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
@@ -14277,6 +14342,11 @@ function graphEntryIdentity(entry) {
|
|
|
14277
14342
|
channel: entry.channel
|
|
14278
14343
|
});
|
|
14279
14344
|
}
|
|
14345
|
+
function orphanedEntryMatchesCurrentGraph(entry, graph, selected) {
|
|
14346
|
+
return graph.lock.canonical.artifacts.some(
|
|
14347
|
+
(artifact) => artifact.owners.some((owner) => ownerMatchesGraphPackage(graph.lock, owner, selected)) && artifact.type === entry.entry.artifactType && artifact.name === entry.entry.artifactName && artifact.installName === entry.entry.installName && artifact.kind === entry.entry.kind
|
|
14348
|
+
);
|
|
14349
|
+
}
|
|
14280
14350
|
async function readExactConfig(path) {
|
|
14281
14351
|
return workspaceConfigSchema.parse(JSON.parse(await readFile37(path, "utf8")));
|
|
14282
14352
|
}
|
|
@@ -14294,9 +14364,7 @@ async function buildJournalManifestStates(plan, selected) {
|
|
|
14294
14364
|
const parsed = installManifestSchema.parse(before);
|
|
14295
14365
|
if (parsed.version !== 2) throw new Error(`Manifest changed to unsupported v1 state: ${path}`);
|
|
14296
14366
|
sourceByPath.set(path, { raw: before, manifest: { ...parsed, revision: computeManifestRevision(before), legacy: false } });
|
|
14297
|
-
const entries = parsed.entries.filter(
|
|
14298
|
-
(entry) => !(entryMatchesPackages(entry, selected) && renderedPaths.has(renderedEntryPath(parsed, entry)))
|
|
14299
|
-
);
|
|
14367
|
+
const entries = parsed.entries.filter((entry) => !renderedPaths.has(renderedEntryPath(parsed, entry)));
|
|
14300
14368
|
const removeEmptyLegacyManifest = isLegacySelfNormalizationPlan(plan) && entries.length === 0;
|
|
14301
14369
|
const afterManifest = removeEmptyLegacyManifest ? void 0 : withManifestRevision({ ...parsed, revision: computeManifestRevision(before), legacy: false, entries });
|
|
14302
14370
|
const after = afterManifest ? writableManifest(afterManifest) : null;
|
|
@@ -14314,7 +14382,7 @@ async function buildJournalManifestStates(plan, selected) {
|
|
|
14314
14382
|
if (source.manifest.revision !== transfer.sourceManifestRevision) {
|
|
14315
14383
|
throw new Error(`Source manifest changed after planning: ${transfer.sourceManifestPath}`);
|
|
14316
14384
|
}
|
|
14317
|
-
const desiredEntries = source.manifest.entries.filter((entry) =>
|
|
14385
|
+
const desiredEntries = source.manifest.entries.filter((entry) => transfer.renderedPaths.includes(renderedEntryPath(source.manifest, entry)) && !transfer.unmanagedSourceRenderedPaths.includes(renderedEntryPath(source.manifest, entry))).map((entry) => ({ ...entry, workspaceOwner: workspaceOwnerForRoot(plan.destination.root, plan.destination.fleetId) }));
|
|
14318
14386
|
const before = await readOptionalRecord(transfer.destinationManifestPath);
|
|
14319
14387
|
const parsedBefore = before ? installManifestSchema.parse(before) : void 0;
|
|
14320
14388
|
if (parsedBefore && parsedBefore.version !== 2) {
|
|
@@ -14335,25 +14403,27 @@ async function buildJournalManifestStates(plan, selected) {
|
|
|
14335
14403
|
graphLockPath: "journaled",
|
|
14336
14404
|
manifestPath: transfer.destinationManifestPath
|
|
14337
14405
|
};
|
|
14338
|
-
const after = mergeDestinationManifest(
|
|
14406
|
+
const after = desiredEntries.length > 0 || existing ? mergeDestinationManifest(
|
|
14339
14407
|
existing,
|
|
14340
14408
|
state,
|
|
14341
14409
|
source.manifest.generatedAt,
|
|
14342
14410
|
desiredEntries,
|
|
14343
14411
|
workspaceOwnersForPlanSource(plan)
|
|
14344
|
-
);
|
|
14412
|
+
) : void 0;
|
|
14345
14413
|
const beforeRevision = before ? computeManifestRevision(before) : null;
|
|
14346
|
-
const afterRevision = computeManifestRevision(after);
|
|
14414
|
+
const afterRevision = after ? computeManifestRevision(after) : null;
|
|
14347
14415
|
if (beforeRevision !== transfer.destinationManifestRevision || afterRevision !== transfer.destinationManifestAfterRevision) {
|
|
14348
|
-
throw new Error(
|
|
14416
|
+
throw new Error(
|
|
14417
|
+
`Destination manifest changed after planning: ${transfer.destinationManifestPath} (before ${beforeRevision ?? "absent"}, expected ${transfer.destinationManifestRevision ?? "absent"}; after ${afterRevision ?? "absent"}, expected ${transfer.destinationManifestAfterRevision ?? "absent"}).`
|
|
14418
|
+
);
|
|
14349
14419
|
}
|
|
14350
14420
|
if (transfer.sourceManifestPath === transfer.destinationManifestPath) {
|
|
14351
14421
|
const sourceState = states.find((candidate) => candidate.path === transfer.sourceManifestPath);
|
|
14352
14422
|
if (!sourceState) throw new Error(`Source manifest journal state is missing: ${transfer.sourceManifestPath}`);
|
|
14353
|
-
sourceState.after = after;
|
|
14423
|
+
sourceState.after = after ?? null;
|
|
14354
14424
|
sourceState.afterRevision = afterRevision;
|
|
14355
14425
|
} else {
|
|
14356
|
-
states.push({ path: transfer.destinationManifestPath, before, after, beforeRevision, afterRevision });
|
|
14426
|
+
if (after || before) states.push({ path: transfer.destinationManifestPath, before, after: after ?? null, beforeRevision, afterRevision });
|
|
14357
14427
|
}
|
|
14358
14428
|
}
|
|
14359
14429
|
return states;
|
|
@@ -14495,7 +14565,8 @@ async function assertJournalRecoverable(journal) {
|
|
|
14495
14565
|
function workspaceOwnersForPlanSource(plan) {
|
|
14496
14566
|
return /* @__PURE__ */ new Set([
|
|
14497
14567
|
workspaceOwnerForRoot(plan.source.root),
|
|
14498
|
-
...plan.source.fleetId ? [workspaceOwnerForRoot(plan.source.root, plan.source.fleetId)] : []
|
|
14568
|
+
...plan.source.fleetId ? [workspaceOwnerForRoot(plan.source.root, plan.source.fleetId)] : [],
|
|
14569
|
+
...(plan.request.orphanedOwnerRoots ?? []).map((root) => workspaceOwnerForRoot(root))
|
|
14499
14570
|
]);
|
|
14500
14571
|
}
|
|
14501
14572
|
async function readOptionalRecord(path) {
|
|
@@ -14611,16 +14682,18 @@ fleetCommand.command("show").description("show one registered fleet").argument("
|
|
|
14611
14682
|
Root: ${fleet.root}
|
|
14612
14683
|
Required packages: ${fleet.requiredPackages.join(", ")}`);
|
|
14613
14684
|
});
|
|
14614
|
-
fleetCommand.command("normalize").description("plan or apply duplicate desired-state ownership normalization").argument("<destinationFleet>", "destination fleet id").requiredOption("--from <scope>", "user or fleet:<sourceFleet>").option("--package <name>", "limit to one duplicate package (repeatable)", collectValueOption, []).option("--artifact <type/name>", "limit same-fleet ownership normalization to one artifact (repeatable)", collectValueOption, []).option("--profile <name>", "limit same-fleet installed-state normalization to one concrete profile").option("--apply", "apply a reviewed plan", false).option("--recover", "restore source state from a pending normalization journal", false).option("--plan-digest <sha256>", "exact digest from the reviewed dry-run").option("--json", "print the plan or result as JSON", false).action(async (destinationFleet, options) => {
|
|
14685
|
+
fleetCommand.command("normalize").description("plan or apply duplicate desired-state ownership normalization").argument("<destinationFleet>", "destination fleet id").requiredOption("--from <scope>", "user or fleet:<sourceFleet>").option("--package <name>", "limit to one duplicate package (repeatable)", collectValueOption, []).option("--artifact <type/name>", "limit same-fleet ownership normalization to one artifact (repeatable)", collectValueOption, []).option("--profile <name>", "limit same-fleet installed-state normalization to one concrete profile").option("--agent <name>", "limit same-fleet ownership normalization to one configured local agent").option("--orphaned-owner <workspace-root>", "explicit missing workspace root whose verified runtime ownership may be recovered (repeatable)", collectValueOption, []).option("--apply", "apply a reviewed plan", false).option("--recover", "restore source state from a pending normalization journal", false).option("--plan-digest <sha256>", "exact digest from the reviewed dry-run").option("--json", "print the plan or result as JSON", false).action(async (destinationFleet, options) => {
|
|
14615
14686
|
const request = {
|
|
14616
14687
|
destinationFleet,
|
|
14617
14688
|
from: options.from,
|
|
14618
14689
|
...options.package.length > 0 ? { packages: options.package } : {},
|
|
14619
14690
|
...options.artifact.length > 0 ? { artifacts: options.artifact } : {},
|
|
14620
|
-
...options.profile ? { profile: options.profile } : {}
|
|
14691
|
+
...options.profile ? { profile: options.profile } : {},
|
|
14692
|
+
...options.agent ? { agent: options.agent } : {},
|
|
14693
|
+
...options.orphanedOwner.length > 0 ? { orphanedOwnerRoots: options.orphanedOwner } : {}
|
|
14621
14694
|
};
|
|
14622
|
-
if (options.recover && (options.apply || options.planDigest || options.package.length > 0 || options.artifact.length > 0 || options.profile)) {
|
|
14623
|
-
throw new Error("--recover cannot be combined with --apply, --plan-digest, --package, --artifact, or --
|
|
14695
|
+
if (options.recover && (options.apply || options.planDigest || options.package.length > 0 || options.artifact.length > 0 || options.profile || options.agent || options.orphanedOwner.length > 0)) {
|
|
14696
|
+
throw new Error("--recover cannot be combined with --apply, --plan-digest, --package, --artifact, --profile, --agent, or --orphaned-owner.");
|
|
14624
14697
|
}
|
|
14625
14698
|
const result = options.recover ? await recoverFleetNormalization(request) : options.apply ? await applyFleetNormalization({ ...request, apply: true, planDigest: options.planDigest }) : await planFleetNormalization(request);
|
|
14626
14699
|
console.log(options.json ? JSON.stringify(result, null, 2) : formatFleetNormalization(result));
|
|
@@ -17264,11 +17337,15 @@ function formatFleetNormalization(result) {
|
|
|
17264
17337
|
const packageArgs = result.packages.map((pkg) => `--package ${shellQuoteArg2(pkg.name)}`).join(" ");
|
|
17265
17338
|
const artifactArgs = result.request.artifacts?.map((artifact) => ` --artifact ${shellQuoteArg2(artifact)}`).join("") ?? "";
|
|
17266
17339
|
const profileArg = result.request.profile ? ` --profile ${shellQuoteArg2(result.request.profile)}` : "";
|
|
17340
|
+
const agentArg = result.request.agent ? ` --agent ${shellQuoteArg2(result.request.agent)}` : "";
|
|
17341
|
+
const orphanedOwnerArgs = result.request.orphanedOwnerRoots?.map((root) => ` --orphaned-owner ${shellQuoteArg2(root)}`).join("") ?? "";
|
|
17342
|
+
const orphanedUnmanaged = result.installedState.orphanedUnmanagedPaths;
|
|
17267
17343
|
return [
|
|
17268
17344
|
`Fleet normalization plan: ${result.source.root} -> ${result.destination.root}`,
|
|
17269
17345
|
`Packages: ${result.packages.map((pkg) => pkg.name).join(", ")}`,
|
|
17346
|
+
...orphanedUnmanaged.length > 0 ? [`Retired orphan ownership (files retained unmanaged): ${orphanedUnmanaged.join(", ")}`] : [],
|
|
17270
17347
|
`Plan digest: ${result.planDigest}`,
|
|
17271
|
-
`Apply: agentwheel fleet normalize ${result.destination.fleetId} --from ${result.request.from} ${packageArgs}${artifactArgs}${profileArg} --apply --plan-digest ${result.planDigest}`
|
|
17348
|
+
`Apply: agentwheel fleet normalize ${result.destination.fleetId} --from ${result.request.from} ${packageArgs}${artifactArgs}${profileArg}${agentArg}${orphanedOwnerArgs} --apply --plan-digest ${result.planDigest}`
|
|
17272
17349
|
].join("\n");
|
|
17273
17350
|
}
|
|
17274
17351
|
function shouldDefaultUserInstall(nameOrSource, options) {
|
package/openpack.json
CHANGED
package/package.json
CHANGED