agentwheel 0.9.0 → 0.11.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 +89 -11
- package/dist/index.js +328 -53
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +31 -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
|
|
@@ -33,9 +34,23 @@ agentwheel install
|
|
|
33
34
|
No lock-in. No central gatekeeper. Packages live in plain git repos or local folders, customizations
|
|
34
35
|
live in your workspace, and runtimes stay generated output.
|
|
35
36
|
|
|
36
|
-
> **Status: early (v0.
|
|
37
|
-
> `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for
|
|
38
|
-
>
|
|
37
|
+
> **Status: early (v0.11).** The public CLI vocabulary is package-manager style:
|
|
38
|
+
> `add`, `install`, `update`, and `uninstall`. A hidden `sync` shim remains for old bootstrapped
|
|
39
|
+
> skills; use `install` in all new docs and scripts.
|
|
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.
|
|
39
54
|
|
|
40
55
|
## Core Commands
|
|
41
56
|
|
|
@@ -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
|
|
|
@@ -254,6 +272,58 @@ still owned by another configured package.
|
|
|
254
272
|
}
|
|
255
273
|
```
|
|
256
274
|
|
|
275
|
+
### Source Overrides
|
|
276
|
+
|
|
277
|
+
Use package-level `overrides` when a workspace intentionally wants one selected source to replace
|
|
278
|
+
an artifact that arrives from another package, such as a forked skill replacing the same skill
|
|
279
|
+
pulled in by a meta-package. Overrides are explicit; package array order never decides precedence.
|
|
280
|
+
|
|
281
|
+
```jsonc
|
|
282
|
+
{
|
|
283
|
+
"schemaVersion": 1,
|
|
284
|
+
"packages": [
|
|
285
|
+
{
|
|
286
|
+
"name": "nestdev-must-have-core",
|
|
287
|
+
"source": "github:NestDevLab/agent-must-have#core",
|
|
288
|
+
"driver": "git",
|
|
289
|
+
"adapter": "codex",
|
|
290
|
+
"mode": "tracking"
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"name": "agent-toolkit-nestdev",
|
|
294
|
+
"source": "github:Yehonal/agent-toolkit#main",
|
|
295
|
+
"driver": "git",
|
|
296
|
+
"adapter": "codex",
|
|
297
|
+
"mode": "tracking",
|
|
298
|
+
"select": [
|
|
299
|
+
"rules/self-improve-on-correction.md",
|
|
300
|
+
"skills/self-improve"
|
|
301
|
+
],
|
|
302
|
+
"overrides": [
|
|
303
|
+
"github:FrancescoBorzi/agent-toolkit::rules/self-improve-on-correction.md",
|
|
304
|
+
"github:FrancescoBorzi/agent-toolkit::skills/self-improve"
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The `source::type/name` selector identifies the artifact to replace. `github:owner/repo` matches
|
|
312
|
+
that repository at any ref; add `#main` or another ref to narrow it. The replacing package must
|
|
313
|
+
select exactly one artifact with the same `type/name`, and the override must match exactly one
|
|
314
|
+
losing artifact. Otherwise planning fails instead of hiding a collision.
|
|
315
|
+
|
|
316
|
+
The same declaration can be created from the CLI:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
agentwheel add github:Yehonal/agent-toolkit#main \
|
|
320
|
+
--skill self-improve \
|
|
321
|
+
--override 'github:FrancescoBorzi/agent-toolkit::skills/self-improve'
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
`agentwheel plan`, `agentwheel deps tree`, and `agentwheel deps why` print `OVERRIDE` lines for
|
|
325
|
+
these decisions, and graph locks store them for review.
|
|
326
|
+
|
|
257
327
|
Migrating an existing legacy package takes one command:
|
|
258
328
|
|
|
259
329
|
```bash
|
|
@@ -267,7 +337,9 @@ Drift detection blocks accidental edits to generated runtime files. Intentional
|
|
|
267
337
|
|
|
268
338
|
- **Layer** local instructions with `agentwheel remember`.
|
|
269
339
|
- **Add** separate local artifacts under `.agentwheel/additions`.
|
|
270
|
-
- **Override** an upstream item under `.agentwheel/overrides`.
|
|
340
|
+
- **Override content** for an upstream item under `.agentwheel/overrides`.
|
|
341
|
+
- **Override source precedence** with package `overrides` when a forked source should replace a
|
|
342
|
+
colliding artifact from another package.
|
|
271
343
|
- **Eject** an item into `.agentwheel/ejected` when you want local ownership.
|
|
272
344
|
|
|
273
345
|
## Custom And Private Runtimes
|
|
@@ -290,15 +362,21 @@ published:
|
|
|
290
362
|
agentwheel install ./my-pack --adapter-config ./myco-internal.jsonc
|
|
291
363
|
```
|
|
292
364
|
|
|
365
|
+
For adapter behavior that needs code, load a programmatic adapter module:
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
agentwheel install ./my-pack --adapter-module ./myco-adapter.js
|
|
369
|
+
```
|
|
370
|
+
|
|
293
371
|
Built-in runtime targets:
|
|
294
372
|
|
|
295
373
|
| Runtime | Main targets |
|
|
296
374
|
|---|---|
|
|
297
|
-
| **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, MCP/hooks/settings, semantic plugin planning |
|
|
375
|
+
| **OpenClaw** | `.openclaw/AGENTS.md`, `.openclaw/skills`, `.openclaw/rules`, `.openclaw/commands`, `.openclaw/agents`, MCP/hooks/settings, semantic plugin planning |
|
|
298
376
|
| **Claude Code** | `.claude/CLAUDE.md`, `.claude/skills`, `.claude/commands`, `.claude/agents`, `.claude/rules`, `.claude/.mcp.json`, `.claude/settings.json` |
|
|
299
377
|
| **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` |
|
|
378
|
+
| **Hermes** | `.hermes/AGENTS.md`, `.hermes/skills`, `.hermes/rules`, `.hermes/commands`, `.hermes/agents`, MCP/hooks/settings |
|
|
379
|
+
| **GitHub Copilot** | `.github/copilot-instructions.md`, `.github/instructions`, `.github/prompts`, `.github/skills`, `.github/agents`, `.vscode/mcp.json` |
|
|
302
380
|
|
|
303
381
|
## Docs
|
|
304
382
|
|
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" },
|
|
@@ -295,7 +297,8 @@ var graphLockRootSchema = z3.object({
|
|
|
295
297
|
graphNodeId: z3.string().min(1),
|
|
296
298
|
mode: z3.enum(["pinned", "tracking"]),
|
|
297
299
|
selected: z3.array(z3.string().min(1)),
|
|
298
|
-
aliases: z3.record(z3.string(), z3.string().min(1)).optional()
|
|
300
|
+
aliases: z3.record(z3.string(), z3.string().min(1)).optional(),
|
|
301
|
+
overrides: z3.array(z3.string().min(1)).optional()
|
|
299
302
|
});
|
|
300
303
|
var graphLockEdgeSchema = z3.object({
|
|
301
304
|
from: z3.string().min(1),
|
|
@@ -344,6 +347,15 @@ var graphLockNamespacingSchema = z3.object({
|
|
|
344
347
|
installName: z3.string().min(1),
|
|
345
348
|
reason: z3.enum(["alias", "transitive-collision"])
|
|
346
349
|
});
|
|
350
|
+
var graphLockOverrideSchema = z3.object({
|
|
351
|
+
rootId: z3.string().min(1),
|
|
352
|
+
selector: z3.string().min(1),
|
|
353
|
+
graphNodeId: z3.string().min(1),
|
|
354
|
+
overriddenGraphNodeId: z3.string().min(1),
|
|
355
|
+
type: artifactTypeSchema,
|
|
356
|
+
name: z3.string().min(1),
|
|
357
|
+
installName: z3.string().min(1)
|
|
358
|
+
});
|
|
347
359
|
var graphLockCanonicalSchema = z3.object({
|
|
348
360
|
targetFingerprint: z3.string().min(1).optional(),
|
|
349
361
|
roots: z3.array(graphLockRootSchema),
|
|
@@ -352,6 +364,7 @@ var graphLockCanonicalSchema = z3.object({
|
|
|
352
364
|
includeEdges: z3.array(graphLockIncludeEdgeSchema).default([]),
|
|
353
365
|
artifacts: z3.array(graphLockArtifactSchema).default([]),
|
|
354
366
|
namespacing: z3.array(graphLockNamespacingSchema).default([]),
|
|
367
|
+
overrides: z3.array(graphLockOverrideSchema).default([]),
|
|
355
368
|
plainNameIncumbents: z3.array(graphLockPlainNameIncumbentSchema).default([])
|
|
356
369
|
});
|
|
357
370
|
var graphLockSchema = z3.object({
|
|
@@ -382,7 +395,11 @@ function canonicalizeGraphLock(lock) {
|
|
|
382
395
|
version: 1,
|
|
383
396
|
canonical: {
|
|
384
397
|
targetFingerprint: parsed.canonical.targetFingerprint,
|
|
385
|
-
roots: [...parsed.canonical.roots].map((root) => ({
|
|
398
|
+
roots: [...parsed.canonical.roots].map((root) => ({
|
|
399
|
+
...root,
|
|
400
|
+
selected: sortedUnique(root.selected),
|
|
401
|
+
overrides: root.overrides ? sortedUnique(root.overrides) : void 0
|
|
402
|
+
})).sort((a, b) => `${a.rootId}:${a.graphNodeId}`.localeCompare(`${b.rootId}:${b.graphNodeId}`)),
|
|
386
403
|
nodes: [...parsed.canonical.nodes].map((node) => ({
|
|
387
404
|
...node,
|
|
388
405
|
requiredBy: sortedUnique(node.requiredBy),
|
|
@@ -393,6 +410,7 @@ function canonicalizeGraphLock(lock) {
|
|
|
393
410
|
includeEdges: [...parsed.canonical.includeEdges].sort((a, b) => `${a.fromNodeId}:${a.alias}:${a.toNodeId}:${a.selector}`.localeCompare(`${b.fromNodeId}:${b.alias}:${b.toNodeId}:${b.selector}`)),
|
|
394
411
|
artifacts: [...parsed.canonical.artifacts].map((artifact) => ({ ...artifact, owners: sortedUnique(artifact.owners) })).sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector)),
|
|
395
412
|
namespacing: [...parsed.canonical.namespacing].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.name}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.name}`)),
|
|
413
|
+
overrides: [...parsed.canonical.overrides].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.overriddenGraphNodeId}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.overriddenGraphNodeId}`)),
|
|
396
414
|
plainNameIncumbents: [...parsed.canonical.plainNameIncumbents].sort((a, b) => `${a.adapter}:${a.targetFingerprint}:${a.type}:${a.name}`.localeCompare(`${b.adapter}:${b.targetFingerprint}:${b.type}:${b.name}`))
|
|
397
415
|
}
|
|
398
416
|
};
|
|
@@ -542,13 +560,13 @@ async function spawnWithInput(command, args, input) {
|
|
|
542
560
|
if (result.stderr) throw new Error(result.stderr);
|
|
543
561
|
}
|
|
544
562
|
function waitForProcess(child, label) {
|
|
545
|
-
return new Promise((
|
|
563
|
+
return new Promise((resolve19, reject) => {
|
|
546
564
|
const stderr = [];
|
|
547
565
|
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
548
566
|
child.on("error", reject);
|
|
549
567
|
child.on("close", (code) => {
|
|
550
568
|
const message = Buffer.concat(stderr).toString("utf8");
|
|
551
|
-
if (code === 0)
|
|
569
|
+
if (code === 0) resolve19({ stderr: "" });
|
|
552
570
|
else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
|
|
553
571
|
});
|
|
554
572
|
});
|
|
@@ -654,6 +672,7 @@ import { resolve as resolve3 } from "path";
|
|
|
654
672
|
// src/model/manifest.ts
|
|
655
673
|
import { z as z4 } from "zod";
|
|
656
674
|
var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
|
|
675
|
+
var legacyUnownedWorkspaceOwner = "legacy:unowned";
|
|
657
676
|
var manifestEntryV1Schema = z4.object({
|
|
658
677
|
path: z4.string().min(1),
|
|
659
678
|
artifactType: artifactTypeSchema,
|
|
@@ -676,6 +695,7 @@ var manifestEntrySchema = manifestEntryV1Schema.extend({
|
|
|
676
695
|
dependencyRole: dependencyRoleSchema.default("root"),
|
|
677
696
|
owners: z4.array(z4.string().min(1)).min(1),
|
|
678
697
|
refCount: z4.number().int().positive(),
|
|
698
|
+
workspaceOwner: z4.string().min(1).default(legacyUnownedWorkspaceOwner),
|
|
679
699
|
graphLockDigest: z4.string().min(1).optional()
|
|
680
700
|
}).transform((entry) => {
|
|
681
701
|
const owners = [...new Set(entry.owners)].sort();
|
|
@@ -1388,6 +1408,7 @@ function manifestEntryForOperation(operation, values) {
|
|
|
1388
1408
|
dependencyRole: operation.dependencyRole ?? "root",
|
|
1389
1409
|
owners,
|
|
1390
1410
|
refCount: owners.length,
|
|
1411
|
+
workspaceOwner: operation.workspaceOwner ?? "workspace:unknown",
|
|
1391
1412
|
kind: operation.kind,
|
|
1392
1413
|
hash: values.hash,
|
|
1393
1414
|
sourceHash: values.sourceHash,
|
|
@@ -1476,6 +1497,10 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1476
1497
|
return createPlanFromOperations(desired, adapter, targetRoot, manifest, transport, options);
|
|
1477
1498
|
}
|
|
1478
1499
|
async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
|
|
1500
|
+
const workspaceOwner = options.workspaceOwner;
|
|
1501
|
+
if (workspaceOwner) {
|
|
1502
|
+
for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
|
|
1503
|
+
}
|
|
1479
1504
|
for (const op of desiredOps) assertOperationContained(op, targetRoot);
|
|
1480
1505
|
const migration = await migrateManifestForPlan(manifest, desiredOps, targetRoot, transport);
|
|
1481
1506
|
const effectiveEntries = migration.entries;
|
|
@@ -1518,6 +1543,12 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1518
1543
|
continue;
|
|
1519
1544
|
}
|
|
1520
1545
|
const existing = manifestByPath.get(op.relativeDestPath);
|
|
1546
|
+
if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
|
|
1547
|
+
if (!await canAdoptLegacyUnownedEntry(existing, op, transport)) {
|
|
1548
|
+
operations.push(keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op));
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1521
1552
|
const exists = await transport.pathExists(op.destPath);
|
|
1522
1553
|
if (!exists) {
|
|
1523
1554
|
operations.push({ ...op, action: "create", reason: "destination missing" });
|
|
@@ -1559,6 +1590,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1559
1590
|
const destPath = join5(targetRoot, entry.path);
|
|
1560
1591
|
if (!await transport.pathExists(destPath)) continue;
|
|
1561
1592
|
const currentHash = await transport.hashPath(destPath);
|
|
1593
|
+
if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
|
|
1594
|
+
operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
|
|
1595
|
+
continue;
|
|
1596
|
+
}
|
|
1562
1597
|
if (currentHash !== entry.hash) {
|
|
1563
1598
|
operations.push({
|
|
1564
1599
|
action: "drift",
|
|
@@ -1714,6 +1749,16 @@ function operationMatchesManifestEntry(op, entry) {
|
|
|
1714
1749
|
if ("graphNodeId" in entry && entry.graphNodeId && op.graphNodeId && entry.graphNodeId === op.graphNodeId) return true;
|
|
1715
1750
|
return op.packageName !== void 0 && entry.packageName === op.packageName;
|
|
1716
1751
|
}
|
|
1752
|
+
function entryOwnedByWorkspace(entry, workspaceOwner) {
|
|
1753
|
+
return "workspaceOwner" in entry && entry.workspaceOwner === workspaceOwner;
|
|
1754
|
+
}
|
|
1755
|
+
async function canAdoptLegacyUnownedEntry(entry, op, transport) {
|
|
1756
|
+
if (!("workspaceOwner" in entry) || entry.workspaceOwner !== legacyUnownedWorkspaceOwner) return false;
|
|
1757
|
+
if (entry.artifactType !== op.artifactType || entry.kind !== op.kind) return false;
|
|
1758
|
+
if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
|
|
1759
|
+
if (!await transport.pathExists(op.destPath)) return false;
|
|
1760
|
+
return await transport.hashPath(op.destPath) === entry.hash;
|
|
1761
|
+
}
|
|
1717
1762
|
function collisionOperation(op, group, incumbent) {
|
|
1718
1763
|
const owners = group.map(describeOperationOwner).sort();
|
|
1719
1764
|
const incumbentText = incumbent ? `; incumbent ${describeOperationOwner(incumbent)} keeps the plain name` : "";
|
|
@@ -1737,6 +1782,7 @@ function adoptLegacyEntry(entry, op) {
|
|
|
1737
1782
|
dependencyRole: op.dependencyRole ?? "root",
|
|
1738
1783
|
owners,
|
|
1739
1784
|
refCount: owners.length,
|
|
1785
|
+
workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
|
|
1740
1786
|
graphLockDigest: op.graphLockDigest,
|
|
1741
1787
|
composedFrom: op.composedFrom ?? entry.composedFrom
|
|
1742
1788
|
};
|
|
@@ -1759,6 +1805,7 @@ function operationMetadataFromEntry(entry) {
|
|
|
1759
1805
|
graphNodeId: entry.graphNodeId,
|
|
1760
1806
|
dependencyRole: entry.dependencyRole,
|
|
1761
1807
|
owners: entry.owners,
|
|
1808
|
+
workspaceOwner: entry.workspaceOwner,
|
|
1762
1809
|
graphLockDigest: entry.graphLockDigest
|
|
1763
1810
|
};
|
|
1764
1811
|
}
|
|
@@ -1766,7 +1813,31 @@ function operationMetadataFromEntry(entry) {
|
|
|
1766
1813
|
installName: entry.artifactName,
|
|
1767
1814
|
logicalSelector: `${entry.artifactType}/${entry.artifactName}`,
|
|
1768
1815
|
dependencyRole: "root",
|
|
1769
|
-
owners: [entry.packageName ?? "legacy"]
|
|
1816
|
+
owners: [entry.packageName ?? "legacy"],
|
|
1817
|
+
workspaceOwner: legacyUnownedWorkspaceOwner
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, operation, currentHash) {
|
|
1821
|
+
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
1822
|
+
return {
|
|
1823
|
+
action: "keep",
|
|
1824
|
+
artifactType: entry.artifactType,
|
|
1825
|
+
artifactName: entry.artifactName,
|
|
1826
|
+
kind: entry.kind,
|
|
1827
|
+
destPath: operation?.destPath ?? join5(targetRoot, entry.path),
|
|
1828
|
+
relativeDestPath: entry.path,
|
|
1829
|
+
desiredHash: entry.sourceHash,
|
|
1830
|
+
currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
|
|
1831
|
+
manifestHash: entry.hash,
|
|
1832
|
+
reason: `foreign artifact owned by ${owner}; kept outside workspace ${workspaceOwner}`,
|
|
1833
|
+
channel: entry.channel,
|
|
1834
|
+
packageName: entry.packageName,
|
|
1835
|
+
semanticCommand: entry.semanticCommand,
|
|
1836
|
+
execute: entry.executed,
|
|
1837
|
+
mergeStrategy: entry.mergeStrategy,
|
|
1838
|
+
composedFrom: entry.composedFrom,
|
|
1839
|
+
preserveInManifest: true,
|
|
1840
|
+
...operationMetadataFromEntry(entry)
|
|
1770
1841
|
};
|
|
1771
1842
|
}
|
|
1772
1843
|
function normalizeOperationOwners(op) {
|
|
@@ -2083,6 +2154,9 @@ function formatGraphPlan(result) {
|
|
|
2083
2154
|
for (const decision of result.bundle.graphLock.canonical.namespacing) {
|
|
2084
2155
|
lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
2085
2156
|
}
|
|
2157
|
+
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
2158
|
+
lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
2159
|
+
}
|
|
2086
2160
|
if (result.graphDiff.length > 0) {
|
|
2087
2161
|
lines.push("Graph diff:");
|
|
2088
2162
|
lines.push(...result.graphDiff);
|
|
@@ -2120,6 +2194,9 @@ function formatLockDependencyTree(lock) {
|
|
|
2120
2194
|
for (const decision of lock.canonical.namespacing) {
|
|
2121
2195
|
lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
2122
2196
|
}
|
|
2197
|
+
for (const decision of lock.canonical.overrides) {
|
|
2198
|
+
lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
2199
|
+
}
|
|
2123
2200
|
return lines.join("\n");
|
|
2124
2201
|
}
|
|
2125
2202
|
function formatDepsWhy(lock, manifest, query) {
|
|
@@ -2142,6 +2219,9 @@ function formatDepsWhy(lock, manifest, query) {
|
|
|
2142
2219
|
}
|
|
2143
2220
|
const namespace = lock.canonical.namespacing.find((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name);
|
|
2144
2221
|
lines.push(namespace ? `NAME ${namespace.reason}: ${namespace.type}/${namespace.name} -> ${namespace.type}/${namespace.installName}` : `NAME plain: ${match.type}/${match.name}`);
|
|
2222
|
+
for (const override of lock.canonical.overrides.filter((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name)) {
|
|
2223
|
+
lines.push(`OVERRIDE replaces ${override.overriddenGraphNodeId}:${override.type}/${override.name} via ${override.rootId}`);
|
|
2224
|
+
}
|
|
2145
2225
|
return lines.join("\n");
|
|
2146
2226
|
}
|
|
2147
2227
|
function formatSelected(selected, reasons) {
|
|
@@ -2714,7 +2794,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
|
2714
2794
|
if (Date.now() - started > timeoutMs) {
|
|
2715
2795
|
throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
|
|
2716
2796
|
}
|
|
2717
|
-
await new Promise((
|
|
2797
|
+
await new Promise((resolve19) => setTimeout(resolve19, 50));
|
|
2718
2798
|
}
|
|
2719
2799
|
}
|
|
2720
2800
|
try {
|
|
@@ -3721,7 +3801,8 @@ var workspacePackageSchema = z6.object({
|
|
|
3721
3801
|
requestedRef: z6.string().min(1).optional(),
|
|
3722
3802
|
select: z6.array(z6.string().min(1)).optional(),
|
|
3723
3803
|
skills: z6.array(z6.string().min(1)).optional(),
|
|
3724
|
-
aliases: z6.record(z6.string(), z6.string().min(1)).optional()
|
|
3804
|
+
aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
|
|
3805
|
+
overrides: z6.array(z6.string().min(1)).optional()
|
|
3725
3806
|
});
|
|
3726
3807
|
var workspaceProfileRuntimeSchema = z6.object({
|
|
3727
3808
|
agent: z6.string().min(1).optional(),
|
|
@@ -4260,6 +4341,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
4260
4341
|
requiredBy: `workspace:${rootId}`,
|
|
4261
4342
|
rootId,
|
|
4262
4343
|
aliases: root.aliases,
|
|
4344
|
+
overrides: root.overrides,
|
|
4263
4345
|
useLock: root.useLock ?? options.lockedResolution,
|
|
4264
4346
|
depth: 0,
|
|
4265
4347
|
optional: false,
|
|
@@ -4291,7 +4373,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
4291
4373
|
generatedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
4292
4374
|
};
|
|
4293
4375
|
}
|
|
4294
|
-
function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = []) {
|
|
4376
|
+
function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = [], overrides = []) {
|
|
4295
4377
|
const roots = graph.roots.map((root) => ({
|
|
4296
4378
|
rootId: root.rootId,
|
|
4297
4379
|
source: root.source,
|
|
@@ -4299,7 +4381,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4299
4381
|
graphNodeId: root.graphNodeId,
|
|
4300
4382
|
mode: root.mode,
|
|
4301
4383
|
selected: root.selected,
|
|
4302
|
-
aliases: root.aliases
|
|
4384
|
+
aliases: root.aliases,
|
|
4385
|
+
overrides: root.overrides
|
|
4303
4386
|
}));
|
|
4304
4387
|
return {
|
|
4305
4388
|
version: 1,
|
|
@@ -4311,6 +4394,7 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4311
4394
|
includeEdges,
|
|
4312
4395
|
artifacts,
|
|
4313
4396
|
namespacing,
|
|
4397
|
+
overrides,
|
|
4314
4398
|
plainNameIncumbents: []
|
|
4315
4399
|
}
|
|
4316
4400
|
};
|
|
@@ -4318,13 +4402,33 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4318
4402
|
async function processRequirement(requirement, options, fetchCache, nodesByKey, rootResults, edgeMap) {
|
|
4319
4403
|
try {
|
|
4320
4404
|
const lockLabel = options.offline ? "Offline" : options.frozenLock ? "Frozen lock" : "Locked install";
|
|
4321
|
-
|
|
4322
|
-
|
|
4405
|
+
let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
|
|
4406
|
+
let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
|
|
4323
4407
|
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
4324
4408
|
workspaceRoot: options.workspaceRoot,
|
|
4325
4409
|
ref: requirement.ref,
|
|
4326
4410
|
registryClient: options.registryClient
|
|
4327
4411
|
});
|
|
4412
|
+
if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
|
|
4413
|
+
const declared = await normalizeDependencySource(requirement.source, {
|
|
4414
|
+
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
4415
|
+
workspaceRoot: options.workspaceRoot,
|
|
4416
|
+
ref: requirement.ref,
|
|
4417
|
+
registryClient: options.registryClient
|
|
4418
|
+
});
|
|
4419
|
+
if (lockedRootSourceDrifted(declared, lockedByReference.node)) {
|
|
4420
|
+
if (options.frozenLock || options.offline) {
|
|
4421
|
+
throw new Error(
|
|
4422
|
+
`${lockLabel} root '${requirement.rootId}' source differs from declared source:
|
|
4423
|
+
- declared: ${declared.normalizedSource}
|
|
4424
|
+
- locked: ${lockedByReference.node.normalizedSource}
|
|
4425
|
+
Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
|
|
4426
|
+
);
|
|
4427
|
+
}
|
|
4428
|
+
lockedByReference = void 0;
|
|
4429
|
+
normalized = declared;
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4328
4432
|
const frozen = lockedByReference ?? lockedNodeForRequirement(normalized.normalizedSource, requirement, options, lockLabel);
|
|
4329
4433
|
let fetched;
|
|
4330
4434
|
try {
|
|
@@ -4387,7 +4491,8 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
|
|
|
4387
4491
|
graphNodeId: state.node.id,
|
|
4388
4492
|
mode: state.node.mode,
|
|
4389
4493
|
selected: state.node.selected,
|
|
4390
|
-
aliases: requirement.aliases
|
|
4494
|
+
aliases: requirement.aliases,
|
|
4495
|
+
overrides: requirement.overrides
|
|
4391
4496
|
});
|
|
4392
4497
|
}
|
|
4393
4498
|
if (requirement.parentId && requirement.alias) {
|
|
@@ -4747,6 +4852,18 @@ function lockedNodeForRequirementReference(requirement, options, label) {
|
|
|
4747
4852
|
requestedRef: node.driver === "git" ? node.resolvedCommit ?? node.requestedRef : node.requestedRef
|
|
4748
4853
|
};
|
|
4749
4854
|
}
|
|
4855
|
+
function shouldCheckLockedRootSource(requirement) {
|
|
4856
|
+
if (!requirement.useLock) return false;
|
|
4857
|
+
if (requirement.depth !== 0 || !requirement.rootId) return false;
|
|
4858
|
+
return isExplicitNonRegistrySource(requirement.source);
|
|
4859
|
+
}
|
|
4860
|
+
function isExplicitNonRegistrySource(source) {
|
|
4861
|
+
const trimmed = source.trim();
|
|
4862
|
+
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:");
|
|
4863
|
+
}
|
|
4864
|
+
function lockedRootSourceDrifted(declared, locked) {
|
|
4865
|
+
return declared.normalizedSource !== locked.normalizedSource || declared.requestedRef !== locked.requestedRef;
|
|
4866
|
+
}
|
|
4750
4867
|
function normalizedSourceFromLockedNode(node) {
|
|
4751
4868
|
return {
|
|
4752
4869
|
source: node.source,
|
|
@@ -4765,8 +4882,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
4765
4882
|
async function withCachePathLock(path, fn) {
|
|
4766
4883
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
4767
4884
|
let release = () => void 0;
|
|
4768
|
-
const current = previous.then(() => new Promise((
|
|
4769
|
-
release =
|
|
4885
|
+
const current = previous.then(() => new Promise((resolve19) => {
|
|
4886
|
+
release = resolve19;
|
|
4770
4887
|
}));
|
|
4771
4888
|
cacheLocks.set(path, current);
|
|
4772
4889
|
await previous;
|
|
@@ -4974,7 +5091,7 @@ import { rm as rm8 } from "fs/promises";
|
|
|
4974
5091
|
// src/lifecycle/source-plan.ts
|
|
4975
5092
|
import { createHash as createHash6 } from "crypto";
|
|
4976
5093
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
4977
|
-
import { dirname as dirname17, join as join22 } from "path";
|
|
5094
|
+
import { dirname as dirname17, join as join22, resolve as resolve14 } from "path";
|
|
4978
5095
|
|
|
4979
5096
|
// src/resolve/graph-diff.ts
|
|
4980
5097
|
function diffGraphLocks(previous, next) {
|
|
@@ -4982,7 +5099,8 @@ function diffGraphLocks(previous, next) {
|
|
|
4982
5099
|
return [
|
|
4983
5100
|
...diffNodes(previous.canonical.nodes, next.canonical.nodes),
|
|
4984
5101
|
...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
|
|
4985
|
-
...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing)
|
|
5102
|
+
...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
|
|
5103
|
+
...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
|
|
4986
5104
|
];
|
|
4987
5105
|
}
|
|
4988
5106
|
function diffNodes(previous, next) {
|
|
@@ -5044,6 +5162,23 @@ function diffNamespacing(previous, next) {
|
|
|
5044
5162
|
}
|
|
5045
5163
|
return lines.sort((a, b) => a.localeCompare(b));
|
|
5046
5164
|
}
|
|
5165
|
+
function diffOverrides(previous, next) {
|
|
5166
|
+
const previousByKey = new Map(previous.map((decision) => [overrideKey(decision), decision]));
|
|
5167
|
+
const nextByKey = new Map(next.map((decision) => [overrideKey(decision), decision]));
|
|
5168
|
+
const lines = [];
|
|
5169
|
+
for (const [key, decision] of nextByKey) {
|
|
5170
|
+
const old = previousByKey.get(key);
|
|
5171
|
+
if (!old) {
|
|
5172
|
+
lines.push(`ADDED override ${formatOverride(decision)}`);
|
|
5173
|
+
} else if (old.graphNodeId !== decision.graphNodeId || old.installName !== decision.installName) {
|
|
5174
|
+
lines.push(`CHANGED override ${decision.selector} ${old.graphNodeId}:${old.type}/${old.name} -> ${decision.graphNodeId}:${decision.type}/${decision.name}`);
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
for (const [key, decision] of previousByKey) {
|
|
5178
|
+
if (!nextByKey.has(key)) lines.push(`REMOVED override ${formatOverride(decision)}`);
|
|
5179
|
+
}
|
|
5180
|
+
return lines.sort((a, b) => a.localeCompare(b));
|
|
5181
|
+
}
|
|
5047
5182
|
function stableNodeKey(node) {
|
|
5048
5183
|
return `${node.normalizedSource}\0${node.name}`;
|
|
5049
5184
|
}
|
|
@@ -5063,9 +5198,15 @@ function formatIncludeEdge(edge) {
|
|
|
5063
5198
|
function namespaceKey(decision) {
|
|
5064
5199
|
return `${decision.graphNodeId}\0${decision.type}\0${decision.name}`;
|
|
5065
5200
|
}
|
|
5201
|
+
function overrideKey(decision) {
|
|
5202
|
+
return `${decision.rootId}\0${decision.selector}\0${decision.overriddenGraphNodeId}\0${decision.type}\0${decision.name}`;
|
|
5203
|
+
}
|
|
5066
5204
|
function formatNamespace(decision) {
|
|
5067
5205
|
return `${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`;
|
|
5068
5206
|
}
|
|
5207
|
+
function formatOverride(decision) {
|
|
5208
|
+
return `${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`;
|
|
5209
|
+
}
|
|
5069
5210
|
function short(hash) {
|
|
5070
5211
|
return hash.slice(0, 12);
|
|
5071
5212
|
}
|
|
@@ -5167,13 +5308,13 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
5167
5308
|
owners: [...rawNode.node.requiredBy].sort((a, b) => a.localeCompare(b))
|
|
5168
5309
|
})));
|
|
5169
5310
|
}
|
|
5170
|
-
const { artifacts: namedArtifacts, namespacing } = assignInstallNames(graph, artifacts);
|
|
5311
|
+
const { artifacts: namedArtifacts, namespacing, overrides } = assignInstallNames(graph, artifacts);
|
|
5171
5312
|
const sortedArtifacts = namedArtifacts.sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
|
|
5172
5313
|
return {
|
|
5173
5314
|
root,
|
|
5174
5315
|
nodes: graph.nodes,
|
|
5175
5316
|
artifacts: sortedArtifacts,
|
|
5176
|
-
graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing)
|
|
5317
|
+
graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing, overrides)
|
|
5177
5318
|
};
|
|
5178
5319
|
}
|
|
5179
5320
|
function aliasEdgeMap(graph) {
|
|
@@ -5217,7 +5358,8 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5217
5358
|
decisions.set(decisionKey(updated), namespaceDecision(updated, "alias"));
|
|
5218
5359
|
return updated;
|
|
5219
5360
|
});
|
|
5220
|
-
const
|
|
5361
|
+
const { artifacts: withOverrides, overrides } = applyWorkspaceOverrides(graph, withAliases);
|
|
5362
|
+
const collisionGroups = [...groupBy(withOverrides, (artifact) => `${artifact.type}\0${artifact.installName}`).values()].filter((group) => group.length > 1);
|
|
5221
5363
|
const toRename = /* @__PURE__ */ new Set();
|
|
5222
5364
|
for (const group of collisionGroups) {
|
|
5223
5365
|
if (group.some((artifact) => decisions.has(decisionKey(artifact)))) throw installNameCollisionError(group);
|
|
@@ -5228,11 +5370,11 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5228
5370
|
}
|
|
5229
5371
|
}
|
|
5230
5372
|
const used = /* @__PURE__ */ new Map();
|
|
5231
|
-
for (const artifact of
|
|
5373
|
+
for (const artifact of withOverrides) {
|
|
5232
5374
|
if (toRename.has(artifact)) continue;
|
|
5233
5375
|
used.set(`${artifact.type}\0${artifact.installName}`, artifact);
|
|
5234
5376
|
}
|
|
5235
|
-
const out =
|
|
5377
|
+
const out = withOverrides.filter((artifact) => !toRename.has(artifact));
|
|
5236
5378
|
for (const group of groupBy([...toRename], (artifact) => `${artifact.type}\0${artifact.name}`).values()) {
|
|
5237
5379
|
const renamed = namespaceTransitiveGroup(group, used);
|
|
5238
5380
|
for (const artifact of renamed) {
|
|
@@ -5241,7 +5383,52 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5241
5383
|
out.push(artifact);
|
|
5242
5384
|
}
|
|
5243
5385
|
}
|
|
5244
|
-
|
|
5386
|
+
const finalInstallNames = new Map(out.map((artifact) => [decisionKey(artifact), artifact.installName]));
|
|
5387
|
+
const finalOverrides = overrides.map((override) => ({
|
|
5388
|
+
...override,
|
|
5389
|
+
installName: finalInstallNames.get(`${override.graphNodeId}\0${override.type}\0${override.name}`) ?? override.installName
|
|
5390
|
+
}));
|
|
5391
|
+
return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
|
|
5392
|
+
}
|
|
5393
|
+
function applyWorkspaceOverrides(graph, artifacts) {
|
|
5394
|
+
const directives = workspaceOverrides(graph);
|
|
5395
|
+
if (directives.length === 0) return { artifacts, overrides: [] };
|
|
5396
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
5397
|
+
let remaining = [...artifacts];
|
|
5398
|
+
const decisions = [];
|
|
5399
|
+
for (const directive of directives) {
|
|
5400
|
+
const matched = remaining.filter((artifact) => overrideMatchesArtifact(directive.selector, artifact, nodeById.get(artifact.graphNodeId)));
|
|
5401
|
+
const losers = matched.filter((artifact) => !directive.reachable.has(artifact.graphNodeId));
|
|
5402
|
+
if (matched.length === 0) {
|
|
5403
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} did not match any rendered artifact.`);
|
|
5404
|
+
}
|
|
5405
|
+
if (losers.length === 0) {
|
|
5406
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched only artifacts inside the replacing root.`);
|
|
5407
|
+
}
|
|
5408
|
+
if (losers.length > 1) {
|
|
5409
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched multiple artifacts: ${losers.map((artifact) => artifact.logicalSelector).sort().join(", ")}`);
|
|
5410
|
+
}
|
|
5411
|
+
const loser = losers[0];
|
|
5412
|
+
const winners = remaining.filter((artifact) => artifact !== loser && directive.reachable.has(artifact.graphNodeId) && artifact.type === loser.type && artifact.name === loser.name);
|
|
5413
|
+
if (winners.length === 0) {
|
|
5414
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} has no selected replacement for ${loser.type}/${loser.name}.`);
|
|
5415
|
+
}
|
|
5416
|
+
if (winners.length > 1) {
|
|
5417
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} has multiple selected replacements for ${loser.type}/${loser.name}: ${winners.map((artifact) => artifact.logicalSelector).sort().join(", ")}`);
|
|
5418
|
+
}
|
|
5419
|
+
const winner = winners[0];
|
|
5420
|
+
remaining = remaining.filter((artifact) => artifact !== loser);
|
|
5421
|
+
decisions.push({
|
|
5422
|
+
rootId: directive.rootId,
|
|
5423
|
+
selector: directive.selector,
|
|
5424
|
+
graphNodeId: winner.graphNodeId,
|
|
5425
|
+
overriddenGraphNodeId: loser.graphNodeId,
|
|
5426
|
+
type: winner.type,
|
|
5427
|
+
name: winner.name,
|
|
5428
|
+
installName: winner.installName
|
|
5429
|
+
});
|
|
5430
|
+
}
|
|
5431
|
+
return { artifacts: remaining, overrides: decisions };
|
|
5245
5432
|
}
|
|
5246
5433
|
function namespaceTransitiveGroup(group, used) {
|
|
5247
5434
|
const sorted = [...group].sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
|
|
@@ -5279,6 +5466,16 @@ function workspaceAliases(graph) {
|
|
|
5279
5466
|
}
|
|
5280
5467
|
return aliases;
|
|
5281
5468
|
}
|
|
5469
|
+
function workspaceOverrides(graph) {
|
|
5470
|
+
const overrides = [];
|
|
5471
|
+
for (const root of graph.roots) {
|
|
5472
|
+
const reachable = reachableNodeIds(graph, root.graphNodeId);
|
|
5473
|
+
for (const selector of root.overrides ?? []) {
|
|
5474
|
+
overrides.push({ rootId: root.rootId, selector, reachable });
|
|
5475
|
+
}
|
|
5476
|
+
}
|
|
5477
|
+
return overrides;
|
|
5478
|
+
}
|
|
5282
5479
|
function validateAliasScopes(graph, artifacts, aliases) {
|
|
5283
5480
|
for (const alias of aliases) {
|
|
5284
5481
|
const matching = artifacts.filter((artifact) => {
|
|
@@ -5295,6 +5492,35 @@ function aliasMatchesArtifact(selector, artifact, node) {
|
|
|
5295
5492
|
const artifactSelector = `${artifact.type}/${artifact.name}`;
|
|
5296
5493
|
return selector === `${artifact.graphNodeId}:${artifactSelector}` || node !== void 0 && selector === `${node.name}@${node.version}:${artifactSelector}` || node !== void 0 && selector === `${node.name}:${artifactSelector}`;
|
|
5297
5494
|
}
|
|
5495
|
+
function overrideMatchesArtifact(selector, artifact, node) {
|
|
5496
|
+
const sourceSeparator = selector.lastIndexOf("::");
|
|
5497
|
+
if (sourceSeparator >= 0) {
|
|
5498
|
+
const sourceSelector = selector.slice(0, sourceSeparator).trim();
|
|
5499
|
+
const artifactSelector = selector.slice(sourceSeparator + 2).trim();
|
|
5500
|
+
return artifactSelector === `${artifact.type}/${artifact.name}` && sourceMatchesArtifact(sourceSelector, node);
|
|
5501
|
+
}
|
|
5502
|
+
return aliasMatchesArtifact(selector, artifact, node);
|
|
5503
|
+
}
|
|
5504
|
+
function sourceMatchesArtifact(selector, node) {
|
|
5505
|
+
if (!node || selector.length === 0) return false;
|
|
5506
|
+
if (selector === node.source || selector === node.normalizedSource || selector === node.name || selector === node.id) return true;
|
|
5507
|
+
const normalized = node.normalizedSource.toLowerCase();
|
|
5508
|
+
const source = node.source.toLowerCase();
|
|
5509
|
+
const value = selector.toLowerCase();
|
|
5510
|
+
if (value === source || value === normalized || value === node.name.toLowerCase() || value === node.id.toLowerCase()) return true;
|
|
5511
|
+
const github = /^github:([^#]+?)(?:#(.+))?$/.exec(value);
|
|
5512
|
+
if (github) {
|
|
5513
|
+
const repo = github[1].replace(/\.git$/i, "");
|
|
5514
|
+
const ref = github[2];
|
|
5515
|
+
const prefix = `git:https://github.com/${repo}.git#`;
|
|
5516
|
+
return ref ? normalized.includes(`${prefix}${ref}`) : normalized.includes(prefix);
|
|
5517
|
+
}
|
|
5518
|
+
if (!value.includes(":") && value.includes("/")) {
|
|
5519
|
+
const repo = value.replace(/\.git$/i, "");
|
|
5520
|
+
return normalized.includes(`github.com/${repo}.git#`) || source.includes(`github.com/${repo}.git`);
|
|
5521
|
+
}
|
|
5522
|
+
return false;
|
|
5523
|
+
}
|
|
5298
5524
|
function reachableNodeIds(graph, rootNodeId) {
|
|
5299
5525
|
const reachable = /* @__PURE__ */ new Set();
|
|
5300
5526
|
const queue = [rootNodeId];
|
|
@@ -5512,7 +5738,8 @@ async function createGraphSourcePlan(options) {
|
|
|
5512
5738
|
const manifest = await readInstallManifest(options.targetRoot, options.adapter.name, transport);
|
|
5513
5739
|
const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
|
|
5514
5740
|
baseRevision: manifest?.revision ?? null,
|
|
5515
|
-
graphLockDigest
|
|
5741
|
+
graphLockDigest,
|
|
5742
|
+
workspaceOwner: workspaceOwnerId(workspaceRoot)
|
|
5516
5743
|
});
|
|
5517
5744
|
return {
|
|
5518
5745
|
plan,
|
|
@@ -5570,6 +5797,9 @@ function sanitizePathSegment(value) {
|
|
|
5570
5797
|
function digestGraphLock(lock) {
|
|
5571
5798
|
return createHash6("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
|
|
5572
5799
|
}
|
|
5800
|
+
function workspaceOwnerId(workspaceRoot) {
|
|
5801
|
+
return `workspace-root:${resolve14(workspaceRoot)}`;
|
|
5802
|
+
}
|
|
5573
5803
|
function assertFrozenGraph(previousLock, graph, frozen, label) {
|
|
5574
5804
|
if (!frozen) return;
|
|
5575
5805
|
if (!previousLock) {
|
|
@@ -5652,7 +5882,8 @@ async function syncProfile(options) {
|
|
|
5652
5882
|
mode: options.mode ?? pkg.mode,
|
|
5653
5883
|
ref: pkg.requestedRef,
|
|
5654
5884
|
select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
5655
|
-
aliases: pkg.aliases
|
|
5885
|
+
aliases: pkg.aliases,
|
|
5886
|
+
overrides: pkg.overrides
|
|
5656
5887
|
})),
|
|
5657
5888
|
targetRoot: target.targetRoot,
|
|
5658
5889
|
workspaceRoot: options.workspaceRoot,
|
|
@@ -5743,7 +5974,7 @@ async function packageFromSource(source, options) {
|
|
|
5743
5974
|
}
|
|
5744
5975
|
|
|
5745
5976
|
// src/runtime/target.ts
|
|
5746
|
-
import { basename as basename12, dirname as dirname18, join as join23, resolve as
|
|
5977
|
+
import { basename as basename12, dirname as dirname18, join as join23, resolve as resolve15 } from "path";
|
|
5747
5978
|
var runtimeMarkers = [
|
|
5748
5979
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
5749
5980
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -5752,9 +5983,9 @@ var runtimeMarkers = [
|
|
|
5752
5983
|
{ adapter: "copilot", dirs: [".github"] }
|
|
5753
5984
|
];
|
|
5754
5985
|
async function resolveRuntimeTarget(request = {}) {
|
|
5755
|
-
const cwd =
|
|
5986
|
+
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
5756
5987
|
if (request.targetRoot) {
|
|
5757
|
-
const targetRoot =
|
|
5988
|
+
const targetRoot = resolve15(request.targetRoot);
|
|
5758
5989
|
return {
|
|
5759
5990
|
adapter: request.adapter ?? "openclaw",
|
|
5760
5991
|
targetRoot,
|
|
@@ -5783,7 +6014,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
5783
6014
|
async function resolveAllRuntimeTargets(request = {}) {
|
|
5784
6015
|
if (request.targetRoot) return [await resolveRuntimeTarget(request)];
|
|
5785
6016
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
5786
|
-
const cwd =
|
|
6017
|
+
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
5787
6018
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
5788
6019
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
5789
6020
|
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
@@ -5792,8 +6023,29 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
5792
6023
|
}
|
|
5793
6024
|
return targets;
|
|
5794
6025
|
}
|
|
6026
|
+
async function resolveAllDetectedRuntimeTargets(request = {}) {
|
|
6027
|
+
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
6028
|
+
const scanRoot = runtimeScanRoot(request);
|
|
6029
|
+
const matches = await detectRuntimeTargets(scanRoot, request.adapter);
|
|
6030
|
+
if (matches.length === 0) {
|
|
6031
|
+
throw new Error(`No runtime directories detected at ${scanRoot}. Pass --target-root or --agent.`);
|
|
6032
|
+
}
|
|
6033
|
+
return Promise.all(matches.map(async (match) => ({
|
|
6034
|
+
...match,
|
|
6035
|
+
workspaceRoot: await findWorkspaceRoot(match.targetRoot),
|
|
6036
|
+
transport: "local",
|
|
6037
|
+
source: "auto-detect"
|
|
6038
|
+
})));
|
|
6039
|
+
}
|
|
5795
6040
|
async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
5796
|
-
const
|
|
6041
|
+
const unique = await detectRuntimeTargets(cwd, adapterFilter);
|
|
6042
|
+
if (unique.length > 1) {
|
|
6043
|
+
throw new Error(`Multiple runtime directories detected: ${unique.map((item) => `${item.adapter} at ${item.targetRoot}`).join(", ")}. Pass --adapter, --agent, or --all-detected.`);
|
|
6044
|
+
}
|
|
6045
|
+
return unique[0];
|
|
6046
|
+
}
|
|
6047
|
+
async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
6048
|
+
const root = resolve15(cwd);
|
|
5797
6049
|
const matches = [];
|
|
5798
6050
|
for (const marker of runtimeMarkers) {
|
|
5799
6051
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
@@ -5805,11 +6057,7 @@ async function detectRuntimeTarget(cwd = process.cwd(), adapterFilter) {
|
|
|
5805
6057
|
}
|
|
5806
6058
|
}
|
|
5807
6059
|
}
|
|
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];
|
|
6060
|
+
return dedupeTargets(matches);
|
|
5813
6061
|
}
|
|
5814
6062
|
function targetFromAgent(name, config, workspaceRoot) {
|
|
5815
6063
|
const agent = config.agents[name];
|
|
@@ -5838,6 +6086,11 @@ function dedupeTargets(matches) {
|
|
|
5838
6086
|
}
|
|
5839
6087
|
return [...byKey.values()];
|
|
5840
6088
|
}
|
|
6089
|
+
function runtimeScanRoot(request) {
|
|
6090
|
+
const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
6091
|
+
if (request.targetRoot) return root;
|
|
6092
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename12(root))) ? dirname18(root) : root;
|
|
6093
|
+
}
|
|
5841
6094
|
|
|
5842
6095
|
// src/cli/update-check.ts
|
|
5843
6096
|
import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile10 } from "fs/promises";
|
|
@@ -5919,9 +6172,9 @@ function normalizeVersion(version) {
|
|
|
5919
6172
|
|
|
5920
6173
|
// src/model/package-validate.ts
|
|
5921
6174
|
import { stat as stat11 } from "fs/promises";
|
|
5922
|
-
import { resolve as
|
|
6175
|
+
import { resolve as resolve16 } from "path";
|
|
5923
6176
|
async function validatePackage(root) {
|
|
5924
|
-
const packageRoot =
|
|
6177
|
+
const packageRoot = resolve16(root);
|
|
5925
6178
|
const findings = [];
|
|
5926
6179
|
const manifestPath = await findPackageManifestPath(packageRoot);
|
|
5927
6180
|
if (!manifestPath) {
|
|
@@ -5989,7 +6242,7 @@ async function validateManifestComposeInclude(packageRoot, selector, optional, f
|
|
|
5989
6242
|
try {
|
|
5990
6243
|
validateSelector(selector, "compose.include", findings, manifestPath, { fragmentsOnly: true, aliases });
|
|
5991
6244
|
if (isCrossPackageSelector(selector)) return;
|
|
5992
|
-
const full =
|
|
6245
|
+
const full = resolve16(packageRoot, selector);
|
|
5993
6246
|
if (full !== packageRoot && !full.startsWith(`${packageRoot}/`)) {
|
|
5994
6247
|
findings.push({ level: "error", message: `Compose include escapes package root: ${selector}`, path: manifestPath });
|
|
5995
6248
|
return;
|
|
@@ -6043,10 +6296,10 @@ function isCrossPackageSelector(value) {
|
|
|
6043
6296
|
|
|
6044
6297
|
// src/model/package-migrate.ts
|
|
6045
6298
|
import { readFile as readFile17, rename as rename4, writeFile as writeFile11 } from "fs/promises";
|
|
6046
|
-
import { join as join26, resolve as
|
|
6299
|
+
import { join as join26, resolve as resolve17 } from "path";
|
|
6047
6300
|
import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
|
|
6048
6301
|
async function migratePackageManifest(root) {
|
|
6049
|
-
const packageRoot =
|
|
6302
|
+
const packageRoot = resolve17(root);
|
|
6050
6303
|
for (const name of openPackManifestNames) {
|
|
6051
6304
|
const path = join26(packageRoot, name);
|
|
6052
6305
|
if (await pathExists(path)) {
|
|
@@ -6133,7 +6386,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
|
|
|
6133
6386
|
if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
|
|
6134
6387
|
console.log(nextInstallNudge());
|
|
6135
6388
|
});
|
|
6136
|
-
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").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>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").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, []).action(async (source, options) => {
|
|
6389
|
+
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").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>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").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("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
|
|
6137
6390
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
6138
6391
|
const entry = await packageEntryFromSource(source, targetRoot, options);
|
|
6139
6392
|
await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
|
|
@@ -6165,13 +6418,13 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
6165
6418
|
}
|
|
6166
6419
|
if (!result.ok) process.exitCode = 1;
|
|
6167
6420
|
});
|
|
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) => {
|
|
6421
|
+
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("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--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
6422
|
await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
|
|
6170
6423
|
});
|
|
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) => {
|
|
6424
|
+
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("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--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
6425
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6173
6426
|
});
|
|
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) => {
|
|
6427
|
+
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("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--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
6428
|
console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
|
|
6176
6429
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6177
6430
|
});
|
|
@@ -6191,6 +6444,9 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
6191
6444
|
for (const decision of result.bundle.graphLock.canonical.namespacing) {
|
|
6192
6445
|
console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
6193
6446
|
}
|
|
6447
|
+
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
6448
|
+
console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
6449
|
+
}
|
|
6194
6450
|
await rm9(result.bundle.root, { recursive: true, force: true });
|
|
6195
6451
|
}
|
|
6196
6452
|
continue;
|
|
@@ -6345,7 +6601,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
6345
6601
|
let extraPackage;
|
|
6346
6602
|
if (nameOrSource && !configured) {
|
|
6347
6603
|
try {
|
|
6348
|
-
const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, options);
|
|
6604
|
+
const entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...options, adapter: options.adapter ?? target.adapter });
|
|
6349
6605
|
scope = entry.name;
|
|
6350
6606
|
if (behavior.apply) {
|
|
6351
6607
|
await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
|
|
@@ -6407,7 +6663,8 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
6407
6663
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
6408
6664
|
mode: options.mode ?? "pinned",
|
|
6409
6665
|
requestedRef: bundle.source.requestedRef,
|
|
6410
|
-
select: selectedArtifacts
|
|
6666
|
+
select: selectedArtifacts,
|
|
6667
|
+
overrides: overrideArtifactsFromOptions(options)
|
|
6411
6668
|
};
|
|
6412
6669
|
} finally {
|
|
6413
6670
|
await rm9(bundle.root, { recursive: true, force: true });
|
|
@@ -6433,9 +6690,15 @@ function nextInstallNudge() {
|
|
|
6433
6690
|
return "Preview: agentwheel plan - Apply: agentwheel install";
|
|
6434
6691
|
}
|
|
6435
6692
|
async function resolveCliTargets(options) {
|
|
6693
|
+
if (options.all && options.allDetected) {
|
|
6694
|
+
throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
|
|
6695
|
+
}
|
|
6436
6696
|
if (options.all) {
|
|
6437
6697
|
return resolveAllRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, all: options.all });
|
|
6438
6698
|
}
|
|
6699
|
+
if (options.allDetected) {
|
|
6700
|
+
return resolveAllDetectedRuntimeTargets({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent, allDetected: options.allDetected });
|
|
6701
|
+
}
|
|
6439
6702
|
return [await resolveRuntimeTarget({ targetRoot: options.targetRoot, adapter: options.adapter, agent: options.agent })];
|
|
6440
6703
|
}
|
|
6441
6704
|
async function resolveAdapterForTarget(target, options) {
|
|
@@ -6523,6 +6786,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
6523
6786
|
ref: pkg.requestedRef,
|
|
6524
6787
|
select: selectedArtifacts ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
6525
6788
|
aliases: pkg.aliases,
|
|
6789
|
+
overrides: pkg.overrides,
|
|
6526
6790
|
useLock: behavior.mode === "install" ? true : !updateThisPackage
|
|
6527
6791
|
};
|
|
6528
6792
|
}),
|
|
@@ -6659,6 +6923,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
|
|
|
6659
6923
|
graphNodeId: fresh?.graphNodeId ?? ("graphNodeId" in entry ? entry.graphNodeId : void 0),
|
|
6660
6924
|
dependencyRole: fresh?.dependencyRole ?? ("dependencyRole" in entry ? entry.dependencyRole : "root"),
|
|
6661
6925
|
owners: fresh?.owners ?? owners,
|
|
6926
|
+
workspaceOwner: fresh?.workspaceOwner ?? ("workspaceOwner" in entry ? entry.workspaceOwner : "legacy:unowned"),
|
|
6662
6927
|
graphLockDigest: fresh ? void 0 : "graphLockDigest" in entry ? entry.graphLockDigest : void 0
|
|
6663
6928
|
};
|
|
6664
6929
|
}
|
|
@@ -6702,7 +6967,8 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
6702
6967
|
mode: pkg2.mode,
|
|
6703
6968
|
ref: pkg2.requestedRef,
|
|
6704
6969
|
select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
|
|
6705
|
-
aliases: pkg2.aliases
|
|
6970
|
+
aliases: pkg2.aliases,
|
|
6971
|
+
overrides: pkg2.overrides
|
|
6706
6972
|
})),
|
|
6707
6973
|
targetRoot: remainingGroup.target.targetRoot,
|
|
6708
6974
|
workspaceRoot: remainingGroup.target.workspaceRoot,
|
|
@@ -6865,9 +7131,16 @@ function collectSkillOption(value, previous) {
|
|
|
6865
7131
|
function collectTrustOption(value, previous) {
|
|
6866
7132
|
return [...previous, value];
|
|
6867
7133
|
}
|
|
7134
|
+
function collectOverrideOption(value, previous) {
|
|
7135
|
+
return [...previous, ...splitSelectorList(value)];
|
|
7136
|
+
}
|
|
6868
7137
|
function selectedArtifactsFromOptions(options) {
|
|
6869
7138
|
return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
|
|
6870
7139
|
}
|
|
7140
|
+
function overrideArtifactsFromOptions(options) {
|
|
7141
|
+
const values = options.overrides ?? options.override;
|
|
7142
|
+
return values && values.length > 0 ? values : void 0;
|
|
7143
|
+
}
|
|
6871
7144
|
function filterUninstallPlanBySelection(plan, selected) {
|
|
6872
7145
|
if (!selected?.length) return plan;
|
|
6873
7146
|
const requested = normalizeArtifactSelectors(selected) ?? [];
|
|
@@ -6955,7 +7228,7 @@ function withFleetExample(config) {
|
|
|
6955
7228
|
};
|
|
6956
7229
|
}
|
|
6957
7230
|
async function findAgentwheelPackageRoot(start) {
|
|
6958
|
-
let current =
|
|
7231
|
+
let current = resolve18(start);
|
|
6959
7232
|
while (true) {
|
|
6960
7233
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
6961
7234
|
const parent = dirname21(current);
|
|
@@ -6988,7 +7261,9 @@ async function main() {
|
|
|
6988
7261
|
});
|
|
6989
7262
|
await program.parseAsync();
|
|
6990
7263
|
}
|
|
6991
|
-
|
|
7264
|
+
try {
|
|
7265
|
+
await main();
|
|
7266
|
+
} catch (error) {
|
|
6992
7267
|
console.error(error instanceof Error ? error.message : String(error));
|
|
6993
7268
|
process.exitCode = 1;
|
|
6994
|
-
}
|
|
7269
|
+
}
|
package/openpack.json
CHANGED
package/package.json
CHANGED
|
@@ -141,6 +141,34 @@ agentwheel add github:owner/repo --select skills/code-review --select rules/core
|
|
|
141
141
|
agentwheel add github:owner/repo --select skills/code-review,rules/core.md
|
|
142
142
|
```
|
|
143
143
|
|
|
144
|
+
Use a source override when a selected package should replace the same artifact coming from another
|
|
145
|
+
source, such as a forked skill overriding a meta-pack dependency. The declaration is explicit and
|
|
146
|
+
planning fails if it does not match exactly one losing artifact and one selected replacement:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
agentwheel add github:Yehonal/agent-toolkit#main \
|
|
150
|
+
--skill self-improve \
|
|
151
|
+
--override 'github:FrancescoBorzi/agent-toolkit::skills/self-improve'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Equivalent config:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"name": "agent-toolkit-nestdev",
|
|
159
|
+
"source": "github:Yehonal/agent-toolkit#main",
|
|
160
|
+
"driver": "git",
|
|
161
|
+
"adapter": "codex",
|
|
162
|
+
"mode": "tracking",
|
|
163
|
+
"select": ["skills/self-improve"],
|
|
164
|
+
"overrides": ["github:FrancescoBorzi/agent-toolkit::skills/self-improve"]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`source::type/name` identifies the artifact being replaced. `github:owner/repo` matches any ref
|
|
169
|
+
for that repo; include `#main` or another ref to narrow it. Review `OVERRIDE` lines in
|
|
170
|
+
`agentwheel plan`, `agentwheel deps tree`, or `agentwheel deps why` before applying fleet-wide.
|
|
171
|
+
|
|
144
172
|
Use `--name` for a stable local alias:
|
|
145
173
|
|
|
146
174
|
```bash
|
|
@@ -289,7 +317,9 @@ Drift means a managed runtime output changed outside agentwheel. Fix drift by ch
|
|
|
289
317
|
|
|
290
318
|
- Layer local instructions with `agentwheel remember`.
|
|
291
319
|
- Add separate local artifacts under `.agentwheel/additions`.
|
|
292
|
-
- Override
|
|
320
|
+
- Override upstream content under `.agentwheel/overrides`.
|
|
321
|
+
- Override source precedence with package `overrides` when a forked source should replace a
|
|
322
|
+
colliding upstream artifact.
|
|
293
323
|
- Eject an item into `.agentwheel/ejected` when the user wants local ownership.
|
|
294
324
|
|
|
295
325
|
Append durable local instruction text:
|