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