agentwheel 0.14.8 → 0.14.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,13 +6,14 @@ import {
6
6
  isIgnoredGeneratedEntry,
7
7
  pathExists,
8
8
  writeJsonAtomic
9
- } from "./chunk-QJTTISLY.js";
9
+ } from "./chunk-PKAPR55N.js";
10
10
 
11
11
  // src/cli/index.ts
12
+ import { createHash as createHash11 } from "crypto";
12
13
  import { existsSync } from "fs";
13
- import { mkdir as mkdir21, rm as rm11, writeFile as writeFile20 } from "fs/promises";
14
+ import { mkdir as mkdir22, rm as rm11, writeFile as writeFile21 } from "fs/promises";
14
15
  import { homedir as homedir9 } from "os";
15
- import { dirname as dirname30, join as join41, resolve as resolve20 } from "path";
16
+ import { dirname as dirname31, join as join41, resolve as resolve20 } from "path";
16
17
  import { fileURLToPath as fileURLToPath3 } from "url";
17
18
  import { Command } from "commander";
18
19
 
@@ -485,9 +486,9 @@ async function resolveAdapter(options) {
485
486
 
486
487
  // src/install/apply.ts
487
488
  import { execFile as execFile3 } from "child_process";
488
- import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile9 } from "fs/promises";
489
+ import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile10 } from "fs/promises";
489
490
  import { tmpdir as tmpdir3 } from "os";
490
- import { basename as basename3, dirname as dirname10, join as join5 } from "path";
491
+ import { basename as basename3, dirname as dirname11, join as join5 } from "path";
491
492
  import { promisify as promisify3 } from "util";
492
493
 
493
494
  // src/model/graph-lock.ts
@@ -518,7 +519,18 @@ var graphLockRootSchema = z3.object({
518
519
  mode: z3.enum(["pinned", "tracking"]),
519
520
  selected: z3.array(z3.string().min(1)),
520
521
  aliases: z3.record(z3.string(), z3.string().min(1)).optional(),
521
- overrides: z3.array(z3.string().min(1)).optional()
522
+ overrides: z3.array(z3.string().min(1)).optional(),
523
+ selectionImport: z3.object({
524
+ configPath: z3.string().min(1),
525
+ configHash: z3.string().min(16),
526
+ exportHash: z3.string().min(16),
527
+ exportName: z3.string().min(1),
528
+ extends: z3.array(z3.string().min(1)),
529
+ inherited: z3.array(z3.string().min(1)),
530
+ additions: z3.array(z3.string().min(1)),
531
+ exclusions: z3.array(z3.string().min(1)),
532
+ effective: z3.array(z3.string().min(1))
533
+ }).optional()
522
534
  });
