agentwheel 0.20.0 → 0.20.2

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.
@@ -2,14 +2,16 @@
2
2
  import {
3
3
  applyCombinedInstallPlan,
4
4
  applyInstallPlan,
5
+ commitManifestMetadataJournal,
5
6
  recoverPendingApply,
6
7
  uninstall
7
- } from "./chunk-USHT7FGV.js";
8
- import "./chunk-HOYIIUL5.js";
8
+ } from "./chunk-BAJEWUI6.js";
9
+ import "./chunk-Z4N7TB6Q.js";
9
10
  import "./chunk-7VPI5J5Y.js";
10
11
  export {
11
12
  applyCombinedInstallPlan,
12
13
  applyInstallPlan,
14
+ commitManifestMetadataJournal,
13
15
  recoverPendingApply,
14
16
  uninstall
15
17
  };
@@ -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-Z4N7TB6Q.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,
@@ -1562,6 +1607,12 @@ function shouldPreserveKeptOperationWhenKeepingFiles(operation) {
1562
1607
  function isForceRemovableKeep(operation) {
1563
1608
  return operation.action === "keep" && operation.preserveInManifest !== true;
1564
1609
  }
1610
+ async function commitManifestMetadataJournal(journal, transport = localTransport) {
1611
+ if (journal.mode !== "uninstall" || journal.operations.length !== 0) {
1612
+ throw new Error("Manifest metadata journal must contain no runtime operations.");
1613
+ }
1614
+ return commitJournalState(journal, transport, void 0, journal.createdAt);
1615
+ }
1565
1616
  async function commitJournalState(journal, transport, entries, now) {
1566
1617
  const manifest = withManifestRevision({
1567
1618
  ...journal.manifest,
@@ -1724,9 +1775,33 @@ async function applyOperation(operation, context) {
1724
1775
  if (!operation.desiredHash) {
1725
1776
  throw new Error(`Invalid skip operation missing hash: ${operation.relativeDestPath}`);
1726
1777
  }
1778
+ let verifiedCurrentHash = operation.currentHash;
1779
+ if (operation.mode === managedInstructionBlockMode) {
1780
+ const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
1781
+ if (!await managedInstructionBlockLanded(operation.destPath, selector, operation.desiredHash, transport)) {
1782
+ throw new Error(`Managed block changed after planning: ${operation.relativeDestPath}`);
1783
+ }
1784
+ verifiedCurrentHash = operation.desiredHash;
1785
+ } else if (operation.mergeStrategy && hasMergeRemovalContent(operation.mergeRemoval)) {
1786
+ if (!await transport.pathExists(operation.destPath)) {
1787
+ throw new Error(`Merge skip destination disappeared after planning: ${operation.relativeDestPath}`);
1788
+ }
1789
+ assertExactMergeContribution(operation.mergeRemoval, operation.mergeStrategy, await transport.readFile(operation.destPath));
1790
+ verifiedCurrentHash = await transport.hashPath(operation.destPath);
1791
+ } else if (!operation.semanticPlugin && !operation.programmaticOperation) {
1792
+ if (!await transport.pathExists(operation.destPath)) {
1793
+ throw new Error(`Skip destination disappeared after planning: ${operation.relativeDestPath}`);
1794
+ }
1795
+ const currentHash = await transport.hashPath(operation.destPath);
1796
+ const expectedHash = operation.currentHash ?? operation.desiredHash;
1797
+ if (currentHash !== expectedHash) {
1798
+ throw new Error(`Skip destination changed after planning: ${operation.relativeDestPath}`);
1799
+ }
1800
+ verifiedCurrentHash = currentHash;
1801
+ }
1727
1802
  return manifestEntryForOperation(operation, {
1728
1803
  now,
1729
- hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && operation.currentHash ? operation.currentHash : operation.desiredHash,
1804
+ hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && verifiedCurrentHash ? verifiedCurrentHash : operation.desiredHash,
1730
1805
  sourceHash: operation.desiredHash,
1731
1806
  graphLockDigest: context.graphLockDigest
1732
1807
  });
@@ -2210,6 +2285,7 @@ export {
2210
2285
  installManifestSchema,
2211
2286
  readInstallManifest,
2212
2287
  listInstallManifests,
2288
+ computeInstallManifestInventoryRevision,
2213
2289
  writeInstallManifest,
2214
2290
  normalizeTargetRoot,
2215
2291
  withManifestRevision,
@@ -2221,6 +2297,7 @@ export {
2221
2297
  combineMergeRemovals,
2222
2298
  hasMergeRemovalContent,
2223
2299
  mergeRemovalForInstall,
2300
+ mergeContributionAbsent,
2224
2301
  normalizeOwners,
2225
2302
  managedInstructionBlockMode,
2226
2303
  desiredManagedInstructionBlockHash,
@@ -2231,5 +2308,6 @@ export {
2231
2308
  applyInstallPlan,
2232
2309
  applyCombinedInstallPlan,
2233
2310
  recoverPendingApply,
2234
- uninstall
2311
+ uninstall,
2312
+ commitManifestMetadataJournal
2235
2313
  };
@@ -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-OGC6E2Y4.js"),
2628
+ import("./transaction-7CNV5MAQ.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,30 @@ 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
+ }
2873
+ async function listAllApplyJournals(targetRoot, adapter, transport = localTransport) {
2874
+ const suffix = ".apply-journal.json";
2875
+ const found = [];
2876
+ for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
2877
+ if (!fileName.endsWith(suffix)) continue;
2878
+ const path = join6(metadataDir(targetRoot), fileName);
2879
+ const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
2880
+ if (journal.adapter === adapter) found.push({ path, journal });
2881
+ }
2882
+ return found.sort((a, b) => a.path.localeCompare(b.path));
2883
+ }
2853
2884
  async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, expectedTransportDescription) {
2854
2885
  const absolutePath = resolve9(path);
2855
2886
  const journal = parseApplyJournal(JSON.parse(await readFile7(absolutePath, "utf8")));
@@ -3129,6 +3160,8 @@ export {
3129
3160
  acquireApplyLock,
3130
3161
  writeApplyJournal,
3131
3162
  readApplyJournal,
3163
+ listApplyJournals,
3164
+ listAllApplyJournals,
3132
3165
  readLinkedLocalApplyJournal,
3133
3166
  removeApplyJournal,
3134
3167
  abortApplyJournal,