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 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 and --artifact are supported only for legacy same-fleet ownership normalization.");
13298
- }
13299
- const installedState = legacySelfNormalization ? await inspectLegacySelfInstalledState(destinationScope, candidates, normalizedRequest.profile, normalizedRequest.artifacts) : await inspectInstalledState(sourceScope, destinationScope, candidates);
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 === legacyOwner);
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 !== legacyOwner && entry.entry.workspaceOwner !== fleetOwner);
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
- if (!graphArtifacts.has(graphEntryIdentity(entry.entry))) {
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
- desiredEntries.push({ ...entry.entry, workspaceOwner: fleetOwner });
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
- /* @__PURE__ */ new Set([legacyOwner])
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(expectedEntries.map((entry) => entry.renderedPath)),
13762
- destinationRenderedPaths: sortedUnique8(expectedEntries.map((entry) => entry.renderedPath))
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 state = await legacyTargetStateForGraph(scope, graph);
13922
- const manifest = (await collectManifestPaths([state.manifestPath]))[0];
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 --profile.");
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.18.2",
4
+ "version": "0.18.4",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.18.2",
3
+ "version": "0.18.4",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.18.2"
8
+ version: "0.18.4"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.18.2"
8
+ version: "0.18.4"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery