agentwheel 0.20.10 → 0.20.12

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
@@ -427,6 +427,22 @@ apply journal that embeds and exactly correlates the missing manifest and graph
427
427
  discoverable for recovery. Legacy SSH state is also preserved and refused because the old records do
428
428
  not prove which endpoint owned it.
429
429
 
430
+ After reviewing `agentwheel ownership recovery-plan`, use `--recover-legacy-state` only for an
431
+ invalid local candidate whose historical files must remain preserved. The recovery plan ignores
432
+ that candidate when constructing the new stable state; it does not authorize runtime replacement.
433
+ Adopting matching unmanaged files still requires `--force-conflict`, while replacing differing files
434
+ still requires `--replace-conflict`. Review the resulting plan before applying the same flags:
435
+
436
+ ```bash
437
+ agentwheel plan --fleet example-fleet --agent lab-claude \
438
+ --recover-legacy-state --force-conflict --replace-conflict
439
+ agentwheel install --fleet example-fleet --agent lab-claude \
440
+ --recover-legacy-state --force-conflict --replace-conflict
441
+ ```
442
+
443
+ Once the stable manifest exists, normal status and planning use it while retaining the invalid
444
+ historical candidate for explicit later cleanup.
445
+
430
446
  ## Core Ideas
431
447
 
432
448
  **Three places, one direction:**
@@ -5,9 +5,9 @@ import {
5
5
  commitManifestMetadataJournal,
6
6
  recoverPendingApply,
7
7
  uninstall
8
- } from "./chunk-P2JK34W7.js";
9
- import "./chunk-J7DKPDS7.js";
10
- import "./chunk-7VPI5J5Y.js";
8
+ } from "./chunk-A44XYLUT.js";
9
+ import "./chunk-FP26KSBM.js";
10
+ import "./chunk-GE7F76AC.js";
11
11
  export {
12
12
  applyCombinedInstallPlan,
13
13
  applyInstallPlan,
@@ -24,11 +24,11 @@ import {
24
24
  rollbackCompletedOperations,
25
25
  sourceLockPath,
26
26
  writeApplyJournal
27
- } from "./chunk-J7DKPDS7.js";
27
+ } from "./chunk-FP26KSBM.js";
28
28
  import {
29
29
  pathExists,
30
30
  writeJsonAtomic
31
- } from "./chunk-7VPI5J5Y.js";
31
+ } from "./chunk-GE7F76AC.js";
32
32
 
33
33
  // src/install/apply.ts
34
34
  import { execFile } from "child_process";
@@ -4,7 +4,7 @@ import {
4
4
  hashPath,
5
5
  pathExists,
6
6
  writeJsonAtomic
7
- } from "./chunk-7VPI5J5Y.js";
7
+ } from "./chunk-GE7F76AC.js";
8
8
 
9
9
  // src/install/transaction.ts
10
10
  import { createHash as createHash6 } from "crypto";
@@ -2865,8 +2865,8 @@ async function recoverMutationRuntime(operationId, options = {}) {
2865
2865
  }
2866
2866
  mutation = GovernedMutation.activateExisting(receipt, baseline, lock);
2867
2867
  const [{ recoverPendingApply }, { readLinkedLocalApplyJournal: readLinkedLocalApplyJournal2, removeApplyJournal: removeApplyJournal2, localPathExists: localPathExists2 }] = await Promise.all([
2868
- import("./apply-ZNEUBJU3.js"),
2869
- import("./transaction-4ERCJXG7.js")
2868
+ import("./apply-ZGCJLZMZ.js"),
2869
+ import("./transaction-4KBNHDQX.js")
2870
2870
  ]);
2871
2871
  let missingReservation = false;
2872
2872
  for (const entry of pending) {
@@ -90,6 +90,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn, description = "cache"
90
90
  break;
91
91
  } catch (error) {
92
92
  if (!isAlreadyExists(error)) throw error;
93
+ if (await removeStaleFilesystemLock(lockPath)) continue;
93
94
  if (Date.now() - started > timeoutMs) {
94
95
  throw new Error(`Timed out waiting for ${description} lock at ${lockPath}`);
95
96
  }
@@ -102,6 +103,26 @@ async function withFilesystemLock(lockPath, timeoutMs, fn, description = "cache"
102
103
  await rm(lockPath, { recursive: true, force: true });
103
104
  }
104
105
  }
