agentwheel 0.20.0 → 0.20.1

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.
@@ -4,8 +4,8 @@ import {
4
4
  applyInstallPlan,
5
5
  recoverPendingApply,
6
6
  uninstall
7
- } from "./chunk-USHT7FGV.js";
8
- import "./chunk-HOYIIUL5.js";
7
+ } from "./chunk-AFVGRRZW.js";
8
+ import "./chunk-BJEZXIXX.js";
9
9
  import "./chunk-7VPI5J5Y.js";
10
10
  export {
11
11
  applyCombinedInstallPlan,
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  acquireApplyLock,
4
+ applyJournalPath,
4
5
  artifactFormatSchema,
5
6
  artifactTypeSchema,
6
7
  assertApplyJournalRecoveryAllowed,
@@ -11,6 +12,7 @@ import {
11
12
  fileKindSchema,
12
13
  installManifestPath,
13
14
  installationTypeSchema,
15
+ listApplyJournals,
14
16
  localPathExists,
15
17
  localTransport,
16
18
  metadataDir,
@@ -22,7 +24,7 @@ import {
22
24
  rollbackCompletedOperations,
23
25
  sourceLockPath,
24
26
  writeApplyJournal
25
- } from "./chunk-HOYIIUL5.js";
27
+ } from "./chunk-BJEZXIXX.js";
26
28
  import {
27
29
  pathExists,
28
30
  writeJsonAtomic
@@ -549,16 +551,15 @@ async function readInstallManifest(targetRoot, adapter, transport = localTranspo
549
551
  async function listInstallManifests(targetRoot, adapter, transport = localTransport) {
550
552
  const dir = metadataDir(targetRoot);
551
553
  const suffix = ".install-manifest.json";
552
- const prefix = `${adapter}.`;
553
554
  const found = [];
554
555
  for (const fileName of await transport.listDir(dir)) {
555
556
  if (!fileName.endsWith(suffix)) continue;
556
557
  const stateKey = fileName.slice(0, -suffix.length);
557
- if (stateKey !== adapter && !stateKey.startsWith(prefix)) continue;
558
558
  const path = join(dir, fileName);
559
559
  try {
560
560
  const raw = JSON.parse(await transport.readFile(path));
561
561
  const parsed = installManifestSchema.parse(raw);
562
+ if (parsed.adapter !== adapter) continue;
562
563
  found.push({ path, fileName, stateKey, manifest: { ...parsed, revision: computeManifestRevision(raw) } });
563
564
  } catch (error) {
564
565
  throw new Error(`Unreadable install manifest at ${path}: ${error instanceof Error ? error.message : String(error)}`);
@@ -566,6 +567,10 @@ async function listInstallManifests(targetRoot, adapter, transport = localTransp
566
567
  }
567
568
  return found.sort((a, b) => a.fileName.localeCompare(b.fileName));
568
569
  }
570
+ async function computeInstallManifestInventoryRevision(targetRoot, adapter, transport = localTransport) {
571
+ const inventory = (await listInstallManifests(targetRoot, adapter, transport)).map((item) => [item.fileName, item.manifest.revision]);
572
+ return createHash2("sha256").update(JSON.stringify(inventory)).digest("hex");
573
+ }
569
574
  async function writeInstallManifest(manifest, transport = localTransport) {
570
575
  const next = withManifestRevision(manifest);
571
576
  await transport.writeJsonAtomic(installManifestPath(next.targetRoot, next.adapter, {
@@ -1360,6 +1365,7 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
1360
1365
  assertGovernedRuntimeTransportSupported(transport);
1361
1366
  const lock = await acquireApplyLock(targetRoot, adapter, transport, {}, scope);
1362
1367
  try {
1368
+ await assertRuntimeJournalGate(targetRoot, adapter, transport, scope, true);
1363
1369
  const journal = await readApplyJournal(targetRoot, adapter, transport, scope);
1364
1370
  if (!journal) return void 0;
1365
1371
  assertApplyJournalRecoveryAllowed(journal);
@@ -1402,6 +1408,26 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
1402
1408
  await lock.release();
1403
1409
  }
1404
1410
  }
1411
+ async function assertRuntimeJournalGate(targetRoot, adapter, transport, scope, allowRequestedJournal = false) {
1412
+ const pending = await listApplyJournals(targetRoot, adapter, transport, {
1413
+ installationType: scope.installationType
1414
+ });
1415
+ if (pending.length === 0) return;
1416
+ const requestedPath = applyJournalPath(targetRoot, adapter, scope);
1417
+ if (allowRequestedJournal && pending.length === 1 && pending[0].path === requestedPath) return;
1418
+ throw new Error(
1419
+ `Cannot mutate ${adapter}/${scope.installationType ?? "local"} at ${targetRoot} while runtime apply journal(s) are pending: ` + pending.map((item) => item.path).join(", ")
1420
+ );
1421
+ }
1422
+ async function assertRuntimeStateRevision(plan, transport) {
1423
+ if (!plan.runtimeStateRevision) return;
1424
+ const current = await computeInstallManifestInventoryRevision(plan.targetRoot, plan.adapter, transport);
1425
+ if (current !== plan.runtimeStateRevision) {
1426
+ throw new Error(
1427
+ `Runtime manifest inventory changed after planning: expected ${plan.runtimeStateRevision}, found ${current}; replan needed.`
1428
+ );
1429
+ }
1430
+ }
1405
1431
  async function applyPlanTransactionally(plan, options = {}) {
1406
1432
  const transport = options.transport ?? localTransport;
1407
1433
  const scope = { installationType: plan.installationType, stateKey: plan.stateKey };
@@ -1412,6 +1438,8 @@ async function applyPlanTransactionally(plan, options = {}) {
1412
1438
  assertGovernedRuntimeTransportSupported(transport);
1413
1439
  const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, options.lock, scope);
1414
1440
  try {
1441
+ await assertRuntimeJournalGate(plan.targetRoot, plan.adapter, transport, scope);
1442
+ await assertRuntimeStateRevision(plan, transport);
1415
1443
  await assertBaseRevision(plan, transport);
1416
1444
  const now = (/* @__PURE__ */ new Date()).toISOString();
1417
1445
  const graphLockDigest = options.graphLockDigest ?? plan.graphLockDigest;
@@ -1493,7 +1521,7 @@ async function uninstall(plan, options = {}) {
1493
1521
  const preserved = [...preservedKept, ...skipped].filter((operation) => operation.preserveInManifest !== false);
1494
1522
  for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
1495
1523
  const now = (/* @__PURE__ */ new Date()).toISOString();
1496
- const finalManifest = withManifestRevision({
1524
+ let finalManifest = withManifestRevision({
1497
1525
  version: 2,
1498
1526
  adapter: plan.adapter,
1499
1527
  installationType: plan.installationType,
@@ -1517,8 +1545,25 @@ async function uninstall(plan, options = {}) {
1517
1545
  });
1518
1546
  const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, resolvedOptions.lock, scope);
1519
1547
  try {
1548
+ await assertRuntimeJournalGate(plan.targetRoot, plan.adapter, transport, scope);
1549
+ await assertRuntimeStateRevision(plan, transport);
1520
1550
  await assertBaseRevision(plan, transport);
1521
1551
  await assertExactMergeRemovalPreconditions(removable, transport);
1552
+ const revalidatedSkips = /* @__PURE__ */ new Map();
1553
+ for (const operation of skipped) {
1554
+ const entry = await applyOperation(operation, {
1555
+ transport,
1556
+ now,
1557
+ graphLockDigest: operation.graphLockDigest ?? plan.graphLockDigest
1558
+ });
1559
+ if (entry) revalidatedSkips.set(operation.relativeDestPath, entry);
1560
+ }
1561
+ if (revalidatedSkips.size > 0) {
1562
+ finalManifest = withManifestRevision({
1563
+ ...finalManifest,
1564
+ entries: finalManifest.entries.map((entry) => revalidatedSkips.get(entry.path) ?? entry)
1565
+ });
1566
+ }
1522
1567
  const mutation = mutationMetadataForApplyJournal();
1523
1568
  const journal = {
1524
1569
  version: mutation ? 2 : 1,
@@ -1724,9 +1769,33 @@ async function applyOperation(operation, context) {
1724
1769
  if (!operation.desiredHash) {
1725
1770
  throw new Error(`Invalid skip operation missing hash: ${operation.relativeDestPath}`);
1726
1771
  }
1772
+ let verifiedCurrentHash = operation.currentHash;
1773
+ if (operation.mode === managedInstructionBlockMode) {
1774
+ const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
1775
+ if (!await managedInstructionBlockLanded(operation.destPath, selector, operation.desiredHash, transport)) {
1776
+ throw new Error(`Managed block changed after planning: ${operation.relativeDestPath}`);
1777
+ }
1778
+ verifiedCurrentHash = operation.desiredHash;
1779
+ } else if (operation.mergeStrategy && hasMergeRemovalContent(operation.mergeRemoval)) {
1780
+ if (!await transport.pathExists(operation.destPath)) {
1781
+ throw new Error(`Merge skip destination disappeared after planning: ${operation.relativeDestPath}`);
1782
+ }
1783
+ assertExactMergeContribution(operation.mergeRemoval, operation.mergeStrategy, await transport.readFile(operation.destPath));
1784
+ verifiedCurrentHash = await transport.hashPath(operation.destPath);
1785
+ } else if (!operation.semanticPlugin && !operation.programmaticOperation) {
1786
+ if (!await transport.pathExists(operation.destPath)) {
1787
+ throw new Error(`Skip destination disappeared after planning: ${operation.relativeDestPath}`);
1788
+ }
1789
+ const currentHash = await transport.hashPath(operation.destPath);
1790
+ const expectedHash = operation.currentHash ?? operation.desiredHash;
1791
+ if (currentHash !== expectedHash) {
1792
+ throw new Error(`Skip destination changed after planning: ${operation.relativeDestPath}`);
1793
+ }
1794
+ verifiedCurrentHash = currentHash;
1795
+ }
1727
1796
  return manifestEntryForOperation(operation, {
1728
1797
  now,
1729
- hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && operation.currentHash ? operation.currentHash : operation.desiredHash,
1798
+ hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && verifiedCurrentHash ? verifiedCurrentHash : operation.desiredHash,
1730
1799
  sourceHash: operation.desiredHash,
1731
1800
  graphLockDigest: context.graphLockDigest
1732
1801
  });
@@ -2210,6 +2279,7 @@ export {
2210
2279
  installManifestSchema,
2211
2280
  readInstallManifest,
2212
2281
  listInstallManifests,
2282
+ computeInstallManifestInventoryRevision,
2213
2283
  writeInstallManifest,
2214
2284
  normalizeTargetRoot,
2215
2285
  withManifestRevision,
@@ -344,7 +344,7 @@ var targetRegistryInputSchema = z2.union([
344
344
  targetRegistrySchema
345
345
  ]);
346
346
  var adapterSchema = z2.object({
347
- name: z2.string().min(1),
347
+ name: z2.string().regex(/^[a-z0-9][a-z0-9._-]*$/i, "adapter name must be a canonical path-safe identifier"),
348
348
  displayName: z2.string().min(1).optional(),
349
349
  targets: z2.partialRecord(
350
350
  artifactTypeSchema,
@@ -460,6 +460,7 @@ function metadataDir(targetRoot) {
460
460
  return join(targetRoot, ".agentwheel");
461
461
  }
462
462
  function stateKeyFor(adapter, scope = {}) {
463
+ assertPathSafeAdapterName(adapter);
463
464
  if (scope.stateKey) {
464
465
  const explicit = sanitizeStateKey(scope.stateKey);
465
466
  const adapterScoped = scope.fleetId && explicit !== adapter && !explicit.startsWith(`${adapter}.`) ? `${adapter}.${explicit}` : explicit;
@@ -470,6 +471,11 @@ function stateKeyFor(adapter, scope = {}) {
470
471
  const fingerprint = scope.targetFingerprint ? `.${scope.targetFingerprint}` : "";
471
472
  return sanitizeStateKey(`${adapter}.${installationType}${fingerprint}`);
472
473
  }
474
+ function assertPathSafeAdapterName(adapter) {
475
+ if (!/^[a-z0-9][a-z0-9._-]*$/i.test(adapter)) {
476
+ throw new Error(`Adapter name '${adapter}' is not a canonical path-safe identifier.`);
477
+ }
478
+ }
473
479
  function installManifestPath(targetRoot, adapter, scope = {}) {
474
480
  return join(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.install-manifest.json`);
475
481
  }
@@ -2618,8 +2624,8 @@ async function recoverMutationRuntime(operationId, options = {}) {
2618
2624
  }
2619
2625
  mutation = GovernedMutation.activateExisting(receipt, baseline, lock);
2620
2626
  const [{ recoverPendingApply }, { readLinkedLocalApplyJournal: readLinkedLocalApplyJournal2, removeApplyJournal: removeApplyJournal2, localPathExists: localPathExists2 }] = await Promise.all([
2621
- import("./apply-7HJQQOCI.js"),
2622
- import("./transaction-IM5MZS4B.js")
2627
+ import("./apply-R5VHCIUN.js"),
2628
+ import("./transaction-ITXC7FFV.js")
2623
2629
  ]);
2624
2630
  let missingReservation = false;
2625
2631
  for (const entry of pending) {
@@ -2782,7 +2788,8 @@ function upsertRuntimeJournal(entries, link2, status) {
2782
2788
 
2783
2789
  // src/install/transaction.ts
2784
2790
  function applyLockPath(targetRoot, adapter, scope = {}) {
2785
- return join6(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-lock`);
2791
+ const installationType = scope.installationType ?? "local";
2792
+ return join6(metadataDir(targetRoot), `${stateKeyFor(adapter, { installationType })}.runtime-apply-lock`);
2786
2793
  }
2787
2794
  function applyJournalPath(targetRoot, adapter, scope = {}) {
2788
2795
  return join6(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-journal.json`);
@@ -2850,6 +2857,19 @@ async function readApplyJournal(targetRoot, adapter, transport = localTransport,
2850
2857
  if (!await transport.pathExists(path)) return void 0;
2851
2858
  return parseApplyJournal(JSON.parse(await transport.readFile(path)));
2852
2859
  }
2860
+ async function listApplyJournals(targetRoot, adapter, transport = localTransport, scope = {}) {
2861
+ const installationType = scope.installationType ?? "local";
2862
+ const suffix = ".apply-journal.json";
2863
+ const found = [];
2864
+ for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
2865
+ if (!fileName.endsWith(suffix)) continue;
2866
+ const path = join6(metadataDir(targetRoot), fileName);
2867
+ const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
2868
+ if (journal.adapter !== adapter || (journal.installationType ?? "local") !== installationType) continue;
2869
+ found.push({ path, journal });
2870
+ }
2871
+ return found.sort((a, b) => a.path.localeCompare(b.path));
2872
+ }
2853
2873
  async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, expectedTransportDescription) {
2854
2874
  const absolutePath = resolve9(path);
2855
2875
  const journal = parseApplyJournal(JSON.parse(await readFile7(absolutePath, "utf8")));
@@ -3129,6 +3149,7 @@ export {
3129
3149
  acquireApplyLock,
3130
3150
  writeApplyJournal,
3131
3151
  readApplyJournal,
3152
+ listApplyJournals,
3132
3153
  readLinkedLocalApplyJournal,
3133
3154
  removeApplyJournal,
3134
3155
  abortApplyJournal,
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  canonicalizeGraphLock,
10
10
  claudeInstructionBridgesAgents,
11
11
  combineMergeRemovals,
12
+ computeInstallManifestInventoryRevision,
12
13
  computeManifestRevision,
13
14
  computeTargetFingerprint,
14
15
  desiredManagedInstructionBlockHash,
@@ -30,7 +31,7 @@ import {
30
31
  uninstall,
31
32
  withManifestRevision,
32
33
  writeInstallManifest
33
- } from "./chunk-USHT7FGV.js";
34
+ } from "./chunk-AFVGRRZW.js";
34
35
  import {
35
36
  CURRENT_WORKSPACE_SCHEMA_VERSION,
36
37
  GovernedMutation,
@@ -38,6 +39,7 @@ import {
38
39
  acquireApplyLock,
39
40
  adapterSchema,
40
41
  adapterTargetSupport,
42
+ applyLockPath,
41
43
  artifactFormatSchema,
42
44
  artifactTypeSchema,
43
45
  assertGovernedRuntimeTransportSupported,
@@ -53,6 +55,7 @@ import {
53
55
  installRootForAdapterInstallationType,
54
56
  installRootForArtifacts,
55
57
  isCompositeWorkspaceProfile,
58
+ listApplyJournals,
56
59
  listMutationReceipts,
57
60
  loadAdapterConfig,
58
61
  localTransport,
@@ -86,7 +89,7 @@ import {
86
89
  workspaceExportsSchema,
87
90
  workspaceSelectionImportSchema,
88
91
  writeWorkspaceConfig
89
- } from "./chunk-HOYIIUL5.js";
92
+ } from "./chunk-BJEZXIXX.js";
90
93
  import {
91
94
  inferSourceDriverName
92
95
  } from "./chunk-EMDIG24I.js";
@@ -1449,6 +1452,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
1449
1452
  const installationType = resolveInstallationTypeForArtifacts(adapter, installableArtifacts.map((artifact) => artifact.type), requestedInstallationType);
1450
1453
  const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, installableArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
1451
1454
  await validateArtifactsForInstall(installableArtifacts, adapter, installationType);
1455
+ const runtimeStateRevision = await computeInstallManifestInventoryRevision(installRoot, adapter.name, transport);
1452
1456
  for (const artifact of installableArtifacts) {
1453
1457
  if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
1454
1458
  throw new Error(`Dependency-provided ${artifact.type} artifacts cannot be installed until per-subentry ownership exists: ${artifact.type}/${artifact.name}`);
@@ -1462,7 +1466,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
1462
1466
  }
1463
1467
  }
1464
1468
  await addProgrammaticOperations(desired, adapter, installRoot);
1465
- return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType });
1469
+ return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType, runtimeStateRevision });
1466
1470
  }
1467
1471
  async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
1468
1472
  const workspaceOwner = options.workspaceOwner;
@@ -1512,7 +1516,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1512
1516
  }
1513
1517
  }
1514
1518
  const incompleteMergeOwnership = existing2 && existing2.mergeStrategy && !("mergeCreatedDestination" in existing2 && existing2.mergeCreatedDestination === true) && !("mergeRemoval" in existing2 && hasMergeRemovalContent(existing2.mergeRemoval));
1515
- const adoptExisting = exists2 && (!existing2 || incompleteMergeOwnership) && options.forceConflict === true;
1519
+ const adoptExisting = exists2 && (incompleteMergeOwnership || !existing2 && options.forceConflict === true);
1516
1520
  let mergeRemoval;
1517
1521
  try {
1518
1522
  mergeRemoval = await mergeRemovalForInstall(op.sourcePath, op.mergeStrategy, currentContent, {
@@ -1544,7 +1548,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1544
1548
  mergeCreatedDestination: existing2?.mergeCreatedDestination,
1545
1549
  currentHash: currentHash2,
1546
1550
  manifestHash: existing2?.hash,
1547
- reason: existing2 ? "force repairing exact incomplete merge ownership" : "force adopting exact unmanaged merge contribution"
1551
+ reason: existing2 ? "repairing exact incomplete merge ownership" : "force adopting exact unmanaged merge contribution"
1548
1552
  });
1549
1553
  continue;
1550
1554
  }
@@ -1684,6 +1688,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1684
1688
  }
1685
1689
  for (const operation of operations) assertOperationContained(operation, targetRoot);
1686
1690
  operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
1691
+ const runtimeStateRevision = await computeInstallManifestInventoryRevision(targetRoot, adapter.name, transport);
1692
+ if (options.runtimeStateRevision && runtimeStateRevision !== options.runtimeStateRevision) {
1693
+ throw new Error("Runtime manifest inventory changed during planning; replan needed.");
1694
+ }
1687
1695
  return {
1688
1696
  adapter: adapter.name,
1689
1697
  installationType: options.installationType ?? defaultInstallationType,
@@ -1692,6 +1700,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1692
1700
  operations,
1693
1701
  hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
1694
1702
  baseRevision: options.baseRevision ?? manifest?.revision ?? null,
1703
+ runtimeStateRevision,
1695
1704
  migrationReport: migration.report,
1696
1705
  graphLockDigest: options.graphLockDigest,
1697
1706
  adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
@@ -2182,6 +2191,7 @@ function isPendingInstallOperation(operation) {
2182
2191
  // src/install/uninstall.ts
2183
2192
  import { join as join13 } from "path";
2184
2193
  async function createUninstallPlan(manifest, transport = localTransport) {
2194
+ const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
2185
2195
  const operations = [];
2186
2196
  for (const entry of manifest.entries) {
2187
2197
  const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
@@ -2237,6 +2247,9 @@ async function createUninstallPlan(manifest, transport = localTransport) {
2237
2247
  }
2238
2248
  }
2239
2249
  operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
2250
+ if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
2251
+ throw new Error("Runtime manifest inventory changed during uninstall planning; replan needed.");
2252
+ }
2240
2253
  return {
2241
2254
  adapter: manifest.adapter,
2242
2255
  installationType: "installationType" in manifest ? manifest.installationType : defaultInstallationType,
@@ -2245,10 +2258,12 @@ async function createUninstallPlan(manifest, transport = localTransport) {
2245
2258
  operations,
2246
2259
  hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
2247
2260
  baseRevision: manifest.revision,
2261
+ runtimeStateRevision,
2248
2262
  adapterCode: manifest.adapterCode
2249
2263
  };
2250
2264
  }
2251
2265
  async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport = localTransport, options = {}) {
2266
+ const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
2252
2267
  const installationType = "installationType" in manifest ? manifest.installationType : defaultInstallationType;
2253
2268
  const stateKey = "stateKey" in manifest ? manifest.stateKey : void 0;
2254
2269
  const desiredPlan = await createCombinedInstallPlan(remainingDesired, adapter, manifest.targetRoot, void 0, transport, { installationType, stateKey });
@@ -2338,6 +2353,9 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
2338
2353
  }
2339
2354
  }
2340
2355
  operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
2356
+ if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
2357
+ throw new Error("Runtime manifest inventory changed during ownership uninstall planning; replan needed.");
2358
+ }
2341
2359
  return {
2342
2360
  adapter: manifest.adapter,
2343
2361
  installationType,
@@ -2346,6 +2364,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
2346
2364
  operations,
2347
2365
  hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
2348
2366
  baseRevision: manifest.revision,
2367
+ runtimeStateRevision,
2349
2368
  adapterCode: manifest.adapterCode,
2350
2369
  graphLockDigest: options.graphLockDigest
2351
2370
  };
@@ -6914,7 +6933,7 @@ import { rm as rm7 } from "fs/promises";
6914
6933
  // src/lifecycle/source-plan.ts
6915
6934
  import { createHash as createHash8 } from "crypto";
6916
6935
  import { mkdir as mkdir14 } from "fs/promises";
6917
- import { dirname as dirname17, join as join33, resolve as resolve14 } from "path";
6936
+ import { dirname as dirname17, join as join33, resolve as resolve15 } from "path";
6918
6937
 
6919
6938
  // src/resolve/graph-diff.ts
6920
6939
  function diffGraphLocks(previous, next) {
@@ -7735,6 +7754,185 @@ async function createExactMcpRetirementPlan(desiredArtifacts, adapter, targetRoo
7735
7754
  };
7736
7755
  }
7737
7756
 
7757
+ // src/model/fleet.ts
7758
+ import { lstat as lstat2, readFile as readFile20, realpath } from "fs/promises";
7759
+ import { homedir as homedir7 } from "os";
7760
+ import { isAbsolute as isAbsolute2, relative as relative7, resolve as resolve14 } from "path";
7761
+ async function resolveWorkspaceScope(request = {}) {
7762
+ const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
7763
+ if (selected > 1) {
7764
+ throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
7765
+ }
7766
+ const globalRoot = resolve14(request.globalRoot ?? homedir7());
7767
+ if (request.user) {
7768
+ const config2 = await readWorkspaceConfig(globalRoot);
7769
+ assertNonFleetScope("user", config2);
7770
+ return { kind: "user", root: globalRoot, config: config2 };
7771
+ }
7772
+ if (request.fleet !== void 0) {
7773
+ const id = fleetIdSchema.parse(request.fleet);
7774
+ const registration = await showRegisteredFleet(id, { globalRoot });
7775
+ const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
7776
+ const config2 = await readRequiredConfig(root, `fleet '${id}'`);
7777
+ assertFleetContract(id, registration, config2);
7778
+ return { kind: "fleet", root, fleetId: id, config: config2 };
7779
+ }
7780
+ const cwd = resolve14(request.cwd ?? process.cwd());
7781
+ const discoveredRoot = await findExistingWorkspaceRoot(cwd);
7782
+ if (!discoveredRoot || discoveredRoot === globalRoot) {
7783
+ if (request.local === true && cwd !== globalRoot) {
7784
+ return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
7785
+ }
7786
+ throw missingScopeError(request.local === true ? "local" : "implicit local");
7787
+ }
7788
+ const config = await readRequiredConfig(discoveredRoot, "local");
7789
+ assertNonFleetScope("local", config);
7790
+ return { kind: "local", root: discoveredRoot, config };
7791
+ }
7792
+ async function registerFleet(request) {
7793
+ const id = fleetIdSchema.parse(request.id);
7794
+ const requiredPackages = sortedUnique4(request.requiredPackages);
7795
+ if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
7796
+ const globalRoot = resolve14(request.globalRoot ?? homedir7());
7797
+ const home = await readWorkspaceConfig(globalRoot);
7798
+ if (supportsFleetConfig(home) && home.fleetId) {
7799
+ throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
7800
+ }
7801
+ const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
7802
+ if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
7803
+ const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
7804
+ const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
7805
+ if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
7806
+ if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
7807
+ const target = await readRequiredConfig(root, `fleet '${id}'`);
7808
+ const registration = registeredFleetSchema.parse({ root, requiredPackages });
7809
+ assertFleetContract(id, registration, target);
7810
+ const upgraded = workspaceConfigSchema.parse({
7811
+ ...home,
7812
+ schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
7813
+ exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
7814
+ fleets: { ...existing, [id]: registration }
7815
+ });
7816
+ const configPath = globalWorkspaceConfigPath(globalRoot);
7817
+ declareMutationPath(configPath);
7818
+ await writeJsonAtomic(configPath, upgraded);
7819
+ return { id, ...registration };
7820
+ }
7821
+ async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
7822
+ const canonical = /* @__PURE__ */ new Map();
7823
+ const normalized = {};
7824
+ for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
7825
+ const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
7826
+ if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
7827
+ const incumbent = canonical.get(root);
7828
+ if (incumbent) {
7829
+ throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
7830
+ }
7831
+ canonical.set(root, id);
7832
+ normalized[id] = registeredFleetSchema.parse({ ...registration, root });
7833
+ }
7834
+ return normalized;
7835
+ }
7836
+ async function listRegisteredFleets(options = {}) {
7837
+ const globalRoot = resolve14(options.globalRoot ?? homedir7());
7838
+ const home = await readWorkspaceConfig(globalRoot);
7839
+ if (!supportsFleetConfig(home)) return [];
7840
+ return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
7841
+ }
7842
+ async function showRegisteredFleet(idInput, options = {}) {
7843
+ const id = fleetIdSchema.parse(idInput);
7844
+ const fleets = await listRegisteredFleets(options);
7845
+ const registration = fleets.find((candidate) => candidate.id === id);
7846
+ if (!registration) {
7847
+ throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
7848
+ }
7849
+ return registration;
7850
+ }
7851
+ async function resolveWorkspaceOwnershipScope(workspaceRootInput, options = {}) {
7852
+ const workspaceRoot = await canonicalizeDirectory(resolve14(workspaceRootInput), "Workspace ownership root");
7853
+ if (options.fleetId) {
7854
+ return { root: workspaceRoot, fleetId: fleetIdSchema.parse(options.fleetId) };
7855
+ }
7856
+ const syntacticallyContaining = (await listRegisteredFleets({ globalRoot: options.globalRoot })).filter((fleet2) => containsPath(resolve14(fleet2.root), workspaceRoot));
7857
+ const registered = await Promise.all(syntacticallyContaining.map(async (fleet2) => ({
7858
+ ...fleet2,
7859
+ root: await canonicalizeDirectory(fleet2.root, `Registered fleet '${fleet2.id}' root`)
7860
+ })));
7861
+ const containing = registered.filter((fleet2) => containsPath(fleet2.root, workspaceRoot));
7862
+ if (containing.length > 1) {
7863
+ throw new Error(
7864
+ `Workspace ${workspaceRoot} is contained by multiple registered fleets: ` + containing.map((fleet2) => `'${fleet2.id}' at ${fleet2.root}`).join(", ") + ". Resolve the overlapping registrations before planning ownership."
7865
+ );
7866
+ }
7867
+ const fleet = containing[0];
7868
+ if (fleet) assertFleetContract(fleet.id, fleet, await readRequiredConfig(fleet.root, `fleet '${fleet.id}'`));
7869
+ return fleet ? { root: fleet.root, fleetId: fleet.id } : { root: workspaceRoot };
7870
+ }
7871
+ async function readRequiredConfig(root, label) {
7872
+ const path = workspaceConfigPath(root);
7873
+ if (!await pathExists(path)) throw missingScopeError(label);
7874
+ try {
7875
+ return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
7876
+ } catch (error) {
7877
+ throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
7878
+ }
7879
+ }
7880
+ function assertFleetContract(id, registration, config) {
7881
+ if (!supportsFleetConfig(config)) {
7882
+ throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
7883
+ }
7884
+ if (config.fleetId !== id) {
7885
+ throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
7886
+ }
7887
+ if (Object.keys(config.fleets).length > 0) {
7888
+ throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
7889
+ }
7890
+ const names = new Set(config.packages.map((pkg) => pkg.name));
7891
+ for (const packageName of registration.requiredPackages) {
7892
+ if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
7893
+ }
7894
+ }
7895
+ async function assertCanonicalDirectory(input, label) {
7896
+ const canonical = await canonicalizeDirectory(input, label);
7897
+ const normalized = resolve14(input);
7898
+ const stats = await lstat2(input);
7899
+ if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
7900
+ throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
7901
+ }
7902
+ return canonical;
7903
+ }
7904
+ async function canonicalizeDirectory(input, label) {
7905
+ if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
7906
+ let canonical;
7907
+ try {
7908
+ canonical = await realpath(input);
7909
+ } catch {
7910
+ throw new Error(`${label} does not exist: ${input}`);
7911
+ }
7912
+ const stats = await lstat2(canonical);
7913
+ if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
7914
+ return canonical;
7915
+ }
7916
+ function missingScopeError(label) {
7917
+ return new Error(
7918
+ `Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
7919
+ );
7920
+ }
7921
+ function assertNonFleetScope(kind, config) {
7922
+ if (supportsFleetConfig(config) && config.fleetId) {
7923
+ throw new Error(
7924
+ `The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
7925
+ );
7926
+ }
7927
+ }
7928
+ function sortedUnique4(values) {
7929
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
7930
+ }
7931
+ function containsPath(parent, candidate) {
7932
+ const path = relative7(parent, candidate);
7933
+ return path === "" || !path.startsWith("..") && !isAbsolute2(path);
7934
+ }
7935
+
7738
7936
  // src/lifecycle/source-plan.ts
7739
7937
  async function createGraphSourcePlan(options) {
7740
7938
  if (options.roots.length === 0) {
@@ -7813,7 +8011,11 @@ async function createGraphSourcePlan(options) {
7813
8011
  const graphLockDigest = digestGraphLock(bundle.graphLock);
7814
8012
  const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
7815
8013
  const manifest = await readInstallManifest(resolvedInstallRoot, options.adapter.name, transport, { installationType: resolvedInstallationType, stateKey });
7816
- const workspaceOwner = workspaceOwnerForRoot(workspaceRoot, options.fleetId);
8014
+ const ownership = await resolveWorkspaceOwnershipScope(workspaceRoot, {
8015
+ fleetId: options.fleetId,
8016
+ globalRoot: options.globalRoot
8017
+ });
8018
+ const workspaceOwner = workspaceOwnerForRoot(ownership.root, ownership.fleetId);
7817
8019
  const plan = options.retireExactMcp ? await createExactMcpRetirementPlan(
7818
8020
  desiredArtifacts,
7819
8021
  options.adapter,
@@ -7847,7 +8049,8 @@ async function createGraphSourcePlan(options) {
7847
8049
  workspaceOwner,
7848
8050
  globalRoot: options.globalRoot,
7849
8051
  stateKey,
7850
- plannedPaths: plan.operations.map((operation) => operation.relativeDestPath)
8052
+ plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
8053
+ plannedOperations: plan.operations
7851
8054
  });
7852
8055
  }
7853
8056
  return {
@@ -7874,7 +8077,8 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
7874
8077
  workspaceOwner: options.workspaceOwner,
7875
8078
  globalRoot: options.globalRoot,
7876
8079
  stateKey: plan.stateKey,
7877
- plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath)
8080
+ plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
8081
+ plannedOperations: plan.operations
7878
8082
  });
7879
8083
  }
7880
8084
  function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
@@ -7924,8 +8128,15 @@ var workspaceOwnerPrefix2 = "workspace-root:";
7924
8128
  async function assertNoForeignWorkspaceState(check) {
7925
8129
  const planned = new Set(check.plannedPaths);
7926
8130
  if (planned.size === 0) return;
8131
+ const plannedOperations = /* @__PURE__ */ new Map();
8132
+ for (const operation of check.plannedOperations) {
8133
+ if (!planned.has(operation.relativeDestPath)) continue;
8134
+ const operations = plannedOperations.get(operation.relativeDestPath) ?? [];
8135
+ operations.push(operation);
8136
+ plannedOperations.set(operation.relativeDestPath, operations);
8137
+ }
7927
8138
  const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
7928
- const root = resolve14(check.workspaceRoot);
8139
+ const root = resolve15(check.workspaceRoot);
7929
8140
  const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
7930
8141
  const foreign = [];
7931
8142
  for (const entry of manifests) {
@@ -7937,7 +8148,15 @@ async function assertNoForeignWorkspaceState(check) {
7937
8148
  if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
7938
8149
  const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
7939
8150
  bucket.entryCount += 1;
7940
- if (planned.has(manifestEntry.path)) bucket.collidingPaths.push(manifestEntry.path);
8151
+ if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
8152
+ check,
8153
+ manifestEntry.path,
8154
+ manifestEntry.mergeStrategy,
8155
+ manifestEntry.mergeRemoval,
8156
+ plannedOperations.get(manifestEntry.path) ?? []
8157
+ )) {
8158
+ bucket.collidingPaths.push(manifestEntry.path);
8159
+ }
7941
8160
  byOwner.set(owner, bucket);
7942
8161
  }
7943
8162
  for (const [owner, bucket] of byOwner) {
@@ -7959,6 +8178,34 @@ async function assertNoForeignWorkspaceState(check) {
7959
8178
  parseWorkspaceOwner(check.workspaceOwner)?.fleetId ? "Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet." : "Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."
7960
8179
  ].join("\n"));
7961
8180
  }
8181
+ async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
8182
+ if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
8183
+ const operation = operations[0];
8184
+ if (operation.mergeStrategy !== foreignStrategy || !hasMergeRemovalContent(operation.mergeRemoval)) return false;
8185
+ if (!disjointMcpMergeContributions(foreignStrategy, foreignRemoval, operation.mergeRemoval)) return false;
8186
+ try {
8187
+ const content = await check.transport.readFile(join33(check.installRoot, relativePath));
8188
+ assertExactMergeContribution(foreignRemoval, foreignStrategy, content);
8189
+ assertExactMergeContribution(operation.mergeRemoval, foreignStrategy, content);
8190
+ return true;
8191
+ } catch {
8192
+ return false;
8193
+ }
8194
+ }
8195
+ function disjointMcpMergeContributions(strategy, left, right) {
8196
+ const leftNames = mcpServerNames(strategy, left);
8197
+ const rightNames = mcpServerNames(strategy, right);
8198
+ if (!leftNames || !rightNames) return false;
8199
+ return [...leftNames].every((name) => !rightNames.has(name));
8200
+ }
8201
+ function mcpServerNames(strategy, removal) {
8202
+ if (strategy !== "codex-toml-mcp" && strategy !== "json-deep") return void 0;
8203
+ const keys = Object.keys(removal);
8204
+ if (strategy === "json-deep" && (keys.length !== 1 || keys[0] !== "mcpServers")) return void 0;
8205
+ const servers = removal.mcpServers ?? (strategy === "codex-toml-mcp" ? removal : void 0);
8206
+ if (!servers || Array.isArray(servers) || typeof servers !== "object") return void 0;
8207
+ return new Set(Object.keys(servers));
8208
+ }
7962
8209
  function globalConfigRoot(globalRoot) {
7963
8210
  return dirname17(dirname17(globalWorkspaceConfigPath(globalRoot)));
7964
8211
  }
@@ -8031,17 +8278,17 @@ function selectionImportKey2(selection) {
8031
8278
  exportHash: selection.exportHash,
8032
8279
  exportName: selection.exportName,
8033
8280
  extends: selection.extends,
8034
- inherited: sortedUnique4(selection.inherited),
8035
- additions: sortedUnique4(selection.additions),
8036
- exclusions: sortedUnique4(selection.exclusions),
8037
- effective: sortedUnique4(selection.effective)
8281
+ inherited: sortedUnique5(selection.inherited),
8282
+ additions: sortedUnique5(selection.additions),
8283
+ exclusions: sortedUnique5(selection.exclusions),
8284
+ effective: sortedUnique5(selection.effective)
8038
8285
  });
8039
8286
  }
8040
- function sortedUnique4(values) {
8287
+ function sortedUnique5(values) {
8041
8288
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
8042
8289
  }
8043
8290
  function selectorKey(selectors) {
8044
- return sortedUnique4(selectors).join("\0");
8291
+ return sortedUnique5(selectors).join("\0");
8045
8292
  }
8046
8293
  async function assertTrusted(sources, options) {
8047
8294
  if (sources.length === 0) return;
@@ -8102,7 +8349,7 @@ function targetLabel(target) {
8102
8349
  }
8103
8350
 
8104
8351
  // src/runtime/target.ts
8105
- import { basename as basename18, dirname as dirname18, join as join34, resolve as resolve15 } from "path";
8352
+ import { basename as basename18, dirname as dirname18, join as join34, resolve as resolve16 } from "path";
8106
8353
  var runtimeMarkers = [
8107
8354
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
8108
8355
  { adapter: "claude", dirs: [".claude"] },
@@ -8111,9 +8358,9 @@ var runtimeMarkers = [
8111
8358
  { adapter: "copilot", dirs: [".github"] }
8112
8359
  ];
8113
8360
  async function resolveRuntimeTarget(request = {}) {
8114
- const cwd = resolve15(request.cwd ?? process.cwd());
8361
+ const cwd = resolve16(request.cwd ?? process.cwd());
8115
8362
  if (request.targetRoot) {
8116
- const targetRoot = resolve15(request.targetRoot);
8363
+ const targetRoot = resolve16(request.targetRoot);
8117
8364
  return {
8118
8365
  adapter: request.adapter ?? "openclaw",
8119
8366
  installationType: request.installationType,
@@ -8145,7 +8392,7 @@ async function resolveRuntimeTarget(request = {}) {
8145
8392
  async function resolveAllRuntimeTargets(request = {}) {
8146
8393
  if (request.targetRoot) return [await resolveRuntimeTarget(request)];
8147
8394
  if (request.agent) return [await resolveRuntimeTarget(request)];
8148
- const cwd = resolve15(request.cwd ?? process.cwd());
8395
+ const cwd = resolve16(request.cwd ?? process.cwd());
8149
8396
  const workspaceRoot = await findWorkspaceRoot(cwd);
8150
8397
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
8151
8398
  const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
@@ -8155,8 +8402,8 @@ async function resolveAllRuntimeTargets(request = {}) {
8155
8402
  return targets;
8156
8403
  }
8157
8404
  async function resolveProfileRuntimeTargets(request) {
8158
- const cwd = resolve15(request.cwd ?? process.cwd());
8159
- const workspaceRoot = request.targetRoot ? resolve15(request.targetRoot) : await findWorkspaceRoot(cwd);
8405
+ const cwd = resolve16(request.cwd ?? process.cwd());
8406
+ const workspaceRoot = request.targetRoot ? resolve16(request.targetRoot) : await findWorkspaceRoot(cwd);
8160
8407
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
8161
8408
  const profile = config.profiles[request.profile];
8162
8409
  if (!profile) {
@@ -8222,7 +8469,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
8222
8469
  return unique[0];
8223
8470
  }
8224
8471
  async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
8225
- const root = resolve15(cwd);
8472
+ const root = resolve16(cwd);
8226
8473
  const matches = [];
8227
8474
  for (const marker of runtimeMarkers) {
8228
8475
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
@@ -8271,7 +8518,7 @@ function dedupeTargets(matches) {
8271
8518
  return [...byKey.values()];
8272
8519
  }
8273
8520
  function runtimeScanRoot(request) {
8274
- const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
8521
+ const root = resolve16(request.targetRoot ?? request.cwd ?? process.cwd());
8275
8522
  if (request.targetRoot) return root;
8276
8523
  return runtimeMarkers.some((marker) => marker.dirs.includes(basename18(root))) ? dirname18(root) : root;
8277
8524
  }
@@ -8581,8 +8828,8 @@ function shellQuoteArg(value) {
8581
8828
  }
8582
8829
 
8583
8830
  // src/cli/update-check.ts
8584
- import { mkdir as mkdir15, readFile as readFile20, writeFile as writeFile11 } from "fs/promises";
8585
- import { homedir as homedir7 } from "os";
8831
+ import { mkdir as mkdir15, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
8832
+ import { homedir as homedir8 } from "os";
8586
8833
  import { dirname as dirname19, join as join35 } from "path";
8587
8834
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
8588
8835
  var DEFAULT_TIMEOUT_MS = 300;
@@ -8591,7 +8838,7 @@ async function maybeCheckForUpdate(options) {
8591
8838
  if (isDisabled(options)) return;
8592
8839
  const now = options.now?.() ?? /* @__PURE__ */ new Date();
8593
8840
  const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
8594
- const cachePath = options.cachePath ?? join35(homedir7(), ".agentwheel", "update-check.json");
8841
+ const cachePath = options.cachePath ?? join35(homedir8(), ".agentwheel", "update-check.json");
8595
8842
  try {
8596
8843
  const cached = await readCache(cachePath);
8597
8844
  if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
@@ -8628,7 +8875,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
8628
8875
  }
8629
8876
  async function readCache(path) {
8630
8877
  try {
8631
- const parsed = JSON.parse(await readFile20(path, "utf8"));
8878
+ const parsed = JSON.parse(await readFile21(path, "utf8"));
8632
8879
  if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
8633
8880
  return { checkedAt: parsed.checkedAt, latest: parsed.latest };
8634
8881
  } catch {
@@ -8660,9 +8907,9 @@ function normalizeVersion(version) {
8660
8907
 
8661
8908
  // src/model/package-validate.ts
8662
8909
  import { stat as stat12 } from "fs/promises";
8663
- import { resolve as resolve16 } from "path";
8910
+ import { resolve as resolve17 } from "path";
8664
8911
  async function validatePackage(root) {
8665
- const packageRoot = resolve16(root);
8912
+ const packageRoot = resolve17(root);
8666
8913
  const findings = [];
8667
8914
  const manifestPath = await findPackageManifestPath(packageRoot);
8668
8915
  if (!manifestPath) {
@@ -8763,7 +9010,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
8763
9010
  try {
8764
9011
  validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
8765
9012
  if (isCrossPackageSelector(selector)) return;
8766
- const full = resolve16(packageRoot, selector);
9013
+ const full = resolve17(packageRoot, selector);
8767
9014
  if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
8768
9015
  findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
8769
9016
  return;
@@ -8816,11 +9063,11 @@ function isCrossPackageSelector(value) {
8816
9063
  }
8817
9064
 
8818
9065
  // src/model/package-migrate.ts
8819
- import { readFile as readFile21, rename as rename3, writeFile as writeFile12 } from "fs/promises";
8820
- import { join as join37, resolve as resolve17 } from "path";
9066
+ import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
9067
+ import { join as join37, resolve as resolve18 } from "path";
8821
9068
  import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
8822
9069
  async function migratePackageManifest(root) {
8823
- const packageRoot = resolve17(root);
9070
+ const packageRoot = resolve18(root);
8824
9071
  for (const name of openPackManifestNames) {
8825
9072
  const path = join37(packageRoot, name);
8826
9073
  if (await pathExists(path)) {
@@ -8834,7 +9081,7 @@ async function migratePackageManifest(root) {
8834
9081
  const from = join37(packageRoot, legacyName);
8835
9082
  const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
8836
9083
  const to = join37(packageRoot, toName);
8837
- const content = await readFile21(from, "utf8");
9084
+ const content = await readFile22(from, "utf8");
8838
9085
  const updated = updateSchemaVersion(content);
8839
9086
  declareMutationPath(from);
8840
9087
  declareMutationPath(to);
@@ -8882,7 +9129,7 @@ function resolveCliVersion() {
8882
9129
  }
8883
9130
 
8884
9131
  // src/lifecycle/ownership.ts
8885
- import { resolve as resolve18 } from "path";
9132
+ import { resolve as resolve19 } from "path";
8886
9133
  async function planArtifactOwnershipHandoff(request) {
8887
9134
  return validateOwnershipHandoff(request, request.transport ?? localTransport);
8888
9135
  }
@@ -8895,7 +9142,9 @@ async function applyArtifactOwnershipHandoff(request) {
8895
9142
  const scope = { installationType: request.installationType, stateKey: request.stateKey };
8896
9143
  const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, scope);
8897
9144
  try {
8898
- if (await readApplyJournal(request.targetRoot, request.adapter, transport, scope)) {
9145
+ if ((await listApplyJournals(request.targetRoot, request.adapter, transport, {
9146
+ installationType: request.installationType
9147
+ })).length > 0) {
8899
9148
  throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
8900
9149
  }
8901
9150
  const plan = await validateOwnershipHandoff(request, transport);
@@ -9001,8 +9250,8 @@ function containedArtifactPath(targetRoot, relativePath) {
9001
9250
  if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
9002
9251
  throw new Error(`Unsafe managed artifact path: ${relativePath}`);
9003
9252
  }
9004
- const root = resolve18(targetRoot);
9005
- const candidate = resolve18(root, relativePath);
9253
+ const root = resolve19(targetRoot);
9254
+ const candidate = resolve19(root, relativePath);
9006
9255
  if (candidate !== root && !candidate.startsWith(`${root}/`)) {
9007
9256
  throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
9008
9257
  }
@@ -9035,8 +9284,8 @@ function assertManifestIdentity(manifest, request) {
9035
9284
 
9036
9285
  // src/version/policy.ts
9037
9286
  import { execFile as execFile3 } from "child_process";
9038
- import { readFile as readFile22 } from "fs/promises";
9039
- import { join as join39, resolve as resolve19 } from "path";
9287
+ import { readFile as readFile23 } from "fs/promises";
9288
+ import { join as join39, resolve as resolve20 } from "path";
9040
9289
  import { promisify as promisify3 } from "util";
9041
9290
  import { parse as parseJsonc } from "jsonc-parser";
9042
9291
  import { z as z5 } from "zod";
@@ -9130,7 +9379,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
9130
9379
  if (tagged.length > 0) return tagged;
9131
9380
  }
9132
9381
  if (driverName === "local") {
9133
- const root = resolve19(workspaceRoot, pkg.source);
9382
+ const root = resolve20(workspaceRoot, pkg.source);
9134
9383
  const manifest2 = await readPackageManifest(root);
9135
9384
  const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
9136
9385
  try {
@@ -9244,7 +9493,7 @@ function versionCachePath(workspaceRoot) {
9244
9493
  async function readVersionCache(path) {
9245
9494
  if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
9246
9495
  try {
9247
- return versionCacheSchema.parse(JSON.parse(await readFile22(path, "utf8")));
9496
+ return versionCacheSchema.parse(JSON.parse(await readFile23(path, "utf8")));
9248
9497
  } catch {
9249
9498
  return { schemaVersion: 1, sources: {} };
9250
9499
  }
@@ -9252,8 +9501,8 @@ async function readVersionCache(path) {
9252
9501
 
9253
9502
  // src/profile/members.ts
9254
9503
  import { execFile as execFile4 } from "child_process";
9255
- import { readFile as readFile23 } from "fs/promises";
9256
- import { join as join40, resolve as resolve20 } from "path";
9504
+ import { readFile as readFile24 } from "fs/promises";
9505
+ import { join as join40, resolve as resolve21 } from "path";
9257
9506
  import { promisify as promisify4 } from "util";
9258
9507
  import { z as z7 } from "zod";
9259
9508
 
@@ -9428,7 +9677,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
9428
9677
  let stderr = "";
9429
9678
  try {
9430
9679
  if (member.transport === "local") {
9431
- const workspace = resolve20(parentWorkspace, member.workspace);
9680
+ const workspace = resolve21(parentWorkspace, member.workspace);
9432
9681
  const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
9433
9682
  cwd: workspace,
9434
9683
  env: env2,
@@ -9474,7 +9723,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
9474
9723
  process.execPath,
9475
9724
  [process.argv[1], "--no-update-check", ...args],
9476
9725
  {
9477
- cwd: resolve20(parentWorkspace, member.workspace),
9726
+ cwd: resolve21(parentWorkspace, member.workspace),
9478
9727
  env: env2,
9479
9728
  maxBuffer: 20 * 1024 * 1024
9480
9729
  }
@@ -9554,7 +9803,7 @@ function memberCachePath(workspaceRoot, profileName, memberId) {
9554
9803
  async function readMemberCache(path) {
9555
9804
  if (!await pathExists(path)) return void 0;
9556
9805
  try {
9557
- return memberCacheSchema.parse(JSON.parse(await readFile23(path, "utf8")));
9806
+ return memberCacheSchema.parse(JSON.parse(await readFile24(path, "utf8")));
9558
9807
  } catch {
9559
9808
  return void 0;
9560
9809
  }
@@ -9575,7 +9824,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
9575
9824
  }
9576
9825
  }
9577
9826
  function compositeKey(workspaceRoot, profileName) {
9578
- return `${resolve20(workspaceRoot)}#${profileName}`;
9827
+ return `${resolve21(workspaceRoot)}#${profileName}`;
9579
9828
  }
9580
9829
 
9581
9830
  // src/status/repository.ts
@@ -9623,8 +9872,8 @@ function valueAfter(lines, prefix) {
9623
9872
 
9624
9873
  // src/catalogue/client.ts
9625
9874
  import { createHash as createHash9 } from "crypto";
9626
- import { readFile as readFile24, rm as rm8 } from "fs/promises";
9627
- import { homedir as homedir8 } from "os";
9875
+ import { readFile as readFile25, rm as rm8 } from "fs/promises";
9876
+ import { homedir as homedir9 } from "os";
9628
9877
  import { join as join41 } from "path";
9629
9878
 
9630
9879
  // src/model/catalogue.ts
@@ -9841,7 +10090,7 @@ var CatalogueClient = class {
9841
10090
  async readCache() {
9842
10091
  if (!await pathExists(this.cachePath)) return void 0;
9843
10092
  try {
9844
- const value = JSON.parse(await readFile24(this.cachePath, "utf8"));
10093
+ const value = JSON.parse(await readFile25(this.cachePath, "utf8"));
9845
10094
  const envelope = catalogueCacheEnvelopeSchema.parse(value);
9846
10095
  if (envelope.contentHash) {
9847
10096
  const contentHash = catalogueContentHash(envelope.enriched, envelope.vercel);
@@ -9902,7 +10151,7 @@ var CatalogueClient = class {
9902
10151
  }
9903
10152
  };
9904
10153
  function defaultCatalogueCachePath() {
9905
- return join41(homedir8(), ".agentwheel", "catalogue-cache.json");
10154
+ return join41(homedir9(), ".agentwheel", "catalogue-cache.json");
9906
10155
  }
9907
10156
  function sameSources2(a, b) {
9908
10157
  return a.length === b.length && a.every((source, index) => source === b[index]);
@@ -10271,7 +10520,7 @@ function includesToken(normalizedText, token) {
10271
10520
 
10272
10521
  // src/semantic/index.ts
10273
10522
  import { createHash as createHash10 } from "crypto";
10274
- import { homedir as homedir9 } from "os";
10523
+ import { homedir as homedir10 } from "os";
10275
10524
  import { join as join42 } from "path";
10276
10525
  import { env, pipeline } from "@huggingface/transformers";
10277
10526
  var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
@@ -10366,7 +10615,7 @@ var SemanticSearchClient = class {
10366
10615
  }
10367
10616
  };
10368
10617
  async function embedQuery(query) {
10369
- env.cacheDir = join42(homedir9(), ".agentwheel", "semantic-models");
10618
+ env.cacheDir = join42(homedir10(), ".agentwheel", "semantic-models");
10370
10619
  const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
10371
10620
  revision: CONTRACT.model.revision,
10372
10621
  dtype: CONTRACT.model.dtype,
@@ -10451,7 +10700,7 @@ function ensureTrailingSlash(value) {
10451
10700
 
10452
10701
  // src/trial/skill.ts
10453
10702
  import { createHash as createHash11 } from "crypto";
10454
- import { readFile as readFile25, stat as stat13 } from "fs/promises";
10703
+ import { readFile as readFile26, stat as stat13 } from "fs/promises";
10455
10704
  import { join as join43 } from "path";
10456
10705
  import { parse as parseYaml2 } from "yaml";
10457
10706
  var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
@@ -10469,7 +10718,7 @@ async function createSkillTrial(driver, resolved, selectors) {
10469
10718
  if (info.size > MAX_TRIAL_SKILL_BYTES) {
10470
10719
  throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
10471
10720
  }
10472
- const content = await readFile25(path, "utf8");
10721
+ const content = await readFile26(path, "utf8");
10473
10722
  const frontmatter = readSkillFrontmatter(content, artifact);
10474
10723
  return {
10475
10724
  schemaVersion: 1,
@@ -10498,166 +10747,11 @@ function readSkillFrontmatter(content, artifact) {
10498
10747
  return { name: value.name, description: value.description };
10499
10748
  }
10500
10749
 
10501
- // src/model/fleet.ts
10502
- import { lstat as lstat2, readFile as readFile26, realpath } from "fs/promises";
10503
- import { homedir as homedir10 } from "os";
10504
- import { isAbsolute as isAbsolute2, resolve as resolve21 } from "path";
10505
- async function resolveWorkspaceScope(request = {}) {
10506
- const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
10507
- if (selected > 1) {
10508
- throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
10509
- }
10510
- const globalRoot = resolve21(request.globalRoot ?? homedir10());
10511
- if (request.user) {
10512
- const config2 = await readWorkspaceConfig(globalRoot);
10513
- assertNonFleetScope("user", config2);
10514
- return { kind: "user", root: globalRoot, config: config2 };
10515
- }
10516
- if (request.fleet !== void 0) {
10517
- const id = fleetIdSchema.parse(request.fleet);
10518
- const registration = await showRegisteredFleet(id, { globalRoot });
10519
- const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
10520
- const config2 = await readRequiredConfig(root, `fleet '${id}'`);
10521
- assertFleetContract(id, registration, config2);
10522
- return { kind: "fleet", root, fleetId: id, config: config2 };
10523
- }
10524
- const cwd = resolve21(request.cwd ?? process.cwd());
10525
- const discoveredRoot = await findExistingWorkspaceRoot(cwd);
10526
- if (!discoveredRoot || discoveredRoot === globalRoot) {
10527
- if (request.local === true && cwd !== globalRoot) {
10528
- return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
10529
- }
10530
- throw missingScopeError(request.local === true ? "local" : "implicit local");
10531
- }
10532
- const config = await readRequiredConfig(discoveredRoot, "local");
10533
- assertNonFleetScope("local", config);
10534
- return { kind: "local", root: discoveredRoot, config };
10535
- }
10536
- async function registerFleet(request) {
10537
- const id = fleetIdSchema.parse(request.id);
10538
- const requiredPackages = sortedUnique5(request.requiredPackages);
10539
- if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
10540
- const globalRoot = resolve21(request.globalRoot ?? homedir10());
10541
- const home = await readWorkspaceConfig(globalRoot);
10542
- if (supportsFleetConfig(home) && home.fleetId) {
10543
- throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
10544
- }
10545
- const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
10546
- if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
10547
- const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
10548
- const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
10549
- if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
10550
- if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
10551
- const target = await readRequiredConfig(root, `fleet '${id}'`);
10552
- const registration = registeredFleetSchema.parse({ root, requiredPackages });
10553
- assertFleetContract(id, registration, target);
10554
- const upgraded = workspaceConfigSchema.parse({
10555
- ...home,
10556
- schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
10557
- exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
10558
- fleets: { ...existing, [id]: registration }
10559
- });
10560
- const configPath = globalWorkspaceConfigPath(globalRoot);
10561
- declareMutationPath(configPath);
10562
- await writeJsonAtomic(configPath, upgraded);
10563
- return { id, ...registration };
10564
- }
10565
- async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
10566
- const canonical = /* @__PURE__ */ new Map();
10567
- const normalized = {};
10568
- for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
10569
- const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
10570
- if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
10571
- const incumbent = canonical.get(root);
10572
- if (incumbent) {
10573
- throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
10574
- }
10575
- canonical.set(root, id);
10576
- normalized[id] = registeredFleetSchema.parse({ ...registration, root });
10577
- }
10578
- return normalized;
10579
- }
10580
- async function listRegisteredFleets(options = {}) {
10581
- const globalRoot = resolve21(options.globalRoot ?? homedir10());
10582
- const home = await readWorkspaceConfig(globalRoot);
10583
- if (!supportsFleetConfig(home)) return [];
10584
- return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
10585
- }
10586
- async function showRegisteredFleet(idInput, options = {}) {
10587
- const id = fleetIdSchema.parse(idInput);
10588
- const fleets = await listRegisteredFleets(options);
10589
- const registration = fleets.find((candidate) => candidate.id === id);
10590
- if (!registration) {
10591
- throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
10592
- }
10593
- return registration;
10594
- }
10595
- async function readRequiredConfig(root, label) {
10596
- const path = workspaceConfigPath(root);
10597
- if (!await pathExists(path)) throw missingScopeError(label);
10598
- try {
10599
- return workspaceConfigSchema.parse(JSON.parse(await readFile26(path, "utf8")));
10600
- } catch (error) {
10601
- throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
10602
- }
10603
- }
10604
- function assertFleetContract(id, registration, config) {
10605
- if (!supportsFleetConfig(config)) {
10606
- throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
10607
- }
10608
- if (config.fleetId !== id) {
10609
- throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
10610
- }
10611
- if (Object.keys(config.fleets).length > 0) {
10612
- throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
10613
- }
10614
- const names = new Set(config.packages.map((pkg) => pkg.name));
10615
- for (const packageName of registration.requiredPackages) {
10616
- if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
10617
- }
10618
- }
10619
- async function assertCanonicalDirectory(input, label) {
10620
- const canonical = await canonicalizeDirectory(input, label);
10621
- const normalized = resolve21(input);
10622
- const stats = await lstat2(input);
10623
- if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
10624
- throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
10625
- }
10626
- return canonical;
10627
- }
10628
- async function canonicalizeDirectory(input, label) {
10629
- if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
10630
- let canonical;
10631
- try {
10632
- canonical = await realpath(input);
10633
- } catch {
10634
- throw new Error(`${label} does not exist: ${input}`);
10635
- }
10636
- const stats = await lstat2(canonical);
10637
- if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
10638
- return canonical;
10639
- }
10640
- function missingScopeError(label) {
10641
- return new Error(
10642
- `Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
10643
- );
10644
- }
10645
- function assertNonFleetScope(kind, config) {
10646
- if (supportsFleetConfig(config) && config.fleetId) {
10647
- throw new Error(
10648
- `The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
10649
- );
10650
- }
10651
- }
10652
- function sortedUnique5(values) {
10653
- return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
10654
- }
10655
-
10656
10750
  // src/lifecycle/fleet-normalize.ts
10657
10751
  import { createHash as createHash12 } from "crypto";
10658
10752
  import { readFile as readFile27, readdir as readdir9, rm as rm9 } from "fs/promises";
10659
10753
  import { homedir as homedir11 } from "os";
10660
- import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative7, resolve as resolve22 } from "path";
10754
+ import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve22 } from "path";
10661
10755
  async function planFleetNormalization(request) {
10662
10756
  const normalizedRequest = normalizeRequest(request);
10663
10757
  const destinationScope = await resolveWorkspaceScope({
@@ -11403,7 +11497,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
11403
11497
  if (!path.endsWith(".graph-lock.json")) continue;
11404
11498
  const lock = await readGraphLock(path);
11405
11499
  if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
11406
- const parts = relative7(root, path).split(/[\\/]/);
11500
+ const parts = relative8(root, path).split(/[\\/]/);
11407
11501
  if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
11408
11502
  const [targetKey2, adapter, fileName] = parts;
11409
11503
  if (targetKeys && !targetKeys.has(targetKey2)) continue;
@@ -11976,6 +12070,7 @@ async function acquireJournalManifestLocks(manifests) {
11976
12070
  try {
11977
12071
  const unique = /* @__PURE__ */ new Map();
11978
12072
  for (const item of manifests) unique.set(item.path, item);
12073
+ const acquiredPaths = /* @__PURE__ */ new Set();
11979
12074
  for (const item of [...unique.values()].sort((a, b) => a.path.localeCompare(b.path))) {
11980
12075
  const raw = await readOptionalRecord(item.path) ?? item.before ?? item.after;
11981
12076
  if (!raw && !(item.targetRoot && item.adapter && item.installationType && item.stateKey)) {
@@ -11988,9 +12083,13 @@ async function acquireJournalManifestLocks(manifests) {
11988
12083
  const installationType = manifest?.installationType ?? item.installationType;
11989
12084
  const stateKey = manifest?.stateKey ?? item.stateKey;
11990
12085
  const scope = { installationType, stateKey };
11991
- const lock = await acquireApplyLock(targetRoot, adapter, void 0, {}, scope);
11992
- locks.push(lock);
11993
- if (await readApplyJournal(targetRoot, adapter, void 0, scope)) {
12086
+ const lockPath = applyLockPath(targetRoot, adapter, scope);
12087
+ if (!acquiredPaths.has(lockPath)) {
12088
+ const lock = await acquireApplyLock(targetRoot, adapter, void 0, {}, scope);
12089
+ locks.push(lock);
12090
+ acquiredPaths.add(lockPath);
12091
+ }
12092
+ if ((await listApplyJournals(targetRoot, adapter, void 0, { installationType })).length > 0) {
11994
12093
  throw new Error(`Cannot normalize while an Agentwheel apply journal is pending for ${item.path}.`);
11995
12094
  }
11996
12095
  }
@@ -13522,10 +13621,13 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
13522
13621
  targetOptions.focusedArtifact,
13523
13622
  focusedArtifactOwnerKeys(result.bundle.graphLock, previousGroupLock, scopedRootId, targetOptions.focusedArtifact)
13524
13623
  )).map((operation) => operation.relativeDestPath);
13624
+ const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
13625
+ fleetId: group.target.fleetId
13626
+ });
13525
13627
  await assertNoForeignWorkspaceStateForPlan(scopedResult.plan, {
13526
13628
  transport,
13527
13629
  workspaceRoot: group.target.workspaceRoot,
13528
- workspaceOwner: workspaceOwnerForRoot(group.target.workspaceRoot, group.target.fleetId),
13630
+ workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
13529
13631
  plannedPaths: focusedPaths
13530
13632
  });
13531
13633
  }
@@ -14092,6 +14194,8 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
14092
14194
  semanticPlugin: entry.semanticPlugin,
14093
14195
  execute: entry.executed,
14094
14196
  mergeStrategy: entry.mergeStrategy,
14197
+ mergeRemoval: "mergeRemoval" in entry ? entry.mergeRemoval : void 0,
14198
+ mergeCreatedDestination: "mergeCreatedDestination" in entry ? entry.mergeCreatedDestination : void 0,
14095
14199
  composedFrom: entry.composedFrom,
14096
14200
  installName: fresh?.installName ?? ("installName" in entry ? entry.installName : entry.artifactName),
14097
14201
  logicalSelector: fresh?.logicalSelector ?? ("logicalSelector" in entry ? entry.logicalSelector : `${entry.artifactType}/${entry.artifactName}`),
@@ -8,6 +8,7 @@ import {
8
8
  applyLockPath,
9
9
  assertApplyJournalRecoveryAllowed,
10
10
  assertGovernedRuntimeTransportSupported,
11
+ listApplyJournals,
11
12
  localPathExists,
12
13
  mutationMetadataForApplyJournal,
13
14
  readApplyJournal,
@@ -16,7 +17,7 @@ import {
16
17
  removeApplyJournal,
17
18
  rollbackCompletedOperations,
18
19
  writeApplyJournal
19
- } from "./chunk-HOYIIUL5.js";
20
+ } from "./chunk-BJEZXIXX.js";
20
21
  import "./chunk-7VPI5J5Y.js";
21
22
  export {
22
23
  abortApplyJournal,
@@ -27,6 +28,7 @@ export {
27
28
  applyLockPath,
28
29
  assertApplyJournalRecoveryAllowed,
29
30
  assertGovernedRuntimeTransportSupported,
31
+ listApplyJournals,
30
32
  localPathExists,
31
33
  mutationMetadataForApplyJournal,
32
34
  readApplyJournal,
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.20.0",
4
+ "version": "0.20.1",
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.0",
3
+ "version": "0.20.1",
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.0"
8
+ version: "0.20.1"
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.20.0"
8
+ version: "0.20.1"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery