agentwheel 0.20.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{apply-7HJQQOCI.js → apply-R5VHCIUN.js} +2 -2
- package/dist/{chunk-USHT7FGV.js → chunk-AFVGRRZW.js} +75 -5
- package/dist/{chunk-HOYIIUL5.js → chunk-BJEZXIXX.js} +25 -4
- package/dist/index.js +323 -219
- package/dist/{transaction-IM5MZS4B.js → transaction-ITXC7FFV.js} +3 -1
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +1 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
|
@@ -4,8 +4,8 @@ import {
|
|
|
4
4
|
applyInstallPlan,
|
|
5
5
|
recoverPendingApply,
|
|
6
6
|
uninstall
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-AFVGRRZW.js";
|
|
8
|
+
import "./chunk-BJEZXIXX.js";
|
|
9
9
|
import "./chunk-7VPI5J5Y.js";
|
|
10
10
|
export {
|
|
11
11
|
applyCombinedInstallPlan,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
acquireApplyLock,
|
|
4
|
+
applyJournalPath,
|
|
4
5
|
artifactFormatSchema,
|
|
5
6
|
artifactTypeSchema,
|
|
6
7
|
assertApplyJournalRecoveryAllowed,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
fileKindSchema,
|
|
12
13
|
installManifestPath,
|
|
13
14
|
installationTypeSchema,
|
|
15
|
+
listApplyJournals,
|
|
14
16
|
localPathExists,
|
|
15
17
|
localTransport,
|
|
16
18
|
metadataDir,
|
|
@@ -22,7 +24,7 @@ import {
|
|
|
22
24
|
rollbackCompletedOperations,
|
|
23
25
|
sourceLockPath,
|
|
24
26
|
writeApplyJournal
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-BJEZXIXX.js";
|
|
26
28
|
import {
|
|
27
29
|
pathExists,
|
|
28
30
|
writeJsonAtomic
|
|
@@ -549,16 +551,15 @@ async function readInstallManifest(targetRoot, adapter, transport = localTranspo
|
|
|
549
551
|
async function listInstallManifests(targetRoot, adapter, transport = localTransport) {
|
|
550
552
|
const dir = metadataDir(targetRoot);
|
|
551
553
|
const suffix = ".install-manifest.json";
|
|
552
|
-
const prefix = `${adapter}.`;
|
|
553
554
|
const found = [];
|
|
554
555
|
for (const fileName of await transport.listDir(dir)) {
|
|
555
556
|
if (!fileName.endsWith(suffix)) continue;
|
|
556
557
|
const stateKey = fileName.slice(0, -suffix.length);
|
|
557
|
-
if (stateKey !== adapter && !stateKey.startsWith(prefix)) continue;
|
|
558
558
|
const path = join(dir, fileName);
|
|
559
559
|
try {
|
|
560
560
|
const raw = JSON.parse(await transport.readFile(path));
|
|
561
561
|
const parsed = installManifestSchema.parse(raw);
|
|
562
|
+
if (parsed.adapter !== adapter) continue;
|
|
562
563
|
found.push({ path, fileName, stateKey, manifest: { ...parsed, revision: computeManifestRevision(raw) } });
|
|
563
564
|
} catch (error) {
|
|
564
565
|
throw new Error(`Unreadable install manifest at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -566,6 +567,10 @@ async function listInstallManifests(targetRoot, adapter, transport = localTransp
|
|
|
566
567
|
}
|
|
567
568
|
return found.sort((a, b) => a.fileName.localeCompare(b.fileName));
|
|
568
569
|
}
|
|
570
|
+
async function computeInstallManifestInventoryRevision(targetRoot, adapter, transport = localTransport) {
|
|
571
|
+
const inventory = (await listInstallManifests(targetRoot, adapter, transport)).map((item) => [item.fileName, item.manifest.revision]);
|
|
572
|
+
return createHash2("sha256").update(JSON.stringify(inventory)).digest("hex");
|
|
573
|
+
}
|
|
569
574
|
async function writeInstallManifest(manifest, transport = localTransport) {
|
|
570
575
|
const next = withManifestRevision(manifest);
|
|
571
576
|
await transport.writeJsonAtomic(installManifestPath(next.targetRoot, next.adapter, {
|
|
@@ -1360,6 +1365,7 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
|
|
|
1360
1365
|
assertGovernedRuntimeTransportSupported(transport);
|
|
1361
1366
|
const lock = await acquireApplyLock(targetRoot, adapter, transport, {}, scope);
|
|
1362
1367
|
try {
|
|
1368
|
+
await assertRuntimeJournalGate(targetRoot, adapter, transport, scope, true);
|
|
1363
1369
|
const journal = await readApplyJournal(targetRoot, adapter, transport, scope);
|
|
1364
1370
|
if (!journal) return void 0;
|
|
1365
1371
|
assertApplyJournalRecoveryAllowed(journal);
|
|
@@ -1402,6 +1408,26 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
|
|
|
1402
1408
|
await lock.release();
|
|
1403
1409
|
}
|
|
1404
1410
|
}
|
|
1411
|
+
async function assertRuntimeJournalGate(targetRoot, adapter, transport, scope, allowRequestedJournal = false) {
|
|
1412
|
+
const pending = await listApplyJournals(targetRoot, adapter, transport, {
|
|
1413
|
+
installationType: scope.installationType
|
|
1414
|
+
});
|
|
1415
|
+
if (pending.length === 0) return;
|
|
1416
|
+
const requestedPath = applyJournalPath(targetRoot, adapter, scope);
|
|
1417
|
+
if (allowRequestedJournal && pending.length === 1 && pending[0].path === requestedPath) return;
|
|
1418
|
+
throw new Error(
|
|
1419
|
+
`Cannot mutate ${adapter}/${scope.installationType ?? "local"} at ${targetRoot} while runtime apply journal(s) are pending: ` + pending.map((item) => item.path).join(", ")
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1422
|
+
async function assertRuntimeStateRevision(plan, transport) {
|
|
1423
|
+
if (!plan.runtimeStateRevision) return;
|
|
1424
|
+
const current = await computeInstallManifestInventoryRevision(plan.targetRoot, plan.adapter, transport);
|
|
1425
|
+
if (current !== plan.runtimeStateRevision) {
|
|
1426
|
+
throw new Error(
|
|
1427
|
+
`Runtime manifest inventory changed after planning: expected ${plan.runtimeStateRevision}, found ${current}; replan needed.`
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1405
1431
|
async function applyPlanTransactionally(plan, options = {}) {
|
|
1406
1432
|
const transport = options.transport ?? localTransport;
|
|
1407
1433
|
const scope = { installationType: plan.installationType, stateKey: plan.stateKey };
|
|
@@ -1412,6 +1438,8 @@ async function applyPlanTransactionally(plan, options = {}) {
|
|
|
1412
1438
|
assertGovernedRuntimeTransportSupported(transport);
|
|
1413
1439
|
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, options.lock, scope);
|
|
1414
1440
|
try {
|
|
1441
|
+
await assertRuntimeJournalGate(plan.targetRoot, plan.adapter, transport, scope);
|
|
1442
|
+
await assertRuntimeStateRevision(plan, transport);
|
|
1415
1443
|
await assertBaseRevision(plan, transport);
|
|
1416
1444
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1417
1445
|
const graphLockDigest = options.graphLockDigest ?? plan.graphLockDigest;
|
|
@@ -1493,7 +1521,7 @@ async function uninstall(plan, options = {}) {
|
|
|
1493
1521
|
const preserved = [...preservedKept, ...skipped].filter((operation) => operation.preserveInManifest !== false);
|
|
1494
1522
|
for (const operation of [...removable, ...preserved]) assertOperationContained(operation, plan.targetRoot);
|
|
1495
1523
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1496
|
-
|
|
1524
|
+
let finalManifest = withManifestRevision({
|
|
1497
1525
|
version: 2,
|
|
1498
1526
|
adapter: plan.adapter,
|
|
1499
1527
|
installationType: plan.installationType,
|
|
@@ -1517,8 +1545,25 @@ async function uninstall(plan, options = {}) {
|
|
|
1517
1545
|
});
|
|
1518
1546
|
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, resolvedOptions.lock, scope);
|
|
1519
1547
|
try {
|
|
1548
|
+
await assertRuntimeJournalGate(plan.targetRoot, plan.adapter, transport, scope);
|
|
1549
|
+
await assertRuntimeStateRevision(plan, transport);
|
|
1520
1550
|
await assertBaseRevision(plan, transport);
|
|
1521
1551
|
await assertExactMergeRemovalPreconditions(removable, transport);
|
|
1552
|
+
const revalidatedSkips = /* @__PURE__ */ new Map();
|
|
1553
|
+
for (const operation of skipped) {
|
|
1554
|
+
const entry = await applyOperation(operation, {
|
|
1555
|
+
transport,
|
|
1556
|
+
now,
|
|
1557
|
+
graphLockDigest: operation.graphLockDigest ?? plan.graphLockDigest
|
|
1558
|
+
});
|
|
1559
|
+
if (entry) revalidatedSkips.set(operation.relativeDestPath, entry);
|
|
1560
|
+
}
|
|
1561
|
+
if (revalidatedSkips.size > 0) {
|
|
1562
|
+
finalManifest = withManifestRevision({
|
|
1563
|
+
...finalManifest,
|
|
1564
|
+
entries: finalManifest.entries.map((entry) => revalidatedSkips.get(entry.path) ?? entry)
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1522
1567
|
const mutation = mutationMetadataForApplyJournal();
|
|
1523
1568
|
const journal = {
|
|
1524
1569
|
version: mutation ? 2 : 1,
|
|
@@ -1724,9 +1769,33 @@ async function applyOperation(operation, context) {
|
|
|
1724
1769
|
if (!operation.desiredHash) {
|
|
1725
1770
|
throw new Error(`Invalid skip operation missing hash: ${operation.relativeDestPath}`);
|
|
1726
1771
|
}
|
|
1772
|
+
let verifiedCurrentHash = operation.currentHash;
|
|
1773
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
1774
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
1775
|
+
if (!await managedInstructionBlockLanded(operation.destPath, selector, operation.desiredHash, transport)) {
|
|
1776
|
+
throw new Error(`Managed block changed after planning: ${operation.relativeDestPath}`);
|
|
1777
|
+
}
|
|
1778
|
+
verifiedCurrentHash = operation.desiredHash;
|
|
1779
|
+
} else if (operation.mergeStrategy && hasMergeRemovalContent(operation.mergeRemoval)) {
|
|
1780
|
+
if (!await transport.pathExists(operation.destPath)) {
|
|
1781
|
+
throw new Error(`Merge skip destination disappeared after planning: ${operation.relativeDestPath}`);
|
|
1782
|
+
}
|
|
1783
|
+
assertExactMergeContribution(operation.mergeRemoval, operation.mergeStrategy, await transport.readFile(operation.destPath));
|
|
1784
|
+
verifiedCurrentHash = await transport.hashPath(operation.destPath);
|
|
1785
|
+
} else if (!operation.semanticPlugin && !operation.programmaticOperation) {
|
|
1786
|
+
if (!await transport.pathExists(operation.destPath)) {
|
|
1787
|
+
throw new Error(`Skip destination disappeared after planning: ${operation.relativeDestPath}`);
|
|
1788
|
+
}
|
|
1789
|
+
const currentHash = await transport.hashPath(operation.destPath);
|
|
1790
|
+
const expectedHash = operation.currentHash ?? operation.desiredHash;
|
|
1791
|
+
if (currentHash !== expectedHash) {
|
|
1792
|
+
throw new Error(`Skip destination changed after planning: ${operation.relativeDestPath}`);
|
|
1793
|
+
}
|
|
1794
|
+
verifiedCurrentHash = currentHash;
|
|
1795
|
+
}
|
|
1727
1796
|
return manifestEntryForOperation(operation, {
|
|
1728
1797
|
now,
|
|
1729
|
-
hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) &&
|
|
1798
|
+
hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && verifiedCurrentHash ? verifiedCurrentHash : operation.desiredHash,
|
|
1730
1799
|
sourceHash: operation.desiredHash,
|
|
1731
1800
|
graphLockDigest: context.graphLockDigest
|
|
1732
1801
|
});
|
|
@@ -2210,6 +2279,7 @@ export {
|
|
|
2210
2279
|
installManifestSchema,
|
|
2211
2280
|
readInstallManifest,
|
|
2212
2281
|
listInstallManifests,
|
|
2282
|
+
computeInstallManifestInventoryRevision,
|
|
2213
2283
|
writeInstallManifest,
|
|
2214
2284
|
normalizeTargetRoot,
|
|
2215
2285
|
withManifestRevision,
|
|
@@ -344,7 +344,7 @@ var targetRegistryInputSchema = z2.union([
|
|
|
344
344
|
targetRegistrySchema
|
|
345
345
|
]);
|
|
346
346
|
var adapterSchema = z2.object({
|
|
347
|
-
name: z2.string().
|
|
347
|
+
name: z2.string().regex(/^[a-z0-9][a-z0-9._-]*$/i, "adapter name must be a canonical path-safe identifier"),
|
|
348
348
|
displayName: z2.string().min(1).optional(),
|
|
349
349
|
targets: z2.partialRecord(
|
|
350
350
|
artifactTypeSchema,
|
|
@@ -460,6 +460,7 @@ function metadataDir(targetRoot) {
|
|
|
460
460
|
return join(targetRoot, ".agentwheel");
|
|
461
461
|
}
|
|
462
462
|
function stateKeyFor(adapter, scope = {}) {
|
|
463
|
+
assertPathSafeAdapterName(adapter);
|
|
463
464
|
if (scope.stateKey) {
|
|
464
465
|
const explicit = sanitizeStateKey(scope.stateKey);
|
|
465
466
|
const adapterScoped = scope.fleetId && explicit !== adapter && !explicit.startsWith(`${adapter}.`) ? `${adapter}.${explicit}` : explicit;
|
|
@@ -470,6 +471,11 @@ function stateKeyFor(adapter, scope = {}) {
|
|
|
470
471
|
const fingerprint = scope.targetFingerprint ? `.${scope.targetFingerprint}` : "";
|
|
471
472
|
return sanitizeStateKey(`${adapter}.${installationType}${fingerprint}`);
|
|
472
473
|
}
|
|
474
|
+
function assertPathSafeAdapterName(adapter) {
|
|
475
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(adapter)) {
|
|
476
|
+
throw new Error(`Adapter name '${adapter}' is not a canonical path-safe identifier.`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
473
479
|
function installManifestPath(targetRoot, adapter, scope = {}) {
|
|
474
480
|
return join(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.install-manifest.json`);
|
|
475
481
|
}
|
|
@@ -2618,8 +2624,8 @@ async function recoverMutationRuntime(operationId, options = {}) {
|
|
|
2618
2624
|
}
|
|
2619
2625
|
mutation = GovernedMutation.activateExisting(receipt, baseline, lock);
|
|
2620
2626
|
const [{ recoverPendingApply }, { readLinkedLocalApplyJournal: readLinkedLocalApplyJournal2, removeApplyJournal: removeApplyJournal2, localPathExists: localPathExists2 }] = await Promise.all([
|
|
2621
|
-
import("./apply-
|
|
2622
|
-
import("./transaction-
|
|
2627
|
+
import("./apply-R5VHCIUN.js"),
|
|
2628
|
+
import("./transaction-ITXC7FFV.js")
|
|
2623
2629
|
]);
|
|
2624
2630
|
let missingReservation = false;
|
|
2625
2631
|
for (const entry of pending) {
|
|
@@ -2782,7 +2788,8 @@ function upsertRuntimeJournal(entries, link2, status) {
|
|
|
2782
2788
|
|
|
2783
2789
|
// src/install/transaction.ts
|
|
2784
2790
|
function applyLockPath(targetRoot, adapter, scope = {}) {
|
|
2785
|
-
|
|
2791
|
+
const installationType = scope.installationType ?? "local";
|
|
2792
|
+
return join6(metadataDir(targetRoot), `${stateKeyFor(adapter, { installationType })}.runtime-apply-lock`);
|
|
2786
2793
|
}
|
|
2787
2794
|
function applyJournalPath(targetRoot, adapter, scope = {}) {
|
|
2788
2795
|
return join6(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-journal.json`);
|
|
@@ -2850,6 +2857,19 @@ async function readApplyJournal(targetRoot, adapter, transport = localTransport,
|
|
|
2850
2857
|
if (!await transport.pathExists(path)) return void 0;
|
|
2851
2858
|
return parseApplyJournal(JSON.parse(await transport.readFile(path)));
|
|
2852
2859
|
}
|
|
2860
|
+
async function listApplyJournals(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
2861
|
+
const installationType = scope.installationType ?? "local";
|
|
2862
|
+
const suffix = ".apply-journal.json";
|
|
2863
|
+
const found = [];
|
|
2864
|
+
for (const fileName of await transport.listDir(metadataDir(targetRoot))) {
|
|
2865
|
+
if (!fileName.endsWith(suffix)) continue;
|
|
2866
|
+
const path = join6(metadataDir(targetRoot), fileName);
|
|
2867
|
+
const journal = parseApplyJournal(JSON.parse(await transport.readFile(path)));
|
|
2868
|
+
if (journal.adapter !== adapter || (journal.installationType ?? "local") !== installationType) continue;
|
|
2869
|
+
found.push({ path, journal });
|
|
2870
|
+
}
|
|
2871
|
+
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
2872
|
+
}
|
|
2853
2873
|
async function readLinkedLocalApplyJournal(path, operationId, expectedDigest, expectedTransportDescription) {
|
|
2854
2874
|
const absolutePath = resolve9(path);
|
|
2855
2875
|
const journal = parseApplyJournal(JSON.parse(await readFile7(absolutePath, "utf8")));
|
|
@@ -3129,6 +3149,7 @@ export {
|
|
|
3129
3149
|
acquireApplyLock,
|
|
3130
3150
|
writeApplyJournal,
|
|
3131
3151
|
readApplyJournal,
|
|
3152
|
+
listApplyJournals,
|
|
3132
3153
|
readLinkedLocalApplyJournal,
|
|
3133
3154
|
removeApplyJournal,
|
|
3134
3155
|
abortApplyJournal,
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
canonicalizeGraphLock,
|
|
10
10
|
claudeInstructionBridgesAgents,
|
|
11
11
|
combineMergeRemovals,
|
|
12
|
+
computeInstallManifestInventoryRevision,
|
|
12
13
|
computeManifestRevision,
|
|
13
14
|
computeTargetFingerprint,
|
|
14
15
|
desiredManagedInstructionBlockHash,
|
|
@@ -30,7 +31,7 @@ import {
|
|
|
30
31
|
uninstall,
|
|
31
32
|
withManifestRevision,
|
|
32
33
|
writeInstallManifest
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-AFVGRRZW.js";
|
|
34
35
|
import {
|
|
35
36
|
CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
36
37
|
GovernedMutation,
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
acquireApplyLock,
|
|
39
40
|
adapterSchema,
|
|
40
41
|
adapterTargetSupport,
|
|
42
|
+
applyLockPath,
|
|
41
43
|
artifactFormatSchema,
|
|
42
44
|
artifactTypeSchema,
|
|
43
45
|
assertGovernedRuntimeTransportSupported,
|
|
@@ -53,6 +55,7 @@ import {
|
|
|
53
55
|
installRootForAdapterInstallationType,
|
|
54
56
|
installRootForArtifacts,
|
|
55
57
|
isCompositeWorkspaceProfile,
|
|
58
|
+
listApplyJournals,
|
|
56
59
|
listMutationReceipts,
|
|
57
60
|
loadAdapterConfig,
|
|
58
61
|
localTransport,
|
|
@@ -86,7 +89,7 @@ import {
|
|
|
86
89
|
workspaceExportsSchema,
|
|
87
90
|
workspaceSelectionImportSchema,
|
|
88
91
|
writeWorkspaceConfig
|
|
89
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-BJEZXIXX.js";
|
|
90
93
|
import {
|
|
91
94
|
inferSourceDriverName
|
|
92
95
|
} from "./chunk-EMDIG24I.js";
|
|
@@ -1449,6 +1452,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1449
1452
|
const installationType = resolveInstallationTypeForArtifacts(adapter, installableArtifacts.map((artifact) => artifact.type), requestedInstallationType);
|
|
1450
1453
|
const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, installableArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
1451
1454
|
await validateArtifactsForInstall(installableArtifacts, adapter, installationType);
|
|
1455
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(installRoot, adapter.name, transport);
|
|
1452
1456
|
for (const artifact of installableArtifacts) {
|
|
1453
1457
|
if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
|
|
1454
1458
|
throw new Error(`Dependency-provided ${artifact.type} artifacts cannot be installed until per-subentry ownership exists: ${artifact.type}/${artifact.name}`);
|
|
@@ -1462,7 +1466,7 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1462
1466
|
}
|
|
1463
1467
|
}
|
|
1464
1468
|
await addProgrammaticOperations(desired, adapter, installRoot);
|
|
1465
|
-
return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType });
|
|
1469
|
+
return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType, runtimeStateRevision });
|
|
1466
1470
|
}
|
|
1467
1471
|
async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
|
|
1468
1472
|
const workspaceOwner = options.workspaceOwner;
|
|
@@ -1512,7 +1516,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1512
1516
|
}
|
|
1513
1517
|
}
|
|
1514
1518
|
const incompleteMergeOwnership = existing2 && existing2.mergeStrategy && !("mergeCreatedDestination" in existing2 && existing2.mergeCreatedDestination === true) && !("mergeRemoval" in existing2 && hasMergeRemovalContent(existing2.mergeRemoval));
|
|
1515
|
-
const adoptExisting = exists2 && (
|
|
1519
|
+
const adoptExisting = exists2 && (incompleteMergeOwnership || !existing2 && options.forceConflict === true);
|
|
1516
1520
|
let mergeRemoval;
|
|
1517
1521
|
try {
|
|
1518
1522
|
mergeRemoval = await mergeRemovalForInstall(op.sourcePath, op.mergeStrategy, currentContent, {
|
|
@@ -1544,7 +1548,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1544
1548
|
mergeCreatedDestination: existing2?.mergeCreatedDestination,
|
|
1545
1549
|
currentHash: currentHash2,
|
|
1546
1550
|
manifestHash: existing2?.hash,
|
|
1547
|
-
reason: existing2 ? "
|
|
1551
|
+
reason: existing2 ? "repairing exact incomplete merge ownership" : "force adopting exact unmanaged merge contribution"
|
|
1548
1552
|
});
|
|
1549
1553
|
continue;
|
|
1550
1554
|
}
|
|
@@ -1684,6 +1688,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1684
1688
|
}
|
|
1685
1689
|
for (const operation of operations) assertOperationContained(operation, targetRoot);
|
|
1686
1690
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
1691
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(targetRoot, adapter.name, transport);
|
|
1692
|
+
if (options.runtimeStateRevision && runtimeStateRevision !== options.runtimeStateRevision) {
|
|
1693
|
+
throw new Error("Runtime manifest inventory changed during planning; replan needed.");
|
|
1694
|
+
}
|
|
1687
1695
|
return {
|
|
1688
1696
|
adapter: adapter.name,
|
|
1689
1697
|
installationType: options.installationType ?? defaultInstallationType,
|
|
@@ -1692,6 +1700,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1692
1700
|
operations,
|
|
1693
1701
|
hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
|
|
1694
1702
|
baseRevision: options.baseRevision ?? manifest?.revision ?? null,
|
|
1703
|
+
runtimeStateRevision,
|
|
1695
1704
|
migrationReport: migration.report,
|
|
1696
1705
|
graphLockDigest: options.graphLockDigest,
|
|
1697
1706
|
adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
|
|
@@ -2182,6 +2191,7 @@ function isPendingInstallOperation(operation) {
|
|
|
2182
2191
|
// src/install/uninstall.ts
|
|
2183
2192
|
import { join as join13 } from "path";
|
|
2184
2193
|
async function createUninstallPlan(manifest, transport = localTransport) {
|
|
2194
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
|
|
2185
2195
|
const operations = [];
|
|
2186
2196
|
for (const entry of manifest.entries) {
|
|
2187
2197
|
const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
|
|
@@ -2237,6 +2247,9 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2237
2247
|
}
|
|
2238
2248
|
}
|
|
2239
2249
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
2250
|
+
if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
|
|
2251
|
+
throw new Error("Runtime manifest inventory changed during uninstall planning; replan needed.");
|
|
2252
|
+
}
|
|
2240
2253
|
return {
|
|
2241
2254
|
adapter: manifest.adapter,
|
|
2242
2255
|
installationType: "installationType" in manifest ? manifest.installationType : defaultInstallationType,
|
|
@@ -2245,10 +2258,12 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2245
2258
|
operations,
|
|
2246
2259
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
2247
2260
|
baseRevision: manifest.revision,
|
|
2261
|
+
runtimeStateRevision,
|
|
2248
2262
|
adapterCode: manifest.adapterCode
|
|
2249
2263
|
};
|
|
2250
2264
|
}
|
|
2251
2265
|
async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport = localTransport, options = {}) {
|
|
2266
|
+
const runtimeStateRevision = await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport);
|
|
2252
2267
|
const installationType = "installationType" in manifest ? manifest.installationType : defaultInstallationType;
|
|
2253
2268
|
const stateKey = "stateKey" in manifest ? manifest.stateKey : void 0;
|
|
2254
2269
|
const desiredPlan = await createCombinedInstallPlan(remainingDesired, adapter, manifest.targetRoot, void 0, transport, { installationType, stateKey });
|
|
@@ -2338,6 +2353,9 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2338
2353
|
}
|
|
2339
2354
|
}
|
|
2340
2355
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
2356
|
+
if (runtimeStateRevision !== await computeInstallManifestInventoryRevision(manifest.targetRoot, manifest.adapter, transport)) {
|
|
2357
|
+
throw new Error("Runtime manifest inventory changed during ownership uninstall planning; replan needed.");
|
|
2358
|
+
}
|
|
2341
2359
|
return {
|
|
2342
2360
|
adapter: manifest.adapter,
|
|
2343
2361
|
installationType,
|
|
@@ -2346,6 +2364,7 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2346
2364
|
operations,
|
|
2347
2365
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
2348
2366
|
baseRevision: manifest.revision,
|
|
2367
|
+
runtimeStateRevision,
|
|
2349
2368
|
adapterCode: manifest.adapterCode,
|
|
2350
2369
|
graphLockDigest: options.graphLockDigest
|
|
2351
2370
|
};
|
|
@@ -6914,7 +6933,7 @@ import { rm as rm7 } from "fs/promises";
|
|
|
6914
6933
|
// src/lifecycle/source-plan.ts
|
|
6915
6934
|
import { createHash as createHash8 } from "crypto";
|
|
6916
6935
|
import { mkdir as mkdir14 } from "fs/promises";
|
|
6917
|
-
import { dirname as dirname17, join as join33, resolve as
|
|
6936
|
+
import { dirname as dirname17, join as join33, resolve as resolve15 } from "path";
|
|
6918
6937
|
|
|
6919
6938
|
// src/resolve/graph-diff.ts
|
|
6920
6939
|
function diffGraphLocks(previous, next) {
|
|
@@ -7735,6 +7754,185 @@ async function createExactMcpRetirementPlan(desiredArtifacts, adapter, targetRoo
|
|
|
7735
7754
|
};
|
|
7736
7755
|
}
|
|
7737
7756
|
|
|
7757
|
+
// src/model/fleet.ts
|
|
7758
|
+
import { lstat as lstat2, readFile as readFile20, realpath } from "fs/promises";
|
|
7759
|
+
import { homedir as homedir7 } from "os";
|
|
7760
|
+
import { isAbsolute as isAbsolute2, relative as relative7, resolve as resolve14 } from "path";
|
|
7761
|
+
async function resolveWorkspaceScope(request = {}) {
|
|
7762
|
+
const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
|
|
7763
|
+
if (selected > 1) {
|
|
7764
|
+
throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
|
|
7765
|
+
}
|
|
7766
|
+
const globalRoot = resolve14(request.globalRoot ?? homedir7());
|
|
7767
|
+
if (request.user) {
|
|
7768
|
+
const config2 = await readWorkspaceConfig(globalRoot);
|
|
7769
|
+
assertNonFleetScope("user", config2);
|
|
7770
|
+
return { kind: "user", root: globalRoot, config: config2 };
|
|
7771
|
+
}
|
|
7772
|
+
if (request.fleet !== void 0) {
|
|
7773
|
+
const id = fleetIdSchema.parse(request.fleet);
|
|
7774
|
+
const registration = await showRegisteredFleet(id, { globalRoot });
|
|
7775
|
+
const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
7776
|
+
const config2 = await readRequiredConfig(root, `fleet '${id}'`);
|
|
7777
|
+
assertFleetContract(id, registration, config2);
|
|
7778
|
+
return { kind: "fleet", root, fleetId: id, config: config2 };
|
|
7779
|
+
}
|
|
7780
|
+
const cwd = resolve14(request.cwd ?? process.cwd());
|
|
7781
|
+
const discoveredRoot = await findExistingWorkspaceRoot(cwd);
|
|
7782
|
+
if (!discoveredRoot || discoveredRoot === globalRoot) {
|
|
7783
|
+
if (request.local === true && cwd !== globalRoot) {
|
|
7784
|
+
return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
|
|
7785
|
+
}
|
|
7786
|
+
throw missingScopeError(request.local === true ? "local" : "implicit local");
|
|
7787
|
+
}
|
|
7788
|
+
const config = await readRequiredConfig(discoveredRoot, "local");
|
|
7789
|
+
assertNonFleetScope("local", config);
|
|
7790
|
+
return { kind: "local", root: discoveredRoot, config };
|
|
7791
|
+
}
|
|
7792
|
+
async function registerFleet(request) {
|
|
7793
|
+
const id = fleetIdSchema.parse(request.id);
|
|
7794
|
+
const requiredPackages = sortedUnique4(request.requiredPackages);
|
|
7795
|
+
if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
|
|
7796
|
+
const globalRoot = resolve14(request.globalRoot ?? homedir7());
|
|
7797
|
+
const home = await readWorkspaceConfig(globalRoot);
|
|
7798
|
+
if (supportsFleetConfig(home) && home.fleetId) {
|
|
7799
|
+
throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
|
|
7800
|
+
}
|
|
7801
|
+
const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
|
|
7802
|
+
if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
|
|
7803
|
+
const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
|
|
7804
|
+
const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
|
|
7805
|
+
if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
|
|
7806
|
+
if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
|
|
7807
|
+
const target = await readRequiredConfig(root, `fleet '${id}'`);
|
|
7808
|
+
const registration = registeredFleetSchema.parse({ root, requiredPackages });
|
|
7809
|
+
assertFleetContract(id, registration, target);
|
|
7810
|
+
const upgraded = workspaceConfigSchema.parse({
|
|
7811
|
+
...home,
|
|
7812
|
+
schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
7813
|
+
exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
|
|
7814
|
+
fleets: { ...existing, [id]: registration }
|
|
7815
|
+
});
|
|
7816
|
+
const configPath = globalWorkspaceConfigPath(globalRoot);
|
|
7817
|
+
declareMutationPath(configPath);
|
|
7818
|
+
await writeJsonAtomic(configPath, upgraded);
|
|
7819
|
+
return { id, ...registration };
|
|
7820
|
+
}
|
|
7821
|
+
async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
|
|
7822
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
7823
|
+
const normalized = {};
|
|
7824
|
+
for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
|
|
7825
|
+
const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
7826
|
+
if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
|
|
7827
|
+
const incumbent = canonical.get(root);
|
|
7828
|
+
if (incumbent) {
|
|
7829
|
+
throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
|
|
7830
|
+
}
|
|
7831
|
+
canonical.set(root, id);
|
|
7832
|
+
normalized[id] = registeredFleetSchema.parse({ ...registration, root });
|
|
7833
|
+
}
|
|
7834
|
+
return normalized;
|
|
7835
|
+
}
|
|
7836
|
+
async function listRegisteredFleets(options = {}) {
|
|
7837
|
+
const globalRoot = resolve14(options.globalRoot ?? homedir7());
|
|
7838
|
+
const home = await readWorkspaceConfig(globalRoot);
|
|
7839
|
+
if (!supportsFleetConfig(home)) return [];
|
|
7840
|
+
return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
|
|
7841
|
+
}
|
|
7842
|
+
async function showRegisteredFleet(idInput, options = {}) {
|
|
7843
|
+
const id = fleetIdSchema.parse(idInput);
|
|
7844
|
+
const fleets = await listRegisteredFleets(options);
|
|
7845
|
+
const registration = fleets.find((candidate) => candidate.id === id);
|
|
7846
|
+
if (!registration) {
|
|
7847
|
+
throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
|
|
7848
|
+
}
|
|
7849
|
+
return registration;
|
|
7850
|
+
}
|
|
7851
|
+
async function resolveWorkspaceOwnershipScope(workspaceRootInput, options = {}) {
|
|
7852
|
+
const workspaceRoot = await canonicalizeDirectory(resolve14(workspaceRootInput), "Workspace ownership root");
|
|
7853
|
+
if (options.fleetId) {
|
|
7854
|
+
return { root: workspaceRoot, fleetId: fleetIdSchema.parse(options.fleetId) };
|
|
7855
|
+
}
|
|
7856
|
+
const syntacticallyContaining = (await listRegisteredFleets({ globalRoot: options.globalRoot })).filter((fleet2) => containsPath(resolve14(fleet2.root), workspaceRoot));
|
|
7857
|
+
const registered = await Promise.all(syntacticallyContaining.map(async (fleet2) => ({
|
|
7858
|
+
...fleet2,
|
|
7859
|
+
root: await canonicalizeDirectory(fleet2.root, `Registered fleet '${fleet2.id}' root`)
|
|
7860
|
+
})));
|
|
7861
|
+
const containing = registered.filter((fleet2) => containsPath(fleet2.root, workspaceRoot));
|
|
7862
|
+
if (containing.length > 1) {
|
|
7863
|
+
throw new Error(
|
|
7864
|
+
`Workspace ${workspaceRoot} is contained by multiple registered fleets: ` + containing.map((fleet2) => `'${fleet2.id}' at ${fleet2.root}`).join(", ") + ". Resolve the overlapping registrations before planning ownership."
|
|
7865
|
+
);
|
|
7866
|
+
}
|
|
7867
|
+
const fleet = containing[0];
|
|
7868
|
+
if (fleet) assertFleetContract(fleet.id, fleet, await readRequiredConfig(fleet.root, `fleet '${fleet.id}'`));
|
|
7869
|
+
return fleet ? { root: fleet.root, fleetId: fleet.id } : { root: workspaceRoot };
|
|
7870
|
+
}
|
|
7871
|
+
async function readRequiredConfig(root, label) {
|
|
7872
|
+
const path = workspaceConfigPath(root);
|
|
7873
|
+
if (!await pathExists(path)) throw missingScopeError(label);
|
|
7874
|
+
try {
|
|
7875
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile20(path, "utf8")));
|
|
7876
|
+
} catch (error) {
|
|
7877
|
+
throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
7878
|
+
}
|
|
7879
|
+
}
|
|
7880
|
+
function assertFleetContract(id, registration, config) {
|
|
7881
|
+
if (!supportsFleetConfig(config)) {
|
|
7882
|
+
throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
|
|
7883
|
+
}
|
|
7884
|
+
if (config.fleetId !== id) {
|
|
7885
|
+
throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
|
|
7886
|
+
}
|
|
7887
|
+
if (Object.keys(config.fleets).length > 0) {
|
|
7888
|
+
throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
|
|
7889
|
+
}
|
|
7890
|
+
const names = new Set(config.packages.map((pkg) => pkg.name));
|
|
7891
|
+
for (const packageName of registration.requiredPackages) {
|
|
7892
|
+
if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
|
|
7893
|
+
}
|
|
7894
|
+
}
|
|
7895
|
+
async function assertCanonicalDirectory(input, label) {
|
|
7896
|
+
const canonical = await canonicalizeDirectory(input, label);
|
|
7897
|
+
const normalized = resolve14(input);
|
|
7898
|
+
const stats = await lstat2(input);
|
|
7899
|
+
if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
|
|
7900
|
+
throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
|
|
7901
|
+
}
|
|
7902
|
+
return canonical;
|
|
7903
|
+
}
|
|
7904
|
+
async function canonicalizeDirectory(input, label) {
|
|
7905
|
+
if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
|
|
7906
|
+
let canonical;
|
|
7907
|
+
try {
|
|
7908
|
+
canonical = await realpath(input);
|
|
7909
|
+
} catch {
|
|
7910
|
+
throw new Error(`${label} does not exist: ${input}`);
|
|
7911
|
+
}
|
|
7912
|
+
const stats = await lstat2(canonical);
|
|
7913
|
+
if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
|
|
7914
|
+
return canonical;
|
|
7915
|
+
}
|
|
7916
|
+
function missingScopeError(label) {
|
|
7917
|
+
return new Error(
|
|
7918
|
+
`Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
|
|
7919
|
+
);
|
|
7920
|
+
}
|
|
7921
|
+
function assertNonFleetScope(kind, config) {
|
|
7922
|
+
if (supportsFleetConfig(config) && config.fleetId) {
|
|
7923
|
+
throw new Error(
|
|
7924
|
+
`The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
|
|
7925
|
+
);
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
function sortedUnique4(values) {
|
|
7929
|
+
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
7930
|
+
}
|
|
7931
|
+
function containsPath(parent, candidate) {
|
|
7932
|
+
const path = relative7(parent, candidate);
|
|
7933
|
+
return path === "" || !path.startsWith("..") && !isAbsolute2(path);
|
|
7934
|
+
}
|
|
7935
|
+
|
|
7738
7936
|
// src/lifecycle/source-plan.ts
|
|
7739
7937
|
async function createGraphSourcePlan(options) {
|
|
7740
7938
|
if (options.roots.length === 0) {
|
|
@@ -7813,7 +8011,11 @@ async function createGraphSourcePlan(options) {
|
|
|
7813
8011
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
7814
8012
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
7815
8013
|
const manifest = await readInstallManifest(resolvedInstallRoot, options.adapter.name, transport, { installationType: resolvedInstallationType, stateKey });
|
|
7816
|
-
const
|
|
8014
|
+
const ownership = await resolveWorkspaceOwnershipScope(workspaceRoot, {
|
|
8015
|
+
fleetId: options.fleetId,
|
|
8016
|
+
globalRoot: options.globalRoot
|
|
8017
|
+
});
|
|
8018
|
+
const workspaceOwner = workspaceOwnerForRoot(ownership.root, ownership.fleetId);
|
|
7817
8019
|
const plan = options.retireExactMcp ? await createExactMcpRetirementPlan(
|
|
7818
8020
|
desiredArtifacts,
|
|
7819
8021
|
options.adapter,
|
|
@@ -7847,7 +8049,8 @@ async function createGraphSourcePlan(options) {
|
|
|
7847
8049
|
workspaceOwner,
|
|
7848
8050
|
globalRoot: options.globalRoot,
|
|
7849
8051
|
stateKey,
|
|
7850
|
-
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath)
|
|
8052
|
+
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8053
|
+
plannedOperations: plan.operations
|
|
7851
8054
|
});
|
|
7852
8055
|
}
|
|
7853
8056
|
return {
|
|
@@ -7874,7 +8077,8 @@ async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
|
7874
8077
|
workspaceOwner: options.workspaceOwner,
|
|
7875
8078
|
globalRoot: options.globalRoot,
|
|
7876
8079
|
stateKey: plan.stateKey,
|
|
7877
|
-
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath)
|
|
8080
|
+
plannedPaths: options.plannedPaths ?? plan.operations.map((operation) => operation.relativeDestPath),
|
|
8081
|
+
plannedOperations: plan.operations
|
|
7878
8082
|
});
|
|
7879
8083
|
}
|
|
7880
8084
|
function graphLockPathForTarget(workspaceRoot, targetKey2, adapter, targetFingerprintParts2) {
|
|
@@ -7924,8 +8128,15 @@ var workspaceOwnerPrefix2 = "workspace-root:";
|
|
|
7924
8128
|
async function assertNoForeignWorkspaceState(check) {
|
|
7925
8129
|
const planned = new Set(check.plannedPaths);
|
|
7926
8130
|
if (planned.size === 0) return;
|
|
8131
|
+
const plannedOperations = /* @__PURE__ */ new Map();
|
|
8132
|
+
for (const operation of check.plannedOperations) {
|
|
8133
|
+
if (!planned.has(operation.relativeDestPath)) continue;
|
|
8134
|
+
const operations = plannedOperations.get(operation.relativeDestPath) ?? [];
|
|
8135
|
+
operations.push(operation);
|
|
8136
|
+
plannedOperations.set(operation.relativeDestPath, operations);
|
|
8137
|
+
}
|
|
7927
8138
|
const manifests = await listInstallManifests(check.installRoot, check.adapter, check.transport);
|
|
7928
|
-
const root =
|
|
8139
|
+
const root = resolve15(check.workspaceRoot);
|
|
7929
8140
|
const ownsSubWorkspaces = root !== globalConfigRoot(check.globalRoot);
|
|
7930
8141
|
const foreign = [];
|
|
7931
8142
|
for (const entry of manifests) {
|
|
@@ -7937,7 +8148,15 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
7937
8148
|
if (ownedByWorkspace(owner, check.workspaceOwner, root, ownsSubWorkspaces)) continue;
|
|
7938
8149
|
const bucket = byOwner.get(owner) ?? { entryCount: 0, collidingPaths: [] };
|
|
7939
8150
|
bucket.entryCount += 1;
|
|
7940
|
-
if (planned.has(manifestEntry.path)
|
|
8151
|
+
if (planned.has(manifestEntry.path) && !await isDisjointVerifiedMergeContribution(
|
|
8152
|
+
check,
|
|
8153
|
+
manifestEntry.path,
|
|
8154
|
+
manifestEntry.mergeStrategy,
|
|
8155
|
+
manifestEntry.mergeRemoval,
|
|
8156
|
+
plannedOperations.get(manifestEntry.path) ?? []
|
|
8157
|
+
)) {
|
|
8158
|
+
bucket.collidingPaths.push(manifestEntry.path);
|
|
8159
|
+
}
|
|
7941
8160
|
byOwner.set(owner, bucket);
|
|
7942
8161
|
}
|
|
7943
8162
|
for (const [owner, bucket] of byOwner) {
|
|
@@ -7959,6 +8178,34 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
7959
8178
|
parseWorkspaceOwner(check.workspaceOwner)?.fleetId ? "Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet." : "Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."
|
|
7960
8179
|
].join("\n"));
|
|
7961
8180
|
}
|
|
8181
|
+
async function isDisjointVerifiedMergeContribution(check, relativePath, foreignStrategy, foreignRemoval, operations) {
|
|
8182
|
+
if (!foreignStrategy || !hasMergeRemovalContent(foreignRemoval) || operations.length !== 1) return false;
|
|
8183
|
+
const operation = operations[0];
|
|
8184
|
+
if (operation.mergeStrategy !== foreignStrategy || !hasMergeRemovalContent(operation.mergeRemoval)) return false;
|
|
8185
|
+
if (!disjointMcpMergeContributions(foreignStrategy, foreignRemoval, operation.mergeRemoval)) return false;
|
|
8186
|
+
try {
|
|
8187
|
+
const content = await check.transport.readFile(join33(check.installRoot, relativePath));
|
|
8188
|
+
assertExactMergeContribution(foreignRemoval, foreignStrategy, content);
|
|
8189
|
+
assertExactMergeContribution(operation.mergeRemoval, foreignStrategy, content);
|
|
8190
|
+
return true;
|
|
8191
|
+
} catch {
|
|
8192
|
+
return false;
|
|
8193
|
+
}
|
|
8194
|
+
}
|
|
8195
|
+
function disjointMcpMergeContributions(strategy, left, right) {
|
|
8196
|
+
const leftNames = mcpServerNames(strategy, left);
|
|
8197
|
+
const rightNames = mcpServerNames(strategy, right);
|
|
8198
|
+
if (!leftNames || !rightNames) return false;
|
|
8199
|
+
return [...leftNames].every((name) => !rightNames.has(name));
|
|
8200
|
+
}
|
|
8201
|
+
function mcpServerNames(strategy, removal) {
|
|
8202
|
+
if (strategy !== "codex-toml-mcp" && strategy !== "json-deep") return void 0;
|
|
8203
|
+
const keys = Object.keys(removal);
|
|
8204
|
+
if (strategy === "json-deep" && (keys.length !== 1 || keys[0] !== "mcpServers")) return void 0;
|
|
8205
|
+
const servers = removal.mcpServers ?? (strategy === "codex-toml-mcp" ? removal : void 0);
|
|
8206
|
+
if (!servers || Array.isArray(servers) || typeof servers !== "object") return void 0;
|
|
8207
|
+
return new Set(Object.keys(servers));
|
|
8208
|
+
}
|
|
7962
8209
|
function globalConfigRoot(globalRoot) {
|
|
7963
8210
|
return dirname17(dirname17(globalWorkspaceConfigPath(globalRoot)));
|
|
7964
8211
|
}
|
|
@@ -8031,17 +8278,17 @@ function selectionImportKey2(selection) {
|
|
|
8031
8278
|
exportHash: selection.exportHash,
|
|
8032
8279
|
exportName: selection.exportName,
|
|
8033
8280
|
extends: selection.extends,
|
|
8034
|
-
inherited:
|
|
8035
|
-
additions:
|
|
8036
|
-
exclusions:
|
|
8037
|
-
effective:
|
|
8281
|
+
inherited: sortedUnique5(selection.inherited),
|
|
8282
|
+
additions: sortedUnique5(selection.additions),
|
|
8283
|
+
exclusions: sortedUnique5(selection.exclusions),
|
|
8284
|
+
effective: sortedUnique5(selection.effective)
|
|
8038
8285
|
});
|
|
8039
8286
|
}
|
|
8040
|
-
function
|
|
8287
|
+
function sortedUnique5(values) {
|
|
8041
8288
|
return [...new Set(values)].sort((a, b) => a.localeCompare(b));
|
|
8042
8289
|
}
|
|
8043
8290
|
function selectorKey(selectors) {
|
|
8044
|
-
return
|
|
8291
|
+
return sortedUnique5(selectors).join("\0");
|
|
8045
8292
|
}
|
|
8046
8293
|
async function assertTrusted(sources, options) {
|
|
8047
8294
|
if (sources.length === 0) return;
|
|
@@ -8102,7 +8349,7 @@ function targetLabel(target) {
|
|
|
8102
8349
|
}
|
|
8103
8350
|
|
|
8104
8351
|
// src/runtime/target.ts
|
|
8105
|
-
import { basename as basename18, dirname as dirname18, join as join34, resolve as
|
|
8352
|
+
import { basename as basename18, dirname as dirname18, join as join34, resolve as resolve16 } from "path";
|
|
8106
8353
|
var runtimeMarkers = [
|
|
8107
8354
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
8108
8355
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -8111,9 +8358,9 @@ var runtimeMarkers = [
|
|
|
8111
8358
|
{ adapter: "copilot", dirs: [".github"] }
|
|
8112
8359
|
];
|
|
8113
8360
|
async function resolveRuntimeTarget(request = {}) {
|
|
8114
|
-
const cwd =
|
|
8361
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8115
8362
|
if (request.targetRoot) {
|
|
8116
|
-
const targetRoot =
|
|
8363
|
+
const targetRoot = resolve16(request.targetRoot);
|
|
8117
8364
|
return {
|
|
8118
8365
|
adapter: request.adapter ?? "openclaw",
|
|
8119
8366
|
installationType: request.installationType,
|
|
@@ -8145,7 +8392,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
8145
8392
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
8146
8393
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
8147
8394
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
8148
|
-
const cwd =
|
|
8395
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8149
8396
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
8150
8397
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8151
8398
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType, request.fleetId));
|
|
@@ -8155,8 +8402,8 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
8155
8402
|
return targets;
|
|
8156
8403
|
}
|
|
8157
8404
|
async function resolveProfileRuntimeTargets(request) {
|
|
8158
|
-
const cwd =
|
|
8159
|
-
const workspaceRoot = request.targetRoot ?
|
|
8405
|
+
const cwd = resolve16(request.cwd ?? process.cwd());
|
|
8406
|
+
const workspaceRoot = request.targetRoot ? resolve16(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
8160
8407
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
8161
8408
|
const profile = config.profiles[request.profile];
|
|
8162
8409
|
if (!profile) {
|
|
@@ -8222,7 +8469,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
8222
8469
|
return unique[0];
|
|
8223
8470
|
}
|
|
8224
8471
|
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
8225
|
-
const root =
|
|
8472
|
+
const root = resolve16(cwd);
|
|
8226
8473
|
const matches = [];
|
|
8227
8474
|
for (const marker of runtimeMarkers) {
|
|
8228
8475
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
@@ -8271,7 +8518,7 @@ function dedupeTargets(matches) {
|
|
|
8271
8518
|
return [...byKey.values()];
|
|
8272
8519
|
}
|
|
8273
8520
|
function runtimeScanRoot(request) {
|
|
8274
|
-
const root =
|
|
8521
|
+
const root = resolve16(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
8275
8522
|
if (request.targetRoot) return root;
|
|
8276
8523
|
return runtimeMarkers.some((marker) => marker.dirs.includes(basename18(root))) ? dirname18(root) : root;
|
|
8277
8524
|
}
|
|
@@ -8581,8 +8828,8 @@ function shellQuoteArg(value) {
|
|
|
8581
8828
|
}
|
|
8582
8829
|
|
|
8583
8830
|
// src/cli/update-check.ts
|
|
8584
|
-
import { mkdir as mkdir15, readFile as
|
|
8585
|
-
import { homedir as
|
|
8831
|
+
import { mkdir as mkdir15, readFile as readFile21, writeFile as writeFile11 } from "fs/promises";
|
|
8832
|
+
import { homedir as homedir8 } from "os";
|
|
8586
8833
|
import { dirname as dirname19, join as join35 } from "path";
|
|
8587
8834
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8588
8835
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
@@ -8591,7 +8838,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
8591
8838
|
if (isDisabled(options)) return;
|
|
8592
8839
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
8593
8840
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
8594
|
-
const cachePath = options.cachePath ?? join35(
|
|
8841
|
+
const cachePath = options.cachePath ?? join35(homedir8(), ".agentwheel", "update-check.json");
|
|
8595
8842
|
try {
|
|
8596
8843
|
const cached = await readCache(cachePath);
|
|
8597
8844
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -8628,7 +8875,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
8628
8875
|
}
|
|
8629
8876
|
async function readCache(path) {
|
|
8630
8877
|
try {
|
|
8631
|
-
const parsed = JSON.parse(await
|
|
8878
|
+
const parsed = JSON.parse(await readFile21(path, "utf8"));
|
|
8632
8879
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
8633
8880
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
8634
8881
|
} catch {
|
|
@@ -8660,9 +8907,9 @@ function normalizeVersion(version) {
|
|
|
8660
8907
|
|
|
8661
8908
|
// src/model/package-validate.ts
|
|
8662
8909
|
import { stat as stat12 } from "fs/promises";
|
|
8663
|
-
import { resolve as
|
|
8910
|
+
import { resolve as resolve17 } from "path";
|
|
8664
8911
|
async function validatePackage(root) {
|
|
8665
|
-
const packageRoot =
|
|
8912
|
+
const packageRoot = resolve17(root);
|
|
8666
8913
|
const findings = [];
|
|
8667
8914
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
8668
8915
|
if (!manifestPath) {
|
|
@@ -8763,7 +9010,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
8763
9010
|
try {
|
|
8764
9011
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
8765
9012
|
if (isCrossPackageSelector(selector)) return;
|
|
8766
|
-
const full =
|
|
9013
|
+
const full = resolve17(packageRoot, selector);
|
|
8767
9014
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
8768
9015
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
8769
9016
|
return;
|
|
@@ -8816,11 +9063,11 @@ function isCrossPackageSelector(value) {
|
|
|
8816
9063
|
}
|
|
8817
9064
|
|
|
8818
9065
|
// src/model/package-migrate.ts
|
|
8819
|
-
import { readFile as
|
|
8820
|
-
import { join as join37, resolve as
|
|
9066
|
+
import { readFile as readFile22, rename as rename3, writeFile as writeFile12 } from "fs/promises";
|
|
9067
|
+
import { join as join37, resolve as resolve18 } from "path";
|
|
8821
9068
|
import { applyEdits, modify, parse as parse2 } from "jsonc-parser";
|
|
8822
9069
|
async function migratePackageManifest(root) {
|
|
8823
|
-
const packageRoot =
|
|
9070
|
+
const packageRoot = resolve18(root);
|
|
8824
9071
|
for (const name of openPackManifestNames) {
|
|
8825
9072
|
const path = join37(packageRoot, name);
|
|
8826
9073
|
if (await pathExists(path)) {
|
|
@@ -8834,7 +9081,7 @@ async function migratePackageManifest(root) {
|
|
|
8834
9081
|
const from = join37(packageRoot, legacyName);
|
|
8835
9082
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
8836
9083
|
const to = join37(packageRoot, toName);
|
|
8837
|
-
const content = await
|
|
9084
|
+
const content = await readFile22(from, "utf8");
|
|
8838
9085
|
const updated = updateSchemaVersion(content);
|
|
8839
9086
|
declareMutationPath(from);
|
|
8840
9087
|
declareMutationPath(to);
|
|
@@ -8882,7 +9129,7 @@ function resolveCliVersion() {
|
|
|
8882
9129
|
}
|
|
8883
9130
|
|
|
8884
9131
|
// src/lifecycle/ownership.ts
|
|
8885
|
-
import { resolve as
|
|
9132
|
+
import { resolve as resolve19 } from "path";
|
|
8886
9133
|
async function planArtifactOwnershipHandoff(request) {
|
|
8887
9134
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
8888
9135
|
}
|
|
@@ -8895,7 +9142,9 @@ async function applyArtifactOwnershipHandoff(request) {
|
|
|
8895
9142
|
const scope = { installationType: request.installationType, stateKey: request.stateKey };
|
|
8896
9143
|
const lock = await acquireApplyLock(request.targetRoot, request.adapter, transport, {}, scope);
|
|
8897
9144
|
try {
|
|
8898
|
-
if (await
|
|
9145
|
+
if ((await listApplyJournals(request.targetRoot, request.adapter, transport, {
|
|
9146
|
+
installationType: request.installationType
|
|
9147
|
+
})).length > 0) {
|
|
8899
9148
|
throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
|
|
8900
9149
|
}
|
|
8901
9150
|
const plan = await validateOwnershipHandoff(request, transport);
|
|
@@ -9001,8 +9250,8 @@ function containedArtifactPath(targetRoot, relativePath) {
|
|
|
9001
9250
|
if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
|
|
9002
9251
|
throw new Error(`Unsafe managed artifact path: ${relativePath}`);
|
|
9003
9252
|
}
|
|
9004
|
-
const root =
|
|
9005
|
-
const candidate =
|
|
9253
|
+
const root = resolve19(targetRoot);
|
|
9254
|
+
const candidate = resolve19(root, relativePath);
|
|
9006
9255
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
9007
9256
|
throw new Error(`Managed artifact path escapes target root: ${relativePath}`);
|
|
9008
9257
|
}
|
|
@@ -9035,8 +9284,8 @@ function assertManifestIdentity(manifest, request) {
|
|
|
9035
9284
|
|
|
9036
9285
|
// src/version/policy.ts
|
|
9037
9286
|
import { execFile as execFile3 } from "child_process";
|
|
9038
|
-
import { readFile as
|
|
9039
|
-
import { join as join39, resolve as
|
|
9287
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
9288
|
+
import { join as join39, resolve as resolve20 } from "path";
|
|
9040
9289
|
import { promisify as promisify3 } from "util";
|
|
9041
9290
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
9042
9291
|
import { z as z5 } from "zod";
|
|
@@ -9130,7 +9379,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
9130
9379
|
if (tagged.length > 0) return tagged;
|
|
9131
9380
|
}
|
|
9132
9381
|
if (driverName === "local") {
|
|
9133
|
-
const root =
|
|
9382
|
+
const root = resolve20(workspaceRoot, pkg.source);
|
|
9134
9383
|
const manifest2 = await readPackageManifest(root);
|
|
9135
9384
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
9136
9385
|
try {
|
|
@@ -9244,7 +9493,7 @@ function versionCachePath(workspaceRoot) {
|
|
|
9244
9493
|
async function readVersionCache(path) {
|
|
9245
9494
|
if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
|
|
9246
9495
|
try {
|
|
9247
|
-
return versionCacheSchema.parse(JSON.parse(await
|
|
9496
|
+
return versionCacheSchema.parse(JSON.parse(await readFile23(path, "utf8")));
|
|
9248
9497
|
} catch {
|
|
9249
9498
|
return { schemaVersion: 1, sources: {} };
|
|
9250
9499
|
}
|
|
@@ -9252,8 +9501,8 @@ async function readVersionCache(path) {
|
|
|
9252
9501
|
|
|
9253
9502
|
// src/profile/members.ts
|
|
9254
9503
|
import { execFile as execFile4 } from "child_process";
|
|
9255
|
-
import { readFile as
|
|
9256
|
-
import { join as join40, resolve as
|
|
9504
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
9505
|
+
import { join as join40, resolve as resolve21 } from "path";
|
|
9257
9506
|
import { promisify as promisify4 } from "util";
|
|
9258
9507
|
import { z as z7 } from "zod";
|
|
9259
9508
|
|
|
@@ -9428,7 +9677,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
9428
9677
|
let stderr = "";
|
|
9429
9678
|
try {
|
|
9430
9679
|
if (member.transport === "local") {
|
|
9431
|
-
const workspace =
|
|
9680
|
+
const workspace = resolve21(parentWorkspace, member.workspace);
|
|
9432
9681
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
9433
9682
|
cwd: workspace,
|
|
9434
9683
|
env: env2,
|
|
@@ -9474,7 +9723,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
9474
9723
|
process.execPath,
|
|
9475
9724
|
[process.argv[1], "--no-update-check", ...args],
|
|
9476
9725
|
{
|
|
9477
|
-
cwd:
|
|
9726
|
+
cwd: resolve21(parentWorkspace, member.workspace),
|
|
9478
9727
|
env: env2,
|
|
9479
9728
|
maxBuffer: 20 * 1024 * 1024
|
|
9480
9729
|
}
|
|
@@ -9554,7 +9803,7 @@ function memberCachePath(workspaceRoot, profileName, memberId) {
|
|
|
9554
9803
|
async function readMemberCache(path) {
|
|
9555
9804
|
if (!await pathExists(path)) return void 0;
|
|
9556
9805
|
try {
|
|
9557
|
-
return memberCacheSchema.parse(JSON.parse(await
|
|
9806
|
+
return memberCacheSchema.parse(JSON.parse(await readFile24(path, "utf8")));
|
|
9558
9807
|
} catch {
|
|
9559
9808
|
return void 0;
|
|
9560
9809
|
}
|
|
@@ -9575,7 +9824,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
9575
9824
|
}
|
|
9576
9825
|
}
|
|
9577
9826
|
function compositeKey(workspaceRoot, profileName) {
|
|
9578
|
-
return `${
|
|
9827
|
+
return `${resolve21(workspaceRoot)}#${profileName}`;
|
|
9579
9828
|
}
|
|
9580
9829
|
|
|
9581
9830
|
// src/status/repository.ts
|
|
@@ -9623,8 +9872,8 @@ function valueAfter(lines, prefix) {
|
|
|
9623
9872
|
|
|
9624
9873
|
// src/catalogue/client.ts
|
|
9625
9874
|
import { createHash as createHash9 } from "crypto";
|
|
9626
|
-
import { readFile as
|
|
9627
|
-
import { homedir as
|
|
9875
|
+
import { readFile as readFile25, rm as rm8 } from "fs/promises";
|
|
9876
|
+
import { homedir as homedir9 } from "os";
|
|
9628
9877
|
import { join as join41 } from "path";
|
|
9629
9878
|
|
|
9630
9879
|
// src/model/catalogue.ts
|
|
@@ -9841,7 +10090,7 @@ var CatalogueClient = class {
|
|
|
9841
10090
|
async readCache() {
|
|
9842
10091
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
9843
10092
|
try {
|
|
9844
|
-
const value = JSON.parse(await
|
|
10093
|
+
const value = JSON.parse(await readFile25(this.cachePath, "utf8"));
|
|
9845
10094
|
const envelope = catalogueCacheEnvelopeSchema.parse(value);
|
|
9846
10095
|
if (envelope.contentHash) {
|
|
9847
10096
|
const contentHash = catalogueContentHash(envelope.enriched, envelope.vercel);
|
|
@@ -9902,7 +10151,7 @@ var CatalogueClient = class {
|
|
|
9902
10151
|
}
|
|
9903
10152
|
};
|
|
9904
10153
|
function defaultCatalogueCachePath() {
|
|
9905
|
-
return join41(
|
|
10154
|
+
return join41(homedir9(), ".agentwheel", "catalogue-cache.json");
|
|
9906
10155
|
}
|
|
9907
10156
|
function sameSources2(a, b) {
|
|
9908
10157
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -10271,7 +10520,7 @@ function includesToken(normalizedText, token) {
|
|
|
10271
10520
|
|
|
10272
10521
|
// src/semantic/index.ts
|
|
10273
10522
|
import { createHash as createHash10 } from "crypto";
|
|
10274
|
-
import { homedir as
|
|
10523
|
+
import { homedir as homedir10 } from "os";
|
|
10275
10524
|
import { join as join42 } from "path";
|
|
10276
10525
|
import { env, pipeline } from "@huggingface/transformers";
|
|
10277
10526
|
var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
|
|
@@ -10366,7 +10615,7 @@ var SemanticSearchClient = class {
|
|
|
10366
10615
|
}
|
|
10367
10616
|
};
|
|
10368
10617
|
async function embedQuery(query) {
|
|
10369
|
-
env.cacheDir = join42(
|
|
10618
|
+
env.cacheDir = join42(homedir10(), ".agentwheel", "semantic-models");
|
|
10370
10619
|
const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
|
|
10371
10620
|
revision: CONTRACT.model.revision,
|
|
10372
10621
|
dtype: CONTRACT.model.dtype,
|
|
@@ -10451,7 +10700,7 @@ function ensureTrailingSlash(value) {
|
|
|
10451
10700
|
|
|
10452
10701
|
// src/trial/skill.ts
|
|
10453
10702
|
import { createHash as createHash11 } from "crypto";
|
|
10454
|
-
import { readFile as
|
|
10703
|
+
import { readFile as readFile26, stat as stat13 } from "fs/promises";
|
|
10455
10704
|
import { join as join43 } from "path";
|
|
10456
10705
|
import { parse as parseYaml2 } from "yaml";
|
|
10457
10706
|
var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
|
|
@@ -10469,7 +10718,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
10469
10718
|
if (info.size > MAX_TRIAL_SKILL_BYTES) {
|
|
10470
10719
|
throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
|
|
10471
10720
|
}
|
|
10472
|
-
const content = await
|
|
10721
|
+
const content = await readFile26(path, "utf8");
|
|
10473
10722
|
const frontmatter = readSkillFrontmatter(content, artifact);
|
|
10474
10723
|
return {
|
|
10475
10724
|
schemaVersion: 1,
|
|
@@ -10498,166 +10747,11 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
10498
10747
|
return { name: value.name, description: value.description };
|
|
10499
10748
|
}
|
|
10500
10749
|
|
|
10501
|
-
// src/model/fleet.ts
|
|
10502
|
-
import { lstat as lstat2, readFile as readFile26, realpath } from "fs/promises";
|
|
10503
|
-
import { homedir as homedir10 } from "os";
|
|
10504
|
-
import { isAbsolute as isAbsolute2, resolve as resolve21 } from "path";
|
|
10505
|
-
async function resolveWorkspaceScope(request = {}) {
|
|
10506
|
-
const selected = [request.user === true, request.local === true, request.fleet !== void 0].filter(Boolean).length;
|
|
10507
|
-
if (selected > 1) {
|
|
10508
|
-
throw new Error("Choose exactly one workspace selector: --user, --local, or --fleet <id>.");
|
|
10509
|
-
}
|
|
10510
|
-
const globalRoot = resolve21(request.globalRoot ?? homedir10());
|
|
10511
|
-
if (request.user) {
|
|
10512
|
-
const config2 = await readWorkspaceConfig(globalRoot);
|
|
10513
|
-
assertNonFleetScope("user", config2);
|
|
10514
|
-
return { kind: "user", root: globalRoot, config: config2 };
|
|
10515
|
-
}
|
|
10516
|
-
if (request.fleet !== void 0) {
|
|
10517
|
-
const id = fleetIdSchema.parse(request.fleet);
|
|
10518
|
-
const registration = await showRegisteredFleet(id, { globalRoot });
|
|
10519
|
-
const root = await assertCanonicalDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
10520
|
-
const config2 = await readRequiredConfig(root, `fleet '${id}'`);
|
|
10521
|
-
assertFleetContract(id, registration, config2);
|
|
10522
|
-
return { kind: "fleet", root, fleetId: id, config: config2 };
|
|
10523
|
-
}
|
|
10524
|
-
const cwd = resolve21(request.cwd ?? process.cwd());
|
|
10525
|
-
const discoveredRoot = await findExistingWorkspaceRoot(cwd);
|
|
10526
|
-
if (!discoveredRoot || discoveredRoot === globalRoot) {
|
|
10527
|
-
if (request.local === true && cwd !== globalRoot) {
|
|
10528
|
-
return { kind: "local", root: cwd, config: await readWorkspaceConfig(cwd) };
|
|
10529
|
-
}
|
|
10530
|
-
throw missingScopeError(request.local === true ? "local" : "implicit local");
|
|
10531
|
-
}
|
|
10532
|
-
const config = await readRequiredConfig(discoveredRoot, "local");
|
|
10533
|
-
assertNonFleetScope("local", config);
|
|
10534
|
-
return { kind: "local", root: discoveredRoot, config };
|
|
10535
|
-
}
|
|
10536
|
-
async function registerFleet(request) {
|
|
10537
|
-
const id = fleetIdSchema.parse(request.id);
|
|
10538
|
-
const requiredPackages = sortedUnique5(request.requiredPackages);
|
|
10539
|
-
if (requiredPackages.length === 0) throw new Error("Fleet registration requires at least one --required-package <name>.");
|
|
10540
|
-
const globalRoot = resolve21(request.globalRoot ?? homedir10());
|
|
10541
|
-
const home = await readWorkspaceConfig(globalRoot);
|
|
10542
|
-
if (supportsFleetConfig(home) && home.fleetId) {
|
|
10543
|
-
throw new Error(`The home config is fleet '${home.fleetId}', so it cannot own the global fleet registry.`);
|
|
10544
|
-
}
|
|
10545
|
-
const existing = supportsFleetConfig(home) ? await canonicalizeExistingFleetRoots(home.fleets, globalRoot) : {};
|
|
10546
|
-
if (existing[id]) throw new Error(`Fleet '${id}' is already registered.`);
|
|
10547
|
-
const root = await assertCanonicalDirectory(request.root, `Fleet '${id}' root`);
|
|
10548
|
-
const duplicateRoot = Object.entries(existing).find(([, value]) => value.root === root);
|
|
10549
|
-
if (duplicateRoot) throw new Error(`Fleet root ${root} is already registered as '${duplicateRoot[0]}'.`);
|
|
10550
|
-
if (root === globalRoot) throw new Error("A fleet root must be outside the user config root contract.");
|
|
10551
|
-
const target = await readRequiredConfig(root, `fleet '${id}'`);
|
|
10552
|
-
const registration = registeredFleetSchema.parse({ root, requiredPackages });
|
|
10553
|
-
assertFleetContract(id, registration, target);
|
|
10554
|
-
const upgraded = workspaceConfigSchema.parse({
|
|
10555
|
-
...home,
|
|
10556
|
-
schemaVersion: CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
10557
|
-
exports: home.schemaVersion >= 2 ? home.exports : { selections: {} },
|
|
10558
|
-
fleets: { ...existing, [id]: registration }
|
|
10559
|
-
});
|
|
10560
|
-
const configPath = globalWorkspaceConfigPath(globalRoot);
|
|
10561
|
-
declareMutationPath(configPath);
|
|
10562
|
-
await writeJsonAtomic(configPath, upgraded);
|
|
10563
|
-
return { id, ...registration };
|
|
10564
|
-
}
|
|
10565
|
-
async function canonicalizeExistingFleetRoots(fleets, globalRoot) {
|
|
10566
|
-
const canonical = /* @__PURE__ */ new Map();
|
|
10567
|
-
const normalized = {};
|
|
10568
|
-
for (const [id, registration] of Object.entries(fleets).sort(([a], [b]) => a.localeCompare(b))) {
|
|
10569
|
-
const root = await canonicalizeDirectory(registration.root, `Registered fleet '${id}' root`);
|
|
10570
|
-
if (root === globalRoot) throw new Error(`Registered fleet '${id}' root must be outside the user config root contract.`);
|
|
10571
|
-
const incumbent = canonical.get(root);
|
|
10572
|
-
if (incumbent) {
|
|
10573
|
-
throw new Error(`Fleet root ${root} is registered more than once as '${incumbent}' and '${id}'.`);
|
|
10574
|
-
}
|
|
10575
|
-
canonical.set(root, id);
|
|
10576
|
-
normalized[id] = registeredFleetSchema.parse({ ...registration, root });
|
|
10577
|
-
}
|
|
10578
|
-
return normalized;
|
|
10579
|
-
}
|
|
10580
|
-
async function listRegisteredFleets(options = {}) {
|
|
10581
|
-
const globalRoot = resolve21(options.globalRoot ?? homedir10());
|
|
10582
|
-
const home = await readWorkspaceConfig(globalRoot);
|
|
10583
|
-
if (!supportsFleetConfig(home)) return [];
|
|
10584
|
-
return Object.entries(home.fleets).sort(([a], [b]) => a.localeCompare(b)).map(([id, registration]) => ({ id, ...registration }));
|
|
10585
|
-
}
|
|
10586
|
-
async function showRegisteredFleet(idInput, options = {}) {
|
|
10587
|
-
const id = fleetIdSchema.parse(idInput);
|
|
10588
|
-
const fleets = await listRegisteredFleets(options);
|
|
10589
|
-
const registration = fleets.find((candidate) => candidate.id === id);
|
|
10590
|
-
if (!registration) {
|
|
10591
|
-
throw new Error(`Unknown fleet '${id}'. Use 'agentwheel fleet list' or register it with 'agentwheel fleet register'.`);
|
|
10592
|
-
}
|
|
10593
|
-
return registration;
|
|
10594
|
-
}
|
|
10595
|
-
async function readRequiredConfig(root, label) {
|
|
10596
|
-
const path = workspaceConfigPath(root);
|
|
10597
|
-
if (!await pathExists(path)) throw missingScopeError(label);
|
|
10598
|
-
try {
|
|
10599
|
-
return workspaceConfigSchema.parse(JSON.parse(await readFile26(path, "utf8")));
|
|
10600
|
-
} catch (error) {
|
|
10601
|
-
throw new Error(`Invalid ${label} Agentwheel config at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
10602
|
-
}
|
|
10603
|
-
}
|
|
10604
|
-
function assertFleetContract(id, registration, config) {
|
|
10605
|
-
if (!supportsFleetConfig(config)) {
|
|
10606
|
-
throw new Error(`Fleet '${id}' config must use schemaVersion 3 or newer before registration or selection.`);
|
|
10607
|
-
}
|
|
10608
|
-
if (config.fleetId !== id) {
|
|
10609
|
-
throw new Error(`Fleet fleetId mismatch: expected '${id}', found '${config.fleetId ?? "missing"}'.`);
|
|
10610
|
-
}
|
|
10611
|
-
if (Object.keys(config.fleets).length > 0) {
|
|
10612
|
-
throw new Error(`Fleet '${id}' config may not contain the home fleets registry.`);
|
|
10613
|
-
}
|
|
10614
|
-
const names = new Set(config.packages.map((pkg) => pkg.name));
|
|
10615
|
-
for (const packageName of registration.requiredPackages) {
|
|
10616
|
-
if (!names.has(packageName)) throw new Error(`Fleet '${id}' is missing required package '${packageName}'.`);
|
|
10617
|
-
}
|
|
10618
|
-
}
|
|
10619
|
-
async function assertCanonicalDirectory(input, label) {
|
|
10620
|
-
const canonical = await canonicalizeDirectory(input, label);
|
|
10621
|
-
const normalized = resolve21(input);
|
|
10622
|
-
const stats = await lstat2(input);
|
|
10623
|
-
if (stats.isSymbolicLink() || canonical !== normalized || input !== normalized) {
|
|
10624
|
-
throw new Error(`${label} must be canonical and symlink-unambiguous: ${input} resolves to ${canonical}.`);
|
|
10625
|
-
}
|
|
10626
|
-
return canonical;
|
|
10627
|
-
}
|
|
10628
|
-
async function canonicalizeDirectory(input, label) {
|
|
10629
|
-
if (!isAbsolute2(input)) throw new Error(`${label} must be an absolute canonical path.`);
|
|
10630
|
-
let canonical;
|
|
10631
|
-
try {
|
|
10632
|
-
canonical = await realpath(input);
|
|
10633
|
-
} catch {
|
|
10634
|
-
throw new Error(`${label} does not exist: ${input}`);
|
|
10635
|
-
}
|
|
10636
|
-
const stats = await lstat2(canonical);
|
|
10637
|
-
if (!stats.isDirectory()) throw new Error(`${label} is not a directory: ${input}`);
|
|
10638
|
-
return canonical;
|
|
10639
|
-
}
|
|
10640
|
-
function missingScopeError(label) {
|
|
10641
|
-
return new Error(
|
|
10642
|
-
`Missing ${label} Agentwheel workspace config. Select --user, --local, or --fleet <id>, or run 'agentwheel init workspace' in the intended local root. Agentwheel never falls back to home desired state.`
|
|
10643
|
-
);
|
|
10644
|
-
}
|
|
10645
|
-
function assertNonFleetScope(kind, config) {
|
|
10646
|
-
if (supportsFleetConfig(config) && config.fleetId) {
|
|
10647
|
-
throw new Error(
|
|
10648
|
-
`The ${kind} config declares fleetId '${config.fleetId}'. Select it through the registered --fleet <id> scope.`
|
|
10649
|
-
);
|
|
10650
|
-
}
|
|
10651
|
-
}
|
|
10652
|
-
function sortedUnique5(values) {
|
|
10653
|
-
return [...new Set(values.map((value) => value.trim()).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
10654
|
-
}
|
|
10655
|
-
|
|
10656
10750
|
// src/lifecycle/fleet-normalize.ts
|
|
10657
10751
|
import { createHash as createHash12 } from "crypto";
|
|
10658
10752
|
import { readFile as readFile27, readdir as readdir9, rm as rm9 } from "fs/promises";
|
|
10659
10753
|
import { homedir as homedir11 } from "os";
|
|
10660
|
-
import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as
|
|
10754
|
+
import { basename as basename19, isAbsolute as isAbsolute3, join as join44, relative as relative8, resolve as resolve22 } from "path";
|
|
10661
10755
|
async function planFleetNormalization(request) {
|
|
10662
10756
|
const normalizedRequest = normalizeRequest(request);
|
|
10663
10757
|
const destinationScope = await resolveWorkspaceScope({
|
|
@@ -11403,7 +11497,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
11403
11497
|
if (!path.endsWith(".graph-lock.json")) continue;
|
|
11404
11498
|
const lock = await readGraphLock(path);
|
|
11405
11499
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
11406
|
-
const parts =
|
|
11500
|
+
const parts = relative8(root, path).split(/[\\/]/);
|
|
11407
11501
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
11408
11502
|
const [targetKey2, adapter, fileName] = parts;
|
|
11409
11503
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
@@ -11976,6 +12070,7 @@ async function acquireJournalManifestLocks(manifests) {
|
|
|
11976
12070
|
try {
|
|
11977
12071
|
const unique = /* @__PURE__ */ new Map();
|
|
11978
12072
|
for (const item of manifests) unique.set(item.path, item);
|
|
12073
|
+
const acquiredPaths = /* @__PURE__ */ new Set();
|
|
11979
12074
|
for (const item of [...unique.values()].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
11980
12075
|
const raw = await readOptionalRecord(item.path) ?? item.before ?? item.after;
|
|
11981
12076
|
if (!raw && !(item.targetRoot && item.adapter && item.installationType && item.stateKey)) {
|
|
@@ -11988,9 +12083,13 @@ async function acquireJournalManifestLocks(manifests) {
|
|
|
11988
12083
|
const installationType = manifest?.installationType ?? item.installationType;
|
|
11989
12084
|
const stateKey = manifest?.stateKey ?? item.stateKey;
|
|
11990
12085
|
const scope = { installationType, stateKey };
|
|
11991
|
-
const
|
|
11992
|
-
|
|
11993
|
-
|
|
12086
|
+
const lockPath = applyLockPath(targetRoot, adapter, scope);
|
|
12087
|
+
if (!acquiredPaths.has(lockPath)) {
|
|
12088
|
+
const lock = await acquireApplyLock(targetRoot, adapter, void 0, {}, scope);
|
|
12089
|
+
locks.push(lock);
|
|
12090
|
+
acquiredPaths.add(lockPath);
|
|
12091
|
+
}
|
|
12092
|
+
if ((await listApplyJournals(targetRoot, adapter, void 0, { installationType })).length > 0) {
|
|
11994
12093
|
throw new Error(`Cannot normalize while an Agentwheel apply journal is pending for ${item.path}.`);
|
|
11995
12094
|
}
|
|
11996
12095
|
}
|
|
@@ -13522,10 +13621,13 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
13522
13621
|
targetOptions.focusedArtifact,
|
|
13523
13622
|
focusedArtifactOwnerKeys(result.bundle.graphLock, previousGroupLock, scopedRootId, targetOptions.focusedArtifact)
|
|
13524
13623
|
)).map((operation) => operation.relativeDestPath);
|
|
13624
|
+
const ownership = await resolveWorkspaceOwnershipScope(group.target.workspaceRoot, {
|
|
13625
|
+
fleetId: group.target.fleetId
|
|
13626
|
+
});
|
|
13525
13627
|
await assertNoForeignWorkspaceStateForPlan(scopedResult.plan, {
|
|
13526
13628
|
transport,
|
|
13527
13629
|
workspaceRoot: group.target.workspaceRoot,
|
|
13528
|
-
workspaceOwner: workspaceOwnerForRoot(
|
|
13630
|
+
workspaceOwner: workspaceOwnerForRoot(ownership.root, ownership.fleetId),
|
|
13529
13631
|
plannedPaths: focusedPaths
|
|
13530
13632
|
});
|
|
13531
13633
|
}
|
|
@@ -14092,6 +14194,8 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
14092
14194
|
semanticPlugin: entry.semanticPlugin,
|
|
14093
14195
|
execute: entry.executed,
|
|
14094
14196
|
mergeStrategy: entry.mergeStrategy,
|
|
14197
|
+
mergeRemoval: "mergeRemoval" in entry ? entry.mergeRemoval : void 0,
|
|
14198
|
+
mergeCreatedDestination: "mergeCreatedDestination" in entry ? entry.mergeCreatedDestination : void 0,
|
|
14095
14199
|
composedFrom: entry.composedFrom,
|
|
14096
14200
|
installName: fresh?.installName ?? ("installName" in entry ? entry.installName : entry.artifactName),
|
|
14097
14201
|
logicalSelector: fresh?.logicalSelector ?? ("logicalSelector" in entry ? entry.logicalSelector : `${entry.artifactType}/${entry.artifactName}`),
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
applyLockPath,
|
|
9
9
|
assertApplyJournalRecoveryAllowed,
|
|
10
10
|
assertGovernedRuntimeTransportSupported,
|
|
11
|
+
listApplyJournals,
|
|
11
12
|
localPathExists,
|
|
12
13
|
mutationMetadataForApplyJournal,
|
|
13
14
|
readApplyJournal,
|
|
@@ -16,7 +17,7 @@ import {
|
|
|
16
17
|
removeApplyJournal,
|
|
17
18
|
rollbackCompletedOperations,
|
|
18
19
|
writeApplyJournal
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BJEZXIXX.js";
|
|
20
21
|
import "./chunk-7VPI5J5Y.js";
|
|
21
22
|
export {
|
|
22
23
|
abortApplyJournal,
|
|
@@ -27,6 +28,7 @@ export {
|
|
|
27
28
|
applyLockPath,
|
|
28
29
|
assertApplyJournalRecoveryAllowed,
|
|
29
30
|
assertGovernedRuntimeTransportSupported,
|
|
31
|
+
listApplyJournals,
|
|
30
32
|
localPathExists,
|
|
31
33
|
mutationMetadataForApplyJournal,
|
|
32
34
|
readApplyJournal,
|
package/openpack.json
CHANGED
package/package.json
CHANGED