106
+ async function removeStaleFilesystemLock(lockPath) {
107
+ try {
108
+ const owner = JSON.parse(await readFile(join(lockPath, "owner.json"), "utf8"));
109
+ if (typeof owner.pid !== "number" || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || isProcessAlive(owner.pid)) return false;
110
+ const stalePath = `${lockPath}.stale-${process.pid}-${Date.now()}`;
111
+ await rename(lockPath, stalePath);
112
+ await rm(stalePath, { recursive: true, force: true });
113
+ return true;
114
+ } catch {
115
+ return false;
116
+ }
117
+ }
118
+ function isProcessAlive(pid) {
119
+ try {
120
+ process.kill(pid, 0);
121
+ return true;
122
+ } catch (error) {
123
+ return typeof error === "object" && error !== null && "code" in error && error.code !== "ESRCH";
124
+ }
125
+ }
105
126
  function isAlreadyExists(error) {
106
127
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
107
128
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  pathExists
4
- } from "./chunk-7VPI5J5Y.js";
4
+ } from "./chunk-GE7F76AC.js";
5
5
 
6
6
  // src/source/identify.ts
7
7
  import { homedir } from "os";
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  inferSourceDriverName,
4
4
  isExplicitSource
5
- } from "./chunk-EMDIG24I.js";
6
- import "./chunk-7VPI5J5Y.js";
5
+ } from "./chunk-U2GRG4UI.js";
6
+ import "./chunk-GE7F76AC.js";
7
7
  export {
8
8
  inferSourceDriverName,
9
9
  isExplicitSource
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  uninstall,
38
38
  withManifestRevision,
39
39
  writeInstallManifest
40
- } from "./chunk-P2JK34W7.js";
40
+ } from "./chunk-A44XYLUT.js";
41
41
  import {
42
42
  CURRENT_WORKSPACE_SCHEMA_VERSION,
43
43
  GovernedMutation,
@@ -98,10 +98,10 @@ import {
98
98
  workspaceSelectionImportSchema,
99
99
  writeApplyJournal,
100
100
  writeWorkspaceConfig
101
- } from "./chunk-J7DKPDS7.js";
101
+ } from "./chunk-FP26KSBM.js";
102
102
  import {
103
103
  inferSourceDriverName
104
- } from "./chunk-EMDIG24I.js";
104
+ } from "./chunk-U2GRG4UI.js";
105
105
  import {
106
106
  hashPath,
107
107
  isAlreadyExists,
@@ -110,7 +110,7 @@ import {
110
110
  pathExists,
111
111
  withFilesystemLock,
112
112
  writeJsonAtomic
113
- } from "./chunk-7VPI5J5Y.js";
113
+ } from "./chunk-GE7F76AC.js";
114
114
 
115
115
  // src/cli/index.ts
116
116
  import { createHash as createHash15 } from "crypto";
@@ -5789,7 +5789,7 @@ var RegistryClient = class {
5789
5789
  }
5790
5790
  };
5791
5791
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
5792
- const { isExplicitSource } = await import("./identify-D64HZTUT.js");
5792
+ const { isExplicitSource } = await import("./identify-O54QXPQR.js");
5793
5793
  if (await isExplicitSource(source)) return { source };
5794
5794
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
5795
5795
  if (!entry) {
@@ -8233,7 +8233,9 @@ async function createGraphSourcePlan(options) {
8233
8233
  graphLockPath,
8234
8234
  stableManifest,
8235
8235
  stableLock: stableLock2,
8236
- transport
8236
+ transport,
8237
+ recoverLegacyState: options.recoverLegacyState,
8238
+ warn
8237
8239
  });
8238
8240
  return { priorState: priorState2, stableLock: stableLock2, stableSourceLock: stableSourceLock2 };
8239
8241
  };
@@ -8495,7 +8497,10 @@ function validateJournalManifestEnvelope(value, options, stateKey, journalPath)
8495
8497
  return manifest;
8496
8498
  }
8497
8499
  async function resolvePriorTargetState(options) {
8498
- const candidates = await discoverLegacyStateCandidates(options);
8500
+ const candidates = await discoverLegacyStateCandidates({
8501
+ ...options,
8502
+ recoverLegacyState: options.recoverLegacyState === true || options.stableManifest !== void 0
8503
+ });
8499
8504
  const correlated = candidates.filter((candidate) => candidate.manifest);
8500
8505
  if (options.stableManifest) {
8501
8506
  for (const candidate of correlated) {
@@ -8536,6 +8541,14 @@ async function resolvePriorTargetState(options) {
8536
8541
  };
8537
8542
  }
8538
8543
  if (options.stableLock) return { graphLock: options.stableLock };
8544
+ if (candidates.length > 0 && options.recoverLegacyState) {
8545
+ for (const candidate of candidates) {
8546
+ options.warn?.(
8547
+ `Legacy graph-only target state for ${options.adapter} at ${candidate.graphLockPath} has no correlated install manifest; runtime identity and ownership cannot be proven. Preserving it outside the recovered stable state.`
8548
+ );
8549
+ }
8550
+ return {};
8551
+ }
8539
8552
  if (candidates.length > 1) {
8540
8553
  throw new Error(`Ambiguous legacy target state for ${options.adapter}: multiple graph-only candidates were found.`);
8541
8554
  }
@@ -8563,34 +8576,28 @@ async function discoverLegacyStateCandidates(options) {
8563
8576
  const fingerprint = name.slice(0, -suffix.length);
8564
8577
  if (!/^[a-f0-9]{64}$/u.test(fingerprint)) continue;
8565
8578
  const graphLockPath = join33(directory, name);
8566
- let graphLock;
8567
8579
  try {
8580
+ let graphLock;
8568
8581
  graphLock = await readGraphLock(graphLockPath);
8569
- } catch (error) {
8570
- throw new Error(
8571
- `Legacy graph lock at ${graphLockPath} is invalid or unsupported: ` + (error instanceof Error ? error.message : String(error))
8572
- );
8573
- }
8574
- if (graphLock.canonical.targetFingerprint !== fingerprint) {
8575
- throw new Error(
8576
- `Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
8577
- );
8578
- }
8579
- if (options.transport.kind === "ssh") {
8580
- throw new Error(
8581
- `Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
8582
- );
8583
- }
8584
- const graphLockDigest = digestGraphLock(graphLock);
8585
- const candidateStateKey = stateKeyFor(options.adapter, {
8586
- installationType: options.installationType,
8587
- stateKey: options.explicitStateKey,
8588
- targetFingerprint: fingerprint,
8589
- fleetId: options.fleetId
8590
- });
8591
- let manifest;
8592
- let sourceLock;
8593
- try {
8582
+ if (graphLock.canonical.targetFingerprint !== fingerprint) {
8583
+ throw new Error(
8584
+ `Legacy graph lock fingerprint does not match its filename at ${graphLockPath}.`
8585
+ );
8586
+ }
8587
+ if (options.transport.kind === "ssh") {
8588
+ throw new Error(
8589
+ `Legacy SSH target state at ${graphLockPath} does not record its original endpoint; Agentwheel cannot prove endpoint identity, so the graph lock was preserved.`
8590
+ );
8591
+ }
8592
+ const graphLockDigest = digestGraphLock(graphLock);
8593
+ const candidateStateKey = stateKeyFor(options.adapter, {
8594
+ installationType: options.installationType,
8595
+ stateKey: options.explicitStateKey,
8596
+ targetFingerprint: fingerprint,
8597
+ fleetId: options.fleetId
8598
+ });
8599
+ let manifest;
8600
+ let sourceLock;
8594
8601
  manifest = await readInstallManifest(options.installRoot, options.adapter, options.transport, {
8595
8602
  installationType: options.installationType,
8596
8603
  stateKey: candidateStateKey
@@ -8599,32 +8606,32 @@ async function discoverLegacyStateCandidates(options) {
8599
8606
  installationType: options.installationType,
8600
8607
  stateKey: candidateStateKey
8601
8608
  });
8602
- } catch (error) {
8603
- throw new Error(
8604
- `Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error))
8605
- );
8606
- }
8607
- if (!manifest) {
8608
- if (sourceLock) {
8609
- throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
8609
+ if (!manifest) {
8610
+ if (sourceLock) {
8611
+ throw new Error(`Legacy source lock for ${graphLockPath} has no correlated install manifest.`);
8612
+ }
8613
+ candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
8614
+ continue;
8610
8615
  }
8611
- candidates.push({ stateKey: candidateStateKey, graphLockPath, graphLockDigest, graphLock });
8612
- continue;
8616
+ const validated = validateLegacyManifestCandidate(manifest, {
8617
+ ...options,
8618
+ candidateStateKey,
8619
+ graphLockDigest,
8620
+ graphLockPath
8621
+ });
8622
+ candidates.push({
8623
+ stateKey: candidateStateKey,
8624
+ graphLockPath,
8625
+ graphLockDigest,
8626
+ graphLock,
8627
+ manifest: validated,
8628
+ sourceLock
8629
+ });
8630
+ } catch (error) {
8631
+ const message = `Legacy target state for ${graphLockPath} is invalid: ` + (error instanceof Error ? error.message : String(error));
8632
+ if (!options.recoverLegacyState) throw new Error(message);
8633
+ options.warn?.(`${message} Preserving it outside the recovered stable state.`);
8613
8634
  }
8614
- const validated = validateLegacyManifestCandidate(manifest, {
8615
- ...options,
8616
- candidateStateKey,
8617
- graphLockDigest,
8618
- graphLockPath
8619
- });
8620
- candidates.push({
8621
- stateKey: candidateStateKey,
8622
- graphLockPath,
8623
- graphLockDigest,
8624
- graphLock,
8625
- manifest: validated,
8626
- sourceLock
8627
- });
8628
8635
  }
8629
8636
  return candidates;
8630
8637
  }
@@ -13703,10 +13710,10 @@ program.command("scan").description("scan a package source for validation findin
13703
13710
  await releaseGitSnapshotLease(resolved.cacheLeasePath);
13704
13711
  }
13705
13712
  });
13706
- program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
13713
+ program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--recover-legacy-state", "build a stable state while preserving invalid legacy candidates", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
13707
13714
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
13708
13715
  });
13709
- program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
13716
+ program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--recover-legacy-state", "build a stable state while preserving invalid legacy candidates", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
13710
13717
  await runInstallCommand(source, options, { apply: !options.dryRun });
13711
13718
  });
13712
13719
  program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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 agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-foreign-state", "plan even when another workspace owns install state at the same paths", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "exclude unrelated configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
@@ -14907,7 +14914,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
14907
14914
  }
14908
14915
  const priorGroupState = await resolveCliTargetState(group.target, {
14909
14916
  ...targetOptions,
14910
- installationType: group.installationType
14917
+ installationType: group.installationType,
14918
+ recoverLegacyState: targetOptions.recoverLegacyState
14911
14919
  });
14912
14920
  const previousGroupLock = priorGroupState.graphLock;
14913
14921
  const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
@@ -15016,7 +15024,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
15016
15024
  deferForeignStateCheck: targetOptions.deferForeignStateCheck === true || targetOptions.focusedArtifact !== void 0,
15017
15025
  replaceConflict: targetOptions.replaceConflict,
15018
15026
  retireExactMcp: targetOptions.retireExactMcp,
15019
- expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner
15027
+ expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner,
15028
+ recoverLegacyState: targetOptions.recoverLegacyState
15020
15029
  });
15021
15030
  if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
15022
15031
  const manifest = result.previousManifest;
@@ -15842,7 +15851,9 @@ async function resolveCliTargetState(target, options) {
15842
15851
  graphLockPath: stableGraphLockPath,
15843
15852
  stableManifest,
15844
15853
  stableLock,
15845
- transport
15854
+ transport,
15855
+ recoverLegacyState: options.recoverLegacyState,
15856
+ warn: options.warn
15846
15857
  });
15847
15858
  return {
15848
15859
  adapter,
@@ -18,8 +18,8 @@ import {
18
18
  removeApplyJournal,
19
19
  rollbackCompletedOperations,
20
20
  writeApplyJournal
21
- } from "./chunk-J7DKPDS7.js";
22
- import "./chunk-7VPI5J5Y.js";
21
+ } from "./chunk-FP26KSBM.js";
22
+ import "./chunk-GE7F76AC.js";
23
23
  export {
24
24
  abortApplyJournal,
25
25
  acquireApplyLock,
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.20.10",
4
+ "version": "0.20.12",
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.10",
3
+ "version": "0.20.12",
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.10"
8
+ version: "0.20.12"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -618,3 +618,17 @@ nothing else is stale. If it differs, regenerate only the affected graph locks (
618
618
  leave them uncommitted: lock commits belong to the approved install. `update --dependency` cannot
619
619
  recompute an edge whose root node id changed (e.g. after renaming the root package's artifacts);
620
620
  regenerate the locks instead.
621
+
622
+ If target-state discovery rejects an invalid historical graph-lock/manifest pair, first write and
623
+ review `agentwheel ownership recovery-plan`. For a complete local report, preview a stable-state
624
+ recovery with the exact Fleet target:
625
+
626
+ ```bash
627
+ agentwheel plan --fleet <fleet-id> --agent <agent> \
628
+ --recover-legacy-state --force-conflict --replace-conflict
629
+ ```
630
+
631
+ `--recover-legacy-state` preserves the invalid historical files and only bypasses their use as the
632
+ planning base. `--force-conflict` adopts exact existing content; `--replace-conflict` permits the
633
+ reviewed plan to update differing unmanaged content. Apply only the same reviewed target and flags.
634
+ After the first stable install, rerun the ordinary plan without recovery flags and require a no-op.
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.20.10"
8
+ version: "0.20.12"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery