agentwheel 0.8.1 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
 
10
10
  // src/cli/index.ts
11
11
  import { mkdir as mkdir14, rm as rm9, writeFile as writeFile12 } from "fs/promises";
12
- import { dirname as dirname21, join as join28, resolve as resolve17 } from "path";
12
+ import { dirname as dirname21, join as join28, resolve as resolve18 } from "path";
13
13
  import { fileURLToPath as fileURLToPath3 } from "url";
14
14
  import { Command } from "commander";
15
15
 
@@ -131,9 +131,9 @@ var copilotAdapter = {
131
131
  instructions: { enabled: true, dest: ".github/copilot-instructions.md" },
132
132
  rules: { enabled: true, dest: ".github/instructions" },
133
133
  commands: { enabled: true, dest: ".github/prompts" },
134
- // GitHub Copilot has no native SKILL.md runtime surface; keep skills disabled
135
- // until a conversion target is introduced.
136
- skills: { enabled: false, dest: ".github/skills" }
134
+ skills: { enabled: true, dest: ".github/skills" },
135
+ subagents: { enabled: true, dest: ".github/agents" },
136
+ mcp: { enabled: true, dest: ".vscode/mcp.json", merge: "json-deep" }
137
137
  }
138
138
  };
139
139
 
@@ -162,6 +162,7 @@ var hermesAdapter = {
162
162
  rules: { enabled: true, dest: ".hermes/rules" },
163
163
  skills: { enabled: true, dest: ".hermes/skills" },
164
164
  commands: { enabled: true, dest: ".hermes/commands" },
165
+ subagents: { enabled: true, dest: ".hermes/agents" },
165
166
  mcp: { enabled: true, dest: ".hermes/mcp", merge: "json-deep" },
166
167
  hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
167
168
  settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
@@ -177,6 +178,7 @@ var openClawAdapter = {
177
178
  rules: { enabled: true, dest: ".openclaw/rules" },
178
179
  skills: { enabled: true, dest: ".openclaw/skills" },
179
180
  commands: { enabled: true, dest: ".openclaw/commands" },
181
+ subagents: { enabled: true, dest: ".openclaw/agents" },
180
182
  mcp: { enabled: true, dest: ".openclaw/mcp", merge: "json-deep" },
181
183
  hooks: { enabled: true, dest: ".openclaw/hooks", merge: "json-deep" },
182
184
  settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
@@ -542,13 +544,13 @@ async function spawnWithInput(command, args, input) {
542
544
  if (result.stderr) throw new Error(result.stderr);
543
545
  }
544
546
  function waitForProcess(child, label) {
545
- return new Promise((resolve18, reject) => {
547
+ return new Promise((resolve19, reject) => {
546
548
  const stderr = [];
547
549
  child.stderr?.on("data", (chunk) => stderr.push(chunk));
548
550
  child.on("error", reject);
549
551
  child.on("close", (code) => {
550
552
  const message = Buffer.concat(stderr).toString("utf8");
551
- if (code === 0) resolve18({ stderr: "" });
553
+ if (code === 0) resolve19({ stderr: "" });
552
554
  else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
553
555
  });
554
556
  });
@@ -654,6 +656,7 @@ import { resolve as resolve3 } from "path";
654
656
  // src/model/manifest.ts
655
657
  import { z as z4 } from "zod";
656
658
  var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
659
+ var legacyUnownedWorkspaceOwner = "legacy:unowned";
657
660
  var manifestEntryV1Schema = z4.object({
658
661
  path: z4.string().min(1),
659
662
  artifactType: artifactTypeSchema,
@@ -676,6 +679,7 @@ var manifestEntrySchema = manifestEntryV1Schema.extend({
676
679
  dependencyRole: dependencyRoleSchema.default("root"),
677
680
  owners: z4.array(z4.string().min(1)).min(1),
678
681
  refCount: z4.number().int().positive(),
682
+ workspaceOwner: z4.string().min(1).default(legacyUnownedWorkspaceOwner),
679
683
  graphLockDigest: z4.string().min(1).optional()
680
684
  }).transform((entry) => {
681
685
  const owners = [...new Set(entry.owners)].sort();
@@ -1050,9 +1054,6 @@ function normalizeOwners(owners) {
1050
1054
 
1051
1055
  // src/install/apply.ts
1052
1056
  var execFileAsync2 = promisify2(execFile2);
1053
- async function applyInstallPlan(plan, sourceLock, options = {}) {
1054
- return applyPlanTransactionally(plan, { ...options, sourceLock });
1055
- }
1056
1057
  async function applyCombinedInstallPlan(plan, options = {}) {
1057
1058
  return applyPlanTransactionally(plan, options);
1058
1059
  }
@@ -1165,16 +1166,20 @@ async function applyPlanTransactionally(plan, options = {}) {
1165
1166
  async function uninstall(plan, options = {}) {
1166
1167
  const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
1167
1168
  const transport = resolvedOptions.transport ?? localTransport;
1169
+ if (resolvedOptions.keepFiles && resolvedOptions.force) {
1170
+ throw new Error("--keep-files cannot be combined with --force.");
1171
+ }
1168
1172
  if (plan.hasBlockingChanges) {
1169
1173
  const blockers = plan.operations.filter((operation) => operation.action === "conflict");
1170
1174
  throw new Error(`Refusing to uninstall with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
1171
1175
  }
1172
- const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && operation.action === "keep").map((operation) => operation.action === "keep" ? { ...operation, action: "remove", reason: `${operation.reason}; force removing drifted managed file` } : operation);
1173
- const kept = resolvedOptions.force ? [] : plan.operations.filter((operation) => operation.action === "keep");
1176
+ const removable = plan.operations.filter((operation) => operation.action === "remove" || resolvedOptions.force && isForceRemovableKeep(operation)).map((operation) => operation.action === "keep" ? { ...operation, action: "remove", reason: `${operation.reason}; force removing drifted managed file` } : operation);
1177
+ const kept = plan.operations.filter((operation) => operation.action === "keep" && (!resolvedOptions.force || !isForceRemovableKeep(operation)));
1174
1178
  const skipped = plan.operations.filter((operation) => operation.action === "skip");
1175
- const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep").length : 0;
1176
- if (resolvedOptions.dryRun) return { removed: removable.length, kept: kept.length, removedDrifted };
1177
- const preserved = [...kept, ...skipped];
1179
+ const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep" && isForceRemovableKeep(operation)).length : 0;
1180
+ if (resolvedOptions.dryRun) return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1181
+ const preservedKept = resolvedOptions.keepFiles ? kept.filter((operation) => shouldPreserveKeptOperationWhenKeepingFiles(operation)) : kept;
1182
+ const preserved = [...preservedKept, ...skipped];
1178
1183
  for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
1179
1184
  const now = (/* @__PURE__ */ new Date()).toISOString();
1180
1185
  const finalManifest = withManifestRevision({
@@ -1209,7 +1214,7 @@ async function uninstall(plan, options = {}) {
1209
1214
  graphLockDigest: plan.graphLockDigest,
1210
1215
  createdAt: now,
1211
1216
  updatedAt: now,
1212
- operations: removable,
1217
+ operations: resolvedOptions.keepFiles ? [] : removable,
1213
1218
  completed: [],
1214
1219
  manifest: finalManifest,
1215
1220
  graphLockPath: resolvedOptions.graphLock?.path,
@@ -1219,7 +1224,7 @@ async function uninstall(plan, options = {}) {
1219
1224
  workspaceConfig: resolvedOptions.workspaceConfig?.data
1220
1225
  };
1221
1226
  await writeApplyJournal(journal, transport);
1222
- for (const [index, operation] of removable.entries()) {
1227
+ for (const [index, operation] of (resolvedOptions.keepFiles ? [] : removable).entries()) {
1223
1228
  const backup = await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport);
1224
1229
  journal.completed.push(backup);
1225
1230
  await writeApplyJournal(journal, transport);
@@ -1231,7 +1236,13 @@ async function uninstall(plan, options = {}) {
1231
1236
  } finally {
1232
1237
  await lock.release();
1233
1238
  }
1234
- return { removed: removable.length, kept: kept.length, removedDrifted };
1239
+ return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1240
+ }
1241
+ function shouldPreserveKeptOperationWhenKeepingFiles(operation) {
1242
+ return operation.preserveInManifest === true;
1243
+ }
1244
+ function isForceRemovableKeep(operation) {
1245
+ return operation.action === "keep" && operation.preserveInManifest !== true;
1235
1246
  }
1236
1247
  async function commitJournalState(journal, transport, entries, now) {
1237
1248
  const manifest = withManifestRevision({
@@ -1332,6 +1343,17 @@ async function applyOperation(operation, context) {
1332
1343
  graphLockDigest: context.graphLockDigest
1333
1344
  });
1334
1345
  }
1346
+ if (operation.action === "keep") {
1347
+ if (!operation.manifestHash || !operation.desiredHash) {
1348
+ throw new Error(`Invalid keep operation missing manifest/source hash: ${operation.relativeDestPath}`);
1349
+ }
1350
+ return manifestEntryForOperation(operation, {
1351
+ now,
1352
+ hash: operation.manifestHash,
1353
+ sourceHash: operation.desiredHash,
1354
+ graphLockDigest: operation.graphLockDigest ?? context.graphLockDigest
1355
+ });
1356
+ }
1335
1357
  if (operation.action === "remove") {
1336
1358
  await transport.rm(operation.destPath);
1337
1359
  return void 0;
@@ -1370,6 +1392,7 @@ function manifestEntryForOperation(operation, values) {
1370
1392
  dependencyRole: operation.dependencyRole ?? "root",
1371
1393
  owners,
1372
1394
  refCount: owners.length,
1395
+ workspaceOwner: operation.workspaceOwner ?? "workspace:unknown",
1373
1396
  kind: operation.kind,
1374
1397
  hash: values.hash,
1375
1398
  sourceHash: values.sourceHash,
@@ -1377,7 +1400,7 @@ function manifestEntryForOperation(operation, values) {
1377
1400
  channel: operation.channel,
1378
1401
  packageName: operation.packageName,
1379
1402
  semanticCommand: operation.semanticCommand,
1380
- executed: values.executed,
1403
+ executed: values.executed ?? operation.execute,
1381
1404
  mergeStrategy: operation.mergeStrategy,
1382
1405
  composedFrom: operation.composedFrom,
1383
1406
  graphLockDigest: operation.graphLockDigest ?? values.graphLockDigest
@@ -1441,22 +1464,6 @@ function openClawPluginInstallCommand(request) {
1441
1464
  }
1442
1465
 
1443
1466
  // src/install/plan.ts
1444
- async function createInstallPlan(bundle, adapter, targetRoot, manifest, transport = localTransport) {
1445
- const desired = [];
1446
- for (const artifact of bundle.artifacts) {
1447
- const op = operationForArtifact(artifact, adapter, targetRoot, {
1448
- logicalSelector: `${artifact.type}/${artifact.name}`,
1449
- dependencyRole: "root",
1450
- owners: [artifact.packageName ?? bundle.source.packageName ?? bundle.source.source],
1451
- composedFrom: artifact.composedFrom
1452
- });
1453
- if (op) {
1454
- desired.push(op);
1455
- }
1456
- }
1457
- await addProgrammaticOperations(desired, adapter, targetRoot);
1458
- return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, {});
1459
- }
1460
1467
  async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot, manifest, transport = localTransport, options = {}) {
1461
1468
  for (const artifact of desiredArtifacts) {
1462
1469
  if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
@@ -1474,6 +1481,10 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
1474
1481
  return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, options);
1475
1482
  }
1476
1483
  async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
1484
+ const workspaceOwner = options.workspaceOwner;
1485
+ if (workspaceOwner) {
1486
+ for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
1487
+ }
1477
1488
  for (const op of desiredOps) assertOperationContained(op, targetRoot);
1478
1489
  const migration = await migrateManifestForPlan(manifest, desiredOps, targetRoot, transport);
1479
1490
  const effectiveEntries = migration.entries;
@@ -1516,6 +1527,12 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1516
1527
  continue;
1517
1528
  }
1518
1529
  const existing = manifestByPath.get(op.relativeDestPath);
1530
+ if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
1531
+ if (!await canAdoptLegacyUnownedEntry(existing, op, transport)) {
1532
+ operations.push(keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op));
1533
+ continue;
1534
+ }
1535
+ }
1519
1536
  const exists = await transport.pathExists(op.destPath);
1520
1537
  if (!exists) {
1521
1538
  operations.push({ ...op, action: "create", reason: "destination missing" });
@@ -1557,6 +1574,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
1557
1574
  const destPath = join5(targetRoot, entry.path);
1558
1575
  if (!await transport.pathExists(destPath)) continue;
1559
1576
  const currentHash = await transport.hashPath(destPath);
1577
+ if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
1578
+ operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
1579
+ continue;
1580
+ }
1560
1581
  if (currentHash !== entry.hash) {
1561
1582
  operations.push({
1562
1583
  action: "drift",
@@ -1712,6 +1733,16 @@ function operationMatchesManifestEntry(op, entry) {
1712
1733
  if ("graphNodeId" in entry && entry.graphNodeId && op.graphNodeId && entry.graphNodeId === op.graphNodeId) return true;
1713
1734
  return op.packageName !== void 0 && entry.packageName === op.packageName;
1714
1735
  }
1736
+ function entryOwnedByWorkspace(entry, workspaceOwner) {
1737
+ return "workspaceOwner" in entry && entry.workspaceOwner === workspaceOwner;
1738
+ }
1739
+ async function canAdoptLegacyUnownedEntry(entry, op, transport) {
1740
+ if (!("workspaceOwner" in entry) || entry.workspaceOwner !== legacyUnownedWorkspaceOwner) return false;
1741
+ if (entry.artifactType !== op.artifactType || entry.kind !== op.kind) return false;
1742
+ if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
1743
+ if (!await transport.pathExists(op.destPath)) return false;
1744
+ return await transport.hashPath(op.destPath) === entry.hash;
1745
+ }
1715
1746
  function collisionOperation(op, group, incumbent) {
1716
1747
  const owners = group.map(describeOperationOwner).sort();
1717
1748
  const incumbentText = incumbent ? `; incumbent ${describeOperationOwner(incumbent)} keeps the plain name` : "";
@@ -1735,6 +1766,7 @@ function adoptLegacyEntry(entry, op) {
1735
1766
  dependencyRole: op.dependencyRole ?? "root",
1736
1767
  owners,
1737
1768
  refCount: owners.length,
1769
+ workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
1738
1770
  graphLockDigest: op.graphLockDigest,
1739
1771
  composedFrom: op.composedFrom ?? entry.composedFrom
1740
1772
  };
@@ -1757,6 +1789,7 @@ function operationMetadataFromEntry(entry) {
1757
1789
  graphNodeId: entry.graphNodeId,
1758
1790
  dependencyRole: entry.dependencyRole,
1759
1791
  owners: entry.owners,
1792
+ workspaceOwner: entry.workspaceOwner,
1760
1793
  graphLockDigest: entry.graphLockDigest
1761
1794
  };
1762
1795
  }
@@ -1764,7 +1797,31 @@ function operationMetadataFromEntry(entry) {
1764
1797
  installName: entry.artifactName,
1765
1798
  logicalSelector: `${entry.artifactType}/${entry.artifactName}`,
1766
1799
  dependencyRole: "root",
1767
- owners: [entry.packageName ?? "legacy"]
1800
+ owners: [entry.packageName ?? "legacy"],
1801
+ workspaceOwner: legacyUnownedWorkspaceOwner
1802
+ };
1803
+ }
1804
+ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, operation, currentHash) {
1805
+ const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
1806
+ return {
1807
+ action: "keep",
1808
+ artifactType: entry.artifactType,
1809
+ artifactName: entry.artifactName,
1810
+ kind: entry.kind,
1811
+ destPath: operation?.destPath ?? join5(targetRoot, entry.path),
1812
+ relativeDestPath: entry.path,
1813
+ desiredHash: entry.sourceHash,
1814
+ currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
1815
+ manifestHash: entry.hash,
1816
+ reason: `foreign artifact owned by ${owner}; kept outside workspace ${workspaceOwner}`,
1817
+ channel: entry.channel,
1818
+ packageName: entry.packageName,
1819
+ semanticCommand: entry.semanticCommand,
1820
+ execute: entry.executed,
1821
+ mergeStrategy: entry.mergeStrategy,
1822
+ composedFrom: entry.composedFrom,
1823
+ preserveInManifest: true,
1824
+ ...operationMetadataFromEntry(entry)
1768
1825
  };
1769
1826
  }
1770
1827
  function normalizeOperationOwners(op) {
@@ -1938,7 +1995,8 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
1938
1995
  mergeStrategy: entry.mergeStrategy,
1939
1996
  composedFrom: entry.composedFrom,
1940
1997
  ...operationMetadataFromEntry2(entry, remainingOwners),
1941
- graphLockDigest: options.graphLockDigest
1998
+ graphLockDigest: options.graphLockDigest,
1999
+ preserveInManifest: true
1942
2000
  });
1943
2001
  continue;
1944
2002
  }
@@ -2038,7 +2096,7 @@ function formatPlan(plan) {
2038
2096
  const dropped = plan.migrationReport.dropped.length > 0 ? `; dropped unmanaged ${plan.migrationReport.dropped.join(", ")}` : "";
2039
2097
  lines.push(`MIGRATE adopted ${plan.migrationReport.adopted} legacy entries${dropped}`);
2040
2098
  }
2041
- for (const operation of plan.operations) {
2099
+ for (const operation of sortedPlanOperations(plan.operations)) {
2042
2100
  const source = operation.sourcePath ? `${operation.sourcePath} -> ` : "";
2043
2101
  const command = operation.semanticCommand ? ` :: ${operation.semanticCommand.join(" ")}` : "";
2044
2102
  const blocked = operation.blockedReason ? `; ${operation.blockedReason}` : "";
@@ -2050,6 +2108,14 @@ function formatPlan(plan) {
2050
2108
  );
2051
2109
  return lines.join("\n");
2052
2110
  }
2111
+ function sortedPlanOperations(operations) {
2112
+ return [...operations].sort((a, b) => {
2113
+ const destructiveA = a.action === "remove" || a.action === "drift" || a.action === "conflict";
2114
+ const destructiveB = b.action === "remove" || b.action === "drift" || b.action === "conflict";
2115
+ if (destructiveA !== destructiveB) return destructiveA ? -1 : 1;
2116
+ return a.relativeDestPath.localeCompare(b.relativeDestPath);
2117
+ });
2118
+ }
2053
2119
  function formatGraphPlan(result) {
2054
2120
  const lines = [
2055
2121
  ...formatDependencyTree(result.graph),
@@ -2252,7 +2318,17 @@ var packageManifestV2Schema = z5.object({
2252
2318
  runtimes: runtimeListSchema.optional(),
2253
2319
  requires: z5.record(z5.string().min(1), packageDependencySchema).optional(),
2254
2320
  compose: z5.array(packageComposeEntrySchema).optional(),
2255
- provides: z5.array(packageProvideSchema).min(1)
2321
+ provides: z5.array(packageProvideSchema).default([])
2322
+ }).superRefine((manifest, ctx) => {
2323
+ const hasProvides = manifest.provides.length > 0;
2324
+ const hasRequires = Object.keys(manifest.requires ?? {}).length > 0;
2325
+ if (!hasProvides && !hasRequires) {
2326
+ ctx.addIssue({
2327
+ code: z5.ZodIssueCode.custom,
2328
+ path: ["provides"],
2329
+ message: "OpenPack v2 manifest must declare at least one provides entry or one requires dependency"
2330
+ });
2331
+ }
2256
2332
  });
2257
2333
  var packageManifestSchema = z5.union([packageManifestV1Schema, packageManifestV2Schema]);
2258
2334
  var openPackManifestNames = ["openpack.json", "openpack.jsonc"];
@@ -2693,7 +2769,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
2693
2769
  if (Date.now() - started > timeoutMs) {
2694
2770
  throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
2695
2771
  }
2696
- await new Promise((resolve18) => setTimeout(resolve18, 50));
2772
+ await new Promise((resolve19) => setTimeout(resolve19, 50));
2697
2773
  }
2698
2774
  }
2699
2775
  try {
@@ -4239,6 +4315,7 @@ async function resolveDependencyGraph(roots, options) {
4239
4315
  requiredBy: `workspace:${rootId}`,
4240
4316
  rootId,
4241
4317
  aliases: root.aliases,
4318
+ useLock: root.useLock ?? options.lockedResolution,
4242
4319
  depth: 0,
4243
4320
  optional: false,
4244
4321
  chain: [`workspace:${rootId}`]
@@ -4295,19 +4372,41 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
4295
4372
  }
4296
4373
  async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
4297
4374
  try {
4298
- const normalized = await normalizeDependencySource(requirement.source, {
4375
+ const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
4376
+ let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
4377
+ let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
4299
4378
  declaringPackageRoot: requirement.declaringPackageRoot,
4300
4379
  workspaceRoot: options.workspaceRoot,
4301
4380
  ref: requirement.ref,
4302
4381
  registryClient: options.registryClient
4303
4382
  });
4304
- const lockLabel = options.offline ? "Offline" : "Frozen lock";
4305
- const frozen = options.frozenLock ? lockedNodeForSource(normalized.normalizedSource, options.previousLock, lockLabel) : void 0;
4383
+ if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
4384
+ const declared = await normalizeDependencySource(requirement.source, {
4385
+ declaringPackageRoot: requirement.declaringPackageRoot,
4386
+ workspaceRoot: options.workspaceRoot,
4387
+ ref: requirement.ref,
4388
+ registryClient: options.registryClient
4389
+ });
4390
+ if (lockedRootSourceDrifted(declared, lockedByReference.node)) {
4391
+ if (options.frozenLock || options.offline) {
4392
+ throw new Error(
4393
+ `${lockLabel} root '${requirement.rootId}' source differs from declared source:
4394
+ - declared: ${declared.normalizedSource}
4395
+ - locked: ${lockedByReference.node.normalizedSource}
4396
+ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
4397
+ );
4398
+ }
4399
+ lockedByReference = void 0;
4400
+ normalized = declared;
4401
+ }
4402
+ }
4403
+ const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
4306
4404
  let fetched;
4307
4405
  try {
4308
4406
  fetched = await fetchPackage(normalized, requirement.mode, options, fetchCache, frozen?.requestedRef);
4309
4407
  } catch (error) {
4310
- if (!options.frozenLock) throw error;
4408
+ const usingLockedNode = frozen?.node !== void 0;
4409
+ if (!options.frozenLock && !options.offline && !usingLockedNode) throw error;
4311
4410
  const message = error instanceof Error ? error.message : String(error);
4312
4411
  const label = frozen?.node ? `${frozen.node.id} (${normalized.normalizedSource})` : normalized.normalizedSource;
4313
4412
  throw new Error(`${lockLabel} cache missing or stale for locked graph node:
@@ -4407,7 +4506,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4407
4506
  if (!dependency.select?.length && !(state.fullPackageSelected && dependency.select === void 0)) continue;
4408
4507
  state.processedPackageAliases.add(alias);
4409
4508
  if (!dependencyTargetsRuntime(dependency.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
4410
- requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain));
4509
+ requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain, options.lockedResolution === true));
4411
4510
  }
4412
4511
  }
4413
4512
  const artifactsBySelector = new Map(fetched.artifacts.map((artifact) => [artifactSelectorKey(artifact), artifact]));
@@ -4437,6 +4536,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4437
4536
  dependency,
4438
4537
  sortedUnique3([...dependency.select ?? [], parsed.selector]),
4439
4538
  chain,
4539
+ options.lockedResolution === true,
4440
4540
  parsed.optional || dependency.optional === true,
4441
4541
  `required by ${parentSelector}`
4442
4542
  ));
@@ -4466,6 +4566,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4466
4566
  dependency,
4467
4567
  sortedUnique3([...dependency.select ?? [], include.selector]),
4468
4568
  chain,
4569
+ options.lockedResolution === true,
4469
4570
  include.optional || dependency.optional === true
4470
4571
  ));
4471
4572
  }
@@ -4475,12 +4576,13 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
4475
4576
  refreshNode(state);
4476
4577
  return requirements;
4477
4578
  }
4478
- function dependencyRequirement(state, fetched, alias, dependency, select, chain, optional = dependency.optional ?? false, selectionReason) {
4579
+ function dependencyRequirement(state, fetched, alias, dependency, select, chain, lockByDefault, optional = dependency.optional ?? false, selectionReason) {
4479
4580
  return {
4480
4581
  source: dependency.source,
4481
4582
  select,
4482
4583
  mode: dependency.mode ?? "pinned",
4483
4584
  ref: dependency.ref,
4585
+ useLock: lockByDefault || dependency.mode !== "tracking",
4484
4586
  declaringPackageRoot: fetched.resolved.resolvedPath,
4485
4587
  requiredBy: state.node.id,
4486
4588
  alias,
@@ -4626,7 +4728,8 @@ function dependencyTargetsRuntime(runtimes, runtime, nodeId, alias, warn) {
4626
4728
  return false;
4627
4729
  }
4628
4730
  async function fetchPackage(normalized, mode, options, fetchCache, refOverride) {
4629
- const key = `${normalized.driver}\0${normalized.normalizedSource}\0${mode}\0${refOverride ?? ""}\0${options.frozenLock ? "frozen" : ""}`;
4731
+ const hardLockedCheckout = options.frozenLock === true || options.offline === true;
4732
+ const key = `${normalized.driver}\0${normalized.normalizedSource}\0${mode}\0${refOverride ?? ""}\0${hardLockedCheckout ? "hard-locked" : "mutable"}`;
4630
4733
  const existing = fetchCache.get(key);
4631
4734
  if (existing) return existing;
4632
4735
  const promise = (async () => {
@@ -4635,7 +4738,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
4635
4738
  cacheRoot: options.cacheRoot ?? join18(options.workspaceRoot, ".agentwheel", "cache"),
4636
4739
  mode,
4637
4740
  ref: refOverride ?? normalized.requestedRef,
4638
- frozenLock: options.frozenLock
4741
+ frozenLock: hardLockedCheckout
4639
4742
  });
4640
4743
  const fetched = await withCachePathLock(resolved.resolvedPath, () => driver.fetch(resolved));
4641
4744
  const translated = await driver.translate(fetched);
@@ -4664,15 +4767,20 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
4664
4767
  fetchCache.set(key, promise);
4665
4768
  return promise;
4666
4769
  }
4667
- function lockedNodeForSource(normalizedSource, lock, label) {
4668
- if (!lock) {
4669
- throw new Error(`${label} requires an existing graph lock before resolving ${normalizedSource}.`);
4770
+ function lockedNodeForRequirement(normalizedSource, requirement, options, label) {
4771
+ const hard = options.frozenLock === true || options.offline === true;
4772
+ if (!hard && !requirement.useLock) return void 0;
4773
+ if (!options.previousLock) {
4774
+ if (hard) throw new Error(`${label} requires an existing graph lock before resolving ${normalizedSource}.`);
4775
+ return void 0;
4670
4776
  }
4671
- const matches = lock.canonical.nodes.filter((node2) => node2.normalizedSource === normalizedSource);
4777
+ const matches = options.previousLock.canonical.nodes.filter((node2) => node2.normalizedSource === normalizedSource);
4672
4778
  if (matches.length === 0) {
4673
- throw new Error(`${label} cannot resolve new source: ${normalizedSource}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4779
+ if (hard) throw new Error(`${label} cannot resolve new source: ${normalizedSource}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4780
+ return void 0;
4674
4781
  }
4675
4782
  if (matches.length > 1) {
4783
+ if (!hard) return void 0;
4676
4784
  throw new Error(`${label} has multiple nodes for ${normalizedSource}; cannot choose a cached source deterministically.`);
4677
4785
  }
4678
4786
  const node = matches[0];
@@ -4681,6 +4789,59 @@ function lockedNodeForSource(normalizedSource, lock, label) {
4681
4789
  requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4682
4790
  };
4683
4791
  }
4792
+ function lockedNodeForRequirementReference(requirement, options, label) {
4793
+ const hard = options.frozenLock === true || options.offline === true;
4794
+ if (!hard && !requirement.useLock) return void 0;
4795
+ if (!options.previousLock) {
4796
+ if (hard) throw new Error(`${label} requires an existing graph lock before resolving ${requirement.source}.`);
4797
+ return void 0;
4798
+ }
4799
+ let nodeId;
4800
+ let description;
4801
+ if (requirement.depth === 0 && requirement.rootId) {
4802
+ const root = options.previousLock.canonical.roots.find((candidate) => candidate.rootId === requirement.rootId);
4803
+ nodeId = root?.graphNodeId;
4804
+ description = `root ${requirement.rootId}`;
4805
+ } else if (requirement.parentId && requirement.alias) {
4806
+ const edge = options.previousLock.canonical.edges.find((candidate) => candidate.from === requirement.parentId && candidate.alias === requirement.alias);
4807
+ nodeId = edge?.to;
4808
+ description = `dependency ${requirement.parentId}:${requirement.alias}`;
4809
+ }
4810
+ if (!nodeId) {
4811
+ if (hard && description) {
4812
+ throw new Error(`${label} cannot resolve new locked ${description}. Run without ${label === "Offline" ? "--offline" : "--frozen-lock"} first.`);
4813
+ }
4814
+ return void 0;
4815
+ }
4816
+ const node = options.previousLock.canonical.nodes.find((candidate) => candidate.id === nodeId);
4817
+ if (!node) {
4818
+ throw new Error(`${label} graph lock is missing node ${nodeId} for ${description ?? requirement.source}.`);
4819
+ }
4820
+ return {
4821
+ node,
4822
+ requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
4823
+ };
4824
+ }
4825
+ function shouldCheckLockedRootSource(requirement) {
4826
+ if (!requirement.useLock) return false;
4827
+ if (requirement.depth !== 0 || !requirement.rootId) return false;
4828
+ return isExplicitNonRegistrySource(requirement.source);
4829
+ }
4830
+ function isExplicitNonRegistrySource(source) {
4831
+ const trimmed = source.trim();
4832
+ return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:");
4833
+ }
4834
+ function lockedRootSourceDrifted(declared, locked) {
4835
+ return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
4836
+ }
4837
+ function normalizedSourceFromLockedNode(node) {
4838
+ return {
4839
+ source: node.source,
4840
+ normalizedSource: node.normalizedSource,
4841
+ driver: node.driver,
4842
+ requestedRef: node.requestedRef
4843
+ };
4844
+ }
4684
4845
  function verifyIntegrity(integrity, sourceHash, label) {
4685
4846
  if (!integrity) return;
4686
4847
  const expected = integrity.replace(/^sha256[-:]/i, "");
@@ -4691,8 +4852,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
4691
4852
  async function withCachePathLock(path, fn) {
4692
4853
  const previous = cacheLocks.get(path) ?? Promise.resolve();
4693
4854
  let release = () => void 0;
4694
- const current = previous.then(() => new Promise((resolve18) => {
4695
- release = resolve18;
4855
+ const current = previous.then(() => new Promise((resolve19) => {
4856
+ release = resolve19;
4696
4857
  }));
4697
4858
  cacheLocks.set(path, current);
4698
4859
  await previous;
@@ -4900,7 +5061,7 @@ import { rm as rm8 } from "fs/promises";
4900
5061
  // src/lifecycle/source-plan.ts
4901
5062
  import { createHash as createHash6 } from "crypto";
4902
5063
  import { mkdir as mkdir12 } from "fs/promises";
4903
- import { dirname as dirname17, join as join22 } from "path";
5064
+ import { dirname as dirname17, join as join22, resolve as resolve14 } from "path";
4904
5065
 
4905
5066
  // src/resolve/graph-diff.ts
4906
5067
  function diffGraphLocks(previous, next) {
@@ -5381,26 +5542,6 @@ function defaultTrustStorePath() {
5381
5542
  }
5382
5543
 
5383
5544
  // src/lifecycle/source-plan.ts
5384
- async function createSourcePlan(options) {
5385
- const workspaceRoot = options.workspaceRoot ?? options.targetRoot;
5386
- const lockMode = options.frozenLock === true || options.offline === true;
5387
- const resolvedInput = await resolvePackageSource(options.source, workspaceRoot, { offline: lockMode, warn: options.warn });
5388
- const resolvedSource = resolvedInput.source;
5389
- const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedSource));
5390
- const bundle = await stageSource(driver, resolvedSource, {
5391
- workspaceRoot,
5392
- adapter: options.adapter,
5393
- cacheRoot: join22(workspaceRoot, ".agentwheel", "cache"),
5394
- mode: options.mode,
5395
- frozenLock: lockMode,
5396
- select: options.select,
5397
- skills: options.skills
5398
- });
5399
- const transport = options.transport ?? localTransport;
5400
- const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
5401
- const plan = await createInstallPlan(bundle, options.adapter, options.targetRoot, manifest, transport);
5402
- return { plan, bundle, resolvedSource, registryEntryName: resolvedInput.registryEntry?.name };
5403
- }
5404
5545
  async function createGraphSourcePlan(options) {
5405
5546
  if (options.roots.length === 0) {
5406
5547
  throw new Error("At least one source is required for a graph plan.");
@@ -5412,14 +5553,14 @@ async function createGraphSourcePlan(options) {
5412
5553
  warnings.push(message);
5413
5554
  options.warn?.(message);
5414
5555
  };
5415
- const recoveredPendingApply = await recoverPendingApplyIfSafe(options.targetRoot, options.adapter.name, transport);
5556
+ const recoveredPendingApply = options.readOnly === true ? false : await recoverPendingApplyIfSafe(options.targetRoot, options.adapter.name, transport);
5416
5557
  const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
5417
5558
  const trustPolicy = {
5418
5559
  ...normalizeTrustPolicy(workspaceConfig.trust),
5419
5560
  acceptedSources: await readTrustedSources(workspaceRoot, options.trustStorePath)
5420
5561
  };
5421
5562
  const lockMode = options.frozenLock === true || options.offline === true;
5422
- const lockLabel = options.offline === true ? "Offline" : "Frozen lock";
5563
+ const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
5423
5564
  const targetFingerprint = computeTargetFingerprint(options.targetFingerprintParts ?? {
5424
5565
  adapter: options.adapter.name,
5425
5566
  targetRoot: options.targetRoot,
@@ -5434,6 +5575,7 @@ async function createGraphSourcePlan(options) {
5434
5575
  cacheRoot: join22(workspaceRoot, ".agentwheel", "cache"),
5435
5576
  registryClient,
5436
5577
  noDeps: options.noDeps,
5578
+ lockedResolution: options.lockedResolution,
5437
5579
  frozenLock: lockMode,
5438
5580
  offline: options.offline,
5439
5581
  previousLock,
@@ -5444,7 +5586,7 @@ async function createGraphSourcePlan(options) {
5444
5586
  assertTrustArtifactPolicy(graph, trustPolicy);
5445
5587
  const trustEvaluation = evaluateTransitiveTrust(graph, previousLock, trustPolicy, options.trustPatterns ?? [], options.yes === true);
5446
5588
  await assertTrusted(trustEvaluation.promptSources, options);
5447
- const persistedTrustSources = await rememberTrustedSources(workspaceRoot, trustEvaluation.persistSources, options.trustStorePath);
5589
+ const persistedTrustSources = options.readOnly === true ? [] : await rememberTrustedSources(workspaceRoot, trustEvaluation.persistSources, options.trustStorePath);
5448
5590
  for (const source of persistedTrustSources) warn(`remembered trusted transitive source: ${source}`);
5449
5591
  const bundle = await renderGraphForTarget(graph, {
5450
5592
  workspaceRoot,
@@ -5457,7 +5599,8 @@ async function createGraphSourcePlan(options) {
5457
5599
  const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
5458
5600
  const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
5459
5601
  baseRevision: manifest?.revision ?? null,
5460
- graphLockDigest
5602
+ graphLockDigest,
5603
+ workspaceOwner: workspaceOwnerId(workspaceRoot)
5461
5604
  });
5462
5605
  return {
5463
5606
  plan,
@@ -5515,6 +5658,9 @@ function sanitizePathSegment(value) {
5515
5658
  function digestGraphLock(lock) {
5516
5659
  return createHash6("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
5517
5660
  }
5661
+ function workspaceOwnerId(workspaceRoot) {
5662
+ return `workspace-root:${resolve14(workspaceRoot)}`;
5663
+ }
5518
5664
  function assertFrozenGraph(previousLock, graph, frozen, label) {
5519
5665
  if (!frozen) return;
5520
5666
  if (!previousLock) {
@@ -5542,6 +5688,10 @@ ${mismatches.map((item) => `- ${item}`).join("\n")}`);
5542
5688
  }
5543
5689
  async function assertTrusted(sources, options) {
5544
5690
  if (sources.length === 0) return;
5691
+ if (options.readOnly === true) {
5692
+ throw new Error(`New transitive sources require trust. Re-run with --yes or --trust <pattern>:
5693
+ ${sources.map((source) => `- ${source}`).join("\n")}`);
5694
+ }
5545
5695
  if (options.promptTrust) {
5546
5696
  if (await options.promptTrust(sources)) return;
5547
5697
  throw new Error(`Untrusted transitive sources:
@@ -5609,10 +5759,12 @@ async function syncProfile(options) {
5609
5759
  transport: target.transport.kind
5610
5760
  },
5611
5761
  noDeps: options.noDeps,
5762
+ lockedResolution: options.lockedResolution,
5612
5763
  frozenLock: options.frozenLock,
5613
5764
  offline: options.offline,
5614
5765
  yes: options.yes,
5615
5766
  trustPatterns: options.trustPatterns ?? [],
5767
+ readOnly: options.readOnly,
5616
5768
  isTTY: options.isTTY,
5617
5769
  warn: options.warn
5618
5770
  });
@@ -5682,7 +5834,7 @@ async function packageFromSource(source, options) {
5682
5834
  }
5683
5835
 
5684
5836
  // src/runtime/target.ts
5685
- import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve14 } from "path";
5837
+ import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve15 } from "path";
5686
5838
  var runtimeMarkers = [
5687
5839
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
5688
5840
  { adapter: "claude", dirs: [".claude"] },
@@ -5691,9 +5843,9 @@ var runtimeMarkers = [
5691
5843
  { adapter: "copilot", dirs: [".github"] }
5692
5844
  ];
5693
5845
  async function resolveRuntimeTarget(request = {}) {
5694
- const cwd = resolve14(request.cwd ?? process.cwd());
5846
+ const cwd = resolve15(request.cwd ?? process.cwd());
5695
5847
  if (request.targetRoot) {
5696
- const targetRoot = resolve14(request.targetRoot);
5848
+ const targetRoot = resolve15(request.targetRoot);
5697
5849
  return {
5698
5850
  adapter: request.adapter ?? "openclaw",
5699
5851
  targetRoot,
@@ -5722,7 +5874,7 @@ async function resolveRuntimeTarget(request = {}) {
5722
5874
  async function resolveAllRuntimeTargets(request = {}) {
5723
5875
  if (request.targetRoot) return [await resolveRuntimeTarget(request)];
5724
5876
  if (request.agent) return [await resolveRuntimeTarget(request)];
5725
- const cwd = resolve14(request.cwd ?? process.cwd());
5877
+ const cwd = resolve15(request.cwd ?? process.cwd());
5726
5878
  const workspaceRoot = await findWorkspaceRoot(cwd);
5727
5879
  const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
5728
5880
  const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
@@ -5731,8 +5883,29 @@ async function resolveAllRuntimeTargets(request = {}) {
5731
5883
  }
5732
5884
  return targets;
5733
5885
  }
5886
+ async function resolveAllDetectedRuntimeTargets(request = {}) {
5887
+ if (request.agent) return [await resolveRuntimeTarget(request)];
5888
+ const scanRoot = runtimeScanRoot(request);
5889
+ const matches = await detectRuntimeTargets(scanRoot, request.adapter);
5890
+ if (matches.length === 0) {
5891
+ throw new Error(`No runtime directories detected at ${scanRoot}. Pass --target-root or --agent.`);
5892
+ }
5893
+ return Promise.all(matches.map(async (match) => ({
5894
+ ...match,
5895
+ workspaceRoot: await findWorkspaceRoot(match.targetRoot),
5896
+ transport: "local",
5897
+ source: "auto-detect"
5898
+ })));
5899
+ }
5734
5900
  async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5735
- const root = resolve14(cwd);
5901
+ const unique = await detectRuntimeTargets(cwd, adapterFilter);
5902
+ if (unique.length > 1) {
5903
+ throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter, --agent, or --all-detected.`);
5904
+ }
5905
+ return unique[0];
5906
+ }
5907
+ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
5908
+ const root = resolve15(cwd);
5736
5909
  const matches = [];
5737
5910
  for (const marker of runtimeMarkers) {
5738
5911
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
@@ -5744,11 +5917,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
5744
5917
  }
5745
5918
  }
5746
5919
  }
5747
- const unique = dedupeTargets(matches);
5748
- if (unique.length > 1) {
5749
- throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
5750
- }
5751
- return unique[0];
5920
+ return dedupeTargets(matches);
5752
5921
  }
5753
5922
  function targetFromAgent(name, config, workspaceRoot) {
5754
5923
  const agent = config.agents[name];
@@ -5777,6 +5946,11 @@ function dedupeTargets(matches) {
5777
5946
  }
5778
5947
  return [...byKey.values()];
5779
5948
  }
5949
+ function runtimeScanRoot(request) {
5950
+ const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
5951
+ if (request.targetRoot) return root;
5952
+ return runtimeMarkers.some((marker) => marker.dirs.includes(basename12(root))) ? dirname18(root) : root;
5953
+ }
5780
5954
 
5781
5955
  // src/cli/update-check.ts
5782
5956
  import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
@@ -5858,9 +6032,9 @@ function normalizeVersion(version) {
5858
6032
 
5859
6033
  // src/model/package-validate.ts
5860
6034
  import { stat as stat11 } from "fs/promises";
5861
- import { resolve as resolve15 } from "path";
6035
+ import { resolve as resolve16 } from "path";
5862
6036
  async function validatePackage(root) {
5863
- const packageRoot = resolve15(root);
6037
+ const packageRoot = resolve16(root);
5864
6038
  const findings = [];
5865
6039
  const manifestPath = await findPackageManifestPath(packageRoot);
5866
6040
  if (!manifestPath) {
@@ -5928,7 +6102,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
5928
6102
  try {
5929
6103
  validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
5930
6104
  if (isCrossPackageSelector(selector)) return;
5931
- const full = resolve15(packageRoot, selector);
6105
+ const full = resolve16(packageRoot, selector);
5932
6106
  if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
5933
6107
  findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
5934
6108
  return;
@@ -5982,10 +6156,10 @@ function isCrossPackageSelector(value) {
5982
6156
 
5983
6157
  // src/model/package-migrate.ts
5984
6158
  import { readFile as readFile17, rename as rename4, writeFile as writeFile11 } from "fs/promises";
5985
- import { join as join26, resolve as resolve16 } from "path";
6159
+ import { join as join26, resolve as resolve17 } from "path";
5986
6160
  import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
5987
6161
  async function migratePackageManifest(root) {
5988
- const packageRoot = resolve16(root);
6162
+ const packageRoot = resolve17(root);
5989
6163
  for (const name of openPackManifestNames) {
5990
6164
  const path = join26(packageRoot, name);
5991
6165
  if (await pathExists(path)) {
@@ -6047,8 +6221,14 @@ function resolveCliVersion() {
6047
6221
  // src/cli/index.ts
6048
6222
  var CLI_VERSION = resolveCliVersion();
6049
6223
  var program = new Command();
6050
- program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version(CLI_VERSION).option("--no-update-check", "disable npm version update check", false);
6051
- program.command("init").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
6224
+ program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version(CLI_VERSION).showSuggestionAfterError(false).option("--no-update-check", "disable npm version update check", false).addHelpText("after", `
6225
+
6226
+ Core flow:
6227
+ $ agentwheel add github:org/agent-pack --adapter codex
6228
+ $ agentwheel plan
6229
+ $ agentwheel install
6230
+ `);
6231
+ program.command("init").description("initialize an agentwheel workspace or package").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
6052
6232
  const root = normalizeTargetRoot(options.targetRoot);
6053
6233
  if (kind === "package") {
6054
6234
  await initPackage(root);
@@ -6062,48 +6242,17 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
6062
6242
  const bootstrapPackage = config.bootstrapSkills === false ? void 0 : await defaultBootstrapPackage(root);
6063
6243
  const withBootstrap = bootstrapPackage ? upsertPackage(config, bootstrapPackage) : config;
6064
6244
  await writeWorkspaceConfig(root, options.fleetExample ? withFleetExample(withBootstrap) : withBootstrap);
6065
- console.log("Initialized .agentwheel/config.json.");
6245
+ console.log(`Initialized ${workspaceConfigPath(root)}.`);
6246
+ if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
6247
+ console.log(nextInstallNudge());
6066
6248
  });
6067
- program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").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("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").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, []).action(async (source, options) => {
6249
+ program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").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("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").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, []).action(async (source, options) => {
6068
6250
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6069
- const selectedArtifacts = selectedArtifactsFromOptions(options);
6070
- const resolvedInput = await resolvePackageSource(source, targetRoot);
6071
- const resolvedSource = resolvedInput.source;
6072
- const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
6073
- const driver = getSourceDriver(driverName);
6074
- const adapter = await resolveAdapter({
6075
- adapter: options.adapter,
6076
- adapterConfig: options.adapterConfig,
6077
- adapterModule: options.adapterModule,
6078
- allowAdapterCode: options.allowAdapterCode,
6079
- baseDir: targetRoot,
6080
- warn: (message) => console.warn(message)
6081
- });
6082
- const bundle = await stageSource(driver, resolvedSource, {
6083
- workspaceRoot: targetRoot,
6084
- adapter,
6085
- cacheRoot: join28(targetRoot, ".agentwheel", "cache"),
6086
- mode: options.mode,
6087
- select: selectedArtifacts
6088
- });
6089
- const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
6090
- const entry = {
6091
- name,
6092
- source: resolvedSource,
6093
- driver: driverName,
6094
- adapter: adapter.name,
6095
- adapterConfig: options.adapterConfig,
6096
- adapterModule: options.adapterModule,
6097
- adapterCodeHash: adapter.programmatic?.hash,
6098
- mode: options.mode,
6099
- requestedRef: bundle.source.requestedRef,
6100
- select: selectedArtifacts
6101
- };
6251
+ const entry = await packageEntryFromSource(source, targetRoot, options);
6102
6252
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
6103
- await rm9(bundle.root, { recursive: true, force: true });
6104
- console.log(`Added ${name}.`);
6253
+ console.log(`Added ${entry.name}. Preview: agentwheel plan - Apply: agentwheel install`);
6105
6254
  });
6106
- program.command("list").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).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, []).action(async (source, options) => {
6255
+ program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).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, []).action(async (source, options) => {
6107
6256
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6108
6257
  const selectedArtifacts = selectedArtifactsFromOptions(options);
6109
6258
  const resolvedInput = await resolvePackageSource(source, targetRoot);
@@ -6114,7 +6263,7 @@ program.command("list").argument("<source>", "package source").option("--driver
6114
6263
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
6115
6264
  }
6116
6265
  });
6117
- program.command("scan").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
6266
+ program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
6118
6267
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6119
6268
  const resolvedInput = await resolvePackageSource(source, targetRoot);
6120
6269
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
@@ -6129,99 +6278,28 @@ program.command("scan").argument("<source>", "package source").option("--driver
6129
6278
  }
6130
6279
  if (!result.ok) process.exitCode = 1;
6131
6280
  });
6132
- program.command("plan").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--dry-run", "accepted for symmetry; plan never writes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning", false).option("--only-source", "with a source argument, exclude 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6133
- const targets = await resolveCliTargets(options);
6134
- for (const target of targets) {
6135
- if (source && options.noDeps && options.onlySource) {
6136
- const { plan, bundle } = await buildPlan(source, target, options);
6137
- console.log(formatPlan(plan));
6138
- await rm9(bundle.root, { recursive: true, force: true });
6139
- if (plan.hasBlockingChanges) process.exitCode = 1;
6140
- continue;
6141
- }
6142
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6143
- console.log(formatGraphPlan(result));
6144
- await rm9(result.bundle.root, { recursive: true, force: true });
6145
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6146
- }
6147
- }
6281
+ 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").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("--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("--dry-run", "accepted for symmetry; plan never writes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6282
+ await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
6148
6283
  });
6149
- program.command("sync").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning", false).option("--only-source", "with a source argument, exclude 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6150
- if (options.profile) {
6151
- const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
6152
- const results = await syncProfile({
6153
- workspaceRoot: target.workspaceRoot,
6154
- profile: options.profile,
6155
- source,
6156
- driver: options.driver,
6157
- mode: options.mode,
6158
- select: selectedArtifactsFromOptions(options),
6159
- dryRun: options.dryRun,
6160
- executePlugins: options.executePlugins,
6161
- allowAdapterCode: options.allowAdapterCode,
6162
- noDeps: options.noDeps,
6163
- frozenLock: options.frozenLock,
6164
- offline: options.offline,
6165
- yes: options.yes,
6166
- trustPatterns: options.trust ?? [],
6167
- isTTY: process.stdin.isTTY === true,
6168
- warn: (message) => console.warn(message)
6169
- });
6170
- for (const result of results) {
6171
- console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
6172
- console.log(formatPlan(result.plan));
6173
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6174
- }
6175
- if (!options.dryRun) console.log("Applied.");
6176
- return;
6177
- }
6178
- const targets = await resolveCliTargets(options);
6179
- if (!source) {
6180
- for (const target of targets) {
6181
- await runConfiguredGraphPackages(target, options, { useUpdateDecision: false });
6182
- }
6183
- return;
6184
- }
6185
- for (const target of targets) {
6186
- if (options.noDeps && options.onlySource) {
6187
- const { plan, bundle } = await buildPlan(source, target, options);
6188
- console.log(formatPlan(plan));
6189
- if (!options.dryRun) {
6190
- await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins, transport: transportForTarget(target) });
6191
- console.log(`Applied ${target.adapter} at ${target.targetRoot}.`);
6192
- }
6193
- await rm9(bundle.root, { recursive: true, force: true });
6194
- if (plan.hasBlockingChanges) process.exitCode = 1;
6195
- continue;
6196
- }
6197
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6198
- console.log(formatGraphPlan(result));
6199
- if (!options.dryRun) {
6200
- await applyCombinedInstallPlan(result.plan, {
6201
- executePlugins: options.executePlugins,
6202
- transport: transportForTarget(target),
6203
- graphLockDigest: result.graphLockDigest,
6204
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
6205
- });
6206
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
6207
- }
6208
- await rm9(result.bundle.root, { recursive: true, force: true });
6209
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
6210
- }
6211
- }
6284
+ 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").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("--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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude 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("--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) => {
6285
+ await runInstallCommand(source, options, { apply: !options.dryRun });
6286
+ });
6287
+ program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter").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("--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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6288
+ console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
6289
+ await runInstallCommand(source, options, { apply: !options.dryRun });
6212
6290
  });
6213
- program.command("update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning", 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (options) => {
6291
+ program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
6214
6292
  const targets = await resolveCliTargets(options);
6215
6293
  for (const target of targets) {
6216
- await runConfiguredGraphPackages(target, options, { useUpdateDecision: true });
6294
+ await runConfiguredGraphPackages(target, { ...options, scope: name }, { mode: "update" });
6217
6295
  }
6218
6296
  });
6219
6297
  program.command("deps").description("inspect the OpenPack dependency graph").addCommand(
6220
- new Command("tree").argument("[source]", "optional source directory to resolve").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--no-deps", "resolve only root sources and ignore requires with a warning", 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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6298
+ new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--no-deps", "resolve only root sources and ignore requires with a warning").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("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
6221
6299
  const targets = await resolveCliTargets(options);
6222
6300
  for (const target of targets) {
6223
6301
  if (source) {
6224
- for (const result of await buildGraphPlansForTarget(target, source, options, { useUpdateDecision: false })) {
6302
+ for (const result of await buildGraphPlansForTarget(target, source, options, { mode: "install" })) {
6225
6303
  console.log(formatDependencyTree(result.graph).join("\n"));
6226
6304
  for (const decision of result.bundle.graphLock.canonical.namespacing) {
6227
6305
  console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
@@ -6235,7 +6313,7 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
6235
6313
  }
6236
6314
  })
6237
6315
  ).addCommand(
6238
- new Command("why").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (selector, options) => {
6316
+ new Command("why").description("explain why an artifact is installed").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (selector, options) => {
6239
6317
  const targets = await resolveCliTargets(options);
6240
6318
  for (const target of targets) {
6241
6319
  const { lock, adapter } = await readTargetGraphLock(target, options);
@@ -6262,7 +6340,7 @@ program.command("registry").description("manage optional registry indexes").addC
6262
6340
  })
6263
6341
  );
6264
6342
  program.command("trust").description("manage persisted source trust decisions").addCommand(
6265
- new Command("forget").argument("<pattern>", "trusted source glob to revoke").option("--target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
6343
+ new Command("forget").description("forget a persisted trusted source pattern").argument("<pattern>", "trusted source glob to revoke").option("--target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
6266
6344
  const removed = await forgetTrustedSources(normalizeTargetRoot(options.targetRoot), pattern);
6267
6345
  if (removed.length === 0) {
6268
6346
  console.log(`No persisted trust matched ${pattern}.`);
@@ -6289,23 +6367,31 @@ program.command("package").description("validate and migrate OpenPack packages")
6289
6367
  console.log(result.message);
6290
6368
  })
6291
6369
  );
6292
- program.command("remember").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
6370
+ program.command("remember").description("append text to the local instructions overlay").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
6293
6371
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6294
6372
  const result = await remember(targetRoot, options.runtime, text);
6295
- console.log(`Remembered in ${result.overlayPath}. Run: agentwheel sync <source> --adapter ${options.runtime}`);
6373
+ console.log(`Remembered in ${result.overlayPath}.`);
6374
+ console.log(nextInstallNudge());
6296
6375
  });
6297
- program.command("eject").argument("<item>", "package/type/name").option("--target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
6376
+ program.command("eject").description("copy a managed artifact into local ownership").argument("<item>", "package/type/name").option("--target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
6298
6377
  const targetRoot = normalizeTargetRoot(options.targetRoot);
6299
6378
  const result = await ejectArtifact(targetRoot, item);
6300
6379
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
6380
+ console.log(nextInstallNudge());
6301
6381
  });
6302
- program.command("uninstall").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
6382
+ program.command("uninstall").description("remove configured packages or managed runtime files").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--keep-files", "remove from config and manifest but leave runtime files unmanaged", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
6383
+ if (options.keepFiles && options.force) {
6384
+ throw new Error("--keep-files cannot be combined with --force.");
6385
+ }
6303
6386
  const targets = await resolveCliTargets(options);
6304
6387
  for (const target of targets) {
6305
6388
  if (packageName) {
6306
6389
  await uninstallConfiguredPackage(target, packageName, options);
6307
6390
  continue;
6308
6391
  }
6392
+ if (options.keepFiles) {
6393
+ throw new Error("--keep-files requires a configured package name or source.");
6394
+ }
6309
6395
  const adapter = await resolveAdapterForTarget(target, options);
6310
6396
  const transport = transportForTarget(target);
6311
6397
  const manifest = await readInstallManifest(target.targetRoot, adapter.name, transport);
@@ -6326,28 +6412,149 @@ program.command("uninstall").argument("[package]", "configured package name or s
6326
6412
  if (plan.hasBlockingChanges) process.exitCode = 1;
6327
6413
  }
6328
6414
  });
6329
- async function buildPlan(source, target, options) {
6330
- const adapter = await resolveAdapterForTarget(target, options);
6331
- const transport = transportForTarget(target);
6332
- const result = await createSourcePlan({
6333
- source,
6334
- targetRoot: target.targetRoot,
6335
- workspaceRoot: target.workspaceRoot,
6415
+ program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (options) => {
6416
+ const targets = await resolveCliTargets(options);
6417
+ for (const target of targets) {
6418
+ await printStatus(target, options);
6419
+ }
6420
+ });
6421
+ async function runInstallCommand(nameOrSource, options, behavior) {
6422
+ if (options.profile) {
6423
+ const target = await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent });
6424
+ const results = await syncProfile({
6425
+ workspaceRoot: target.workspaceRoot,
6426
+ profile: options.profile,
6427
+ source: nameOrSource,
6428
+ driver: options.driver,
6429
+ mode: options.mode,
6430
+ select: selectedArtifactsFromOptions(options),
6431
+ dryRun: !behavior.apply,
6432
+ executePlugins: options.executePlugins,
6433
+ allowAdapterCode: options.allowAdapterCode,
6434
+ noDeps: noDepsFromOptions(options),
6435
+ lockedResolution: true,
6436
+ frozenLock: options.frozenLock,
6437
+ offline: options.offline,
6438
+ yes: options.yes,
6439
+ trustPatterns: options.trust ?? [],
6440
+ readOnly: !behavior.apply,
6441
+ isTTY: process.stdin.isTTY === true,
6442
+ warn: (message) => console.warn(message)
6443
+ });
6444
+ for (const result of results) {
6445
+ console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
6446
+ console.log(formatPlan(result.plan));
6447
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
6448
+ }
6449
+ if (behavior.apply) console.log("Applied.");
6450
+ return;
6451
+ }
6452
+ const targets = await resolveCliTargets(options);
6453
+ for (const target of targets) {
6454
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6455
+ const configured = nameOrSource ? findConfiguredPackage(config.packages, nameOrSource) : void 0;
6456
+ let source;
6457
+ let scope = configured?.name;
6458
+ let extraPackage;
6459
+ if (nameOrSource && !configured) {
6460
+ try {
6461
+ const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...options, adapter: options.adapter ?? target.adapter });
6462
+ scope = entry.name;
6463
+ if (behavior.apply) {
6464
+ await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
6465
+ } else {
6466
+ source = nameOrSource;
6467
+ extraPackage = entry;
6468
+ }
6469
+ } catch (error) {
6470
+ throw teachingInstallError(nameOrSource, error);
6471
+ }
6472
+ }
6473
+ for (const result of await buildGraphPlansForTarget(target, source, { ...options, scope, extraPackage }, { mode: "install" })) {
6474
+ console.log(formatGraphPlan(result));
6475
+ if (behavior.apply) {
6476
+ await applyCombinedInstallPlan(result.plan, {
6477
+ executePlugins: options.executePlugins,
6478
+ transport: transportForTarget(target),
6479
+ graphLockDigest: result.graphLockDigest,
6480
+ graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
6481
+ });
6482
+ console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
6483
+ }
6484
+ await rm9(result.bundle.root, { recursive: true, force: true });
6485
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
6486
+ }
6487
+ }
6488
+ }
6489
+ async function packageEntryFromSource(source, targetRoot, options) {
6490
+ const selectedArtifacts = selectedArtifactsFromOptions(options);
6491
+ const lockMode = options.frozenLock === true || options.offline === true;
6492
+ const resolvedInput = await resolvePackageSource(source, targetRoot, { offline: lockMode });
6493
+ const resolvedSource = resolvedInput.source;
6494
+ const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
6495
+ const driver = getSourceDriver(driverName);
6496
+ const adapter = await resolveAdapter({
6497
+ adapter: options.adapter ?? "openclaw",
6498
+ adapterConfig: options.adapterConfig,
6499
+ adapterModule: options.adapterModule,
6500
+ allowAdapterCode: options.allowAdapterCode,
6501
+ baseDir: targetRoot,
6502
+ warn: (message) => console.warn(message)
6503
+ });
6504
+ const bundle = await stageSource(driver, resolvedSource, {
6505
+ workspaceRoot: targetRoot,
6336
6506
  adapter,
6337
- driver: options.driver,
6507
+ cacheRoot: join28(targetRoot, ".agentwheel", "cache"),
6338
6508
  mode: options.mode,
6339
- select: selectedArtifactsFromOptions(options),
6340
- frozenLock: options.frozenLock,
6341
- offline: options.offline,
6342
- warn: (message) => console.warn(message),
6343
- transport
6509
+ frozenLock: lockMode,
6510
+ select: selectedArtifacts
6344
6511
  });
6345
- return { plan: result.plan, bundle: result.bundle };
6512
+ try {
6513
+ return {
6514
+ name: options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source,
6515
+ source: resolvedSource,
6516
+ driver: driverName,
6517
+ adapter: adapter.name,
6518
+ adapterConfig: options.adapterConfig,
6519
+ adapterModule: options.adapterModule,
6520
+ adapterCodeHash: adapter.programmatic?.hash,
6521
+ mode: options.mode ?? "pinned",
6522
+ requestedRef: bundle.source.requestedRef,
6523
+ select: selectedArtifacts
6524
+ };
6525
+ } finally {
6526
+ await rm9(bundle.root, { recursive: true, force: true });
6527
+ }
6528
+ }
6529
+ function findConfiguredPackage(packages, value) {
6530
+ return packages.find((pkg) => pkg.name === value || pkg.source === value);
6531
+ }
6532
+ function noDepsFromOptions(options) {
6533
+ return options.noDeps === true || options.deps === false;
6534
+ }
6535
+ function teachingInstallError(input, cause) {
6536
+ const message = cause instanceof Error ? cause.message : String(cause);
6537
+ return new Error(
6538
+ `'${input}' is not a configured package and could not be resolved as a source.
6539
+ To add and install a new package: agentwheel install <source> (e.g. github:org/pack)
6540
+ To see what's configured: agentwheel status
6541
+
6542
+ Resolver error: ${message}`
6543
+ );
6544
+ }
6545
+ function nextInstallNudge() {
6546
+ return "Preview: agentwheel plan - Apply: agentwheel install";
6346
6547
  }
6347
6548
  async function resolveCliTargets(options) {
6549
+ if (options.all && options.allDetected) {
6550
+ throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
6551
+ }
6348
6552
  if (options.all) {
6349
6553
  return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
6350
6554
  }
6555
+ if (options.allDetected) {
6556
+ return resolveAllDetectedRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, allDetected: options.allDetected });
6557
+ }
6351
6558
  return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
