agentwheel 0.18.2 → 0.18.4
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 +133 -32
- 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")) {
|
|
@@ -13918,10 +13983,34 @@ function isCurrentLocalTarget(scope, graph) {
|
|
|
13918
13983
|
}
|
|
13919
13984
|
async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
13920
13985
|
if (!isCurrentLocalTarget(scope, graph)) return false;
|
|
13921
|
-
const
|
|
13922
|
-
const manifest = (await collectManifestPaths([
|
|
13986
|
+
const manifestPath = await legacyManifestPathForConfiguredTarget(scope, graph) ?? (await legacyTargetStateForGraph(scope, graph)).manifestPath;
|
|
13987
|
+
const manifest = (await collectManifestPaths([manifestPath]))[0];
|
|
13923
13988
|
return manifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
|
|
13924
13989
|
}
|
|
13990
|
+
async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
13991
|
+
const agent = scope.config.agents[graph.targetKey];
|
|
13992
|
+
if (!agent || !agent.installationType) return void 0;
|
|
13993
|
+
if (agent.transport === "ssh") {
|
|
13994
|
+
throw new Error(`Installed-state normalization cannot hand off SSH target '${graph.targetKey}' locally.`);
|
|
13995
|
+
}
|
|
13996
|
+
if (agent.adapter !== graph.adapter) {
|
|
13997
|
+
throw new Error(`Graph adapter '${graph.adapter}' does not match configured target adapter '${agent.adapter}'.`);
|
|
13998
|
+
}
|
|
13999
|
+
const adapter = await resolveAdapter({
|
|
14000
|
+
adapter: agent.adapter,
|
|
14001
|
+
adapterConfig: agent.adapterConfig,
|
|
14002
|
+
adapterModule: agent.adapterModule,
|
|
14003
|
+
allowAdapterCode: false,
|
|
14004
|
+
baseDir: scope.root
|
|
14005
|
+
});
|
|
14006
|
+
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
14007
|
+
const installRoot = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
14008
|
+
const stateKey = stateKeyFor(adapter.name, {
|
|
14009
|
+
installationType: agent.installationType,
|
|
14010
|
+
targetFingerprint: graph.lock.canonical.targetFingerprint
|
|
14011
|
+
});
|
|
14012
|
+
return installManifestPath(installRoot, adapter.name, { installationType: agent.installationType, stateKey });
|
|
14013
|
+
}
|
|
13925
14014
|
async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null) {
|
|
13926
14015
|
const agent = scope.config.agents[graph.targetKey];
|
|
13927
14016
|
if (agent?.transport === "ssh") {
|
|
@@ -14253,6 +14342,11 @@ function graphEntryIdentity(entry) {
|
|
|
14253
14342
|
channel: entry.channel
|
|
14254
14343
|
});
|
|
14255
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
|
+
}
|
|
14256
14350
|
async function readExactConfig(path) {
|
|
14257
14351
|
return workspaceConfigSchema.parse(JSON.parse(await readFile37(path, "utf8")));
|
|
14258
14352
|
}
|
|
@@ -14290,7 +14384,7 @@ async function buildJournalManifestStates(plan, selected) {
|
|
|
14290
14384
|
if (source.manifest.revision !== transfer.sourceManifestRevision) {
|
|
14291
14385
|
throw new Error(`Source manifest changed after planning: ${transfer.sourceManifestPath}`);
|
|
14292
14386
|
}
|
|
14293
|
-
const desiredEntries = source.manifest.entries.filter((entry) => entryMatchesPackages(entry, selected) && transfer.renderedPaths.includes(renderedEntryPath(source.manifest, entry))).map((entry) => ({ ...entry, workspaceOwner: workspaceOwnerForRoot(plan.destination.root, plan.destination.fleetId) }));
|
|
14387
|
+
const desiredEntries = source.manifest.entries.filter((entry) => entryMatchesPackages(entry, selected) && 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) }));
|
|
14294
14388
|
const before = await readOptionalRecord(transfer.destinationManifestPath);
|
|
14295
14389
|
const parsedBefore = before ? installManifestSchema.parse(before) : void 0;
|
|
14296
14390
|
if (parsedBefore && parsedBefore.version !== 2) {
|
|
@@ -14311,25 +14405,25 @@ async function buildJournalManifestStates(plan, selected) {
|
|
|
14311
14405
|
graphLockPath: "journaled",
|
|
14312
14406
|
manifestPath: transfer.destinationManifestPath
|
|
14313
14407
|
};
|
|
14314
|
-
const after = mergeDestinationManifest(
|
|
14408
|
+
const after = desiredEntries.length > 0 || existing ? mergeDestinationManifest(
|
|
14315
14409
|
existing,
|
|
14316
14410
|
state,
|
|
14317
14411
|
source.manifest.generatedAt,
|
|
14318
14412
|
desiredEntries,
|
|
14319
14413
|
workspaceOwnersForPlanSource(plan)
|
|
14320
|
-
);
|
|
14414
|
+
) : void 0;
|
|
14321
14415
|
const beforeRevision = before ? computeManifestRevision(before) : null;
|
|
14322
|
-
const afterRevision = computeManifestRevision(after);
|
|
14416
|
+
const afterRevision = after ? computeManifestRevision(after) : null;
|
|
14323
14417
|
if (beforeRevision !== transfer.destinationManifestRevision || afterRevision !== transfer.destinationManifestAfterRevision) {
|
|
14324
14418
|
throw new Error(`Destination manifest changed after planning: ${transfer.destinationManifestPath}`);
|
|
14325
14419
|
}
|
|
14326
14420
|
if (transfer.sourceManifestPath === transfer.destinationManifestPath) {
|
|
14327
14421
|
const sourceState = states.find((candidate) => candidate.path === transfer.sourceManifestPath);
|
|
14328
14422
|
if (!sourceState) throw new Error(`Source manifest journal state is missing: ${transfer.sourceManifestPath}`);
|
|
14329
|
-
sourceState.after = after;
|
|
14423
|
+
sourceState.after = after ?? null;
|
|
14330
14424
|
sourceState.afterRevision = afterRevision;
|
|
14331
14425
|
} else {
|
|
14332
|
-
states.push({ path: transfer.destinationManifestPath, before, after, beforeRevision, afterRevision });
|
|
14426
|
+
if (after || before) states.push({ path: transfer.destinationManifestPath, before, after: after ?? null, beforeRevision, afterRevision });
|
|
14333
14427
|
}
|
|
14334
14428
|
}
|
|
14335
14429
|
return states;
|
|
@@ -14471,7 +14565,8 @@ async function assertJournalRecoverable(journal) {
|
|
|
14471
14565
|
function workspaceOwnersForPlanSource(plan) {
|
|
14472
14566
|
return /* @__PURE__ */ new Set([
|
|
14473
14567
|
workspaceOwnerForRoot(plan.source.root),
|
|
14474
|
-
...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))
|
|
14475
14570
|
]);
|
|
14476
14571
|
}
|
|
14477
14572
|
async function readOptionalRecord(path) {
|
|
@@ -14587,16 +14682,18 @@ fleetCommand.command("show").description("show one registered fleet").argument("
|
|
|
14587
14682
|
Root: ${fleet.root}
|
|
14588
14683
|
Required packages: ${fleet.requiredPackages.join(", ")}`);
|
|
14589
14684
|
});
|
|
14590
|
-
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) => {
|
|
14591
14686
|
const request = {
|
|
14592
14687
|
destinationFleet,
|
|
14593
14688
|
from: options.from,
|
|
14594
14689
|
...options.package.length > 0 ? { packages: options.package } : {},
|
|
14595
14690
|
...options.artifact.length > 0 ? { artifacts: options.artifact } : {},
|
|
14596
|
-
...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 } : {}
|
|
14597
14694
|
};
|
|
14598
|
-
if (options.recover && (options.apply || options.planDigest || options.package.length > 0 || options.artifact.length > 0 || options.profile)) {
|
|
14599
|
-
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.");
|
|
14600
14697
|
}
|
|
14601
14698
|
const result = options.recover ? await recoverFleetNormalization(request) : options.apply ? await applyFleetNormalization({ ...request, apply: true, planDigest: options.planDigest }) : await planFleetNormalization(request);
|
|
14602
14699
|
console.log(options.json ? JSON.stringify(result, null, 2) : formatFleetNormalization(result));
|
|
@@ -17240,11 +17337,15 @@ function formatFleetNormalization(result) {
|
|
|
17240
17337
|
const packageArgs = result.packages.map((pkg) => `--package ${shellQuoteArg2(pkg.name)}`).join(" ");
|
|
17241
17338
|
const artifactArgs = result.request.artifacts?.map((artifact) => ` --artifact ${shellQuoteArg2(artifact)}`).join("") ?? "";
|
|
17242
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;
|
|
17243
17343
|
return [
|
|
17244
17344
|
`Fleet normalization plan: ${result.source.root} -> ${result.destination.root}`,
|
|
17245
17345
|
`Packages: ${result.packages.map((pkg) => pkg.name).join(", ")}`,
|
|
17346
|
+
...orphanedUnmanaged.length > 0 ? [`Retired orphan ownership (files retained unmanaged): ${orphanedUnmanaged.join(", ")}`] : [],
|
|
17246
17347
|
`Plan digest: ${result.planDigest}`,
|
|
17247
|
-
`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}`
|
|
17248
17349
|
].join("\n");
|
|
17249
17350
|
}
|
|
17250
17351
|
function shouldDefaultUserInstall(nameOrSource, options) {
|
package/openpack.json
CHANGED
package/package.json
CHANGED