523
535
  var graphLockEdgeSchema = z3.object({
524
536
  from: z3.string().min(1),
@@ -618,7 +630,14 @@ function canonicalizeGraphLock(lock) {
618
630
  roots: [...parsed.canonical.roots].map((root) => ({
619
631
  ...root,
620
632
  selected: sortedUnique(root.selected),
621
- overrides: root.overrides ? sortedUnique(root.overrides) : void 0
633
+ overrides: root.overrides ? sortedUnique(root.overrides) : void 0,
634
+ selectionImport: root.selectionImport ? {
635
+ ...root.selectionImport,
636
+ inherited: sortedUnique(root.selectionImport.inherited),
637
+ additions: sortedUnique(root.selectionImport.additions),
638
+ exclusions: sortedUnique(root.selectionImport.exclusions),
639
+ effective: sortedUnique(root.selectionImport.effective)
640
+ } : void 0
622
641
  })).sort((a, b) => `${a.rootId}:${a.graphNodeId}`.localeCompare(`${b.rootId}:${b.graphNodeId}`)),
623
642
  nodes: [...parsed.canonical.nodes].map((node) => ({
624
643
  ...node,
@@ -999,6 +1018,15 @@ import { resolve as resolve3 } from "path";
999
1018
 
1000
1019
  // src/model/manifest.ts
1001
1020
  import { z as z4 } from "zod";
1021
+ var mergeValueSchema = z4.lazy(() => z4.union([
1022
+ z4.null(),
1023
+ z4.boolean(),
1024
+ z4.number(),
1025
+ z4.string(),
1026
+ z4.array(mergeValueSchema),
1027
+ z4.record(z4.string(), mergeValueSchema)
1028
+ ]));
1029
+ var mergeRemovalSchema = z4.record(z4.string(), mergeValueSchema);
1002
1030
  var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
1003
1031
  var legacyUnownedWorkspaceOwner = "legacy:unowned";
1004
1032
  var semanticPluginSpecSchema = z4.object({
@@ -1025,6 +1053,8 @@ var manifestEntryV1Schema = z4.object({
1025
1053
  semanticPlugin: semanticPluginSpecSchema.optional(),
1026
1054
  executed: z4.boolean().optional(),
1027
1055
  mergeStrategy: z4.enum(["json-deep", "openclaw-json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
1056
+ mergeRemoval: mergeRemovalSchema.optional(),
1057
+ mergeCreatedDestination: z4.boolean().optional(),
1028
1058
  mode: z4.enum(["managed-block"]).optional(),
1029
1059
  composedFrom: z4.array(composedFromEntrySchema).optional()
1030
1060
  });
@@ -1270,6 +1300,23 @@ async function removeApplyJournal(targetRoot, adapter, transport = localTranspor
1270
1300
  await transport.rm(applyJournalPath(targetRoot, adapter, scope));
1271
1301
  await transport.rm(applyBackupDir(targetRoot, adapter, scope));
1272
1302
  }
1303
+ async function abortApplyJournal(targetRoot, adapter, transport = localTransport, scope = {}) {
1304
+ const lock = await acquireApplyLock(targetRoot, adapter, transport, {}, scope);
1305
+ try {
1306
+ const journalPath = applyJournalPath(targetRoot, adapter, scope);
1307
+ if (!await transport.pathExists(journalPath)) return void 0;
1308
+ const stateKey = stateKeyFor(adapter, scope);
1309
+ const archivePath = join3(metadataDir(targetRoot), "archive", `${stateKey}.apply-journal.failed-${journalTimestamp(/* @__PURE__ */ new Date())}.json`);
1310
+ const content = await transport.readFile(journalPath);
1311
+ await transport.writeFileAtomic(archivePath, content.endsWith("\n") ? content : `${content}
1312
+ `);
1313
+ await transport.rm(journalPath);
1314
+ await transport.rm(applyBackupDir(targetRoot, adapter, scope));
1315
+ return { journalPath, archivePath };
1316
+ } finally {
1317
+ await lock.release();
1318
+ }
1319
+ }
1273
1320
  async function recordBackup(operation, index, targetRoot, adapter, transport = localTransport, scope = {}) {
1274
1321
  const hadExisting = await transport.pathExists(operation.destPath);
1275
1322
  if (!hadExisting || transport.kind !== "local" || operation.action !== "update" && operation.action !== "remove" && operation.action !== "create") {
@@ -1326,6 +1373,9 @@ async function readLockOwner(ownerPath, transport) {
1326
1373
  function isAlreadyExists(error) {
1327
1374
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
1328
1375
  }
1376
+ function journalTimestamp(date) {
1377
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
1378
+ }
1329
1379
  async function localPathExists(path) {
1330
1380
  try {
1331
1381
  await stat2(path);
@@ -1482,6 +1532,123 @@ function dedupeArray2(values) {
1482
1532
  return out;
1483
1533
  }
1484
1534
 
1535
+ // src/install/merge-removal.ts
1536
+ import { mkdir as mkdir8, readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
1537
+ import { dirname as dirname9 } from "path";
1538
+ import { parse as parse3, stringify as stringify2 } from "yaml";
1539
+ async function mergeRemovalForInstall(sourcePath, strategy, currentContent) {
1540
+ const source = await readMergeSource(sourcePath, strategy);
1541
+ if (currentContent === void 0) return source;
1542
+ if (strategy === "codex-toml-mcp") {
1543
+ const existingServers = codexTomlMcpServerNames(currentContent);
1544
+ const servers = isRecord4(source.mcpServers) ? source.mcpServers : source;
1545
+ return { mcpServers: Object.fromEntries(Object.entries(servers).filter(([name]) => !existingServers.has(name))) };
1546
+ }
1547
+ return introducedMergeContent(parseMergeDestination(currentContent, strategy), source);
1548
+ }
1549
+ async function removeMergeContribution(destPath, strategy, removal) {
1550
+ const content = await readFile9(destPath, "utf8");
1551
+ if (strategy === "codex-toml-mcp") {
1552
+ const servers = isRecord4(removal.mcpServers) ? removal.mcpServers : removal;
1553
+ await writeFile8(destPath, removeCodexTomlMcpSections(content, Object.keys(servers)), "utf8");
1554
+ return;
1555
+ }
1556
+ const current = parseMergeDestination(content, strategy);
1557
+ removeIntroducedContent(current, removal);
1558
+ await mkdir8(dirname9(destPath), { recursive: true });
1559
+ await writeFile8(destPath, strategy === "yaml-deep" ? stringify2(current) : `${JSON.stringify(current, null, 2)}
1560
+ `, "utf8");
1561
+ }
1562
+ async function readMergeSource(sourcePath, strategy) {
1563
+ const content = await readFile9(sourcePath, "utf8");
1564
+ if (strategy === "yaml-deep") return requireRecord(normalizeYamlValue2(parse3(content)), "YAML merge source");
1565
+ return requireRecord(JSON.parse(content), "JSON merge source");
1566
+ }
1567
+ function parseMergeDestination(content, strategy) {
1568
+ if (strategy === "yaml-deep") return requireRecord(normalizeYamlValue2(parse3(content)), "YAML merge destination");
1569
+ return requireRecord(JSON.parse(content), "JSON merge destination");
1570
+ }
1571
+ function introducedMergeContent(base, incoming) {
1572
+ const introduced = {};
1573
+ for (const [key, incomingValue] of Object.entries(incoming)) {
1574
+ if (!(key in base)) {
1575
+ introduced[key] = incomingValue;
1576
+ continue;
1577
+ }
1578
+ const baseValue = base[key];
1579
+ if (isRecord4(baseValue) && isRecord4(incomingValue)) {
1580
+ const nested = introducedMergeContent(baseValue, incomingValue);
1581
+ if (Object.keys(nested).length > 0) introduced[key] = nested;
1582
+ } else if (Array.isArray(baseValue) && Array.isArray(incomingValue)) {
1583
+ const additions = incomingValue.filter((value) => !baseValue.some((existing) => sameValue(existing, value)));
1584
+ if (additions.length > 0) introduced[key] = additions;
1585
+ }
1586
+ }
1587
+ return introduced;
1588
+ }
1589
+ function removeIntroducedContent(current, removal) {
1590
+ for (const [key, removalValue] of Object.entries(removal)) {
1591
+ if (!(key in current)) continue;
1592
+ const currentValue = current[key];
1593
+ if (isRecord4(currentValue) && isRecord4(removalValue)) {
1594
+ removeIntroducedContent(currentValue, removalValue);
1595
+ if (Object.keys(currentValue).length === 0) delete current[key];
1596
+ } else if (Array.isArray(currentValue) && Array.isArray(removalValue)) {
1597
+ const remaining = currentValue.filter((value) => !removalValue.some((removed) => sameValue(removed, value)));
1598
+ if (remaining.length === 0) delete current[key];
1599
+ else current[key] = remaining;
1600
+ } else delete current[key];
1601
+ }
1602
+ }
1603
+ function codexTomlMcpServerNames(content) {
1604
+ const names = /* @__PURE__ */ new Set();
1605
+ for (const line of content.split(/\r?\n/)) {
1606
+ const section = line.match(/^\s*\[([^\]]+)]\s*$/)?.[1];
1607
+ const match = section?.match(/^mcp_servers\.([^.\]]+)(?:\.|$)/);
1608
+ if (match?.[1]) names.add(unquoteTomlKey2(match[1]));
1609
+ }
1610
+ return names;
1611
+ }
1612
+ function removeCodexTomlMcpSections(content, serverNames) {
1613
+ if (serverNames.length === 0 || content.trim() === "") return content;
1614
+ const names = new Set(serverNames);
1615
+ const kept = [];
1616
+ let skipping = false;
1617
+ for (const line of content.split(/\r?\n/)) {
1618
+ const section = line.match(/^\s*\[([^\]]+)]\s*$/)?.[1];
1619
+ if (section) {
1620
+ const match = section.match(/^mcp_servers\.([^.\]]+)(?:\.|$)/);
1621
+ skipping = match ? names.has(unquoteTomlKey2(match[1] ?? "")) : false;
1622
+ }
1623
+ if (!skipping) kept.push(line);
1624
+ }
1625
+ return kept.join("\n").replace(/\n{3,}$/g, "\n\n");
1626
+ }
1627
+ function normalizeYamlValue2(value) {
1628
+ if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") return value;
1629
+ if (Array.isArray(value)) return value.map(normalizeYamlValue2);
1630
+ if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, normalizeYamlValue2(child)]));
1631
+ return value === void 0 ? null : String(value);
1632
+ }
1633
+ function requireRecord(value, label) {
1634
+ if (!isRecord4(value)) throw new Error(`${label} must be an object`);
1635
+ return value;
1636
+ }
1637
+ function isRecord4(value) {
1638
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1639
+ }
1640
+ function sameValue(left, right) {
1641
+ return JSON.stringify(left) === JSON.stringify(right);
1642
+ }
1643
+ function unquoteTomlKey2(key) {
1644
+ if (!key.startsWith('"')) return key;
1645
+ try {
1646
+ return JSON.parse(key);
1647
+ } catch {
1648
+ return key;
1649
+ }
1650
+ }
1651
+
1485
1652
  // src/install/desired.ts
1486
1653
  function normalizeOwners(owners) {
1487
1654
  const normalized = [...new Set(owners.map((owner) => owner.trim()).filter(Boolean))].sort();
@@ -1493,13 +1660,13 @@ function normalizeOwners(owners) {
1493
1660
 
1494
1661
  // src/install/instructions-block.ts
1495
1662
  import { createHash as createHash3 } from "crypto";
1496
- import { mkdtemp, readFile as readFile9, realpath, rm as rm3, writeFile as writeFile8 } from "fs/promises";
1663
+ import { mkdtemp, readFile as readFile10, realpath, rm as rm3, writeFile as writeFile9 } from "fs/promises";
1497
1664
  import { tmpdir as tmpdir2 } from "os";
1498
- import { basename as basename2, dirname as dirname9, join as join4, relative as relative2 } from "path";
1665
+ import { basename as basename2, dirname as dirname10, join as join4, relative as relative2 } from "path";
1499
1666
  var managedInstructionBlockMode = "managed-block";
1500
1667
  var managedInstructionBanner = "<!-- agentwheel-managed: edit fragments, not this block -->";
1501
1668
  async function desiredManagedInstructionBlockHash(sourcePath) {
1502
- const source = await readFile9(sourcePath, "utf8");
1669
+ const source = await readFile10(sourcePath, "utf8");
1503
1670
  return hashText(managedBlockBody(source));
1504
1671
  }
1505
1672
  async function readManagedInstructionBlockState(destPath, selector, transport) {
@@ -1519,7 +1686,7 @@ async function readManagedInstructionBlockState(destPath, selector, transport) {
1519
1686
  };
1520
1687
  }
1521
1688
  async function writeManagedInstructionBlock(sourcePath, destPath, selector, transport, options = {}) {
1522
- const source = await readFile9(sourcePath, "utf8");
1689
+ const source = await readFile10(sourcePath, "utf8");
1523
1690
  const desired = renderManagedInstructionBlock(selector, source);
1524
1691
  const existing = await readOptionalText(destPath, transport);
1525
1692
  const merged = upsertManagedInstructionBlock(existing ?? "", selector, desired.block, options);
@@ -1631,7 +1798,7 @@ function assertCleanBlock(block, selector) {
1631
1798
  }
1632
1799
  }
1633
1800
  function referencesAgentsMd(content, claudePath, agentsPath) {
1634
- const claudeDir = dirname9(claudePath);
1801
+ const claudeDir = dirname10(claudePath);
1635
1802
  for (const rawLine of content.split(/\r?\n/)) {
1636
1803
  const line = rawLine.trim();
1637
1804
  const atImport = /^@import\s+(.+)$/i.exec(line);
@@ -1641,7 +1808,7 @@ function referencesAgentsMd(content, claudePath, agentsPath) {
1641
1808
  const cleaned = referenced.trim().replace(/^["']|["']$/g, "");
1642
1809
  if (!/AGENTS\.md$/i.test(cleaned)) continue;
1643
1810
  const candidate = cleaned.startsWith("/") ? cleaned : join4(claudeDir, cleaned);
1644
- if (relative2(dirname9(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1811
+ if (relative2(dirname10(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1645
1812
  if (candidate === agentsPath) return true;
1646
1813
  }
1647
1814
  return false;
@@ -1663,7 +1830,7 @@ async function writeTextWithTransport(path, content, transport) {
1663
1830
  const tempRoot = await mkdtemp(join4(tmpdir2(), "agentwheel-instructions-"));
1664
1831
  const localPath = join4(tempRoot, basename2(path) || "instructions.md");
1665
1832
  try {
1666
- await writeFile8(localPath, content, "utf8");
1833
+ await writeFile9(localPath, content, "utf8");
1667
1834
  await transport.atomicCopy(localPath, path, "file");
1668
1835
  } finally {
1669
1836
  await rm3(tempRoot, { recursive: true, force: true });
@@ -1813,7 +1980,7 @@ async function uninstall(plan, options = {}) {
1813
1980
  const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep" && isForceRemovableKeep(operation)).length : 0;
1814
1981
  if (resolvedOptions.dryRun) return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1815
1982
  const preservedKept = resolvedOptions.keepFiles ? kept.filter((operation) => shouldPreserveKeptOperationWhenKeepingFiles(operation)) : kept;
1816
- const preserved = [...preservedKept, ...skipped];
1983
+ const preserved = [...preservedKept, ...skipped].filter((operation) => operation.preserveInManifest !== false);
1817
1984
  for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
1818
1985
  const now = (/* @__PURE__ */ new Date()).toISOString();
1819
1986
  const finalManifest = withManifestRevision({
@@ -2017,6 +2184,12 @@ async function applyOperation(operation, context) {
2017
2184
  if (operation.mode === managedInstructionBlockMode) {
2018
2185
  const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
2019
2186
  await removeManagedInstructionBlock(operation.destPath, selector, transport, managedBlockMutationOptions(operation));
2187
+ } else if (operation.mergeStrategy) {
2188
+ if (operation.mergeCreatedDestination) {
2189
+ await transport.rm(operation.destPath);
2190
+ } else if (operation.mergeRemoval) {
2191
+ await removeMergeWithTransport(operation.destPath, operation.mergeStrategy, operation.mergeRemoval, transport);
2192
+ }
2020
2193
  } else {
2021
2194
  await transport.rm(operation.destPath);
2022
2195
  }
@@ -2038,7 +2211,12 @@ async function executePluginInstall(operation, transport) {
2038
2211
  async function executePluginUninstall(operation, transport) {
2039
2212
  const commands = operation.semanticPlugin?.uninstallCommands ?? [];
2040
2213
  if (commands.length === 0) throw new Error(`Invalid semantic plugin operation missing uninstall command: ${operation.relativeDestPath}`);
2041
- await executeSemanticCommands(operation, commands, transport);
2214
+ try {
2215
+ await executeSemanticCommands(operation, commands, transport);
2216
+ } catch (error) {
2217
+ if (!isPluginAlreadyAbsentError(operation, error)) throw error;
2218
+ console.warn(`WARNING plugin-already-absent ${operation.relativeDestPath}: ${firstErrorLine(error)}`);
2219
+ }
2042
2220
  if (operation.semanticPlugin?.stateRoot) {
2043
2221
  await transport.rm(operation.semanticPlugin.stateRoot);
2044
2222
  }
@@ -2185,6 +2363,35 @@ async function executeSemanticCommands(operation, commands, transport, options =
2185
2363
  await transport.rm(stagingRoot);
2186
2364
  }
2187
2365
  }
2366
+ function isPluginAlreadyAbsentError(operation, error) {
2367
+ const output = commandErrorOutput(error).toLowerCase();
2368
+ if (!output || output.includes("command not found") || output.includes("module not found")) return false;
2369
+ const pluginName = (operation.semanticPlugin?.pluginName ?? operation.artifactName).toLowerCase();
2370
+ const escapedName = escapeRegExp(pluginName);
2371
+ const namedAbsent = [
2372
+ new RegExp(`${escapedName}.{0,120}\\b(not installed|not found|does not exist|absent)\\b`, "s"),
2373
+ new RegExp(`\\b(not installed|not found|does not exist|absent)\\b.{0,120}${escapedName}`, "s"),
2374
+ new RegExp(`\\bunknown plugin\\b.{0,120}${escapedName}`, "s"),
2375
+ new RegExp(`${escapedName}.{0,120}\\bunknown plugin\\b`, "s")
2376
+ ].some((pattern) => pattern.test(output));
2377
+ const genericAbsent = /\b(no such|unknown)\s+plugins?\b/.test(output) || /\bplugins?\b.{0,80}\bnot installed\b/s.test(output);
2378
+ return namedAbsent || genericAbsent;
2379
+ }
2380
+ function commandErrorOutput(error) {
2381
+ if (typeof error === "object" && error !== null) {
2382
+ const stderr = "stderr" in error ? String(error.stderr ?? "") : "";
2383
+ if (stderr.trim()) return stderr;
2384
+ const stdout = "stdout" in error ? String(error.stdout ?? "") : "";
2385
+ if (stdout.trim()) return stdout;
2386
+ }
2387
+ return error instanceof Error ? error.message : String(error);
2388
+ }
2389
+ function firstErrorLine(error) {
2390
+ return commandErrorOutput(error).split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "plugin is already absent";
2391
+ }
2392
+ function escapeRegExp(value) {
2393
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2394
+ }
2188
2395
  function semanticInstallCommands(operation) {
2189
2396
  if (operation.semanticPlugin) return operation.semanticPlugin.installCommands;
2190
2397
  return operation.semanticCommand ? [operation.semanticCommand] : [];
@@ -2248,6 +2455,8 @@ function manifestEntryForOperation(operation, values) {
2248
2455
  semanticPlugin: operation.semanticPlugin,
2249
2456
  executed: values.executed ?? operation.execute,
2250
2457
  mergeStrategy: operation.mergeStrategy,
2458
+ mergeRemoval: operation.mergeRemoval,
2459
+ mergeCreatedDestination: operation.mergeCreatedDestination,
2251
2460
  mode: operation.mode,
2252
2461
  composedFrom: operation.composedFrom,
2253
2462
  graphLockDigest: operation.graphLockDigest ?? values.graphLockDigest
@@ -2308,7 +2517,7 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2308
2517
  const localDest = join5(tempRoot, basename3(destPath) || "merged");
2309
2518
  try {
2310
2519
  if (await transport.pathExists(destPath)) {
2311
- await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2520
+ await writeFile10(localDest, await transport.readFile(destPath), "utf8");
2312
2521
  }
2313
2522
  await merge(sourcePath, localDest);
2314
2523
  await transport.atomicCopy(localDest, destPath, "file");
@@ -2316,13 +2525,28 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2316
2525
  await rm4(tempRoot, { recursive: true, force: true });
2317
2526
  }
2318
2527
  }
2528
+ async function removeMergeWithTransport(destPath, strategy, removal, transport) {
2529
+ if (transport.kind === "local") {
2530
+ await removeMergeContribution(destPath, strategy, removal);
2531
+ return;
2532
+ }
2533
+ const tempRoot = await mkdtemp2(join5(tmpdir3(), "agentwheel-merge-remove-"));
2534
+ const localDest = join5(tempRoot, basename3(destPath) || "merged");
2535
+ try {
2536
+ await writeFile10(localDest, await transport.readFile(destPath), "utf8");
2537
+ await removeMergeContribution(localDest, strategy, removal);
2538
+ await transport.atomicCopy(localDest, destPath, "file");
2539
+ } finally {
2540
+ await rm4(tempRoot, { recursive: true, force: true });
2541
+ }
2542
+ }
2319
2543
  async function mergeOpenClawJsonWithTransport(sourcePath, destPath, transport) {
2320
2544
  const tempRoot = await mkdtemp2(join5(tmpdir3(), "agentwheel-openclaw-merge-"));
2321
2545
  const localDest = join5(tempRoot, basename3(destPath) || "openclaw.json");
2322
2546
  const validationPath = transport.kind === "local" ? localDest : `${destPath}.validate-agentwheel-${process.pid}-${Date.now()}`;
2323
2547
  try {
2324
2548
  if (await transport.pathExists(destPath)) {
2325
- await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2549
+ await writeFile10(localDest, await transport.readFile(destPath), "utf8");
2326
2550
  }
2327
2551
  await mergeOpenClawJsonFile(sourcePath, localDest);
2328
2552
  if (transport.kind !== "local") {
@@ -2339,7 +2563,7 @@ async function validateOpenClawConfig(configPath, destPath, transport) {
2339
2563
  if (!transport.execFile) {
2340
2564
  throw new Error(`Cannot validate OpenClaw config over ${transport.description}: transport does not support command execution.`);
2341
2565
  }
2342
- const openClawHome = dirname10(destPath);
2566
+ const openClawHome = dirname11(destPath);
2343
2567
  const bundledBin = join5(openClawHome, "npm", "node_modules", ".bin", "openclaw");
2344
2568
  const script = String.raw`
2345
2569
  set -euo pipefail
@@ -2366,8 +2590,8 @@ printf '%s' "$out" | node -e 'let s=""; process.stdin.on("data", c => s += c); p
2366
2590
  import { basename as basename8, join as join16, relative as relative3 } from "path";
2367
2591
 
2368
2592
  // src/staging/codex-subagents.ts
2369
- import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
2370
- import { basename as basename4, dirname as dirname11, join as join6 } from "path";
2593
+ import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
2594
+ import { basename as basename4, dirname as dirname12, join as join6 } from "path";
2371
2595
  var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
2372
2596
  async function renderCodexSubagents(artifacts, stageRoot, adapter) {
2373
2597
  if (adapter?.name !== "codex") return artifacts;
@@ -2393,7 +2617,7 @@ async function renderCodexSubagent(artifact, stageRoot) {
2393
2617
  const renderedPath = join6(stageRoot, ".agentwheel-rendered", "codex-subagents", `${agentName}.toml`);
2394
2618
  const lowerSourcePath = sourcePath.toLowerCase();
2395
2619
  if (artifact.kind === "file" && (artifact.name.toLowerCase().endsWith(".toml") || lowerSourcePath.endsWith(".toml"))) {
2396
- const content = await readFile10(sourcePath, "utf8");
2620
+ const content = await readFile11(sourcePath, "utf8");
2397
2621
  validateCodexAgentToml(content, sourcePath);
2398
2622
  return {
2399
2623
  ...artifact,
@@ -2410,10 +2634,10 @@ async function renderCodexSubagent(artifact, stageRoot) {
2410
2634
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !lowerSourcePath.endsWith(".md")) {
2411
2635
  throw new Error(`Codex subagent ${artifact.relativePath} must be a .toml file, .md file, or directory containing AGENTS.md.`);
2412
2636
  }
2413
- const markdown = await readFile10(markdownPath, "utf8");
2637
+ const markdown = await readFile11(markdownPath, "utf8");
2414
2638
  const toml = markdownToCodexAgentToml(agentName, markdown);
2415
- await mkdir8(dirname11(renderedPath), { recursive: true });
2416
- await writeFile10(renderedPath, toml, "utf8");
2639
+ await mkdir9(dirname12(renderedPath), { recursive: true });
2640
+ await writeFile11(renderedPath, toml, "utf8");
2417
2641
  return {
2418
2642
  ...artifact,
2419
2643
  name: agentName,
@@ -2430,7 +2654,7 @@ function codexAgentName(artifact) {
2430
2654
  }
2431
2655
  function validateCodexAgentToml(content, path) {
2432
2656
  for (const field of requiredCodexAgentFields) {
2433
- const pattern = new RegExp(`(^|\\n)\\s*${escapeRegExp(field)}\\s*=`, "m");
2657
+ const pattern = new RegExp(`(^|\\n)\\s*${escapeRegExp2(field)}\\s*=`, "m");
2434
2658
  if (!pattern.test(content)) {
2435
2659
  throw new Error(`Codex custom agent TOML ${path} is missing required field '${field}'.`);
2436
2660
  }
@@ -2476,13 +2700,13 @@ function tomlMultilineString(value) {
2476
2700
  ${escaped.endsWith("\n") ? escaped : `${escaped}
2477
2701
  `}"""`;
2478
2702
  }
2479
- function escapeRegExp(value) {
2703
+ function escapeRegExp2(value) {
2480
2704
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2481
2705
  }
2482
2706
 
2483
2707
  // src/staging/copilot-artifacts.ts
2484
- import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
2485
- import { basename as basename5, dirname as dirname12, join as join7 } from "path";
2708
+ import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
2709
+ import { basename as basename5, dirname as dirname13, join as join7 } from "path";
2486
2710
  async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
2487
2711
  if (adapter?.name !== "copilot") return artifacts;
2488
2712
  const names = /* @__PURE__ */ new Set();
@@ -2512,9 +2736,9 @@ async function renderCopilotSubagent(artifact, stageRoot) {
2512
2736
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md")) {
2513
2737
  throw new Error(`Copilot subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2514
2738
  }
2515
- const markdown = await readFile11(markdownPath, "utf8");
2516
- await mkdir9(dirname12(renderedPath), { recursive: true });
2517
- await writeFile11(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
2739
+ const markdown = await readFile12(markdownPath, "utf8");
2740
+ await mkdir10(dirname13(renderedPath), { recursive: true });
2741
+ await writeFile12(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
2518
2742
  return {
2519
2743
  ...artifact,
2520
2744
  name: `${agentName}.agent.md`,
@@ -2567,8 +2791,8 @@ function yamlString(value) {
2567
2791
  }
2568
2792
 
2569
2793
  // src/staging/openclaw-subagents.ts
2570
- import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
2571
- import { basename as basename6, dirname as dirname13, join as join8 } from "path";
2794
+ import { mkdir as mkdir11, readFile as readFile13, writeFile as writeFile13 } from "fs/promises";
2795
+ import { basename as basename6, dirname as dirname14, join as join8 } from "path";
2572
2796
  async function renderOpenClawSubagents(artifacts, stageRoot, adapter) {
2573
2797
  if (adapter?.name !== "openclaw") return artifacts;
2574
2798
  const names = /* @__PURE__ */ new Set();
@@ -2597,15 +2821,15 @@ async function renderOpenClawSubagent(artifact, stageRoot) {
2597
2821
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !sourcePath.toLowerCase().endsWith(".md")) {
2598
2822
  throw new Error(`OpenClaw subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2599
2823
  }
2600
- const parsed = splitFrontmatter3(await readFile12(markdownPath, "utf8"));
2824
+ const parsed = splitFrontmatter3(await readFile13(markdownPath, "utf8"));
2601
2825
  const body = parsed.body.trim().length > 0 ? parsed.body.trim() : `# ${titleFromAgentId(agentId)}
2602
2826
 
2603
2827
  ${parsed.description ?? `OpenClaw subagent ${agentId}.`}`;
2604
2828
  const renderedPath = join8(stageRoot, ".agentwheel-rendered", "openclaw-subagents", agentId, "AGENTS.md");
2605
- await mkdir10(dirname13(renderedPath), { recursive: true });
2606
- await writeFile12(renderedPath, `${body}
2829
+ await mkdir11(dirname14(renderedPath), { recursive: true });
2830
+ await writeFile13(renderedPath, `${body}
2607
2831
  `, "utf8");
2608
- const renderedDir = dirname13(renderedPath);
2832
+ const renderedDir = dirname14(renderedPath);
2609
2833
  return {
2610
2834
  ...artifact,
2611
2835
  name: agentId,
@@ -2639,7 +2863,7 @@ function titleFromAgentId(agentId) {
2639
2863
  import { join as join10 } from "path";
2640
2864
 
2641
2865
  // src/targets/plugins/common.ts
2642
- import { readFile as readFile13 } from "fs/promises";
2866
+ import { readFile as readFile14 } from "fs/promises";
2643
2867
  import { join as join9 } from "path";
2644
2868
  import { parseDocument } from "yaml";
2645
2869
  function pluginStateRoot(request) {
@@ -2663,28 +2887,28 @@ function safeNameSegment(value) {
2663
2887
  async function jsonPluginName(root, relativeManifestPath, fallback) {
2664
2888
  const manifestPath = join9(root, relativeManifestPath);
2665
2889
  if (!await pathExists(manifestPath)) return fallback;
2666
- const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
2890
+ const parsed = JSON.parse(await readFile14(manifestPath, "utf8"));
2667
2891
  return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : fallback;
2668
2892
  }
2669
2893
  async function yamlPluginName(root, relativeManifestPaths, fallback) {
2670
2894
  for (const relativeManifestPath of relativeManifestPaths) {
2671
2895
  const manifestPath = join9(root, relativeManifestPath);
2672
2896
  if (!await pathExists(manifestPath)) continue;
2673
- const document = parseDocument(await readFile13(manifestPath, "utf8"));
2897
+ const document = parseDocument(await readFile14(manifestPath, "utf8"));
2674
2898
  const parsed = document.toJSON();
2675
- if (!isRecord4(parsed)) continue;
2899
+ if (!isRecord5(parsed)) continue;
2676
2900
  for (const key of ["name", "module", "package"]) {
2677
2901
  const value = parsed[key];
2678
2902
  if (typeof value === "string" && value.trim().length > 0) return value.trim();
2679
2903
  }
2680
2904
  const packageValue = parsed.package;
2681
- if (isRecord4(packageValue) && typeof packageValue.name === "string" && packageValue.name.trim().length > 0) {
2905
+ if (isRecord5(packageValue) && typeof packageValue.name === "string" && packageValue.name.trim().length > 0) {
2682
2906
  return packageValue.name.trim();
2683
2907
  }
2684
2908
  }
2685
2909
  return fallback;
2686
2910
  }
2687
- function isRecord4(value) {
2911
+ function isRecord5(value) {
2688
2912
  return typeof value === "object" && value !== null && !Array.isArray(value);
2689
2913
  }
2690
2914
 
@@ -2802,7 +3026,7 @@ async function hermesPluginSpec(request) {
2802
3026
  }
2803
3027
 
2804
3028
  // src/targets/plugins/openclaw.ts
2805
- import { readFile as readFile14 } from "fs/promises";
3029
+ import { readFile as readFile15 } from "fs/promises";
2806
3030
  import { join as join14 } from "path";
2807
3031
  function openClawPluginInstallCommand(request) {
2808
3032
  return ["openclaw", "plugins", "install", "--force", request.path];
@@ -2832,7 +3056,7 @@ async function openClawPluginName(root, fallback) {
2832
3056
  for (const manifestName of ["plugin.json", "openclaw.plugin.json"]) {
2833
3057
  const manifestPath = join14(root, manifestName);
2834
3058
  if (!await pathExists(manifestPath)) continue;
2835
- const parsed = JSON.parse(await readFile14(manifestPath, "utf8"));
3059
+ const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
2836
3060
  if (typeof parsed.name === "string" && parsed.name.trim().length > 0) return parsed.name.trim();
2837
3061
  }
2838
3062
  return fallback;
@@ -2842,7 +3066,7 @@ async function openClawClawHubPluginMetadata(root, fallback) {
2842
3066
  if (!await pathExists(metadataPath)) {
2843
3067
  throw new Error("OpenClaw ClawHub plugins must contain clawhub.json");
2844
3068
  }
2845
- const parsed = JSON.parse(await readFile14(metadataPath, "utf8"));
3069
+ const parsed = JSON.parse(await readFile15(metadataPath, "utf8"));
2846
3070
  const installSpec = stringField(parsed.installSpec);
2847
3071
  if (!installSpec?.startsWith("clawhub:")) {
2848
3072
  throw new Error("OpenClaw ClawHub plugin metadata must declare installSpec starting with clawhub:");
@@ -2973,7 +3197,7 @@ function skipWarning(selector, adapter, installationType, artifactType, support)
2973
3197
  }
2974
3198
 
2975
3199
  // src/validation/artifacts.ts
2976
- import { readFile as readFile15 } from "fs/promises";
3200
+ import { readFile as readFile16 } from "fs/promises";
2977
3201
  import { basename as basename7, join as join15 } from "path";
2978
3202
  import { parseDocument as parseDocument2 } from "yaml";
2979
3203
  var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
@@ -3178,7 +3402,7 @@ async function validateGenericStructure(artifact, target) {
3178
3402
  async function validateSkillFrontmatter(artifact, skillMdPath) {
3179
3403
  let content;
3180
3404
  try {
3181
- content = await readFile15(skillMdPath, "utf8");
3405
+ content = await readFile16(skillMdPath, "utf8");
3182
3406
  } catch (error) {
3183
3407
  return [{ artifact, message: `could not read SKILL.md: ${errorMessage(error)}` }];
3184
3408
  }
@@ -3248,7 +3472,7 @@ async function validateHermesPlugin(artifact) {
3248
3472
  return [{ artifact, message: "Hermes plugins must contain plugin.yaml or plugin.yml" }];
3249
3473
  }
3250
3474
  try {
3251
- const document = parseDocument2(await readFile15(manifestPath, "utf8"));
3475
+ const document = parseDocument2(await readFile16(manifestPath, "utf8"));
3252
3476
  if (document.errors.length > 0) {
3253
3477
  return [{ artifact, message: `Hermes ${basename7(manifestPath)} must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` }];
3254
3478
  }
@@ -3272,8 +3496,8 @@ async function firstExistingPath(paths) {
3272
3496
  }
3273
3497
  async function parseJsonPluginManifest(manifestPath, label) {
3274
3498
  try {
3275
- const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
3276
- if (!isRecord5(parsed)) {
3499
+ const parsed = JSON.parse(await readFile16(manifestPath, "utf8"));
3500
+ if (!isRecord6(parsed)) {
3277
3501
  return { ok: false, message: `${label} ${basename7(manifestPath)} must be a JSON object` };
3278
3502
  }
3279
3503
  if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
@@ -3319,8 +3543,8 @@ async function validateOpenClawClawHubPlugin(artifact) {
3319
3543
  return [{ artifact, message: "OpenClaw ClawHub plugins must contain clawhub.json" }];
3320
3544
  }
3321
3545
  try {
3322
- const parsed = JSON.parse(await readFile15(metadataPath, "utf8"));
3323
- if (!isRecord5(parsed)) {
3546
+ const parsed = JSON.parse(await readFile16(metadataPath, "utf8"));
3547
+ if (!isRecord6(parsed)) {
3324
3548
  return [{ artifact, message: "OpenClaw ClawHub clawhub.json must be a JSON object" }];
3325
3549
  }
3326
3550
  if (typeof parsed.installSpec !== "string" || !parsed.installSpec.trim().startsWith("clawhub:")) {
@@ -3343,8 +3567,8 @@ async function openClawPluginManifestPaths(artifact) {
3343
3567
  async function parseOpenClawPluginManifest(manifestPath) {
3344
3568
  const manifestName = basename7(manifestPath);
3345
3569
  try {
3346
- const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
3347
- if (!isRecord5(parsed)) {
3570
+ const parsed = JSON.parse(await readFile16(manifestPath, "utf8"));
3571
+ if (!isRecord6(parsed)) {
3348
3572
  return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be a JSON object` };
3349
3573
  }
3350
3574
  if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
@@ -3360,8 +3584,8 @@ async function parseJsonObjectArtifact(artifact) {
3360
3584
  return { ok: false, message: "merge artifacts must be JSON files" };
3361
3585
  }
3362
3586
  try {
3363
- const parsed = JSON.parse(await readFile15(artifactPath(artifact), "utf8"));
3364
- if (!isRecord5(parsed)) return { ok: false, message: "merge artifacts must contain a JSON object" };
3587
+ const parsed = JSON.parse(await readFile16(artifactPath(artifact), "utf8"));
3588
+ if (!isRecord6(parsed)) return { ok: false, message: "merge artifacts must contain a JSON object" };
3365
3589
  return { ok: true, value: parsed };
3366
3590
  } catch (error) {
3367
3591
  return { ok: false, message: `merge artifact must be valid JSON: ${errorMessage(error)}` };
@@ -3372,7 +3596,7 @@ async function parseYamlObjectArtifact(artifact) {
3372
3596
  return { ok: false, message: "merge artifacts must be YAML files" };
3373
3597
  }
3374
3598
  try {
3375
- const document = parseDocument2(await readFile15(artifactPath(artifact), "utf8"));
3599
+ const document = parseDocument2(await readFile16(artifactPath(artifact), "utf8"));
3376
3600
  if (document.errors.length > 0) {
3377
3601
  return { ok: false, message: `merge artifact must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` };
3378
3602
  }
@@ -3384,10 +3608,10 @@ async function parseYamlObjectArtifact(artifact) {
3384
3608
  }
3385
3609
  }
3386
3610
  function extractMcpServers2(source) {
3387
- const raw = isRecord5(source.mcpServers) ? source.mcpServers : source;
3611
+ const raw = isRecord6(source.mcpServers) ? source.mcpServers : source;
3388
3612
  const servers = {};
3389
3613
  for (const [name, value] of Object.entries(raw)) {
3390
- if (isRecord5(value)) servers[name] = value;
3614
+ if (isRecord6(value)) servers[name] = value;
3391
3615
  }
3392
3616
  return servers;
3393
3617
  }
@@ -3404,7 +3628,7 @@ function hasExtension(artifact, extension) {
3404
3628
  function isPluginFormat(value) {
3405
3629
  return value !== void 0 && pluginFormats.includes(value);
3406
3630
  }
3407
- function isRecord5(value) {
3631
+ function isRecord6(value) {
3408
3632
  return typeof value === "object" && value !== null && !Array.isArray(value);
3409
3633
  }
3410
3634
  function isUnknownRecord(value) {
@@ -3472,7 +3696,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3472
3696
  if (op.action === "plugin") {
3473
3697
  const existing2 = manifestByPath.get(op.relativeDestPath);
3474
3698
  if (existing2 && existing2.sourceHash === op.desiredHash && existing2.executed === true) {
3475
- operations.push({ ...op, action: "skip", manifestHash: existing2.hash, reason: "semantic plugin already executed" });
3699
+ operations.push({ ...op, action: "skip", manifestHash: existing2.hash, execute: true, reason: "semantic plugin already executed" });
3476
3700
  } else {
3477
3701
  operations.push(existing2 && existing2.sourceHash === op.desiredHash ? { ...op, manifestHash: existing2.hash, reason: "semantic plugin pending execution" } : op);
3478
3702
  }
@@ -3494,15 +3718,16 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3494
3718
  if (op.mergeStrategy) {
3495
3719
  const existing2 = manifestByPath.get(op.relativeDestPath);
3496
3720
  const exists2 = await transport.pathExists(op.destPath);
3721
+ const mergeRemoval = await mergeRemovalForInstall(op.sourcePath, op.mergeStrategy, exists2 ? await transport.readFile(op.destPath) : void 0);
3497
3722
  if (!exists2) {
3498
- operations.push({ ...op, action: "create", reason: "merge destination missing" });
3723
+ operations.push({ ...op, action: "create", mergeRemoval, mergeCreatedDestination: true, reason: "merge destination missing" });
3499
3724
  continue;
3500
3725
  }
3501
3726
  const currentHash2 = await transport.hashPath(op.destPath);
3502
3727
  if (existing2 && existing2.sourceHash === op.desiredHash) {
3503
- operations.push(options.forceDrift ? { ...op, action: "update", currentHash: currentHash2, manifestHash: existing2.hash, reason: "force refreshing managed merge destination" } : { ...op, action: "skip", currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
3728
+ operations.push(options.forceDrift ? { ...op, action: "update", mergeRemoval, mergeCreatedDestination: existing2.mergeCreatedDestination, currentHash: currentHash2, manifestHash: existing2.hash, reason: "force refreshing managed merge destination" } : { ...op, action: "skip", mergeRemoval: existing2.mergeRemoval, mergeCreatedDestination: existing2.mergeCreatedDestination, currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
3504
3729
  } else {
3505
- operations.push({ ...op, action: "update", currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing destination" });
3730
+ operations.push({ ...op, action: "update", mergeRemoval, mergeCreatedDestination: existing2?.mergeCreatedDestination, currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing destination" });
3506
3731
  }
3507
3732
  continue;
3508
3733
  }
@@ -3619,7 +3844,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3619
3844
  relativeDestPath: entry.path,
3620
3845
  currentHash,
3621
3846
  manifestHash: entry.hash,
3622
- reason: "artifact removed from source",
3847
+ reason: staleRemovalReason(entry),
3623
3848
  channel: entry.channel,
3624
3849
  packageName: entry.packageName,
3625
3850
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -3934,7 +4159,9 @@ function operationMetadataFromEntry(entry) {
3934
4159
  dependencyRole: entry.dependencyRole,
3935
4160
  owners: entry.owners,
3936
4161
  workspaceOwner: entry.workspaceOwner,
3937
- graphLockDigest: entry.graphLockDigest
4162
+ graphLockDigest: entry.graphLockDigest,
4163
+ mergeRemoval: entry.mergeRemoval,
4164
+ mergeCreatedDestination: entry.mergeCreatedDestination
3938
4165
  };
3939
4166
  }
3940
4167
  return {
@@ -3973,6 +4200,10 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
3973
4200
  function normalizeOperationOwners(op) {
3974
4201
  return normalizeOwners(op.owners ?? [op.packageName ?? op.artifactName]);
3975
4202
  }
4203
+ function staleRemovalReason(entry) {
4204
+ if (!entry.mergeStrategy) return "artifact removed from source";
4205
+ return "mergeCreatedDestination" in entry && entry.mergeCreatedDestination === true ? "remove entire file created by Agentwheel as merge destination" : "remove managed merge contribution; preserving merge destination";
4206
+ }
3976
4207
  function isGuardedMergeTarget(type) {
3977
4208
  return type === "mcp" || type === "hooks" || type === "settings" || type === "plugins";
3978
4209
  }
@@ -4147,6 +4378,8 @@ async function createUninstallPlan(manifest, transport = localTransport) {
4147
4378
  composedFrom: entry.composedFrom,
4148
4379
  ...operationMetadataFromEntry2(entry)
4149
4380
  });
4381
+ } else if (entry.mergeStrategy && !("mergeRemoval" in entry && entry.mergeRemoval !== void 0) && !("mergeCreatedDestination" in entry && entry.mergeCreatedDestination === true)) {
4382
+ operations.push(legacyMergeKeepOperation(entry, destPath));
4150
4383
  } else {
4151
4384
  operations.push({
4152
4385
  action: "remove",
@@ -4158,7 +4391,7 @@ async function createUninstallPlan(manifest, transport = localTransport) {
4158
4391
  desiredHash: entry.sourceHash,
4159
4392
  currentHash,
4160
4393
  manifestHash: entry.hash,
4161
- reason: "uninstall managed artifact",
4394
+ reason: removalReason(entry, "uninstall managed artifact"),
4162
4395
  channel: entry.channel,
4163
4396
  packageName: entry.packageName,
4164
4397
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -4246,6 +4479,8 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
4246
4479
  ...operationMetadataFromEntry2(entry),
4247
4480
  graphLockDigest: options.graphLockDigest
4248
4481
  });
4482
+ } else if (entry.mergeStrategy && !("mergeRemoval" in entry && entry.mergeRemoval !== void 0) && !("mergeCreatedDestination" in entry && entry.mergeCreatedDestination === true)) {
4483
+ operations.push(legacyMergeKeepOperation(entry, destPath));
4249
4484
  } else {
4250
4485
  operations.push({
4251
4486
  action: "remove",
@@ -4257,7 +4492,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
4257
4492
  desiredHash: entry.sourceHash,
4258
4493
  currentHash,
4259
4494
  manifestHash: entry.hash,
4260
- reason: "owner set became empty",
4495
+ reason: removalReason(entry, "owner set became empty"),
4261
4496
  channel: entry.channel,
4262
4497
  packageName: entry.packageName,
4263
4498
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -4297,7 +4532,9 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
4297
4532
  graphNodeId: entry.graphNodeId,
4298
4533
  dependencyRole: entry.dependencyRole,
4299
4534
  owners: ownersOverride ?? entry.owners,
4300
- graphLockDigest: entry.graphLockDigest
4535
+ graphLockDigest: entry.graphLockDigest,
4536
+ mergeRemoval: entry.mergeRemoval,
4537
+ mergeCreatedDestination: entry.mergeCreatedDestination
4301
4538
  };
4302
4539
  }
4303
4540
  return {
@@ -4307,6 +4544,34 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
4307
4544
  owners: ownersOverride?.length ? ownersOverride : [entry.packageName ?? "legacy"]
4308
4545
  };
4309
4546
  }
4547
+ function legacyMergeKeepOperation(entry, destPath) {
4548
+ return {
4549
+ action: "keep",
4550
+ artifactType: entry.artifactType,
4551
+ artifactName: entry.artifactName,
4552
+ kind: entry.kind,
4553
+ destPath,
4554
+ relativeDestPath: entry.path,
4555
+ desiredHash: entry.sourceHash,
4556
+ currentHash: entry.hash,
4557
+ manifestHash: entry.hash,
4558
+ reason: "legacy merge ownership is incomplete; preserving destination and releasing management",
4559
+ channel: entry.channel,
4560
+ packageName: entry.packageName,
4561
+ semanticCommand: entry.semanticCommand,
4562
+ semanticPlugin: "semanticPlugin" in entry ? entry.semanticPlugin : void 0,
4563
+ execute: entry.executed,
4564
+ mergeStrategy: entry.mergeStrategy,
4565
+ mode: entry.mode,
4566
+ composedFrom: entry.composedFrom,
4567
+ preserveInManifest: false,
4568
+ ...operationMetadataFromEntry2(entry, [])
4569
+ };
4570
+ }
4571
+ function removalReason(entry, fallback) {
4572
+ if (!entry.mergeStrategy) return fallback;
4573
+ return "mergeCreatedDestination" in entry && entry.mergeCreatedDestination === true ? "remove entire file created by Agentwheel as merge destination" : "remove managed merge contribution; preserving merge destination";
4574
+ }
4310
4575
 
4311
4576
  // src/cli/format.ts
4312
4577
  var labels = {
@@ -4345,6 +4610,56 @@ function formatPlan(plan) {
4345
4610
  );
4346
4611
  return lines.join("\n");
4347
4612
  }
4613
+ function planReport(targets, warnings = []) {
4614
+ return {
4615
+ schemaVersion: 1,
4616
+ targets: [...targets].sort(comparePlanReportTargets),
4617
+ warnings: [...warnings].sort((a, b) => a.localeCompare(b))
4618
+ };
4619
+ }
4620
+ function installPlanReportTarget(plan, graphLockDigest = plan.graphLockDigest) {
4621
+ return {
4622
+ adapter: plan.adapter,
4623
+ installationType: plan.installationType,
4624
+ targetRoot: plan.targetRoot,
4625
+ graphLockDigest: graphLockDigest ?? null,
4626
+ hasBlockingChanges: plan.hasBlockingChanges,
4627
+ summary: planReportSummary(plan),
4628
+ operations: sortedPlanOperations(plan.operations).map(planReportOperation)
4629
+ };
4630
+ }
4631
+ function comparePlanReportTargets(a, b) {
4632
+ return a.adapter.localeCompare(b.adapter) || a.installationType.localeCompare(b.installationType) || a.targetRoot.localeCompare(b.targetRoot);
4633
+ }
4634
+ function planReportSummary(plan) {
4635
+ const summary = summarizePlan(plan);
4636
+ return {
4637
+ create: summary.create,
4638
+ update: summary.update,
4639
+ skip: summary.skip,
4640
+ remove: summary.remove,
4641
+ keep: summary.keep,
4642
+ drift: summary.drift,
4643
+ conflict: summary.conflict,
4644
+ plugin: summary.plugin
4645
+ };
4646
+ }
4647
+ function planReportOperation(operation) {
4648
+ return {
4649
+ action: operation.action,
4650
+ channel: operation.channel,
4651
+ artifactType: operation.artifactType,
4652
+ artifactName: operation.artifactName,
4653
+ logicalSelector: operation.logicalSelector ?? null,
4654
+ relativeDestPath: operation.relativeDestPath,
4655
+ reason: operation.reason,
4656
+ packageName: operation.packageName ?? null,
4657
+ owners: operation.owners ?? [],
4658
+ graphNodeId: operation.graphNodeId ?? null,
4659
+ dependencyRole: operation.dependencyRole ?? null,
4660
+ blockedReason: operation.blockedReason ?? null
4661
+ };
4662
+ }
4348
4663
  function formatSemanticCommandSuffix(operation) {
4349
4664
  const commands = semanticCommandsForOperation(operation);
4350
4665
  return commands.length > 0 ? ` :: ${commands.map((command) => command.join(" ")).join(" && ")}` : "";
@@ -4368,6 +4683,7 @@ function sortedPlanOperations(operations) {
4368
4683
  function formatGraphPlan(result) {
4369
4684
  const lines = [
4370
4685
  ...formatDependencyTree(result.graph),
4686
+ ...formatSelectionImports(result.graph),
4371
4687
  `LOCK ${result.graphLockPath} (${result.graphLockDigest})`
4372
4688
  ];
4373
4689
  if (result.recoveredPendingApply) {
@@ -4411,6 +4727,20 @@ function formatDependencyTree(graph) {
4411
4727
  }
4412
4728
  return lines;
4413
4729
  }
4730
+ function formatSelectionImports(graph) {
4731
+ const lines = [];
4732
+ for (const root of graph.roots) {
4733
+ const selection = root.selectionImport;
4734
+ if (!selection) continue;
4735
+ lines.push(
4736
+ `IMPORT root=${root.rootId} source=${root.normalizedSource} config=${selection.configPath} configSha256=${selection.configHash} export=${selection.exportName} exportSha256=${selection.exportHash} extends=[${selection.extends.join(",")}]`
4737
+ );
4738
+ lines.push(
4739
+ `SELECT root=${root.rootId} inherited=[${selection.inherited.join(",")}] add=[${selection.additions.join(",")}] exclude=[${selection.exclusions.join(",")}] effective=[${selection.effective.join(",")}]`
4740
+ );
4741
+ }
4742
+ return lines;
4743
+ }
4414
4744
  function formatLockDependencyTree(lock) {
4415
4745
  const depths = lockNodeDepths(lock);
4416
4746
  const lines = ["Dependency graph"];
@@ -4420,6 +4750,16 @@ function formatLockDependencyTree(lock) {
4420
4750
  const requiredBy = node.requiredBy.length > 0 ? node.requiredBy.join(",") : "<root>";
4421
4751
  lines.push(`${label.padEnd(7)} ${node.id} source=${node.normalizedSource} selected=[${selected}] requiredBy=[${requiredBy}]`);
4422
4752
  }
4753
+ for (const root of lock.canonical.roots) {
4754
+ const selection = root.selectionImport;
4755
+ if (!selection) continue;
4756
+ lines.push(
4757
+ `IMPORT root=${root.rootId} source=${root.normalizedSource} config=${selection.configPath} configSha256=${selection.configHash} export=${selection.exportName} exportSha256=${selection.exportHash} extends=[${selection.extends.join(",")}]`
4758
+ );
4759
+ lines.push(
4760
+ `SELECT root=${root.rootId} inherited=[${selection.inherited.join(",")}] add=[${selection.additions.join(",")}] exclude=[${selection.exclusions.join(",")}] effective=[${selection.effective.join(",")}]`
4761
+ );
4762
+ }
4423
4763
  for (const edge of lock.canonical.edges) {
4424
4764
  const selected = edge.selected.length > 0 ? edge.selected.join(",") : "<none>";
4425
4765
  lines.push(`EDGE ${edge.from} --${edge.alias}--> ${edge.to} selected=[${selected}]`);
@@ -4511,124 +4851,607 @@ function ownerChains(lock, nodeId) {
4511
4851
  return incoming.flatMap((edge) => ownerChains(lock, edge.from).map((chain) => [...chain, `${edge.alias}:${nodeId}`]));
4512
4852
  }
4513
4853
 
4514
- // src/source/clawhub.ts
4515
- import { mkdir as mkdir11, rm as rm5, writeFile as writeFile13 } from "fs/promises";
4516
- import { basename as basename10, dirname as dirname14, join as join20, resolve as resolve6 } from "path";
4517
-
4518
- // src/source/local.ts
4519
- import { createHash as createHash4 } from "crypto";
4520
- import { readdir, stat as stat3 } from "fs/promises";
4521
- import { basename as basename9, join as join19, relative as relative4, resolve as resolve5 } from "path";
4522
-
4523
- // src/model/package.ts
4524
- import { readFile as readFile16 } from "fs/promises";
4525
- import { join as join18 } from "path";
4526
- import { parse as parse3, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
4527
- import { z as z5 } from "zod";
4528
- var legacyArtifactTypeSchema = z5.enum([
4529
- "instructions",
4530
- "rules",
4531
- "skills",
4532
- "commands",
4533
- "subagents",
4534
- "mcp",
4535
- "hooks",
4536
- "settings",
4537
- "plugins"
4538
- ]);
4539
- var runtimeListSchema = z5.array(z5.string().min(1));
4540
- var packageProvideBaseSchema = z5.object({
4541
- path: z5.string().min(1),
4542
- format: artifactFormatSchema.optional(),
4543
- assets: z5.array(packageAssetSchema).optional(),
4544
- required: z5.boolean().optional()
4545
- });
4546
- var packageItemSchema = z5.object({
4547
- format: artifactFormatSchema.optional(),
4548
- requires: z5.array(packageItemRequireSchema).optional(),
4549
- suggests: z5.array(packageItemSuggestSchema).optional(),
4550
- compose: z5.array(packageComposeEntrySchema).optional(),
4551
- runtimes: runtimeListSchema.optional()
4552
- });
4553
- var packageDependencySchema = z5.object({
4554
- source: z5.string().min(1),
4555
- ref: z5.string().min(1).optional(),
4556
- version: z5.string().min(1).optional(),
4557
- select: z5.array(z5.string().min(1)).optional(),
4558
- mode: z5.enum(["pinned", "tracking"]).optional(),
4559
- optional: z5.boolean().optional(),
4560
- integrity: z5.string().min(1).optional(),
4561
- runtimes: runtimeListSchema.optional()
4562
- });
4563
- var packageSuggestionSchema = packageDependencySchema.extend({
4564
- reason: z5.string().min(1).optional(),
4565
- when: z5.string().min(1).optional()
4566
- });
4567
- var packageProvideV1Schema = packageProvideBaseSchema.extend({
4568
- type: legacyArtifactTypeSchema
4569
- });
4570
- var packageProvideSchema = packageProvideBaseSchema.extend({
4571
- type: artifactTypeSchema,
4572
- runtimes: runtimeListSchema.optional(),
4573
- items: z5.record(z5.string().min(1), packageItemSchema).optional()
4574
- });
4575
- var packageManifestV1Schema = z5.object({
4576
- schemaVersion: z5.literal(1),
4577
- name: z5.string().min(1),
4578
- version: z5.string().min(1),
4579
- provides: z5.array(packageProvideV1Schema).min(1)
4580
- });
4581
- var packageManifestV2Schema = z5.object({
4582
- schemaVersion: z5.literal(2),
4583
- name: z5.string().min(1),
4584
- version: z5.string().min(1),
4585
- runtimes: runtimeListSchema.optional(),
4586
- requires: z5.record(z5.string().min(1), packageDependencySchema).optional(),
4587
- suggests: z5.record(z5.string().min(1), packageSuggestionSchema).optional(),
4588
- compose: z5.array(packageComposeEntrySchema).optional(),
4589
- provides: z5.array(packageProvideSchema).default([])
4590
- }).superRefine((manifest, ctx) => {
4591
- const hasProvides = manifest.provides.length > 0;
4592
- const hasRequires = Object.keys(manifest.requires ?? {}).length > 0;
4593
- if (!hasProvides && !hasRequires) {
4594
- ctx.addIssue({
4595
- code: z5.ZodIssueCode.custom,
4596
- path: ["provides"],
4597
- message: "OpenPack v2 manifest must declare at least one provides entry or one requires dependency"
4598
- });
4854
+ // src/cli/render.ts
4855
+ function renderReport(report, format) {
4856
+ if (format === "json") return renderJson(report);
4857
+ if (format === "mermaid") return renderMermaid(report);
4858
+ return renderHtml(report);
4859
+ }
4860
+ function renderJson(report) {
4861
+ return JSON.stringify(report, null, 2);
4862
+ }
4863
+ function renderMermaid(report) {
4864
+ const packageNames = sortedPackageNames(report);
4865
+ const packageIds = new Map(packageNames.map((name, index) => [name, `pkg_${index}`]));
4866
+ const targetIds = new Map(report.targets.map((target, index) => [targetKey(target, index), `rt_${index}`]));
4867
+ const edges = mermaidEdges(report, packageIds, targetIds);
4868
+ const targetHealth = new Map(report.targets.map((target, index) => [
4869
+ `rt_${index}`,
4870
+ healthForOperations(target.operations)
4871
+ ]));
4872
+ const packageHealth = new Map(packageNames.map((name) => [
4873
+ packageIds.get(name),
4874
+ worstHealth(edges.filter((edge) => edge.packageName === name).map((edge) => edge.health))
4875
+ ]));
4876
+ const lines = [
4877
+ "flowchart LR",
4878
+ " classDef ok fill:#dff5e3,stroke:#2f7d4f,color:#173a25",
4879
+ " classDef pending fill:#e5f0ff,stroke:#3367b0,color:#17345f",
4880
+ " classDef warn fill:#fff3cd,stroke:#9a6a00,color:#4f3600",
4881
+ " classDef bad fill:#fde2e1,stroke:#b42318,color:#5f1712"
4882
+ ];
4883
+ for (const [index, target] of report.targets.entries()) {
4884
+ lines.push(` rt_${index}["${escapeMermaidLabel(`${target.adapter}/${target.installationType}`)}"]`);
4599
4885
  }
4600
- });
4601
- var packageManifestSchema = z5.union([packageManifestV1Schema, packageManifestV2Schema]);
4602
- var openPackManifestNames = ["openpack.json", "openpack.jsonc"];
4603
- var legacyPackageManifestNames = ["agentwheel.json", "agentwheel.jsonc"];
4604
- var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNames];
4605
- var warnedLegacyManifestPaths = /* @__PURE__ */ new Set();
4606
- async function findPackageManifestPath(root, options = {}) {
4607
- for (const name of packageManifestNames) {
4608
- const candidate = join18(root, name);
4609
- if (!await pathExists(candidate)) continue;
4610
- if (isLegacyPackageManifestName(name) && options.warnLegacy !== false && !warnedLegacyManifestPaths.has(candidate)) {
4611
- warnedLegacyManifestPaths.add(candidate);
4612
- (options.warn ?? console.warn)(`Deprecated package manifest ${name}; use openpack.json or openpack.jsonc.`);
4613
- }
4614
- return candidate;
4886
+ for (const name of packageNames) {
4887
+ lines.push(` ${packageIds.get(name)}["${escapeMermaidLabel(name)}"]`);
4615
4888
  }
4616
- return void 0;
4617
- }
4618
- async function readPackageManifest(root) {
4619
- const path = await findPackageManifestPath(root);
4620
- if (!path) return void 0;
4621
- const content = await readFile16(path, "utf8");
4622
- const errors = [];
4623
- const parsed = parse3(content, errors, { allowTrailingComma: true, disallowComments: false });
4624
- if (errors.length > 0) {
4625
- const details = errors.map((error) => `${printParseErrorCode2(error.error)} at offset ${error.offset}`).join(", ");
4626
- throw new Error(`Invalid package manifest ${path}: ${details}`);
4889
+ for (const edge of edges) {
4890
+ const label = edge.operationCount === 1 ? "1 op" : `${edge.operationCount} ops`;
4891
+ lines.push(` ${edge.packageId} ${edge.id}@-->|${label}| ${edge.targetId}`);
4892
+ }
4893
+ for (const [id, health] of [...targetHealth.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
4894
+ lines.push(` class ${id} ${health}`);
4895
+ }
4896
+ for (const [id, health] of [...packageHealth.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
4897
+ lines.push(` class ${id} ${health}`);
4898
+ }
4899
+ for (const edge of edges) {
4900
+ lines.push(` class ${edge.id} ${edge.health}`);
4901
+ }
4902
+ return lines.join("\n");
4903
+ }
4904
+ function renderHtml(report, options = {}) {
4905
+ const assets = options.assets ?? "inline";
4906
+ const mermaid = renderMermaid(report);
4907
+ const totals = reportTotals(report);
4908
+ const linkedScript = assets === "linked" ? '\n<script src="mermaid.js"></script>\n<script>if (window.mermaid) { window.mermaid.initialize({ startOnLoad: true }); }</script>' : "";
4909
+ const targetSections = report.targets.map((target, index) => renderTargetSection(target, index)).join("\n");
4910
+ const warnings = report.warnings.length > 0 ? `<section class="panel"><h2>Warnings</h2><ul>${report.warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("")}</ul></section>` : "";
4911
+ return [
4912
+ "<!doctype html>",
4913
+ '<html lang="en">',
4914
+ "<head>",
4915
+ '<meta charset="utf-8">',
4916
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
4917
+ "<title>Agentwheel Plan Report</title>",
4918
+ `<style>${htmlStyles()}</style>`,
4919
+ "</head>",
4920
+ "<body>",
4921
+ "<main>",
4922
+ '<header class="hero">',
4923
+ "<h1>Agentwheel Plan Report</h1>",
4924
+ `<p>${totals.targets} targets, ${totals.operations} operations, ${totals.blockingTargets} blocking targets, ${report.warnings.length} warnings</p>`,
4925
+ "</header>",
4926
+ '<section class="totals" aria-label="Plan totals">',
4927
+ ...summaryItems(totals.summary).map(([name, value]) => `<div><span>${escapeHtml(name)}</span><strong>${value}</strong></div>`),
4928
+ "</section>",
4929
+ warnings,
4930
+ targetSections,
4931
+ '<details class="panel diagram" open>',
4932
+ "<summary>Mermaid source</summary>",
4933
+ `<pre class="mermaid">${escapeHtml(mermaid)}</pre>`,
4934
+ "</details>",
4935
+ "</main>",
4936
+ linkedScript,
4937
+ "</body>",
4938
+ "</html>"
4939
+ ].filter(Boolean).join("\n");
4940
+ }
4941
+ function renderTargetSection(target, index) {
4942
+ return [
4943
+ `<section class="panel target ${target.hasBlockingChanges ? "is-blocking" : ""}">`,
4944
+ "<header>",
4945
+ `<h2>${escapeHtml(target.adapter)}/${escapeHtml(target.installationType)}</h2>`,
4946
+ `<p>${escapeHtml(target.targetRoot)}</p>`,
4947
+ "</header>",
4948
+ '<div class="summary">',
4949
+ ...summaryItems(target.summary).map(([name, value]) => `<span><b>${value}</b> ${escapeHtml(name)}</span>`),
4950
+ "</div>",
4951
+ "<table>",
4952
+ "<thead><tr><th>Action</th><th>Channel</th><th>Artifact</th><th>Path</th><th>Reason</th></tr></thead>",
4953
+ `<tbody>${target.operations.map((operation) => renderOperationRow(operation)).join("")}</tbody>`,
4954
+ "</table>",
4955
+ `<p class="digest">Target ${index + 1} lock: ${escapeHtml(target.graphLockDigest ?? "none")}</p>`,
4956
+ "</section>"
4957
+ ].join("\n");
4958
+ }
4959
+ function renderOperationRow(operation) {
4960
+ const artifact = `${operation.artifactType}/${operation.artifactName}`;
4961
+ return [
4962
+ `<tr class="health-${healthForAction(operation.action)}">`,
4963
+ `<td>${escapeHtml(operation.action)}</td>`,
4964
+ `<td>${escapeHtml(operation.channel)}</td>`,
4965
+ `<td>${escapeHtml(artifact)}</td>`,
4966
+ `<td><code>${escapeHtml(operation.relativeDestPath)}</code></td>`,
4967
+ `<td>${escapeHtml(operation.reason)}</td>`,
4968
+ "</tr>"
4969
+ ].join("");
4970
+ }
4971
+ function mermaidEdges(report, packageIds, targetIds) {
4972
+ const edgeGroups = /* @__PURE__ */ new Map();
4973
+ for (const [targetIndex, target] of report.targets.entries()) {
4974
+ const targetId = targetIds.get(targetKey(target, targetIndex));
4975
+ const operationsByPackage = /* @__PURE__ */ new Map();
4976
+ for (const operation of target.operations) {
4977
+ const name = operation.packageName ?? "(unowned)";
4978
+ operationsByPackage.set(name, [...operationsByPackage.get(name) ?? [], operation]);
4979
+ }
4980
+ for (const [packageName, operations] of operationsByPackage) {
4981
+ edgeGroups.set(`${packageName}\0${targetId}`, { packageName, targetId, operations });
4982
+ }
4983
+ }
4984
+ return [...edgeGroups.values()].sort((a, b) => a.packageName.localeCompare(b.packageName) || a.targetId.localeCompare(b.targetId)).map((edge, index) => ({
4985
+ id: `edge_${index}`,
4986
+ packageName: edge.packageName,
4987
+ packageId: packageIds.get(edge.packageName),
4988
+ targetId: edge.targetId,
4989
+ operationCount: edge.operations.length,
4990
+ health: healthForOperations(edge.operations)
4991
+ }));
4992
+ }
4993
+ function sortedPackageNames(report) {
4994
+ const names = /* @__PURE__ */ new Set();
4995
+ for (const target of report.targets) {
4996
+ for (const operation of target.operations) {
4997
+ names.add(operation.packageName ?? "(unowned)");
4998
+ }
4999
+ }
5000
+ return [...names].sort((a, b) => a.localeCompare(b));
5001
+ }
5002
+ function targetKey(target, index) {
5003
+ return `${index}\0${target.adapter}\0${target.installationType}\0${target.targetRoot}`;
5004
+ }
5005
+ function healthForOperations(operations) {
5006
+ return worstHealth(operations.map((operation) => healthForAction(operation.action)));
5007
+ }
5008
+ function worstHealth(healths) {
5009
+ return healths.sort((a, b) => healthRank(b) - healthRank(a))[0] ?? "ok";
5010
+ }
5011
+ function healthForAction(action) {
5012
+ if (action === "conflict" || action === "remove") return "bad";
5013
+ if (action === "drift") return "warn";
5014
+ if (action === "create" || action === "update" || action === "plugin" || action === "program") return "pending";
5015
+ return "ok";
5016
+ }
5017
+ function healthRank(health) {
5018
+ return { ok: 0, pending: 1, warn: 2, bad: 3 }[health];
5019
+ }
5020
+ function reportTotals(report) {
5021
+ const summary = {
5022
+ create: 0,
5023
+ update: 0,
5024
+ skip: 0,
5025
+ remove: 0,
5026
+ keep: 0,
5027
+ drift: 0,
5028
+ conflict: 0,
5029
+ plugin: 0
5030
+ };
5031
+ let operations = 0;
5032
+ for (const target of report.targets) {
5033
+ operations += target.operations.length;
5034
+ for (const [name] of summaryItems(target.summary)) {
5035
+ summary[name] += target.summary[name];
5036
+ }
5037
+ }
5038
+ return {
5039
+ targets: report.targets.length,
5040
+ operations,
5041
+ blockingTargets: report.targets.filter((target) => target.hasBlockingChanges).length,
5042
+ summary
5043
+ };
5044
+ }
5045
+ function summaryItems(summary) {
5046
+ return [
5047
+ ["create", summary.create],
5048
+ ["update", summary.update],
5049
+ ["skip", summary.skip],
5050
+ ["remove", summary.remove],
5051
+ ["keep", summary.keep],
5052
+ ["drift", summary.drift],
5053
+ ["conflict", summary.conflict],
5054
+ ["plugin", summary.plugin]
5055
+ ];
5056
+ }
5057
+ function escapeMermaidLabel(value) {
5058
+ return value.replaceAll("\\", "\\\\").replaceAll('"', "#quot;").replaceAll("\n", " ");
5059
+ }
5060
+ function escapeHtml(value) {
5061
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
5062
+ }
5063
+ function htmlStyles() {
5064
+ return `
5065
+ :root { color-scheme: light dark; --bg: #f7f8fb; --panel: #ffffff; --text: #1d2433; --muted: #617086; --line: #d8dee9; --ok: #e7f6ec; --pending: #e7f0ff; --warn: #fff4d8; --bad: #ffe6e4; }
5066
+ * { box-sizing: border-box; }
5067
+ body { margin: 0; background: var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
5068
+ main { width: min(1180px, calc(100% - 32px)); margin: 0 auto; padding: 32px 0; }
5069
+ .hero { margin-bottom: 20px; }
5070
+ h1, h2, p { margin: 0; }
5071
+ h1 { font-size: 28px; line-height: 1.2; }
5072
+ h2 { font-size: 18px; line-height: 1.3; }
5073
+ .hero p, .target header p, .digest { color: var(--muted); margin-top: 6px; }
5074
+ .totals { display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 8px; margin-bottom: 16px; }
5075
+ .totals div, .summary span { border: 1px solid var(--line); background: var(--panel); border-radius: 6px; padding: 8px 10px; }
5076
+ .totals span { display: block; color: var(--muted); font-size: 12px; text-transform: uppercase; }
5077
+ .totals strong { display: block; font-size: 20px; margin-top: 2px; }
5078
+ .panel { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 16px; margin: 16px 0; overflow: auto; }
5079
+ .target.is-blocking { border-color: #c23a31; }
5080
+ .summary { display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
5081
+ .summary span { font-size: 13px; }
5082
+ table { width: 100%; border-collapse: collapse; min-width: 760px; }
5083
+ th, td { border-bottom: 1px solid var(--line); padding: 8px; text-align: left; vertical-align: top; font-size: 13px; }
5084
+ th { color: var(--muted); font-size: 12px; text-transform: uppercase; }
5085
+ code, pre { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
5086
+ pre { white-space: pre-wrap; margin: 12px 0 0; padding: 12px; border: 1px solid var(--line); border-radius: 6px; background: color-mix(in srgb, var(--panel), var(--bg) 55%); }
5087
+ summary { cursor: pointer; font-weight: 700; }
5088
+ .health-ok { background: var(--ok); }
5089
+ .health-pending { background: var(--pending); }
5090
+ .health-warn { background: var(--warn); }
5091
+ .health-bad { background: var(--bad); }
5092
+ @media (prefers-color-scheme: dark) {
5093
+ :root { --bg: #11151d; --panel: #191f2b; --text: #edf1f7; --muted: #a6b1c2; --line: #323b4f; --ok: #173824; --pending: #172d4f; --warn: #44330d; --bad: #4d1d1a; }
5094
+ }
5095
+ `.trim();
5096
+ }
5097
+
5098
+ // src/cli/serve.ts
5099
+ import { createHash as createHash4 } from "crypto";
5100
+ import { createReadStream } from "fs";
5101
+ import { createRequire } from "module";
5102
+ import { isIP } from "net";
5103
+ import { createServer } from "http";
5104
+ var DEFAULT_BIND = "127.0.0.1";
5105
+ var DEFAULT_PORT = 8765;
5106
+ var DEFAULT_INTERVAL_SECONDS = 60;
5107
+ var require2 = createRequire(import.meta.url);
5108
+ async function servePlanDashboard(options) {
5109
+ const bind = options.bind ?? DEFAULT_BIND;
5110
+ const port = options.port ?? DEFAULT_PORT;
5111
+ const intervalSeconds = options.intervalSeconds ?? DEFAULT_INTERVAL_SECONDS;
5112
+ const log = options.log ?? console.log;
5113
+ const warn = options.warn ?? console.warn;
5114
+ const errorLog = options.error ?? console.error;
5115
+ if (!isLoopbackBind(bind)) {
5116
+ warn(`WARNING: agentwheel serve is bound to ${bind}; this unauthenticated dashboard may expose runtime topology on a non-local interface.`);
5117
+ }
5118
+ let cache = await buildServeCache(options.buildReport);
5119
+ let refreshing = false;
5120
+ let timer;
5121
+ const server = createServer((request, response) => {
5122
+ void handleRequest(request, response, () => cache);
5123
+ });
5124
+ await listen(server, port, bind);
5125
+ const address = server.address();
5126
+ log(`Serving agentwheel plan report at ${servedUrl(address, bind, port)}`);
5127
+ if (!options.once) {
5128
+ timer = setInterval(() => {
5129
+ if (refreshing) return;
5130
+ refreshing = true;
5131
+ void buildServeCache(options.buildReport).then((nextCache) => {
5132
+ cache = nextCache;
5133
+ }).catch((error) => {
5134
+ const message = errorMessage2(error);
5135
+ errorLog(`agentwheel serve refresh failed: ${message}`);
5136
+ cache = {
5137
+ ...cache,
5138
+ mermaidAvailable: mermaidAssetAvailable(),
5139
+ html: decorateServedHtml(cache.baseHtml, {
5140
+ version: cache.version,
5141
+ mermaidAvailable: mermaidAssetAvailable(),
5142
+ lastError: message
5143
+ })
5144
+ };
5145
+ }).finally(() => {
5146
+ refreshing = false;
5147
+ });
5148
+ }, intervalSeconds * 1e3);
5149
+ timer.unref?.();
5150
+ }
5151
+ await waitForShutdown(server, () => {
5152
+ if (timer) clearInterval(timer);
5153
+ });
5154
+ }
5155
+ function parseServePort(value) {
5156
+ if (value === void 0) return DEFAULT_PORT;
5157
+ const port = Number(value);
5158
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
5159
+ throw new Error(`Invalid --port value: ${value}. Expected an integer from 0 to 65535.`);
5160
+ }
5161
+ return port;
5162
+ }
5163
+ function parseServeIntervalSeconds(value) {
5164
+ if (value === void 0) return DEFAULT_INTERVAL_SECONDS;
5165
+ const seconds = Number(value);
5166
+ if (!Number.isFinite(seconds) || seconds <= 0) {
5167
+ throw new Error(`Invalid --interval value: ${value}. Expected a positive number of seconds.`);
5168
+ }
5169
+ return seconds;
5170
+ }
5171
+ function isLoopbackBind(bind) {
5172
+ const normalized = bind.trim().toLowerCase();
5173
+ if (normalized === "localhost" || normalized === "::1" || normalized === "[::1]" || normalized === "0:0:0:0:0:0:0:1") return true;
5174
+ if (isIP(normalized) === 4) return normalized.startsWith("127.");
5175
+ return false;
5176
+ }
5177
+ function resolveMermaidAssetPath() {
5178
+ try {
5179
+ return require2.resolve("mermaid/dist/mermaid.min.js");
5180
+ } catch {
5181
+ return void 0;
5182
+ }
5183
+ }
5184
+ async function buildServeCache(buildReport) {
5185
+ const report = await buildReport();
5186
+ const json = `${renderJson(report)}
5187
+ `;
5188
+ const version = createHash4("sha256").update(json).digest("hex");
5189
+ const baseHtml = renderHtml(report, { assets: "linked" });
5190
+ const mermaidAvailable = mermaidAssetAvailable();
5191
+ return {
5192
+ json,
5193
+ version,
5194
+ baseHtml,
5195
+ mermaidAvailable,
5196
+ html: decorateServedHtml(baseHtml, { version, mermaidAvailable })
5197
+ };
5198
+ }
5199
+ function mermaidAssetAvailable() {
5200
+ return resolveMermaidAssetPath() !== void 0;
5201
+ }
5202
+ async function handleRequest(request, response, cacheForRequest) {
5203
+ if (request.method !== "GET") {
5204
+ writeText(response, 404, "Not found\n");
5205
+ return;
5206
+ }
5207
+ const url = new URL(request.url ?? "/", "http://agentwheel.local");
5208
+ const cache = cacheForRequest();
5209
+ if (url.pathname === "/") {
5210
+ response.writeHead(200, {
5211
+ "content-type": "text/html; charset=utf-8",
5212
+ "cache-control": "no-store"
5213
+ });
5214
+ response.end(cache.html);
5215
+ return;
5216
+ }
5217
+ if (url.pathname === "/report.json") {
5218
+ response.writeHead(200, {
5219
+ "content-type": "application/json; charset=utf-8",
5220
+ "cache-control": "no-store"
5221
+ });
5222
+ response.end(cache.json);
5223
+ return;
5224
+ }
5225
+ if (url.pathname === "/version") {
5226
+ writeText(response, 200, `${cache.version}
5227
+ `);
5228
+ return;
5229
+ }
5230
+ if (url.pathname === "/mermaid.js") {
5231
+ await writeMermaidAsset(response);
5232
+ return;
5233
+ }
5234
+ writeText(response, 404, "Not found\n");
5235
+ }
5236
+ function writeText(response, statusCode, body) {
5237
+ response.writeHead(statusCode, {
5238
+ "content-type": "text/plain; charset=utf-8",
5239
+ "cache-control": "no-store"
5240
+ });
5241
+ response.end(body);
5242
+ }
5243
+ async function writeMermaidAsset(response) {
5244
+ const assetPath = resolveMermaidAssetPath();
5245
+ if (!assetPath) {
5246
+ writeText(response, 404, "Mermaid is not installed. Install mermaid to render the diagram.\n");
5247
+ return;
5248
+ }
5249
+ response.writeHead(200, {
5250
+ "content-type": "application/javascript; charset=utf-8",
5251
+ "cache-control": "public, max-age=3600"
5252
+ });
5253
+ await new Promise((resolve21) => {
5254
+ const stream = createReadStream(assetPath);
5255
+ stream.on("error", () => {
5256
+ if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
5257
+ else response.destroy();
5258
+ resolve21();
5259
+ });
5260
+ stream.on("end", resolve21);
5261
+ stream.pipe(response);
5262
+ });
5263
+ }
5264
+ function decorateServedHtml(html, options) {
5265
+ const notes = [
5266
+ options.mermaidAvailable ? "" : '<section class="panel serve-note"><strong>Diagram rendering unavailable.</strong> Install <code>mermaid</code> to render the diagram.</section>',
5267
+ options.lastError ? `<section class="panel serve-error" role="alert"><strong>Last refresh failed.</strong> ${escapeHtml2(options.lastError)}</section>` : ""
5268
+ ].filter(Boolean).join("\n");
5269
+ const withNotes = notes ? injectBefore(html, "</main>", `${notes}
5270
+ `) : html;
5271
+ return injectBefore(withNotes, "</body>", `${livenessScript(options.version)}
5272
+ `);
5273
+ }
5274
+ function livenessScript(version) {
5275
+ return `<script>(()=>{const current=${JSON.stringify(version)};async function check(){try{const response=await fetch("/version",{cache:"no-store"});if(!response.ok)return;const next=(await response.text()).trim();if(next&&next!==current)location.reload();}catch{}}setInterval(check,5000);})();</script>`;
5276
+ }
5277
+ function injectBefore(value, marker, insertion) {
5278
+ const index = value.lastIndexOf(marker);
5279
+ if (index === -1) return `${value}
5280
+ ${insertion}`;
5281
+ return `${value.slice(0, index)}${insertion}${value.slice(index)}`;
5282
+ }
5283
+ function escapeHtml2(value) {
5284
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
5285
+ }
5286
+ function listen(server, port, bind) {
5287
+ return new Promise((resolve21, reject) => {
5288
+ const onError = (error) => {
5289
+ server.off("listening", onListening);
5290
+ reject(error);
5291
+ };
5292
+ const onListening = () => {
5293
+ server.off("error", onError);
5294
+ resolve21();
5295
+ };
5296
+ server.once("error", onError);
5297
+ server.once("listening", onListening);
5298
+ server.listen(port, bind);
5299
+ });
5300
+ }
5301
+ function waitForShutdown(server, beforeClose) {
5302
+ return new Promise((resolve21) => {
5303
+ let closing = false;
5304
+ const shutdown = () => {
5305
+ if (closing) return;
5306
+ closing = true;
5307
+ process.off("SIGINT", shutdown);
5308
+ process.off("SIGTERM", shutdown);
5309
+ beforeClose();
5310
+ server.close(() => resolve21());
5311
+ };
5312
+ process.once("SIGINT", shutdown);
5313
+ process.once("SIGTERM", shutdown);
5314
+ server.once("close", () => {
5315
+ if (closing) return;
5316
+ closing = true;
5317
+ process.off("SIGINT", shutdown);
5318
+ process.off("SIGTERM", shutdown);
5319
+ beforeClose();
5320
+ resolve21();
5321
+ });
5322
+ });
5323
+ }
5324
+ function servedUrl(address, bind, requestedPort) {
5325
+ if (typeof address === "object" && address) {
5326
+ return `http://${formatUrlHost(address.address || bind)}:${address.port}/`;
5327
+ }
5328
+ return `http://${formatUrlHost(bind)}:${requestedPort}/`;
5329
+ }
5330
+ function formatUrlHost(host) {
5331
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
5332
+ }
5333
+ function errorMessage2(error) {
5334
+ return error instanceof Error ? error.message : String(error);
5335
+ }
5336
+
5337
+ // src/source/clawhub.ts
5338
+ import { mkdir as mkdir12, rm as rm5, writeFile as writeFile14 } from "fs/promises";
5339
+ import { basename as basename10, dirname as dirname15, join as join20, resolve as resolve6 } from "path";
5340
+
5341
+ // src/source/local.ts
5342
+ import { createHash as createHash5 } from "crypto";
5343
+ import { readdir, stat as stat3 } from "fs/promises";
5344
+ import { basename as basename9, join as join19, relative as relative4, resolve as resolve5 } from "path";
5345
+
5346
+ // src/model/package.ts
5347
+ import { readFile as readFile17 } from "fs/promises";
5348
+ import { join as join18 } from "path";
5349
+ import { parse as parse4, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
5350
+ import { z as z5 } from "zod";
5351
+ var legacyArtifactTypeSchema = z5.enum([
5352
+ "instructions",
5353
+ "rules",
5354
+ "skills",
5355
+ "commands",
5356
+ "subagents",
5357
+ "mcp",
5358
+ "hooks",
5359
+ "settings",
5360
+ "plugins"
5361
+ ]);
5362
+ var runtimeListSchema = z5.array(z5.string().min(1));
5363
+ var packageProvideBaseSchema = z5.object({
5364
+ path: z5.string().min(1),
5365
+ format: artifactFormatSchema.optional(),
5366
+ assets: z5.array(packageAssetSchema).optional(),
5367
+ required: z5.boolean().optional()
5368
+ });
5369
+ var packageItemSchema = z5.object({
5370
+ format: artifactFormatSchema.optional(),
5371
+ requires: z5.array(packageItemRequireSchema).optional(),
5372
+ suggests: z5.array(packageItemSuggestSchema).optional(),
5373
+ compose: z5.array(packageComposeEntrySchema).optional(),
5374
+ runtimes: runtimeListSchema.optional()
5375
+ });
5376
+ var packageDependencySchema = z5.object({
5377
+ source: z5.string().min(1),
5378
+ ref: z5.string().min(1).optional(),
5379
+ version: z5.string().min(1).optional(),
5380
+ select: z5.array(z5.string().min(1)).optional(),
5381
+ mode: z5.enum(["pinned", "tracking"]).optional(),
5382
+ optional: z5.boolean().optional(),
5383
+ integrity: z5.string().min(1).optional(),
5384
+ runtimes: runtimeListSchema.optional()
5385
+ });
5386
+ var packageSuggestionSchema = packageDependencySchema.extend({
5387
+ reason: z5.string().min(1).optional(),
5388
+ when: z5.string().min(1).optional()
5389
+ });
5390
+ var packageProvideV1Schema = packageProvideBaseSchema.extend({
5391
+ type: legacyArtifactTypeSchema
5392
+ });
5393
+ var packageProvideSchema = packageProvideBaseSchema.extend({
5394
+ type: artifactTypeSchema,
5395
+ runtimes: runtimeListSchema.optional(),
5396
+ items: z5.record(z5.string().min(1), packageItemSchema).optional()
5397
+ });
5398
+ var packageManifestV1Schema = z5.object({
5399
+ schemaVersion: z5.literal(1),
5400
+ name: z5.string().min(1),
5401
+ version: z5.string().min(1),
5402
+ provides: z5.array(packageProvideV1Schema).min(1)
5403
+ });
5404
+ var packageManifestV2Schema = z5.object({
5405
+ schemaVersion: z5.literal(2),
5406
+ name: z5.string().min(1),
5407
+ version: z5.string().min(1),
5408
+ runtimes: runtimeListSchema.optional(),
5409
+ requires: z5.record(z5.string().min(1), packageDependencySchema).optional(),
5410
+ suggests: z5.record(z5.string().min(1), packageSuggestionSchema).optional(),
5411
+ compose: z5.array(packageComposeEntrySchema).optional(),
5412
+ provides: z5.array(packageProvideSchema).default([])
5413
+ }).superRefine((manifest, ctx) => {
5414
+ const hasProvides = manifest.provides.length > 0;
5415
+ const hasRequires = Object.keys(manifest.requires ?? {}).length > 0;
5416
+ if (!hasProvides && !hasRequires) {
5417
+ ctx.addIssue({
5418
+ code: z5.ZodIssueCode.custom,
5419
+ path: ["provides"],
5420
+ message: "OpenPack v2 manifest must declare at least one provides entry or one requires dependency"
5421
+ });
5422
+ }
5423
+ });
5424
+ var packageManifestSchema = z5.union([packageManifestV1Schema, packageManifestV2Schema]);
5425
+ var openPackManifestNames = ["openpack.json", "openpack.jsonc"];
5426
+ var legacyPackageManifestNames = ["agentwheel.json", "agentwheel.jsonc"];
5427
+ var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNames];
5428
+ var warnedLegacyManifestPaths = /* @__PURE__ */ new Set();
5429
+ async function findPackageManifestPath(root, options = {}) {
5430
+ for (const name of packageManifestNames) {
5431
+ const candidate = join18(root, name);
5432
+ if (!await pathExists(candidate)) continue;
5433
+ if (isLegacyPackageManifestName(name) && options.warnLegacy !== false && !warnedLegacyManifestPaths.has(candidate)) {
5434
+ warnedLegacyManifestPaths.add(candidate);
5435
+ (options.warn ?? console.warn)(`Deprecated package manifest ${name}; use openpack.json or openpack.jsonc.`);
5436
+ }
5437
+ return candidate;
5438
+ }
5439
+ return void 0;
5440
+ }
5441
+ async function readPackageManifest(root) {
5442
+ const path = await findPackageManifestPath(root);
5443
+ if (!path) return void 0;
5444
+ const content = await readFile17(path, "utf8");
5445
+ const errors = [];
5446
+ const parsed = parse4(content, errors, { allowTrailingComma: true, disallowComments: false });
5447
+ if (errors.length > 0) {
5448
+ const details = errors.map((error) => `${printParseErrorCode2(error.error)} at offset ${error.offset}`).join(", ");
5449
+ throw new Error(`Invalid package manifest ${path}: ${details}`);
4627
5450
  }
4628
5451
  return parsePackageManifest(parsed, path);
4629
5452
  }
4630
5453
  function parsePackageManifest(parsed, path = "package manifest") {
4631
- if (!isRecord6(parsed)) {
5454
+ if (!isRecord7(parsed)) {
4632
5455
  throw new Error(`Invalid package manifest ${path}: expected an object`);
4633
5456
  }
4634
5457
  if (parsed.schemaVersion === 1) {
@@ -4656,7 +5479,7 @@ function v1OpenPackViolations(manifest) {
4656
5479
  }
4657
5480
  const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
4658
5481
  for (const [index, provide] of provides.entries()) {
4659
- if (!isRecord6(provide)) continue;
5482
+ if (!isRecord7(provide)) continue;
4660
5483
  if (provide.type === "fragments") violations.push(`provides[${index}].type=fragments`);
4661
5484
  for (const key of ["format", "items", "compose", "runtimes"]) {
4662
5485
  if (Object.prototype.hasOwnProperty.call(provide, key)) violations.push(`provides[${index}].${key}`);
@@ -4667,7 +5490,7 @@ function v1OpenPackViolations(manifest) {
4667
5490
  function isLegacyPackageManifestName(name) {
4668
5491
  return legacyPackageManifestNames.includes(name);
4669
5492
  }
4670
- function isRecord6(value) {
5493
+ function isRecord7(value) {
4671
5494
  return typeof value === "object" && value !== null && !Array.isArray(value);
4672
5495
  }
4673
5496
 
@@ -4811,7 +5634,7 @@ var LocalSourceDriver = class {
4811
5634
  };
4812
5635
  async function hashLocalSource(root, manifest) {
4813
5636
  if (!manifest) return hashPath(root);
4814
- const hash = createHash4("sha256").update("local-openpack\0");
5637
+ const hash = createHash5("sha256").update("local-openpack\0");
4815
5638
  const manifestPath = await findPackageManifestPath(root, { warnLegacy: false });
4816
5639
  if (manifestPath) {
4817
5640
  hash.update("manifest\0");
@@ -5046,8 +5869,8 @@ async function writeGeneratedPackage(root, packageInfo) {
5046
5869
  };
5047
5870
  const pluginPath = join20(root, "plugins", pluginId, "clawhub.json");
5048
5871
  await rm5(root, { recursive: true, force: true });
5049
- await mkdir11(dirname14(pluginPath), { recursive: true });
5050
- await writeFile13(join20(root, "openpack.json"), `${JSON.stringify({
5872
+ await mkdir12(dirname15(pluginPath), { recursive: true });
5873
+ await writeFile14(join20(root, "openpack.json"), `${JSON.stringify({
5051
5874
  schemaVersion: 2,
5052
5875
  name: `clawhub/${name}`,
5053
5876
  version: packageInfo.latestVersion ?? "latest",
@@ -5063,7 +5886,7 @@ async function writeGeneratedPackage(root, packageInfo) {
5063
5886
  ]
5064
5887
  }, null, 2)}
5065
5888
  `, "utf8");
5066
- await writeFile13(pluginPath, `${JSON.stringify(plugin, null, 2)}
5889
+ await writeFile14(pluginPath, `${JSON.stringify(plugin, null, 2)}
5067
5890
  `, "utf8");
5068
5891
  }
5069
5892
  function installNameFor2(value) {
@@ -5077,9 +5900,9 @@ function cachePathFor(packageName, cacheRoot) {
5077
5900
 
5078
5901
  // src/source/git.ts
5079
5902
  import { execFile as execFile4 } from "child_process";
5080
- import { cp as cp2, mkdir as mkdir12, rename as rename3, rm as rm6, writeFile as writeFile14 } from "fs/promises";
5903
+ import { cp as cp2, mkdir as mkdir13, rename as rename3, rm as rm6, writeFile as writeFile15 } from "fs/promises";
5081
5904
  import { homedir as homedir2 } from "os";
5082
- import { basename as basename11, dirname as dirname15, join as join21, resolve as resolve7 } from "path";
5905
+ import { basename as basename11, dirname as dirname16, join as join21, resolve as resolve7 } from "path";
5083
5906
  import { promisify as promisify4 } from "util";
5084
5907
  var execFileAsync4 = promisify4(execFile4);
5085
5908
  var GitSourceDriver = class {
@@ -5102,7 +5925,7 @@ var GitSourceDriver = class {
5102
5925
  async fetch(resolved) {
5103
5926
  return withFilesystemLock(`${resolved.resolvedPath}.lock`, resolved.cacheLockTimeoutMs ?? 3e4, async () => {
5104
5927
  const parsed = parseGitSource(resolved.source);
5105
- await mkdir12(resolve7(resolved.resolvedPath, ".."), { recursive: true });
5928
+ await mkdir13(resolve7(resolved.resolvedPath, ".."), { recursive: true });
5106
5929
  if (!await pathExists(join21(resolved.resolvedPath, ".git"))) {
5107
5930
  if (resolved.frozenLock) {
5108
5931
  throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
@@ -5178,9 +6001,9 @@ async function git(args) {
5178
6001
  return execFileAsync4("git", args, { maxBuffer: 1024 * 1024 * 10 });
5179
6002
  }
5180
6003
  async function snapshotCheckout(checkoutPath, commit) {
5181
- const snapshotPath = join21(dirname15(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
6004
+ const snapshotPath = join21(dirname16(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
5182
6005
  if (await pathExists(snapshotPath)) return snapshotPath;
5183
- const tempPath = join21(dirname15(checkoutPath), `${basename11(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
6006
+ const tempPath = join21(dirname16(checkoutPath), `${basename11(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
5184
6007
  await rm6(tempPath, { recursive: true, force: true });
5185
6008
  await cp2(checkoutPath, tempPath, { recursive: true, dereference: true });
5186
6009
  await rm6(join21(tempPath, ".git"), { recursive: true, force: true });
@@ -5194,12 +6017,12 @@ async function snapshotCheckout(checkoutPath, commit) {
5194
6017
  return snapshotPath;
5195
6018
  }
5196
6019
  async function withFilesystemLock(lockPath, timeoutMs, fn) {
5197
- await mkdir12(dirname15(lockPath), { recursive: true });
6020
+ await mkdir13(dirname16(lockPath), { recursive: true });
5198
6021
  const started = Date.now();
5199
6022
  while (true) {
5200
6023
  try {
5201
- await mkdir12(lockPath);
5202
- await writeFile14(join21(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
6024
+ await mkdir13(lockPath);
6025
+ await writeFile15(join21(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
5203
6026
  break;
5204
6027
  } catch (error) {
5205
6028
  if (!isAlreadyExists2(error)) throw error;
@@ -5220,8 +6043,8 @@ function isAlreadyExists2(error) {
5220
6043
  }
5221
6044
 
5222
6045
  // src/source/mcp-registry.ts
5223
- import { mkdir as mkdir13, writeFile as writeFile15 } from "fs/promises";
5224
- import { basename as basename12, dirname as dirname16, join as join22, resolve as resolve8 } from "path";
6046
+ import { mkdir as mkdir14, writeFile as writeFile16 } from "fs/promises";
6047
+ import { basename as basename12, dirname as dirname17, join as join22, resolve as resolve8 } from "path";
5225
6048
  var registryBaseUrl = "https://registry.modelcontextprotocol.io/v0.1";
5226
6049
  var sourcePrefix2 = "mcp-registry:";
5227
6050
  var McpRegistrySourceDriver = class {
@@ -5328,15 +6151,15 @@ function isSafeHttpUrl(value) {
5328
6151
  async function writeGeneratedPackage2(root, server) {
5329
6152
  const serverId = installNameFor3(server.serverName);
5330
6153
  const mcpPath = join22(root, "mcp", `${serverId}.json`);
5331
- await mkdir13(dirname16(mcpPath), { recursive: true });
5332
- await writeFile15(join22(root, "openpack.json"), `${JSON.stringify({
6154
+ await mkdir14(dirname17(mcpPath), { recursive: true });
6155
+ await writeFile16(join22(root, "openpack.json"), `${JSON.stringify({
5333
6156
  schemaVersion: 2,
5334
6157
  name: `mcp-registry/${server.serverName}`,
5335
6158
  version: server.version ?? "latest",
5336
6159
  provides: [{ type: "mcp", path: "mcp" }]
5337
6160
  }, null, 2)}
5338
6161
  `, "utf8");
5339
- await writeFile15(mcpPath, `${JSON.stringify({
6162
+ await writeFile16(mcpPath, `${JSON.stringify({
5340
6163
  mcpServers: {
5341
6164
  [serverId]: {
5342
6165
  type: "streamable-http",
@@ -5356,14 +6179,14 @@ function cachePathFor3(serverName, cacheRoot) {
5356
6179
  }
5357
6180
 
5358
6181
  // src/source/skillkit.ts
5359
- import { cp as cp3, mkdir as mkdir14, readFile as readFile17, rm as rm7 } from "fs/promises";
6182
+ import { cp as cp3, mkdir as mkdir15, readFile as readFile18, rm as rm7 } from "fs/promises";
5360
6183
  import { homedir as homedir3 } from "os";
5361
- import { basename as basename14, dirname as dirname18, join as join24, resolve as resolve9 } from "path";
6184
+ import { basename as basename14, dirname as dirname19, join as join24, resolve as resolve9 } from "path";
5362
6185
  import * as defaultSkillKit from "@skillkit/core";
5363
6186
 
5364
6187
  // src/source/skill-artifacts.ts
5365
6188
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
5366
- import { basename as basename13, dirname as dirname17, extname as extname2, join as join23 } from "path";
6189
+ import { basename as basename13, dirname as dirname18, extname as extname2, join as join23 } from "path";
5367
6190
  async function artifactsFromSkillPaths(paths, packageName) {
5368
6191
  const artifacts = [];
5369
6192
  const seen = /* @__PURE__ */ new Set();
@@ -5400,7 +6223,7 @@ async function artifactFromSkillPath(item, packageName) {
5400
6223
  };
5401
6224
  }
5402
6225
  if (stats.isFile() && basename13(item.path).toLowerCase() === "skill.md") {
5403
- const dir = dirname17(item.path);
6226
+ const dir = dirname18(item.path);
5404
6227
  const name = sanitizeSkillName(item.name ?? basename13(dir));
5405
6228
  return {
5406
6229
  type: "skills",
@@ -5493,7 +6316,7 @@ var SkillKitSourceDriver = class {
5493
6316
  if (!provider?.clone) {
5494
6317
  throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
5495
6318
  }
5496
- await mkdir14(dirname18(resolved.resolvedPath), { recursive: true });
6319
+ await mkdir15(dirname19(resolved.resolvedPath), { recursive: true });
5497
6320
  const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
5498
6321
  if (!result.success || !result.path) {
5499
6322
  throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
@@ -5539,7 +6362,7 @@ var SkillKitSourceDriver = class {
5539
6362
  for (const skill of this.discover(resolved.resolvedPath)) {
5540
6363
  const skillMd = join24(skill.path, "SKILL.md");
5541
6364
  if (await pathExists(skillMd)) {
5542
- this.core.translateSkill(await readFile17(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
6365
+ this.core.translateSkill(await readFile18(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
5543
6366
  }
5544
6367
  }
5545
6368
  return resolved;
@@ -5715,14 +6538,14 @@ function getSourceDriver(name = "local") {
5715
6538
  }
5716
6539
 
5717
6540
  // src/staging/staging.ts
5718
- import { chmod, cp as cp5, mkdir as mkdir16, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
5719
- import { basename as basename18, dirname as dirname21, join as join28, relative as relative7, resolve as resolve12, sep as sep2 } from "path";
6541
+ import { chmod, cp as cp5, mkdir as mkdir17, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
6542
+ import { basename as basename18, dirname as dirname22, join as join28, relative as relative7, resolve as resolve12, sep as sep2 } from "path";
5720
6543
  import { tmpdir as tmpdir4 } from "os";
5721
6544
 
5722
6545
  // src/compose/markdown.ts
5723
- import { createHash as createHash5 } from "crypto";
5724
- import { readdir as readdir3, readFile as readFile18, stat as stat6, writeFile as writeFile16 } from "fs/promises";
5725
- import { basename as basename16, dirname as dirname19, extname as extname3, join as join26, relative as relative6, resolve as resolve11, sep } from "path";
6546
+ import { createHash as createHash6 } from "crypto";
6547
+ import { readdir as readdir3, readFile as readFile19, stat as stat6, writeFile as writeFile17 } from "fs/promises";
6548
+ import { basename as basename16, dirname as dirname20, extname as extname3, join as join26, relative as relative6, resolve as resolve11, sep } from "path";
5726
6549
  var includePattern = /<!--\s*openpack:include(\?)?\s+([^>]+?)\s*-->/g;
5727
6550
  var escapedIncludePattern = /<!--\s*openpack\\:include(\?)?\s+([^>]+?)\s*-->/g;
5728
6551
  var generatedPattern = /<!--\s*(?:BEGIN|END)\s+openpack:include\b/;
@@ -5732,12 +6555,13 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
5732
6555
  for (const artifact of artifacts) byKey.set(artifactKey(artifact), artifact);
5733
6556
  const artifactPaths = artifactPathMap(artifacts);
5734
6557
  for (const artifact of orderedForExpansion(artifacts)) {
6558
+ if (artifact.type === "fragments") continue;
5735
6559
  const files = await markdownFilesForArtifact(artifact);
5736
6560
  if (files.length === 0) continue;
5737
6561
  const composedFrom = [];
5738
6562
  for (const file of files) {
5739
6563
  const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
5740
- if (result.changed) await writeFile16(file, result.content, "utf8");
6564
+ if (result.changed) await writeFile17(file, result.content, "utf8");
5741
6565
  composedFrom.push(...result.composedFrom);
5742
6566
  }
5743
6567
  const stagedPath = artifact.stagedPath ?? artifact.sourcePath;
@@ -5759,7 +6583,7 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
5759
6583
  }
5760
6584
  }
5761
6585
  async function expandFile(file, packageRoot, appendEntries, artifactPaths, options) {
5762
- const raw = await readFile18(file, "utf8");
6586
+ const raw = await readFile19(file, "utf8");
5763
6587
  const owner = ownerSelector(packageRoot, file, options.nodeId);
5764
6588
  const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
5765
6589
  let content = expanded.content;
@@ -5852,7 +6676,7 @@ async function expandInclude(selector, packageRoot, artifactPaths, options) {
5852
6676
  if (!stats.isFile()) {
5853
6677
  throw new Error(`OpenPack include is not a file: ${displaySelector}`);
5854
6678
  }
5855
- const raw = sourceContent ?? await readFile18(sourcePath, "utf8");
6679
+ const raw = sourceContent ?? await readFile19(sourcePath, "utf8");
5856
6680
  const { optional: _optional, markers: _markers, chain: _chain, ...childOptions } = options;
5857
6681
  const expanded = await expandContent(raw, includePackageRoot, [...options.chain, displaySelector], includeArtifactPaths, {
5858
6682
  ...childOptions,
@@ -5942,7 +6766,7 @@ async function listMarkdownFiles(root) {
5942
6766
  function composeEntriesForFile(artifact, file) {
5943
6767
  if (!artifact.compose?.length) return [];
5944
6768
  if (artifact.kind === "file") return [resolve11(artifact.stagedPath ?? artifact.sourcePath), resolve11(file)].every(Boolean) && resolve11(artifact.stagedPath ?? artifact.sourcePath) === resolve11(file) ? artifact.compose : [];
5945
- return basename16(file) === "SKILL.md" && dirname19(file) === resolve11(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
6769
+ return basename16(file) === "SKILL.md" && dirname20(file) === resolve11(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
5946
6770
  }
5947
6771
  function orderedForExpansion(artifacts) {
5948
6772
  return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
@@ -5973,7 +6797,7 @@ function cleanSelector(value) {
5973
6797
  return value.trim().replace(/\s+/g, " ");
5974
6798
  }
5975
6799
  function sha256(content) {
5976
- return createHash5("sha256").update(content).digest("hex");
6800
+ return createHash6("sha256").update(content).digest("hex");
5977
6801
  }
5978
6802
  function uniqueComposedFrom(entries) {
5979
6803
  if (entries.length === 0) return [];
@@ -5988,8 +6812,8 @@ function artifactPathMap(artifacts) {
5988
6812
  }
5989
6813
 
5990
6814
  // src/staging/customize.ts
5991
- import { cp as cp4, mkdir as mkdir15, readdir as readdir4, readFile as readFile19, writeFile as writeFile17 } from "fs/promises";
5992
- import { dirname as dirname20, join as join27 } from "path";
6815
+ import { cp as cp4, mkdir as mkdir16, readdir as readdir4, readFile as readFile20, writeFile as writeFile18 } from "fs/promises";
6816
+ import { dirname as dirname21, join as join27 } from "path";
5993
6817
  async function applyCustomizations(artifacts, options) {
5994
6818
  let next = [...artifacts];
5995
6819
  next = await applyReplacements2(next, options, "override", installableArtifactTypes());
@@ -6010,11 +6834,11 @@ async function applyInstructionOverlay(artifacts, options) {
6010
6834
  const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
6011
6835
  if (index < 0) return artifacts;
6012
6836
  const artifact = artifacts[index];
6013
- const managed = await readFile19(artifact.stagedPath ?? artifact.sourcePath, "utf8");
6014
- const local = await readFile19(overlayPath, "utf8");
6837
+ const managed = await readFile20(artifact.stagedPath ?? artifact.sourcePath, "utf8");
6838
+ const local = await readFile20(overlayPath, "utf8");
6015
6839
  const composedPath = join27(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
6016
- await mkdir15(dirname20(composedPath), { recursive: true });
6017
- await writeFile17(
6840
+ await mkdir16(dirname21(composedPath), { recursive: true });
6841
+ await writeFile18(
6018
6842
  composedPath,
6019
6843
  [
6020
6844
  "<!-- BEGIN agentwheel managed: upstream -->",
@@ -6086,7 +6910,7 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
6086
6910
  const artifactKind = entry.isDirectory() ? "dir" : "file";
6087
6911
  const existing = byKey.get(artifactMapKey);
6088
6912
  const stagedPath = join27(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
6089
- await mkdir15(dirname20(stagedPath), { recursive: true });
6913
+ await mkdir16(dirname21(stagedPath), { recursive: true });
6090
6914
  await cp4(full, stagedPath, { recursive: artifactKind === "dir", dereference: true });
6091
6915
  byKey.set(artifactMapKey, {
6092
6916
  ...existing,
@@ -6146,7 +6970,7 @@ async function stageResolvedArtifactsRaw(resolved, artifacts) {
6146
6970
  const stagedArtifacts = [];
6147
6971
  for (const artifact of artifacts) {
6148
6972
  const stagedPath = join28(root, artifact.relativePath);
6149
- await mkdir16(dirname21(stagedPath), { recursive: true });
6973
+ await mkdir17(dirname22(stagedPath), { recursive: true });
6150
6974
  await cp5(artifact.sourcePath, stagedPath, {
6151
6975
  recursive: artifact.kind === "dir",
6152
6976
  dereference: true,
@@ -6240,7 +7064,7 @@ async function copyAsset(asset, source, dest) {
6240
7064
  const sourceStats = await stat8(source);
6241
7065
  if (sourceStats.isFile()) {
6242
7066
  if (matchesAny(basename18(source), asset.include)) {
6243
- await mkdir16(dest, { recursive: true });
7067
+ await mkdir17(dest, { recursive: true });
6244
7068
  await copyAssetFile(source, join28(dest, basename18(source)), asset);
6245
7069
  }
6246
7070
  return;
@@ -6249,7 +7073,7 @@ async function copyAsset(asset, source, dest) {
6249
7073
  throw new Error(`Asset include source is not a file or directory: ${source}`);
6250
7074
  }
6251
7075
  if (!asset.include?.length) {
6252
- await mkdir16(dirname21(dest), { recursive: true });
7076
+ await mkdir17(dirname22(dest), { recursive: true });
6253
7077
  await cp5(source, dest, { recursive: true, dereference: true });
6254
7078
  if (asset.mode === "copy") await normalizeCopiedModes(dest);
6255
7079
  return;
@@ -6261,7 +7085,7 @@ async function copyAsset(asset, source, dest) {
6261
7085
  }
6262
7086
  }
6263
7087
  async function copyAssetFile(source, dest, asset) {
6264
- await mkdir16(dirname21(dest), { recursive: true });
7088
+ await mkdir17(dirname22(dest), { recursive: true });
6265
7089
  await cp5(source, dest, { dereference: true });
6266
7090
  if (asset.mode === "copy") await chmod(dest, 420);
6267
7091
  }
@@ -6309,11 +7133,41 @@ function matchesGlob(path, pattern) {
6309
7133
  }
6310
7134
 
6311
7135
  // src/model/workspace.ts
6312
- import { readFile as readFile20 } from "fs/promises";
7136
+ import { readFile as readFile21 } from "fs/promises";
6313
7137
  import { homedir as homedir4 } from "os";
6314
- import { dirname as dirname22, join as join29, resolve as resolve13 } from "path";
7138
+ import { dirname as dirname23, join as join29, resolve as resolve13 } from "path";
6315
7139
  import { z as z6 } from "zod";
6316
- var workspacePackageSchema = z6.object({
7140
+ var artifactSelectorListSchema = z6.array(z6.string().min(1));
7141
+ var workspaceSelectionImportSchema = z6.object({
7142
+ export: z6.string().min(1),
7143
+ add: artifactSelectorListSchema.optional(),
7144
+ exclude: artifactSelectorListSchema.optional()
7145
+ }).strict();
7146
+ var workspaceSelectionExportSchema = z6.object({
7147
+ extends: z6.string().min(1).optional(),
7148
+ select: artifactSelectorListSchema.optional(),
7149
+ add: artifactSelectorListSchema.optional(),
7150
+ exclude: artifactSelectorListSchema.optional()
7151
+ }).strict().superRefine((selection, ctx) => {
7152
+ if (selection.extends && selection.select) {
7153
+ ctx.addIssue({
7154
+ code: "custom",
7155
+ path: ["select"],
7156
+ message: "Selection exports may use either select or extends, not both."
7157
+ });
7158
+ }
7159
+ if (!selection.extends && !selection.select) {
7160
+ ctx.addIssue({
7161
+ code: "custom",
7162
+ path: ["select"],
7163
+ message: "Selection exports without extends require select."
7164
+ });
7165
+ }
7166
+ });
7167
+ var workspaceExportsSchema = z6.object({
7168
+ selections: z6.record(z6.string().min(1), workspaceSelectionExportSchema).default({})
7169
+ }).strict();
7170
+ var workspacePackageBaseSchema = z6.object({
6317
7171
  name: z6.string().min(1),
6318
7172
  source: z6.string().min(1),
6319
7173
  driver: z6.enum(["local", "git", "skillkit", "vercel-skills", "mcp-registry", "clawhub"]).default("local"),
@@ -6331,6 +7185,22 @@ var workspacePackageSchema = z6.object({
6331
7185
  aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
6332
7186
  overrides: z6.array(z6.string().min(1)).optional()
6333
7187
  });
7188
+ var workspacePackageSchema = workspacePackageBaseSchema.extend({
7189
+ selection: workspaceSelectionImportSchema.optional()
7190
+ }).superRefine((pkg, ctx) => {
7191
+ if (pkg.selection && (pkg.select !== void 0 || pkg.skills !== void 0)) {
7192
+ ctx.addIssue({
7193
+ code: "custom",
7194
+ path: ["selection"],
7195
+ message: "Packages may use either selection or select/skills, not both."
7196
+ });
7197
+ }
7198
+ });
7199
+ var workspacePackageV1Schema = workspacePackageBaseSchema.extend({
7200
+ selection: z6.never().optional()
7201
+ });
7202
+ var commandSchema = z6.array(z6.string().min(1)).min(1);
7203
+ var commandListSchema = z6.array(commandSchema).min(1).optional();
6334
7204
  var workspaceProfileRuntimeSchema = z6.object({
6335
7205
  agent: z6.string().min(1).optional(),
6336
7206
  adapter: z6.string().min(1).default("openclaw"),
@@ -6338,7 +7208,9 @@ var workspaceProfileRuntimeSchema = z6.object({
6338
7208
  adapterModule: z6.string().min(1).optional(),
6339
7209
  installationType: installationTypeSchema.optional(),
6340
7210
  targetRoot: z6.string().min(1).optional(),
6341
- executePlugins: z6.boolean().optional()
7211
+ executePlugins: z6.boolean().optional(),
7212
+ reloadRuntimes: z6.boolean().optional(),
7213
+ reloadCommands: commandListSchema
6342
7214
  });
6343
7215
  var workspaceProfileSchema = z6.object({
6344
7216
  runtimes: z6.array(workspaceProfileRuntimeSchema).min(1)
@@ -6363,7 +7235,8 @@ var workspaceAgentSchema = z6.object({
6363
7235
  host: z6.string().min(1).optional(),
6364
7236
  user: z6.string().min(1).optional(),
6365
7237
  port: z6.number().int().positive().optional(),
6366
- identityFile: z6.string().min(1).optional()
7238
+ identityFile: z6.string().min(1).optional(),
7239
+ reloadCommands: commandListSchema
6367
7240
  }).superRefine((agent, ctx) => {
6368
7241
  if (agent.transport !== "ssh") return;
6369
7242
  if (!agent.host) {
@@ -6374,22 +7247,34 @@ var workspaceAgentSchema = z6.object({
6374
7247
  });
6375
7248
  }
6376
7249
  });
6377
- var workspaceConfigSchema = z6.object({
6378
- schemaVersion: z6.literal(1),
6379
- packages: z6.array(workspacePackageSchema).default([]),
7250
+ var workspaceConfigBaseSchema = z6.object({
6380
7251
  bootstrapSkills: z6.boolean().optional(),
6381
7252
  registry: workspaceRegistrySchema,
6382
7253
  trust: workspaceTrustSchema,
6383
7254
  profiles: z6.record(z6.string(), workspaceProfileSchema).default({}),
6384
7255
  agents: z6.record(z6.string(), workspaceAgentSchema).default({})
6385
7256
  });
7257
+ var workspaceConfigV1Schema = workspaceConfigBaseSchema.extend({
7258
+ schemaVersion: z6.literal(1),
7259
+ packages: z6.array(workspacePackageV1Schema).default([]),
7260
+ exports: z6.never().optional()
7261
+ });
7262
+ var workspaceConfigV2Schema = workspaceConfigBaseSchema.extend({
7263
+ schemaVersion: z6.literal(2),
7264
+ packages: z6.array(workspacePackageSchema).default([]),
7265
+ exports: workspaceExportsSchema.default({ selections: {} })
7266
+ });
7267
+ var workspaceConfigSchema = z6.discriminatedUnion("schemaVersion", [
7268
+ workspaceConfigV1Schema,
7269
+ workspaceConfigV2Schema
7270
+ ]);
6386
7271
  function workspaceConfigPath(workspaceRoot) {
6387
7272
  return join29(workspaceRoot, ".agentwheel", "config.json");
6388
7273
  }
6389
7274
  async function readWorkspaceConfig(workspaceRoot) {
6390
7275
  const path = workspaceConfigPath(workspaceRoot);
6391
7276
  if (!await pathExists(path)) return emptyWorkspaceConfig();
6392
- return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
7277
+ return workspaceConfigSchema.parse(JSON.parse(await readFile21(path, "utf8")));
6393
7278
  }
6394
7279
  async function writeWorkspaceConfig(workspaceRoot, config) {
6395
7280
  await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
@@ -6399,7 +7284,7 @@ function upsertPackage(config, entry) {
6399
7284
  const packages = parsed.packages.filter((candidate) => candidate.name !== entry.name);
6400
7285
  packages.push(entry);
6401
7286
  packages.sort((a, b) => a.name.localeCompare(b.name));
6402
- return { schemaVersion: 1, packages, bootstrapSkills: parsed.bootstrapSkills, registry: parsed.registry ?? {}, trust: parsed.trust ?? {}, profiles: parsed.profiles ?? {}, agents: parsed.agents ?? {} };
7287
+ return workspaceConfigSchema.parse({ ...parsed, packages });
6403
7288
  }
6404
7289
  function globalWorkspaceConfigPath(globalRoot = homedir4()) {
6405
7290
  return join29(globalRoot, ".agentwheel", "config.json");
@@ -6408,7 +7293,7 @@ async function findWorkspaceRoot(start = process.cwd()) {
6408
7293
  let current = resolve13(start);
6409
7294
  while (true) {
6410
7295
  if (await pathExists(workspaceConfigPath(current))) return current;
6411
- const parent = dirname22(current);
7296
+ const parent = dirname23(current);
6412
7297
  if (parent === current) return resolve13(start);
6413
7298
  current = parent;
6414
7299
  }
@@ -6419,8 +7304,10 @@ async function readMergedWorkspaceConfig(projectRoot, options = {}) {
6419
7304
  return mergeWorkspaceConfig(global, project);
6420
7305
  }
6421
7306
  function mergeWorkspaceConfig(global, project) {
7307
+ const schemaVersion = global.schemaVersion === 2 || project.schemaVersion === 2 ? 2 : 1;
7308
+ const exports = project.schemaVersion === 2 ? project.exports : global.schemaVersion === 2 ? global.exports : void 0;
6422
7309
  return workspaceConfigSchema.parse({
6423
- schemaVersion: 1,
7310
+ schemaVersion,
6424
7311
  packages: project.packages.length > 0 ? project.packages : global.packages,
6425
7312
  bootstrapSkills: project.bootstrapSkills ?? global.bootstrapSkills,
6426
7313
  registry: {
@@ -6431,7 +7318,8 @@ function mergeWorkspaceConfig(global, project) {
6431
7318
  },
6432
7319
  trust: mergeWorkspaceTrust(global.trust, project.trust),
6433
7320
  profiles: { ...global.profiles, ...project.profiles },
6434
- agents: { ...global.agents, ...project.agents }
7321
+ agents: { ...global.agents, ...project.agents },
7322
+ ...exports ? { exports } : {}
6435
7323
  });
6436
7324
  }
6437
7325
  function resolveConfigPath(path, baseRoot) {
@@ -6444,7 +7332,7 @@ function emptyWorkspaceConfig() {
6444
7332
  }
6445
7333
  async function readConfigPath(path) {
6446
7334
  if (!await pathExists(path)) return emptyWorkspaceConfig();
6447
- return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
7335
+ return workspaceConfigSchema.parse(JSON.parse(await readFile21(path, "utf8")));
6448
7336
  }
6449
7337
  function mergeWorkspaceTrust(global, project) {
6450
7338
  return {
@@ -6459,56 +7347,174 @@ function sortedUnique2(values) {
6459
7347
  }
6460
7348
 
6461
7349
  // src/lifecycle/customization.ts
6462
- import { appendFile, cp as cp6, mkdir as mkdir17, rm as rm9 } from "fs/promises";
6463
- import { dirname as dirname24, join as join32 } from "path";
7350
+ import { appendFile, cp as cp6, mkdir as mkdir18, rm as rm9 } from "fs/promises";
7351
+ import { dirname as dirname25, join as join32 } from "path";
6464
7352
 
6465
7353
  // src/resolve/graph.ts
6466
- import { createHash as createHash6 } from "crypto";
6467
- import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile22, stat as stat10 } from "fs/promises";
7354
+ import { createHash as createHash8 } from "crypto";
7355
+ import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile24, stat as stat10 } from "fs/promises";
6468
7356
  import { tmpdir as tmpdir5 } from "os";
6469
7357
  import { basename as basename19, extname as extname4, join as join31 } from "path";
6470
7358
 
7359
+ // src/model/workspace-composition.ts
7360
+ import { createHash as createHash7 } from "crypto";
7361
+ import { readFile as readFile22 } from "fs/promises";
7362
+ import { z as z7 } from "zod";
7363
+ var selectionSourceConfigSchema = z7.object({
7364
+ schemaVersion: z7.literal(2),
7365
+ exports: workspaceExportsSchema
7366
+ }).passthrough();
7367
+ async function resolveSelectionImport(sourceRoot, sourceDriver, selection) {
7368
+ const parsedSelection = workspaceSelectionImportSchema.parse(selection);
7369
+ if (sourceDriver !== "local" && sourceDriver !== "git") {
7370
+ throw new Error(
7371
+ `Selection import '${parsedSelection.export}' is unsupported for source driver '${sourceDriver}'. Only local and git sources expose a stable project configuration.`
7372
+ );
7373
+ }
7374
+ const path = workspaceConfigPath(sourceRoot);
7375
+ if (!await pathExists(path)) {
7376
+ throw new Error(`Selection import '${parsedSelection.export}' requires source config: ${path}`);
7377
+ }
7378
+ let raw;
7379
+ try {
7380
+ raw = JSON.parse(await readFile22(path, "utf8"));
7381
+ } catch (error) {
7382
+ const message = error instanceof Error ? error.message : String(error);
7383
+ throw new Error(`Selection import '${parsedSelection.export}' cannot parse ${path}: ${message}`);
7384
+ }
7385
+ if (!isRecord8(raw) || raw.schemaVersion !== 2) {
7386
+ throw new Error(`Selection import '${parsedSelection.export}' requires schemaVersion 2 in ${path}.`);
7387
+ }
7388
+ let sourceConfig;
7389
+ try {
7390
+ sourceConfig = selectionSourceConfigSchema.parse(raw);
7391
+ } catch (error) {
7392
+ const message = error instanceof Error ? error.message : String(error);
7393
+ throw new Error(`Selection import '${parsedSelection.export}' has invalid exports in ${path}: ${message}`);
7394
+ }
7395
+ const selections = sourceConfig.exports.selections;
7396
+ let exportSelection;
7397
+ let additions;
7398
+ let exclusions;
7399
+ try {
7400
+ exportSelection = resolveExport(parsedSelection.export, selections, []);
7401
+ additions = normalizeSelectors(parsedSelection.add, `selection import '${parsedSelection.export}' add`);
7402
+ exclusions = normalizeSelectors(parsedSelection.exclude, `selection import '${parsedSelection.export}' exclude`);
7403
+ } catch (error) {
7404
+ const message = error instanceof Error ? error.message : String(error);
7405
+ throw new Error(`Selection import '${parsedSelection.export}' in ${path}: ${message}`);
7406
+ }
7407
+ const effective = applySelection(exportSelection.effective, additions, exclusions);
7408
+ const configHash = sha2562(stableJson({ selections }));
7409
+ const exportHash = sha2562(stableJson({
7410
+ export: parsedSelection.export,
7411
+ chain: exportSelection.chain.map((name) => ({ name, selection: selections[name] }))
7412
+ }));
7413
+ return {
7414
+ configPath: ".agentwheel/config.json",
7415
+ configHash,
7416
+ exportHash,
7417
+ exportName: parsedSelection.export,
7418
+ extends: exportSelection.chain,
7419
+ inherited: exportSelection.effective,
7420
+ additions,
7421
+ exclusions,
7422
+ effective
7423
+ };
7424
+ }
7425
+ function resolveExport(name, selections, stack) {
7426
+ if (stack.includes(name)) {
7427
+ throw new Error(`Selection export cycle: ${[...stack, name].join(" -> ")}`);
7428
+ }
7429
+ if (!Object.prototype.hasOwnProperty.call(selections, name)) {
7430
+ throw new Error(`Selection export not found: ${name}`);
7431
+ }
7432
+ const selection = selections[name];
7433
+ const nextStack = [...stack, name];
7434
+ const inherited = selection.extends ? resolveExport(selection.extends, selections, nextStack) : { chain: [], effective: normalizeSelectors(selection.select, `selection export '${name}' select`) };
7435
+ const additions = normalizeSelectors(selection.add, `selection export '${name}' add`);
7436
+ const exclusions = normalizeSelectors(selection.exclude, `selection export '${name}' exclude`);
7437
+ return {
7438
+ chain: [...inherited.chain, name],
7439
+ effective: applySelection(inherited.effective, additions, exclusions)
7440
+ };
7441
+ }
7442
+ function applySelection(inherited, additions, exclusions) {
7443
+ const excluded = new Set(exclusions);
7444
+ return sortedUnique3([...inherited, ...additions]).filter((selector) => !excluded.has(selector));
7445
+ }
7446
+ function normalizeSelectors(values, label) {
7447
+ try {
7448
+ return sortedUnique3(normalizeArtifactSelectors(values) ?? []);
7449
+ } catch (error) {
7450
+ const message = error instanceof Error ? error.message : String(error);
7451
+ throw new Error(`Invalid ${label}: ${message}`);
7452
+ }
7453
+ }
7454
+ function sortedUnique3(values) {
7455
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
7456
+ }
7457
+ function sha2562(value) {
7458
+ return createHash7("sha256").update(value).digest("hex");
7459
+ }
7460
+ function stableJson(value) {
7461
+ return JSON.stringify(stableValue2(value));
7462
+ }
7463
+ function stableValue2(value) {
7464
+ if (Array.isArray(value)) return value.map(stableValue2);
7465
+ if (!value || typeof value !== "object") return value;
7466
+ const out = {};
7467
+ for (const key of Object.keys(value).sort((a, b) => a.localeCompare(b))) {
7468
+ const item = value[key];
7469
+ if (item !== void 0) out[key] = stableValue2(item);
7470
+ }
7471
+ return out;
7472
+ }
7473
+ function isRecord8(value) {
7474
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
7475
+ }
7476
+
6471
7477
  // src/resolve/identity.ts
6472
7478
  import { homedir as homedir6 } from "os";
6473
7479
  import { resolve as resolve15 } from "path";
6474
7480
 
6475
7481
  // src/registry/client.ts
6476
- import { readFile as readFile21, rm as rm8, stat as stat9 } from "fs/promises";
7482
+ import { readFile as readFile23, rm as rm8, stat as stat9 } from "fs/promises";
6477
7483
  import { homedir as homedir5 } from "os";
6478
- import { dirname as dirname23, join as join30, resolve as resolve14 } from "path";
7484
+ import { dirname as dirname24, join as join30, resolve as resolve14 } from "path";
6479
7485
  import { fileURLToPath } from "url";
6480
7486
 
6481
7487
  // src/model/registry.ts
6482
- import { z as z7 } from "zod";
6483
- var registryEntrySchema = z7.object({
6484
- name: z7.string().min(1),
6485
- source: z7.string().min(1),
6486
- type: z7.enum(["package", "skill", "plugin", "mcp", "adapter"]).default("package"),
6487
- description: z7.string().default(""),
6488
- tags: z7.array(z7.string().min(1)).default([]),
6489
- select: z7.array(z7.string().min(1)).optional(),
6490
- skills: z7.array(z7.string().min(1)).optional(),
6491
- homepageUrl: z7.string().min(1).optional(),
6492
- homepageLinkLabel: z7.string().min(1).optional(),
6493
- sourceUrl: z7.string().min(1).optional(),
6494
- sourceLinkLabel: z7.string().min(1).optional(),
6495
- openpack: z7.object({
6496
- schemaVersion: z7.number().int().positive().optional(),
6497
- specVersion: z7.string().min(1).optional()
7488
+ import { z as z8 } from "zod";
7489
+ var registryEntrySchema = z8.object({
7490
+ name: z8.string().min(1),
7491
+ source: z8.string().min(1),
7492
+ type: z8.enum(["package", "skill", "plugin", "mcp", "adapter"]).default("package"),
7493
+ description: z8.string().default(""),
7494
+ tags: z8.array(z8.string().min(1)).default([]),
7495
+ select: z8.array(z8.string().min(1)).optional(),
7496
+ skills: z8.array(z8.string().min(1)).optional(),
7497
+ homepageUrl: z8.string().min(1).optional(),
7498
+ homepageLinkLabel: z8.string().min(1).optional(),
7499
+ sourceUrl: z8.string().min(1).optional(),
7500
+ sourceLinkLabel: z8.string().min(1).optional(),
7501
+ openpack: z8.object({
7502
+ schemaVersion: z8.number().int().positive().optional(),
7503
+ specVersion: z8.string().min(1).optional()
6498
7504
  }).passthrough().optional()
6499
7505
  });
6500
- var registryIndexSchema = z7.union([
6501
- z7.array(registryEntrySchema),
6502
- z7.object({
6503
- schemaVersion: z7.literal(1).optional(),
6504
- entries: z7.array(registryEntrySchema)
7506
+ var registryIndexSchema = z8.union([
7507
+ z8.array(registryEntrySchema),
7508
+ z8.object({
7509
+ schemaVersion: z8.literal(1).optional(),
7510
+ entries: z8.array(registryEntrySchema)
6505
7511
  })
6506
7512
  ]).transform((value) => Array.isArray(value) ? value : value.entries);
6507
- var registryCacheSchema = z7.object({
6508
- version: z7.literal(1),
6509
- fetchedAt: z7.string().datetime(),
6510
- sources: z7.array(z7.string().min(1)),
6511
- entries: z7.array(registryEntrySchema)
7513
+ var registryCacheSchema = z8.object({
7514
+ version: z8.literal(1),
7515
+ fetchedAt: z8.string().datetime(),
7516
+ sources: z8.array(z8.string().min(1)),
7517
+ entries: z8.array(registryEntrySchema)
6512
7518
  });
6513
7519
 
6514
7520
  // src/registry/client.ts
@@ -6579,7 +7585,7 @@ var RegistryClient = class {
6579
7585
  }
6580
7586
  async readCache() {
6581
7587
  if (!await pathExists(this.cachePath)) return void 0;
6582
- return registryCacheSchema.parse(JSON.parse(await readFile21(this.cachePath, "utf8")));
7588
+ return registryCacheSchema.parse(JSON.parse(await readFile23(this.cachePath, "utf8")));
6583
7589
  }
6584
7590
  isExpired(cache, ttlMs) {
6585
7591
  return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
@@ -6598,10 +7604,10 @@ var RegistryClient = class {
6598
7604
  if (await pathExists(filePath)) {
6599
7605
  const fullPath = resolve14(filePath);
6600
7606
  const stats = await stat9(fullPath);
6601
- return readFile21(stats.isDirectory() ? join30(fullPath, "index.json") : fullPath, "utf8");
7607
+ return readFile23(stats.isDirectory() ? join30(fullPath, "index.json") : fullPath, "utf8");
6602
7608
  }
6603
- const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join30(dirname23(this.cachePath), "registry-repos") }));
6604
- return readFile21(join30(resolved.resolvedPath, "index.json"), "utf8");
7609
+ const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join30(dirname24(this.cachePath), "registry-repos") }));
7610
+ return readFile23(join30(resolved.resolvedPath, "index.json"), "utf8");
6605
7611
  }
6606
7612
  warnCompatibility(entries) {
6607
7613
  for (const entry of entries) {
@@ -6615,7 +7621,7 @@ var RegistryClient = class {
6615
7621
  }
6616
7622
  };
6617
7623
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
6618
- const { isExplicitSource } = await import("./identify-T4RE5RBD.js");
7624
+ const { isExplicitSource } = await import("./identify-VV7SXUIQ.js");
6619
7625
  if (await isExplicitSource(source)) return { source };
6620
7626
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
6621
7627
  if (!entry) {
@@ -6718,7 +7724,7 @@ function isBareRegistryName(source) {
6718
7724
  return !source.includes(":") && !isLocalSource(source);
6719
7725
  }
6720
7726
  function isLocalSource(source) {
6721
- return source === "~" || source.startsWith("~/") || source.startsWith("./") || source.startsWith("../") || source.startsWith("/") || source.startsWith("local:");
7727
+ return source === "." || source === ".." || source === "~" || source.startsWith("~/") || source.startsWith("./") || source.startsWith("../") || source.startsWith("/") || source.startsWith("local:");
6722
7728
  }
6723
7729
  function localSourcePath(source) {
6724
7730
  return source.startsWith("local:") ? source.slice("local:".length) : source;
@@ -6894,11 +7900,13 @@ async function resolveDependencyGraph(roots, options) {
6894
7900
  const nodesByKey = /* @__PURE__ */ new Map();
6895
7901
  const rootResults = [];
6896
7902
  const edgeMap = /* @__PURE__ */ new Map();
7903
+ assertDependencyUpdateSelectors(options.previousLock, options.dependencyUpdateSelectors);
6897
7904
  const queue = roots.map((root, index) => {
6898
7905
  const rootId = root.rootId ?? `root-${index + 1}`;
6899
7906
  return {
6900
7907
  source: root.source,
6901
7908
  select: root.select,
7909
+ selection: root.selection,
6902
7910
  mode: root.mode ?? "pinned",
6903
7911
  ref: root.ref,
6904
7912
  declaringPackageRoot: options.workspaceRoot,
@@ -6907,11 +7915,12 @@ async function resolveDependencyGraph(roots, options) {
6907
7915
  aliases: root.aliases,
6908
7916
  overrides: root.overrides,
6909
7917
  useLock: root.useLock ?? options.lockedResolution,
7918
+ updateClosure: root.useLock === false,
6910
7919
  depth: 0,
6911
7920
  optional: false,
6912
7921
  chain: [`workspace:${rootId}`],
6913
7922
  includeSuggestions: root.includeSuggestions ?? options.includeSuggestions,
6914
- suggestionAliases: sortedUnique3([...root.suggestionAliases ?? [], ...options.suggestionAliases ?? []])
7923
+ suggestionAliases: sortedUnique4([...root.suggestionAliases ?? [], ...options.suggestionAliases ?? []])
6915
7924
  };
6916
7925
  });
6917
7926
  let iterations = 0;
@@ -6948,7 +7957,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
6948
7957
  mode: root.mode,
6949
7958
  selected: root.selected,
6950
7959
  aliases: root.aliases,
6951
- overrides: root.overrides
7960
+ overrides: root.overrides,
7961
+ selectionImport: root.selectionImport
6952
7962
  }));
6953
7963
  return {
6954
7964
  version: 1,
@@ -7012,7 +8022,14 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7012
8022
  throw new Error(`Package ${fetched.name}@${fetched.version} does not satisfy requested version ${requirement.version}`);
7013
8023
  }
7014
8024
  const nodeKey = `${normalized.normalizedSource}\0${fetched.name}`;
7015
- const selected = computeSelectedSelectors(fetched.artifacts, requirement.select, requirement.depth === 0, requirement.chain);
8025
+ let selectionImport = requirement.selection ? await resolveSelectionImport(fetched.resolved.resolvedPath, fetched.resolved.driver, requirement.selection) : void 0;
8026
+ const selected = computeSelectedSelectors(
8027
+ fetched.artifacts,
8028
+ selectionImport?.effective ?? requirement.select,
8029
+ requirement.depth === 0,
8030
+ requirement.chain
8031
+ );
8032
+ if (selectionImport) selectionImport = { ...selectionImport, effective: selected };
7016
8033
  let state = nodesByKey.get(nodeKey);
7017
8034
  if (!state) {
7018
8035
  const id = graphNodeId(fetched.name, fetched.version, normalized.normalizedSource, fetched.resolved.resolvedCommit, fetched.sourceHash);
@@ -7043,13 +8060,15 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7043
8060
  depth: requirement.depth,
7044
8061
  fullPackageSelected: false,
7045
8062
  includeSuggestions: false,
7046
- suggestionAliases: /* @__PURE__ */ new Set()
8063
+ suggestionAliases: /* @__PURE__ */ new Set(),
8064
+ updateClosure: false
7047
8065
  };
7048
8066
  nodesByKey.set(nodeKey, state);
7049
8067
  }
7050
8068
  state.depth = Math.min(state.depth, requirement.depth);
7051
- state.fullPackageSelected = state.fullPackageSelected || requirement.select === void 0;
8069
+ state.fullPackageSelected = state.fullPackageSelected || requirement.select === void 0 && !requirement.selection;
7052
8070
  state.includeSuggestions = state.includeSuggestions || requirement.includeSuggestions === true;
8071
+ state.updateClosure = state.updateClosure || requirement.updateClosure === true;
7053
8072
  for (const alias of requirement.suggestionAliases ?? []) state.suggestionAliases.add(alias);
7054
8073
  state.requiredBy.add(requirement.requiredBy);
7055
8074
  for (const selector of selected) addSelectedSelector(state, selector, requirement.selectionReason);
@@ -7063,7 +8082,8 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7063
8082
  mode: state.node.mode,
7064
8083
  selected: state.node.selected,
7065
8084
  aliases: requirement.aliases,
7066
- overrides: requirement.overrides
8085
+ overrides: requirement.overrides,
8086
+ selectionImport
7067
8087
  });
7068
8088
  }
7069
8089
  if (requirement.parentId && requirement.alias) {
@@ -7079,7 +8099,7 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7079
8099
  version: requirement.version,
7080
8100
  mode: requirement.mode,
7081
8101
  optional: requirement.optional,
7082
- selected: sortedUnique3([...previous?.selected ?? [], ...selected])
8102
+ selected: sortedUnique4([...previous?.selected ?? [], ...selected])
7083
8103
  });
7084
8104
  }
7085
8105
  return await collectDependencyNeeds(state, fetched, options, requirement.chain);
@@ -7110,7 +8130,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7110
8130
  if (!dependency.select?.length && !(state.fullPackageSelected && dependency.select === void 0)) continue;
7111
8131
  state.processedPackageAliases.add(alias);
7112
8132
  if (!dependencyTargetsRuntime(dependency.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
7113
- requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain, options.lockedResolution === true));
8133
+ requirements.push(dependencyRequirement(state, fetched, alias, dependency, dependency.select, chain, options));
7114
8134
  }
7115
8135
  for (const [alias, suggestion] of suggestionEntries) {
7116
8136
  if (state.processedSuggestions.has(alias)) continue;
@@ -7146,9 +8166,9 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7146
8166
  fetched,
7147
8167
  parsed.alias,
7148
8168
  dependency,
7149
- sortedUnique3([...dependency.select ?? [], parsed.selector]),
8169
+ sortedUnique4([...dependency.select ?? [], parsed.selector]),
7150
8170
  chain,
7151
- options.lockedResolution === true,
8171
+ options,
7152
8172
  parsed.optional || dependency.optional === true,
7153
8173
  `required by ${parentSelector}`
7154
8174
  ));
@@ -7198,9 +8218,9 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7198
8218
  fetched,
7199
8219
  include.alias,
7200
8220
  dependency,
7201
- sortedUnique3([...dependency.select ?? [], include.selector]),
8221
+ sortedUnique4([...dependency.select ?? [], include.selector]),
7202
8222
  chain,
7203
- options.lockedResolution === true,
8223
+ options,
7204
8224
  include.optional || dependency.optional === true
7205
8225
  ));
7206
8226
  }
@@ -7210,13 +8230,15 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7210
8230
  refreshNode(state);
7211
8231
  return requirements;
7212
8232
  }
7213
- function dependencyRequirement(state, fetched, alias, dependency, select, chain, lockByDefault, optional = dependency.optional ?? false, selectionReason) {
8233
+ function dependencyRequirement(state, fetched, alias, dependency, select, chain, options, optional = dependency.optional ?? false, selectionReason) {
8234
+ const updateClosure = state.updateClosure || dependencyUpdateEdgeSelected(state.node.id, alias, options);
7214
8235
  return {
7215
8236
  source: dependency.source,
7216
8237
  select,
7217
8238
  mode: dependency.mode ?? "pinned",
7218
8239
  ref: dependency.ref,
7219
- useLock: lockByDefault || dependency.mode !== "tracking",
8240
+ useLock: dependency.mode !== "tracking" || options.lockedResolution === true && !updateClosure,
8241
+ updateClosure,
7220
8242
  declaringPackageRoot: fetched.resolved.resolvedPath,
7221
8243
  requiredBy: state.node.id,
7222
8244
  alias,
@@ -7228,7 +8250,7 @@ function dependencyRequirement(state, fetched, alias, dependency, select, chain,
7228
8250
  integrity: dependency.integrity,
7229
8251
  selectionReason,
7230
8252
  includeSuggestions: state.includeSuggestions,
7231
- suggestionAliases: sortedUnique3([...state.suggestionAliases])
8253
+ suggestionAliases: sortedUnique4([...state.suggestionAliases])
7232
8254
  };
7233
8255
  }
7234
8256
  function suggestionRequirement(state, fetched, alias, suggestion, select, chain, options, optional = shouldTreatSuggestionAsOptional(alias, suggestion, options), selectionReason) {
@@ -7239,11 +8261,51 @@ function suggestionRequirement(state, fetched, alias, suggestion, select, chain,
7239
8261
  suggestion,
7240
8262
  select,
7241
8263
  chain,
7242
- options.lockedResolution === true,
8264
+ options,
7243
8265
  optional,
7244
8266
  selectionReason
7245
8267
  );
7246
8268
  }
8269
+ function assertDependencyUpdateSelectors(lock, selectors) {
8270
+ for (const selector of sortedUnique4(selectors ?? [])) {
8271
+ if (!lock) throw new Error(`Dependency update '${selector}' requires an existing graph lock.`);
8272
+ const nodeIds = matchingDependencyUpdateNodeIds(lock, selector);
8273
+ if (nodeIds.size === 0) throw new Error(`Tracking dependency not found in graph lock: ${selector}`);
8274
+ if (nodeIds.size > 1) {
8275
+ throw new Error(`Dependency update selector is ambiguous: ${selector}. Use an exact node id or source.`);
8276
+ }
8277
+ }
8278
+ }
8279
+ function matchingDependencyUpdateNodeIds(lock, selector) {
8280
+ const nodes = new Map(lock.canonical.nodes.map((node) => [node.id, node]));
8281
+ const nodeIds = new Set(matchingDependencyUpdateEdges(lock, selector).map((edge) => edge.to));
8282
+ for (const root of lock.canonical.roots) {
8283
+ if (root.mode !== "tracking") continue;
8284
+ const node = nodes.get(root.graphNodeId);
8285
+ if (!node) continue;
8286
+ if (dependencyUpdateSelectorMatchesRoot(root, node, selector)) {
8287
+ nodeIds.add(root.graphNodeId);
8288
+ }
8289
+ }
8290
+ return nodeIds;
8291
+ }
8292
+ function dependencyUpdateSelectorMatchesRoot(root, node, selector) {
8293
+ return selector === root.rootId || selector === root.source || selector === root.normalizedSource || selector === root.graphNodeId || selector === node.name || selector === node.source || selector === node.normalizedSource;
8294
+ }
8295
+ function dependencyUpdateEdgeSelected(parentId, alias, options) {
8296
+ const lock = options.previousLock;
8297
+ if (!lock) return false;
8298
+ const selectedNodeIds = new Set((options.dependencyUpdateSelectors ?? []).flatMap((selector) => matchingDependencyUpdateEdges(lock, selector).map((edge) => edge.to)));
8299
+ return lock.canonical.edges.some((edge) => edge.from === parentId && edge.alias === alias && selectedNodeIds.has(edge.to));
8300
+ }
8301
+ function matchingDependencyUpdateEdges(lock, selector) {
8302
+ const nodes = new Map(lock.canonical.nodes.map((node) => [node.id, node]));
8303
+ return lock.canonical.edges.filter((edge) => {
8304
+ const node = nodes.get(edge.to);
8305
+ if (!node || node.mode !== "tracking") return false;
8306
+ return selector === edge.alias || selector === edge.source || selector === edge.normalizedSource || selector === node.id || selector === node.name || selector === node.source || selector === node.normalizedSource;
8307
+ });
8308
+ }
7247
8309
  function dependencyForAlias(dependencies, nodeId, alias) {
7248
8310
  const dependency = dependencies[alias];
7249
8311
  if (!dependency) {
@@ -7259,7 +8321,7 @@ function suggestionForAlias(suggestions, nodeId, alias) {
7259
8321
  return suggestion;
7260
8322
  }
7261
8323
  function warnNoDepsOnce(state, aliases, warn) {
7262
- const unique = sortedUnique3(aliases.filter(Boolean));
8324
+ const unique = sortedUnique4(aliases.filter(Boolean));
7263
8325
  if (unique.length === 0 || state.processedPackageAliases.has("__noDepsWarned")) return;
7264
8326
  state.processedPackageAliases.add("__noDepsWarned");
7265
8327
  warn?.(`--no-deps ignored dependencies for ${state.node.id}: ${unique.join(", ")}`);
@@ -7300,7 +8362,7 @@ function suggestionOptionsForState(state, options) {
7300
8362
  return {
7301
8363
  ...options,
7302
8364
  includeSuggestions: state.includeSuggestions || options.includeSuggestions === true,
7303
- suggestionAliases: sortedUnique3([...options.suggestionAliases ?? [], ...state.suggestionAliases])
8365
+ suggestionAliases: sortedUnique4([...options.suggestionAliases ?? [], ...state.suggestionAliases])
7304
8366
  };
7305
8367
  }
7306
8368
  function explicitSuggestionAlias(alias, options) {
@@ -7312,7 +8374,7 @@ function shouldTreatSuggestionAsOptional(alias, suggestion, options) {
7312
8374
  }
7313
8375
  function combinedSelectors(base, extra) {
7314
8376
  const values = [...base ?? [], ...extra ?? []];
7315
- return values.length > 0 ? sortedUnique3(values) : void 0;
8377
+ return values.length > 0 ? sortedUnique4(values) : void 0;
7316
8378
  }
7317
8379
  function parseDependencySelector(value) {
7318
8380
  const cleaned = value.trim();
@@ -7352,7 +8414,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
7352
8414
  const file = stack.shift();
7353
8415
  if (scanned.has(file)) continue;
7354
8416
  scanned.add(file);
7355
- const content = await readFile22(file, "utf8");
8417
+ const content = await readFile24(file, "utf8");
7356
8418
  for (const include of extractOpenPackIncludeSelectors(content)) {
7357
8419
  await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
7358
8420
  }
@@ -7560,8 +8622,8 @@ async function withCachePathLock(path, fn) {
7560
8622
  }
7561
8623
  }
7562
8624
  function computeSelectedSelectors(artifacts, select, isRoot, chain) {
7563
- if (isRoot && !select?.length) {
7564
- return sortedUnique3(artifacts.map(artifactSelectorKey));
8625
+ if (isRoot && select === void 0) {
8626
+ return sortedUnique4(artifacts.map(artifactSelectorKey));
7565
8627
  }
7566
8628
  const explicit = normalizeArtifactSelectors(select) ?? [];
7567
8629
  const available = new Set(artifacts.map(artifactSelectorKey));
@@ -7570,7 +8632,7 @@ function computeSelectedSelectors(artifacts, select, isRoot, chain) {
7570
8632
  throw new Error(`Selected artifact not found in package: ${missing.join(", ")} (${chain.join(" -> ")})`);
7571
8633
  }
7572
8634
  const required = artifacts.filter((artifact) => artifact.required && artifact.type !== "fragments").map(artifactSelectorKey);
7573
- return sortedUnique3([...explicit, ...required]);
8635
+ return sortedUnique4([...explicit, ...required]);
7574
8636
  }
7575
8637
  function materializeRawNode(state) {
7576
8638
  refreshNode(state);
@@ -7583,11 +8645,11 @@ function materializeRawNode(state) {
7583
8645
  };
7584
8646
  }
7585
8647
  function refreshNode(state) {
7586
- state.node.requiredBy = sortedUnique3([...state.requiredBy]);
7587
- state.node.selected = sortedUnique3([...state.selected]);
8648
+ state.node.requiredBy = sortedUnique4([...state.requiredBy]);
8649
+ state.node.selected = sortedUnique4([...state.selected]);
7588
8650
  const selectionReasons = {};
7589
8651
  for (const [selector, reasons] of [...state.selectionReasons.entries()].sort(([a], [b]) => a.localeCompare(b))) {
7590
- selectionReasons[selector] = sortedUnique3([...reasons]);
8652
+ selectionReasons[selector] = sortedUnique4([...reasons]);
7591
8653
  }
7592
8654
  state.node.selectionReasons = Object.keys(selectionReasons).length > 0 ? selectionReasons : void 0;
7593
8655
  }
@@ -7616,10 +8678,10 @@ function detectDirectCollisions(nodes) {
7616
8678
  }
7617
8679
  }
7618
8680
  function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
7619
- const digest = createHash6("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
8681
+ const digest = createHash8("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
7620
8682
  return `${name}@${version}+${digest}`;
7621
8683
  }
7622
- function sortedUnique3(values) {
8684
+ function sortedUnique4(values) {
7623
8685
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
7624
8686
  }
7625
8687
  async function mapLimit(items, limit, fn) {
@@ -7638,7 +8700,7 @@ async function mapLimit(items, limit, fn) {
7638
8700
  // src/lifecycle/customization.ts
7639
8701
  async function remember(workspaceRoot, runtime, text) {
7640
8702
  const overlayPath = join32(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
7641
- await mkdir17(dirname24(overlayPath), { recursive: true });
8703
+ await mkdir18(dirname25(overlayPath), { recursive: true });
7642
8704
  await appendFile(overlayPath, `${text.trim()}
7643
8705
  `, "utf8");
7644
8706
  return { overlayPath };
@@ -7662,7 +8724,7 @@ async function ejectArtifact(workspaceRoot, item) {
7662
8724
  }
7663
8725
  const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
7664
8726
  const ejectedPath = join32(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
7665
- await mkdir17(dirname24(ejectedPath), { recursive: true });
8727
+ await mkdir18(dirname25(ejectedPath), { recursive: true });
7666
8728
  await rm9(ejectedPath, { recursive: true, force: true });
7667
8729
  await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
7668
8730
  return {
@@ -7754,20 +8816,40 @@ function ejectCommands(candidates, parsed) {
7754
8816
  import { rm as rm10 } from "fs/promises";
7755
8817
 
7756
8818
  // src/lifecycle/source-plan.ts
7757
- import { createHash as createHash8 } from "crypto";
7758
- import { mkdir as mkdir19 } from "fs/promises";
7759
- import { dirname as dirname26, join as join35, resolve as resolve16 } from "path";
8819
+ import { createHash as createHash10 } from "crypto";
8820
+ import { mkdir as mkdir20 } from "fs/promises";
8821
+ import { dirname as dirname27, join as join35 } from "path";
7760
8822
 
7761
8823
  // src/resolve/graph-diff.ts
7762
8824
  function diffGraphLocks(previous, next) {
7763
8825
  if (!previous) return [];
7764
8826
  return [
8827
+ ...diffRoots(previous.canonical.roots, next.canonical.roots),
7765
8828
  ...diffNodes(previous.canonical.nodes, next.canonical.nodes),
7766
8829
  ...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
7767
8830
  ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
7768
8831
  ...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
7769
8832
  ];
7770
8833
  }
8834
+ function diffRoots(previous, next) {
8835
+ const previousById = new Map(previous.map((root) => [root.rootId, root]));
8836
+ const nextById = new Map(next.map((root) => [root.rootId, root]));
8837
+ const lines = [];
8838
+ for (const [rootId, root] of nextById) {
8839
+ const old = previousById.get(rootId);
8840
+ if (!old) {
8841
+ lines.push(`ADDED root ${rootId}`);
8842
+ continue;
8843
+ }
8844
+ if (selectionImportKey(old) !== selectionImportKey(root)) {
8845
+ lines.push(`CHANGED selection import ${rootId}: ${formatSelectionImport(old)} -> ${formatSelectionImport(root)}`);
8846
+ }
8847
+ }
8848
+ for (const rootId of previousById.keys()) {
8849
+ if (!nextById.has(rootId)) lines.push(`REMOVED root ${rootId}`);
8850
+ }
8851
+ return lines.sort((a, b) => a.localeCompare(b));
8852
+ }
7771
8853
  function diffNodes(previous, next) {
7772
8854
  const previousById = new Map(previous.map((node) => [node.id, node]));
7773
8855
  const nextById = new Map(next.map((node) => [node.id, node]));
@@ -7875,10 +8957,30 @@ function formatOverride(decision) {
7875
8957
  function short(hash) {
7876
8958
  return hash.slice(0, 12);
7877
8959
  }
8960
+ function selectionImportKey(root) {
8961
+ const selection = root.selectionImport;
8962
+ if (!selection) return "";
8963
+ return JSON.stringify({
8964
+ configPath: selection.configPath,
8965
+ configHash: selection.configHash,
8966
+ exportHash: selection.exportHash,
8967
+ exportName: selection.exportName,
8968
+ extends: selection.extends,
8969
+ inherited: [...selection.inherited].sort(),
8970
+ additions: [...selection.additions].sort(),
8971
+ exclusions: [...selection.exclusions].sort(),
8972
+ effective: [...selection.effective].sort()
8973
+ });
8974
+ }
8975
+ function formatSelectionImport(root) {
8976
+ const selection = root.selectionImport;
8977
+ if (!selection) return "<direct selectors>";
8978
+ return `${selection.exportName} sha256:${short(selection.exportHash)} effective=[${selection.effective.join(",")}]`;
8979
+ }
7878
8980
 
7879
8981
  // src/resolve/render.ts
7880
- import { createHash as createHash7 } from "crypto";
7881
- import { readFile as readFile23, mkdtemp as mkdtemp5 } from "fs/promises";
8982
+ import { createHash as createHash9 } from "crypto";
8983
+ import { readFile as readFile25, mkdtemp as mkdtemp5 } from "fs/promises";
7882
8984
  import { tmpdir as tmpdir6 } from "os";
7883
8985
  import { join as join33 } from "path";
7884
8986
  async function renderGraphForTarget(graph, targetContext = {}) {
@@ -7932,7 +9034,7 @@ async function renderGraphForTarget(graph, targetContext = {}) {
7932
9034
  if (sourceContent === void 0) {
7933
9035
  throw new Error(`OpenPack include source is not a file: ${edge.to}:${request.selector}`);
7934
9036
  }
7935
- const sourceHash = target.artifacts.find((artifact) => artifact.relativePath.replaceAll("\\", "/") === request.selector)?.hash ?? target.rawNode.artifacts.find((artifact) => artifact.relativePath.replaceAll("\\", "/") === request.selector)?.hash ?? sha2562(sourceContent);
9037
+ const sourceHash = target.artifacts.find((artifact) => artifact.relativePath.replaceAll("\\", "/") === request.selector)?.hash ?? target.rawNode.artifacts.find((artifact) => artifact.relativePath.replaceAll("\\", "/") === request.selector)?.hash ?? sha2563(sourceContent);
7936
9038
  const includeEdge = {
7937
9039
  fromNodeId: request.fromNodeId,
7938
9040
  alias: request.alias,
@@ -8003,7 +9105,7 @@ async function artifactContentMap(artifacts) {
8003
9105
  const out = /* @__PURE__ */ new Map();
8004
9106
  for (const artifact of artifacts) {
8005
9107
  if (artifact.kind !== "file") continue;
8006
- out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile23(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
9108
+ out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile25(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
8007
9109
  }
8008
9110
  return out;
8009
9111
  }
@@ -8016,8 +9118,8 @@ function filterArtifactsByRuntime2(artifacts, adapterName, selectedSet) {
8016
9118
  return false;
8017
9119
  });
8018
9120
  }
8019
- function sha2562(content) {
8020
- return createHash7("sha256").update(content).digest("hex");
9121
+ function sha2563(content) {
9122
+ return createHash9("sha256").update(content).digest("hex");
8021
9123
  }
8022
9124
  function assignInstallNames(graph, artifacts) {
8023
9125
  const aliases = workspaceAliases(graph);
@@ -8266,19 +9368,19 @@ function lockArtifactFor(artifact) {
8266
9368
  }
8267
9369
 
8268
9370
  // src/lifecycle/trust.ts
8269
- import { mkdir as mkdir18, readFile as readFile24 } from "fs/promises";
9371
+ import { mkdir as mkdir19, readFile as readFile26 } from "fs/promises";
8270
9372
  import { homedir as homedir7 } from "os";
8271
- import { dirname as dirname25, join as join34 } from "path";
8272
- import { z as z8 } from "zod";
8273
- var trustStoreSchema = z8.object({
8274
- version: z8.literal(1),
8275
- acceptedSources: z8.array(z8.string().min(1)).default([])
9373
+ import { dirname as dirname26, join as join34 } from "path";
9374
+ import { z as z9 } from "zod";
9375
+ var trustStoreSchema = z9.object({
9376
+ version: z9.literal(1),
9377
+ acceptedSources: z9.array(z9.string().min(1)).default([])
8276
9378
  });
8277
9379
  function normalizeTrustPolicy(policy) {
8278
9380
  return {
8279
- allow: sortedUnique4(policy?.allow ?? []),
8280
- acceptedSources: sortedUnique4(policy?.acceptedSources ?? []),
8281
- denyArtifactTypes: sortedUnique4(policy?.denyArtifactTypes ?? []),
9381
+ allow: sortedUnique5(policy?.allow ?? []),
9382
+ acceptedSources: sortedUnique5(policy?.acceptedSources ?? []),
9383
+ denyArtifactTypes: sortedUnique5(policy?.denyArtifactTypes ?? []),
8282
9384
  requireReviewForTransitive: policy?.requireReviewForTransitive ?? true
8283
9385
  };
8284
9386
  }
@@ -8315,10 +9417,10 @@ async function readTrustedSources(_workspaceRoot, storePath = defaultTrustStoreP
8315
9417
  return (await readTrustStore(storePath)).acceptedSources;
8316
9418
  }
8317
9419
  async function rememberTrustedSources(_workspaceRoot, sources, storePath = defaultTrustStorePath()) {
8318
- const unique = sortedUnique4(sources);
9420
+ const unique = sortedUnique5(sources);
8319
9421
  if (unique.length === 0) return [];
8320
9422
  const store = await readTrustStore(storePath);
8321
- const acceptedSources = sortedUnique4([...store.acceptedSources, ...unique]);
9423
+ const acceptedSources = sortedUnique5([...store.acceptedSources, ...unique]);
8322
9424
  await writeTrustStore(storePath, { version: 1, acceptedSources });
8323
9425
  return unique.filter((source) => !store.acceptedSources.includes(source));
8324
9426
  }
@@ -8337,21 +9439,155 @@ function matchesGlob2(value, pattern) {
8337
9439
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
8338
9440
  return new RegExp(`^${escaped}$`).test(value);
8339
9441
  }
8340
- function sortedUnique4(values) {
9442
+ function sortedUnique5(values) {
8341
9443
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
8342
9444
  }
8343
9445
  async function readTrustStore(path) {
8344
9446
  if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
8345
- return trustStoreSchema.parse(JSON.parse(await readFile24(path, "utf8")));
9447
+ return trustStoreSchema.parse(JSON.parse(await readFile26(path, "utf8")));
8346
9448
  }
8347
9449
  async function writeTrustStore(path, store) {
8348
- await mkdir18(dirname25(path), { recursive: true });
9450
+ await mkdir19(dirname26(path), { recursive: true });
8349
9451
  await writeJsonAtomic(path, trustStoreSchema.parse(store));
8350
9452
  }
8351
9453
  function defaultTrustStorePath() {
8352
9454
  return process.env.AGENTWHEEL_TRUST_STORE ?? join34(homedir7(), ".agentwheel", "trust.json");
8353
9455
  }
8354
9456
 
9457
+ // src/lifecycle/ownership.ts
9458
+ import { resolve as resolve16 } from "path";
9459
+ function workspaceOwnerForRoot(workspaceRoot) {
9460
+ return `workspace-root:${resolve16(workspaceRoot)}`;
9461
+ }
9462
+ async function planArtifactOwnershipHandoff(request) {
9463
+ return validateOwnershipHandoff(request, request.transport ?? localTransport);
9464
+ }
9465
+ async function applyArtifactOwnershipHandoff(request) {
9466
+ if (!request.expectedHash || !request.expectedRevision) {
9467
+ throw new Error("Applying an ownership handoff requires expectedHash and expectedRevision from a reviewed dry-run.");
9468
+ }
9469
+ const transport = request.transport ?? localTransport;
9470
+ const scope = { installationType: request.installationType, stateKey: request.stateKey };
9471
+ const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, scope);
9472
+ try {
9473
+ if (await readApplyJournal(request.targetRoot, request.adapter, transport, scope)) {
9474
+ throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
9475
+ }
9476
+ const plan = await validateOwnershipHandoff(request, transport);
9477
+ const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
9478
+ if (!manifest || manifest.version !== 2) {
9479
+ throw new Error("Ownership handoff requires an Agentwheel v2 install manifest.");
9480
+ }
9481
+ assertManifestIdentity(manifest, request);
9482
+ if (manifest.revision !== request.expectedRevision) {
9483
+ throw new Error(`Manifest revision changed while locked: expected ${request.expectedRevision}, found ${manifest.revision}`);
9484
+ }
9485
+ const matches = manifest.entries.filter(
9486
+ (entry) => entry.artifactType === request.artifactType && entry.artifactName === request.artifactName
9487
+ );
9488
+ const selected = matches[0];
9489
+ if (!selected || matches.length !== 1) {
9490
+ throw new Error(`Ownership handoff selection changed while locked: ${request.artifactType}/${request.artifactName}`);
9491
+ }
9492
+ if (selected.workspaceOwner !== plan.fromOwner || selected.hash !== request.expectedHash) {
9493
+ throw new Error(`Ownership handoff preconditions changed while locked: ${request.artifactType}/${request.artifactName}`);
9494
+ }
9495
+ await writeInstallManifest({
9496
+ ...manifest,
9497
+ entries: manifest.entries.map((entry) => entry.artifactType === request.artifactType && entry.artifactName === request.artifactName ? { ...entry, workspaceOwner: plan.toOwner } : entry)
9498
+ }, transport);
9499
+ return plan;
9500
+ } finally {
9501
+ await lock.release();
9502
+ }
9503
+ }
9504
+ async function validateOwnershipHandoff(request, transport) {
9505
+ if (request.expectedHash) assertSha256(request.expectedHash, "expected artifact hash");
9506
+ if (request.expectedRevision) assertSha256(request.expectedRevision, "expected manifest revision");
9507
+ const scope = { installationType: request.installationType, stateKey: request.stateKey };
9508
+ const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
9509
+ if (!manifest) throw new Error(`No install manifest for ${request.adapter} at ${request.targetRoot}`);
9510
+ if (manifest.version !== 2) throw new Error("Ownership handoff requires an Agentwheel v2 install manifest.");
9511
+ assertManifestIdentity(manifest, request);
9512
+ if (request.expectedRevision && manifest.revision !== request.expectedRevision) {
9513
+ throw new Error(`Manifest revision precondition failed: expected ${request.expectedRevision}, found ${manifest.revision}`);
9514
+ }
9515
+ const matches = manifest.entries.filter(
9516
+ (entry2) => entry2.artifactType === request.artifactType && entry2.artifactName === request.artifactName
9517
+ );
9518
+ if (matches.length !== 1) {
9519
+ throw new Error(
9520
+ `Expected exactly one managed artifact for ${request.artifactType}/${request.artifactName}, found ${matches.length}`
9521
+ );
9522
+ }
9523
+ const entry = matches[0];
9524
+ const fromOwner = workspaceOwnerForRoot(request.fromWorkspaceRoot);
9525
+ const toOwner = workspaceOwnerForRoot(request.toWorkspaceRoot);
9526
+ if (fromOwner === toOwner) throw new Error("Ownership handoff requires different workspace roots.");
9527
+ if (entry.workspaceOwner !== fromOwner) {
9528
+ throw new Error(`Old owner precondition failed for ${entry.path}: expected ${fromOwner}, found ${entry.workspaceOwner}`);
9529
+ }
9530
+ if (request.expectedHash && entry.hash !== request.expectedHash) {
9531
+ throw new Error(`Manifest hash precondition failed for ${entry.path}: expected ${request.expectedHash}, found ${entry.hash}`);
9532
+ }
9533
+ const destPath = containedArtifactPath(request.targetRoot, entry.path);
9534
+ if (!await transport.pathExists(destPath)) throw new Error(`Managed artifact is missing: ${entry.path}`);
9535
+ const currentHash = await transport.hashPath(destPath);
9536
+ if (currentHash !== entry.hash) {
9537
+ throw new Error(`Managed artifact is drifted at ${entry.path}: manifest ${entry.hash}, current ${currentHash}`);
9538
+ }
9539
+ if (request.expectedHash && currentHash !== request.expectedHash) {
9540
+ throw new Error(`Current hash precondition failed for ${entry.path}: expected ${request.expectedHash}, found ${currentHash}`);
9541
+ }
9542
+ return {
9543
+ adapter: request.adapter,
9544
+ installationType: request.installationType,
9545
+ stateKey: request.stateKey,
9546
+ targetRoot: request.targetRoot,
9547
+ selector: `${request.artifactType}/${request.artifactName}`,
9548
+ path: entry.path,
9549
+ artifactHash: currentHash,
9550
+ manifestRevision: manifest.revision,
9551
+ fromOwner,
9552
+ toOwner
9553
+ };
9554
+ }
9555
+ function containedArtifactPath(targetRoot, relativePath) {
9556
+ if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
9557
+ throw new Error(`Unsafe managed artifact path: ${relativePath}`);
9558
+ }
9559
+ const root = resolve16(targetRoot);
9560
+ const candidate = resolve16(root, relativePath);
9561
+ if (candidate !== root && !candidate.startsWith(`${root}/`)) {
9562
+ throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
9563
+ }
9564
+ return candidate;
9565
+ }
9566
+ function assertSha256(value, label) {
9567
+ if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`${label} must be a lowercase SHA-256 digest.`);
9568
+ }
9569
+ function assertManifestIdentity(manifest, request) {
9570
+ const requestedInstallationType = request.installationType ?? defaultInstallationType;
9571
+ const requestedStateKey = stateKeyFor(request.adapter, {
9572
+ installationType: requestedInstallationType,
9573
+ stateKey: request.stateKey
9574
+ });
9575
+ const manifestStateKey = stateKeyFor(manifest.adapter, {
9576
+ installationType: manifest.installationType,
9577
+ stateKey: manifest.stateKey
9578
+ });
9579
+ const mismatches = [];
9580
+ if (manifest.targetRoot !== request.targetRoot) mismatches.push(`targetRoot ${manifest.targetRoot}`);
9581
+ if (manifest.adapter !== request.adapter) mismatches.push(`adapter ${manifest.adapter}`);
9582
+ if (manifest.installationType !== requestedInstallationType) {
9583
+ mismatches.push(`installationType ${manifest.installationType}`);
9584
+ }
9585
+ if (manifestStateKey !== requestedStateKey) mismatches.push(`stateKey ${manifestStateKey}`);
9586
+ if (mismatches.length > 0) {
9587
+ throw new Error(`Install manifest identity does not match requested target: ${mismatches.join(", ")}`);
9588
+ }
9589
+ }
9590
+
8355
9591
  // src/lifecycle/source-plan.ts
8356
9592
  async function createGraphSourcePlan(options) {
8357
9593
  if (options.roots.length === 0) {
@@ -8392,6 +9628,7 @@ async function createGraphSourcePlan(options) {
8392
9628
  noDeps: options.noDeps,
8393
9629
  includeSuggestions: options.includeSuggestions,
8394
9630
  suggestionAliases: options.suggestionAliases,
9631
+ dependencyUpdateSelectors: options.dependencyUpdateSelectors,
8395
9632
  lockedResolution: options.lockedResolution,
8396
9633
  frozenLock: lockMode,
8397
9634
  offline: options.offline,
@@ -8426,7 +9663,7 @@ async function createGraphSourcePlan(options) {
8426
9663
  const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
8427
9664
  baseRevision: manifest?.revision ?? null,
8428
9665
  graphLockDigest,
8429
- workspaceOwner: workspaceOwnerId(workspaceRoot),
9666
+ workspaceOwner: workspaceOwnerForRoot(workspaceRoot),
8430
9667
  installationType: resolvedInstallationType,
8431
9668
  stateKey,
8432
9669
  forceDrift: options.forceDrift,
@@ -8448,8 +9685,8 @@ async function createGraphSourcePlan(options) {
8448
9685
  recoveredPendingApply
8449
9686
  };
8450
9687
  }
8451
- function graphLockPathForTarget(workspaceRoot, targetKey, adapter, targetFingerprintParts2) {
8452
- return pathForGraphLock(workspaceRoot, targetKey, adapter, computeTargetFingerprint(targetFingerprintParts2));
9688
+ function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
9689
+ return pathForGraphLock(workspaceRoot, targetKey2, adapter, computeTargetFingerprint(targetFingerprintParts2));
8453
9690
  }
8454
9691
  function desiredArtifactsFromGraphBundle(bundle) {
8455
9692
  return bundle.artifacts.map((artifact) => desiredArtifactFromResolved(artifact));
@@ -8491,17 +9728,14 @@ async function readExistingGraphLock(path) {
8491
9728
  if (!await pathExists(path)) return void 0;
8492
9729
  return readGraphLock(path);
8493
9730
  }
8494
- function pathForGraphLock(workspaceRoot, targetKey, adapter, targetFingerprint) {
8495
- return join35(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
9731
+ function pathForGraphLock(workspaceRoot, targetKey2, adapter, targetFingerprint) {
9732
+ return join35(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey2), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
8496
9733
  }
8497
9734
  function sanitizePathSegment(value) {
8498
9735
  return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
8499
9736
  }
8500
9737
  function digestGraphLock(lock) {
8501
- return createHash8("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
8502
- }
8503
- function workspaceOwnerId(workspaceRoot) {
8504
- return `workspace-root:${resolve16(workspaceRoot)}`;
9738
+ return createHash10("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
8505
9739
  }
8506
9740
  function assertFrozenGraph(previousLock, graph, frozen, label) {
8507
9741
  if (!frozen) return;
@@ -8523,11 +9757,51 @@ function assertFrozenGraph(previousLock, graph, frozen, label) {
8523
9757
  mismatches.push(`${node.id}: sourceHash ${locked.sourceHash} -> ${node.sourceHash}`);
8524
9758
  }
8525
9759
  }
9760
+ const lockedRoots = new Map(previousLock.canonical.roots.map((root) => [root.rootId, root]));
9761
+ const graphRootIds = new Set(graph.roots.map((root) => root.rootId));
9762
+ for (const root of graph.roots) {
9763
+ const locked = lockedRoots.get(root.rootId);
9764
+ if (!locked) {
9765
+ mismatches.push(`${root.rootId}: new graph root`);
9766
+ continue;
9767
+ }
9768
+ if (selectionImportKey2(locked.selectionImport) !== selectionImportKey2(root.selectionImport)) {
9769
+ mismatches.push(`${root.rootId}: selection import changed`);
9770
+ }
9771
+ if (selectorKey(locked.selected) !== selectorKey(root.selected)) {
9772
+ mismatches.push(`${root.rootId}: selected artifacts changed`);
9773
+ }
9774
+ }
9775
+ for (const root of previousLock.canonical.roots) {
9776
+ if (!graphRootIds.has(root.rootId)) {
9777
+ mismatches.push(`${root.rootId}: removed graph root`);
9778
+ }
9779
+ }
8526
9780
  if (mismatches.length > 0) {
8527
9781
  throw new Error(`${label} would change graph nodes:
8528
9782
  ${mismatches.map((item) => `- ${item}`).join("\n")}`);
8529
9783
  }
8530
9784
  }
9785
+ function selectionImportKey2(selection) {
9786
+ if (!selection) return "";
9787
+ return JSON.stringify({
9788
+ configPath: selection.configPath,
9789
+ configHash: selection.configHash,
9790
+ exportHash: selection.exportHash,
9791
+ exportName: selection.exportName,
9792
+ extends: selection.extends,
9793
+ inherited: sortedUnique6(selection.inherited),
9794
+ additions: sortedUnique6(selection.additions),
9795
+ exclusions: sortedUnique6(selection.exclusions),
9796
+ effective: sortedUnique6(selection.effective)
9797
+ });
9798
+ }
9799
+ function sortedUnique6(values) {
9800
+ return [...new Set(values)].sort((a, b) => a.localeCompare(b));
9801
+ }
9802
+ function selectorKey(selectors) {
9803
+ return sortedUnique6(selectors).join("\0");
9804
+ }
8531
9805
  async function assertTrusted(sources, options) {
8532
9806
  if (sources.length === 0) return;
8533
9807
  if (options.readOnly === true) {
@@ -8555,8 +9829,39 @@ Type yes to continue: `);
8555
9829
  ${sources.map((source) => `- ${source}`).join("\n")}`);
8556
9830
  }
8557
9831
 
9832
+ // src/runtime/reload.ts
9833
+ function hasExecutedSemanticPluginChanges(plan, options = {}) {
9834
+ return plan.operations.some((operation) => {
9835
+ if (operation.action === "plugin") return options.executePlugins === true;
9836
+ if (operation.action === "remove" && operation.semanticPlugin) return operation.execute !== false;
9837
+ return false;
9838
+ });
9839
+ }
9840
+ async function reloadRuntimeAfterPluginChanges(plan, target, transport, options = {}) {
9841
+ if (!options.enabled) return false;
9842
+ if (!hasExecutedSemanticPluginChanges(plan, options)) return false;
9843
+ const commands = target.reloadCommands ?? [];
9844
+ if (commands.length === 0) {
9845
+ throw new Error(`--reload-runtimes requested for ${targetLabel(target)} but no reloadCommands are configured on the agent or profile runtime.`);
9846
+ }
9847
+ if (!transport.execFile) {
9848
+ throw new Error(`Cannot reload ${targetLabel(target)} over ${transport.description}: transport does not support command execution.`);
9849
+ }
9850
+ for (const [command, ...args] of commands) {
9851
+ if (!command) throw new Error(`Invalid empty reload command for ${targetLabel(target)}.`);
9852
+ await transport.execFile(command, args, { cwd: target.targetRoot });
9853
+ }
9854
+ return true;
9855
+ }
9856
+ function formatReloadCommands(commands) {
9857
+ return (commands ?? []).map((command) => command.join(" ")).join(" && ");
9858
+ }
9859
+ function targetLabel(target) {
9860
+ return target.targetKey ?? target.agentName ?? `${target.adapter} at ${target.targetRoot}`;
9861
+ }
9862
+
8558
9863
  // src/runtime/target.ts
8559
- import { basename as basename20, dirname as dirname27, join as join36, resolve as resolve17 } from "path";
9864
+ import { basename as basename20, dirname as dirname28, join as join36, resolve as resolve17 } from "path";
8560
9865
  var runtimeMarkers = [
8561
9866
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
8562
9867
  { adapter: "claude", dirs: [".claude"] },
@@ -8624,6 +9929,9 @@ function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installatio
8624
9929
  ...target,
8625
9930
  adapterConfig: runtime.adapterConfig,
8626
9931
  adapterModule: runtime.adapterModule,
9932
+ executePlugins: runtime.executePlugins,
9933
+ reloadRuntimes: runtime.reloadRuntimes,
9934
+ reloadCommands: runtime.reloadCommands ?? target.reloadCommands,
8627
9935
  targetKey: runtime.agent,
8628
9936
  source: "profile"
8629
9937
  };
@@ -8635,6 +9943,9 @@ function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installatio
8635
9943
  installationType: installationType ?? runtime.installationType,
8636
9944
  targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot,
8637
9945
  workspaceRoot,
9946
+ executePlugins: runtime.executePlugins,
9947
+ reloadRuntimes: runtime.reloadRuntimes,
9948
+ reloadCommands: runtime.reloadCommands,
8638
9949
  transport: "local",
8639
9950
  source: "profile"
8640
9951
  };
@@ -8668,7 +9979,7 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
8668
9979
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
8669
9980
  for (const dir of marker.dirs) {
8670
9981
  if (basename20(root) === dir) {
8671
- matches.push({ adapter: marker.adapter, targetRoot: dirname27(root) });
9982
+ matches.push({ adapter: marker.adapter, targetRoot: dirname28(root) });
8672
9983
  } else if (await pathExists(join36(root, dir))) {
8673
9984
  matches.push({ adapter: marker.adapter, targetRoot: root });
8674
9985
  }
@@ -8690,6 +10001,7 @@ function targetFromAgent(name, config, workspaceRoot, installationType) {
8690
10001
  installationType: installationType ?? agent.installationType,
8691
10002
  targetRoot: agent.transport === "ssh" ? agent.root : resolveConfigPath(agent.root, workspaceRoot),
8692
10003
  workspaceRoot,
10004
+ reloadCommands: agent.reloadCommands,
8693
10005
  transport: agent.transport,
8694
10006
  ssh: agent.transport === "ssh" ? {
8695
10007
  host: agent.host ?? "",
@@ -8710,7 +10022,7 @@ function dedupeTargets(matches) {
8710
10022
  function runtimeScanRoot(request) {
8711
10023
  const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
8712
10024
  if (request.targetRoot) return root;
8713
- return runtimeMarkers.some((marker) => marker.dirs.includes(basename20(root))) ? dirname27(root) : root;
10025
+ return runtimeMarkers.some((marker) => marker.dirs.includes(basename20(root))) ? dirname28(root) : root;
8714
10026
  }
8715
10027
 
8716
10028
  // src/lifecycle/profile.ts
@@ -8725,6 +10037,10 @@ async function syncProfile(options) {
8725
10037
  throw new Error("Profile sync needs a source argument or configured packages.");
8726
10038
  }
8727
10039
  const results = [];
10040
+ const selected = normalizeArtifactSelectors(options.select, options.skills);
10041
+ if (selected && packages.some((pkg) => pkg.selection)) {
10042
+ throw new Error("--select/--skill cannot be combined with a package selection import.");
10043
+ }
8728
10044
  for (const runtime of profile.runtimes) {
8729
10045
  const target = resolveProfileRuntimeTarget(runtime, config, options.workspaceRoot, options.installationType);
8730
10046
  const transport = transportForTarget(target);
@@ -8738,14 +10054,14 @@ async function syncProfile(options) {
8738
10054
  });
8739
10055
  const installationType = target.installationType ?? defaultInstallationType;
8740
10056
  resolveInstallationTypeForAdapter(adapter, installationType);
8741
- const selected = normalizeArtifactSelectors(options.select, options.skills);
8742
10057
  const graphPlan = await createGraphSourcePlan({
8743
10058
  roots: packages.map((pkg) => ({
8744
10059
  rootId: pkg.name,
8745
10060
  source: pkg.source,
8746
10061
  mode: options.mode ?? pkg.mode,
8747
10062
  ref: pkg.requestedRef,
8748
- select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
10063
+ select: pkg.selection ? void 0 : selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
10064
+ selection: pkg.selection,
8749
10065
  aliases: pkg.aliases,
8750
10066
  overrides: pkg.overrides,
8751
10067
  includeSuggestions: options.includeSuggestions === true || pkg.withSuggestions === true,
@@ -8784,21 +10100,31 @@ async function syncProfile(options) {
8784
10100
  replaceConflict: options.replaceConflict
8785
10101
  });
8786
10102
  try {
8787
- results.push({
10103
+ const result = {
8788
10104
  runtime: adapter.name,
8789
10105
  targetRoot: installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh"),
8790
10106
  transport: transport.kind,
8791
10107
  packageName: packages.map((pkg) => pkg.name).join(","),
8792
- plan: graphPlan.plan
8793
- });
10108
+ plan: graphPlan.plan,
10109
+ graphPlan,
10110
+ graphLockDigest: graphPlan.graphLockDigest,
10111
+ warnings: graphPlan.warnings
10112
+ };
8794
10113
  if (!options.dryRun) {
10114
+ const executePlugins = runtime.executePlugins ?? options.executePlugins;
8795
10115
  await applyCombinedInstallPlan(graphPlan.plan, {
8796
- executePlugins: runtime.executePlugins ?? options.executePlugins,
10116
+ executePlugins,
8797
10117
  transport,
8798
10118
  graphLockDigest: graphPlan.graphLockDigest,
8799
10119
  graphLock: { path: graphPlan.graphLockPath, lock: graphPlan.bundle.graphLock }
8800
10120
  });
10121
+ result.reloaded = await reloadRuntimeAfterPluginChanges(graphPlan.plan, target, transport, {
10122
+ enabled: runtime.reloadRuntimes ?? options.reloadRuntimes,
10123
+ executePlugins
10124
+ });
10125
+ result.reloadCommandSummary = result.reloaded ? formatReloadCommands(target.reloadCommands) : void 0;
8801
10126
  }
10127
+ results.push(result);
8802
10128
  } finally {
8803
10129
  await rm10(graphPlan.bundle.root, { recursive: true, force: true });
8804
10130
  }
@@ -8994,9 +10320,9 @@ function shellQuoteArg(value) {
8994
10320
  }
8995
10321
 
8996
10322
  // src/cli/update-check.ts
8997
- import { mkdir as mkdir20, readFile as readFile25, writeFile as writeFile18 } from "fs/promises";
10323
+ import { mkdir as mkdir21, readFile as readFile27, writeFile as writeFile19 } from "fs/promises";
8998
10324
  import { homedir as homedir8 } from "os";
8999
- import { dirname as dirname28, join as join37 } from "path";
10325
+ import { dirname as dirname29, join as join37 } from "path";
9000
10326
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
9001
10327
  var DEFAULT_TIMEOUT_MS = 300;
9002
10328
  var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
@@ -9041,7 +10367,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
9041
10367
  }
9042
10368
  async function readCache(path) {
9043
10369
  try {
9044
- const parsed = JSON.parse(await readFile25(path, "utf8"));
10370
+ const parsed = JSON.parse(await readFile27(path, "utf8"));
9045
10371
  if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
9046
10372
  return { checkedAt: parsed.checkedAt, latest: parsed.latest };
9047
10373
  } catch {
@@ -9049,8 +10375,8 @@ async function readCache(path) {
9049
10375
  }
9050
10376
  }
9051
10377
  async function writeCache(path, cache) {
9052
- await mkdir20(dirname28(path), { recursive: true });
9053
- await writeFile18(path, `${JSON.stringify(cache, null, 2)}
10378
+ await mkdir21(dirname29(path), { recursive: true });
10379
+ await writeFile19(path, `${JSON.stringify(cache, null, 2)}
9054
10380
  `, "utf8");
9055
10381
  }
9056
10382
  function warnIfNewer(latest, current, stderr = process.stderr) {
@@ -9213,9 +10539,9 @@ function isCrossPackageSelector(value) {
9213
10539
  }
9214
10540
 
9215
10541
  // src/model/package-migrate.ts
9216
- import { readFile as readFile26, rename as rename4, writeFile as writeFile19 } from "fs/promises";
10542
+ import { readFile as readFile28, rename as rename4, writeFile as writeFile20 } from "fs/promises";
9217
10543
  import { join as join39, resolve as resolve19 } from "path";
9218
- import { applyEdits, modify, parse as parse4 } from "jsonc-parser";
10544
+ import { applyEdits, modify, parse as parse5 } from "jsonc-parser";
9219
10545
  async function migratePackageManifest(root) {
9220
10546
  const packageRoot = resolve19(root);
9221
10547
  for (const name of openPackManifestNames) {
@@ -9231,10 +10557,10 @@ async function migratePackageManifest(root) {
9231
10557
  const from = join39(packageRoot, legacyName);
9232
10558
  const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
9233
10559
  const to = join39(packageRoot, toName);
9234
- const content = await readFile26(from, "utf8");
10560
+ const content = await readFile28(from, "utf8");
9235
10561
  const updated = updateSchemaVersion(content);
9236
10562
  await rename4(from, to);
9237
- await writeFile19(to, updated, "utf8");
10563
+ await writeFile20(to, updated, "utf8");
9238
10564
  return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
9239
10565
  }
9240
10566
  async function firstExistingLegacyManifest(root) {
@@ -9245,7 +10571,7 @@ async function firstExistingLegacyManifest(root) {
9245
10571
  }
9246
10572
  function updateSchemaVersion(content) {
9247
10573
  const errors = [];
9248
- parse4(content, errors, { allowTrailingComma: true, disallowComments: false });
10574
+ parse5(content, errors, { allowTrailingComma: true, disallowComments: false });
9249
10575
  if (errors.length > 0) {
9250
10576
  throw new Error("Cannot migrate invalid JSON/JSONC package manifest.");
9251
10577
  }
@@ -9257,11 +10583,11 @@ function updateSchemaVersion(content) {
9257
10583
 
9258
10584
  // src/cli/version.ts
9259
10585
  import { readFileSync } from "fs";
9260
- import { dirname as dirname29, join as join40 } from "path";
10586
+ import { dirname as dirname30, join as join40 } from "path";
9261
10587
  import { fileURLToPath as fileURLToPath2 } from "url";
9262
10588
  var FALLBACK_VERSION = "0.0.0";
9263
10589
  function resolveCliVersion() {
9264
- let dir = dirname29(fileURLToPath2(import.meta.url));
10590
+ let dir = dirname30(fileURLToPath2(import.meta.url));
9265
10591
  while (true) {
9266
10592
  try {
9267
10593
  const pkg = JSON.parse(readFileSync(join40(dir, "package.json"), "utf8"));
@@ -9270,7 +10596,7 @@ function resolveCliVersion() {
9270
10596
  }
9271
10597
  } catch {
9272
10598
  }
9273
- const parent = dirname29(dir);
10599
+ const parent = dirname30(dir);
9274
10600
  if (parent === dir) return FALLBACK_VERSION;
9275
10601
  dir = parent;
9276
10602
  }
@@ -9280,6 +10606,7 @@ function resolveCliVersion() {
9280
10606
  var CLI_VERSION = resolveCliVersion();
9281
10607
  var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
9282
10608
  var COMPANION_SKILL_NAME = "agentwheel";
10609
+ var planOutputFormats = ["human", "json", "mermaid", "html"];
9283
10610
  var program = new Command();
9284
10611
  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", `
9285
10612
 
@@ -9339,17 +10666,33 @@ program.command("scan").description("scan a package source for validation findin
9339
10666
  }
9340
10667
  if (!result.ok) process.exitCode = 1;
9341
10668
  });
9342
- program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "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) => {
10669
+ program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "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) => {
9343
10670
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
9344
10671
  });
9345
- program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--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) => {
10672
+ program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "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) => {
9346
10673
  await runInstallCommand(source, options, { apply: !options.dryRun });
9347
10674
  });
9348
- 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 or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--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) => {
10675
+ program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "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, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
10676
+ await servePlanDashboard({
10677
+ bind: options.bind,
10678
+ port: parseServePort(options.port),
10679
+ intervalSeconds: parseServeIntervalSeconds(options.interval),
10680
+ once: options.once === true,
10681
+ buildReport: () => buildPlanReport(source, options)
10682
+ });
10683
+ });
10684
+ 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 or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "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) => {
9349
10685
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
9350
10686
  await runInstallCommand(source, options, { apply: !options.dryRun });
9351
10687
  });
9352
- 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 or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--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("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).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) => {
10688
+ 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 or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--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("--dependency <name-or-source>", "update one tracking dependency while keeping unrelated graph nodes locked (repeatable)", collectDependencyOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--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) => {
10689
+ if (name && options.dependency.length > 0) throw new Error("A package argument cannot be combined with --dependency.");
10690
+ if (options.dependency.length > 0 && (options.select.length > 0 || options.skill.length > 0)) {
10691
+ throw new Error("--dependency cannot be combined with --select or --skill; package selections remain unchanged.");
10692
+ }
10693
+ if (options.dependency.length > 0 && (options.frozenLock || options.offline)) {
10694
+ throw new Error("--dependency cannot be combined with --frozen-lock or --offline.");
10695
+ }
9353
10696
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
9354
10697
  const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
9355
10698
  for (const target of targets) {
@@ -9465,6 +10808,45 @@ program.command("remember").description("append text to the local instructions o
9465
10808
  console.log(`Remembered in ${result.overlayPath}.`);
9466
10809
  console.log(nextInstallNudge());
9467
10810
  });
10811
+ var ownershipCommand = program.command("ownership").description("inspect and transfer manifest ownership without rewriting runtime artifacts");
10812
+ ownershipCommand.command("handoff").description("transfer one managed artifact between Agentwheel workspace roots").argument("<selector>", "exact artifact selector in type/name form").requiredOption("--from-workspace-root <path>", "current owning workspace root").requiredOption("--to-workspace-root <path>", "new owning workspace root").option("--expected-hash <sha256>", "expected current artifact hash; required when applying").option("--expected-revision <sha256>", "expected install manifest revision; required when applying").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--profile <name>", "workspace runtime profile (must resolve to one target)").option("--dry-run", "validate all preconditions without writing the manifest", false).action(async (selector, options) => {
10813
+ if (!options.dryRun && (!options.expectedHash || !options.expectedRevision)) {
10814
+ throw new Error("Applying an ownership handoff requires --expected-hash and --expected-revision from a reviewed --dry-run.");
10815
+ }
10816
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
10817
+ const targets = await resolveCliTargets(normalizedOptions);
10818
+ if (targets.length !== 1) {
10819
+ throw new Error(`Ownership handoff requires exactly one runtime target, found ${targets.length}. Select one --agent or adapter target.`);
10820
+ }
10821
+ const [artifactType, artifactName, ...extra] = selector.split("/");
10822
+ if (!artifactType || !artifactName || extra.length > 0) {
10823
+ throw new Error(`Ownership handoff selector must be exactly type/name: ${selector}`);
10824
+ }
10825
+ const target = targets[0];
10826
+ const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
10827
+ const adapter = await resolveAdapterForTarget(target, adapterOptions);
10828
+ const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
10829
+ const state = installStateForTarget(target, adapter, adapterOptions, installationType);
10830
+ const request = {
10831
+ ...state,
10832
+ targetRoot: state.installRoot,
10833
+ adapter: adapter.name,
10834
+ artifactType,
10835
+ artifactName,
10836
+ fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
10837
+ toWorkspaceRoot: normalizeCliPath(options.toWorkspaceRoot),
10838
+ expectedHash: options.expectedHash,
10839
+ expectedRevision: options.expectedRevision,
10840
+ transport: transportForTarget(target)
10841
+ };
10842
+ const result = options.dryRun ? await planArtifactOwnershipHandoff(request) : await applyArtifactOwnershipHandoff(request);
10843
+ console.log(`${options.dryRun ? "Ownership handoff plan" : "Ownership handed off"}: ${result.selector}`);
10844
+ console.log(`Target: ${result.adapter}/${result.installationType ?? "default"} at ${result.targetRoot}`);
10845
+ console.log(`Path: ${result.path}`);
10846
+ console.log(`Hash: ${result.artifactHash}`);
10847
+ console.log(`Manifest revision: ${result.manifestRevision}`);
10848
+ console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
10849
+ });
9468
10850
  program.command("eject").description("copy a managed artifact into local ownership").argument("<item>", "package/type/name").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
9469
10851
  const targetRoot = normalizeTargetRoot(options.targetRoot);
9470
10852
  const result = await ejectArtifact(targetRoot, item);
@@ -9514,6 +10896,38 @@ program.command("status").description("show configured packages and runtime inst
9514
10896
  await printStatus(target, normalizedOptions);
9515
10897
  }
9516
10898
  });
10899
+ var journalCommand = program.command("journal").description("inspect or abort pending apply journals");
10900
+ journalCommand.command("list").description("show pending apply journals for resolved runtime targets").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
10901
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
10902
+ const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
10903
+ let pending = 0;
10904
+ for (const target of targets) {
10905
+ const state = await journalStateForTarget(target, normalizedOptions);
10906
+ const journal = await readApplyJournal(state.installRoot, state.adapter.name, state.transport, state.state);
10907
+ if (!journal) continue;
10908
+ pending += 1;
10909
+ console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
10910
+ console.log(` journal: ${join41(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
10911
+ console.log(` stateKey: ${state.state.stateKey}`);
10912
+ console.log(` createdAt: ${journal.createdAt}`);
10913
+ console.log(` updatedAt: ${journal.updatedAt}`);
10914
+ console.log(` operations: ${journal.operations.length}, completed: ${journal.completed.length}`);
10915
+ }
10916
+ if (pending === 0) console.log("No pending apply journals.");
10917
+ });
10918
+ journalCommand.command("abort").description("archive pending apply journals without touching runtime files").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
10919
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
10920
+ const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
10921
+ let aborted = 0;
10922
+ for (const target of targets) {
10923
+ const state = await journalStateForTarget(target, normalizedOptions);
10924
+ const result = await abortApplyJournal(state.installRoot, state.adapter.name, state.transport, state.state);
10925
+ if (!result) continue;
10926
+ aborted += 1;
10927
+ console.log(`Archived ${state.adapter.name}/${state.installationType} pending journal: ${result.archivePath}`);
10928
+ }
10929
+ if (aborted === 0) console.log("No pending apply journals.");
10930
+ });
9517
10931
  program.command("doctor").description("check agentwheel runtime setup and companion skill guidance").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").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("--skill <name>", "check a specific skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--source <source>", "source to use in suggested install commands").option("--json", "print machine-readable doctor report", false).action(async (options) => {
9518
10932
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
9519
10933
  const target = await resolveRuntimeTarget({
@@ -9526,7 +10940,15 @@ program.command("doctor").description("check agentwheel runtime setup and compan
9526
10940
  });
9527
10941
  async function runInstallCommand(nameOrSource, options, behavior) {
9528
10942
  const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
9529
- if (options.profile) {
10943
+ const outputFormat = effectivePlanOutputFormat(normalizedOptions);
10944
+ if (outputFormat !== "human") {
10945
+ const report = await buildPlanReport(nameOrSource, normalizedOptions);
10946
+ if (report.targets.some((target) => target.hasBlockingChanges)) process.exitCode = 1;
10947
+ process.stdout.write(`${renderReport(report, outputFormat)}
10948
+ `);
10949
+ return;
10950
+ }
10951
+ if (normalizedOptions.profile) {
9530
10952
  const target = await resolveRuntimeTarget({
9531
10953
  targetRoot: normalizedOptions.targetRoot,
9532
10954
  adapter: normalizedOptions.adapter,
@@ -9535,7 +10957,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9535
10957
  });
9536
10958
  const results = await syncProfile({
9537
10959
  workspaceRoot: target.workspaceRoot,
9538
- profile: options.profile,
10960
+ profile: normalizedOptions.profile,
9539
10961
  source: nameOrSource,
9540
10962
  driver: normalizedOptions.driver,
9541
10963
  mode: normalizedOptions.mode,
@@ -9543,6 +10965,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9543
10965
  installationType: normalizedOptions.installationType,
9544
10966
  dryRun: !behavior.apply,
9545
10967
  executePlugins: normalizedOptions.executePlugins,
10968
+ reloadRuntimes: shouldReloadRuntimes(normalizedOptions),
9546
10969
  allowAdapterCode: normalizedOptions.allowAdapterCode,
9547
10970
  forceDrift: normalizedOptions.forceDrift,
9548
10971
  forceConflict: normalizedOptions.forceConflict,
@@ -9562,9 +10985,12 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9562
10985
  for (const result of results) {
9563
10986
  console.log(`Profile ${normalizedOptions.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
9564
10987
  console.log(formatPlan(result.plan));
10988
+ if (result.reloaded) console.log(`Reloaded runtime via ${result.reloadCommandSummary}.`);
9565
10989
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
9566
10990
  }
9567
- if (behavior.apply) console.log("Applied.");
10991
+ if (behavior.apply) {
10992
+ console.log("Applied.");
10993
+ }
9568
10994
  return;
9569
10995
  }
9570
10996
  const targets = await resolveCliTargets(normalizedOptions);
@@ -9588,16 +11014,23 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9588
11014
  throw teachingInstallError(nameOrSource, error);
9589
11015
  }
9590
11016
  }
9591
- for (const result of await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage }, { mode: "install" })) {
11017
+ for (const result of await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage, reportFormat: outputFormat }, { mode: "install" })) {
9592
11018
  console.log(formatGraphPlan(result));
9593
11019
  if (behavior.apply) {
11020
+ const transport = transportForTarget(target);
11021
+ const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
9594
11022
  await applyCombinedInstallPlan(result.plan, {
9595
- executePlugins: targetOptions.executePlugins,
9596
- transport: transportForTarget(target),
11023
+ executePlugins,
11024
+ transport,
9597
11025
  graphLockDigest: result.graphLockDigest,
9598
11026
  graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
9599
11027
  });
11028
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
11029
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
11030
+ executePlugins
11031
+ });
9600
11032
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
11033
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
9601
11034
  }
9602
11035
  await rm11(result.bundle.root, { recursive: true, force: true });
9603
11036
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
@@ -9607,6 +11040,111 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9607
11040
  }
9608
11041
  }
9609
11042
  }
11043
+ async function buildPlanReport(nameOrSource, options) {
11044
+ const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
11045
+ const reportTargets = [];
11046
+ const reportWarnings = [];
11047
+ const collectWarning = (message) => {
11048
+ reportWarnings.push(message);
11049
+ };
11050
+ if (normalizedOptions.profile) {
11051
+ const target = await resolveRuntimeTarget({
11052
+ targetRoot: normalizedOptions.targetRoot,
11053
+ adapter: normalizedOptions.adapter,
11054
+ installationType: normalizedOptions.installationType,
11055
+ agent: normalizedOptions.agent
11056
+ });
11057
+ const results = await syncProfile({
11058
+ workspaceRoot: target.workspaceRoot,
11059
+ profile: normalizedOptions.profile,
11060
+ source: nameOrSource,
11061
+ driver: normalizedOptions.driver,
11062
+ mode: normalizedOptions.mode,
11063
+ select: selectedArtifactsFromOptions(normalizedOptions),
11064
+ installationType: normalizedOptions.installationType,
11065
+ dryRun: true,
11066
+ executePlugins: normalizedOptions.executePlugins,
11067
+ allowAdapterCode: normalizedOptions.allowAdapterCode,
11068
+ forceDrift: normalizedOptions.forceDrift,
11069
+ forceConflict: normalizedOptions.forceConflict,
11070
+ replaceConflict: normalizedOptions.replaceConflict,
11071
+ noDeps: noDepsFromOptions(normalizedOptions),
11072
+ includeSuggestions: normalizedOptions.withSuggestions,
11073
+ suggestionAliases: suggestionAliasesFromOptions(normalizedOptions),
11074
+ lockedResolution: true,
11075
+ frozenLock: normalizedOptions.frozenLock,
11076
+ offline: normalizedOptions.offline,
11077
+ yes: normalizedOptions.yes,
11078
+ trustPatterns: normalizedOptions.trust ?? [],
11079
+ readOnly: true,
11080
+ isTTY: false
11081
+ });
11082
+ for (const result of results) {
11083
+ reportTargets.push(installPlanReportTarget(result.plan, result.graphLockDigest));
11084
+ reportWarnings.push(...result.warnings);
11085
+ }
11086
+ return planReport(reportTargets, reportWarnings);
11087
+ }
11088
+ const targets = await resolveCliTargets(normalizedOptions);
11089
+ for (const target of targets) {
11090
+ const targetOptions = optionsForResolvedTarget(normalizedOptions, target);
11091
+ const config = await readMergedWorkspaceConfig(target.workspaceRoot);
11092
+ const configured = nameOrSource ? findConfiguredPackageForTarget(config.packages, nameOrSource, targetOptions, target) : void 0;
11093
+ let source;
11094
+ let scope = configured?.name;
11095
+ let extraPackage;
11096
+ if (nameOrSource && !configured) {
11097
+ try {
11098
+ let entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, {
11099
+ ...targetOptions,
11100
+ adapter: targetOptions.adapter ?? target.adapter,
11101
+ warn: collectWarning
11102
+ });
11103
+ if (targetOptions.multiAdapterSource) {
11104
+ entry = packageEntryWithAdapterSuffix(entry);
11105
+ }
11106
+ scope = entry.name;
11107
+ source = nameOrSource;
11108
+ extraPackage = entry;
11109
+ } catch (error) {
11110
+ throw teachingInstallError(nameOrSource, error);
11111
+ }
11112
+ }
11113
+ const results = await buildGraphPlansForTarget(
11114
+ target,
11115
+ source,
11116
+ {
11117
+ ...targetOptions,
11118
+ scope,
11119
+ extraPackage,
11120
+ dryRun: true,
11121
+ reportFormat: "json",
11122
+ suppressEmptyMessage: true,
11123
+ warn: collectWarning
11124
+ },
11125
+ { mode: "install" }
11126
+ );
11127
+ for (const result of results) {
11128
+ reportTargets.push(installPlanReportTarget(result.plan, result.graphLockDigest));
11129
+ reportWarnings.push(...result.warnings);
11130
+ await rm11(result.bundle.root, { recursive: true, force: true });
11131
+ }
11132
+ }
11133
+ return planReport(reportTargets, reportWarnings);
11134
+ }
11135
+ function effectivePlanOutputFormat(options) {
11136
+ const format = options.format ?? "human";
11137
+ if (!isPlanOutputFormat(format)) {
11138
+ throw new Error(`Unknown --format value: ${format}. Valid formats: ${planOutputFormats.join(", ")}.`);
11139
+ }
11140
+ if (options.json && format !== "human" && format !== "json") {
11141
+ throw new Error(`--json conflicts with --format ${format}. Use --format json instead.`);
11142
+ }
11143
+ return format !== "human" ? format : options.json ? "json" : "human";
11144
+ }
11145
+ function isPlanOutputFormat(value) {
11146
+ return planOutputFormats.includes(value);
11147
+ }
9610
11148
  async function packageEntryFromSource(source, targetRoot, options) {
9611
11149
  const lockMode = options.frozenLock === true || options.offline === true;
9612
11150
  const resolvedInput = await resolvePackageSource(source, targetRoot, { offline: lockMode });
@@ -9620,7 +11158,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
9620
11158
  adapterModule: options.adapterModule,
9621
11159
  allowAdapterCode: options.allowAdapterCode,
9622
11160
  baseDir: targetRoot,
9623
- warn: (message) => console.warn(message)
11161
+ warn: options.warn ?? ((message) => console.warn(message))
9624
11162
  });
9625
11163
  const bundle = await stageSource(driver, resolvedSource, {
9626
11164
  workspaceRoot: targetRoot,
@@ -9662,6 +11200,9 @@ function findConfiguredPackageForTarget(packages, value, options, target) {
9662
11200
  function noDepsFromOptions(options) {
9663
11201
  return options.noDeps === true || options.deps === false;
9664
11202
  }
11203
+ function shouldReloadRuntimes(options) {
11204
+ return options.reloadRuntimes === true || options.restartRuntimes === true;
11205
+ }
9665
11206
  function packageEntryWithAdapterSuffix(entry) {
9666
11207
  const suffix = `-${entry.adapter}`;
9667
11208
  return entry.name.endsWith(suffix) ? entry : { ...entry, name: `${entry.name}${suffix}` };
@@ -9763,7 +11304,7 @@ async function resolveAdapterForTarget(target, options) {
9763
11304
  adapterModule: adapterOptions.adapterModule,
9764
11305
  allowAdapterCode: adapterOptions.allowAdapterCode,
9765
11306
  baseDir: target.workspaceRoot,
9766
- warn: (message) => console.warn(message)
11307
+ warn: options.warn ?? ((message) => console.warn(message))
9767
11308
  });
9768
11309
  }
9769
11310
  function adapterOptionsForTarget(target, options) {
@@ -9782,13 +11323,20 @@ async function runConfiguredGraphPackages(target, options, behavior) {
9782
11323
  console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
9783
11324
  console.log(formatGraphPlan(result));
9784
11325
  if (!options.dryRun) {
11326
+ const transport = transportForTarget(target);
11327
+ const executePlugins = target.executePlugins ?? options.executePlugins;
9785
11328
  await applyCombinedInstallPlan(result.plan, {
9786
- executePlugins: options.executePlugins,
9787
- transport: transportForTarget(target),
11329
+ executePlugins,
11330
+ transport,
9788
11331
  graphLockDigest: result.graphLockDigest,
9789
11332
  graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
9790
11333
  });
11334
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
11335
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
11336
+ executePlugins
11337
+ });
9791
11338
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
11339
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
9792
11340
  }
9793
11341
  await rm11(result.bundle.root, { recursive: true, force: true });
9794
11342
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
@@ -9799,6 +11347,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9799
11347
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
9800
11348
  const groups = /* @__PURE__ */ new Map();
9801
11349
  const selectedArtifacts = selectedArtifactsFromOptions(targetOptions);
11350
+ const dependencyUpdateSelectors = sortedUniqueValues(targetOptions.dependency ?? []);
11351
+ const scopedDependencyUpdate = behavior.mode === "update" && dependencyUpdateSelectors.length > 0;
9802
11352
  const scopedPackage = targetOptions.scope ? findConfiguredPackage(config.packages, targetOptions.scope) : void 0;
9803
11353
  const scopedRootId = scopedPackage?.name ?? (source ? targetOptions.scope : void 0);
9804
11354
  if (targetOptions.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${targetOptions.scope}`);
@@ -9832,39 +11382,65 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9832
11382
  groups.set(key, group);
9833
11383
  }
9834
11384
  if (groups.size === 0) {
9835
- console.log(`No packages configured at ${target.workspaceRoot}.`);
11385
+ const message = `No packages configured at ${target.workspaceRoot}.`;
11386
+ if (!targetOptions.suppressEmptyMessage) {
11387
+ if (targetOptions.reportFormat && targetOptions.reportFormat !== "human") console.warn(message);
11388
+ else console.log(message);
11389
+ }
9836
11390
  return [];
9837
11391
  }
9838
11392
  const results = [];
9839
11393
  for (const group of groups.values()) {
9840
- const adapter = await resolveAdapterForTarget(group.target, group.adapterOptions);
11394
+ const adapter = await resolveAdapterForTarget(group.target, { ...group.adapterOptions, warn: targetOptions.warn });
9841
11395
  const transport = transportForTarget(group.target);
9842
11396
  const allPackages = [...group.packages, ...group.extraPackages];
9843
11397
  const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
9844
11398
  if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
11399
+ const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
11400
+ if (scopedDependencyUpdate) {
11401
+ const graphLockPath = graphLockPathForTarget(
11402
+ group.target.workspaceRoot,
11403
+ targetKeyForTarget(group.target, adapter.name),
11404
+ adapter.name,
11405
+ targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType)
11406
+ );
11407
+ const previousLock = await (await pathExists(graphLockPath) ? readGraphLock(graphLockPath) : void 0);
11408
+ for (const pkg of group.packages) {
11409
+ const root = previousLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
11410
+ if (root?.mode !== "tracking") continue;
11411
+ const node = previousLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId);
11412
+ if (node && dependencyUpdateSelectors.some((selector) => dependencyUpdateSelectorMatchesRoot(root, node, selector))) {
11413
+ dependencyUpdateRootNames.add(pkg.name);
11414
+ }
11415
+ }
11416
+ }
9845
11417
  const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
9846
11418
  const roots = [
9847
11419
  ...allPackages.map((pkg) => {
9848
- const updateThisPackage = behavior.mode === "update" && pkg.mode === "tracking" && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
11420
+ const updateThisPackage = behavior.mode === "update" && !scopedDependencyUpdate && pkg.mode === "tracking" && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
9849
11421
  const packageIsScoped = scopedRootId ? pkg.name === scopedRootId || pkg.source === targetOptions.scope : true;
11422
+ if (pkg.selection && selectedArtifacts && packageIsScoped) {
11423
+ throw new Error(`--select/--skill cannot override imported selection for configured package '${pkg.name}'.`);
11424
+ }
9850
11425
  return {
9851
11426
  rootId: pkg.name,
9852
11427
  source: pkg.source,
9853
11428
  mode: pkg.mode,
9854
11429
  ref: pkg.requestedRef,
9855
- select: selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
11430
+ select: pkg.selection ? void 0 : selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
11431
+ selection: pkg.selection,
9856
11432
  aliases: pkg.aliases,
9857
11433
  overrides: pkg.overrides,
9858
11434
  includeSuggestions: targetOptions.withSuggestions === true || pkg.withSuggestions === true,
9859
11435
  suggestionAliases: packageSuggestionAliases(pkg, targetOptions),
9860
- useLock: behavior.mode === "install" ? true : !updateThisPackage
11436
+ useLock: behavior.mode === "install" ? true : scopedDependencyUpdate ? !dependencyUpdateRootNames.has(pkg.name) : !updateThisPackage
9861
11437
  };
9862
11438
  }),
9863
11439
  ...group.extraRoots
9864
11440
  ];
9865
11441
  if (behavior.mode === "update") {
9866
11442
  const changed = roots.filter((root) => root.useLock === false);
9867
- if (changed.length === 0) {
11443
+ if (changed.length === 0 && !scopedDependencyUpdate) {
9868
11444
  const label = targetOptions.scope ? ` ${targetOptions.scope}` : "";
9869
11445
  console.log(`No tracking packages to update${label}.`);
9870
11446
  continue;
@@ -9883,7 +11459,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9883
11459
  noDeps: noDepsFromOptions(targetOptions),
9884
11460
  includeSuggestions: targetOptions.withSuggestions,
9885
11461
  suggestionAliases: suggestionAliasesFromOptions(targetOptions),
9886
- lockedResolution: behavior.mode === "install",
11462
+ lockedResolution: behavior.mode === "install" || scopedDependencyUpdate,
11463
+ dependencyUpdateSelectors,
9887
11464
  frozenLock: targetOptions.frozenLock,
9888
11465
  offline: targetOptions.offline,
9889
11466
  yes: targetOptions.yes,
@@ -9894,16 +11471,179 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9894
11471
  forceConflict: targetOptions.forceConflict,
9895
11472
  replaceConflict: targetOptions.replaceConflict
9896
11473
  });
9897
- if (behavior.mode === "install" && scopedRootId) {
11474
+ if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
9898
11475
  const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
9899
11476
  const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
9900
11477
  results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
11478
+ } else if (scopedDependencyUpdate) {
11479
+ const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
11480
+ const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
11481
+ const previousLock = await readGraphLock(result.graphLockPath);
11482
+ results.push(scopeUpdatePlanToDependencies(result, dependencyUpdateSelectors, previousLock, manifest));
9901
11483
  } else {
9902
11484
  results.push(result);
9903
11485
  }
9904
11486
  }
9905
11487
  return results;
9906
11488
  }
11489
+ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest) {
11490
+ const selectedNames = dependencyUpdatePackageNames(previousLock, selectors);
11491
+ const allowedNodeIds = new Set(result.graph.nodes.filter((node) => selectedNames.has(node.name)).map((node) => node.id));
11492
+ const queue = [...allowedNodeIds];
11493
+ while (queue.length > 0) {
11494
+ const parentId = queue.shift();
11495
+ for (const edge of result.graph.edges) {
11496
+ if (edge.from !== parentId || allowedNodeIds.has(edge.to)) continue;
11497
+ allowedNodeIds.add(edge.to);
11498
+ queue.push(edge.to);
11499
+ }
11500
+ }
11501
+ const selectedPreviousNodeIds = dependencyUpdateNodeIds(previousLock, selectors);
11502
+ const allowedNames = new Set(result.graph.nodes.filter((node) => allowedNodeIds.has(node.id)).map((node) => node.name));
11503
+ for (const name of selectedNames) allowedNames.add(name);
11504
+ const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
11505
+ const operations = result.plan.operations.flatMap((operation) => {
11506
+ if (operation.action === "skip" || operation.action === "keep" || operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNames)) {
11507
+ return [operation];
11508
+ }
11509
+ return transformOutOfScopeOperation(
11510
+ operation,
11511
+ manifestByPath.get(operation.relativeDestPath),
11512
+ result.plan.targetRoot,
11513
+ `scoped dependency update ${selectors.join(", ")}`
11514
+ );
11515
+ });
11516
+ const selectedRootIds = dependencyUpdateRootIds(previousLock, selectors);
11517
+ const graphLock = preserveUnrelatedGraphPackages(
11518
+ result.bundle.graphLock,
11519
+ previousLock,
11520
+ allowedNodeIds,
11521
+ selectedPreviousNodeIds,
11522
+ selectedRootIds
11523
+ );
11524
+ const graphLockDigest = createHash11("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
11525
+ return {
11526
+ ...result,
11527
+ bundle: { ...result.bundle, graphLock },
11528
+ graphLockDigest,
11529
+ graphDiff: diffGraphLocks(previousLock, graphLock),
11530
+ plan: {
11531
+ ...result.plan,
11532
+ operations,
11533
+ graphLockDigest,
11534
+ hasBlockingChanges: operations.some((operation) => operation.action === "drift" || operation.action === "conflict")
11535
+ }
11536
+ };
11537
+ }
11538
+ function preserveUnrelatedGraphPackages(current, previous, selectedCurrentNodeIds, selectedPreviousNodeIds, selectedRootIds) {
11539
+ const selectedCurrentRoots = current.canonical.roots.filter((root) => selectedRootIds.has(root.rootId));
11540
+ const currentRootsById = new Map(selectedCurrentRoots.map((root) => [root.rootId, root]));
11541
+ const roots = previous.canonical.roots.map((root) => currentRootsById.get(root.rootId) ?? root);
11542
+ for (const root of selectedCurrentRoots) {
11543
+ if (!previous.canonical.roots.some((candidate) => candidate.rootId === root.rootId)) roots.push(root);
11544
+ }
11545
+ const currentNodes = current.canonical.nodes.filter((node) => selectedCurrentNodeIds.has(node.id));
11546
+ const previousNodes = previous.canonical.nodes.filter((node) => !selectedPreviousNodeIds.has(node.id));
11547
+ const selectedCurrentArtifacts = current.canonical.artifacts.filter((artifact) => selectedCurrentNodeIds.has(artifact.graphNodeId));
11548
+ const previousArtifacts = previous.canonical.artifacts.filter((artifact) => !selectedPreviousNodeIds.has(artifact.graphNodeId));
11549
+ const edgeTouchesSelected = (from, to) => selectedCurrentNodeIds.has(from) || selectedCurrentNodeIds.has(to);
11550
+ const previousEdge = (edge) => !selectedPreviousNodeIds.has(edge.from) && !selectedPreviousNodeIds.has(edge.to);
11551
+ const previousIncludeEdge = (edge) => !selectedPreviousNodeIds.has(edge.fromNodeId) && !selectedPreviousNodeIds.has(edge.toNodeId);
11552
+ const selectedCurrentRootIds = new Set(selectedCurrentRoots.map((root) => root.graphNodeId));
11553
+ const currentRootOrNode = (id) => selectedCurrentNodeIds.has(id) || selectedCurrentRootIds.has(id);
11554
+ const previousRootOrNode = (id) => selectedPreviousNodeIds.has(id) || previous.canonical.roots.some((root) => selectedRootIds.has(root.rootId) && root.graphNodeId === id);
11555
+ return canonicalizeGraphLock({
11556
+ ...current,
11557
+ canonical: {
11558
+ ...current.canonical,
11559
+ roots,
11560
+ nodes: [...currentNodes, ...previousNodes],
11561
+ edges: [
11562
+ ...current.canonical.edges.filter((edge) => edgeTouchesSelected(edge.from, edge.to)),
11563
+ ...previous.canonical.edges.filter(previousEdge)
11564
+ ],
11565
+ includeEdges: [
11566
+ ...current.canonical.includeEdges.filter((edge) => edgeTouchesSelected(edge.fromNodeId, edge.toNodeId)),
11567
+ ...previous.canonical.includeEdges.filter(previousIncludeEdge)
11568
+ ],
11569
+ artifacts: [...selectedCurrentArtifacts, ...previousArtifacts],
11570
+ namespacing: [
11571
+ ...current.canonical.namespacing.filter((entry) => currentRootOrNode(entry.graphNodeId)),
11572
+ ...previous.canonical.namespacing.filter((entry) => !previousRootOrNode(entry.graphNodeId))
11573
+ ],
11574
+ overrides: [
11575
+ ...current.canonical.overrides.filter((entry) => currentRootOrNode(entry.graphNodeId) || currentRootOrNode(entry.overriddenGraphNodeId)),
11576
+ ...previous.canonical.overrides.filter((entry) => !previousRootOrNode(entry.graphNodeId) && !previousRootOrNode(entry.overriddenGraphNodeId))
11577
+ ],
11578
+ plainNameIncumbents: [
11579
+ ...current.canonical.plainNameIncumbents.filter((entry) => currentRootOrNode(entry.graphNodeId)),
11580
+ ...previous.canonical.plainNameIncumbents.filter((entry) => !previousRootOrNode(entry.graphNodeId))
11581
+ ]
11582
+ }
11583
+ });
11584
+ }
11585
+ function dependencyUpdateRootIds(lock, selectors) {
11586
+ const nodes = new Map(lock.canonical.nodes.map((node) => [node.id, node]));
11587
+ return new Set(lock.canonical.roots.filter((root) => {
11588
+ const node = nodes.get(root.graphNodeId);
11589
+ return root.mode === "tracking" && node && selectors.some((selector) => dependencyUpdateSelectorMatchesRoot(root, node, selector));
11590
+ }).map((root) => root.rootId));
11591
+ }
11592
+ function dependencyUpdateNodeIds(lock, selectors) {
11593
+ const nodes = new Map(lock.canonical.nodes.map((node) => [node.id, node]));
11594
+ const selected = /* @__PURE__ */ new Set();
11595
+ for (const root of lock.canonical.roots) {
11596
+ const node = nodes.get(root.graphNodeId);
11597
+ if (root.mode === "tracking" && node && selectors.some((selector) => dependencyUpdateSelectorMatchesRoot(root, node, selector))) {
11598
+ selected.add(root.graphNodeId);
11599
+ }
11600
+ }
11601
+ for (const selector of selectors) {
11602
+ for (const edge of lock.canonical.edges) {
11603
+ const node = nodes.get(edge.to);
11604
+ if (!node || node.mode !== "tracking") continue;
11605
+ if (selector === edge.alias || selector === edge.source || selector === edge.normalizedSource || selector === node.id || selector === node.name || selector === node.source || selector === node.normalizedSource) {
11606
+ selected.add(edge.to);
11607
+ }
11608
+ }
11609
+ }
11610
+ const queue = [...selected];
11611
+ while (queue.length > 0) {
11612
+ const parentId = queue.shift();
11613
+ for (const edge of lock.canonical.edges) {
11614
+ if (edge.from !== parentId || selected.has(edge.to)) continue;
11615
+ selected.add(edge.to);
11616
+ queue.push(edge.to);
11617
+ }
11618
+ }
11619
+ return selected;
11620
+ }
11621
+ function dependencyUpdatePackageNames(lock, selectors) {
11622
+ const nodes = new Map(lock.canonical.nodes.map((node) => [node.id, node]));
11623
+ const names = /* @__PURE__ */ new Set();
11624
+ for (const selector of selectors) {
11625
+ for (const root of lock.canonical.roots) {
11626
+ const node = nodes.get(root.graphNodeId);
11627
+ if (root.mode !== "tracking" || !node) continue;
11628
+ if (dependencyUpdateSelectorMatchesRoot(root, node, selector)) {
11629
+ names.add(node.name);
11630
+ }
11631
+ }
11632
+ for (const edge of lock.canonical.edges) {
11633
+ const node = nodes.get(edge.to);
11634
+ if (!node || node.mode !== "tracking") continue;
11635
+ if (selector === edge.alias || selector === edge.source || selector === edge.normalizedSource || selector === node.id || selector === node.name || selector === node.source || selector === node.normalizedSource) {
11636
+ names.add(node.name);
11637
+ }
11638
+ }
11639
+ }
11640
+ return names;
11641
+ }
11642
+ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNames) {
11643
+ if (operation.graphNodeId && allowedNodeIds.has(operation.graphNodeId)) return true;
11644
+ if (operation.owners?.some((owner) => allowedNodeIds.has(owner))) return true;
11645
+ return operation.composedFrom?.some((entry) => [...allowedNames].some((name) => entry.selector.startsWith(`${name}@`) || entry.selector.startsWith(`${name}:`))) === true;
11646
+ }
9907
11647
  function scopeInstallPlanToRoot(result, rootId, manifest) {
9908
11648
  const scopedOwners = scopedGraphOwnerKeys(result, rootId);
9909
11649
  const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
@@ -9917,7 +11657,7 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
9917
11657
  continue;
9918
11658
  }
9919
11659
  const entry = manifestByPath.get(operation.relativeDestPath);
9920
- const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, rootId);
11660
+ const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, `scoped install ${rootId}`);
9921
11661
  for (const scopedOperation of transformed) {
9922
11662
  if (preservedPaths.has(scopedOperation.relativeDestPath)) continue;
9923
11663
  preservedPaths.add(scopedOperation.relativeDestPath);
@@ -9927,7 +11667,7 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
9927
11667
  for (const entry of manifest?.entries ?? []) {
9928
11668
  if (plannedPaths.has(entry.path) || preservedPaths.has(entry.path) || entryBelongsToScopedRoot(entry, scopedOwners)) continue;
9929
11669
  preservedPaths.add(entry.path);
9930
- operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, rootId));
11670
+ operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, `scoped install ${rootId}`));
9931
11671
  }
9932
11672
  return {
9933
11673
  ...result,
@@ -9938,16 +11678,16 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
9938
11678
  }
9939
11679
  };
9940
11680
  }
9941
- function transformOutOfScopeOperation(operation, entry, targetRoot, rootId) {
11681
+ function transformOutOfScopeOperation(operation, entry, targetRoot, scopeDescription) {
9942
11682
  if (operation.action === "skip") return [operation];
9943
11683
  if (operation.action === "update" || operation.action === "drift") {
9944
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId, operation, { freshMetadata: true })] : [];
11684
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription, operation, { freshMetadata: true })] : [];
9945
11685
  }
9946
11686
  if (operation.action === "remove") {
9947
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
11687
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription)] : [];
9948
11688
  }
9949
11689
  if (operation.action === "plugin" || operation.action === "program") {
9950
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
11690
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription)] : [];
9951
11691
  }
9952
11692
  return [];
9953
11693
  }
@@ -9975,7 +11715,7 @@ function entryBelongsToScopedRoot(entry, scopedOwners) {
9975
11715
  const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
9976
11716
  return owners.some((owner) => scopedOwners.has(owner));
9977
11717
  }
9978
- function keepManifestEntryOperation(entry, targetRoot, rootId, operation, options = {}) {
11718
+ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operation, options = {}) {
9979
11719
  const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
9980
11720
  const fresh = options.freshMetadata ? operation : void 0;
9981
11721
  return {
@@ -9988,7 +11728,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
9988
11728
  desiredHash: entry.sourceHash,
9989
11729
  currentHash: operation?.currentHash ?? entry.hash,
9990
11730
  manifestHash: entry.hash,
9991
- reason: `preserved outside scoped install ${rootId}`,
11731
+ reason: `preserved outside ${scopeDescription}`,
9992
11732
  channel: entry.channel,
9993
11733
  packageName: entry.packageName,
9994
11734
  semanticCommand: entry.semanticCommand,
@@ -10046,7 +11786,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
10046
11786
  source: pkg2.source,
10047
11787
  mode: pkg2.mode,
10048
11788
  ref: pkg2.requestedRef,
10049
- select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
11789
+ select: pkg2.selection ? void 0 : normalizeArtifactSelectors(pkg2.select, pkg2.skills),
11790
+ selection: pkg2.selection,
10050
11791
  aliases: pkg2.aliases,
10051
11792
  overrides: pkg2.overrides,
10052
11793
  includeSuggestions: options.withSuggestions === true || pkg2.withSuggestions === true,
@@ -10206,6 +11947,14 @@ async function printStatus(target, options) {
10206
11947
  }
10207
11948
  await printPendingInstallWork(target, options);
10208
11949
  }
11950
+ async function journalStateForTarget(target, options) {
11951
+ const adapterOptions = adapterOptionsForTarget(target, options);
11952
+ const adapter = await resolveAdapterForTarget(target, adapterOptions);
11953
+ const transport = transportForTarget(target);
11954
+ const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
11955
+ const state = installStateForTarget(target, adapter, adapterOptions, installationType);
11956
+ return { adapter, transport, installationType, installRoot: state.installRoot, state };
11957
+ }
10209
11958
  async function printPendingInstallWork(target, options) {
10210
11959
  let results = [];
10211
11960
  try {
@@ -10369,6 +12118,12 @@ function collectSuggestionOption(value, previous) {
10369
12118
  function collectTrustOption(value, previous) {
10370
12119
  return [...previous, value];
10371
12120
  }
12121
+ function collectDependencyOption(value, previous) {
12122
+ return [...previous, ...splitSelectorList(value)];
12123
+ }
12124
+ function sortedUniqueValues(values) {
12125
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
12126
+ }
10372
12127
  function collectOverrideOption(value, previous) {
10373
12128
  return [...previous, ...splitSelectorList(value)];
10374
12129
  }
@@ -10470,9 +12225,9 @@ function filterUninstallPlanBySelection(plan, selected) {
10470
12225
  };
10471
12226
  }
10472
12227
  async function initPackage(root) {
10473
- await mkdir21(join41(root, "instructions"), { recursive: true });
10474
- await mkdir21(join41(root, "rules"), { recursive: true });
10475
- await mkdir21(join41(root, "skills"), { recursive: true });
12228
+ await mkdir22(join41(root, "instructions"), { recursive: true });
12229
+ await mkdir22(join41(root, "rules"), { recursive: true });
12230
+ await mkdir22(join41(root, "skills"), { recursive: true });
10476
12231
  const manifestPath = join41(root, "openpack.json");
10477
12232
  const manifest = {
10478
12233
  schemaVersion: 2,
@@ -10484,12 +12239,12 @@ async function initPackage(root) {
10484
12239
  { type: "skills", path: "skills" }
10485
12240
  ]
10486
12241
  };
10487
- await writeFile20(manifestPath, `${JSON.stringify(manifest, null, 2)}
12242
+ await writeFile21(manifestPath, `${JSON.stringify(manifest, null, 2)}
10488
12243
  `, "utf8");
10489
- await writeFile20(join41(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
12244
+ await writeFile21(join41(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
10490
12245
  }
10491
12246
  async function defaultBootstrapPackage(_root) {
10492
- const packageRoot = await findAgentwheelPackageRoot(dirname30(fileURLToPath3(import.meta.url)));
12247
+ const packageRoot = await findAgentwheelPackageRoot(dirname31(fileURLToPath3(import.meta.url)));
10493
12248
  if (!packageRoot) return void 0;
10494
12249
  return {
10495
12250
  name: "agentwheel",
@@ -10536,7 +12291,7 @@ async function findAgentwheelPackageRoot(start) {
10536
12291
  let current = resolve20(start);
10537
12292
  while (true) {
10538
12293
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
10539
- const parent = dirname30(current);
12294
+ const parent = dirname31(current);
10540
12295
  if (parent === current) return void 0;
10541
12296
  current = parent;
10542
12297
  }