6352
6559
  }
6353
6560
  async function resolveAdapterForTarget(target, options) {
@@ -6363,7 +6570,7 @@ async function resolveAdapterForTarget(target, options) {
6363
6570
  async function runConfiguredGraphPackages(target, options, behavior) {
6364
6571
  const results = await buildGraphPlansForTarget(target, void 0, options, behavior);
6365
6572
  for (const result of results) {
6366
- console.log(`${behavior.useUpdateDecision ? "Update" : "Sync"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
6573
+ console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
6367
6574
  console.log(formatGraphPlan(result));
6368
6575
  if (!options.dryRun) {
6369
6576
  await applyCombinedInstallPlan(result.plan, {
@@ -6378,10 +6585,13 @@ async function runConfiguredGraphPackages(target, options, behavior) {
6378
6585
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
6379
6586
  }
6380
6587
  }
6381
- async function buildGraphPlansForTarget(target, source, options, _behavior) {
6588
+ async function buildGraphPlansForTarget(target, source, options, behavior) {
6382
6589
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6383
6590
  const groups = /* @__PURE__ */ new Map();
6384
6591
  const selectedArtifacts = selectedArtifactsFromOptions(options);
6592
+ const scopedPackage = options.scope ? findConfiguredPackage(config.packages, options.scope) : void 0;
6593
+ const scopedRootId = scopedPackage?.name ?? (source ? options.scope : void 0);
6594
+ if (options.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${options.scope}`);
6385
6595
  if (!source || !options.onlySource) {
6386
6596
  for (const pkg of config.packages) {
6387
6597
  const group = graphGroupForPackage(groups, target, pkg, options);
@@ -6403,14 +6613,11 @@ async function buildGraphPlansForTarget(target, source, options, _behavior) {
6403
6613
  allowAdapterCode: options.allowAdapterCode
6404
6614
  },
6405
6615
  packages: [],
6406
- extraRoots: []
6616
+ extraRoots: [],
6617
+ extraPackages: []
6407
6618
  };
6408
- group.extraRoots.push({
6409
- rootId: `source:${source}`,
6410
- source,
6411
- mode: options.mode ?? "pinned",
6412
- select: selectedArtifacts
6413
- });
6619
+ const entry = options.extraPackage ?? await packageEntryFromSource(source, target.workspaceRoot, options);
6620
+ group.extraPackages.push(entry);
6414
6621
  groups.set(key, group);
6415
6622
  }
6416
6623
  if (groups.size === 0) {
@@ -6420,36 +6627,161 @@ async function buildGraphPlansForTarget(target, source, options, _behavior) {
6420
6627
  const results = [];
6421
6628
  for (const group of groups.values()) {
6422
6629
  const adapter = await resolveAdapterForTarget(group.target, group.adapterOptions);
6630
+ const transport = transportForTarget(group.target);
6631
+ const allPackages = [...group.packages, ...group.extraPackages];
6632
+ const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === options.scope);
6633
+ if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
6634
+ const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
6423
6635
  const roots = [
6424
- ...group.packages.map((pkg) => ({
6425
- rootId: pkg.name,
6426
- source: pkg.source,
6427
- mode: pkg.mode,
6428
- ref: pkg.requestedRef,
6429
- select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6430
- aliases: pkg.aliases
6431
- })),
6636
+ ...allPackages.map((pkg) => {
6637
+ const updateThisPackage = behavior.mode === "update" && pkg.mode === "tracking" && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
6638
+ return {
6639
+ rootId: pkg.name,
6640
+ source: pkg.source,
6641
+ mode: pkg.mode,
6642
+ ref: pkg.requestedRef,
6643
+ select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
6644
+ aliases: pkg.aliases,
6645
+ useLock: behavior.mode === "install" ? true : !updateThisPackage
6646
+ };
6647
+ }),
6432
6648
  ...group.extraRoots
6433
6649
  ];
6650
+ if (behavior.mode === "update") {
6651
+ const changed = roots.filter((root) => root.useLock === false);
6652
+ if (changed.length === 0) {
6653
+ const label = options.scope ? ` ${options.scope}` : "";
6654
+ console.log(`No tracking packages to update${label}.`);
6655
+ continue;
6656
+ }
6657
+ }
6434
6658
  if (roots.length === 0) continue;
6435
- results.push(await createGraphSourcePlan({
6659
+ const result = await createGraphSourcePlan({
6436
6660
  roots,
6437
6661
  targetRoot: group.target.targetRoot,
6438
6662
  workspaceRoot: group.target.workspaceRoot,
6439
6663
  adapter,
6440
- transport: transportForTarget(group.target),
6664
+ transport,
6441
6665
  targetKey: group.target.agentName ?? group.target.source,
6442
6666
  targetFingerprintParts: targetFingerprintParts(group.target, adapter, group.adapterOptions),
6443
- noDeps: options.noDeps,
6667
+ noDeps: noDepsFromOptions(options),
6668
+ lockedResolution: behavior.mode === "install",
6444
6669
  frozenLock: options.frozenLock,
6445
6670
  offline: options.offline,
6446
6671
  yes: options.yes,
6447
6672
  trustPatterns: options.trust ?? [],
6673
+ readOnly: options.dryRun === true,
6448
6674
  isTTY: process.stdin.isTTY === true
6449
- }));
6675
+ });
6676
+ if (behavior.mode === "install" && scopedRootId) {
6677
+ const manifest = await readInstallManifest(group.target.targetRoot, adapter.name, transport);
6678
+ results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
6679
+ } else {
6680
+ results.push(result);
6681
+ }
6450
6682
  }
6451
6683
  return results;
6452
6684
  }
6685
+ function scopeInstallPlanToRoot(result, rootId, manifest) {
6686
+ const scopedOwners = scopedGraphOwnerKeys(result, rootId);
6687
+ const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
6688
+ const preservedPaths = /* @__PURE__ */ new Set();
6689
+ const plannedPaths = /* @__PURE__ */ new Set();
6690
+ const operations = [];
6691
+ for (const operation of result.plan.operations) {
6692
+ plannedPaths.add(operation.relativeDestPath);
6693
+ if (operationBelongsToScopedRoot(operation, scopedOwners)) {
6694
+ operations.push(operation);
6695
+ continue;
6696
+ }
6697
+ const entry = manifestByPath.get(operation.relativeDestPath);
6698
+ const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, rootId);
6699
+ for (const scopedOperation of transformed) {
6700
+ if (preservedPaths.has(scopedOperation.relativeDestPath)) continue;
6701
+ preservedPaths.add(scopedOperation.relativeDestPath);
6702
+ operations.push(scopedOperation);
6703
+ }
6704
+ }
6705
+ for (const entry of manifest?.entries ?? []) {
6706
+ if (plannedPaths.has(entry.path) || preservedPaths.has(entry.path) || entryBelongsToScopedRoot(entry, scopedOwners)) continue;
6707
+ preservedPaths.add(entry.path);
6708
+ operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, rootId));
6709
+ }
6710
+ return {
6711
+ ...result,
6712
+ plan: {
6713
+ ...result.plan,
6714
+ operations,
6715
+ hasBlockingChanges: operations.some((operation) => operation.action === "drift" || operation.action === "conflict")
6716
+ }
6717
+ };
6718
+ }
6719
+ function transformOutOfScopeOperation(operation, entry, targetRoot, rootId) {
6720
+ if (operation.action === "skip") return [operation];
6721
+ if (operation.action === "update" || operation.action === "drift") {
6722
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId, operation, { freshMetadata: true })] : [];
6723
+ }
6724
+ if (operation.action === "remove") {
6725
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
6726
+ }
6727
+ if (operation.action === "plugin" || operation.action === "program") {
6728
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
6729
+ }
6730
+ return [];
6731
+ }
6732
+ function scopedGraphOwnerKeys(result, rootId) {
6733
+ const root = result.graph.roots.find((candidate) => candidate.rootId === rootId);
6734
+ if (!root) throw new Error(`Resolved graph root not found for scoped install: ${rootId}`);
6735
+ const keys = /* @__PURE__ */ new Set([`workspace:${rootId}`]);
6736
+ const queue = [root.graphNodeId];
6737
+ while (queue.length > 0) {
6738
+ const nodeId = queue.shift();
6739
+ if (keys.has(nodeId)) continue;
6740
+ keys.add(nodeId);
6741
+ for (const edge of result.graph.edges) {
6742
+ if (edge.from === nodeId) queue.push(edge.to);
6743
+ }
6744
+ }
6745
+ return keys;
6746
+ }
6747
+ function operationBelongsToScopedRoot(operation, scopedOwners) {
6748
+ if (operation.graphNodeId && scopedOwners.has(operation.graphNodeId)) return true;
6749
+ return operation.owners?.some((owner) => scopedOwners.has(owner)) === true;
6750
+ }
6751
+ function entryBelongsToScopedRoot(entry, scopedOwners) {
6752
+ if ("graphNodeId" in entry && entry.graphNodeId && scopedOwners.has(entry.graphNodeId)) return true;
6753
+ const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
6754
+ return owners.some((owner) => scopedOwners.has(owner));
6755
+ }
6756
+ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, options = {}) {
6757
+ const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
6758
+ const fresh = options.freshMetadata ? operation : void 0;
6759
+ return {
6760
+ action: "keep",
6761
+ artifactType: entry.artifactType,
6762
+ artifactName: entry.artifactName,
6763
+ kind: entry.kind,
6764
+ destPath: operation?.destPath ?? join28(targetRoot, entry.path),
6765
+ relativeDestPath: entry.path,
6766
+ desiredHash: entry.sourceHash,
6767
+ currentHash: operation?.currentHash ?? entry.hash,
6768
+ manifestHash: entry.hash,
6769
+ reason: `preserved outside scoped install ${rootId}`,
6770
+ channel: entry.channel,
6771
+ packageName: entry.packageName,
6772
+ semanticCommand: entry.semanticCommand,
6773
+ execute: entry.executed,
6774
+ mergeStrategy: entry.mergeStrategy,
6775
+ composedFrom: entry.composedFrom,
6776
+ installName: fresh?.installName ?? ("installName" in entry ? entry.installName : entry.artifactName),
6777
+ logicalSelector: fresh?.logicalSelector ?? ("logicalSelector" in entry ? entry.logicalSelector : `${entry.artifactType}/${entry.artifactName}`),
6778
+ graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
6779
+ dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
6780
+ owners: fresh?.owners ?? owners,
6781
+ workspaceOwner: fresh?.workspaceOwner ?? ("workspaceOwner" in entry ? entry.workspaceOwner : "legacy:unowned"),
6782
+ graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
6783
+ };
6784
+ }
6453
6785
  async function uninstallConfiguredPackage(target, packageName, options) {
6454
6786
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6455
6787
  const removed = config.packages.filter((pkg) => pkg.name === packageName || pkg.source === packageName);
@@ -6498,10 +6830,12 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6498
6830
  transport,
6499
6831
  targetKey: remainingGroup.target.agentName ?? remainingGroup.target.source,
6500
6832
  targetFingerprintParts: targetFingerprintParts(remainingGroup.target, remainingAdapter, remainingGroup.adapterOptions),
6833
+ lockedResolution: true,
6501
6834
  frozenLock: options.frozenLock,
6502
6835
  offline: options.offline,
6503
6836
  yes: options.yes,
6504
6837
  trustPatterns: options.trust ?? [],
6838
+ readOnly: options.dryRun === true,
6505
6839
  isTTY: process.stdin.isTTY === true
6506
6840
  });
6507
6841
  remainingGraphPlan = result2;
@@ -6522,6 +6856,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
6522
6856
  const result = await uninstall(plan, {
6523
6857
  dryRun: options.dryRun,
6524
6858
  force: options.force,
6859
+ keepFiles: options.keepFiles,
6525
6860
  transport,
6526
6861
  ...graphLockFinalState,
6527
6862
  workspaceConfig: {
@@ -6550,7 +6885,8 @@ function graphGroupForPackage(groups, target, pkg, options) {
6550
6885
  target: packageTarget,
6551
6886
  adapterOptions,
6552
6887
  packages: [],
6553
- extraRoots: []
6888
+ extraRoots: [],
6889
+ extraPackages: []
6554
6890
  };
6555
6891
  groups.set(key, created);
6556
6892
  return created;
@@ -6593,6 +6929,53 @@ async function readTargetGraphLock(target, options) {
6593
6929
  }
6594
6930
  return { adapter, path, lock: await readGraphLock(path) };
6595
6931
  }
6932
+ async function printStatus(target, options) {
6933
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
6934
+ const adapter = await resolveAdapterForTarget(target, options);
6935
+ const transport = transportForTarget(target);
6936
+ console.log(`Status for ${adapter.name} at ${target.targetRoot}`);
6937
+ if (config.packages.length === 0) {
6938
+ console.log(`Configured packages: none at ${target.workspaceRoot}`);
6939
+ return;
6940
+ }
6941
+ console.log("Configured packages:");
6942
+ for (const pkg of config.packages) {
6943
+ console.log(`- ${pkg.name} (${pkg.mode}) ${pkg.source}`);
6944
+ }
6945
+ const manifest = await readInstallManifest(target.targetRoot, adapter.name, transport);
6946
+ console.log(manifest ? `Install manifest: ${manifest.entries.length} entries, revision ${manifest.revision}` : "Install manifest: missing");
6947
+ try {
6948
+ const { path, lock } = await readTargetGraphLock(target, options);
6949
+ console.log(`Graph lock: ${path}`);
6950
+ console.log(`Locked graph: ${lock.canonical.roots.length} roots, ${lock.canonical.nodes.length} nodes, ${lock.canonical.artifacts.length} artifacts`);
6951
+ } catch {
6952
+ console.log("Graph lock: missing");
6953
+ }
6954
+ await printPendingInstallWork(target, options);
6955
+ }
6956
+ async function printPendingInstallWork(target, options) {
6957
+ let results = [];
6958
+ try {
6959
+ results = await buildGraphPlansForTarget(target, void 0, { ...options, dryRun: true }, { mode: "install" });
6960
+ const operations = results.flatMap((result) => result.plan.operations);
6961
+ const pending = operations.filter((operation) => operation.action !== "skip");
6962
+ if (pending.length === 0) {
6963
+ console.log("Pending install work: none");
6964
+ return;
6965
+ }
6966
+ const counts = [...pending.reduce((map, operation) => {
6967
+ map.set(operation.action, (map.get(operation.action) ?? 0) + 1);
6968
+ return map;
6969
+ }, /* @__PURE__ */ new Map())].map(([action, count]) => `${action}=${count}`).join(", ");
6970
+ const blocking = pending.filter((operation) => operation.action === "conflict" || operation.action === "drift").length;
6971
+ console.log(`Pending install work: ${pending.length} operations (${counts}${blocking ? `; blocking=${blocking}` : ""})`);
6972
+ } catch (error) {
6973
+ const message = error instanceof Error ? error.message : String(error);
6974
+ console.log(`Pending install work: unavailable (${message})`);
6975
+ } finally {
6976
+ await Promise.all(results.map((result) => rm9(result.bundle.root, { recursive: true, force: true })));
6977
+ }
6978
+ }
6596
6979
  function collectSelectOption(value, previous) {
6597
6980
  return [...previous, ...splitSelectorList(value)];
6598
6981
  }
@@ -6692,7 +7075,7 @@ function withFleetExample(config) {
6692
7075
  };
6693
7076
  }
6694
7077
  async function findAgentwheelPackageRoot(start) {
6695
- let current = resolve17(start);
7078
+ let current = resolve18(start);
6696
7079
  while (true) {
6697
7080
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
6698
7081
  const parent = dirname21(current);