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/AGENT.md +1 -1
- package/README.md +128 -2
- package/dist/{chunk-QJTTISLY.js → chunk-PKAPR55N.js} +1 -1
- package/dist/{identify-T4RE5RBD.js → identify-VV7SXUIQ.js} +1 -1
- package/dist/index.js +1965 -304
- package/install.md +7 -0
- package/openpack.json +1 -1
- package/package.json +6 -2
- package/skills/agentwheel/SKILL.md +49 -1
- package/skills/agentwheel/references/ct111-selfhost-instruction-activation.md +24 -0
- package/skills/agentwheel/references/fleet-control-cleanup-ct107-node-and-effective-adapters.md +39 -0
- package/skills/agentwheel/references/fleet-plugin-rollouts.md +69 -0
- package/skills/agentwheel/references/harness-path-map-rules-2026-06-13.md +98 -0
- package/skills/agentwheel/references/hermes-background-review-routing.md +98 -0
- package/skills/agentwheel/references/hermes-plugin-rollout-source-routing.md +20 -0
- package/skills/agentwheel/references/hermes-profile-scoped-skill-rollout.md +43 -0
- package/skills/agentwheel/references/hermes-self-improve-runtime-drift.md +25 -0
- package/skills/agentwheel/references/multi-runtime-plugin-packs.md +83 -0
- package/skills/agentwheel/references/optional-openclaw-plugin-artifacts.md +76 -0
- package/skills/agentwheel/references/package-manifest-and-registry-updates.md +76 -0
package/dist/index.js
CHANGED
|
@@ -6,13 +6,14 @@ import {
|
|
|
6
6
|
isIgnoredGeneratedEntry,
|
|
7
7
|
pathExists,
|
|
8
8
|
writeJsonAtomic
|
|
9
|
-
} from "./chunk-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
2424
|
-
import { basename as basename4, dirname as
|
|
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
|
|
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
|
|
2637
|
+
const markdown = await readFile11(markdownPath, "utf8");
|
|
2468
2638
|
const toml = markdownToCodexAgentToml(agentName, markdown);
|
|
2469
|
-
await
|
|
2470
|
-
await
|
|
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
|
|
2539
|
-
import { basename as basename5, dirname as
|
|
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
|
|
2570
|
-
await
|
|
2571
|
-
await
|
|
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
|
|
2625
|
-
import { basename as basename6, dirname as
|
|
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
|
|
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
|
|
2660
|
-
await
|
|
2829
|
+
await mkdir11(dirname14(renderedPath), { recursive: true });
|
|
2830
|
+
await writeFile13(renderedPath, `${body}
|
|
2661
2831
|
`, "utf8");
|
|
2662
|
-
const renderedDir =
|
|
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
|
|
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
|
|
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
|
|
2897
|
+
const document = parseDocument(await readFile14(manifestPath, "utf8"));
|
|
2728
2898
|
const parsed = document.toJSON();
|
|
2729
|
-
if (!
|
|
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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3330
|
-
if (!
|
|
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
|
|
3377
|
-
if (!
|
|
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
|
|
3401
|
-
if (!
|
|
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
|
|
3418
|
-
if (!
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
|
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:
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
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
|
|
4570
|
-
import { basename as basename10, dirname as
|
|
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
|
|
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
|
|
5347
|
+
import { readFile as readFile17 } from "fs/promises";
|
|
4579
5348
|
import { join as join18 } from "path";
|
|
4580
|
-
import { parse as
|
|
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
|
|
5444
|
+
const content = await readFile17(path, "utf8");
|
|
4676
5445
|
const errors = [];
|
|
4677
|
-
const parsed =
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
|
5104
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
6004
|
+
const snapshotPath = join21(dirname16(checkoutPath), `${basename11(checkoutPath)}-${commit.slice(0, 12)}`);
|
|
5236
6005
|
if (await pathExists(snapshotPath)) return snapshotPath;
|
|
5237
|
-
const tempPath = join21(
|
|
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
|
|
6020
|
+
await mkdir13(dirname16(lockPath), { recursive: true });
|
|
5252
6021
|
const started = Date.now();
|
|
5253
6022
|
while (true) {
|
|
5254
6023
|
try {
|
|
5255
|
-
await
|
|
5256
|
-
await
|
|
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
|
|
5278
|
-
import { basename as basename12, dirname as
|
|
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
|
|
5386
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
5773
|
-
import { basename as basename18, dirname as
|
|
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
|
|
5778
|
-
import { readdir as readdir3, readFile as
|
|
5779
|
-
import { basename as basename16, dirname as
|
|
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
|
|
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
|
|
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
|
|
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" &&
|
|
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
|
|
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
|
|
6046
|
-
import { dirname as
|
|
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
|
|
6068
|
-
const local = await
|
|
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
|
|
6071
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7136
|
+
import { readFile as readFile21 } from "fs/promises";
|
|
6367
7137
|
import { homedir as homedir4 } from "os";
|
|
6368
|
-
import { dirname as
|
|
7138
|
+
import { dirname as dirname23, join as join29, resolve as resolve13 } from "path";
|
|
6369
7139
|
import { z as z6 } from "zod";
|
|
6370
|
-
var
|
|
6371
|
-
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
6517
|
-
import { dirname as
|
|
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
|
|
6521
|
-
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as
|
|
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
|
|
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
|
|
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
|
|
6537
|
-
var registryEntrySchema =
|
|
6538
|
-
name:
|
|
6539
|
-
source:
|
|
6540
|
-
type:
|
|
6541
|
-
description:
|
|
6542
|
-
tags:
|
|
6543
|
-
select:
|
|
6544
|
-
skills:
|
|
6545
|
-
homepageUrl:
|
|
6546
|
-
homepageLinkLabel:
|
|
6547
|
-
sourceUrl:
|
|
6548
|
-
sourceLinkLabel:
|
|
6549
|
-
openpack:
|
|
6550
|
-
schemaVersion:
|
|
6551
|
-
specVersion:
|
|
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 =
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
schemaVersion:
|
|
6558
|
-
entries:
|
|
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 =
|
|
6562
|
-
version:
|
|
6563
|
-
fetchedAt:
|
|
6564
|
-
sources:
|
|
6565
|
-
entries:
|
|
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
|
|
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
|
|
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(
|
|
6658
|
-
return
|
|
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-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
8169
|
+
sortedUnique4([...dependency.select ?? [], parsed.selector]),
|
|
7204
8170
|
chain,
|
|
7205
|
-
options
|
|
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
|
-
|
|
8221
|
+
sortedUnique4([...dependency.select ?? [], include.selector]),
|
|
7256
8222
|
chain,
|
|
7257
|
-
options
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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:
|
|
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 ?
|
|
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
|
|
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 &&
|
|
7618
|
-
return
|
|
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
|
|
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 =
|
|
7641
|
-
state.node.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] =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7812
|
-
import { mkdir as
|
|
7813
|
-
import { dirname as
|
|
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
|
|
7935
|
-
import { readFile as
|
|
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 ??
|
|
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
|
|
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
|
|
8074
|
-
return
|
|
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
|
|
9371
|
+
import { mkdir as mkdir19, readFile as readFile26 } from "fs/promises";
|
|
8324
9372
|
import { homedir as homedir7 } from "os";
|
|
8325
|
-
import { dirname as
|
|
8326
|
-
import { z as
|
|
8327
|
-
var trustStoreSchema =
|
|
8328
|
-
version:
|
|
8329
|
-
acceptedSources:
|
|
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:
|
|
8334
|
-
acceptedSources:
|
|
8335
|
-
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 =
|
|
9420
|
+
const unique = sortedUnique5(sources);
|
|
8373
9421
|
if (unique.length === 0) return [];
|
|
8374
9422
|
const store = await readTrustStore(storePath);
|
|
8375
|
-
const acceptedSources =
|
|
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
|
|
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
|
|
9447
|
+
return trustStoreSchema.parse(JSON.parse(await readFile26(path, "utf8")));
|
|
8400
9448
|
}
|
|
8401
9449
|
async function writeTrustStore(path, store) {
|
|
8402
|
-
await
|
|
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:
|
|
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,
|
|
8506
|
-
return pathForGraphLock(workspaceRoot,
|
|
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,
|
|
8549
|
-
return join35(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(
|
|
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
|
|
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
|
|
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:
|
|
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))) ?
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9107
|
-
await
|
|
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
|
|
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
|
|
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
|
|
10560
|
+
const content = await readFile28(from, "utf8");
|
|
9289
10561
|
const updated = updateSchemaVersion(content);
|
|
9290
10562
|
await rename4(from, to);
|
|
9291
|
-
await
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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("
|
|
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
|
-
|
|
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:
|
|
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)
|
|
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
|
|
9682
|
-
transport
|
|
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
|
|
9873
|
-
transport
|
|
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
|
-
|
|
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,
|
|
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,
|
|
11684
|
+
return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription, operation, { freshMetadata: true })] : [];
|
|
10031
11685
|
}
|
|
10032
11686
|
if (operation.action === "remove") {
|
|
10033
|
-
return entry ? [keepManifestEntryOperation(entry, targetRoot,
|
|
11687
|
+
return entry ? [keepManifestEntryOperation(entry, targetRoot, scopeDescription)] : [];
|
|
10034
11688
|
}
|
|
10035
11689
|
if (operation.action === "plugin" || operation.action === "program") {
|
|
10036
|
-
return entry ? [keepManifestEntryOperation(entry, targetRoot,
|
|
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,
|
|
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
|
|
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
|
|
10568
|
-
await
|
|
10569
|
-
await
|
|
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
|
|
12242
|
+
await writeFile21(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
10582
12243
|
`, "utf8");
|
|
10583
|
-
await
|
|
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(
|
|
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 =
|
|
12294
|
+
const parent = dirname31(current);
|
|
10634
12295
|
if (parent === current) return void 0;
|
|
10635
12296
|
current = parent;
|
|
10636
12297
|
}
|