agentwheel 0.9.0 → 0.10.0
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/README.md +31 -7
- package/dist/index.js +154 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p align="center"><strong>One source. Every agent.</strong></p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
Weave
|
|
10
|
+
Weave skills, rules, instructions, commands, subagents, MCP, hooks, settings, and plugins across every AI agent you use,
|
|
11
11
|
from any source, reconciled into each runtime with your private tweaks intact.
|
|
12
12
|
</p>
|
|
13
13
|
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
## Why agentwheel?
|
|
17
17
|
|
|
18
|
-
Your AI agents are multiplying. Claude
|
|
19
|
-
wants skills, rules, instructions, commands,
|
|
18
|
+
Your AI agents are multiplying. OpenClaw, Claude Code, Codex CLI, Copilot, Hermes — and the next
|
|
19
|
+
harness your team adopts. Each one wants its own skills, rules, instructions, commands, subagents,
|
|
20
|
+
MCP servers, hooks, and settings, in its own layout.
|
|
20
21
|
|
|
21
22
|
**agentwheel makes that one declared state.** Add packages to `.agentwheel/config.json`, preview the
|
|
22
23
|
runtime changes, then install the declared state into each target. Updates move tracking sources
|
|
@@ -37,6 +38,20 @@ live in your workspace, and runtimes stay generated output.
|
|
|
37
38
|
> `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for one release only
|
|
38
39
|
> so old bootstrapped skills can self-update; use `install` in all new docs and scripts.
|
|
39
40
|
|
|
41
|
+
## Supported runtimes & resources
|
|
42
|
+
|
|
43
|
+
agentwheel installs OpenPack resources into five built-in runtimes and into custom harnesses:
|
|
44
|
+
|
|
45
|
+
- **OpenClaw** — `.openclaw/`
|
|
46
|
+
- **Claude Code** — `.claude/`
|
|
47
|
+
- **Codex CLI** — `.codex/`
|
|
48
|
+
- **GitHub Copilot** — `.github/`
|
|
49
|
+
- **Hermes** — `.hermes/`
|
|
50
|
+
- **Bring your own** — JSONC config adapters with `--adapter-config`, or programmatic adapters with `--adapter-module`
|
|
51
|
+
|
|
52
|
+
Supported resource types include instructions, rules, skills, commands, subagents, MCP, hooks,
|
|
53
|
+
settings, plugins, and fragments; see the full per-runtime target table below.
|
|
54
|
+
|
|
40
55
|
## Core Commands
|
|
41
56
|
|
|
42
57
|
| Command | Meaning |
|
|
@@ -133,6 +148,7 @@ For a control-plane setup, define named agents in config. Global config lives at
|
|
|
133
148
|
agentwheel install --agent lab-openclaw
|
|
134
149
|
agentwheel install --all
|
|
135
150
|
agentwheel install --profile daily
|
|
151
|
+
agentwheel install --all-detected
|
|
136
152
|
```
|
|
137
153
|
|
|
138
154
|
SSH targets use the same manifest and drift model as local targets. Planning reads the remote
|
|
@@ -146,7 +162,9 @@ agentwheel init --fleet-example
|
|
|
146
162
|
```
|
|
147
163
|
|
|
148
164
|
Target resolution order is exact: `--target-root` wins, then `--agent`, then auto-detect from the
|
|
149
|
-
current directory, then fallback to the current directory.
|
|
165
|
+
current directory, then fallback to the current directory. `--all-detected` is an explicit escape hatch
|
|
166
|
+
for applying to every runtime marker found in the current directory or `--target-root`; `--all` remains
|
|
167
|
+
reserved for configured agents.
|
|
150
168
|
|
|
151
169
|
## Core Ideas
|
|
152
170
|
|
|
@@ -290,15 +308,21 @@ published:
|
|
|
290
308
|
agentwheel install ./my-pack --adapter-config ./myco-internal.jsonc
|
|
291
309
|
```
|
|
292
310
|
|
|
311
|
+
For adapter behavior that needs code, load a programmatic adapter module:
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
agentwheel install ./my-pack --adapter-module ./myco-adapter.js
|
|
315
|
+
```
|
|
316
|
+
|
|
293
317
|
Built-in runtime targets:
|
|
294
318
|
|
|
295
319
|
| Runtime | Main targets |
|
|
296
320
|
|---|---|
|
|
297
|
-
| **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, MCP/hooks/settings, semantic plugin planning |
|
|
321
|
+
| **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, `.openclaw/agents`, MCP/hooks/settings, semantic plugin planning |
|
|
298
322
|
| **Claude Code** | `.claude/CLAUDE.md`, `.claude/skills`, `.claude/commands`, `.claude/agents`, `.claude/rules`, `.claude/.mcp.json`, `.claude/settings.json` |
|
|
299
323
|
| **Codex CLI** | `.codex/AGENTS.md`, `.codex/skills`, `.codex/commands`, `.codex/agents`, `.codex/rules`, `.codex/config.toml`, `.codex/hooks.json` |
|
|
300
|
-
| **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, MCP/hooks/settings |
|
|
301
|
-
| **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts` |
|
|
324
|
+
| **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, `.hermes/agents`, MCP/hooks/settings |
|
|
325
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts`, `.github/skills`, `.github/agents`, `.vscode/mcp.json` |
|
|
302
326
|
|
|
303
327
|
## Docs
|
|
304
328
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/cli/index.ts
|
|
11
11
|
import { mkdir as mkdir14, rm as rm9, writeFile as writeFile12 } from "fs/promises";
|
|
12
|
-
import { dirname as dirname21, join as join28, resolve as
|
|
12
|
+
import { dirname as dirname21, join as join28, resolve as resolve18 } from "path";
|
|
13
13
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14
14
|
import { Command } from "commander";
|
|
15
15
|
|
|
@@ -131,9 +131,9 @@ var copilotAdapter = {
|
|
|
131
131
|
instructions: { enabled: true, dest: ".github/copilot-instructions.md" },
|
|
132
132
|
rules: { enabled: true, dest: ".github/instructions" },
|
|
133
133
|
commands: { enabled: true, dest: ".github/prompts" },
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
skills: { enabled: true, dest: ".github/skills" },
|
|
135
|
+
subagents: { enabled: true, dest: ".github/agents" },
|
|
136
|
+
mcp: { enabled: true, dest: ".vscode/mcp.json", merge: "json-deep" }
|
|
137
137
|
}
|
|
138
138
|
};
|
|
139
139
|
|
|
@@ -162,6 +162,7 @@ var hermesAdapter = {
|
|
|
162
162
|
rules: { enabled: true, dest: ".hermes/rules" },
|
|
163
163
|
skills: { enabled: true, dest: ".hermes/skills" },
|
|
164
164
|
commands: { enabled: true, dest: ".hermes/commands" },
|
|
165
|
+
subagents: { enabled: true, dest: ".hermes/agents" },
|
|
165
166
|
mcp: { enabled: true, dest: ".hermes/mcp", merge: "json-deep" },
|
|
166
167
|
hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
|
|
167
168
|
settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
|
|
@@ -177,6 +178,7 @@ var openClawAdapter = {
|
|
|
177
178
|
rules: { enabled: true, dest: ".openclaw/rules" },
|
|
178
179
|
skills: { enabled: true, dest: ".openclaw/skills" },
|
|
179
180
|
commands: { enabled: true, dest: ".openclaw/commands" },
|
|
181
|
+
subagents: { enabled: true, dest: ".openclaw/agents" },
|
|
180
182
|
mcp: { enabled: true, dest: ".openclaw/mcp", merge: "json-deep" },
|
|
181
183
|
hooks: { enabled: true, dest: ".openclaw/hooks", merge: "json-deep" },
|
|
182
184
|
settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
|
|
@@ -542,13 +544,13 @@ async function spawnWithInput(command, args, input) {
|
|
|
542
544
|
if (result.stderr) throw new Error(result.stderr);
|
|
543
545
|
}
|
|
544
546
|
function waitForProcess(child, label) {
|
|
545
|
-
return new Promise((
|
|
547
|
+
return new Promise((resolve19, reject) => {
|
|
546
548
|
const stderr = [];
|
|
547
549
|
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
548
550
|
child.on("error", reject);
|
|
549
551
|
child.on("close", (code) => {
|
|
550
552
|
const message = Buffer.concat(stderr).toString("utf8");
|
|
551
|
-
if (code === 0)
|
|
553
|
+
if (code === 0) resolve19({ stderr: "" });
|
|
552
554
|
else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
|
|
553
555
|
});
|
|
554
556
|
});
|
|
@@ -654,6 +656,7 @@ import { resolve as resolve3 } from "path";
|
|
|
654
656
|
// src/model/manifest.ts
|
|
655
657
|
import { z as z4 } from "zod";
|
|
656
658
|
var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
|
|
659
|
+
var legacyUnownedWorkspaceOwner = "legacy:unowned";
|
|
657
660
|
var manifestEntryV1Schema = z4.object({
|
|
658
661
|
path: z4.string().min(1),
|
|
659
662
|
artifactType: artifactTypeSchema,
|
|
@@ -676,6 +679,7 @@ var manifestEntrySchema = manifestEntryV1Schema.extend({
|
|
|
676
679
|
dependencyRole: dependencyRoleSchema.default("root"),
|
|
677
680
|
owners: z4.array(z4.string().min(1)).min(1),
|
|
678
681
|
refCount: z4.number().int().positive(),
|
|
682
|
+
workspaceOwner: z4.string().min(1).default(legacyUnownedWorkspaceOwner),
|
|
679
683
|
graphLockDigest: z4.string().min(1).optional()
|
|
680
684
|
}).transform((entry) => {
|
|
681
685
|
const owners = [...new Set(entry.owners)].sort();
|
|
@@ -1388,6 +1392,7 @@ function manifestEntryForOperation(operation, values) {
|
|
|
1388
1392
|
dependencyRole: operation.dependencyRole ?? "root",
|
|
1389
1393
|
owners,
|
|
1390
1394
|
refCount: owners.length,
|
|
1395
|
+
workspaceOwner: operation.workspaceOwner ?? "workspace:unknown",
|
|
1391
1396
|
kind: operation.kind,
|
|
1392
1397
|
hash: values.hash,
|
|
1393
1398
|
sourceHash: values.sourceHash,
|
|
@@ -1476,6 +1481,10 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1476
1481
|
return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, options);
|
|
1477
1482
|
}
|
|
1478
1483
|
async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
|
|
1484
|
+
const workspaceOwner = options.workspaceOwner;
|
|
1485
|
+
if (workspaceOwner) {
|
|
1486
|
+
for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
|
|
1487
|
+
}
|
|
1479
1488
|
for (const op of desiredOps) assertOperationContained(op, targetRoot);
|
|
1480
1489
|
const migration = await migrateManifestForPlan(manifest, desiredOps, targetRoot, transport);
|
|
1481
1490
|
const effectiveEntries = migration.entries;
|
|
@@ -1518,6 +1527,12 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1518
1527
|
continue;
|
|
1519
1528
|
}
|
|
1520
1529
|
const existing = manifestByPath.get(op.relativeDestPath);
|
|
1530
|
+
if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
|
|
1531
|
+
if (!await canAdoptLegacyUnownedEntry(existing, op, transport)) {
|
|
1532
|
+
operations.push(keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op));
|
|
1533
|
+
continue;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1521
1536
|
const exists = await transport.pathExists(op.destPath);
|
|
1522
1537
|
if (!exists) {
|
|
1523
1538
|
operations.push({ ...op, action: "create", reason: "destination missing" });
|
|
@@ -1559,6 +1574,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1559
1574
|
const destPath = join5(targetRoot, entry.path);
|
|
1560
1575
|
if (!await transport.pathExists(destPath)) continue;
|
|
1561
1576
|
const currentHash = await transport.hashPath(destPath);
|
|
1577
|
+
if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
|
|
1578
|
+
operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1562
1581
|
if (currentHash !== entry.hash) {
|
|
1563
1582
|
operations.push({
|
|
1564
1583
|
action: "drift",
|
|
@@ -1714,6 +1733,16 @@ function operationMatchesManifestEntry(op, entry) {
|
|
|
1714
1733
|
if ("graphNodeId" in entry && entry.graphNodeId && op.graphNodeId && entry.graphNodeId === op.graphNodeId) return true;
|
|
1715
1734
|
return op.packageName !== void 0 && entry.packageName === op.packageName;
|
|
1716
1735
|
}
|
|
1736
|
+
function entryOwnedByWorkspace(entry, workspaceOwner) {
|
|
1737
|
+
return "workspaceOwner" in entry && entry.workspaceOwner === workspaceOwner;
|
|
1738
|
+
}
|
|
1739
|
+
async function canAdoptLegacyUnownedEntry(entry, op, transport) {
|
|
1740
|
+
if (!("workspaceOwner" in entry) || entry.workspaceOwner !== legacyUnownedWorkspaceOwner) return false;
|
|
1741
|
+
if (entry.artifactType !== op.artifactType || entry.kind !== op.kind) return false;
|
|
1742
|
+
if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
|
|
1743
|
+
if (!await transport.pathExists(op.destPath)) return false;
|
|
1744
|
+
return await transport.hashPath(op.destPath) === entry.hash;
|
|
1745
|
+
}
|
|
1717
1746
|
function collisionOperation(op, group, incumbent) {
|
|
1718
1747
|
const owners = group.map(describeOperationOwner).sort();
|
|
1719
1748
|
const incumbentText = incumbent ? `; incumbent ${describeOperationOwner(incumbent)} keeps the plain name` : "";
|
|
@@ -1737,6 +1766,7 @@ function adoptLegacyEntry(entry, op) {
|
|
|
1737
1766
|
dependencyRole: op.dependencyRole ?? "root",
|
|
1738
1767
|
owners,
|
|
1739
1768
|
refCount: owners.length,
|
|
1769
|
+
workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
|
|
1740
1770
|
graphLockDigest: op.graphLockDigest,
|
|
1741
1771
|
composedFrom: op.composedFrom ?? entry.composedFrom
|
|
1742
1772
|
};
|
|
@@ -1759,6 +1789,7 @@ function operationMetadataFromEntry(entry) {
|
|
|
1759
1789
|
graphNodeId: entry.graphNodeId,
|
|
1760
1790
|
dependencyRole: entry.dependencyRole,
|
|
1761
1791
|
owners: entry.owners,
|
|
1792
|
+
workspaceOwner: entry.workspaceOwner,
|
|
1762
1793
|
graphLockDigest: entry.graphLockDigest
|
|
1763
1794
|
};
|
|
1764
1795
|
}
|
|
@@ -1766,7 +1797,31 @@ function operationMetadataFromEntry(entry) {
|
|
|
1766
1797
|
installName: entry.artifactName,
|
|
1767
1798
|
logicalSelector: `${entry.artifactType}/${entry.artifactName}`,
|
|
1768
1799
|
dependencyRole: "root",
|
|
1769
|
-
owners: [entry.packageName ?? "legacy"]
|
|
1800
|
+
owners: [entry.packageName ?? "legacy"],
|
|
1801
|
+
workspaceOwner: legacyUnownedWorkspaceOwner
|
|
1802
|
+
};
|
|
1803
|
+
}
|
|
1804
|
+
function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, operation, currentHash) {
|
|
1805
|
+
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
1806
|
+
return {
|
|
1807
|
+
action: "keep",
|
|
1808
|
+
artifactType: entry.artifactType,
|
|
1809
|
+
artifactName: entry.artifactName,
|
|
1810
|
+
kind: entry.kind,
|
|
1811
|
+
destPath: operation?.destPath ?? join5(targetRoot, entry.path),
|
|
1812
|
+
relativeDestPath: entry.path,
|
|
1813
|
+
desiredHash: entry.sourceHash,
|
|
1814
|
+
currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
|
|
1815
|
+
manifestHash: entry.hash,
|
|
1816
|
+
reason: `foreign artifact owned by ${owner}; kept outside workspace ${workspaceOwner}`,
|
|
1817
|
+
channel: entry.channel,
|
|
1818
|
+
packageName: entry.packageName,
|
|
1819
|
+
semanticCommand: entry.semanticCommand,
|
|
1820
|
+
execute: entry.executed,
|
|
1821
|
+
mergeStrategy: entry.mergeStrategy,
|
|
1822
|
+
composedFrom: entry.composedFrom,
|
|
1823
|
+
preserveInManifest: true,
|
|
1824
|
+
...operationMetadataFromEntry(entry)
|
|
1770
1825
|
};
|
|
1771
1826
|
}
|
|
1772
1827
|
function normalizeOperationOwners(op) {
|
|
@@ -2714,7 +2769,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
|
2714
2769
|
if (Date.now() - started > timeoutMs) {
|
|
2715
2770
|
throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
|
|
2716
2771
|
}
|
|
2717
|
-
await new Promise((
|
|
2772
|
+
await new Promise((resolve19) => setTimeout(resolve19, 50));
|
|
2718
2773
|
}
|
|
2719
2774
|
}
|
|
2720
2775
|
try {
|
|
@@ -4318,13 +4373,33 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4318
4373
|
async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
|
|
4319
4374
|
try {
|
|
4320
4375
|
const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
|
|
4321
|
-
|
|
4322
|
-
|
|
4376
|
+
let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
|
|
4377
|
+
let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
|
|
4323
4378
|
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
4324
4379
|
workspaceRoot: options.workspaceRoot,
|
|
4325
4380
|
ref: requirement.ref,
|
|
4326
4381
|
registryClient: options.registryClient
|
|
4327
4382
|
});
|
|
4383
|
+
if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
|
|
4384
|
+
const declared = await normalizeDependencySource(requirement.source, {
|
|
4385
|
+
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
4386
|
+
workspaceRoot: options.workspaceRoot,
|
|
4387
|
+
ref: requirement.ref,
|
|
4388
|
+
registryClient: options.registryClient
|
|
4389
|
+
});
|
|
4390
|
+
if (lockedRootSourceDrifted(declared, lockedByReference.node)) {
|
|
4391
|
+
if (options.frozenLock || options.offline) {
|
|
4392
|
+
throw new Error(
|
|
4393
|
+
`${lockLabel} root '${requirement.rootId}' source differs from declared source:
|
|
4394
|
+
- declared: ${declared.normalizedSource}
|
|
4395
|
+
- locked: ${lockedByReference.node.normalizedSource}
|
|
4396
|
+
Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
|
|
4397
|
+
);
|
|
4398
|
+
}
|
|
4399
|
+
lockedByReference = void 0;
|
|
4400
|
+
normalized = declared;
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4328
4403
|
const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
|
|
4329
4404
|
let fetched;
|
|
4330
4405
|
try {
|
|
@@ -4747,6 +4822,18 @@ function lockedNodeForRequirementReference(requirement, options, label) {
|
|
|
4747
4822
|
requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
|
|
4748
4823
|
};
|
|
4749
4824
|
}
|
|
4825
|
+
function shouldCheckLockedRootSource(requirement) {
|
|
4826
|
+
if (!requirement.useLock) return false;
|
|
4827
|
+
if (requirement.depth !== 0 || !requirement.rootId) return false;
|
|
4828
|
+
return isExplicitNonRegistrySource(requirement.source);
|
|
4829
|
+
}
|
|
4830
|
+
function isExplicitNonRegistrySource(source) {
|
|
4831
|
+
const trimmed = source.trim();
|
|
4832
|
+
return trimmed === "~" || trimmed.startsWith("~/") || trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("/") || trimmed.startsWith("local:") || trimmed.startsWith("github:") || trimmed.startsWith("git:") || trimmed.startsWith("skillkit:") || trimmed.startsWith("vercel:");
|
|
4833
|
+
}
|
|
4834
|
+
function lockedRootSourceDrifted(declared, locked) {
|
|
4835
|
+
return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
|
|
4836
|
+
}
|
|
4750
4837
|
function normalizedSourceFromLockedNode(node) {
|
|
4751
4838
|
return {
|
|
4752
4839
|
source: node.source,
|
|
@@ -4765,8 +4852,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
4765
4852
|
async function withCachePathLock(path, fn) {
|
|
4766
4853
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
4767
4854
|
let release = () => void 0;
|
|
4768
|
-
const current = previous.then(() => new Promise((
|
|
4769
|
-
release =
|
|
4855
|
+
const current = previous.then(() => new Promise((resolve19) => {
|
|
4856
|
+
release = resolve19;
|
|
4770
4857
|
}));
|
|
4771
4858
|
cacheLocks.set(path, current);
|
|
4772
4859
|
await previous;
|
|
@@ -4974,7 +5061,7 @@ import { rm as rm8 } from "fs/promises";
|
|
|
4974
5061
|
// src/lifecycle/source-plan.ts
|
|
4975
5062
|
import { createHash as createHash6 } from "crypto";
|
|
4976
5063
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
4977
|
-
import { dirname as dirname17, join as join22 } from "path";
|
|
5064
|
+
import { dirname as dirname17, join as join22, resolve as resolve14 } from "path";
|
|
4978
5065
|
|
|
4979
5066
|
// src/resolve/graph-diff.ts
|
|
4980
5067
|
function diffGraphLocks(previous, next) {
|
|
@@ -5512,7 +5599,8 @@ async function createGraphSourcePlan(options) {
|
|
|
5512
5599
|
const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
|
|
5513
5600
|
const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
|
|
5514
5601
|
baseRevision: manifest?.revision ?? null,
|
|
5515
|
-
graphLockDigest
|
|
5602
|
+
graphLockDigest,
|
|
5603
|
+
workspaceOwner: workspaceOwnerId(workspaceRoot)
|
|
5516
5604
|
});
|
|
5517
5605
|
return {
|
|
5518
5606
|
plan,
|
|
@@ -5570,6 +5658,9 @@ function sanitizePathSegment(value) {
|
|
|
5570
5658
|
function digestGraphLock(lock) {
|
|
5571
5659
|
return createHash6("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
|
|
5572
5660
|
}
|
|
5661
|
+
function workspaceOwnerId(workspaceRoot) {
|
|
5662
|
+
return `workspace-root:${resolve14(workspaceRoot)}`;
|
|
5663
|
+
}
|
|
5573
5664
|
function assertFrozenGraph(previousLock, graph, frozen, label) {
|
|
5574
5665
|
if (!frozen) return;
|
|
5575
5666
|
if (!previousLock) {
|
|
@@ -5743,7 +5834,7 @@ async function packageFromSource(source, options) {
|
|
|
5743
5834
|
}
|
|
5744
5835
|
|
|
5745
5836
|
// src/runtime/target.ts
|
|
5746
|
-
import { basename as basename12, dirname as dirname18, join as join23, resolve as
|
|
5837
|
+
import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve15 } from "path";
|
|
5747
5838
|
var runtimeMarkers = [
|
|
5748
5839
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
5749
5840
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -5752,9 +5843,9 @@ var runtimeMarkers = [
|
|
|
5752
5843
|
{ adapter: "copilot", dirs: [".github"] }
|
|
5753
5844
|
];
|
|
5754
5845
|
async function resolveRuntimeTarget(request = {}) {
|
|
5755
|
-
const cwd =
|
|
5846
|
+
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
5756
5847
|
if (request.targetRoot) {
|
|
5757
|
-
const targetRoot =
|
|
5848
|
+
const targetRoot = resolve15(request.targetRoot);
|
|
5758
5849
|
return {
|
|
5759
5850
|
adapter: request.adapter ?? "openclaw",
|
|
5760
5851
|
targetRoot,
|
|
@@ -5783,7 +5874,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
5783
5874
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
5784
5875
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
5785
5876
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
5786
|
-
const cwd =
|
|
5877
|
+
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
5787
5878
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
5788
5879
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
5789
5880
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
@@ -5792,8 +5883,29 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
5792
5883
|
}
|
|
5793
5884
|
return targets;
|
|
5794
5885
|
}
|
|
5886
|
+
async function resolveAllDetectedRuntimeTargets(request = {}) {
|
|
5887
|
+
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
5888
|
+
const scanRoot = runtimeScanRoot(request);
|
|
5889
|
+
const matches = await detectRuntimeTargets(scanRoot, request.adapter);
|
|
5890
|
+
if (matches.length === 0) {
|
|
5891
|
+
throw new Error(`No runtime directories detected at ${scanRoot}. Pass --target-root or --agent.`);
|
|
5892
|
+
}
|
|
5893
|
+
return Promise.all(matches.map(async (match) => ({
|
|
5894
|
+
...match,
|
|
5895
|
+
workspaceRoot: await findWorkspaceRoot(match.targetRoot),
|
|
5896
|
+
transport: "local",
|
|
5897
|
+
source: "auto-detect"
|
|
5898
|
+
})));
|
|
5899
|
+
}
|
|
5795
5900
|
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
5796
|
-
const
|
|
5901
|
+
const unique = await detectRuntimeTargets(cwd, adapterFilter);
|
|
5902
|
+
if (unique.length > 1) {
|
|
5903
|
+
throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter, --agent, or --all-detected.`);
|
|
5904
|
+
}
|
|
5905
|
+
return unique[0];
|
|
5906
|
+
}
|
|
5907
|
+
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
5908
|
+
const root = resolve15(cwd);
|
|
5797
5909
|
const matches = [];
|
|
5798
5910
|
for (const marker of runtimeMarkers) {
|
|
5799
5911
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
@@ -5805,11 +5917,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
5805
5917
|
}
|
|
5806
5918
|
}
|
|
5807
5919
|
}
|
|
5808
|
-
|
|
5809
|
-
if (unique.length > 1) {
|
|
5810
|
-
throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter or --agent.`);
|
|
5811
|
-
}
|
|
5812
|
-
return unique[0];
|
|
5920
|
+
return dedupeTargets(matches);
|
|
5813
5921
|
}
|
|
5814
5922
|
function targetFromAgent(name, config, workspaceRoot) {
|
|
5815
5923
|
const agent = config.agents[name];
|
|
@@ -5838,6 +5946,11 @@ function dedupeTargets(matches) {
|
|
|
5838
5946
|
}
|
|
5839
5947
|
return [...byKey.values()];
|
|
5840
5948
|
}
|
|
5949
|
+
function runtimeScanRoot(request) {
|
|
5950
|
+
const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
5951
|
+
if (request.targetRoot) return root;
|
|
5952
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename12(root))) ? dirname18(root) : root;
|
|
5953
|
+
}
|
|
5841
5954
|
|
|
5842
5955
|
// src/cli/update-check.ts
|
|
5843
5956
|
import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
|
|
@@ -5919,9 +6032,9 @@ function normalizeVersion(version) {
|
|
|
5919
6032
|
|
|
5920
6033
|
// src/model/package-validate.ts
|
|
5921
6034
|
import { stat as stat11 } from "fs/promises";
|
|
5922
|
-
import { resolve as
|
|
6035
|
+
import { resolve as resolve16 } from "path";
|
|
5923
6036
|
async function validatePackage(root) {
|
|
5924
|
-
const packageRoot =
|
|
6037
|
+
const packageRoot = resolve16(root);
|
|
5925
6038
|
const findings = [];
|
|
5926
6039
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
5927
6040
|
if (!manifestPath) {
|
|
@@ -5989,7 +6102,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
5989
6102
|
try {
|
|
5990
6103
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
5991
6104
|
if (isCrossPackageSelector(selector)) return;
|
|
5992
|
-
const full =
|
|
6105
|
+
const full = resolve16(packageRoot, selector);
|
|
5993
6106
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
5994
6107
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
5995
6108
|
return;
|
|
@@ -6043,10 +6156,10 @@ function isCrossPackageSelector(value) {
|
|
|
6043
6156
|
|
|
6044
6157
|
// src/model/package-migrate.ts
|
|
6045
6158
|
import { readFile as readFile17, rename as rename4, writeFile as writeFile11 } from "fs/promises";
|
|
6046
|
-
import { join as join26, resolve as
|
|
6159
|
+
import { join as join26, resolve as resolve17 } from "path";
|
|
6047
6160
|
import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
|
|
6048
6161
|
async function migratePackageManifest(root) {
|
|
6049
|
-
const packageRoot =
|
|
6162
|
+
const packageRoot = resolve17(root);
|
|
6050
6163
|
for (const name of openPackManifestNames) {
|
|
6051
6164
|
const path = join26(packageRoot, name);
|
|
6052
6165
|
if (await pathExists(path)) {
|
|
@@ -6165,13 +6278,13 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
6165
6278
|
}
|
|
6166
6279
|
if (!result.ok) process.exitCode = 1;
|
|
6167
6280
|
});
|
|
6168
|
-
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").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--dry-run", "accepted for symmetry; plan never writes", 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) => {
|
|
6281
|
+
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").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("--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("--dry-run", "accepted for symmetry; plan never writes", 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) => {
|
|
6169
6282
|
await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
|
|
6170
6283
|
});
|
|
6171
|
-
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").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", 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) => {
|
|
6284
|
+
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").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("--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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", 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) => {
|
|
6172
6285
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6173
6286
|
});
|
|
6174
|
-
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").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("--target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", 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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
6287
|
+
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").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("--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("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
6175
6288
|
console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
|
|
6176
6289
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6177
6290
|
});
|
|
@@ -6345,7 +6458,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
6345
6458
|
let extraPackage;
|
|
6346
6459
|
if (nameOrSource && !configured) {
|
|
6347
6460
|
try {
|
|
6348
|
-
const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, options);
|
|
6461
|
+
const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...options, adapter: options.adapter ?? target.adapter });
|
|
6349
6462
|
scope = entry.name;
|
|
6350
6463
|
if (behavior.apply) {
|
|
6351
6464
|
await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
|
|
@@ -6433,9 +6546,15 @@ function nextInstallNudge() {
|
|
|
6433
6546
|
return "Preview: agentwheel plan - Apply: agentwheel install";
|
|
6434
6547
|
}
|
|
6435
6548
|
async function resolveCliTargets(options) {
|
|
6549
|
+
if (options.all && options.allDetected) {
|
|
6550
|
+
throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
|
|
6551
|
+
}
|
|
6436
6552
|
if (options.all) {
|
|
6437
6553
|
return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
|
|
6438
6554
|
}
|
|
6555
|
+
if (options.allDetected) {
|
|
6556
|
+
return resolveAllDetectedRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, allDetected: options.allDetected });
|
|
6557
|
+
}
|
|
6439
6558
|
return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
|
|
6440
6559
|
}
|
|
6441
6560
|
async function resolveAdapterForTarget(target, options) {
|
|
@@ -6659,6 +6778,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
|
|
|
6659
6778
|
graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
|
|
6660
6779
|
dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
|
|
6661
6780
|
owners: fresh?.owners ?? owners,
|
|
6781
|
+
workspaceOwner: fresh?.workspaceOwner ?? ("workspaceOwner" in entry ? entry.workspaceOwner : "legacy:unowned"),
|
|
6662
6782
|
graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
|
|
6663
6783
|
};
|
|
6664
6784
|
}
|
|
@@ -6955,7 +7075,7 @@ function withFleetExample(config) {
|
|
|
6955
7075
|
};
|
|
6956
7076
|
}
|
|
6957
7077
|
async function findAgentwheelPackageRoot(start) {
|
|
6958
|
-
let current =
|
|
7078
|
+
let current = resolve18(start);
|
|
6959
7079
|
while (true) {
|
|
6960
7080
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
6961
7081
|
const parent = dirname21(current);
|