agentwheel 0.20.12 → 0.20.13

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 CHANGED
@@ -414,12 +414,14 @@ label is not endpoint identity.
414
414
 
415
415
  State created by an older Agentwheel release may be named with the full target fingerprint. For a
416
416
  local target, Agentwheel adopts that state only when one canonical graph lock, its v2 install
417
- manifest, the contribution owner, target details, and graph-lock digest all agree. A supported
418
- install or uninstall carries an explicit migration and revalidates the old and new state under the
419
- shared apply lock before replacing the legacy paths. An explicit `stateKey` remains the manifest and
420
- source-lock key while its legacy graph-lock path moves. Ownership handoff and retirement require a
421
- full install to migrate first. Status, dependency inspection, uninstall, and journal commands use
422
- the same discovery rules.
417
+ manifest, the contribution owner, and target details agree. When a tracked graph lock has advanced
418
+ past the installed manifest, Agentwheel uses the manifest only as prior runtime state and resolves
419
+ the graph fresh; it never treats that newer lock as the previously installed graph. Mixed digests
420
+ inside the owned contribution still fail closed. A supported install or uninstall carries an
421
+ explicit migration and revalidates the old and new state under the shared apply lock before replacing
422
+ the legacy paths. An explicit `stateKey` remains the manifest and source-lock key while its legacy
423
+ graph-lock path moves. Status, dependency inspection, uninstall, and journal commands use the same
424
+ discovery rules.
423
425
 
424
426
  Agentwheel refuses ambiguous candidates, a graph lock without its correlated manifest, or
425
427
  disagreeing stable and legacy state, and preserves those files for manual reconciliation. A pending
@@ -443,6 +445,21 @@ agentwheel install --fleet example-fleet --agent lab-claude \
443
445
  Once the stable manifest exists, normal status and planning use it while retaining the invalid
444
446
  historical candidate for explicit later cleanup.
445
447
 
448
+ When stale ownership must be retired before the target-state migration can run, bind the retirement
449
+ to the exact already-installed Fleet manifest instead of asking target-state discovery to choose it:
450
+
451
+ ```bash
452
+ agentwheel ownership retire-stale \
453
+ --from-workspace-root <missing-worktree> \
454
+ --to-workspace-root <fleet-root> \
455
+ --source-state-key <stale-state-key> \
456
+ --destination-state-key <fleet-owned-state-key> \
457
+ --fleet <fleet-id> --agent <agent> --json
458
+ ```
459
+
460
+ The dry-run still proves exact destination coverage and runtime bytes. Apply only its emitted command,
461
+ which includes the plan, source, destination, and manifest-inventory revisions.
462
+
446
463
  ## Core Ideas
447
464
 
448
465
  **Three places, one direction:**
package/dist/index.js CHANGED
@@ -8469,12 +8469,15 @@ async function discoverTargetApplyJournal(options) {
8469
8469
  throw new Error(`Legacy apply journal graph-lock digest mismatch at ${item.path}.`);
8470
8470
  }
8471
8471
  const manifest = validateJournalManifestEnvelope(journal.manifest, options, journalStateKey, item.path);
8472
- validateLegacyManifestCandidate(manifest, {
8472
+ const validatedManifest = validateLegacyManifestCandidate(manifest, {
8473
8473
  ...options,
8474
8474
  candidateStateKey: journalStateKey,
8475
8475
  graphLockDigest: journal.graphLockDigest,
8476
8476
  graphLockPath: journal.graphLockPath
8477
8477
  });
8478
+ if (!validatedManifest.graphLockCorrelated) {
8479
+ throw new Error(`Legacy apply journal install manifest graph-lock digest mismatch at ${item.path}.`);
8480
+ }
8478
8481
  correlated.push({ path: item.path, stateKey: journalStateKey });
8479
8482
  }
8480
8483
  if (correlated.length > 1) {
@@ -8496,6 +8499,8 @@ function validateJournalManifestEnvelope(value, options, stateKey, journalPath)
8496
8499
  }
8497
8500
  return manifest;
8498
8501
  }
8502
+ var LegacyContributionScopeMismatchError = class extends Error {
8503
+ };
8499
8504
  async function resolvePriorTargetState(options) {
8500
8505
  const candidates = await discoverLegacyStateCandidates({
8501
8506
  ...options,
@@ -8517,7 +8522,7 @@ async function resolvePriorTargetState(options) {
8517
8522
  if (!options.stableLock && sameKeyCandidates.length === 1) {
8518
8523
  const candidate = sameKeyCandidates[0];
8519
8524
  return {
8520
- graphLock: candidate.graphLock,
8525
+ graphLock: candidate.graphLockCorrelated ? candidate.graphLock : void 0,
8521
8526
  manifest: options.stableManifest,
8522
8527
  migration: migrationFromCandidate(candidate)
8523
8528
  };
@@ -8535,7 +8540,7 @@ async function resolvePriorTargetState(options) {
8535
8540
  if (correlated.length === 1) {
8536
8541
  const candidate = correlated[0];
8537
8542
  return {
8538
- graphLock: candidate.graphLock,
8543
+ graphLock: candidate.graphLockCorrelated ? candidate.graphLock : void 0,
8539
8544
  manifest: candidate.manifest,
8540
8545
  migration: migrationFromCandidate(candidate)
8541
8546
  };
@@ -8610,7 +8615,7 @@ async function discoverLegacyStateCandidates(options) {
8610
8615
  if (sourceLock) {
8611
8616
  throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
8612
8617
  }
8613
- candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
8618
+ candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock, graphLockCorrelated: false });
8614
8619
  continue;
8615
8620
  }
8616
8621
  const validated = validateLegacyManifestCandidate(manifest, {
@@ -8619,16 +8624,26 @@ async function discoverLegacyStateCandidates(options) {
8619
8624
  graphLockDigest,
8620
8625
  graphLockPath
8621
8626
  });
8627
+ if (!validated.graphLockCorrelated) {
8628
+ options.warn?.(
8629
+ `Legacy graph lock at ${graphLockPath} advanced beyond its installed manifest; using the manifest as prior runtime state and resolving the dependency graph fresh.`
8630
+ );
8631
+ }
8622
8632
  candidates.push({
8623
8633
  stateKey: candidateStateKey,
8624
8634
  graphLockPath,
8625
8635
  graphLockDigest,
8626
8636
  graphLock,
8627
- manifest: validated,
8637
+ graphLockCorrelated: validated.graphLockCorrelated,
8638
+ manifest: validated.manifest,
8628
8639
  sourceLock
8629
8640
  });
8630
8641
  } catch (error) {
8631
8642
  const message = `Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error));
8643
+ if (error instanceof LegacyContributionScopeMismatchError) {
8644
+ options.warn?.(`${message} Preserving it as foreign state for ownership validation.`);
8645
+ continue;
8646
+ }
8632
8647
  if (!options.recoverLegacyState) throw new Error(message);
8633
8648
  options.warn?.(`${message} Preserving it outside the recovered stable state.`);
8634
8649
  }
@@ -8651,14 +8666,17 @@ function validateLegacyManifestCandidate(manifest, options) {
8651
8666
  if (normalizeInstallRoot2(manifest.targetRoot, options.transport.kind) !== normalizeInstallRoot2(options.installRoot, options.transport.kind)) {
8652
8667
  throw new Error(`Legacy target state target root mismatch at ${options.graphLockPath}.`);
8653
8668
  }
8654
- const correlated = manifest.entries.filter((entry) => entry.graphLockDigest === options.graphLockDigest);
8655
- if (correlated.length === 0) {
8656
- throw new Error(`Legacy install manifest graph-lock digest does not match ${options.graphLockPath}.`);
8669
+ const ownedEntries = manifest.entries.filter((entry) => entry.workspaceOwner === options.workspaceOwner);
8670
+ if (ownedEntries.length === 0) {
8671
+ throw new LegacyContributionScopeMismatchError(
8672
+ `Legacy target state contribution scope does not match ${options.graphLockPath}.`
8673
+ );
8657
8674
  }
8658
- if (correlated.some((entry) => entry.workspaceOwner !== options.workspaceOwner)) {
8659
- throw new Error(`Legacy target state contribution scope does not match ${options.graphLockPath}.`);
8675
+ const manifestDigests = new Set(ownedEntries.map((entry) => entry.graphLockDigest));
8676
+ if (manifestDigests.size !== 1) {
8677
+ throw new Error(`Legacy install manifest has inconsistent graph-lock digests at ${options.graphLockPath}.`);
8660
8678
  }
8661
- return manifest;
8679
+ return { manifest, graphLockCorrelated: manifestDigests.has(options.graphLockDigest) };
8662
8680
  }
8663
8681
  function migrationFromCandidate(candidate) {
8664
8682
  return {
@@ -11907,6 +11925,13 @@ async function planFleetNormalization(request) {
11907
11925
  const sourcePackage = sourceByName.get(name);
11908
11926
  const destinationPackage = destinationByName.get(name);
11909
11927
  if (!sourcePackage || !destinationPackage) {
11928
+ if (legacySelfNormalization && normalizedRequest.orphanedOwnerRoots?.length && normalizedRequest.packages?.includes(name)) {
11929
+ packages.push({
11930
+ name,
11931
+ declarationDigest: sha2565(canonicalJson2({ name, evidence: "orphaned-installed-state" }))
11932
+ });
11933
+ continue;
11934
+ }
11910
11935
  throw new Error(`Package '${name}' must be an existing duplicate in both source and destination fleets.`);
11911
11936
  }
11912
11937
  const sourceDeclaration = canonicalJson2(sourcePackage);
@@ -12376,11 +12401,12 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12376
12401
  const legacyOwner = workspaceOwnerForRoot(fleet.root);
12377
12402
  const fleetOwner = workspaceOwnerForRoot(fleet.root, fleet.fleetId);
12378
12403
  const orphanedOwners = await orphanedWorkspaceOwners(orphanedOwnerRoots);
12404
+ const allowOrphanedRetiredPackages = orphanedOwners.size > 0;
12379
12405
  const knownLegacyOwners = /* @__PURE__ */ new Set([legacyOwner, ...orphanedOwners]);
12380
12406
  const transferableOwners = orphanedOwners.size > 0 ? orphanedOwners : /* @__PURE__ */ new Set([legacyOwner]);
12381
12407
  let normalizedGraphCount = 0;
12382
12408
  for (const graph of graphCandidates) {
12383
- const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
12409
+ const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages);
12384
12410
  if (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
12385
12411
  const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
12386
12412
  if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
@@ -12395,7 +12421,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12395
12421
  continue;
12396
12422
  }
12397
12423
  }
12398
- const legacyState = await legacyTargetStateForGraph(fleet, graph);
12424
+ const legacyState = await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages);
12399
12425
  if (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
12400
12426
  throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
12401
12427
  }
@@ -12410,8 +12436,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12410
12436
  }
12411
12437
  const manifestPaths = /* @__PURE__ */ new Set();
12412
12438
  for (const graph of graphs) {
12413
- manifestPaths.add((await legacyTargetStateForGraph(fleet, graph)).manifestPath);
12414
- manifestPaths.add((await targetStateForGraph(fleet, graph, fleet.fleetId)).manifestPath);
12439
+ manifestPaths.add((await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages)).manifestPath);
12440
+ manifestPaths.add((await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages)).manifestPath);
12415
12441
  }
12416
12442
  const manifests = await collectManifestPaths([...manifestPaths]);
12417
12443
  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) })));
@@ -12448,8 +12474,8 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
12448
12474
  const renderedPaths = /* @__PURE__ */ new Set();
12449
12475
  const orphanedUnmanagedPaths = /* @__PURE__ */ new Set();
12450
12476
  for (const graph of graphs) {
12451
- const legacyState = await legacyTargetStateForGraph(fleet, graph);
12452
- const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId);
12477
+ const legacyState = await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages);
12478
+ const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages);
12453
12479
  if (graph.path === destinationState.graphLockPath) {
12454
12480
  throw new Error(`Fleet '${fleet.fleetId}' graph state is already normalized to fleet-qualified identity.`);
12455
12481
  }
@@ -12733,7 +12759,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
12733
12759
  });
12734
12760
  return installManifestPath(installRoot, adapter.name, { installationType: agent.installationType, stateKey });
12735
12761
  }
12736
- async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null) {
12762
+ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null, allowMissingNamedAgentPackages = false) {
12737
12763
  const agent = scope.config.agents[graph.targetKey];
12738
12764
  if (agent?.transport === "ssh") {
12739
12765
  throw new Error(`Installed-state normalization cannot hand off SSH target '${graph.targetKey}' locally.`);
@@ -12742,10 +12768,10 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12742
12768
  throw new Error(`Graph adapter '${graph.adapter}' does not match configured target adapter '${agent.adapter}'.`);
12743
12769
  }
12744
12770
  const graphPackages = graph.lock.canonical.roots.map((root) => scope.config.packages.find((pkg) => pkg.name === root.rootId));
12745
- if (graphPackages.some((pkg) => !pkg)) {
12771
+ if (graphPackages.some((pkg) => !pkg) && !(allowMissingNamedAgentPackages && agent && agent.installationType)) {
12746
12772
  throw new Error(`Graph target '${graph.targetKey}' contains a package absent from the fleet configuration.`);
12747
12773
  }
12748
- const packages = graphPackages;
12774
+ const packages = graphPackages.filter((pkg) => pkg !== void 0);
12749
12775
  if (!agent) {
12750
12776
  if (graph.targetKey !== graph.adapter) {
12751
12777
  throw new Error(
@@ -12760,18 +12786,18 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
12760
12786
  throw new Error(`Graph adapter '${graph.adapter}' does not match direct package adapter '${packageAdapter}'.`);
12761
12787
  }
12762
12788
  }
12763
- const installationType = oneEffectiveValue(
12789
+ const installationType = packages.length > 0 ? oneEffectiveValue(
12764
12790
  packages.map((pkg) => pkg.installationType ?? agent?.installationType ?? "local"),
12765
12791
  `installation type for target '${graph.targetKey}'`
12766
- );
12767
- const adapterConfig = oneEffectiveOptionalValue(
12792
+ ) : agent.installationType;
12793
+ const adapterConfig = packages.length > 0 ? oneEffectiveOptionalValue(
12768
12794
  packages.map((pkg) => agent?.adapterConfig ?? pkg.adapterConfig),
12769
12795
  `adapter config for target '${graph.targetKey}'`
12770
- );
12771
- const adapterModule = oneEffectiveOptionalValue(
12796
+ ) : agent?.adapterConfig;
12797
+ const adapterModule = packages.length > 0 ? oneEffectiveOptionalValue(
12772
12798
  packages.map((pkg) => agent?.adapterModule ?? pkg.adapterModule),
12773
12799
  `adapter module for target '${graph.targetKey}'`
12774
- );
12800
+ ) : agent?.adapterModule;
12775
12801
  const adapter = await resolveAdapter({
12776
12802
  adapter: agent?.adapter ?? graph.adapter,
12777
12803
  adapterConfig,
@@ -12841,8 +12867,8 @@ async function installedTargetStateForGraph(scope, graph, manifests, owners, sel
12841
12867
  assertCorrelatedGraphManifest(graph, state, manifest, owners, selected);
12842
12868
  return state;
12843
12869
  }
12844
- async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null) {
12845
- const current = await targetStateForGraph(scope, graph, identityFleetId);
12870
+ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope.fleetId ?? null, allowMissingNamedAgentPackages = false) {
12871
+ const current = await targetStateForGraph(scope, graph, identityFleetId, allowMissingNamedAgentPackages);
12846
12872
  const targetFingerprint = graph.lock.canonical.targetFingerprint;
12847
12873
  if (!targetFingerprint || !/^[a-f0-9]{64}$/u.test(targetFingerprint)) {
12848
12874
  throw new Error(`Released graph lock has an invalid target fingerprint: ${graph.path}`);
@@ -12887,8 +12913,8 @@ function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected)
12887
12913
  }
12888
12914
  }
12889
12915
  }
12890
- async function legacyTargetStateForGraph(scope, graph) {
12891
- return releasedTargetStateForGraph(scope, graph, null);
12916
+ async function legacyTargetStateForGraph(scope, graph, allowMissingNamedAgentPackages = false) {
12917
+ return releasedTargetStateForGraph(scope, graph, null, allowMissingNamedAgentPackages);
12892
12918
  }
12893
12919
  function assertGraphIsSubset(source, destination) {
12894
12920
  const sourceRoots = new Map(source.lock.canonical.roots.map((root) => [root.rootId, canonicalJson2(root)]));
@@ -14000,7 +14026,7 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
14000
14026
  await rm10(stage, { recursive: true, force: true });
14001
14027
  }
14002
14028
  });
14003
- ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
14029
+ ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").option("--destination-state-key <key>", "exact Fleet-owned destination install-manifest state key (defaults to the stable target state)").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
14004
14030
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
14005
14031
  const fleet = await showRegisteredFleet(options.fleet);
14006
14032
  const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
@@ -14015,19 +14041,16 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
14015
14041
  if (target.fleetId !== options.fleet || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
14016
14042
  throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
14017
14043
  }
14018
- const state = await resolveCliTargetState(target, normalizedOptions);
14019
- const { adapter, installationType } = state;
14020
- if (state.migration) {
14021
- throw new Error(
14022
- "Migrate the target state before ownership retirement by running a full agentwheel install for this target."
14023
- );
14024
- }
14044
+ const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
14045
+ const adapter = await resolveAdapterForTarget(target, adapterOptions);
14046
+ const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
14047
+ const stableState = installStateForTarget(target, adapter, adapterOptions, installationType);
14025
14048
  const request = {
14026
- targetRoot: state.installRoot,
14049
+ targetRoot: stableState.installRoot,
14027
14050
  adapter: adapter.name,
14028
14051
  installationType,
14029
14052
  sourceStateKey: options.sourceStateKey,
14030
- destinationStateKey: state.stableStateKey,
14053
+ destinationStateKey: options.destinationStateKey ?? stableState.stateKey,
14031
14054
  fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
14032
14055
  toWorkspaceRoot,
14033
14056
  toFleetId: fleet.id,
@@ -14050,6 +14073,8 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
14050
14073
  request.toWorkspaceRoot,
14051
14074
  "--source-state-key",
14052
14075
  request.sourceStateKey,
14076
+ "--destination-state-key",
14077
+ request.destinationStateKey,
14053
14078
  "--fleet",
14054
14079
  request.toFleetId,
14055
14080
  ...targetArgs,
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.20.12",
4
+ "version": "0.20.13",
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.20.12",
3
+ "version": "0.20.13",
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.20.12"
8
+ version: "0.20.13"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -632,3 +632,10 @@ agentwheel plan --fleet <fleet-id> --agent <agent> \
632
632
  planning base. `--force-conflict` adopts exact existing content; `--replace-conflict` permits the
633
633
  reviewed plan to update differing unmanaged content. Apply only the same reviewed target and flags.
634
634
  After the first stable install, rerun the ordinary plan without recovery flags and require a no-op.
635
+
636
+ An advanced tracked graph lock with an otherwise valid owned v2 manifest is an ordinary migration:
637
+ Agentwheel keeps the manifest as prior runtime state, resolves the graph fresh, and does not trust the
638
+ advanced lock as installed history. If stale foreign ownership blocks that migration, use
639
+ `agentwheel ownership retire-stale --destination-state-key <exact-fleet-state-key>` to produce a
640
+ metadata-only, revision-gated retirement plan against the already-installed Fleet manifest. Do not
641
+ guess the state key or apply without the exact command emitted by the dry-run.
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.12"
8
+ version: "0.20.13"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery