agentwheel 0.14.9 → 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
  });
@@ -1502,6 +1532,123 @@ function dedupeArray2(values) {
1502
1532
  return out;
1503
1533
  }
1504
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
+
1505
1652
  // src/install/desired.ts
1506
1653
  function normalizeOwners(owners) {
1507
1654
  const normalized = [...new Set(owners.map((owner) => owner.trim()).filter(Boolean))].sort();
@@ -1513,13 +1660,13 @@ function normalizeOwners(owners) {
1513
1660
 
1514
1661
  // src/install/instructions-block.ts
1515
1662
  import { createHash as createHash3 } from "crypto";
1516
- 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";
1517
1664
  import { tmpdir as tmpdir2 } from "os";
1518
- 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";
1519
1666
  var managedInstructionBlockMode = "managed-block";
1520
1667
  var managedInstructionBanner = "<!-- agentwheel-managed: edit fragments, not this block -->";
1521
1668
  async function desiredManagedInstructionBlockHash(sourcePath) {
1522
- const source = await readFile9(sourcePath, "utf8");
1669
+ const source = await readFile10(sourcePath, "utf8");
1523
1670
  return hashText(managedBlockBody(source));
1524
1671
  }
1525
1672
  async function readManagedInstructionBlockState(destPath, selector, transport) {
@@ -1539,7 +1686,7 @@ async function readManagedInstructionBlockState(destPath, selector, transport) {
1539
1686
  };
1540
1687
  }
1541
1688
  async function writeManagedInstructionBlock(sourcePath, destPath, selector, transport, options = {}) {
1542
- const source = await readFile9(sourcePath, "utf8");
1689
+ const source = await readFile10(sourcePath, "utf8");
1543
1690
  const desired = renderManagedInstructionBlock(selector, source);
1544
1691
  const existing = await readOptionalText(destPath, transport);
1545
1692
  const merged = upsertManagedInstructionBlock(existing ?? "", selector, desired.block, options);
@@ -1651,7 +1798,7 @@ function assertCleanBlock(block, selector) {
1651
1798
  }
1652
1799
  }
1653
1800
  function referencesAgentsMd(content, claudePath, agentsPath) {
1654
- const claudeDir = dirname9(claudePath);
1801
+ const claudeDir = dirname10(claudePath);
1655
1802
  for (const rawLine of content.split(/\r?\n/)) {
1656
1803
  const line = rawLine.trim();
1657
1804
  const atImport = /^@import\s+(.+)$/i.exec(line);
@@ -1661,7 +1808,7 @@ function referencesAgentsMd(content, claudePath, agentsPath) {
1661
1808
  const cleaned = referenced.trim().replace(/^["']|["']$/g, "");
1662
1809
  if (!/AGENTS\.md$/i.test(cleaned)) continue;
1663
1810
  const candidate = cleaned.startsWith("/") ? cleaned : join4(claudeDir, cleaned);
1664
- if (relative2(dirname9(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1811
+ if (relative2(dirname10(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
1665
1812
  if (candidate === agentsPath) return true;
1666
1813
  }
1667
1814
  return false;
@@ -1683,7 +1830,7 @@ async function writeTextWithTransport(path, content, transport) {
1683
1830
  const tempRoot = await mkdtemp(join4(tmpdir2(), "agentwheel-instructions-"));
1684
1831
  const localPath = join4(tempRoot, basename2(path) || "instructions.md");
1685
1832
  try {
1686
- await writeFile8(localPath, content, "utf8");
1833
+ await writeFile9(localPath, content, "utf8");
1687
1834
  await transport.atomicCopy(localPath, path, "file");
1688
1835
  } finally {
1689
1836
  await rm3(tempRoot, { recursive: true, force: true });
@@ -1833,7 +1980,7 @@ async function uninstall(plan, options = {}) {
1833
1980
  const removedDrifted = resolvedOptions.force ? plan.operations.filter((operation) => operation.action === "keep" && isForceRemovableKeep(operation)).length : 0;
1834
1981
  if (resolvedOptions.dryRun) return { removed: resolvedOptions.keepFiles ? 0 : removable.length, kept: kept.length, removedDrifted };
1835
1982
  const preservedKept = resolvedOptions.keepFiles ? kept.filter((operation) => shouldPreserveKeptOperationWhenKeepingFiles(operation)) : kept;
1836
- const preserved = [...preservedKept, ...skipped];
1983
+ const preserved = [...preservedKept, ...skipped].filter((operation) => operation.preserveInManifest !== false);
1837
1984
  for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
1838
1985
  const now = (/* @__PURE__ */ new Date()).toISOString();
1839
1986
  const finalManifest = withManifestRevision({
@@ -2037,6 +2184,12 @@ async function applyOperation(operation, context) {
2037
2184
  if (operation.mode === managedInstructionBlockMode) {
2038
2185
  const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
2039
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
+ }
2040
2193
  } else {
2041
2194
  await transport.rm(operation.destPath);
2042
2195
  }
@@ -2302,6 +2455,8 @@ function manifestEntryForOperation(operation, values) {
2302
2455
  semanticPlugin: operation.semanticPlugin,
2303
2456
  executed: values.executed ?? operation.execute,
2304
2457
  mergeStrategy: operation.mergeStrategy,
2458
+ mergeRemoval: operation.mergeRemoval,
2459
+ mergeCreatedDestination: operation.mergeCreatedDestination,
2305
2460
  mode: operation.mode,
2306
2461
  composedFrom: operation.composedFrom,
2307
2462
  graphLockDigest: operation.graphLockDigest ?? values.graphLockDigest
@@ -2362,7 +2517,7 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2362
2517
  const localDest = join5(tempRoot, basename3(destPath) || "merged");
2363
2518
  try {
2364
2519
  if (await transport.pathExists(destPath)) {
2365
- await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2520
+ await writeFile10(localDest, await transport.readFile(destPath), "utf8");
2366
2521
  }
2367
2522
  await merge(sourcePath, localDest);
2368
2523
  await transport.atomicCopy(localDest, destPath, "file");
@@ -2370,13 +2525,28 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
2370
2525
  await rm4(tempRoot, { recursive: true, force: true });
2371
2526
  }
2372
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
+ }
2373
2543
  async function mergeOpenClawJsonWithTransport(sourcePath, destPath, transport) {
2374
2544
  const tempRoot = await mkdtemp2(join5(tmpdir3(), "agentwheel-openclaw-merge-"));
2375
2545
  const localDest = join5(tempRoot, basename3(destPath) || "openclaw.json");
2376
2546
  const validationPath = transport.kind === "local" ? localDest : `${destPath}.validate-agentwheel-${process.pid}-${Date.now()}`;
2377
2547
  try {
2378
2548
  if (await transport.pathExists(destPath)) {
2379
- await writeFile9(localDest, await transport.readFile(destPath), "utf8");
2549
+ await writeFile10(localDest, await transport.readFile(destPath), "utf8");
2380
2550
  }
2381
2551
  await mergeOpenClawJsonFile(sourcePath, localDest);
2382
2552
  if (transport.kind !== "local") {
@@ -2393,7 +2563,7 @@ async function validateOpenClawConfig(configPath, destPath, transport) {
2393
2563
  if (!transport.execFile) {
2394
2564
  throw new Error(`Cannot validate OpenClaw config over ${transport.description}: transport does not support command execution.`);
2395
2565
  }
2396
- const openClawHome = dirname10(destPath);
2566
+ const openClawHome = dirname11(destPath);
2397
2567
  const bundledBin = join5(openClawHome, "npm", "node_modules", ".bin", "openclaw");
2398
2568
  const script = String.raw`
2399
2569
  set -euo pipefail
@@ -2420,8 +2590,8 @@ printf '%s' "$out" | node -e 'let s=""; process.stdin.on("data", c => s += c); p
2420
2590
  import { basename as basename8, join as join16, relative as relative3 } from "path";
2421
2591
 
2422
2592
  // src/staging/codex-subagents.ts
2423
- import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
2424
- 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";
2425
2595
  var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
2426
2596
  async function renderCodexSubagents(artifacts, stageRoot, adapter) {
2427
2597
  if (adapter?.name !== "codex") return artifacts;
@@ -2447,7 +2617,7 @@ async function renderCodexSubagent(artifact, stageRoot) {
2447
2617
  const renderedPath = join6(stageRoot, ".agentwheel-rendered", "codex-subagents", `${agentName}.toml`);
2448
2618
  const lowerSourcePath = sourcePath.toLowerCase();
2449
2619
  if (artifact.kind === "file" && (artifact.name.toLowerCase().endsWith(".toml") || lowerSourcePath.endsWith(".toml"))) {
2450
- const content = await readFile10(sourcePath, "utf8");
2620
+ const content = await readFile11(sourcePath, "utf8");
2451
2621
  validateCodexAgentToml(content, sourcePath);
2452
2622
  return {
2453
2623
  ...artifact,
@@ -2464,10 +2634,10 @@ async function renderCodexSubagent(artifact, stageRoot) {
2464
2634
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !lowerSourcePath.endsWith(".md")) {
2465
2635
  throw new Error(`Codex subagent ${artifact.relativePath} must be a .toml file, .md file, or directory containing AGENTS.md.`);
2466
2636
  }
2467
- const markdown = await readFile10(markdownPath, "utf8");
2637
+ const markdown = await readFile11(markdownPath, "utf8");
2468
2638
  const toml = markdownToCodexAgentToml(agentName, markdown);
2469
- await mkdir8(dirname11(renderedPath), { recursive: true });
2470
- await writeFile10(renderedPath, toml, "utf8");
2639
+ await mkdir9(dirname12(renderedPath), { recursive: true });
2640
+ await writeFile11(renderedPath, toml, "utf8");
2471
2641
  return {
2472
2642
  ...artifact,
2473
2643
  name: agentName,
@@ -2535,8 +2705,8 @@ function escapeRegExp2(value) {
2535
2705
  }
2536
2706
 
2537
2707
  // src/staging/copilot-artifacts.ts
2538
- import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
2539
- 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";
2540
2710
  async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
2541
2711
  if (adapter?.name !== "copilot") return artifacts;
2542
2712
  const names = /* @__PURE__ */ new Set();
@@ -2566,9 +2736,9 @@ async function renderCopilotSubagent(artifact, stageRoot) {
2566
2736
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md")) {
2567
2737
  throw new Error(`Copilot subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2568
2738
  }
2569
- const markdown = await readFile11(markdownPath, "utf8");
2570
- await mkdir9(dirname12(renderedPath), { recursive: true });
2571
- 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");
2572
2742
  return {
2573
2743
  ...artifact,
2574
2744
  name: `${agentName}.agent.md`,
@@ -2621,8 +2791,8 @@ function yamlString(value) {
2621
2791
  }
2622
2792
 
2623
2793
  // src/staging/openclaw-subagents.ts
2624
- import { mkdir as mkdir10, readFile as readFile12, writeFile as writeFile12 } from "fs/promises";
2625
- 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";
2626
2796
  async function renderOpenClawSubagents(artifacts, stageRoot, adapter) {
2627
2797
  if (adapter?.name !== "openclaw") return artifacts;
2628
2798
  const names = /* @__PURE__ */ new Set();
@@ -2651,15 +2821,15 @@ async function renderOpenClawSubagent(artifact, stageRoot) {
2651
2821
  if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !sourcePath.toLowerCase().endsWith(".md")) {
2652
2822
  throw new Error(`OpenClaw subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
2653
2823
  }
2654
- const parsed = splitFrontmatter3(await readFile12(markdownPath, "utf8"));
2824
+ const parsed = splitFrontmatter3(await readFile13(markdownPath, "utf8"));
2655
2825
  const body = parsed.body.trim().length > 0 ? parsed.body.trim() : `# ${titleFromAgentId(agentId)}
2656
2826
 
2657
2827
  ${parsed.description ?? `OpenClaw subagent ${agentId}.`}`;
2658
2828
  const renderedPath = join8(stageRoot, ".agentwheel-rendered", "openclaw-subagents", agentId, "AGENTS.md");
2659
- await mkdir10(dirname13(renderedPath), { recursive: true });
2660
- await writeFile12(renderedPath, `${body}
2829
+ await mkdir11(dirname14(renderedPath), { recursive: true });
2830
+ await writeFile13(renderedPath, `${body}
2661
2831
  `, "utf8");
2662
- const renderedDir = dirname13(renderedPath);
2832
+ const renderedDir = dirname14(renderedPath);
2663
2833
  return {
2664
2834
  ...artifact,
2665
2835
  name: agentId,
@@ -2693,7 +2863,7 @@ function titleFromAgentId(agentId) {
2693
2863
  import { join as join10 } from "path";
2694
2864
 
2695
2865
  // src/targets/plugins/common.ts
2696
- import { readFile as readFile13 } from "fs/promises";
2866
+ import { readFile as readFile14 } from "fs/promises";
2697
2867
  import { join as join9 } from "path";
2698
2868
  import { parseDocument } from "yaml";
2699
2869
  function pluginStateRoot(request) {
@@ -2717,28 +2887,28 @@ function safeNameSegment(value) {
2717
2887
  async function jsonPluginName(root, relativeManifestPath, fallback) {
2718
2888
  const manifestPath = join9(root, relativeManifestPath);
2719
2889
  if (!await pathExists(manifestPath)) return fallback;
2720
- const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
2890
+ const parsed = JSON.parse(await readFile14(manifestPath, "utf8"));
2721
2891
  return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : fallback;
2722
2892
  }
2723
2893
  async function yamlPluginName(root, relativeManifestPaths, fallback) {
2724
2894
  for (const relativeManifestPath of relativeManifestPaths) {
2725
2895
  const manifestPath = join9(root, relativeManifestPath);
2726
2896
  if (!await pathExists(manifestPath)) continue;
2727
- const document = parseDocument(await readFile13(manifestPath, "utf8"));
2897
+ const document = parseDocument(await readFile14(manifestPath, "utf8"));
2728
2898
  const parsed = document.toJSON();
2729
- if (!isRecord4(parsed)) continue;
2899
+ if (!isRecord5(parsed)) continue;
2730
2900
  for (const key of ["name", "module", "package"]) {
2731
2901
  const value = parsed[key];
2732
2902
  if (typeof value === "string" && value.trim().length > 0) return value.trim();
2733
2903
  }
2734
2904
  const packageValue = parsed.package;
2735
- 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) {
2736
2906
  return packageValue.name.trim();
2737
2907
  }
2738
2908
  }
2739
2909
  return fallback;
2740
2910
  }
2741
- function isRecord4(value) {
2911
+ function isRecord5(value) {
2742
2912
  return typeof value === "object" && value !== null && !Array.isArray(value);
2743
2913
  }
2744
2914
 
@@ -2856,7 +3026,7 @@ async function hermesPluginSpec(request) {
2856
3026
  }
2857
3027
 
2858
3028
  // src/targets/plugins/openclaw.ts
2859
- import { readFile as readFile14 } from "fs/promises";
3029
+ import { readFile as readFile15 } from "fs/promises";
2860
3030
  import { join as join14 } from "path";
2861
3031
  function openClawPluginInstallCommand(request) {
2862
3032
  return ["openclaw", "plugins", "install", "--force", request.path];
@@ -2886,7 +3056,7 @@ async function openClawPluginName(root, fallback) {
2886
3056
  for (const manifestName of ["plugin.json", "openclaw.plugin.json"]) {
2887
3057
  const manifestPath = join14(root, manifestName);
2888
3058
  if (!await pathExists(manifestPath)) continue;
2889
- const parsed = JSON.parse(await readFile14(manifestPath, "utf8"));
3059
+ const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
2890
3060
  if (typeof parsed.name === "string" && parsed.name.trim().length > 0) return parsed.name.trim();
2891
3061
  }
2892
3062
  return fallback;
@@ -2896,7 +3066,7 @@ async function openClawClawHubPluginMetadata(root, fallback) {
2896
3066
  if (!await pathExists(metadataPath)) {
2897
3067
  throw new Error("OpenClaw ClawHub plugins must contain clawhub.json");
2898
3068
  }
2899
- const parsed = JSON.parse(await readFile14(metadataPath, "utf8"));
3069
+ const parsed = JSON.parse(await readFile15(metadataPath, "utf8"));
2900
3070
  const installSpec = stringField(parsed.installSpec);
2901
3071
  if (!installSpec?.startsWith("clawhub:")) {
2902
3072
  throw new Error("OpenClaw ClawHub plugin metadata must declare installSpec starting with clawhub:");
@@ -3027,7 +3197,7 @@ function skipWarning(selector, adapter, installationType, artifactType, support)
3027
3197
  }
3028
3198
 
3029
3199
  // src/validation/artifacts.ts
3030
- import { readFile as readFile15 } from "fs/promises";
3200
+ import { readFile as readFile16 } from "fs/promises";
3031
3201
  import { basename as basename7, join as join15 } from "path";
3032
3202
  import { parseDocument as parseDocument2 } from "yaml";
3033
3203
  var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
@@ -3232,7 +3402,7 @@ async function validateGenericStructure(artifact, target) {
3232
3402
  async function validateSkillFrontmatter(artifact, skillMdPath) {
3233
3403
  let content;
3234
3404
  try {
3235
- content = await readFile15(skillMdPath, "utf8");
3405
+ content = await readFile16(skillMdPath, "utf8");
3236
3406
  } catch (error) {
3237
3407
  return [{ artifact, message: `could not read SKILL.md: ${errorMessage(error)}` }];
3238
3408
  }
@@ -3302,7 +3472,7 @@ async function validateHermesPlugin(artifact) {
3302
3472
  return [{ artifact, message: "Hermes plugins must contain plugin.yaml or plugin.yml" }];
3303
3473
  }
3304
3474
  try {
3305
- const document = parseDocument2(await readFile15(manifestPath, "utf8"));
3475
+ const document = parseDocument2(await readFile16(manifestPath, "utf8"));
3306
3476
  if (document.errors.length > 0) {
3307
3477
  return [{ artifact, message: `Hermes ${basename7(manifestPath)} must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` }];
3308
3478
  }
@@ -3326,8 +3496,8 @@ async function firstExistingPath(paths) {
3326
3496
  }
3327
3497
  async function parseJsonPluginManifest(manifestPath, label) {
3328
3498
  try {
3329
- const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
3330
- if (!isRecord5(parsed)) {
3499
+ const parsed = JSON.parse(await readFile16(manifestPath, "utf8"));
3500
+ if (!isRecord6(parsed)) {
3331
3501
  return { ok: false, message: `${label} ${basename7(manifestPath)} must be a JSON object` };
3332
3502
  }
3333
3503
  if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
@@ -3373,8 +3543,8 @@ async function validateOpenClawClawHubPlugin(artifact) {
3373
3543
  return [{ artifact, message: "OpenClaw ClawHub plugins must contain clawhub.json" }];
3374
3544
  }
3375
3545
  try {
3376
- const parsed = JSON.parse(await readFile15(metadataPath, "utf8"));
3377
- if (!isRecord5(parsed)) {
3546
+ const parsed = JSON.parse(await readFile16(metadataPath, "utf8"));
3547
+ if (!isRecord6(parsed)) {
3378
3548
  return [{ artifact, message: "OpenClaw ClawHub clawhub.json must be a JSON object" }];
3379
3549
  }
3380
3550
  if (typeof parsed.installSpec !== "string" || !parsed.installSpec.trim().startsWith("clawhub:")) {
@@ -3397,8 +3567,8 @@ async function openClawPluginManifestPaths(artifact) {
3397
3567
  async function parseOpenClawPluginManifest(manifestPath) {
3398
3568
  const manifestName = basename7(manifestPath);
3399
3569
  try {
3400
- const parsed = JSON.parse(await readFile15(manifestPath, "utf8"));
3401
- if (!isRecord5(parsed)) {
3570
+ const parsed = JSON.parse(await readFile16(manifestPath, "utf8"));
3571
+ if (!isRecord6(parsed)) {
3402
3572
  return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be a JSON object` };
3403
3573
  }
3404
3574
  if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
@@ -3414,8 +3584,8 @@ async function parseJsonObjectArtifact(artifact) {
3414
3584
  return { ok: false, message: "merge artifacts must be JSON files" };
3415
3585
  }
3416
3586
  try {
3417
- const parsed = JSON.parse(await readFile15(artifactPath(artifact), "utf8"));
3418
- 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" };
3419
3589
  return { ok: true, value: parsed };
3420
3590
  } catch (error) {
3421
3591
  return { ok: false, message: `merge artifact must be valid JSON: ${errorMessage(error)}` };
@@ -3426,7 +3596,7 @@ async function parseYamlObjectArtifact(artifact) {
3426
3596
  return { ok: false, message: "merge artifacts must be YAML files" };
3427
3597
  }
3428
3598
  try {
3429
- const document = parseDocument2(await readFile15(artifactPath(artifact), "utf8"));
3599
+ const document = parseDocument2(await readFile16(artifactPath(artifact), "utf8"));
3430
3600
  if (document.errors.length > 0) {
3431
3601
  return { ok: false, message: `merge artifact must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` };
3432
3602
  }
@@ -3438,10 +3608,10 @@ async function parseYamlObjectArtifact(artifact) {
3438
3608
  }
3439
3609
  }
3440
3610
  function extractMcpServers2(source) {
3441
- const raw = isRecord5(source.mcpServers) ? source.mcpServers : source;
3611
+ const raw = isRecord6(source.mcpServers) ? source.mcpServers : source;
3442
3612
  const servers = {};
3443
3613
  for (const [name, value] of Object.entries(raw)) {
3444
- if (isRecord5(value)) servers[name] = value;
3614
+ if (isRecord6(value)) servers[name] = value;
3445
3615
  }
3446
3616
  return servers;
3447
3617
  }
@@ -3458,7 +3628,7 @@ function hasExtension(artifact, extension) {
3458
3628
  function isPluginFormat(value) {
3459
3629
  return value !== void 0 && pluginFormats.includes(value);
3460
3630
  }
3461
- function isRecord5(value) {
3631
+ function isRecord6(value) {
3462
3632
  return typeof value === "object" && value !== null && !Array.isArray(value);
3463
3633
  }
3464
3634
  function isUnknownRecord(value) {
@@ -3526,7 +3696,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3526
3696
  if (op.action === "plugin") {
3527
3697
  const existing2 = manifestByPath.get(op.relativeDestPath);
3528
3698
  if (existing2 && existing2.sourceHash === op.desiredHash && existing2.executed === true) {
3529
- 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" });
3530
3700
  } else {
3531
3701
  operations.push(existing2 && existing2.sourceHash === op.desiredHash ? { ...op, manifestHash: existing2.hash, reason: "semantic plugin pending execution" } : op);
3532
3702
  }
@@ -3548,15 +3718,16 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3548
3718
  if (op.mergeStrategy) {
3549
3719
  const existing2 = manifestByPath.get(op.relativeDestPath);
3550
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);
3551
3722
  if (!exists2) {
3552
- operations.push({ ...op, action: "create", reason: "merge destination missing" });
3723
+ operations.push({ ...op, action: "create", mergeRemoval, mergeCreatedDestination: true, reason: "merge destination missing" });
3553
3724
  continue;
3554
3725
  }
3555
3726
  const currentHash2 = await transport.hashPath(op.destPath);
3556
3727
  if (existing2 && existing2.sourceHash === op.desiredHash) {
3557
- 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" });
3558
3729
  } else {
3559
- 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" });
3560
3731
  }
3561
3732
  continue;
3562
3733
  }
@@ -3673,7 +3844,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
3673
3844
  relativeDestPath: entry.path,
3674
3845
  currentHash,
3675
3846
  manifestHash: entry.hash,
3676
- reason: "artifact removed from source",
3847
+ reason: staleRemovalReason(entry),
3677
3848
  channel: entry.channel,
3678
3849
  packageName: entry.packageName,
3679
3850
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -3988,7 +4159,9 @@ function operationMetadataFromEntry(entry) {
3988
4159
  dependencyRole: entry.dependencyRole,
3989
4160
  owners: entry.owners,
3990
4161
  workspaceOwner: entry.workspaceOwner,
3991
- graphLockDigest: entry.graphLockDigest
4162
+ graphLockDigest: entry.graphLockDigest,
4163
+ mergeRemoval: entry.mergeRemoval,
4164
+ mergeCreatedDestination: entry.mergeCreatedDestination
3992
4165
  };
3993
4166
  }
3994
4167
  return {
@@ -4027,6 +4200,10 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
4027
4200
  function normalizeOperationOwners(op) {
4028
4201
  return normalizeOwners(op.owners ?? [op.packageName ?? op.artifactName]);
4029
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
+ }
4030
4207
  function isGuardedMergeTarget(type) {
4031
4208
  return type === "mcp" || type === "hooks" || type === "settings" || type === "plugins";
4032
4209
  }
@@ -4201,6 +4378,8 @@ async function createUninstallPlan(manifest, transport = localTransport) {
4201
4378
  composedFrom: entry.composedFrom,
4202
4379
  ...operationMetadataFromEntry2(entry)
4203
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));
4204
4383
  } else {
4205
4384
  operations.push({
4206
4385
  action: "remove",
@@ -4212,7 +4391,7 @@ async function createUninstallPlan(manifest, transport = localTransport) {
4212
4391
  desiredHash: entry.sourceHash,
4213
4392
  currentHash,
4214
4393
  manifestHash: entry.hash,
4215
- reason: "uninstall managed artifact",
4394
+ reason: removalReason(entry, "uninstall managed artifact"),
4216
4395
  channel: entry.channel,
4217
4396
  packageName: entry.packageName,
4218
4397
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -4300,6 +4479,8 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
4300
4479
  ...operationMetadataFromEntry2(entry),
4301
4480
  graphLockDigest: options.graphLockDigest
4302
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));
4303
4484
  } else {
4304
4485
  operations.push({
4305
4486
  action: "remove",
@@ -4311,7 +4492,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
4311
4492
  desiredHash: entry.sourceHash,
4312
4493
  currentHash,
4313
4494
  manifestHash: entry.hash,
4314
- reason: "owner set became empty",
4495
+ reason: removalReason(entry, "owner set became empty"),
4315
4496
  channel: entry.channel,
4316
4497
  packageName: entry.packageName,
4317
4498
  semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
@@ -4351,7 +4532,9 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
4351
4532
  graphNodeId: entry.graphNodeId,
4352
4533
  dependencyRole: entry.dependencyRole,
4353
4534
  owners: ownersOverride ?? entry.owners,
4354
- graphLockDigest: entry.graphLockDigest
4535
+ graphLockDigest: entry.graphLockDigest,
4536
+ mergeRemoval: entry.mergeRemoval,
4537
+ mergeCreatedDestination: entry.mergeCreatedDestination
4355
4538
  };
4356
4539
  }
4357
4540
  return {
@@ -4361,6 +4544,34 @@ function operationMetadataFromEntry2(entry, ownersOverride) {
4361
4544
  owners: ownersOverride?.length ? ownersOverride : [entry.packageName ?? "legacy"]
4362
4545
  };
4363
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
+ }
4364
4575
 
4365
4576
  // src/cli/format.ts
4366
4577
  var labels = {
@@ -4399,6 +4610,56 @@ function formatPlan(plan) {
4399
4610
  );
4400
4611
  return lines.join("\n");
4401
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
+ }
4402
4663
  function formatSemanticCommandSuffix(operation) {
4403
4664
  const commands = semanticCommandsForOperation(operation);
4404
4665
  return commands.length > 0 ? ` :: ${commands.map((command) => command.join(" ")).join(" && ")}` : "";
@@ -4422,6 +4683,7 @@ function sortedPlanOperations(operations) {
4422
4683
  function formatGraphPlan(result) {
4423
4684
  const lines = [
4424
4685
  ...formatDependencyTree(result.graph),
4686
+ ...formatSelectionImports(result.graph),
4425
4687
  `LOCK ${result.graphLockPath} (${result.graphLockDigest})`
4426
4688
  ];
4427
4689
  if (result.recoveredPendingApply) {
@@ -4465,6 +4727,20 @@ function formatDependencyTree(graph) {
4465
4727
  }
4466
4728
  return lines;
4467
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
+ }
4468
4744
  function formatLockDependencyTree(lock) {
4469
4745
  const depths = lockNodeDepths(lock);
4470
4746
  const lines = ["Dependency graph"];
@@ -4474,6 +4750,16 @@ function formatLockDependencyTree(lock) {
4474
4750
  const requiredBy = node.requiredBy.length > 0 ? node.requiredBy.join(",") : "<root>";
4475
4751
  lines.push(`${label.padEnd(7)} ${node.id} source=${node.normalizedSource} selected=[${selected}] requiredBy=[${requiredBy}]`);
4476
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
+ }
4477
4763
  for (const edge of lock.canonical.edges) {
4478
4764
  const selected = edge.selected.length > 0 ? edge.selected.join(",") : "<none>";
4479
4765
  lines.push(`EDGE ${edge.from} --${edge.alias}--> ${edge.to} selected=[${selected}]`);
@@ -4565,19 +4851,502 @@ function ownerChains(lock, nodeId) {
4565
4851
  return incoming.flatMap((edge) => ownerChains(lock, edge.from).map((chain) => [...chain, `${edge.alias}:${nodeId}`]));
4566
4852
  }
4567
4853
 
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}`)}"]`);
4885
+ }
4886
+ for (const name of packageNames) {
4887
+ lines.push(` ${packageIds.get(name)}["${escapeMermaidLabel(name)}"]`);
4888
+ }
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
+
4568
5337
  // src/source/clawhub.ts
4569
- import { mkdir as mkdir11, rm as rm5, writeFile as writeFile13 } from "fs/promises";
4570
- import { basename as basename10, dirname as dirname14, join as join20, resolve as resolve6 } from "path";
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";
4571
5340
 
4572
5341
  // src/source/local.ts
4573
- import { createHash as createHash4 } from "crypto";
5342
+ import { createHash as createHash5 } from "crypto";
4574
5343
  import { readdir, stat as stat3 } from "fs/promises";
4575
5344
  import { basename as basename9, join as join19, relative as relative4, resolve as resolve5 } from "path";
4576
5345
 
4577
5346
  // src/model/package.ts
4578
- import { readFile as readFile16 } from "fs/promises";
5347
+ import { readFile as readFile17 } from "fs/promises";
4579
5348
  import { join as join18 } from "path";
4580
- import { parse as parse3, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
5349
+ import { parse as parse4, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
4581
5350
  import { z as z5 } from "zod";
4582
5351
  var legacyArtifactTypeSchema = z5.enum([
4583
5352
  "instructions",
@@ -4672,9 +5441,9 @@ async function findPackageManifestPath(root, options = {}) {
4672
5441
  async function readPackageManifest(root) {
4673
5442
  const path = await findPackageManifestPath(root);
4674
5443
  if (!path) return void 0;
4675
- const content = await readFile16(path, "utf8");
5444
+ const content = await readFile17(path, "utf8");
4676
5445
  const errors = [];
4677
- const parsed = parse3(content, errors, { allowTrailingComma: true, disallowComments: false });
5446
+ const parsed = parse4(content, errors, { allowTrailingComma: true, disallowComments: false });
4678
5447
  if (errors.length > 0) {
4679
5448
  const details = errors.map((error) => `${printParseErrorCode2(error.error)} at offset ${error.offset}`).join(", ");
4680
5449
  throw new Error(`Invalid package manifest ${path}: ${details}`);
@@ -4682,7 +5451,7 @@ async function readPackageManifest(root) {
4682
5451
  return parsePackageManifest(parsed, path);
4683
5452
  }
4684
5453
  function parsePackageManifest(parsed, path = "package manifest") {
4685
- if (!isRecord6(parsed)) {
5454
+ if (!isRecord7(parsed)) {
4686
5455
  throw new Error(`Invalid package manifest ${path}: expected an object`);
4687
5456
  }
4688
5457
  if (parsed.schemaVersion === 1) {
@@ -4710,7 +5479,7 @@ function v1OpenPackViolations(manifest) {
4710
5479
  }
4711
5480
  const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
4712
5481
  for (const [index, provide] of provides.entries()) {
4713
- if (!isRecord6(provide)) continue;
5482
+ if (!isRecord7(provide)) continue;
4714
5483
  if (provide.type === "fragments") violations.push(`provides[${index}].type=fragments`);
4715
5484
  for (const key of ["format", "items", "compose", "runtimes"]) {
4716
5485
  if (Object.prototype.hasOwnProperty.call(provide, key)) violations.push(`provides[${index}].${key}`);
@@ -4721,7 +5490,7 @@ function v1OpenPackViolations(manifest) {
4721
5490
  function isLegacyPackageManifestName(name) {
4722
5491
  return legacyPackageManifestNames.includes(name);
4723
5492
  }
4724
- function isRecord6(value) {
5493
+ function isRecord7(value) {
4725
5494
  return typeof value === "object" && value !== null && !Array.isArray(value);
4726
5495
  }
4727
5496
 
@@ -4865,7 +5634,7 @@ var LocalSourceDriver = class {
4865
5634
  };
4866
5635
  async function hashLocalSource(root, manifest) {
4867
5636
  if (!manifest) return hashPath(root);
4868
- const hash = createHash4("sha256").update("local-openpack\0");
5637
+ const hash = createHash5("sha256").update("local-openpack\0");
4869
5638
  const manifestPath = await findPackageManifestPath(root, { warnLegacy: false });
4870
5639
  if (manifestPath) {
4871
5640
  hash.update("manifest\0");
@@ -5100,8 +5869,8 @@ async function writeGeneratedPackage(root, packageInfo) {
5100
5869
  };
5101
5870
  const pluginPath = join20(root, "plugins", pluginId, "clawhub.json");
5102
5871
  await rm5(root, { recursive: true, force: true });
5103
- await mkdir11(dirname14(pluginPath), { recursive: true });
5104
- await writeFile13(join20(root, "openpack.json"), `${JSON.stringify({
5872
+ await mkdir12(dirname15(pluginPath), { recursive: true });
5873
+ await writeFile14(join20(root, "openpack.json"), `${JSON.stringify({
5105
5874
  schemaVersion: 2,
5106
5875
  name: `clawhub/${name}`,
5107
5876
  version: packageInfo.latestVersion ?? "latest",
@@ -5117,7 +5886,7 @@ async function writeGeneratedPackage(root, packageInfo) {
5117
5886
  ]
5118
5887
  }, null, 2)}
5119
5888
  `, "utf8");
5120
- await writeFile13(pluginPath, `${JSON.stringify(plugin, null, 2)}
5889
+ await writeFile14(pluginPath, `${JSON.stringify(plugin, null, 2)}
5121
5890
  `, "utf8");
5122
5891
  }
5123
5892
  function installNameFor2(value) {
@@ -5131,9 +5900,9 @@ function cachePathFor(packageName, cacheRoot) {
5131
5900
 
5132
5901
  // src/source/git.ts
5133
5902
  import { execFile as execFile4 } from "child_process";
5134
- 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";
5135
5904
  import { homedir as homedir2 } from "os";
5136
- 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";
5137
5906
  import { promisify as promisify4 } from "util";
5138
5907
  var execFileAsync4 = promisify4(execFile4);
5139
5908
  var GitSourceDriver = class {
@@ -5156,7 +5925,7 @@ var GitSourceDriver = class {
5156
5925
  async fetch(resolved) {
5157
5926
  return withFilesystemLock(`${resolved.resolvedPath}.lock`, resolved.cacheLockTimeoutMs ?? 3e4, async () => {
5158
5927
  const parsed = parseGitSource(resolved.source);
5159
- await mkdir12(resolve7(resolved.resolvedPath, ".."), { recursive: true });
5928
+ await mkdir13(resolve7(resolved.resolvedPath, ".."), { recursive: true });
5160
5929
  if (!await pathExists(join21(resolved.resolvedPath, ".git"))) {
5161
5930
  if (resolved.frozenLock) {
5162
5931
  throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
@@ -5232,9 +6001,9 @@ async function git(args) {
5232
6001
  return execFileAsync4("git", args, { maxBuffer: 1024 * 1024 * 10 });
5233
6002
  }
5234
6003
  async function snapshotCheckout(checkoutPath, commit) {
5235
- const snapshotPath = join21(dirname15(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
6004
+ const snapshotPath = join21(dirname16(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
5236
6005
  if (await pathExists(snapshotPath)) return snapshotPath;
5237
- 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()}`);
5238
6007
  await rm6(tempPath, { recursive: true, force: true });
5239
6008
  await cp2(checkoutPath, tempPath, { recursive: true, dereference: true });
5240
6009
  await rm6(join21(tempPath, ".git"), { recursive: true, force: true });
@@ -5248,12 +6017,12 @@ async function snapshotCheckout(checkoutPath, commit) {
5248
6017
  return snapshotPath;
5249
6018
  }
5250
6019
  async function withFilesystemLock(lockPath, timeoutMs, fn) {
5251
- await mkdir12(dirname15(lockPath), { recursive: true });
6020
+ await mkdir13(dirname16(lockPath), { recursive: true });
5252
6021
  const started = Date.now();
5253
6022
  while (true) {
5254
6023
  try {
5255
- await mkdir12(lockPath);
5256
- 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");
5257
6026
  break;
5258
6027
  } catch (error) {
5259
6028
  if (!isAlreadyExists2(error)) throw error;
@@ -5274,8 +6043,8 @@ function isAlreadyExists2(error) {
5274
6043
  }
5275
6044
 
5276
6045
  // src/source/mcp-registry.ts
5277
- import { mkdir as mkdir13, writeFile as writeFile15 } from "fs/promises";
5278
- 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";
5279
6048
  var registryBaseUrl = "https://registry.modelcontextprotocol.io/v0.1";
5280
6049
  var sourcePrefix2 = "mcp-registry:";
5281
6050
  var McpRegistrySourceDriver = class {
@@ -5382,15 +6151,15 @@ function isSafeHttpUrl(value) {
5382
6151
  async function writeGeneratedPackage2(root, server) {
5383
6152
  const serverId = installNameFor3(server.serverName);
5384
6153
  const mcpPath = join22(root, "mcp", `${serverId}.json`);
5385
- await mkdir13(dirname16(mcpPath), { recursive: true });
5386
- await writeFile15(join22(root, "openpack.json"), `${JSON.stringify({
6154
+ await mkdir14(dirname17(mcpPath), { recursive: true });
6155
+ await writeFile16(join22(root, "openpack.json"), `${JSON.stringify({
5387
6156
  schemaVersion: 2,
5388
6157
  name: `mcp-registry/${server.serverName}`,
5389
6158
  version: server.version ?? "latest",
5390
6159
  provides: [{ type: "mcp", path: "mcp" }]
5391
6160
  }, null, 2)}
5392
6161
  `, "utf8");
5393
- await writeFile15(mcpPath, `${JSON.stringify({
6162
+ await writeFile16(mcpPath, `${JSON.stringify({
5394
6163
  mcpServers: {
5395
6164
  [serverId]: {
5396
6165
  type: "streamable-http",
@@ -5410,14 +6179,14 @@ function cachePathFor3(serverName, cacheRoot) {
5410
6179
  }
5411
6180
 
5412
6181
  // src/source/skillkit.ts
5413
- 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";
5414
6183
  import { homedir as homedir3 } from "os";
5415
- 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";
5416
6185
  import * as defaultSkillKit from "@skillkit/core";
5417
6186
 
5418
6187
  // src/source/skill-artifacts.ts
5419
6188
  import { readdir as readdir2, stat as stat4 } from "fs/promises";
5420
- 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";
5421
6190
  async function artifactsFromSkillPaths(paths, packageName) {
5422
6191
  const artifacts = [];
5423
6192
  const seen = /* @__PURE__ */ new Set();
@@ -5454,7 +6223,7 @@ async function artifactFromSkillPath(item, packageName) {
5454
6223
  };
5455
6224
  }
5456
6225
  if (stats.isFile() && basename13(item.path).toLowerCase() === "skill.md") {
5457
- const dir = dirname17(item.path);
6226
+ const dir = dirname18(item.path);
5458
6227
  const name = sanitizeSkillName(item.name ?? basename13(dir));
5459
6228
  return {
5460
6229
  type: "skills",
@@ -5547,7 +6316,7 @@ var SkillKitSourceDriver = class {
5547
6316
  if (!provider?.clone) {
5548
6317
  throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
5549
6318
  }
5550
- await mkdir14(dirname18(resolved.resolvedPath), { recursive: true });
6319
+ await mkdir15(dirname19(resolved.resolvedPath), { recursive: true });
5551
6320
  const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
5552
6321
  if (!result.success || !result.path) {
5553
6322
  throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
@@ -5593,7 +6362,7 @@ var SkillKitSourceDriver = class {
5593
6362
  for (const skill of this.discover(resolved.resolvedPath)) {
5594
6363
  const skillMd = join24(skill.path, "SKILL.md");
5595
6364
  if (await pathExists(skillMd)) {
5596
- this.core.translateSkill(await readFile17(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
6365
+ this.core.translateSkill(await readFile18(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
5597
6366
  }
5598
6367
  }
5599
6368
  return resolved;
@@ -5769,14 +6538,14 @@ function getSourceDriver(name = "local") {
5769
6538
  }
5770
6539
 
5771
6540
  // src/staging/staging.ts
5772
- import { chmod, cp as cp5, mkdir as mkdir16, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
5773
- 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";
5774
6543
  import { tmpdir as tmpdir4 } from "os";
5775
6544
 
5776
6545
  // src/compose/markdown.ts
5777
- import { createHash as createHash5 } from "crypto";
5778
- import { readdir as readdir3, readFile as readFile18, stat as stat6, writeFile as writeFile16 } from "fs/promises";
5779
- 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";
5780
6549
  var includePattern = /<!--\s*openpack:include(\?)?\s+([^>]+?)\s*-->/g;
5781
6550
  var escapedIncludePattern = /<!--\s*openpack\\:include(\?)?\s+([^>]+?)\s*-->/g;
5782
6551
  var generatedPattern = /<!--\s*(?:BEGIN|END)\s+openpack:include\b/;
@@ -5786,12 +6555,13 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
5786
6555
  for (const artifact of artifacts) byKey.set(artifactKey(artifact), artifact);
5787
6556
  const artifactPaths = artifactPathMap(artifacts);
5788
6557
  for (const artifact of orderedForExpansion(artifacts)) {
6558
+ if (artifact.type === "fragments") continue;
5789
6559
  const files = await markdownFilesForArtifact(artifact);
5790
6560
  if (files.length === 0) continue;
5791
6561
  const composedFrom = [];
5792
6562
  for (const file of files) {
5793
6563
  const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
5794
- if (result.changed) await writeFile16(file, result.content, "utf8");
6564
+ if (result.changed) await writeFile17(file, result.content, "utf8");
5795
6565
  composedFrom.push(...result.composedFrom);
5796
6566
  }
5797
6567
  const stagedPath = artifact.stagedPath ?? artifact.sourcePath;
@@ -5813,7 +6583,7 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
5813
6583
  }
5814
6584
  }
5815
6585
  async function expandFile(file, packageRoot, appendEntries, artifactPaths, options) {
5816
- const raw = await readFile18(file, "utf8");
6586
+ const raw = await readFile19(file, "utf8");
5817
6587
  const owner = ownerSelector(packageRoot, file, options.nodeId);
5818
6588
  const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
5819
6589
  let content = expanded.content;
@@ -5906,7 +6676,7 @@ async function expandInclude(selector, packageRoot, artifactPaths, options) {
5906
6676
  if (!stats.isFile()) {
5907
6677
  throw new Error(`OpenPack include is not a file: ${displaySelector}`);
5908
6678
  }
5909
- const raw = sourceContent ?? await readFile18(sourcePath, "utf8");
6679
+ const raw = sourceContent ?? await readFile19(sourcePath, "utf8");
5910
6680
  const { optional: _optional, markers: _markers, chain: _chain, ...childOptions } = options;
5911
6681
  const expanded = await expandContent(raw, includePackageRoot, [...options.chain, displaySelector], includeArtifactPaths, {
5912
6682
  ...childOptions,
@@ -5996,7 +6766,7 @@ async function listMarkdownFiles(root) {
5996
6766
  function composeEntriesForFile(artifact, file) {
5997
6767
  if (!artifact.compose?.length) return [];
5998
6768
  if (artifact.kind === "file") return [resolve11(artifact.stagedPath ?? artifact.sourcePath), resolve11(file)].every(Boolean) && resolve11(artifact.stagedPath ?? artifact.sourcePath) === resolve11(file) ? artifact.compose : [];
5999
- 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 : [];
6000
6770
  }
6001
6771
  function orderedForExpansion(artifacts) {
6002
6772
  return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
@@ -6027,7 +6797,7 @@ function cleanSelector(value) {
6027
6797
  return value.trim().replace(/\s+/g, " ");
6028
6798
  }
6029
6799
  function sha256(content) {
6030
- return createHash5("sha256").update(content).digest("hex");
6800
+ return createHash6("sha256").update(content).digest("hex");
6031
6801
  }
6032
6802
  function uniqueComposedFrom(entries) {
6033
6803
  if (entries.length === 0) return [];
@@ -6042,8 +6812,8 @@ function artifactPathMap(artifacts) {
6042
6812
  }
6043
6813
 
6044
6814
  // src/staging/customize.ts
6045
- import { cp as cp4, mkdir as mkdir15, readdir as readdir4, readFile as readFile19, writeFile as writeFile17 } from "fs/promises";
6046
- 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";
6047
6817
  async function applyCustomizations(artifacts, options) {
6048
6818
  let next = [...artifacts];
6049
6819
  next = await applyReplacements2(next, options, "override", installableArtifactTypes());
@@ -6064,11 +6834,11 @@ async function applyInstructionOverlay(artifacts, options) {
6064
6834
  const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
6065
6835
  if (index < 0) return artifacts;
6066
6836
  const artifact = artifacts[index];
6067
- const managed = await readFile19(artifact.stagedPath ?? artifact.sourcePath, "utf8");
6068
- const local = await readFile19(overlayPath, "utf8");
6837
+ const managed = await readFile20(artifact.stagedPath ?? artifact.sourcePath, "utf8");
6838
+ const local = await readFile20(overlayPath, "utf8");
6069
6839
  const composedPath = join27(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
6070
- await mkdir15(dirname20(composedPath), { recursive: true });
6071
- await writeFile17(
6840
+ await mkdir16(dirname21(composedPath), { recursive: true });
6841
+ await writeFile18(
6072
6842
  composedPath,
6073
6843
  [
6074
6844
  "<!-- BEGIN agentwheel managed: upstream -->",
@@ -6140,7 +6910,7 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
6140
6910
  const artifactKind = entry.isDirectory() ? "dir" : "file";
6141
6911
  const existing = byKey.get(artifactMapKey);
6142
6912
  const stagedPath = join27(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
6143
- await mkdir15(dirname20(stagedPath), { recursive: true });
6913
+ await mkdir16(dirname21(stagedPath), { recursive: true });
6144
6914
  await cp4(full, stagedPath, { recursive: artifactKind === "dir", dereference: true });
6145
6915
  byKey.set(artifactMapKey, {
6146
6916
  ...existing,
@@ -6200,7 +6970,7 @@ async function stageResolvedArtifactsRaw(resolved, artifacts) {
6200
6970
  const stagedArtifacts = [];
6201
6971
  for (const artifact of artifacts) {
6202
6972
  const stagedPath = join28(root, artifact.relativePath);
6203
- await mkdir16(dirname21(stagedPath), { recursive: true });
6973
+ await mkdir17(dirname22(stagedPath), { recursive: true });
6204
6974
  await cp5(artifact.sourcePath, stagedPath, {
6205
6975
  recursive: artifact.kind === "dir",
6206
6976
  dereference: true,
@@ -6294,7 +7064,7 @@ async function copyAsset(asset, source, dest) {
6294
7064
  const sourceStats = await stat8(source);
6295
7065
  if (sourceStats.isFile()) {
6296
7066
  if (matchesAny(basename18(source), asset.include)) {
6297
- await mkdir16(dest, { recursive: true });
7067
+ await mkdir17(dest, { recursive: true });
6298
7068
  await copyAssetFile(source, join28(dest, basename18(source)), asset);
6299
7069
  }
6300
7070
  return;
@@ -6303,7 +7073,7 @@ async function copyAsset(asset, source, dest) {
6303
7073
  throw new Error(`Asset include source is not a file or directory: ${source}`);
6304
7074
  }
6305
7075
  if (!asset.include?.length) {
6306
- await mkdir16(dirname21(dest), { recursive: true });
7076
+ await mkdir17(dirname22(dest), { recursive: true });
6307
7077
  await cp5(source, dest, { recursive: true, dereference: true });
6308
7078
  if (asset.mode === "copy") await normalizeCopiedModes(dest);
6309
7079
  return;
@@ -6315,7 +7085,7 @@ async function copyAsset(asset, source, dest) {
6315
7085
  }
6316
7086
  }
6317
7087
  async function copyAssetFile(source, dest, asset) {
6318
- await mkdir16(dirname21(dest), { recursive: true });
7088
+ await mkdir17(dirname22(dest), { recursive: true });
6319
7089
  await cp5(source, dest, { dereference: true });
6320
7090
  if (asset.mode === "copy") await chmod(dest, 420);
6321
7091
  }
@@ -6363,19 +7133,49 @@ function matchesGlob(path, pattern) {
6363
7133
  }
6364
7134
 
6365
7135
  // src/model/workspace.ts
6366
- import { readFile as readFile20 } from "fs/promises";
7136
+ import { readFile as readFile21 } from "fs/promises";
6367
7137
  import { homedir as homedir4 } from "os";
6368
- 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";
6369
7139
  import { z as z6 } from "zod";
6370
- var workspacePackageSchema = z6.object({
6371
- name: z6.string().min(1),
6372
- source: z6.string().min(1),
6373
- driver: z6.enum(["local", "git", "skillkit", "vercel-skills", "mcp-registry", "clawhub"]).default("local"),
6374
- adapter: z6.string().min(1).default("openclaw"),
6375
- adapterConfig: z6.string().min(1).optional(),
6376
- adapterModule: z6.string().min(1).optional(),
6377
- adapterCodeHash: z6.string().min(16).optional(),
6378
- installationType: installationTypeSchema.optional(),
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({
7171
+ name: z6.string().min(1),
7172
+ source: z6.string().min(1),
7173
+ driver: z6.enum(["local", "git", "skillkit", "vercel-skills", "mcp-registry", "clawhub"]).default("local"),
7174
+ adapter: z6.string().min(1).default("openclaw"),
7175
+ adapterConfig: z6.string().min(1).optional(),
7176
+ adapterModule: z6.string().min(1).optional(),
7177
+ adapterCodeHash: z6.string().min(16).optional(),
7178
+ installationType: installationTypeSchema.optional(),
6379
7179
  mode: z6.enum(["pinned", "tracking"]).default("pinned"),
6380
7180
  requestedRef: z6.string().min(1).optional(),
6381
7181
  select: z6.array(z6.string().min(1)).optional(),
@@ -6385,6 +7185,22 @@ var workspacePackageSchema = z6.object({
6385
7185
  aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
6386
7186
  overrides: z6.array(z6.string().min(1)).optional()
6387
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();
6388
7204
  var workspaceProfileRuntimeSchema = z6.object({
6389
7205
  agent: z6.string().min(1).optional(),
6390
7206
  adapter: z6.string().min(1).default("openclaw"),
@@ -6392,7 +7208,9 @@ var workspaceProfileRuntimeSchema = z6.object({
6392
7208
  adapterModule: z6.string().min(1).optional(),
6393
7209
  installationType: installationTypeSchema.optional(),
6394
7210
  targetRoot: z6.string().min(1).optional(),
6395
- executePlugins: z6.boolean().optional()
7211
+ executePlugins: z6.boolean().optional(),
7212
+ reloadRuntimes: z6.boolean().optional(),
7213
+ reloadCommands: commandListSchema
6396
7214
  });
6397
7215
  var workspaceProfileSchema = z6.object({
6398
7216
  runtimes: z6.array(workspaceProfileRuntimeSchema).min(1)
@@ -6417,7 +7235,8 @@ var workspaceAgentSchema = z6.object({
6417
7235
  host: z6.string().min(1).optional(),
6418
7236
  user: z6.string().min(1).optional(),
6419
7237
  port: z6.number().int().positive().optional(),
6420
- identityFile: z6.string().min(1).optional()
7238
+ identityFile: z6.string().min(1).optional(),
7239
+ reloadCommands: commandListSchema
6421
7240
  }).superRefine((agent, ctx) => {
6422
7241
  if (agent.transport !== "ssh") return;
6423
7242
  if (!agent.host) {
@@ -6428,22 +7247,34 @@ var workspaceAgentSchema = z6.object({
6428
7247
  });
6429
7248
  }
6430
7249
  });
6431
- var workspaceConfigSchema = z6.object({
6432
- schemaVersion: z6.literal(1),
6433
- packages: z6.array(workspacePackageSchema).default([]),
7250
+ var workspaceConfigBaseSchema = z6.object({
6434
7251
  bootstrapSkills: z6.boolean().optional(),
6435
7252
  registry: workspaceRegistrySchema,
6436
7253
  trust: workspaceTrustSchema,
6437
7254
  profiles: z6.record(z6.string(), workspaceProfileSchema).default({}),
6438
7255
  agents: z6.record(z6.string(), workspaceAgentSchema).default({})
6439
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
+ ]);
6440
7271
  function workspaceConfigPath(workspaceRoot) {
6441
7272
  return join29(workspaceRoot, ".agentwheel", "config.json");
6442
7273
  }
6443
7274
  async function readWorkspaceConfig(workspaceRoot) {
6444
7275
  const path = workspaceConfigPath(workspaceRoot);
6445
7276
  if (!await pathExists(path)) return emptyWorkspaceConfig();
6446
- return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
7277
+ return workspaceConfigSchema.parse(JSON.parse(await readFile21(path, "utf8")));
6447
7278
  }
6448
7279
  async function writeWorkspaceConfig(workspaceRoot, config) {
6449
7280
  await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
@@ -6453,7 +7284,7 @@ function upsertPackage(config, entry) {
6453
7284
  const packages = parsed.packages.filter((candidate) => candidate.name !== entry.name);
6454
7285
  packages.push(entry);
6455
7286
  packages.sort((a, b) => a.name.localeCompare(b.name));
6456
- 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 });
6457
7288
  }
6458
7289
  function globalWorkspaceConfigPath(globalRoot = homedir4()) {
6459
7290
  return join29(globalRoot, ".agentwheel", "config.json");
@@ -6462,7 +7293,7 @@ async function findWorkspaceRoot(start = process.cwd()) {
6462
7293
  let current = resolve13(start);
6463
7294
  while (true) {
6464
7295
  if (await pathExists(workspaceConfigPath(current))) return current;
6465
- const parent = dirname22(current);
7296
+ const parent = dirname23(current);
6466
7297
  if (parent === current) return resolve13(start);
6467
7298
  current = parent;
6468
7299
  }
@@ -6473,8 +7304,10 @@ async function readMergedWorkspaceConfig(projectRoot, options = {}) {
6473
7304
  return mergeWorkspaceConfig(global, project);
6474
7305
  }
6475
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;
6476
7309
  return workspaceConfigSchema.parse({
6477
- schemaVersion: 1,
7310
+ schemaVersion,
6478
7311
  packages: project.packages.length > 0 ? project.packages : global.packages,
6479
7312
  bootstrapSkills: project.bootstrapSkills ?? global.bootstrapSkills,
6480
7313
  registry: {
@@ -6485,7 +7318,8 @@ function mergeWorkspaceConfig(global, project) {
6485
7318
  },
6486
7319
  trust: mergeWorkspaceTrust(global.trust, project.trust),
6487
7320
  profiles: { ...global.profiles, ...project.profiles },
6488
- agents: { ...global.agents, ...project.agents }
7321
+ agents: { ...global.agents, ...project.agents },
7322
+ ...exports ? { exports } : {}
6489
7323
  });
6490
7324
  }
6491
7325
  function resolveConfigPath(path, baseRoot) {
@@ -6498,7 +7332,7 @@ function emptyWorkspaceConfig() {
6498
7332
  }
6499
7333
  async function readConfigPath(path) {
6500
7334
  if (!await pathExists(path)) return emptyWorkspaceConfig();
6501
- return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
7335
+ return workspaceConfigSchema.parse(JSON.parse(await readFile21(path, "utf8")));
6502
7336
  }
6503
7337
  function mergeWorkspaceTrust(global, project) {
6504
7338
  return {
@@ -6513,56 +7347,174 @@ function sortedUnique2(values) {
6513
7347
  }
6514
7348
 
6515
7349
  // src/lifecycle/customization.ts
6516
- import { appendFile, cp as cp6, mkdir as mkdir17, rm as rm9 } from "fs/promises";
6517
- 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";
6518
7352
 
6519
7353
  // src/resolve/graph.ts
6520
- import { createHash as createHash6 } from "crypto";
6521
- 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";
6522
7356
  import { tmpdir as tmpdir5 } from "os";
6523
7357
  import { basename as basename19, extname as extname4, join as join31 } from "path";
6524
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
+
6525
7477
  // src/resolve/identity.ts
6526
7478
  import { homedir as homedir6 } from "os";
6527
7479
  import { resolve as resolve15 } from "path";
6528
7480
 
6529
7481
  // src/registry/client.ts
6530
- 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";
6531
7483
  import { homedir as homedir5 } from "os";
6532
- 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";
6533
7485
  import { fileURLToPath } from "url";
6534
7486
 
6535
7487
  // src/model/registry.ts
6536
- import { z as z7 } from "zod";
6537
- var registryEntrySchema = z7.object({
6538
- name: z7.string().min(1),
6539
- source: z7.string().min(1),
6540
- type: z7.enum(["package", "skill", "plugin", "mcp", "adapter"]).default("package"),
6541
- description: z7.string().default(""),
6542
- tags: z7.array(z7.string().min(1)).default([]),
6543
- select: z7.array(z7.string().min(1)).optional(),
6544
- skills: z7.array(z7.string().min(1)).optional(),
6545
- homepageUrl: z7.string().min(1).optional(),
6546
- homepageLinkLabel: z7.string().min(1).optional(),
6547
- sourceUrl: z7.string().min(1).optional(),
6548
- sourceLinkLabel: z7.string().min(1).optional(),
6549
- openpack: z7.object({
6550
- schemaVersion: z7.number().int().positive().optional(),
6551
- 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()
6552
7504
  }).passthrough().optional()
6553
7505
  });
6554
- var registryIndexSchema = z7.union([
6555
- z7.array(registryEntrySchema),
6556
- z7.object({
6557
- schemaVersion: z7.literal(1).optional(),
6558
- 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)
6559
7511
  })
6560
7512
  ]).transform((value) => Array.isArray(value) ? value : value.entries);
6561
- var registryCacheSchema = z7.object({
6562
- version: z7.literal(1),
6563
- fetchedAt: z7.string().datetime(),
6564
- sources: z7.array(z7.string().min(1)),
6565
- 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)
6566
7518
  });
6567
7519
 
6568
7520
  // src/registry/client.ts
@@ -6633,7 +7585,7 @@ var RegistryClient = class {
6633
7585
  }
6634
7586
  async readCache() {
6635
7587
  if (!await pathExists(this.cachePath)) return void 0;
6636
- return registryCacheSchema.parse(JSON.parse(await readFile21(this.cachePath, "utf8")));
7588
+ return registryCacheSchema.parse(JSON.parse(await readFile23(this.cachePath, "utf8")));
6637
7589
  }
6638
7590
  isExpired(cache, ttlMs) {
6639
7591
  return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
@@ -6652,10 +7604,10 @@ var RegistryClient = class {
6652
7604
  if (await pathExists(filePath)) {
6653
7605
  const fullPath = resolve14(filePath);
6654
7606
  const stats = await stat9(fullPath);
6655
- return readFile21(stats.isDirectory() ? join30(fullPath, "index.json") : fullPath, "utf8");
7607
+ return readFile23(stats.isDirectory() ? join30(fullPath, "index.json") : fullPath, "utf8");
6656
7608
  }
6657
- const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join30(dirname23(this.cachePath), "registry-repos") }));
6658
- 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");
6659
7611
  }
6660
7612
  warnCompatibility(entries) {
6661
7613
  for (const entry of entries) {
@@ -6669,7 +7621,7 @@ var RegistryClient = class {
6669
7621
  }
6670
7622
  };
6671
7623
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
6672
- const { isExplicitSource } = await import("./identify-T4RE5RBD.js");
7624
+ const { isExplicitSource } = await import("./identify-VV7SXUIQ.js");
6673
7625
  if (await isExplicitSource(source)) return { source };
6674
7626
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
6675
7627
  if (!entry) {
@@ -6772,7 +7724,7 @@ function isBareRegistryName(source) {
6772
7724
  return !source.includes(":") && !isLocalSource(source);
6773
7725
  }
6774
7726
  function isLocalSource(source) {
6775
- 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:");
6776
7728
  }
6777
7729
  function localSourcePath(source) {
6778
7730
  return source.startsWith("local:") ? source.slice("local:".length) : source;
@@ -6948,11 +7900,13 @@ async function resolveDependencyGraph(roots, options) {
6948
7900
  const nodesByKey = /* @__PURE__ */ new Map();
6949
7901
  const rootResults = [];
6950
7902
  const edgeMap = /* @__PURE__ */ new Map();
7903
+ assertDependencyUpdateSelectors(options.previousLock, options.dependencyUpdateSelectors);
6951
7904
  const queue = roots.map((root, index) => {
6952
7905
  const rootId = root.rootId ?? `root-${index + 1}`;
6953
7906
  return {
6954
7907
  source: root.source,
6955
7908
  select: root.select,
7909
+ selection: root.selection,
6956
7910
  mode: root.mode ?? "pinned",
6957
7911
  ref: root.ref,
6958
7912
  declaringPackageRoot: options.workspaceRoot,
@@ -6961,11 +7915,12 @@ async function resolveDependencyGraph(roots, options) {
6961
7915
  aliases: root.aliases,
6962
7916
  overrides: root.overrides,
6963
7917
  useLock: root.useLock ?? options.lockedResolution,
7918
+ updateClosure: root.useLock === false,
6964
7919
  depth: 0,
6965
7920
  optional: false,
6966
7921
  chain: [`workspace:${rootId}`],
6967
7922
  includeSuggestions: root.includeSuggestions ?? options.includeSuggestions,
6968
- suggestionAliases: sortedUnique3([...root.suggestionAliases ?? [], ...options.suggestionAliases ?? []])
7923
+ suggestionAliases: sortedUnique4([...root.suggestionAliases ?? [], ...options.suggestionAliases ?? []])
6969
7924
  };
6970
7925
  });
6971
7926
  let iterations = 0;
@@ -7002,7 +7957,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
7002
7957
  mode: root.mode,
7003
7958
  selected: root.selected,
7004
7959
  aliases: root.aliases,
7005
- overrides: root.overrides
7960
+ overrides: root.overrides,
7961
+ selectionImport: root.selectionImport
7006
7962
  }));
7007
7963
  return {
7008
7964
  version: 1,
@@ -7066,7 +8022,14 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7066
8022
  throw new Error(`Package ${fetched.name}@${fetched.version} does not satisfy requested version ${requirement.version}`);
7067
8023
  }
7068
8024
  const nodeKey = `${normalized.normalizedSource}\0${fetched.name}`;
7069
- 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 };
7070
8033
  let state = nodesByKey.get(nodeKey);
7071
8034
  if (!state) {
7072
8035
  const id = graphNodeId(fetched.name, fetched.version, normalized.normalizedSource, fetched.resolved.resolvedCommit, fetched.sourceHash);
@@ -7097,13 +8060,15 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7097
8060
  depth: requirement.depth,
7098
8061
  fullPackageSelected: false,
7099
8062
  includeSuggestions: false,
7100
- suggestionAliases: /* @__PURE__ */ new Set()
8063
+ suggestionAliases: /* @__PURE__ */ new Set(),
8064
+ updateClosure: false
7101
8065
  };
7102
8066
  nodesByKey.set(nodeKey, state);
7103
8067
  }
7104
8068
  state.depth = Math.min(state.depth, requirement.depth);
7105
- state.fullPackageSelected = state.fullPackageSelected || requirement.select === void 0;
8069
+ state.fullPackageSelected = state.fullPackageSelected || requirement.select === void 0 && !requirement.selection;
7106
8070
  state.includeSuggestions = state.includeSuggestions || requirement.includeSuggestions === true;
8071
+ state.updateClosure = state.updateClosure || requirement.updateClosure === true;
7107
8072
  for (const alias of requirement.suggestionAliases ?? []) state.suggestionAliases.add(alias);
7108
8073
  state.requiredBy.add(requirement.requiredBy);
7109
8074
  for (const selector of selected) addSelectedSelector(state, selector, requirement.selectionReason);
@@ -7117,7 +8082,8 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7117
8082
  mode: state.node.mode,
7118
8083
  selected: state.node.selected,
7119
8084
  aliases: requirement.aliases,
7120
- overrides: requirement.overrides
8085
+ overrides: requirement.overrides,
8086
+ selectionImport
7121
8087
  });
7122
8088
  }
7123
8089
  if (requirement.parentId && requirement.alias) {
@@ -7133,7 +8099,7 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
7133
8099
  version: requirement.version,
7134
8100
  mode: requirement.mode,
7135
8101
  optional: requirement.optional,
7136
- selected: sortedUnique3([...previous?.selected ?? [], ...selected])
8102
+ selected: sortedUnique4([...previous?.selected ?? [], ...selected])
7137
8103
  });
7138
8104
  }
7139
8105
  return await collectDependencyNeeds(state, fetched, options, requirement.chain);
@@ -7164,7 +8130,7 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7164
8130
  if (!dependency.select?.length && !(state.fullPackageSelected && dependency.select === void 0)) continue;
7165
8131
  state.processedPackageAliases.add(alias);
7166
8132
  if (!dependencyTargetsRuntime(dependency.runtimes, options.runtime, state.node.id, alias, options.warn)) continue;
7167
- 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));
7168
8134
  }
7169
8135
  for (const [alias, suggestion] of suggestionEntries) {
7170
8136
  if (state.processedSuggestions.has(alias)) continue;
@@ -7200,9 +8166,9 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7200
8166
  fetched,
7201
8167
  parsed.alias,
7202
8168
  dependency,
7203
- sortedUnique3([...dependency.select ?? [], parsed.selector]),
8169
+ sortedUnique4([...dependency.select ?? [], parsed.selector]),
7204
8170
  chain,
7205
- options.lockedResolution === true,
8171
+ options,
7206
8172
  parsed.optional || dependency.optional === true,
7207
8173
  `required by ${parentSelector}`
7208
8174
  ));
@@ -7252,9 +8218,9 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7252
8218
  fetched,
7253
8219
  include.alias,
7254
8220
  dependency,
7255
- sortedUnique3([...dependency.select ?? [], include.selector]),
8221
+ sortedUnique4([...dependency.select ?? [], include.selector]),
7256
8222
  chain,
7257
- options.lockedResolution === true,
8223
+ options,
7258
8224
  include.optional || dependency.optional === true
7259
8225
  ));
7260
8226
  }
@@ -7264,13 +8230,15 @@ async function collectDependencyNeeds(state, fetched, options, chain) {
7264
8230
  refreshNode(state);
7265
8231
  return requirements;
7266
8232
  }
7267
- 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);
7268
8235
  return {
7269
8236
  source: dependency.source,
7270
8237
  select,
7271
8238
  mode: dependency.mode ?? "pinned",
7272
8239
  ref: dependency.ref,
7273
- useLock: lockByDefault || dependency.mode !== "tracking",
8240
+ useLock: dependency.mode !== "tracking" || options.lockedResolution === true && !updateClosure,
8241
+ updateClosure,
7274
8242
  declaringPackageRoot: fetched.resolved.resolvedPath,
7275
8243
  requiredBy: state.node.id,
7276
8244
  alias,
@@ -7282,7 +8250,7 @@ function dependencyRequirement(state, fetched, alias, dependency, select, chain,
7282
8250
  integrity: dependency.integrity,
7283
8251
  selectionReason,
7284
8252
  includeSuggestions: state.includeSuggestions,
7285
- suggestionAliases: sortedUnique3([...state.suggestionAliases])
8253
+ suggestionAliases: sortedUnique4([...state.suggestionAliases])
7286
8254
  };
7287
8255
  }
7288
8256
  function suggestionRequirement(state, fetched, alias, suggestion, select, chain, options, optional = shouldTreatSuggestionAsOptional(alias, suggestion, options), selectionReason) {
@@ -7293,11 +8261,51 @@ function suggestionRequirement(state, fetched, alias, suggestion, select, chain,
7293
8261
  suggestion,
7294
8262
  select,
7295
8263
  chain,
7296
- options.lockedResolution === true,
8264
+ options,
7297
8265
  optional,
7298
8266
  selectionReason
7299
8267
  );
7300
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
+ }
7301
8309
  function dependencyForAlias(dependencies, nodeId, alias) {
7302
8310
  const dependency = dependencies[alias];
7303
8311
  if (!dependency) {
@@ -7313,7 +8321,7 @@ function suggestionForAlias(suggestions, nodeId, alias) {
7313
8321
  return suggestion;
7314
8322
  }
7315
8323
  function warnNoDepsOnce(state, aliases, warn) {
7316
- const unique = sortedUnique3(aliases.filter(Boolean));
8324
+ const unique = sortedUnique4(aliases.filter(Boolean));
7317
8325
  if (unique.length === 0 || state.processedPackageAliases.has("__noDepsWarned")) return;
7318
8326
  state.processedPackageAliases.add("__noDepsWarned");
7319
8327
  warn?.(`--no-deps ignored dependencies for ${state.node.id}: ${unique.join(", ")}`);
@@ -7354,7 +8362,7 @@ function suggestionOptionsForState(state, options) {
7354
8362
  return {
7355
8363
  ...options,
7356
8364
  includeSuggestions: state.includeSuggestions || options.includeSuggestions === true,
7357
- suggestionAliases: sortedUnique3([...options.suggestionAliases ?? [], ...state.suggestionAliases])
8365
+ suggestionAliases: sortedUnique4([...options.suggestionAliases ?? [], ...state.suggestionAliases])
7358
8366
  };
7359
8367
  }
7360
8368
  function explicitSuggestionAlias(alias, options) {
@@ -7366,7 +8374,7 @@ function shouldTreatSuggestionAsOptional(alias, suggestion, options) {
7366
8374
  }
7367
8375
  function combinedSelectors(base, extra) {
7368
8376
  const values = [...base ?? [], ...extra ?? []];
7369
- return values.length > 0 ? sortedUnique3(values) : void 0;
8377
+ return values.length > 0 ? sortedUnique4(values) : void 0;
7370
8378
  }
7371
8379
  function parseDependencySelector(value) {
7372
8380
  const cleaned = value.trim();
@@ -7406,7 +8414,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
7406
8414
  const file = stack.shift();
7407
8415
  if (scanned.has(file)) continue;
7408
8416
  scanned.add(file);
7409
- const content = await readFile22(file, "utf8");
8417
+ const content = await readFile24(file, "utf8");
7410
8418
  for (const include of extractOpenPackIncludeSelectors(content)) {
7411
8419
  await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
7412
8420
  }
@@ -7614,8 +8622,8 @@ async function withCachePathLock(path, fn) {
7614
8622
  }
7615
8623
  }
7616
8624
  function computeSelectedSelectors(artifacts, select, isRoot, chain) {
7617
- if (isRoot && !select?.length) {
7618
- return sortedUnique3(artifacts.map(artifactSelectorKey));
8625
+ if (isRoot && select === void 0) {
8626
+ return sortedUnique4(artifacts.map(artifactSelectorKey));
7619
8627
  }
7620
8628
  const explicit = normalizeArtifactSelectors(select) ?? [];
7621
8629
  const available = new Set(artifacts.map(artifactSelectorKey));
@@ -7624,7 +8632,7 @@ function computeSelectedSelectors(artifacts, select, isRoot, chain) {
7624
8632
  throw new Error(`Selected artifact not found in package: ${missing.join(", ")} (${chain.join(" -> ")})`);
7625
8633
  }
7626
8634
  const required = artifacts.filter((artifact) => artifact.required && artifact.type !== "fragments").map(artifactSelectorKey);
7627
- return sortedUnique3([...explicit, ...required]);
8635
+ return sortedUnique4([...explicit, ...required]);
7628
8636
  }
7629
8637
  function materializeRawNode(state) {
7630
8638
  refreshNode(state);
@@ -7637,11 +8645,11 @@ function materializeRawNode(state) {
7637
8645
  };
7638
8646
  }
7639
8647
  function refreshNode(state) {
7640
- state.node.requiredBy = sortedUnique3([...state.requiredBy]);
7641
- state.node.selected = sortedUnique3([...state.selected]);
8648
+ state.node.requiredBy = sortedUnique4([...state.requiredBy]);
8649
+ state.node.selected = sortedUnique4([...state.selected]);
7642
8650
  const selectionReasons = {};
7643
8651
  for (const [selector, reasons] of [...state.selectionReasons.entries()].sort(([a], [b]) => a.localeCompare(b))) {
7644
- selectionReasons[selector] = sortedUnique3([...reasons]);
8652
+ selectionReasons[selector] = sortedUnique4([...reasons]);
7645
8653
  }
7646
8654
  state.node.selectionReasons = Object.keys(selectionReasons).length > 0 ? selectionReasons : void 0;
7647
8655
  }
@@ -7670,10 +8678,10 @@ function detectDirectCollisions(nodes) {
7670
8678
  }
7671
8679
  }
7672
8680
  function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
7673
- 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);
7674
8682
  return `${name}@${version}+${digest}`;
7675
8683
  }
7676
- function sortedUnique3(values) {
8684
+ function sortedUnique4(values) {
7677
8685
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
7678
8686
  }
7679
8687
  async function mapLimit(items, limit, fn) {
@@ -7692,7 +8700,7 @@ async function mapLimit(items, limit, fn) {
7692
8700
  // src/lifecycle/customization.ts
7693
8701
  async function remember(workspaceRoot, runtime, text) {
7694
8702
  const overlayPath = join32(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
7695
- await mkdir17(dirname24(overlayPath), { recursive: true });
8703
+ await mkdir18(dirname25(overlayPath), { recursive: true });
7696
8704
  await appendFile(overlayPath, `${text.trim()}
7697
8705
  `, "utf8");
7698
8706
  return { overlayPath };
@@ -7716,7 +8724,7 @@ async function ejectArtifact(workspaceRoot, item) {
7716
8724
  }
7717
8725
  const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
7718
8726
  const ejectedPath = join32(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
7719
- await mkdir17(dirname24(ejectedPath), { recursive: true });
8727
+ await mkdir18(dirname25(ejectedPath), { recursive: true });
7720
8728
  await rm9(ejectedPath, { recursive: true, force: true });
7721
8729
  await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
7722
8730
  return {
@@ -7808,20 +8816,40 @@ function ejectCommands(candidates, parsed) {
7808
8816
  import { rm as rm10 } from "fs/promises";
7809
8817
 
7810
8818
  // src/lifecycle/source-plan.ts
7811
- import { createHash as createHash8 } from "crypto";
7812
- import { mkdir as mkdir19 } from "fs/promises";
7813
- 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";
7814
8822
 
7815
8823
  // src/resolve/graph-diff.ts
7816
8824
  function diffGraphLocks(previous, next) {
7817
8825
  if (!previous) return [];
7818
8826
  return [
8827
+ ...diffRoots(previous.canonical.roots, next.canonical.roots),
7819
8828
  ...diffNodes(previous.canonical.nodes, next.canonical.nodes),
7820
8829
  ...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
7821
8830
  ...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
7822
8831
  ...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
7823
8832
  ];
7824
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
+ }
7825
8853
  function diffNodes(previous, next) {
7826
8854
  const previousById = new Map(previous.map((node) => [node.id, node]));
7827
8855
  const nextById = new Map(next.map((node) => [node.id, node]));
@@ -7929,10 +8957,30 @@ function formatOverride(decision) {
7929
8957
  function short(hash) {
7930
8958
  return hash.slice(0, 12);
7931
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
+ }
7932
8980
 
7933
8981
  // src/resolve/render.ts
7934
- import { createHash as createHash7 } from "crypto";
7935
- 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";
7936
8984
  import { tmpdir as tmpdir6 } from "os";
7937
8985
  import { join as join33 } from "path";
7938
8986
  async function renderGraphForTarget(graph, targetContext = {}) {
@@ -7986,7 +9034,7 @@ async function renderGraphForTarget(graph, targetContext = {}) {
7986
9034
  if (sourceContent === void 0) {
7987
9035
  throw new Error(`OpenPack include source is not a file: ${edge.to}:${request.selector}`);
7988
9036
  }
7989
- 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);
7990
9038
  const includeEdge = {
7991
9039
  fromNodeId: request.fromNodeId,
7992
9040
  alias: request.alias,
@@ -8057,7 +9105,7 @@ async function artifactContentMap(artifacts) {
8057
9105
  const out = /* @__PURE__ */ new Map();
8058
9106
  for (const artifact of artifacts) {
8059
9107
  if (artifact.kind !== "file") continue;
8060
- 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"));
8061
9109
  }
8062
9110
  return out;
8063
9111
  }
@@ -8070,8 +9118,8 @@ function filterArtifactsByRuntime2(artifacts, adapterName, selectedSet) {
8070
9118
  return false;
8071
9119
  });
8072
9120
  }
8073
- function sha2562(content) {
8074
- return createHash7("sha256").update(content).digest("hex");
9121
+ function sha2563(content) {
9122
+ return createHash9("sha256").update(content).digest("hex");
8075
9123
  }
8076
9124
  function assignInstallNames(graph, artifacts) {
8077
9125
  const aliases = workspaceAliases(graph);
@@ -8320,19 +9368,19 @@ function lockArtifactFor(artifact) {
8320
9368
  }
8321
9369
 
8322
9370
  // src/lifecycle/trust.ts
8323
- import { mkdir as mkdir18, readFile as readFile24 } from "fs/promises";
9371
+ import { mkdir as mkdir19, readFile as readFile26 } from "fs/promises";
8324
9372
  import { homedir as homedir7 } from "os";
8325
- import { dirname as dirname25, join as join34 } from "path";
8326
- import { z as z8 } from "zod";
8327
- var trustStoreSchema = z8.object({
8328
- version: z8.literal(1),
8329
- 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([])
8330
9378
  });
8331
9379
  function normalizeTrustPolicy(policy) {
8332
9380
  return {
8333
- allow: sortedUnique4(policy?.allow ?? []),
8334
- acceptedSources: sortedUnique4(policy?.acceptedSources ?? []),
8335
- denyArtifactTypes: sortedUnique4(policy?.denyArtifactTypes ?? []),
9381
+ allow: sortedUnique5(policy?.allow ?? []),
9382
+ acceptedSources: sortedUnique5(policy?.acceptedSources ?? []),
9383
+ denyArtifactTypes: sortedUnique5(policy?.denyArtifactTypes ?? []),
8336
9384
  requireReviewForTransitive: policy?.requireReviewForTransitive ?? true
8337
9385
  };
8338
9386
  }
@@ -8369,10 +9417,10 @@ async function readTrustedSources(_workspaceRoot, storePath = defaultTrustStoreP
8369
9417
  return (await readTrustStore(storePath)).acceptedSources;
8370
9418
  }
8371
9419
  async function rememberTrustedSources(_workspaceRoot, sources, storePath = defaultTrustStorePath()) {
8372
- const unique = sortedUnique4(sources);
9420
+ const unique = sortedUnique5(sources);
8373
9421
  if (unique.length === 0) return [];
8374
9422
  const store = await readTrustStore(storePath);
8375
- const acceptedSources = sortedUnique4([...store.acceptedSources, ...unique]);
9423
+ const acceptedSources = sortedUnique5([...store.acceptedSources, ...unique]);
8376
9424
  await writeTrustStore(storePath, { version: 1, acceptedSources });
8377
9425
  return unique.filter((source) => !store.acceptedSources.includes(source));
8378
9426
  }
@@ -8391,21 +9439,155 @@ function matchesGlob2(value, pattern) {
8391
9439
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
8392
9440
  return new RegExp(`^${escaped}$`).test(value);
8393
9441
  }
8394
- function sortedUnique4(values) {
9442
+ function sortedUnique5(values) {
8395
9443
  return [...new Set(values)].sort((a, b) => a.localeCompare(b));
8396
9444
  }
8397
9445
  async function readTrustStore(path) {
8398
9446
  if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
8399
- return trustStoreSchema.parse(JSON.parse(await readFile24(path, "utf8")));
9447
+ return trustStoreSchema.parse(JSON.parse(await readFile26(path, "utf8")));
8400
9448
  }
8401
9449
  async function writeTrustStore(path, store) {
8402
- await mkdir18(dirname25(path), { recursive: true });
9450
+ await mkdir19(dirname26(path), { recursive: true });
8403
9451
  await writeJsonAtomic(path, trustStoreSchema.parse(store));
8404
9452
  }
8405
9453
  function defaultTrustStorePath() {
8406
9454
  return process.env.AGENTWHEEL_TRUST_STORE ?? join34(homedir7(), ".agentwheel", "trust.json");
8407
9455
  }
8408
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
+
8409
9591
  // src/lifecycle/source-plan.ts
8410
9592
  async function createGraphSourcePlan(options) {
8411
9593
  if (options.roots.length === 0) {
@@ -8446,6 +9628,7 @@ async function createGraphSourcePlan(options) {
8446
9628
  noDeps: options.noDeps,
8447
9629
  includeSuggestions: options.includeSuggestions,
8448
9630
  suggestionAliases: options.suggestionAliases,
9631
+ dependencyUpdateSelectors: options.dependencyUpdateSelectors,
8449
9632
  lockedResolution: options.lockedResolution,
8450
9633
  frozenLock: lockMode,
8451
9634
  offline: options.offline,
@@ -8480,7 +9663,7 @@ async function createGraphSourcePlan(options) {
8480
9663
  const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
8481
9664
  baseRevision: manifest?.revision ?? null,
8482
9665
  graphLockDigest,
8483
- workspaceOwner: workspaceOwnerId(workspaceRoot),
9666
+ workspaceOwner: workspaceOwnerForRoot(workspaceRoot),
8484
9667
  installationType: resolvedInstallationType,
8485
9668
  stateKey,
8486
9669
  forceDrift: options.forceDrift,
@@ -8502,8 +9685,8 @@ async function createGraphSourcePlan(options) {
8502
9685
  recoveredPendingApply
8503
9686
  };
8504
9687
  }
8505
- function graphLockPathForTarget(workspaceRoot, targetKey, adapter, targetFingerprintParts2) {
8506
- return pathForGraphLock(workspaceRoot, targetKey, adapter, computeTargetFingerprint(targetFingerprintParts2));
9688
+ function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
9689
+ return pathForGraphLock(workspaceRoot, targetKey2, adapter, computeTargetFingerprint(targetFingerprintParts2));
8507
9690
  }
8508
9691
  function desiredArtifactsFromGraphBundle(bundle) {
8509
9692
  return bundle.artifacts.map((artifact) => desiredArtifactFromResolved(artifact));
@@ -8545,17 +9728,14 @@ async function readExistingGraphLock(path) {
8545
9728
  if (!await pathExists(path)) return void 0;
8546
9729
  return readGraphLock(path);
8547
9730
  }
8548
- function pathForGraphLock(workspaceRoot, targetKey, adapter, targetFingerprint) {
8549
- 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`);
8550
9733
  }
8551
9734
  function sanitizePathSegment(value) {
8552
9735
  return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
8553
9736
  }
8554
9737
  function digestGraphLock(lock) {
8555
- return createHash8("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
8556
- }
8557
- function workspaceOwnerId(workspaceRoot) {
8558
- return `workspace-root:${resolve16(workspaceRoot)}`;
9738
+ return createHash10("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
8559
9739
  }
8560
9740
  function assertFrozenGraph(previousLock, graph, frozen, label) {
8561
9741
  if (!frozen) return;
@@ -8577,11 +9757,51 @@ function assertFrozenGraph(previousLock, graph, frozen, label) {
8577
9757
  mismatches.push(`${node.id}: sourceHash ${locked.sourceHash} -> ${node.sourceHash}`);
8578
9758
  }
8579
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
+ }
8580
9780
  if (mismatches.length > 0) {
8581
9781
  throw new Error(`${label} would change graph nodes:
8582
9782
  ${mismatches.map((item) => `- ${item}`).join("\n")}`);
8583
9783
  }
8584
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
+ }
8585
9805
  async function assertTrusted(sources, options) {
8586
9806
  if (sources.length === 0) return;
8587
9807
  if (options.readOnly === true) {
@@ -8609,8 +9829,39 @@ Type yes to continue: `);
8609
9829
  ${sources.map((source) => `- ${source}`).join("\n")}`);
8610
9830
  }
8611
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
+
8612
9863
  // src/runtime/target.ts
8613
- 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";
8614
9865
  var runtimeMarkers = [
8615
9866
  { adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
8616
9867
  { adapter: "claude", dirs: [".claude"] },
@@ -8678,6 +9929,9 @@ function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installatio
8678
9929
  ...target,
8679
9930
  adapterConfig: runtime.adapterConfig,
8680
9931
  adapterModule: runtime.adapterModule,
9932
+ executePlugins: runtime.executePlugins,
9933
+ reloadRuntimes: runtime.reloadRuntimes,
9934
+ reloadCommands: runtime.reloadCommands ?? target.reloadCommands,
8681
9935
  targetKey: runtime.agent,
8682
9936
  source: "profile"
8683
9937
  };
@@ -8689,6 +9943,9 @@ function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installatio
8689
9943
  installationType: installationType ?? runtime.installationType,
8690
9944
  targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot,
8691
9945
  workspaceRoot,
9946
+ executePlugins: runtime.executePlugins,
9947
+ reloadRuntimes: runtime.reloadRuntimes,
9948
+ reloadCommands: runtime.reloadCommands,
8692
9949
  transport: "local",
8693
9950
  source: "profile"
8694
9951
  };
@@ -8722,7 +9979,7 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
8722
9979
  if (adapterFilter && marker.adapter !== adapterFilter) continue;
8723
9980
  for (const dir of marker.dirs) {
8724
9981
  if (basename20(root) === dir) {
8725
- matches.push({ adapter: marker.adapter, targetRoot: dirname27(root) });
9982
+ matches.push({ adapter: marker.adapter, targetRoot: dirname28(root) });
8726
9983
  } else if (await pathExists(join36(root, dir))) {
8727
9984
  matches.push({ adapter: marker.adapter, targetRoot: root });
8728
9985
  }
@@ -8744,6 +10001,7 @@ function targetFromAgent(name, config, workspaceRoot, installationType) {
8744
10001
  installationType: installationType ?? agent.installationType,
8745
10002
  targetRoot: agent.transport === "ssh" ? agent.root : resolveConfigPath(agent.root, workspaceRoot),
8746
10003
  workspaceRoot,
10004
+ reloadCommands: agent.reloadCommands,
8747
10005
  transport: agent.transport,
8748
10006
  ssh: agent.transport === "ssh" ? {
8749
10007
  host: agent.host ?? "",
@@ -8764,7 +10022,7 @@ function dedupeTargets(matches) {
8764
10022
  function runtimeScanRoot(request) {
8765
10023
  const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
8766
10024
  if (request.targetRoot) return root;
8767
- 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;
8768
10026
  }
8769
10027
 
8770
10028
  // src/lifecycle/profile.ts
@@ -8779,6 +10037,10 @@ async function syncProfile(options) {
8779
10037
  throw new Error("Profile sync needs a source argument or configured packages.");
8780
10038
  }
8781
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
+ }
8782
10044
  for (const runtime of profile.runtimes) {
8783
10045
  const target = resolveProfileRuntimeTarget(runtime, config, options.workspaceRoot, options.installationType);
8784
10046
  const transport = transportForTarget(target);
@@ -8792,14 +10054,14 @@ async function syncProfile(options) {
8792
10054
  });
8793
10055
  const installationType = target.installationType ?? defaultInstallationType;
8794
10056
  resolveInstallationTypeForAdapter(adapter, installationType);
8795
- const selected = normalizeArtifactSelectors(options.select, options.skills);
8796
10057
  const graphPlan = await createGraphSourcePlan({
8797
10058
  roots: packages.map((pkg) => ({
8798
10059
  rootId: pkg.name,
8799
10060
  source: pkg.source,
8800
10061
  mode: options.mode ?? pkg.mode,
8801
10062
  ref: pkg.requestedRef,
8802
- select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
10063
+ select: pkg.selection ? void 0 : selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
10064
+ selection: pkg.selection,
8803
10065
  aliases: pkg.aliases,
8804
10066
  overrides: pkg.overrides,
8805
10067
  includeSuggestions: options.includeSuggestions === true || pkg.withSuggestions === true,
@@ -8838,21 +10100,31 @@ async function syncProfile(options) {
8838
10100
  replaceConflict: options.replaceConflict
8839
10101
  });
8840
10102
  try {
8841
- results.push({
10103
+ const result = {
8842
10104
  runtime: adapter.name,
8843
10105
  targetRoot: installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh"),
8844
10106
  transport: transport.kind,
8845
10107
  packageName: packages.map((pkg) => pkg.name).join(","),
8846
- plan: graphPlan.plan
8847
- });
10108
+ plan: graphPlan.plan,
10109
+ graphPlan,
10110
+ graphLockDigest: graphPlan.graphLockDigest,
10111
+ warnings: graphPlan.warnings
10112
+ };
8848
10113
  if (!options.dryRun) {
10114
+ const executePlugins = runtime.executePlugins ?? options.executePlugins;
8849
10115
  await applyCombinedInstallPlan(graphPlan.plan, {
8850
- executePlugins: runtime.executePlugins ?? options.executePlugins,
10116
+ executePlugins,
8851
10117
  transport,
8852
10118
  graphLockDigest: graphPlan.graphLockDigest,
8853
10119
  graphLock: { path: graphPlan.graphLockPath, lock: graphPlan.bundle.graphLock }
8854
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;
8855
10126
  }
10127
+ results.push(result);
8856
10128
  } finally {
8857
10129
  await rm10(graphPlan.bundle.root, { recursive: true, force: true });
8858
10130
  }
@@ -9048,9 +10320,9 @@ function shellQuoteArg(value) {
9048
10320
  }
9049
10321
 
9050
10322
  // src/cli/update-check.ts
9051
- 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";
9052
10324
  import { homedir as homedir8 } from "os";
9053
- import { dirname as dirname28, join as join37 } from "path";
10325
+ import { dirname as dirname29, join as join37 } from "path";
9054
10326
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
9055
10327
  var DEFAULT_TIMEOUT_MS = 300;
9056
10328
  var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
@@ -9095,7 +10367,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
9095
10367
  }
9096
10368
  async function readCache(path) {
9097
10369
  try {
9098
- const parsed = JSON.parse(await readFile25(path, "utf8"));
10370
+ const parsed = JSON.parse(await readFile27(path, "utf8"));
9099
10371
  if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
9100
10372
  return { checkedAt: parsed.checkedAt, latest: parsed.latest };
9101
10373
  } catch {
@@ -9103,8 +10375,8 @@ async function readCache(path) {
9103
10375
  }
9104
10376
  }
9105
10377
  async function writeCache(path, cache) {
9106
- await mkdir20(dirname28(path), { recursive: true });
9107
- await writeFile18(path, `${JSON.stringify(cache, null, 2)}
10378
+ await mkdir21(dirname29(path), { recursive: true });
10379
+ await writeFile19(path, `${JSON.stringify(cache, null, 2)}
9108
10380
  `, "utf8");
9109
10381
  }
9110
10382
  function warnIfNewer(latest, current, stderr = process.stderr) {
@@ -9267,9 +10539,9 @@ function isCrossPackageSelector(value) {
9267
10539
  }
9268
10540
 
9269
10541
  // src/model/package-migrate.ts
9270
- 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";
9271
10543
  import { join as join39, resolve as resolve19 } from "path";
9272
- import { applyEdits, modify, parse as parse4 } from "jsonc-parser";
10544
+ import { applyEdits, modify, parse as parse5 } from "jsonc-parser";
9273
10545
  async function migratePackageManifest(root) {
9274
10546
  const packageRoot = resolve19(root);
9275
10547
  for (const name of openPackManifestNames) {
@@ -9285,10 +10557,10 @@ async function migratePackageManifest(root) {
9285
10557
  const from = join39(packageRoot, legacyName);
9286
10558
  const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
9287
10559
  const to = join39(packageRoot, toName);
9288
- const content = await readFile26(from, "utf8");
10560
+ const content = await readFile28(from, "utf8");
9289
10561
  const updated = updateSchemaVersion(content);
9290
10562
  await rename4(from, to);
9291
- await writeFile19(to, updated, "utf8");
10563
+ await writeFile20(to, updated, "utf8");
9292
10564
  return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
9293
10565
  }
9294
10566
  async function firstExistingLegacyManifest(root) {
@@ -9299,7 +10571,7 @@ async function firstExistingLegacyManifest(root) {
9299
10571
  }
9300
10572
  function updateSchemaVersion(content) {
9301
10573
  const errors = [];
9302
- parse4(content, errors, { allowTrailingComma: true, disallowComments: false });
10574
+ parse5(content, errors, { allowTrailingComma: true, disallowComments: false });
9303
10575
  if (errors.length > 0) {
9304
10576
  throw new Error("Cannot migrate invalid JSON/JSONC package manifest.");
9305
10577
  }
@@ -9311,11 +10583,11 @@ function updateSchemaVersion(content) {
9311
10583
 
9312
10584
  // src/cli/version.ts
9313
10585
  import { readFileSync } from "fs";
9314
- import { dirname as dirname29, join as join40 } from "path";
10586
+ import { dirname as dirname30, join as join40 } from "path";
9315
10587
  import { fileURLToPath as fileURLToPath2 } from "url";
9316
10588
  var FALLBACK_VERSION = "0.0.0";
9317
10589
  function resolveCliVersion() {
9318
- let dir = dirname29(fileURLToPath2(import.meta.url));
10590
+ let dir = dirname30(fileURLToPath2(import.meta.url));
9319
10591
  while (true) {
9320
10592
  try {
9321
10593
  const pkg = JSON.parse(readFileSync(join40(dir, "package.json"), "utf8"));
@@ -9324,7 +10596,7 @@ function resolveCliVersion() {
9324
10596
  }
9325
10597
  } catch {
9326
10598
  }
9327
- const parent = dirname29(dir);
10599
+ const parent = dirname30(dir);
9328
10600
  if (parent === dir) return FALLBACK_VERSION;
9329
10601
  dir = parent;
9330
10602
  }
@@ -9334,6 +10606,7 @@ function resolveCliVersion() {
9334
10606
  var CLI_VERSION = resolveCliVersion();
9335
10607
  var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
9336
10608
  var COMPANION_SKILL_NAME = "agentwheel";
10609
+ var planOutputFormats = ["human", "json", "mermaid", "html"];
9337
10610
  var program = new Command();
9338
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", `
9339
10612
 
@@ -9393,17 +10666,33 @@ program.command("scan").description("scan a package source for validation findin
9393
10666
  }
9394
10667
  if (!result.ok) process.exitCode = 1;
9395
10668
  });
9396
- 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) => {
9397
10670
  await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
9398
10671
  });
9399
- 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) => {
9400
10673
  await runInstallCommand(source, options, { apply: !options.dryRun });
9401
10674
  });
9402
- 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) => {
9403
10685
  console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
9404
10686
  await runInstallCommand(source, options, { apply: !options.dryRun });
9405
10687
  });
9406
- 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
+ }
9407
10696
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
9408
10697
  const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
9409
10698
  for (const target of targets) {
@@ -9519,6 +10808,45 @@ program.command("remember").description("append text to the local instructions o
9519
10808
  console.log(`Remembered in ${result.overlayPath}.`);
9520
10809
  console.log(nextInstallNudge());
9521
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
+ });
9522
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) => {
9523
10851
  const targetRoot = normalizeTargetRoot(options.targetRoot);
9524
10852
  const result = await ejectArtifact(targetRoot, item);
@@ -9612,7 +10940,15 @@ program.command("doctor").description("check agentwheel runtime setup and compan
9612
10940
  });
9613
10941
  async function runInstallCommand(nameOrSource, options, behavior) {
9614
10942
  const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
9615
- 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) {
9616
10952
  const target = await resolveRuntimeTarget({
9617
10953
  targetRoot: normalizedOptions.targetRoot,
9618
10954
  adapter: normalizedOptions.adapter,
@@ -9621,7 +10957,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9621
10957
  });
9622
10958
  const results = await syncProfile({
9623
10959
  workspaceRoot: target.workspaceRoot,
9624
- profile: options.profile,
10960
+ profile: normalizedOptions.profile,
9625
10961
  source: nameOrSource,
9626
10962
  driver: normalizedOptions.driver,
9627
10963
  mode: normalizedOptions.mode,
@@ -9629,6 +10965,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9629
10965
  installationType: normalizedOptions.installationType,
9630
10966
  dryRun: !behavior.apply,
9631
10967
  executePlugins: normalizedOptions.executePlugins,
10968
+ reloadRuntimes: shouldReloadRuntimes(normalizedOptions),
9632
10969
  allowAdapterCode: normalizedOptions.allowAdapterCode,
9633
10970
  forceDrift: normalizedOptions.forceDrift,
9634
10971
  forceConflict: normalizedOptions.forceConflict,
@@ -9648,9 +10985,12 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9648
10985
  for (const result of results) {
9649
10986
  console.log(`Profile ${normalizedOptions.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
9650
10987
  console.log(formatPlan(result.plan));
10988
+ if (result.reloaded) console.log(`Reloaded runtime via ${result.reloadCommandSummary}.`);
9651
10989
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
9652
10990
  }
9653
- if (behavior.apply) console.log("Applied.");
10991
+ if (behavior.apply) {
10992
+ console.log("Applied.");
10993
+ }
9654
10994
  return;
9655
10995
  }
9656
10996
  const targets = await resolveCliTargets(normalizedOptions);
@@ -9674,16 +11014,23 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9674
11014
  throw teachingInstallError(nameOrSource, error);
9675
11015
  }
9676
11016
  }
9677
- 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" })) {
9678
11018
  console.log(formatGraphPlan(result));
9679
11019
  if (behavior.apply) {
11020
+ const transport = transportForTarget(target);
11021
+ const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
9680
11022
  await applyCombinedInstallPlan(result.plan, {
9681
- executePlugins: targetOptions.executePlugins,
9682
- transport: transportForTarget(target),
11023
+ executePlugins,
11024
+ transport,
9683
11025
  graphLockDigest: result.graphLockDigest,
9684
11026
  graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
9685
11027
  });
11028
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
11029
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
11030
+ executePlugins
11031
+ });
9686
11032
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
11033
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
9687
11034
  }
9688
11035
  await rm11(result.bundle.root, { recursive: true, force: true });
9689
11036
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
@@ -9693,6 +11040,111 @@ async function runInstallCommand(nameOrSource, options, behavior) {
9693
11040
  }
9694
11041
  }
9695
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
+ }
9696
11148
  async function packageEntryFromSource(source, targetRoot, options) {
9697
11149
  const lockMode = options.frozenLock === true || options.offline === true;
9698
11150
  const resolvedInput = await resolvePackageSource(source, targetRoot, { offline: lockMode });
@@ -9706,7 +11158,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
9706
11158
  adapterModule: options.adapterModule,
9707
11159
  allowAdapterCode: options.allowAdapterCode,
9708
11160
  baseDir: targetRoot,
9709
- warn: (message) => console.warn(message)
11161
+ warn: options.warn ?? ((message) => console.warn(message))
9710
11162
  });
9711
11163
  const bundle = await stageSource(driver, resolvedSource, {
9712
11164
  workspaceRoot: targetRoot,
@@ -9748,6 +11200,9 @@ function findConfiguredPackageForTarget(packages, value, options, target) {
9748
11200
  function noDepsFromOptions(options) {
9749
11201
  return options.noDeps === true || options.deps === false;
9750
11202
  }
11203
+ function shouldReloadRuntimes(options) {
11204
+ return options.reloadRuntimes === true || options.restartRuntimes === true;
11205
+ }
9751
11206
  function packageEntryWithAdapterSuffix(entry) {
9752
11207
  const suffix = `-${entry.adapter}`;
9753
11208
  return entry.name.endsWith(suffix) ? entry : { ...entry, name: `${entry.name}${suffix}` };
@@ -9849,7 +11304,7 @@ async function resolveAdapterForTarget(target, options) {
9849
11304
  adapterModule: adapterOptions.adapterModule,
9850
11305
  allowAdapterCode: adapterOptions.allowAdapterCode,
9851
11306
  baseDir: target.workspaceRoot,
9852
- warn: (message) => console.warn(message)
11307
+ warn: options.warn ?? ((message) => console.warn(message))
9853
11308
  });
9854
11309
  }
9855
11310
  function adapterOptionsForTarget(target, options) {
@@ -9868,13 +11323,20 @@ async function runConfiguredGraphPackages(target, options, behavior) {
9868
11323
  console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
9869
11324
  console.log(formatGraphPlan(result));
9870
11325
  if (!options.dryRun) {
11326
+ const transport = transportForTarget(target);
11327
+ const executePlugins = target.executePlugins ?? options.executePlugins;
9871
11328
  await applyCombinedInstallPlan(result.plan, {
9872
- executePlugins: options.executePlugins,
9873
- transport: transportForTarget(target),
11329
+ executePlugins,
11330
+ transport,
9874
11331
  graphLockDigest: result.graphLockDigest,
9875
11332
  graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
9876
11333
  });
11334
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
11335
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
11336
+ executePlugins
11337
+ });
9877
11338
  console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
11339
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
9878
11340
  }
9879
11341
  await rm11(result.bundle.root, { recursive: true, force: true });
9880
11342
  if (result.plan.hasBlockingChanges) process.exitCode = 1;
@@ -9885,6 +11347,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9885
11347
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
9886
11348
  const groups = /* @__PURE__ */ new Map();
9887
11349
  const selectedArtifacts = selectedArtifactsFromOptions(targetOptions);
11350
+ const dependencyUpdateSelectors = sortedUniqueValues(targetOptions.dependency ?? []);
11351
+ const scopedDependencyUpdate = behavior.mode === "update" && dependencyUpdateSelectors.length > 0;
9888
11352
  const scopedPackage = targetOptions.scope ? findConfiguredPackage(config.packages, targetOptions.scope) : void 0;
9889
11353
  const scopedRootId = scopedPackage?.name ?? (source ? targetOptions.scope : void 0);
9890
11354
  if (targetOptions.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${targetOptions.scope}`);
@@ -9918,39 +11382,65 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9918
11382
  groups.set(key, group);
9919
11383
  }
9920
11384
  if (groups.size === 0) {
9921
- 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
+ }
9922
11390
  return [];
9923
11391
  }
9924
11392
  const results = [];
9925
11393
  for (const group of groups.values()) {
9926
- const adapter = await resolveAdapterForTarget(group.target, group.adapterOptions);
11394
+ const adapter = await resolveAdapterForTarget(group.target, { ...group.adapterOptions, warn: targetOptions.warn });
9927
11395
  const transport = transportForTarget(group.target);
9928
11396
  const allPackages = [...group.packages, ...group.extraPackages];
9929
11397
  const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
9930
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
+ }
9931
11417
  const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
9932
11418
  const roots = [
9933
11419
  ...allPackages.map((pkg) => {
9934
- 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));
9935
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
+ }
9936
11425
  return {
9937
11426
  rootId: pkg.name,
9938
11427
  source: pkg.source,
9939
11428
  mode: pkg.mode,
9940
11429
  ref: pkg.requestedRef,
9941
- 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,
9942
11432
  aliases: pkg.aliases,
9943
11433
  overrides: pkg.overrides,
9944
11434
  includeSuggestions: targetOptions.withSuggestions === true || pkg.withSuggestions === true,
9945
11435
  suggestionAliases: packageSuggestionAliases(pkg, targetOptions),
9946
- useLock: behavior.mode === "install" ? true : !updateThisPackage
11436
+ useLock: behavior.mode === "install" ? true : scopedDependencyUpdate ? !dependencyUpdateRootNames.has(pkg.name) : !updateThisPackage
9947
11437
  };
9948
11438
  }),
9949
11439
  ...group.extraRoots
9950
11440
  ];
9951
11441
  if (behavior.mode === "update") {
9952
11442
  const changed = roots.filter((root) => root.useLock === false);
9953
- if (changed.length === 0) {
11443
+ if (changed.length === 0 && !scopedDependencyUpdate) {
9954
11444
  const label = targetOptions.scope ? ` ${targetOptions.scope}` : "";
9955
11445
  console.log(`No tracking packages to update${label}.`);
9956
11446
  continue;
@@ -9969,7 +11459,8 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9969
11459
  noDeps: noDepsFromOptions(targetOptions),
9970
11460
  includeSuggestions: targetOptions.withSuggestions,
9971
11461
  suggestionAliases: suggestionAliasesFromOptions(targetOptions),
9972
- lockedResolution: behavior.mode === "install",
11462
+ lockedResolution: behavior.mode === "install" || scopedDependencyUpdate,
11463
+ dependencyUpdateSelectors,
9973
11464
  frozenLock: targetOptions.frozenLock,
9974
11465
  offline: targetOptions.offline,
9975
11466
  yes: targetOptions.yes,
@@ -9980,16 +11471,179 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
9980
11471
  forceConflict: targetOptions.forceConflict,
9981
11472
  replaceConflict: targetOptions.replaceConflict
9982
11473
  });
9983
- if (behavior.mode === "install" && scopedRootId) {
11474
+ if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
9984
11475
  const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
9985
11476
  const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
9986
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));
9987
11483
  } else {
9988
11484
  results.push(result);
9989
11485
  }
9990
11486
  }
9991
11487
  return results;
9992
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
+ }
9993
11647
  function scopeInstallPlanToRoot(result, rootId, manifest) {
9994
11648
  const scopedOwners = scopedGraphOwnerKeys(result, rootId);
9995
11649
  const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
@@ -10003,7 +11657,7 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
10003
11657
  continue;
10004
11658
  }
10005
11659
  const entry = manifestByPath.get(operation.relativeDestPath);
10006
- const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, rootId);
11660
+ const transformed = transformOutOfScopeOperation(operation, entry, result.plan.targetRoot, `scoped install ${rootId}`);
10007
11661
  for (const scopedOperation of transformed) {
10008
11662
  if (preservedPaths.has(scopedOperation.relativeDestPath)) continue;
10009
11663
  preservedPaths.add(scopedOperation.relativeDestPath);
@@ -10013,7 +11667,7 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
10013
11667
  for (const entry of manifest?.entries ?? []) {
10014
11668
  if (plannedPaths.has(entry.path) || preservedPaths.has(entry.path) || entryBelongsToScopedRoot(entry, scopedOwners)) continue;
10015
11669
  preservedPaths.add(entry.path);
10016
- operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, rootId));
11670
+ operations.push(keepManifestEntryOperation(entry, result.plan.targetRoot, `scoped install ${rootId}`));
10017
11671
  }
10018
11672
  return {
10019
11673
  ...result,
@@ -10024,16 +11678,16 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
10024
11678
  }
10025
11679
  };
10026
11680
  }
10027
- function transformOutOfScopeOperation(operation, entry, targetRoot, rootId) {
11681
+ function transformOutOfScopeOperation(operation, entry, targetRoot, scopeDescription) {
10028
11682
  if (operation.action === "skip") return [operation];
10029
11683
  if (operation.action === "update" || operation.action === "drift") {
10030
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId, operation, { freshMetadata: true })] : [];
11684
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription, operation, { freshMetadata: true })] : [];
10031
11685
  }
10032
11686
  if (operation.action === "remove") {
10033
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
11687
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription)] : [];
10034
11688
  }
10035
11689
  if (operation.action === "plugin" || operation.action === "program") {
10036
- return entry ? [keepManifestEntryOperation(entry, targetRoot, rootId)] : [];
11690
+ return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription)] : [];
10037
11691
  }
10038
11692
  return [];
10039
11693
  }
@@ -10061,7 +11715,7 @@ function entryBelongsToScopedRoot(entry, scopedOwners) {
10061
11715
  const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
10062
11716
  return owners.some((owner) => scopedOwners.has(owner));
10063
11717
  }
10064
- function keepManifestEntryOperation(entry, targetRoot, rootId, operation, options = {}) {
11718
+ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operation, options = {}) {
10065
11719
  const owners = "owners" in entry ? entry.owners : [entry.packageName ?? "legacy"];
10066
11720
  const fresh = options.freshMetadata ? operation : void 0;
10067
11721
  return {
@@ -10074,7 +11728,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
10074
11728
  desiredHash: entry.sourceHash,
10075
11729
  currentHash: operation?.currentHash ?? entry.hash,
10076
11730
  manifestHash: entry.hash,
10077
- reason: `preserved outside scoped install ${rootId}`,
11731
+ reason: `preserved outside ${scopeDescription}`,
10078
11732
  channel: entry.channel,
10079
11733
  packageName: entry.packageName,
10080
11734
  semanticCommand: entry.semanticCommand,
@@ -10132,7 +11786,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
10132
11786
  source: pkg2.source,
10133
11787
  mode: pkg2.mode,
10134
11788
  ref: pkg2.requestedRef,
10135
- select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
11789
+ select: pkg2.selection ? void 0 : normalizeArtifactSelectors(pkg2.select, pkg2.skills),
11790
+ selection: pkg2.selection,
10136
11791
  aliases: pkg2.aliases,
10137
11792
  overrides: pkg2.overrides,
10138
11793
  includeSuggestions: options.withSuggestions === true || pkg2.withSuggestions === true,
@@ -10463,6 +12118,12 @@ function collectSuggestionOption(value, previous) {
10463
12118
  function collectTrustOption(value, previous) {
10464
12119
  return [...previous, value];
10465
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
+ }
10466
12127
  function collectOverrideOption(value, previous) {
10467
12128
  return [...previous, ...splitSelectorList(value)];
10468
12129
  }
@@ -10564,9 +12225,9 @@ function filterUninstallPlanBySelection(plan, selected) {
10564
12225
  };
10565
12226
  }
10566
12227
  async function initPackage(root) {
10567
- await mkdir21(join41(root, "instructions"), { recursive: true });
10568
- await mkdir21(join41(root, "rules"), { recursive: true });
10569
- 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 });
10570
12231
  const manifestPath = join41(root, "openpack.json");
10571
12232
  const manifest = {
10572
12233
  schemaVersion: 2,
@@ -10578,12 +12239,12 @@ async function initPackage(root) {
10578
12239
  { type: "skills", path: "skills" }
10579
12240
  ]
10580
12241
  };
10581
- await writeFile20(manifestPath, `${JSON.stringify(manifest, null, 2)}
12242
+ await writeFile21(manifestPath, `${JSON.stringify(manifest, null, 2)}
10582
12243
  `, "utf8");
10583
- await writeFile20(join41(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
12244
+ await writeFile21(join41(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
10584
12245
  }
10585
12246
  async function defaultBootstrapPackage(_root) {
10586
- const packageRoot = await findAgentwheelPackageRoot(dirname30(fileURLToPath3(import.meta.url)));
12247
+ const packageRoot = await findAgentwheelPackageRoot(dirname31(fileURLToPath3(import.meta.url)));
10587
12248
  if (!packageRoot) return void 0;
10588
12249
  return {
10589
12250
  name: "agentwheel",
@@ -10630,7 +12291,7 @@ async function findAgentwheelPackageRoot(start) {
10630
12291
  let current = resolve20(start);
10631
12292
  while (true) {
10632
12293
  if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
10633
- const parent = dirname30(current);
12294
+ const parent = dirname31(current);
10634
12295
  if (parent === current) return void 0;
10635
12296
  current = parent;
10636
12297
  }