agentwheel 0.10.0 → 0.14.3
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 +118 -31
- package/dist/{chunk-N2LZY7LO.js → chunk-B3FMBTWC.js} +8 -2
- package/dist/{identify-7SEBWCNQ.js → identify-TXIDGMNL.js} +1 -1
- package/dist/index.js +1837 -623
- package/openpack.json +2 -2
- package/package.json +2 -1
- package/skills/agentwheel/SKILL.md +66 -13
- package/skills/agentwheel-smoke/SKILL.md +63 -0
- package/skills/agentwheel-smoke/scripts/smoke.mjs +397 -0
package/dist/index.js
CHANGED
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
atomicCopy,
|
|
4
4
|
hashPath,
|
|
5
5
|
inferSourceDriverName,
|
|
6
|
+
isIgnoredGeneratedEntry,
|
|
6
7
|
pathExists,
|
|
7
8
|
writeJsonAtomic
|
|
8
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-B3FMBTWC.js";
|
|
9
10
|
|
|
10
11
|
// src/cli/index.ts
|
|
11
|
-
import { mkdir as
|
|
12
|
-
import {
|
|
12
|
+
import { mkdir as mkdir16, rm as rm9, writeFile as writeFile14 } from "fs/promises";
|
|
13
|
+
import { homedir as homedir9 } from "os";
|
|
14
|
+
import { dirname as dirname23, join as join31, resolve as resolve18 } from "path";
|
|
13
15
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
14
16
|
import { Command } from "commander";
|
|
15
17
|
|
|
@@ -18,6 +20,7 @@ import { resolve as resolve2 } from "path";
|
|
|
18
20
|
|
|
19
21
|
// src/model/adapter.ts
|
|
20
22
|
import { readFile } from "fs/promises";
|
|
23
|
+
import { homedir } from "os";
|
|
21
24
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
22
25
|
import { z as z2 } from "zod";
|
|
23
26
|
|
|
@@ -36,6 +39,7 @@ var artifactTypeSchema = z.enum([
|
|
|
36
39
|
"fragments"
|
|
37
40
|
]);
|
|
38
41
|
var fileKindSchema = z.enum(["file", "dir"]);
|
|
42
|
+
var artifactFormatSchema = z.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i, "artifact format must be a stable identifier");
|
|
39
43
|
var packageAssetSchema = z.object({
|
|
40
44
|
from: z.string().min(1),
|
|
41
45
|
into: z.string().min(1),
|
|
@@ -68,6 +72,7 @@ var artifactSchema = z.object({
|
|
|
68
72
|
relativePath: z.string().min(1),
|
|
69
73
|
kind: fileKindSchema,
|
|
70
74
|
hash: z.string().min(16),
|
|
75
|
+
format: artifactFormatSchema.optional(),
|
|
71
76
|
packageName: z.string().min(1).optional(),
|
|
72
77
|
channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
|
|
73
78
|
assets: z.array(packageAssetSchema).optional(),
|
|
@@ -79,20 +84,105 @@ var artifactSchema = z.object({
|
|
|
79
84
|
});
|
|
80
85
|
|
|
81
86
|
// src/model/adapter.ts
|
|
87
|
+
var defaultInstallationType = "local";
|
|
88
|
+
var installationTypeSchema = z2.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i, "installation type must be a stable path-safe identifier");
|
|
82
89
|
var targetMappingSchema = z2.object({
|
|
83
90
|
dest: z2.string().min(1),
|
|
84
91
|
enabled: z2.boolean().default(true),
|
|
85
|
-
|
|
92
|
+
root: z2.enum(["target", "home"]).optional(),
|
|
93
|
+
formats: z2.array(z2.string().min(1)).optional(),
|
|
94
|
+
semantic: z2.enum(["openclaw-plugin", "codex-subagent", "copilot-instruction", "copilot-prompt", "copilot-agent"]).optional(),
|
|
86
95
|
merge: z2.enum(["json-deep", "codex-toml-mcp"]).optional()
|
|
87
96
|
});
|
|
97
|
+
var targetRegistrySchema = z2.record(installationTypeSchema, targetMappingSchema);
|
|
98
|
+
var targetRegistryInputSchema = z2.union([
|
|
99
|
+
targetMappingSchema.transform((mapping) => ({ [defaultInstallationType]: mapping })),
|
|
100
|
+
targetRegistrySchema
|
|
101
|
+
]);
|
|
88
102
|
var adapterSchema = z2.object({
|
|
89
103
|
name: z2.string().min(1),
|
|
90
104
|
displayName: z2.string().min(1).optional(),
|
|
91
105
|
targets: z2.partialRecord(
|
|
92
106
|
artifactTypeSchema,
|
|
93
|
-
|
|
107
|
+
targetRegistryInputSchema
|
|
94
108
|
).default({})
|
|
95
109
|
});
|
|
110
|
+
function supportedInstallationTypes(adapter, artifactType) {
|
|
111
|
+
const registries = artifactType ? [adapter.targets[artifactType]] : Object.entries(adapter.targets).filter(([type]) => type !== "fragments").map(([, registry]) => registry);
|
|
112
|
+
const types = /* @__PURE__ */ new Set();
|
|
113
|
+
for (const registry of registries) {
|
|
114
|
+
for (const [installationType, target] of Object.entries(registry ?? {})) {
|
|
115
|
+
if (target.enabled) types.add(installationType);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return [...types].sort((a, b) => a.localeCompare(b));
|
|
119
|
+
}
|
|
120
|
+
function resolveInstallationTypeForArtifacts(adapter, artifactTypes, requested) {
|
|
121
|
+
const installableTypes = [...new Set(artifactTypes.filter((type) => type !== "fragments"))];
|
|
122
|
+
if (installableTypes.length === 0) {
|
|
123
|
+
return requested ?? resolveInstallationTypeForAdapter(adapter, requested);
|
|
124
|
+
}
|
|
125
|
+
for (const type of installableTypes) {
|
|
126
|
+
const supported = supportedInstallationTypes(adapter, type);
|
|
127
|
+
if (supported.length === 0) {
|
|
128
|
+
throw new Error(`Adapter ${adapter.name} does not support ${type} artifacts for any installation type.`);
|
|
129
|
+
}
|
|
130
|
+
if (requested && !supported.includes(requested)) {
|
|
131
|
+
throw new Error(`Adapter ${adapter.name} does not support ${type} artifacts for installation type '${requested}'. Supported: ${supported.join(", ")}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (requested) return requested;
|
|
135
|
+
const [firstType, ...restTypes] = installableTypes;
|
|
136
|
+
let candidates = new Set(supportedInstallationTypes(adapter, firstType));
|
|
137
|
+
for (const type of restTypes) {
|
|
138
|
+
const supported = new Set(supportedInstallationTypes(adapter, type));
|
|
139
|
+
candidates = new Set([...candidates].filter((candidate) => supported.has(candidate)));
|
|
140
|
+
}
|
|
141
|
+
const available = [...candidates].sort((a, b) => a.localeCompare(b));
|
|
142
|
+
if (available.length === 1) return available[0];
|
|
143
|
+
if (available.length === 0) {
|
|
144
|
+
throw new Error(`Adapter ${adapter.name} has no common installation type for: ${installableTypes.join(", ")}`);
|
|
145
|
+
}
|
|
146
|
+
throw new Error(`Installation type required for ${adapter.name}; supported for selected artifacts: ${available.join(", ")}. Pass --installation-type <type>.`);
|
|
147
|
+
}
|
|
148
|
+
function resolveInstallationTypeForAdapter(adapter, requested) {
|
|
149
|
+
const supported = supportedInstallationTypes(adapter);
|
|
150
|
+
if (requested) {
|
|
151
|
+
if (!supported.includes(requested)) {
|
|
152
|
+
throw new Error(`Adapter ${adapter.name} does not support installation type '${requested}'. Supported: ${supported.join(", ") || "<none>"}`);
|
|
153
|
+
}
|
|
154
|
+
return requested;
|
|
155
|
+
}
|
|
156
|
+
if (supported.length === 1) return supported[0];
|
|
157
|
+
if (supported.length === 0) return defaultInstallationType;
|
|
158
|
+
throw new Error(`Installation type required for ${adapter.name}; supported: ${supported.join(", ")}. Pass --installation-type <type>.`);
|
|
159
|
+
}
|
|
160
|
+
function targetMappingForArtifact(adapter, artifactType, installationType) {
|
|
161
|
+
return adapter.targets[artifactType]?.[installationType];
|
|
162
|
+
}
|
|
163
|
+
function installRootForArtifacts(adapter, targetRoot, installationType, artifactTypes, isSsh = false) {
|
|
164
|
+
const roots = new Set(
|
|
165
|
+
[...new Set(artifactTypes.filter((type) => type !== "fragments"))].map((type) => targetMappingForArtifact(adapter, type, installationType)?.root ?? "target")
|
|
166
|
+
);
|
|
167
|
+
if (roots.has("home") && roots.has("target")) {
|
|
168
|
+
throw new Error(`Installation type '${installationType}' for ${adapter.name} mixes home-rooted and target-rooted artifacts.`);
|
|
169
|
+
}
|
|
170
|
+
return roots.has("home") && !isSsh ? userHomeRoot() : targetRoot;
|
|
171
|
+
}
|
|
172
|
+
function installRootForAdapterInstallationType(adapter, targetRoot, installationType, isSsh = false) {
|
|
173
|
+
const roots = /* @__PURE__ */ new Set();
|
|
174
|
+
for (const registry of Object.values(adapter.targets)) {
|
|
175
|
+
const target = registry?.[installationType];
|
|
176
|
+
if (target?.enabled) roots.add(target.root ?? "target");
|
|
177
|
+
}
|
|
178
|
+
if (roots.has("home") && roots.has("target")) {
|
|
179
|
+
throw new Error(`Installation type '${installationType}' for ${adapter.name} mixes home-rooted and target-rooted artifacts.`);
|
|
180
|
+
}
|
|
181
|
+
return roots.has("home") && !isSsh ? userHomeRoot() : targetRoot;
|
|
182
|
+
}
|
|
183
|
+
function userHomeRoot() {
|
|
184
|
+
return process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir();
|
|
185
|
+
}
|
|
96
186
|
async function loadAdapterConfig(path) {
|
|
97
187
|
const content = await readFile(path, "utf8");
|
|
98
188
|
const errors = [];
|
|
@@ -112,28 +202,72 @@ var claudeAdapter = {
|
|
|
112
202
|
name: "claude",
|
|
113
203
|
displayName: "Claude Code",
|
|
114
204
|
targets: {
|
|
115
|
-
instructions: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
205
|
+
instructions: {
|
|
206
|
+
local: { enabled: true, dest: "CLAUDE.md" },
|
|
207
|
+
user: { enabled: true, root: "home", dest: ".claude/CLAUDE.md" }
|
|
208
|
+
},
|
|
209
|
+
rules: {
|
|
210
|
+
local: { enabled: true, dest: ".claude/rules", formats: ["markdown-rule", "claude-markdown-rule"] },
|
|
211
|
+
user: { enabled: true, root: "home", dest: ".claude/rules", formats: ["markdown-rule", "claude-markdown-rule"] }
|
|
212
|
+
},
|
|
213
|
+
skills: {
|
|
214
|
+
local: { enabled: true, dest: ".claude/skills" },
|
|
215
|
+
user: { enabled: true, root: "home", dest: ".claude/skills" }
|
|
216
|
+
},
|
|
217
|
+
commands: {
|
|
218
|
+
local: { enabled: true, dest: ".claude/commands" },
|
|
219
|
+
user: { enabled: true, root: "home", dest: ".claude/commands" }
|
|
220
|
+
},
|
|
221
|
+
subagents: {
|
|
222
|
+
local: { enabled: true, dest: ".claude/agents" },
|
|
223
|
+
user: { enabled: true, root: "home", dest: ".claude/agents" }
|
|
224
|
+
},
|
|
225
|
+
mcp: {
|
|
226
|
+
local: { enabled: true, dest: ".mcp.json", merge: "json-deep" }
|
|
227
|
+
},
|
|
228
|
+
hooks: {
|
|
229
|
+
local: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" },
|
|
230
|
+
user: { enabled: true, root: "home", dest: ".claude/settings.json", merge: "json-deep" }
|
|
231
|
+
},
|
|
232
|
+
settings: {
|
|
233
|
+
local: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" },
|
|
234
|
+
user: { enabled: true, root: "home", dest: ".claude/settings.json", merge: "json-deep" }
|
|
235
|
+
}
|
|
123
236
|
}
|
|
124
237
|
};
|
|
125
238
|
|
|
126
239
|
// src/adapters/copilot.ts
|
|
127
240
|
var copilotAdapter = {
|
|
128
241
|
name: "copilot",
|
|
129
|
-
displayName: "GitHub Copilot",
|
|
242
|
+
displayName: "GitHub Copilot CLI",
|
|
130
243
|
targets: {
|
|
131
|
-
instructions: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
244
|
+
instructions: {
|
|
245
|
+
local: { enabled: true, dest: ".github/copilot-instructions.md" },
|
|
246
|
+
user: { enabled: true, root: "home", dest: ".copilot/copilot-instructions.md" }
|
|
247
|
+
},
|
|
248
|
+
rules: {
|
|
249
|
+
local: { enabled: true, dest: ".github/instructions", formats: ["markdown-rule", "copilot-instruction-rule"], semantic: "copilot-instruction" },
|
|
250
|
+
user: { enabled: true, root: "home", dest: ".copilot/instructions", formats: ["markdown-rule", "copilot-instruction-rule"], semantic: "copilot-instruction" }
|
|
251
|
+
},
|
|
252
|
+
commands: {
|
|
253
|
+
local: { enabled: true, dest: ".github/prompts", semantic: "copilot-prompt" }
|
|
254
|
+
},
|
|
255
|
+
skills: {
|
|
256
|
+
local: { enabled: true, dest: ".github/skills" },
|
|
257
|
+
user: { enabled: true, root: "home", dest: ".copilot/skills" }
|
|
258
|
+
},
|
|
259
|
+
subagents: {
|
|
260
|
+
local: { enabled: true, dest: ".github/agents", semantic: "copilot-agent" },
|
|
261
|
+
user: { enabled: true, root: "home", dest: ".copilot/agents", semantic: "copilot-agent" }
|
|
262
|
+
},
|
|
263
|
+
mcp: {
|
|
264
|
+
local: { enabled: true, dest: ".github/mcp.json", merge: "json-deep" },
|
|
265
|
+
user: { enabled: true, root: "home", dest: ".copilot/mcp-config.json", merge: "json-deep" }
|
|
266
|
+
},
|
|
267
|
+
hooks: {
|
|
268
|
+
local: { enabled: true, dest: ".github/hooks" },
|
|
269
|
+
user: { enabled: true, root: "home", dest: ".copilot/hooks" }
|
|
270
|
+
}
|
|
137
271
|
}
|
|
138
272
|
};
|
|
139
273
|
|
|
@@ -142,14 +276,30 @@ var codexAdapter = {
|
|
|
142
276
|
name: "codex",
|
|
143
277
|
displayName: "Codex CLI",
|
|
144
278
|
targets: {
|
|
145
|
-
instructions: {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
279
|
+
instructions: {
|
|
280
|
+
local: { enabled: true, dest: "AGENTS.md" },
|
|
281
|
+
user: { enabled: true, root: "home", dest: ".codex/AGENTS.md" }
|
|
282
|
+
},
|
|
283
|
+
rules: {
|
|
284
|
+
local: { enabled: true, dest: ".codex/rules", formats: ["codex-command-policy"] },
|
|
285
|
+
user: { enabled: true, root: "home", dest: ".codex/rules", formats: ["codex-command-policy"] }
|
|
286
|
+
},
|
|
287
|
+
skills: {
|
|
288
|
+
local: { enabled: true, dest: ".agents/skills" },
|
|
289
|
+
user: { enabled: true, root: "home", dest: ".agents/skills" }
|
|
290
|
+
},
|
|
291
|
+
subagents: {
|
|
292
|
+
local: { enabled: true, dest: ".codex/agents", semantic: "codex-subagent" },
|
|
293
|
+
user: { enabled: true, root: "home", dest: ".codex/agents", semantic: "codex-subagent" }
|
|
294
|
+
},
|
|
295
|
+
mcp: {
|
|
296
|
+
local: { enabled: true, dest: ".codex/config.toml", merge: "codex-toml-mcp" },
|
|
297
|
+
user: { enabled: true, root: "home", dest: ".codex/config.toml", merge: "codex-toml-mcp" }
|
|
298
|
+
},
|
|
299
|
+
hooks: {
|
|
300
|
+
local: { enabled: true, dest: ".codex/hooks.json", merge: "json-deep" },
|
|
301
|
+
user: { enabled: true, root: "home", dest: ".codex/hooks.json", merge: "json-deep" }
|
|
302
|
+
}
|
|
153
303
|
}
|
|
154
304
|
};
|
|
155
305
|
|
|
@@ -158,14 +308,12 @@ var hermesAdapter = {
|
|
|
158
308
|
name: "hermes",
|
|
159
309
|
displayName: "Hermes",
|
|
160
310
|
targets: {
|
|
161
|
-
instructions: {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
|
|
168
|
-
settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
|
|
311
|
+
instructions: {
|
|
312
|
+
local: { enabled: true, dest: "AGENTS.md" }
|
|
313
|
+
},
|
|
314
|
+
skills: {
|
|
315
|
+
user: { enabled: true, root: "home", dest: ".hermes/skills" }
|
|
316
|
+
}
|
|
169
317
|
}
|
|
170
318
|
};
|
|
171
319
|
|
|
@@ -174,15 +322,13 @@ var openClawAdapter = {
|
|
|
174
322
|
name: "openclaw",
|
|
175
323
|
displayName: "OpenClaw",
|
|
176
324
|
targets: {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
|
|
185
|
-
plugins: { enabled: true, dest: ".openclaw/plugins", semantic: "openclaw-plugin" }
|
|
325
|
+
skills: {
|
|
326
|
+
local: { enabled: true, dest: "skills" },
|
|
327
|
+
user: { enabled: true, root: "home", dest: ".openclaw/skills" }
|
|
328
|
+
},
|
|
329
|
+
plugins: {
|
|
330
|
+
local: { enabled: true, dest: ".openclaw/plugins", formats: ["openclaw-plugin"], semantic: "openclaw-plugin" }
|
|
331
|
+
}
|
|
186
332
|
}
|
|
187
333
|
};
|
|
188
334
|
|
|
@@ -264,11 +410,11 @@ async function resolveAdapter(options) {
|
|
|
264
410
|
}
|
|
265
411
|
|
|
266
412
|
// src/install/apply.ts
|
|
267
|
-
import { execFile as
|
|
413
|
+
import { execFile as execFile3 } from "child_process";
|
|
268
414
|
import { mkdtemp, rm as rm3, writeFile as writeFile6 } from "fs/promises";
|
|
269
415
|
import { tmpdir as tmpdir2 } from "os";
|
|
270
416
|
import { basename as basename2, join as join4 } from "path";
|
|
271
|
-
import { promisify as
|
|
417
|
+
import { promisify as promisify3 } from "util";
|
|
272
418
|
|
|
273
419
|
// src/model/graph-lock.ts
|
|
274
420
|
import { createHash } from "crypto";
|
|
@@ -297,7 +443,8 @@ var graphLockRootSchema = z3.object({
|
|
|
297
443
|
graphNodeId: z3.string().min(1),
|
|
298
444
|
mode: z3.enum(["pinned", "tracking"]),
|
|
299
445
|
selected: z3.array(z3.string().min(1)),
|
|
300
|
-
aliases: z3.record(z3.string(), z3.string().min(1)).optional()
|
|
446
|
+
aliases: z3.record(z3.string(), z3.string().min(1)).optional(),
|
|
447
|
+
overrides: z3.array(z3.string().min(1)).optional()
|
|
301
448
|
});
|
|
302
449
|
var graphLockEdgeSchema = z3.object({
|
|
303
450
|
from: z3.string().min(1),
|
|
@@ -346,6 +493,15 @@ var graphLockNamespacingSchema = z3.object({
|
|
|
346
493
|
installName: z3.string().min(1),
|
|
347
494
|
reason: z3.enum(["alias", "transitive-collision"])
|
|
348
495
|
});
|
|
496
|
+
var graphLockOverrideSchema = z3.object({
|
|
497
|
+
rootId: z3.string().min(1),
|
|
498
|
+
selector: z3.string().min(1),
|
|
499
|
+
graphNodeId: z3.string().min(1),
|
|
500
|
+
overriddenGraphNodeId: z3.string().min(1),
|
|
501
|
+
type: artifactTypeSchema,
|
|
502
|
+
name: z3.string().min(1),
|
|
503
|
+
installName: z3.string().min(1)
|
|
504
|
+
});
|
|
349
505
|
var graphLockCanonicalSchema = z3.object({
|
|
350
506
|
targetFingerprint: z3.string().min(1).optional(),
|
|
351
507
|
roots: z3.array(graphLockRootSchema),
|
|
@@ -354,6 +510,7 @@ var graphLockCanonicalSchema = z3.object({
|
|
|
354
510
|
includeEdges: z3.array(graphLockIncludeEdgeSchema).default([]),
|
|
355
511
|
artifacts: z3.array(graphLockArtifactSchema).default([]),
|
|
356
512
|
namespacing: z3.array(graphLockNamespacingSchema).default([]),
|
|
513
|
+
overrides: z3.array(graphLockOverrideSchema).default([]),
|
|
357
514
|
plainNameIncumbents: z3.array(graphLockPlainNameIncumbentSchema).default([])
|
|
358
515
|
});
|
|
359
516
|
var graphLockSchema = z3.object({
|
|
@@ -384,7 +541,11 @@ function canonicalizeGraphLock(lock) {
|
|
|
384
541
|
version: 1,
|
|
385
542
|
canonical: {
|
|
386
543
|
targetFingerprint: parsed.canonical.targetFingerprint,
|
|
387
|
-
roots: [...parsed.canonical.roots].map((root) => ({
|
|
544
|
+
roots: [...parsed.canonical.roots].map((root) => ({
|
|
545
|
+
...root,
|
|
546
|
+
selected: sortedUnique(root.selected),
|
|
547
|
+
overrides: root.overrides ? sortedUnique(root.overrides) : void 0
|
|
548
|
+
})).sort((a, b) => `${a.rootId}:${a.graphNodeId}`.localeCompare(`${b.rootId}:${b.graphNodeId}`)),
|
|
388
549
|
nodes: [...parsed.canonical.nodes].map((node) => ({
|
|
389
550
|
...node,
|
|
390
551
|
requiredBy: sortedUnique(node.requiredBy),
|
|
@@ -395,6 +556,7 @@ function canonicalizeGraphLock(lock) {
|
|
|
395
556
|
includeEdges: [...parsed.canonical.includeEdges].sort((a, b) => `${a.fromNodeId}:${a.alias}:${a.toNodeId}:${a.selector}`.localeCompare(`${b.fromNodeId}:${b.alias}:${b.toNodeId}:${b.selector}`)),
|
|
396
557
|
artifacts: [...parsed.canonical.artifacts].map((artifact) => ({ ...artifact, owners: sortedUnique(artifact.owners) })).sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector)),
|
|
397
558
|
namespacing: [...parsed.canonical.namespacing].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.name}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.name}`)),
|
|
559
|
+
overrides: [...parsed.canonical.overrides].sort((a, b) => `${a.type}:${a.installName}:${a.graphNodeId}:${a.overriddenGraphNodeId}`.localeCompare(`${b.type}:${b.installName}:${b.graphNodeId}:${b.overriddenGraphNodeId}`)),
|
|
398
560
|
plainNameIncumbents: [...parsed.canonical.plainNameIncumbents].sort((a, b) => `${a.adapter}:${a.targetFingerprint}:${a.type}:${a.name}`.localeCompare(`${b.adapter}:${b.targetFingerprint}:${b.type}:${b.name}`))
|
|
399
561
|
}
|
|
400
562
|
};
|
|
@@ -428,8 +590,11 @@ function stableValue(value) {
|
|
|
428
590
|
}
|
|
429
591
|
|
|
430
592
|
// src/transport/local.ts
|
|
593
|
+
import { execFile } from "child_process";
|
|
431
594
|
import { mkdir as mkdir2, readFile as readFile4, rename as rename2, rm, writeFile as writeFile3 } from "fs/promises";
|
|
432
595
|
import { dirname as dirname2 } from "path";
|
|
596
|
+
import { promisify } from "util";
|
|
597
|
+
var execFileAsync = promisify(execFile);
|
|
433
598
|
var localTransport = {
|
|
434
599
|
kind: "local",
|
|
435
600
|
description: "local filesystem",
|
|
@@ -448,20 +613,23 @@ var localTransport = {
|
|
|
448
613
|
},
|
|
449
614
|
writeJsonAtomic,
|
|
450
615
|
atomicCopy,
|
|
451
|
-
rm: (path) => rm(path, { recursive: true, force: true })
|
|
616
|
+
rm: (path) => rm(path, { recursive: true, force: true }),
|
|
617
|
+
async execFile(command, args, options = {}) {
|
|
618
|
+
await execFileAsync(command, args, { cwd: options.cwd });
|
|
619
|
+
}
|
|
452
620
|
};
|
|
453
621
|
|
|
454
622
|
// src/transport/ssh.ts
|
|
455
|
-
import { execFile, spawn } from "child_process";
|
|
623
|
+
import { execFile as execFile2, spawn } from "child_process";
|
|
456
624
|
import { basename, dirname as dirname3 } from "path";
|
|
457
625
|
import { dirname as posixDirname } from "path/posix";
|
|
458
|
-
import { promisify } from "util";
|
|
459
|
-
var
|
|
626
|
+
import { promisify as promisify2 } from "util";
|
|
627
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
460
628
|
function createSshTransport(config) {
|
|
461
629
|
const endpoint = config.user ? `${config.user}@${config.host}` : config.host;
|
|
462
630
|
const args = baseSshArgs(config, endpoint);
|
|
463
631
|
async function run(command) {
|
|
464
|
-
const { stdout } = await
|
|
632
|
+
const { stdout } = await execFileAsync2("ssh", [...args, command], { maxBuffer: 20 * 1024 * 1024 });
|
|
465
633
|
return stdout;
|
|
466
634
|
}
|
|
467
635
|
async function runWithInput(command, input) {
|
|
@@ -507,6 +675,11 @@ function createSshTransport(config) {
|
|
|
507
675
|
},
|
|
508
676
|
rm(path) {
|
|
509
677
|
return run(`rm -rf -- ${quoteSh(path)}`).then(() => void 0);
|
|
678
|
+
},
|
|
679
|
+
execFile(command, commandArgs, options = {}) {
|
|
680
|
+
const quoted = [command, ...commandArgs].map(quoteSh).join(" ");
|
|
681
|
+
const remoteCommand = options.cwd ? `cd ${quoteSh(options.cwd)} && ${quoted}` : quoted;
|
|
682
|
+
return run(remoteCommand).then(() => void 0);
|
|
510
683
|
}
|
|
511
684
|
};
|
|
512
685
|
}
|
|
@@ -573,6 +746,11 @@ const { createHash } = require("node:crypto");
|
|
|
573
746
|
const { readdirSync, readFileSync, statSync } = require("node:fs");
|
|
574
747
|
const { join, relative } = require("node:path");
|
|
575
748
|
const target = process.argv[1];
|
|
749
|
+
const ignoredNames = new Set([".git", "node_modules", "__pycache__", ".DS_Store"]);
|
|
750
|
+
const ignoredSuffixes = [".pyc", ".pyo"];
|
|
751
|
+
function isIgnoredGeneratedEntry(name) {
|
|
752
|
+
return ignoredNames.has(name) || ignoredSuffixes.some((suffix) => name.endsWith(suffix));
|
|
753
|
+
}
|
|
576
754
|
function hashPath(path) {
|
|
577
755
|
const stats = statSync(path);
|
|
578
756
|
if (stats.isFile()) {
|
|
@@ -590,7 +768,7 @@ function listFiles(root) {
|
|
|
590
768
|
const out = [];
|
|
591
769
|
function walk(dir) {
|
|
592
770
|
for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
593
|
-
if (entry.name
|
|
771
|
+
if (isIgnoredGeneratedEntry(entry.name)) continue;
|
|
594
772
|
const full = join(dir, entry.name);
|
|
595
773
|
if (entry.isDirectory()) walk(full);
|
|
596
774
|
else if (entry.isFile()) out.push(full);
|
|
@@ -706,6 +884,8 @@ var installManifestV1Schema = z4.object({
|
|
|
706
884
|
var installManifestV2Schema = z4.object({
|
|
707
885
|
version: z4.literal(2),
|
|
708
886
|
adapter: z4.string().min(1),
|
|
887
|
+
installationType: installationTypeSchema.default(defaultInstallationType),
|
|
888
|
+
stateKey: z4.string().min(1).optional(),
|
|
709
889
|
targetRoot: z4.string().min(1),
|
|
710
890
|
generatedAt: z4.string().datetime(),
|
|
711
891
|
revision: z4.string().min(16),
|
|
@@ -738,6 +918,7 @@ var sourceLockSchema = z4.object({
|
|
|
738
918
|
relativePath: z4.string().min(1),
|
|
739
919
|
kind: fileKindSchema,
|
|
740
920
|
hash: z4.string().min(16),
|
|
921
|
+
format: artifactFormatSchema.optional(),
|
|
741
922
|
composedFrom: z4.array(composedFromEntrySchema).optional()
|
|
742
923
|
})
|
|
743
924
|
)
|
|
@@ -748,16 +929,25 @@ import { join as join2 } from "path";
|
|
|
748
929
|
function metadataDir(targetRoot) {
|
|
749
930
|
return join2(targetRoot, ".agentwheel");
|
|
750
931
|
}
|
|
751
|
-
function
|
|
752
|
-
|
|
932
|
+
function stateKeyFor(adapter, scope = {}) {
|
|
933
|
+
if (scope.stateKey) return sanitizeStateKey(scope.stateKey);
|
|
934
|
+
const installationType = scope.installationType ?? defaultInstallationType;
|
|
935
|
+
const fingerprint = scope.targetFingerprint ? `.${scope.targetFingerprint}` : "";
|
|
936
|
+
return sanitizeStateKey(`${adapter}.${installationType}${fingerprint}`);
|
|
937
|
+
}
|
|
938
|
+
function installManifestPath(targetRoot, adapter, scope = {}) {
|
|
939
|
+
return join2(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.install-manifest.json`);
|
|
940
|
+
}
|
|
941
|
+
function sourceLockPath(targetRoot, adapter, scope = {}) {
|
|
942
|
+
return join2(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.source-lock.json`);
|
|
753
943
|
}
|
|
754
|
-
function
|
|
755
|
-
return
|
|
944
|
+
function sanitizeStateKey(value) {
|
|
945
|
+
return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
|
|
756
946
|
}
|
|
757
947
|
|
|
758
948
|
// src/install/manifest.ts
|
|
759
|
-
async function readInstallManifest(targetRoot, adapter, transport = localTransport) {
|
|
760
|
-
const path = installManifestPath(targetRoot, adapter);
|
|
949
|
+
async function readInstallManifest(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
950
|
+
const path = installManifestPath(targetRoot, adapter, scope);
|
|
761
951
|
if (!await transport.pathExists(path)) return void 0;
|
|
762
952
|
const raw = JSON.parse(await transport.readFile(path));
|
|
763
953
|
const parsed = installManifestSchema.parse(raw);
|
|
@@ -768,14 +958,17 @@ async function readInstallManifest(targetRoot, adapter, transport = localTranspo
|
|
|
768
958
|
}
|
|
769
959
|
async function writeInstallManifest(manifest, transport = localTransport) {
|
|
770
960
|
const next = withManifestRevision(manifest);
|
|
771
|
-
await transport.writeJsonAtomic(installManifestPath(next.targetRoot, next.adapter
|
|
961
|
+
await transport.writeJsonAtomic(installManifestPath(next.targetRoot, next.adapter, {
|
|
962
|
+
installationType: next.installationType,
|
|
963
|
+
stateKey: next.stateKey
|
|
964
|
+
}), stripReadOnlyManifestFields(next));
|
|
772
965
|
}
|
|
773
|
-
async function writeSourceLock(targetRoot, adapter, lock, transport = localTransport) {
|
|
774
|
-
await transport.writeJsonAtomic(sourceLockPath(targetRoot, adapter), lock);
|
|
966
|
+
async function writeSourceLock(targetRoot, adapter, lock, transport = localTransport, scope = {}) {
|
|
967
|
+
await transport.writeJsonAtomic(sourceLockPath(targetRoot, adapter, scope), lock);
|
|
775
968
|
}
|
|
776
|
-
async function removeStateFiles(targetRoot, adapter, transport = localTransport) {
|
|
777
|
-
await transport.rm(installManifestPath(targetRoot, adapter));
|
|
778
|
-
await transport.rm(sourceLockPath(targetRoot, adapter));
|
|
969
|
+
async function removeStateFiles(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
970
|
+
await transport.rm(installManifestPath(targetRoot, adapter, scope));
|
|
971
|
+
await transport.rm(sourceLockPath(targetRoot, adapter, scope));
|
|
779
972
|
}
|
|
780
973
|
function normalizeTargetRoot(path) {
|
|
781
974
|
return resolve3(path);
|
|
@@ -784,7 +977,11 @@ function withManifestRevision(manifest) {
|
|
|
784
977
|
if (manifest.version !== 2) {
|
|
785
978
|
throw new Error("Install manifest writes must use version 2");
|
|
786
979
|
}
|
|
787
|
-
const
|
|
980
|
+
const raw = stripReadOnlyManifestFields(manifest);
|
|
981
|
+
const normalized = installManifestV2Schema.parse({
|
|
982
|
+
installationType: defaultInstallationType,
|
|
983
|
+
...raw
|
|
984
|
+
});
|
|
788
985
|
const withoutRevision = stripRuntimeManifestFields(normalized);
|
|
789
986
|
return {
|
|
790
987
|
...normalized,
|
|
@@ -843,21 +1040,23 @@ function assertOperationContained(operation, targetRoot) {
|
|
|
843
1040
|
// src/install/transaction.ts
|
|
844
1041
|
import { cp, mkdir as mkdir4, rm as rm2, stat as stat2 } from "fs/promises";
|
|
845
1042
|
import { dirname as dirname5, join as join3 } from "path";
|
|
846
|
-
function applyLockPath(targetRoot, adapter) {
|
|
847
|
-
return join3(metadataDir(targetRoot), `${adapter}.apply-lock`);
|
|
1043
|
+
function applyLockPath(targetRoot, adapter, scope = {}) {
|
|
1044
|
+
return join3(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-lock`);
|
|
848
1045
|
}
|
|
849
|
-
function applyJournalPath(targetRoot, adapter) {
|
|
850
|
-
return join3(metadataDir(targetRoot), `${adapter}.apply-journal.json`);
|
|
1046
|
+
function applyJournalPath(targetRoot, adapter, scope = {}) {
|
|
1047
|
+
return join3(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-journal.json`);
|
|
851
1048
|
}
|
|
852
|
-
function applyBackupDir(targetRoot, adapter) {
|
|
853
|
-
return join3(metadataDir(targetRoot), `${adapter}.apply-backups`);
|
|
1049
|
+
function applyBackupDir(targetRoot, adapter, scope = {}) {
|
|
1050
|
+
return join3(metadataDir(targetRoot), `${stateKeyFor(adapter, scope)}.apply-backups`);
|
|
854
1051
|
}
|
|
855
|
-
async function acquireApplyLock(targetRoot, adapter, transport = localTransport, options = {}) {
|
|
856
|
-
const lockPath = applyLockPath(targetRoot, adapter);
|
|
1052
|
+
async function acquireApplyLock(targetRoot, adapter, transport = localTransport, options = {}, scope = {}) {
|
|
1053
|
+
const lockPath = applyLockPath(targetRoot, adapter, scope);
|
|
857
1054
|
const ownerPath = join3(lockPath, "owner.json");
|
|
858
1055
|
const metadata = {
|
|
859
1056
|
pid: process.pid,
|
|
860
1057
|
adapter,
|
|
1058
|
+
installationType: scope.installationType,
|
|
1059
|
+
stateKey: scope.stateKey,
|
|
861
1060
|
targetRoot,
|
|
862
1061
|
transport: transport.description,
|
|
863
1062
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -876,21 +1075,24 @@ async function acquireApplyLock(targetRoot, adapter, transport = localTransport,
|
|
|
876
1075
|
};
|
|
877
1076
|
}
|
|
878
1077
|
async function writeApplyJournal(journal, transport = localTransport) {
|
|
879
|
-
await transport.writeJsonAtomic(applyJournalPath(journal.targetRoot, journal.adapter
|
|
1078
|
+
await transport.writeJsonAtomic(applyJournalPath(journal.targetRoot, journal.adapter, {
|
|
1079
|
+
installationType: journal.installationType,
|
|
1080
|
+
stateKey: journal.stateKey
|
|
1081
|
+
}), {
|
|
880
1082
|
...journal,
|
|
881
1083
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
882
1084
|
});
|
|
883
1085
|
}
|
|
884
|
-
async function readApplyJournal(targetRoot, adapter, transport = localTransport) {
|
|
885
|
-
const path = applyJournalPath(targetRoot, adapter);
|
|
1086
|
+
async function readApplyJournal(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
1087
|
+
const path = applyJournalPath(targetRoot, adapter, scope);
|
|
886
1088
|
if (!await transport.pathExists(path)) return void 0;
|
|
887
1089
|
return JSON.parse(await transport.readFile(path));
|
|
888
1090
|
}
|
|
889
|
-
async function removeApplyJournal(targetRoot, adapter, transport = localTransport) {
|
|
890
|
-
await transport.rm(applyJournalPath(targetRoot, adapter));
|
|
891
|
-
await transport.rm(applyBackupDir(targetRoot, adapter));
|
|
1091
|
+
async function removeApplyJournal(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
1092
|
+
await transport.rm(applyJournalPath(targetRoot, adapter, scope));
|
|
1093
|
+
await transport.rm(applyBackupDir(targetRoot, adapter, scope));
|
|
892
1094
|
}
|
|
893
|
-
async function recordBackup(operation, index, targetRoot, adapter, transport = localTransport) {
|
|
1095
|
+
async function recordBackup(operation, index, targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
894
1096
|
const hadExisting = await transport.pathExists(operation.destPath);
|
|
895
1097
|
if (!hadExisting || transport.kind !== "local" || operation.action !== "update" && operation.action !== "remove" && operation.action !== "create") {
|
|
896
1098
|
return {
|
|
@@ -900,7 +1102,7 @@ async function recordBackup(operation, index, targetRoot, adapter, transport = l
|
|
|
900
1102
|
hadExisting
|
|
901
1103
|
};
|
|
902
1104
|
}
|
|
903
|
-
const backupPath = join3(applyBackupDir(targetRoot, adapter), String(index));
|
|
1105
|
+
const backupPath = join3(applyBackupDir(targetRoot, adapter, scope), String(index));
|
|
904
1106
|
await rm2(backupPath, { recursive: true, force: true });
|
|
905
1107
|
await mkdir4(dirname5(backupPath), { recursive: true });
|
|
906
1108
|
await cp(operation.destPath, backupPath, { recursive: operation.kind === "dir", dereference: true });
|
|
@@ -1053,14 +1255,14 @@ function normalizeOwners(owners) {
|
|
|
1053
1255
|
}
|
|
1054
1256
|
|
|
1055
1257
|
// src/install/apply.ts
|
|
1056
|
-
var
|
|
1258
|
+
var execFileAsync3 = promisify3(execFile3);
|
|
1057
1259
|
async function applyCombinedInstallPlan(plan, options = {}) {
|
|
1058
1260
|
return applyPlanTransactionally(plan, options);
|
|
1059
1261
|
}
|
|
1060
|
-
async function recoverPendingApply(targetRoot, adapter, transport = localTransport) {
|
|
1061
|
-
const lock = await acquireApplyLock(targetRoot, adapter, transport);
|
|
1262
|
+
async function recoverPendingApply(targetRoot, adapter, transport = localTransport, scope = {}) {
|
|
1263
|
+
const lock = await acquireApplyLock(targetRoot, adapter, transport, {}, scope);
|
|
1062
1264
|
try {
|
|
1063
|
-
const journal = await readApplyJournal(targetRoot, adapter, transport);
|
|
1265
|
+
const journal = await readApplyJournal(targetRoot, adapter, transport, scope);
|
|
1064
1266
|
if (!journal) return void 0;
|
|
1065
1267
|
if (journal.operations.some((operation) => operation.action === "plugin" || operation.action === "program")) {
|
|
1066
1268
|
throw new Error("Cannot automatically recover a journal containing semantic plugin or programmatic operations");
|
|
@@ -1080,10 +1282,10 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
|
|
|
1080
1282
|
}
|
|
1081
1283
|
if (operationNeedsSource(operation) && operation.sourcePath && !await localPathExists(operation.sourcePath)) {
|
|
1082
1284
|
await rollbackStartedOperations(journal, transport);
|
|
1083
|
-
await removeApplyJournal(targetRoot, adapter, transport);
|
|
1285
|
+
await removeApplyJournal(targetRoot, adapter, transport, scope);
|
|
1084
1286
|
return void 0;
|
|
1085
1287
|
}
|
|
1086
|
-
const backup = started ?? await recordBackup(operation, index, targetRoot, adapter, transport);
|
|
1288
|
+
const backup = started ?? await recordBackup(operation, index, targetRoot, adapter, transport, scope);
|
|
1087
1289
|
if (!started && isJournaledMutation(operation)) {
|
|
1088
1290
|
journal.completed.push(backup);
|
|
1089
1291
|
startedByIndex.set(index, backup);
|
|
@@ -1103,11 +1305,12 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
|
|
|
1103
1305
|
}
|
|
1104
1306
|
async function applyPlanTransactionally(plan, options = {}) {
|
|
1105
1307
|
const transport = options.transport ?? localTransport;
|
|
1308
|
+
const scope = { installationType: plan.installationType, stateKey: plan.stateKey };
|
|
1106
1309
|
if (plan.hasBlockingChanges) {
|
|
1107
1310
|
const blockers = plan.operations.filter((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
1108
1311
|
throw new Error(`Refusing to apply with blocking changes: ${blockers.map((item) => item.relativeDestPath).join(", ")}`);
|
|
1109
1312
|
}
|
|
1110
|
-
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, options.lock);
|
|
1313
|
+
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, options.lock, scope);
|
|
1111
1314
|
try {
|
|
1112
1315
|
await assertBaseRevision(plan, transport);
|
|
1113
1316
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1115,6 +1318,8 @@ async function applyPlanTransactionally(plan, options = {}) {
|
|
|
1115
1318
|
const journal = {
|
|
1116
1319
|
version: 1,
|
|
1117
1320
|
adapter: plan.adapter,
|
|
1321
|
+
installationType: plan.installationType,
|
|
1322
|
+
stateKey: plan.stateKey,
|
|
1118
1323
|
targetRoot: plan.targetRoot,
|
|
1119
1324
|
baseRevision: plan.baseRevision,
|
|
1120
1325
|
graphLockDigest,
|
|
@@ -1125,6 +1330,8 @@ async function applyPlanTransactionally(plan, options = {}) {
|
|
|
1125
1330
|
manifest: {
|
|
1126
1331
|
version: 2,
|
|
1127
1332
|
adapter: plan.adapter,
|
|
1333
|
+
installationType: plan.installationType,
|
|
1334
|
+
stateKey: plan.stateKey,
|
|
1128
1335
|
targetRoot: plan.targetRoot,
|
|
1129
1336
|
generatedAt: now,
|
|
1130
1337
|
revision: "pending-apply-0000",
|
|
@@ -1140,7 +1347,7 @@ async function applyPlanTransactionally(plan, options = {}) {
|
|
|
1140
1347
|
const entries = [];
|
|
1141
1348
|
for (const [index, operation] of plan.operations.entries()) {
|
|
1142
1349
|
assertOperationContained(operation, plan.targetRoot);
|
|
1143
|
-
const backup = isJournaledMutation(operation) ? await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport) : void 0;
|
|
1350
|
+
const backup = isJournaledMutation(operation) ? await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport, scope) : void 0;
|
|
1144
1351
|
if (backup) {
|
|
1145
1352
|
journal.completed.push(backup);
|
|
1146
1353
|
await writeApplyJournal(journal, transport);
|
|
@@ -1166,6 +1373,7 @@ async function applyPlanTransactionally(plan, options = {}) {
|
|
|
1166
1373
|
async function uninstall(plan, options = {}) {
|
|
1167
1374
|
const resolvedOptions = typeof options === "boolean" ? { dryRun: options } : options;
|
|
1168
1375
|
const transport = resolvedOptions.transport ?? localTransport;
|
|
1376
|
+
const scope = { installationType: plan.installationType, stateKey: plan.stateKey };
|
|
1169
1377
|
if (resolvedOptions.keepFiles && resolvedOptions.force) {
|
|
1170
1378
|
throw new Error("--keep-files cannot be combined with --force.");
|
|
1171
1379
|
}
|
|
@@ -1185,6 +1393,8 @@ async function uninstall(plan, options = {}) {
|
|
|
1185
1393
|
const finalManifest = withManifestRevision({
|
|
1186
1394
|
version: 2,
|
|
1187
1395
|
adapter: plan.adapter,
|
|
1396
|
+
installationType: plan.installationType,
|
|
1397
|
+
stateKey: plan.stateKey,
|
|
1188
1398
|
targetRoot: plan.targetRoot,
|
|
1189
1399
|
generatedAt: now,
|
|
1190
1400
|
revision: "pending-uninstall-0",
|
|
@@ -1202,13 +1412,15 @@ async function uninstall(plan, options = {}) {
|
|
|
1202
1412
|
});
|
|
1203
1413
|
}).sort((a, b) => a.path.localeCompare(b.path))
|
|
1204
1414
|
});
|
|
1205
|
-
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, resolvedOptions.lock);
|
|
1415
|
+
const lock = await acquireApplyLock(plan.targetRoot, plan.adapter, transport, resolvedOptions.lock, scope);
|
|
1206
1416
|
try {
|
|
1207
1417
|
await assertBaseRevision(plan, transport);
|
|
1208
1418
|
const journal = {
|
|
1209
1419
|
version: 1,
|
|
1210
1420
|
mode: "uninstall",
|
|
1211
1421
|
adapter: plan.adapter,
|
|
1422
|
+
installationType: plan.installationType,
|
|
1423
|
+
stateKey: plan.stateKey,
|
|
1212
1424
|
targetRoot: plan.targetRoot,
|
|
1213
1425
|
baseRevision: plan.baseRevision,
|
|
1214
1426
|
graphLockDigest: plan.graphLockDigest,
|
|
@@ -1225,7 +1437,7 @@ async function uninstall(plan, options = {}) {
|
|
|
1225
1437
|
};
|
|
1226
1438
|
await writeApplyJournal(journal, transport);
|
|
1227
1439
|
for (const [index, operation] of (resolvedOptions.keepFiles ? [] : removable).entries()) {
|
|
1228
|
-
const backup = await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport);
|
|
1440
|
+
const backup = await recordBackup(operation, index, plan.targetRoot, plan.adapter, transport, scope);
|
|
1229
1441
|
journal.completed.push(backup);
|
|
1230
1442
|
await writeApplyJournal(journal, transport);
|
|
1231
1443
|
await applyOperation(operation, { transport, now, graphLockDigest: plan.graphLockDigest });
|
|
@@ -1251,15 +1463,24 @@ async function commitJournalState(journal, transport, entries, now) {
|
|
|
1251
1463
|
entries: entries ? entries.sort((a, b) => a.path.localeCompare(b.path)) : journal.manifest.entries
|
|
1252
1464
|
});
|
|
1253
1465
|
if (journal.mode === "uninstall" && manifest.entries.length === 0) {
|
|
1254
|
-
await removeStateFiles(journal.targetRoot, journal.adapter, transport
|
|
1466
|
+
await removeStateFiles(journal.targetRoot, journal.adapter, transport, {
|
|
1467
|
+
installationType: journal.installationType,
|
|
1468
|
+
stateKey: journal.stateKey
|
|
1469
|
+
});
|
|
1255
1470
|
} else {
|
|
1256
|
-
if (journal.sourceLock) await writeSourceLock(journal.targetRoot, journal.adapter, journal.sourceLock, transport
|
|
1471
|
+
if (journal.sourceLock) await writeSourceLock(journal.targetRoot, journal.adapter, journal.sourceLock, transport, {
|
|
1472
|
+
installationType: journal.installationType,
|
|
1473
|
+
stateKey: journal.stateKey
|
|
1474
|
+
});
|
|
1257
1475
|
await writeInstallManifest(manifest, transport);
|
|
1258
1476
|
}
|
|
1259
1477
|
if (journal.graphLockPath && journal.graphLock) await writeGraphLock(journal.graphLockPath, journal.graphLock);
|
|
1260
1478
|
if (journal.graphLockRemovePath) await rm3(journal.graphLockRemovePath, { force: true });
|
|
1261
1479
|
if (journal.workspaceConfigPath && journal.workspaceConfig) await writeJsonAtomic(journal.workspaceConfigPath, journal.workspaceConfig);
|
|
1262
|
-
await removeApplyJournal(journal.targetRoot, journal.adapter, transport
|
|
1480
|
+
await removeApplyJournal(journal.targetRoot, journal.adapter, transport, {
|
|
1481
|
+
installationType: journal.installationType,
|
|
1482
|
+
stateKey: journal.stateKey
|
|
1483
|
+
});
|
|
1263
1484
|
return manifest;
|
|
1264
1485
|
}
|
|
1265
1486
|
async function applyOperation(operation, context) {
|
|
@@ -1269,18 +1490,10 @@ async function applyOperation(operation, context) {
|
|
|
1269
1490
|
throw new Error(`Invalid plugin operation missing hash: ${operation.relativeDestPath}`);
|
|
1270
1491
|
}
|
|
1271
1492
|
if (context.executePlugins) {
|
|
1272
|
-
if (transport.kind !== "local") {
|
|
1273
|
-
throw new Error(`Cannot execute semantic plugin install over ${transport.description}. Run plugin installation on the remote host.`);
|
|
1274
|
-
}
|
|
1275
1493
|
if (!operation.semanticCommand || operation.semanticCommand.length === 0) {
|
|
1276
1494
|
throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1277
1495
|
}
|
|
1278
|
-
|
|
1279
|
-
const args = operation.semanticCommand.slice(1);
|
|
1280
|
-
if (!command) {
|
|
1281
|
-
throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1282
|
-
}
|
|
1283
|
-
await execFileAsync2(command, args);
|
|
1496
|
+
await executePluginInstall(operation, transport);
|
|
1284
1497
|
}
|
|
1285
1498
|
return manifestEntryForOperation(operation, {
|
|
1286
1499
|
now,
|
|
@@ -1360,6 +1573,32 @@ async function applyOperation(operation, context) {
|
|
|
1360
1573
|
}
|
|
1361
1574
|
return void 0;
|
|
1362
1575
|
}
|
|
1576
|
+
async function executePluginInstall(operation, transport) {
|
|
1577
|
+
const command = operation.semanticCommand?.[0];
|
|
1578
|
+
const args = operation.semanticCommand?.slice(1) ?? [];
|
|
1579
|
+
if (!command) {
|
|
1580
|
+
throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1581
|
+
}
|
|
1582
|
+
if (!operation.sourcePath) {
|
|
1583
|
+
throw new Error(`Invalid plugin operation missing source path: ${operation.relativeDestPath}`);
|
|
1584
|
+
}
|
|
1585
|
+
if (transport.kind === "local") {
|
|
1586
|
+
await execFileAsync3(command, args);
|
|
1587
|
+
return;
|
|
1588
|
+
}
|
|
1589
|
+
if (!transport.execFile) {
|
|
1590
|
+
throw new Error(`Cannot execute semantic plugin install over ${transport.description}: transport does not support remote commands.`);
|
|
1591
|
+
}
|
|
1592
|
+
const stagingRoot = join4(operation.destPath, ".agentwheel", "plugin-staging", `${process.pid}-${Date.now()}`);
|
|
1593
|
+
const remoteSourcePath = join4(stagingRoot, basename2(operation.sourcePath));
|
|
1594
|
+
try {
|
|
1595
|
+
await transport.atomicCopy(operation.sourcePath, remoteSourcePath, operation.kind);
|
|
1596
|
+
const remoteArgs = args.map((arg) => arg === operation.sourcePath ? remoteSourcePath : arg);
|
|
1597
|
+
await transport.execFile(command, remoteArgs, { cwd: operation.destPath });
|
|
1598
|
+
} finally {
|
|
1599
|
+
await transport.rm(stagingRoot);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1363
1602
|
async function entryForCompletedOperation(operation, transport, now, graphLockDigest) {
|
|
1364
1603
|
if (operation.action === "remove") return void 0;
|
|
1365
1604
|
if (operation.action === "create" || operation.action === "update") {
|
|
@@ -1407,7 +1646,10 @@ function manifestEntryForOperation(operation, values) {
|
|
|
1407
1646
|
};
|
|
1408
1647
|
}
|
|
1409
1648
|
async function assertBaseRevision(plan, transport) {
|
|
1410
|
-
const current = await readInstallManifest(plan.targetRoot, plan.adapter, transport
|
|
1649
|
+
const current = await readInstallManifest(plan.targetRoot, plan.adapter, transport, {
|
|
1650
|
+
installationType: plan.installationType,
|
|
1651
|
+
stateKey: plan.stateKey
|
|
1652
|
+
});
|
|
1411
1653
|
const currentRevision = current?.revision ?? null;
|
|
1412
1654
|
if (currentRevision !== plan.baseRevision) {
|
|
1413
1655
|
throw new Error(`Install manifest changed since planning for ${plan.adapter}; replan needed`);
|
|
@@ -1456,15 +1698,476 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
|
|
|
1456
1698
|
}
|
|
1457
1699
|
|
|
1458
1700
|
// src/install/plan.ts
|
|
1459
|
-
import { join as
|
|
1701
|
+
import { join as join8, relative as relative2 } from "path";
|
|
1702
|
+
|
|
1703
|
+
// src/staging/codex-subagents.ts
|
|
1704
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile7 } from "fs/promises";
|
|
1705
|
+
import { basename as basename3, dirname as dirname7, join as join5 } from "path";
|
|
1706
|
+
var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
|
|
1707
|
+
async function renderCodexSubagents(artifacts, stageRoot, adapter) {
|
|
1708
|
+
if (adapter?.name !== "codex") return artifacts;
|
|
1709
|
+
const names = /* @__PURE__ */ new Set();
|
|
1710
|
+
const rendered = [];
|
|
1711
|
+
for (const artifact of artifacts) {
|
|
1712
|
+
if (artifact.type !== "subagents") {
|
|
1713
|
+
rendered.push(artifact);
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
const next = await renderCodexSubagent(artifact, stageRoot);
|
|
1717
|
+
if (names.has(next.name)) {
|
|
1718
|
+
throw new Error(`Codex subagents produce duplicate custom agent name '${next.name}'.`);
|
|
1719
|
+
}
|
|
1720
|
+
names.add(next.name);
|
|
1721
|
+
rendered.push(next);
|
|
1722
|
+
}
|
|
1723
|
+
return rendered;
|
|
1724
|
+
}
|
|
1725
|
+
async function renderCodexSubagent(artifact, stageRoot) {
|
|
1726
|
+
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
1727
|
+
const agentName = codexAgentName(artifact);
|
|
1728
|
+
const renderedPath = join5(stageRoot, ".agentwheel-rendered", "codex-subagents", `${agentName}.toml`);
|
|
1729
|
+
const lowerSourcePath = sourcePath.toLowerCase();
|
|
1730
|
+
if (artifact.kind === "file" && (artifact.name.toLowerCase().endsWith(".toml") || lowerSourcePath.endsWith(".toml"))) {
|
|
1731
|
+
const content = await readFile7(sourcePath, "utf8");
|
|
1732
|
+
validateCodexAgentToml(content, sourcePath);
|
|
1733
|
+
return {
|
|
1734
|
+
...artifact,
|
|
1735
|
+
name: agentName,
|
|
1736
|
+
relativePath: join5("subagents", `${agentName}.toml`),
|
|
1737
|
+
kind: "file",
|
|
1738
|
+
hash: await hashPath(sourcePath)
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
const markdownPath = artifact.kind === "dir" ? join5(sourcePath, "AGENTS.md") : sourcePath;
|
|
1742
|
+
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
1743
|
+
throw new Error(`Codex subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
1744
|
+
}
|
|
1745
|
+
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !lowerSourcePath.endsWith(".md")) {
|
|
1746
|
+
throw new Error(`Codex subagent ${artifact.relativePath} must be a .toml file, .md file, or directory containing AGENTS.md.`);
|
|
1747
|
+
}
|
|
1748
|
+
const markdown = await readFile7(markdownPath, "utf8");
|
|
1749
|
+
const toml = markdownToCodexAgentToml(agentName, markdown);
|
|
1750
|
+
await mkdir6(dirname7(renderedPath), { recursive: true });
|
|
1751
|
+
await writeFile7(renderedPath, toml, "utf8");
|
|
1752
|
+
return {
|
|
1753
|
+
...artifact,
|
|
1754
|
+
name: agentName,
|
|
1755
|
+
sourcePath: renderedPath,
|
|
1756
|
+
stagedPath: renderedPath,
|
|
1757
|
+
relativePath: join5("subagents", `${agentName}.toml`),
|
|
1758
|
+
kind: "file",
|
|
1759
|
+
hash: await hashPath(renderedPath)
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
function codexAgentName(artifact) {
|
|
1763
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename3(artifact.name);
|
|
1764
|
+
return raw.replace(/\.toml$/i, "").replace(/\.md$/i, "");
|
|
1765
|
+
}
|
|
1766
|
+
function validateCodexAgentToml(content, path) {
|
|
1767
|
+
for (const field of requiredCodexAgentFields) {
|
|
1768
|
+
const pattern = new RegExp(`(^|\\n)\\s*${escapeRegExp(field)}\\s*=`, "m");
|
|
1769
|
+
if (!pattern.test(content)) {
|
|
1770
|
+
throw new Error(`Codex custom agent TOML ${path} is missing required field '${field}'.`);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
function markdownToCodexAgentToml(agentName, markdown) {
|
|
1775
|
+
const parsed = splitFrontmatter(markdown);
|
|
1776
|
+
const description = parsed.description ?? firstMeaningfulMarkdownLine(parsed.body) ?? `Custom Codex subagent ${agentName}.`;
|
|
1777
|
+
const developerInstructions = parsed.body.trim().length > 0 ? parsed.body.trimEnd() : description;
|
|
1778
|
+
return [
|
|
1779
|
+
`name = ${tomlString(agentName)}`,
|
|
1780
|
+
`description = ${tomlString(description)}`,
|
|
1781
|
+
`developer_instructions = ${tomlMultilineString(developerInstructions)}`,
|
|
1782
|
+
""
|
|
1783
|
+
].join("\n");
|
|
1784
|
+
}
|
|
1785
|
+
function splitFrontmatter(markdown) {
|
|
1786
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
|
|
1787
|
+
if (!match) return { body: markdown };
|
|
1788
|
+
const frontmatter = match[1] ?? "";
|
|
1789
|
+
const body = markdown.slice(match[0].length);
|
|
1790
|
+
const description = frontmatter.split(/\r?\n/).map((line) => /^description:\s*(?:"([^"]*)"|'([^']*)'|(.+))\s*$/.exec(line.trim())).find((item) => item !== null);
|
|
1791
|
+
return {
|
|
1792
|
+
body,
|
|
1793
|
+
description: description ? (description[1] ?? description[2] ?? description[3] ?? "").trim() : void 0
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
function firstMeaningfulMarkdownLine(markdown) {
|
|
1797
|
+
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
1798
|
+
const line = rawLine.trim();
|
|
1799
|
+
if (!line) continue;
|
|
1800
|
+
const heading = /^#{1,6}\s+(.+)$/.exec(line);
|
|
1801
|
+
return (heading?.[1] ?? line).trim();
|
|
1802
|
+
}
|
|
1803
|
+
return void 0;
|
|
1804
|
+
}
|
|
1805
|
+
function tomlString(value) {
|
|
1806
|
+
return JSON.stringify(value);
|
|
1807
|
+
}
|
|
1808
|
+
function tomlMultilineString(value) {
|
|
1809
|
+
const escaped = value.replaceAll("\\", "\\\\").replaceAll('"""', '\\"\\"\\"').replace(/\r\n?/g, "\n");
|
|
1810
|
+
return `"""
|
|
1811
|
+
${escaped.endsWith("\n") ? escaped : `${escaped}
|
|
1812
|
+
`}"""`;
|
|
1813
|
+
}
|
|
1814
|
+
function escapeRegExp(value) {
|
|
1815
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
// src/staging/copilot-artifacts.ts
|
|
1819
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile8 } from "fs/promises";
|
|
1820
|
+
import { basename as basename4, dirname as dirname8, join as join6 } from "path";
|
|
1821
|
+
async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
|
|
1822
|
+
if (adapter?.name !== "copilot") return artifacts;
|
|
1823
|
+
const names = /* @__PURE__ */ new Set();
|
|
1824
|
+
const rendered = [];
|
|
1825
|
+
for (const artifact of artifacts) {
|
|
1826
|
+
if (artifact.type !== "subagents") {
|
|
1827
|
+
rendered.push(artifact);
|
|
1828
|
+
continue;
|
|
1829
|
+
}
|
|
1830
|
+
const next = await renderCopilotSubagent(artifact, stageRoot);
|
|
1831
|
+
if (names.has(next.name)) {
|
|
1832
|
+
throw new Error(`Copilot subagents produce duplicate custom agent name '${next.name}'.`);
|
|
1833
|
+
}
|
|
1834
|
+
names.add(next.name);
|
|
1835
|
+
rendered.push(next);
|
|
1836
|
+
}
|
|
1837
|
+
return rendered;
|
|
1838
|
+
}
|
|
1839
|
+
async function renderCopilotSubagent(artifact, stageRoot) {
|
|
1840
|
+
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
1841
|
+
const agentName = copilotAgentName(artifact);
|
|
1842
|
+
const renderedPath = join6(stageRoot, ".agentwheel-rendered", "copilot-subagents", `${agentName}.agent.md`);
|
|
1843
|
+
const markdownPath = artifact.kind === "dir" ? join6(sourcePath, "AGENTS.md") : sourcePath;
|
|
1844
|
+
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
1845
|
+
throw new Error(`Copilot subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
1846
|
+
}
|
|
1847
|
+
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md")) {
|
|
1848
|
+
throw new Error(`Copilot subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
|
|
1849
|
+
}
|
|
1850
|
+
const markdown = await readFile8(markdownPath, "utf8");
|
|
1851
|
+
await mkdir7(dirname8(renderedPath), { recursive: true });
|
|
1852
|
+
await writeFile8(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
|
|
1853
|
+
return {
|
|
1854
|
+
...artifact,
|
|
1855
|
+
name: `${agentName}.agent.md`,
|
|
1856
|
+
sourcePath: renderedPath,
|
|
1857
|
+
stagedPath: renderedPath,
|
|
1858
|
+
relativePath: join6("subagents", `${agentName}.agent.md`),
|
|
1859
|
+
kind: "file",
|
|
1860
|
+
hash: await hashPath(renderedPath)
|
|
1861
|
+
};
|
|
1862
|
+
}
|
|
1863
|
+
function copilotAgentName(artifact) {
|
|
1864
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename4(artifact.name);
|
|
1865
|
+
return raw.replace(/\.agent\.md$/i, "").replace(/\.md$/i, "");
|
|
1866
|
+
}
|
|
1867
|
+
function ensureCopilotAgentDescription(agentName, markdown) {
|
|
1868
|
+
const parsed = splitFrontmatter2(markdown);
|
|
1869
|
+
if (parsed.frontmatter !== void 0 && /^description\s*:/im.test(parsed.frontmatter)) {
|
|
1870
|
+
return markdown;
|
|
1871
|
+
}
|
|
1872
|
+
const description = firstMeaningfulMarkdownLine2(parsed.body) ?? `Custom Copilot agent ${agentName}.`;
|
|
1873
|
+
const frontmatter = parsed.frontmatter === void 0 ? `description: ${yamlString(description)}` : `${parsed.frontmatter.trimEnd()}
|
|
1874
|
+
description: ${yamlString(description)}`;
|
|
1875
|
+
return [
|
|
1876
|
+
"---",
|
|
1877
|
+
frontmatter,
|
|
1878
|
+
"---",
|
|
1879
|
+
"",
|
|
1880
|
+
parsed.body.trimStart()
|
|
1881
|
+
].join("\n");
|
|
1882
|
+
}
|
|
1883
|
+
function splitFrontmatter2(markdown) {
|
|
1884
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
|
|
1885
|
+
if (!match) return { body: markdown };
|
|
1886
|
+
return {
|
|
1887
|
+
body: markdown.slice(match[0].length),
|
|
1888
|
+
frontmatter: match[1] ?? ""
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
function firstMeaningfulMarkdownLine2(markdown) {
|
|
1892
|
+
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
1893
|
+
const line = rawLine.trim();
|
|
1894
|
+
if (!line) continue;
|
|
1895
|
+
const heading = /^#{1,6}\s+(.+)$/.exec(line);
|
|
1896
|
+
return (heading?.[1] ?? line).trim();
|
|
1897
|
+
}
|
|
1898
|
+
return void 0;
|
|
1899
|
+
}
|
|
1900
|
+
function yamlString(value) {
|
|
1901
|
+
return JSON.stringify(value);
|
|
1902
|
+
}
|
|
1460
1903
|
|
|
1461
1904
|
// src/targets/plugins/openclaw.ts
|
|
1462
1905
|
function openClawPluginInstallCommand(request) {
|
|
1463
|
-
return ["openclaw", "plugins", "install", "--
|
|
1906
|
+
return ["openclaw", "plugins", "install", "--force", request.path];
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// src/validation/artifacts.ts
|
|
1910
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
1911
|
+
import { basename as basename5, join as join7 } from "path";
|
|
1912
|
+
async function validateArtifactsForInstall(artifacts, adapter, installationType) {
|
|
1913
|
+
const issues = [];
|
|
1914
|
+
for (const artifact of artifacts) {
|
|
1915
|
+
if (artifact.type === "fragments") continue;
|
|
1916
|
+
const target = targetMappingForArtifact(adapter, artifact.type, installationType);
|
|
1917
|
+
if (!target?.enabled) {
|
|
1918
|
+
issues.push({ artifact, message: `adapter ${adapter.name} does not support this artifact for installation type '${installationType}'` });
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
issues.push(...await validateArtifact(artifact, target));
|
|
1922
|
+
}
|
|
1923
|
+
if (issues.length === 0) return;
|
|
1924
|
+
throw new Error([
|
|
1925
|
+
`Package artifacts are not installable for ${adapter.name}/${installationType}:`,
|
|
1926
|
+
...issues.map((issue) => `- ${artifactLabel(issue.artifact)}: ${issue.message}`)
|
|
1927
|
+
].join("\n"));
|
|
1928
|
+
}
|
|
1929
|
+
async function validateArtifact(artifact, target) {
|
|
1930
|
+
const issues = [];
|
|
1931
|
+
const format = artifact.format ?? await inferArtifactFormat(artifact, target) ?? semanticDefaultFormat(target);
|
|
1932
|
+
if (target.formats?.length) {
|
|
1933
|
+
if (!format) {
|
|
1934
|
+
issues.push({
|
|
1935
|
+
artifact,
|
|
1936
|
+
message: `format is unknown; expected one of: ${target.formats.join(", ")}`
|
|
1937
|
+
});
|
|
1938
|
+
} else if (!target.formats.includes(format)) {
|
|
1939
|
+
issues.push({
|
|
1940
|
+
artifact,
|
|
1941
|
+
message: `format '${format}' is not compatible; expected one of: ${target.formats.join(", ")}`
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
issues.push(...await validateKnownFormat(artifact, format, target));
|
|
1946
|
+
issues.push(...await validateGenericStructure(artifact, target));
|
|
1947
|
+
return issues;
|
|
1948
|
+
}
|
|
1949
|
+
async function inferArtifactFormat(artifact, target) {
|
|
1950
|
+
if (artifact.type === "rules") {
|
|
1951
|
+
if (hasExtension(artifact, ".rules")) return "codex-command-policy";
|
|
1952
|
+
if (hasExtension(artifact, ".md") || hasExtension(artifact, ".markdown")) return "markdown-rule";
|
|
1953
|
+
return void 0;
|
|
1954
|
+
}
|
|
1955
|
+
if (artifact.type === "plugins" && target.semantic === "openclaw-plugin") {
|
|
1956
|
+
if (artifact.kind === "dir" && (await openClawPluginManifestPaths(artifact)).length > 0) return "openclaw-plugin";
|
|
1957
|
+
}
|
|
1958
|
+
return void 0;
|
|
1959
|
+
}
|
|
1960
|
+
function semanticDefaultFormat(target) {
|
|
1961
|
+
if (target.semantic === "openclaw-plugin") return "openclaw-plugin";
|
|
1962
|
+
return void 0;
|
|
1963
|
+
}
|
|
1964
|
+
async function validateKnownFormat(artifact, format, target) {
|
|
1965
|
+
if (!format) return [];
|
|
1966
|
+
if (format === "codex-command-policy") return validateCodexCommandPolicyRule(artifact);
|
|
1967
|
+
if (format === "markdown-rule" || format === "claude-markdown-rule" || format === "copilot-instruction-rule") {
|
|
1968
|
+
return validateMarkdownRule(artifact, format);
|
|
1969
|
+
}
|
|
1970
|
+
if (format === "openclaw-plugin" || target.semantic === "openclaw-plugin") {
|
|
1971
|
+
return validateOpenClawPlugin(artifact);
|
|
1972
|
+
}
|
|
1973
|
+
return [];
|
|
1974
|
+
}
|
|
1975
|
+
async function validateGenericStructure(artifact, target) {
|
|
1976
|
+
const issues = [];
|
|
1977
|
+
if (artifact.type === "skills") {
|
|
1978
|
+
if (artifact.kind === "dir") {
|
|
1979
|
+
const skillMd = join7(artifactPath(artifact), "SKILL.md");
|
|
1980
|
+
if (!await pathExists(skillMd)) {
|
|
1981
|
+
issues.push({ artifact, message: "skill directory must contain SKILL.md" });
|
|
1982
|
+
} else {
|
|
1983
|
+
issues.push(...await validateSkillFrontmatter(artifact, skillMd));
|
|
1984
|
+
}
|
|
1985
|
+
} else if (!hasExtension(artifact, ".md")) {
|
|
1986
|
+
issues.push({ artifact, message: "file skill artifacts must be Markdown files" });
|
|
1987
|
+
} else {
|
|
1988
|
+
issues.push(...await validateSkillFrontmatter(artifact, artifactPath(artifact)));
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
if (target.merge === "json-deep") {
|
|
1992
|
+
const parsed = await parseJsonObjectArtifact(artifact);
|
|
1993
|
+
if (!parsed.ok) issues.push({ artifact, message: parsed.message });
|
|
1994
|
+
}
|
|
1995
|
+
if (target.merge === "codex-toml-mcp") {
|
|
1996
|
+
const parsed = await parseJsonObjectArtifact(artifact);
|
|
1997
|
+
if (!parsed.ok) {
|
|
1998
|
+
issues.push({ artifact, message: parsed.message });
|
|
1999
|
+
} else if (Object.keys(extractMcpServers2(parsed.value)).length === 0) {
|
|
2000
|
+
issues.push({ artifact, message: "Codex MCP artifact must contain at least one server object, either under mcpServers or as top-level server entries" });
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return issues;
|
|
2004
|
+
}
|
|
2005
|
+
async function validateSkillFrontmatter(artifact, skillMdPath) {
|
|
2006
|
+
let content;
|
|
2007
|
+
try {
|
|
2008
|
+
content = await readFile9(skillMdPath, "utf8");
|
|
2009
|
+
} catch (error) {
|
|
2010
|
+
return [{ artifact, message: `could not read SKILL.md: ${errorMessage(error)}` }];
|
|
2011
|
+
}
|
|
2012
|
+
if (content.charCodeAt(0) === 65279) {
|
|
2013
|
+
return [{ artifact, message: "SKILL.md must not start with a UTF-8 BOM; YAML frontmatter must be the first bytes" }];
|
|
2014
|
+
}
|
|
2015
|
+
const isDelimiter = (line) => line.trimEnd() === "---";
|
|
2016
|
+
const lines = content.split(/\r?\n/);
|
|
2017
|
+
if (!isDelimiter(lines[0] ?? "")) {
|
|
2018
|
+
return [{ artifact, message: "SKILL.md must begin with YAML frontmatter delimited by '---' on the first line (no content before it)" }];
|
|
2019
|
+
}
|
|
2020
|
+
const closing = lines.findIndex((line, index) => index > 0 && isDelimiter(line));
|
|
2021
|
+
if (closing === -1) {
|
|
2022
|
+
return [{ artifact, message: "SKILL.md frontmatter is not closed with a '---' delimiter" }];
|
|
2023
|
+
}
|
|
2024
|
+
const issues = [];
|
|
2025
|
+
const frontmatter = lines.slice(1, closing);
|
|
2026
|
+
if (!frontmatter.some((line) => /^name\s*:/.test(line))) {
|
|
2027
|
+
issues.push({ artifact, message: "SKILL.md frontmatter must define 'name'" });
|
|
2028
|
+
}
|
|
2029
|
+
if (!frontmatter.some((line) => /^description\s*:/.test(line))) {
|
|
2030
|
+
issues.push({ artifact, message: "SKILL.md frontmatter must define 'description'" });
|
|
2031
|
+
}
|
|
2032
|
+
return issues;
|
|
2033
|
+
}
|
|
2034
|
+
async function validateCodexCommandPolicyRule(artifact) {
|
|
2035
|
+
const issues = [];
|
|
2036
|
+
if (artifact.type !== "rules") {
|
|
2037
|
+
return [{ artifact, message: "codex-command-policy format is only valid for rules artifacts" }];
|
|
2038
|
+
}
|
|
2039
|
+
if (artifact.kind !== "file") {
|
|
2040
|
+
issues.push({ artifact, message: "Codex command-policy rules must be files" });
|
|
2041
|
+
}
|
|
2042
|
+
if (!hasExtension(artifact, ".rules")) {
|
|
2043
|
+
issues.push({ artifact, message: "Codex command-policy rules must use the .rules extension" });
|
|
2044
|
+
}
|
|
2045
|
+
const content = await readUtf8Artifact(artifact, issues);
|
|
2046
|
+
if (content === void 0) return issues;
|
|
2047
|
+
if (!/\bprefix_rule\s*\(/.test(content)) {
|
|
2048
|
+
issues.push({ artifact, message: "Codex command-policy rules must contain at least one prefix_rule(...)" });
|
|
2049
|
+
}
|
|
2050
|
+
if (/\bprefix_rule\s*\(/.test(content) && !/\bpattern\s*=/.test(content)) {
|
|
2051
|
+
issues.push({ artifact, message: "Codex command-policy rules must define a pattern field" });
|
|
2052
|
+
}
|
|
2053
|
+
for (const match of content.matchAll(/\bdecision\s*=\s*["']([^"']+)["']/g)) {
|
|
2054
|
+
const decision = match[1];
|
|
2055
|
+
if (decision !== "allow" && decision !== "prompt" && decision !== "forbidden") {
|
|
2056
|
+
issues.push({ artifact, message: `Codex command-policy decision '${decision}' must be allow, prompt, or forbidden` });
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return issues;
|
|
2060
|
+
}
|
|
2061
|
+
function validateMarkdownRule(artifact, format) {
|
|
2062
|
+
const label = format === "copilot-instruction-rule" ? "Copilot instruction rules" : format === "claude-markdown-rule" ? "Claude markdown rules" : "Markdown rules";
|
|
2063
|
+
const issues = [];
|
|
2064
|
+
if (artifact.type !== "rules") {
|
|
2065
|
+
issues.push({ artifact, message: `${format} format is only valid for rules artifacts` });
|
|
2066
|
+
}
|
|
2067
|
+
if (artifact.kind !== "file") {
|
|
2068
|
+
issues.push({ artifact, message: `${label} must be files` });
|
|
2069
|
+
}
|
|
2070
|
+
if (!hasExtension(artifact, ".md") && !hasExtension(artifact, ".markdown")) {
|
|
2071
|
+
issues.push({ artifact, message: `${label} must use a Markdown extension` });
|
|
2072
|
+
}
|
|
2073
|
+
return issues;
|
|
2074
|
+
}
|
|
2075
|
+
async function validateOpenClawPlugin(artifact) {
|
|
2076
|
+
const issues = [];
|
|
2077
|
+
if (artifact.type !== "plugins") {
|
|
2078
|
+
return [{ artifact, message: "openclaw-plugin format is only valid for plugins artifacts" }];
|
|
2079
|
+
}
|
|
2080
|
+
if (artifact.kind !== "dir") {
|
|
2081
|
+
return [{ artifact, message: "OpenClaw plugins must be directory artifacts" }];
|
|
2082
|
+
}
|
|
2083
|
+
const manifestPaths = await openClawPluginManifestPaths(artifact);
|
|
2084
|
+
if (manifestPaths.length === 0) {
|
|
2085
|
+
return [{ artifact, message: "OpenClaw plugins must contain plugin.json or openclaw.plugin.json" }];
|
|
2086
|
+
}
|
|
2087
|
+
const parsed = await Promise.all(manifestPaths.map(async (manifestPath) => {
|
|
2088
|
+
const result = await parseOpenClawPluginManifest(manifestPath);
|
|
2089
|
+
if (!result.ok) issues.push({ artifact, message: result.message });
|
|
2090
|
+
return result;
|
|
2091
|
+
}));
|
|
2092
|
+
const names = new Set(parsed.filter((result) => result.ok).map((result) => result.name));
|
|
2093
|
+
if (names.size > 1) {
|
|
2094
|
+
issues.push({ artifact, message: "OpenClaw plugin descriptors must declare the same name" });
|
|
2095
|
+
}
|
|
2096
|
+
return issues;
|
|
2097
|
+
}
|
|
2098
|
+
async function openClawPluginManifestPaths(artifact) {
|
|
2099
|
+
const root = artifactPath(artifact);
|
|
2100
|
+
const candidates = [join7(root, "plugin.json"), join7(root, "openclaw.plugin.json")];
|
|
2101
|
+
const existing = await Promise.all(candidates.map(async (candidate) => await pathExists(candidate) ? candidate : void 0));
|
|
2102
|
+
return existing.filter((candidate) => candidate !== void 0);
|
|
2103
|
+
}
|
|
2104
|
+
async function parseOpenClawPluginManifest(manifestPath) {
|
|
2105
|
+
const manifestName = basename5(manifestPath);
|
|
2106
|
+
try {
|
|
2107
|
+
const parsed = JSON.parse(await readFile9(manifestPath, "utf8"));
|
|
2108
|
+
if (!isRecord3(parsed)) {
|
|
2109
|
+
return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be a JSON object` };
|
|
2110
|
+
}
|
|
2111
|
+
if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
|
|
2112
|
+
return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must declare a non-empty name` };
|
|
2113
|
+
}
|
|
2114
|
+
return { ok: true, path: manifestPath, name: parsed.name.trim() };
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be valid JSON: ${errorMessage(error)}` };
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
async function readUtf8Artifact(artifact, issues) {
|
|
2120
|
+
if (artifact.kind !== "file") return void 0;
|
|
2121
|
+
try {
|
|
2122
|
+
return await readFile9(artifactPath(artifact), "utf8");
|
|
2123
|
+
} catch (error) {
|
|
2124
|
+
issues.push({ artifact, message: `could not read artifact: ${errorMessage(error)}` });
|
|
2125
|
+
return void 0;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
async function parseJsonObjectArtifact(artifact) {
|
|
2129
|
+
if (artifact.kind !== "file") {
|
|
2130
|
+
return { ok: false, message: "merge artifacts must be JSON files" };
|
|
2131
|
+
}
|
|
2132
|
+
try {
|
|
2133
|
+
const parsed = JSON.parse(await readFile9(artifactPath(artifact), "utf8"));
|
|
2134
|
+
if (!isRecord3(parsed)) return { ok: false, message: "merge artifacts must contain a JSON object" };
|
|
2135
|
+
return { ok: true, value: parsed };
|
|
2136
|
+
} catch (error) {
|
|
2137
|
+
return { ok: false, message: `merge artifact must be valid JSON: ${errorMessage(error)}` };
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
function extractMcpServers2(source) {
|
|
2141
|
+
const raw = isRecord3(source.mcpServers) ? source.mcpServers : source;
|
|
2142
|
+
const servers = {};
|
|
2143
|
+
for (const [name, value] of Object.entries(raw)) {
|
|
2144
|
+
if (isRecord3(value)) servers[name] = value;
|
|
2145
|
+
}
|
|
2146
|
+
return servers;
|
|
2147
|
+
}
|
|
2148
|
+
function artifactPath(artifact) {
|
|
2149
|
+
return artifact.stagedPath ?? artifact.sourcePath;
|
|
2150
|
+
}
|
|
2151
|
+
function artifactLabel(artifact) {
|
|
2152
|
+
const owner = artifact.packageName ? `${artifact.packageName}:` : "";
|
|
2153
|
+
return `${owner}${artifact.type}/${artifact.name}`;
|
|
2154
|
+
}
|
|
2155
|
+
function hasExtension(artifact, extension) {
|
|
2156
|
+
return basename5(artifact.name).toLowerCase().endsWith(extension) || basename5(artifactPath(artifact)).toLowerCase().endsWith(extension);
|
|
2157
|
+
}
|
|
2158
|
+
function isRecord3(value) {
|
|
2159
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2160
|
+
}
|
|
2161
|
+
function errorMessage(error) {
|
|
2162
|
+
return error instanceof Error ? error.message : String(error);
|
|
1464
2163
|
}
|
|
1465
2164
|
|
|
1466
2165
|
// src/install/plan.ts
|
|
1467
2166
|
async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot, manifest, transport = localTransport, options = {}) {
|
|
2167
|
+
const requestedInstallationType = options.installationType ?? defaultInstallationType;
|
|
2168
|
+
const installationType = resolveInstallationTypeForArtifacts(adapter, desiredArtifacts.map((artifact) => artifact.type), requestedInstallationType);
|
|
2169
|
+
const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
2170
|
+
await validateArtifactsForInstall(desiredArtifacts, adapter, installationType);
|
|
1468
2171
|
for (const artifact of desiredArtifacts) {
|
|
1469
2172
|
if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
|
|
1470
2173
|
throw new Error(`Dependency-provided ${artifact.type} artifacts cannot be installed until per-subentry ownership exists: ${artifact.type}/${artifact.name}`);
|
|
@@ -1472,13 +2175,13 @@ async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot,
|
|
|
1472
2175
|
}
|
|
1473
2176
|
const desired = [];
|
|
1474
2177
|
for (const artifact of desiredArtifacts) {
|
|
1475
|
-
const op = operationForArtifact(artifact, adapter,
|
|
2178
|
+
const op = operationForArtifact(artifact, adapter, installRoot, installationType, artifact.meta);
|
|
1476
2179
|
if (op) {
|
|
1477
2180
|
desired.push(op);
|
|
1478
2181
|
}
|
|
1479
2182
|
}
|
|
1480
|
-
await addProgrammaticOperations(desired, adapter,
|
|
1481
|
-
return createPlanFromOperations(desired, adapter,
|
|
2183
|
+
await addProgrammaticOperations(desired, adapter, installRoot);
|
|
2184
|
+
return createPlanFromOperations(desired, adapter, installRoot, manifest, transport, { ...options, installationType });
|
|
1482
2185
|
}
|
|
1483
2186
|
async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifest, transport, options) {
|
|
1484
2187
|
const workspaceOwner = options.workspaceOwner;
|
|
@@ -1540,11 +2243,38 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1540
2243
|
}
|
|
1541
2244
|
const currentHash = await transport.hashPath(op.destPath);
|
|
1542
2245
|
if (!existing) {
|
|
1543
|
-
|
|
2246
|
+
if (currentHash === op.desiredHash && options.forceConflict) {
|
|
2247
|
+
operations.push({
|
|
2248
|
+
...op,
|
|
2249
|
+
action: "skip",
|
|
2250
|
+
currentHash,
|
|
2251
|
+
reason: "force adopting unmanaged destination with matching hash"
|
|
2252
|
+
});
|
|
2253
|
+
} else if (options.replaceConflict) {
|
|
2254
|
+
operations.push({
|
|
2255
|
+
...op,
|
|
2256
|
+
action: "update",
|
|
2257
|
+
currentHash,
|
|
2258
|
+
reason: "force replacing unmanaged destination"
|
|
2259
|
+
});
|
|
2260
|
+
} else {
|
|
2261
|
+
operations.push({ ...op, action: "conflict", currentHash, reason: "destination exists but is not managed" });
|
|
2262
|
+
}
|
|
1544
2263
|
continue;
|
|
1545
2264
|
}
|
|
1546
2265
|
if (currentHash !== existing.hash) {
|
|
1547
2266
|
const composedFromDiff = changedComposedSelectors(op.composedFrom, existing.composedFrom);
|
|
2267
|
+
if (options.forceDrift) {
|
|
2268
|
+
operations.push({
|
|
2269
|
+
...op,
|
|
2270
|
+
action: "update",
|
|
2271
|
+
currentHash,
|
|
2272
|
+
manifestHash: existing.hash,
|
|
2273
|
+
reason: reasonWithComposedDiff("force replacing drifted managed destination", op.composedFrom, existing.composedFrom),
|
|
2274
|
+
composedFromDiff
|
|
2275
|
+
});
|
|
2276
|
+
continue;
|
|
2277
|
+
}
|
|
1548
2278
|
operations.push({
|
|
1549
2279
|
...op,
|
|
1550
2280
|
action: "drift",
|
|
@@ -1571,7 +2301,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1571
2301
|
}
|
|
1572
2302
|
for (const entry of effectiveEntries) {
|
|
1573
2303
|
if (desired.has(entry.path)) continue;
|
|
1574
|
-
const destPath =
|
|
2304
|
+
const destPath = join8(targetRoot, entry.path);
|
|
1575
2305
|
if (!await transport.pathExists(destPath)) continue;
|
|
1576
2306
|
const currentHash = await transport.hashPath(destPath);
|
|
1577
2307
|
if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
|
|
@@ -1592,7 +2322,8 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1592
2322
|
channel: entry.channel,
|
|
1593
2323
|
packageName: entry.packageName,
|
|
1594
2324
|
composedFrom: entry.composedFrom,
|
|
1595
|
-
...operationMetadataFromEntry(entry)
|
|
2325
|
+
...operationMetadataFromEntry(entry),
|
|
2326
|
+
...options.forceDrift ? { action: "remove", reason: "force removing drifted stale managed destination" } : {}
|
|
1596
2327
|
});
|
|
1597
2328
|
} else {
|
|
1598
2329
|
operations.push({
|
|
@@ -1616,6 +2347,8 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
1616
2347
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
1617
2348
|
return {
|
|
1618
2349
|
adapter: adapter.name,
|
|
2350
|
+
installationType: options.installationType ?? defaultInstallationType,
|
|
2351
|
+
stateKey: options.stateKey,
|
|
1619
2352
|
targetRoot,
|
|
1620
2353
|
operations,
|
|
1621
2354
|
hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
|
|
@@ -1678,7 +2411,7 @@ async function canStrictlyAdoptLegacyEntry(entry, op, targetRoot, transport) {
|
|
|
1678
2411
|
if (entry.artifactType !== op.artifactType || entry.artifactName !== op.artifactName) return false;
|
|
1679
2412
|
if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
|
|
1680
2413
|
if (!packageIdentityMatches(entry, op)) return false;
|
|
1681
|
-
const destPath =
|
|
2414
|
+
const destPath = join8(targetRoot, entry.path);
|
|
1682
2415
|
if (!await transport.pathExists(destPath)) return false;
|
|
1683
2416
|
return await transport.hashPath(destPath) === entry.hash;
|
|
1684
2417
|
}
|
|
@@ -1808,7 +2541,7 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
|
|
|
1808
2541
|
artifactType: entry.artifactType,
|
|
1809
2542
|
artifactName: entry.artifactName,
|
|
1810
2543
|
kind: entry.kind,
|
|
1811
|
-
destPath: operation?.destPath ??
|
|
2544
|
+
destPath: operation?.destPath ?? join8(targetRoot, entry.path),
|
|
1812
2545
|
relativeDestPath: entry.path,
|
|
1813
2546
|
desiredHash: entry.sourceHash,
|
|
1814
2547
|
currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
|
|
@@ -1830,11 +2563,18 @@ function normalizeOperationOwners(op) {
|
|
|
1830
2563
|
function isGuardedMergeTarget(type) {
|
|
1831
2564
|
return type === "mcp" || type === "hooks" || type === "settings" || type === "plugins";
|
|
1832
2565
|
}
|
|
1833
|
-
function operationForArtifact(artifact, adapter, targetRoot, meta) {
|
|
1834
|
-
|
|
1835
|
-
|
|
2566
|
+
function operationForArtifact(artifact, adapter, targetRoot, installationType, meta) {
|
|
2567
|
+
if (artifact.type === "fragments") return void 0;
|
|
2568
|
+
const target = targetMappingForArtifact(adapter, artifact.type, installationType);
|
|
2569
|
+
if (!target?.enabled) {
|
|
2570
|
+
const supported = Object.keys(adapter.targets[artifact.type] ?? {});
|
|
2571
|
+
const suffix = supported.length > 0 ? ` Supported installation types: ${supported.join(", ")}` : "";
|
|
2572
|
+
throw new Error(`Adapter ${adapter.name} does not support ${artifact.type}/${artifact.name} for installation type '${installationType}'.${suffix}`);
|
|
2573
|
+
}
|
|
1836
2574
|
const metadata = operationMetadataFromDesired(artifact, meta);
|
|
1837
|
-
const
|
|
2575
|
+
const rawInstallName = metadata.installName ?? artifact.name;
|
|
2576
|
+
assertSafeInstallName(rawInstallName, `${artifact.type}/${artifact.name}`);
|
|
2577
|
+
const installName = semanticInstallName(artifact, target.semantic, rawInstallName);
|
|
1838
2578
|
assertSafeInstallName(installName, `${artifact.type}/${artifact.name}`);
|
|
1839
2579
|
if (artifact.type === "plugins" && target.semantic === "openclaw-plugin") {
|
|
1840
2580
|
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
@@ -1855,7 +2595,26 @@ function operationForArtifact(artifact, adapter, targetRoot, meta) {
|
|
|
1855
2595
|
...metadata
|
|
1856
2596
|
};
|
|
1857
2597
|
}
|
|
1858
|
-
|
|
2598
|
+
if (artifact.type === "subagents" && target.semantic === "codex-subagent") {
|
|
2599
|
+
const destPath2 = join8(targetRoot, target.dest, `${installName.replace(/\.toml$/i, "")}.toml`);
|
|
2600
|
+
return {
|
|
2601
|
+
action: "create",
|
|
2602
|
+
artifactType: artifact.type,
|
|
2603
|
+
artifactName: artifact.name,
|
|
2604
|
+
kind: "file",
|
|
2605
|
+
sourcePath: artifact.stagedPath ?? artifact.sourcePath,
|
|
2606
|
+
destPath: destPath2,
|
|
2607
|
+
relativeDestPath: relative2(targetRoot, destPath2).replaceAll("\\", "/"),
|
|
2608
|
+
desiredHash: artifact.hash,
|
|
2609
|
+
reason: "destination missing",
|
|
2610
|
+
channel: artifact.channel ?? "managed",
|
|
2611
|
+
packageName: artifact.packageName,
|
|
2612
|
+
composedFrom: metadata.composedFrom,
|
|
2613
|
+
...metadata,
|
|
2614
|
+
installName: installName.replace(/\.toml$/i, "")
|
|
2615
|
+
};
|
|
2616
|
+
}
|
|
2617
|
+
const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ? join8(targetRoot, target.dest) : join8(targetRoot, target.dest, installName);
|
|
1859
2618
|
return {
|
|
1860
2619
|
action: "create",
|
|
1861
2620
|
artifactType: artifact.type,
|
|
@@ -1870,11 +2629,29 @@ function operationForArtifact(artifact, adapter, targetRoot, meta) {
|
|
|
1870
2629
|
packageName: artifact.packageName,
|
|
1871
2630
|
mergeStrategy: target.merge,
|
|
1872
2631
|
composedFrom: metadata.composedFrom,
|
|
1873
|
-
...metadata
|
|
2632
|
+
...metadata,
|
|
2633
|
+
installName
|
|
1874
2634
|
};
|
|
1875
2635
|
}
|
|
1876
|
-
function
|
|
1877
|
-
|
|
2636
|
+
function semanticInstallName(artifact, semantic, installName) {
|
|
2637
|
+
if (artifact.type === "rules" && semantic === "copilot-instruction") {
|
|
2638
|
+
return withExtension(installName, ".instructions.md", [".instructions.md", ".md"]);
|
|
2639
|
+
}
|
|
2640
|
+
if (artifact.type === "commands" && semantic === "copilot-prompt") {
|
|
2641
|
+
return withExtension(installName, ".prompt.md", [".prompt.md", ".md"]);
|
|
2642
|
+
}
|
|
2643
|
+
if (artifact.type === "subagents" && semantic === "copilot-agent") {
|
|
2644
|
+
return withExtension(installName, ".agent.md", [".agent.md", ".md"]);
|
|
2645
|
+
}
|
|
2646
|
+
return installName;
|
|
2647
|
+
}
|
|
2648
|
+
function withExtension(name, targetExtension, knownExtensions) {
|
|
2649
|
+
const match = knownExtensions.find((extension) => name.toLowerCase().endsWith(extension.toLowerCase()));
|
|
2650
|
+
const base = match ? name.slice(0, -match.length) : name;
|
|
2651
|
+
return `${base}${targetExtension}`;
|
|
2652
|
+
}
|
|
2653
|
+
function isFileTarget(dest) {
|
|
2654
|
+
return /\.(json|jsonc|toml|md)$/i.test(dest);
|
|
1878
2655
|
}
|
|
1879
2656
|
function reasonWithComposedDiff(reason, desired, current) {
|
|
1880
2657
|
const changed = changedComposedSelectors(desired, current);
|
|
@@ -1907,13 +2684,19 @@ function summarizePlan(plan) {
|
|
|
1907
2684
|
}
|
|
1908
2685
|
return summary;
|
|
1909
2686
|
}
|
|
2687
|
+
function isPendingInstallAction(action) {
|
|
2688
|
+
return action !== "skip" && action !== "keep";
|
|
2689
|
+
}
|
|
2690
|
+
function isPendingInstallOperation(operation) {
|
|
2691
|
+
return isPendingInstallAction(operation.action);
|
|
2692
|
+
}
|
|
1910
2693
|
|
|
1911
2694
|
// src/install/uninstall.ts
|
|
1912
|
-
import { join as
|
|
2695
|
+
import { join as join9 } from "path";
|
|
1913
2696
|
async function createUninstallPlan(manifest, transport = localTransport) {
|
|
1914
2697
|
const operations = [];
|
|
1915
2698
|
for (const entry of manifest.entries) {
|
|
1916
|
-
const destPath =
|
|
2699
|
+
const destPath = join9(manifest.targetRoot, entry.path);
|
|
1917
2700
|
if (!await transport.pathExists(destPath)) continue;
|
|
1918
2701
|
const currentHash = await transport.hashPath(destPath);
|
|
1919
2702
|
if (currentHash !== entry.hash) {
|
|
@@ -1959,6 +2742,8 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
1959
2742
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
1960
2743
|
return {
|
|
1961
2744
|
adapter: manifest.adapter,
|
|
2745
|
+
installationType: "installationType" in manifest ? manifest.installationType : defaultInstallationType,
|
|
2746
|
+
stateKey: "stateKey" in manifest ? manifest.stateKey : void 0,
|
|
1962
2747
|
targetRoot: manifest.targetRoot,
|
|
1963
2748
|
operations,
|
|
1964
2749
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
@@ -1967,13 +2752,15 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
1967
2752
|
};
|
|
1968
2753
|
}
|
|
1969
2754
|
async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport = localTransport, options = {}) {
|
|
1970
|
-
const
|
|
2755
|
+
const installationType = "installationType" in manifest ? manifest.installationType : defaultInstallationType;
|
|
2756
|
+
const stateKey = "stateKey" in manifest ? manifest.stateKey : void 0;
|
|
2757
|
+
const desiredPlan = await createCombinedInstallPlan(remainingDesired, adapter, manifest.targetRoot, void 0, transport, { installationType, stateKey });
|
|
1971
2758
|
const ownersByPath = new Map(
|
|
1972
2759
|
desiredPlan.operations.filter((operation) => operation.owners?.length).map((operation) => [operation.relativeDestPath, operation.owners ?? []])
|
|
1973
2760
|
);
|
|
1974
2761
|
const operations = [];
|
|
1975
2762
|
for (const entry of manifest.entries) {
|
|
1976
|
-
const destPath =
|
|
2763
|
+
const destPath = join9(manifest.targetRoot, entry.path);
|
|
1977
2764
|
if (!await transport.pathExists(destPath)) continue;
|
|
1978
2765
|
const currentHash = await transport.hashPath(destPath);
|
|
1979
2766
|
const remainingOwners = ownersByPath.get(entry.path) ?? [];
|
|
@@ -2044,6 +2831,8 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2044
2831
|
operations.sort((a, b) => a.relativeDestPath.localeCompare(b.relativeDestPath));
|
|
2045
2832
|
return {
|
|
2046
2833
|
adapter: manifest.adapter,
|
|
2834
|
+
installationType,
|
|
2835
|
+
stateKey,
|
|
2047
2836
|
targetRoot: manifest.targetRoot,
|
|
2048
2837
|
operations,
|
|
2049
2838
|
hasBlockingChanges: operations.some((operation) => operation.action === "conflict"),
|
|
@@ -2091,7 +2880,7 @@ var channelLabels = {
|
|
|
2091
2880
|
ejected: "EJECTED"
|
|
2092
2881
|
};
|
|
2093
2882
|
function formatPlan(plan) {
|
|
2094
|
-
const lines = [`Plan for ${plan.adapter} at ${plan.targetRoot}`];
|
|
2883
|
+
const lines = [`Plan for ${plan.adapter}/${plan.installationType} at ${plan.targetRoot}`];
|
|
2095
2884
|
if (plan.migrationReport) {
|
|
2096
2885
|
const dropped = plan.migrationReport.dropped.length > 0 ? `; dropped unmanaged ${plan.migrationReport.dropped.join(", ")}` : "";
|
|
2097
2886
|
lines.push(`MIGRATE adopted ${plan.migrationReport.adopted} legacy entries${dropped}`);
|
|
@@ -2138,6 +2927,9 @@ function formatGraphPlan(result) {
|
|
|
2138
2927
|
for (const decision of result.bundle.graphLock.canonical.namespacing) {
|
|
2139
2928
|
lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
2140
2929
|
}
|
|
2930
|
+
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
2931
|
+
lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
2932
|
+
}
|
|
2141
2933
|
if (result.graphDiff.length > 0) {
|
|
2142
2934
|
lines.push("Graph diff:");
|
|
2143
2935
|
lines.push(...result.graphDiff);
|
|
@@ -2175,6 +2967,9 @@ function formatLockDependencyTree(lock) {
|
|
|
2175
2967
|
for (const decision of lock.canonical.namespacing) {
|
|
2176
2968
|
lines.push(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
2177
2969
|
}
|
|
2970
|
+
for (const decision of lock.canonical.overrides) {
|
|
2971
|
+
lines.push(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
2972
|
+
}
|
|
2178
2973
|
return lines.join("\n");
|
|
2179
2974
|
}
|
|
2180
2975
|
function formatDepsWhy(lock, manifest, query) {
|
|
@@ -2197,6 +2992,9 @@ function formatDepsWhy(lock, manifest, query) {
|
|
|
2197
2992
|
}
|
|
2198
2993
|
const namespace = lock.canonical.namespacing.find((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name);
|
|
2199
2994
|
lines.push(namespace ? `NAME ${namespace.reason}: ${namespace.type}/${namespace.name} -> ${namespace.type}/${namespace.installName}` : `NAME plain: ${match.type}/${match.name}`);
|
|
2995
|
+
for (const override of lock.canonical.overrides.filter((decision) => decision.graphNodeId === match.graphNodeId && decision.type === match.type && decision.name === match.name)) {
|
|
2996
|
+
lines.push(`OVERRIDE replaces ${override.overriddenGraphNodeId}:${override.type}/${override.name} via ${override.rootId}`);
|
|
2997
|
+
}
|
|
2200
2998
|
return lines.join("\n");
|
|
2201
2999
|
}
|
|
2202
3000
|
function formatSelected(selected, reasons) {
|
|
@@ -2254,15 +3052,15 @@ function ownerChains(lock, nodeId) {
|
|
|
2254
3052
|
}
|
|
2255
3053
|
|
|
2256
3054
|
// src/source/git.ts
|
|
2257
|
-
import { execFile as
|
|
2258
|
-
import { cp as cp2, mkdir as
|
|
2259
|
-
import { homedir } from "os";
|
|
2260
|
-
import { basename as
|
|
2261
|
-
import { promisify as
|
|
3055
|
+
import { execFile as execFile4 } from "child_process";
|
|
3056
|
+
import { cp as cp2, mkdir as mkdir8, rename as rename3, rm as rm4, writeFile as writeFile9 } from "fs/promises";
|
|
3057
|
+
import { homedir as homedir2 } from "os";
|
|
3058
|
+
import { basename as basename7, dirname as dirname9, join as join12, resolve as resolve6 } from "path";
|
|
3059
|
+
import { promisify as promisify4 } from "util";
|
|
2262
3060
|
|
|
2263
3061
|
// src/model/package.ts
|
|
2264
|
-
import { readFile as
|
|
2265
|
-
import { join as
|
|
3062
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
3063
|
+
import { join as join10 } from "path";
|
|
2266
3064
|
import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
|
|
2267
3065
|
import { z as z5 } from "zod";
|
|
2268
3066
|
var legacyArtifactTypeSchema = z5.enum([
|
|
@@ -2279,10 +3077,12 @@ var legacyArtifactTypeSchema = z5.enum([
|
|
|
2279
3077
|
var runtimeListSchema = z5.array(z5.string().min(1));
|
|
2280
3078
|
var packageProvideBaseSchema = z5.object({
|
|
2281
3079
|
path: z5.string().min(1),
|
|
3080
|
+
format: artifactFormatSchema.optional(),
|
|
2282
3081
|
assets: z5.array(packageAssetSchema).optional(),
|
|
2283
3082
|
required: z5.boolean().optional()
|
|
2284
3083
|
});
|
|
2285
3084
|
var packageItemSchema = z5.object({
|
|
3085
|
+
format: artifactFormatSchema.optional(),
|
|
2286
3086
|
requires: z5.array(packageItemRequireSchema).optional(),
|
|
2287
3087
|
compose: z5.array(packageComposeEntrySchema).optional(),
|
|
2288
3088
|
runtimes: runtimeListSchema.optional()
|
|
@@ -2337,7 +3137,7 @@ var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNa
|
|
|
2337
3137
|
var warnedLegacyManifestPaths = /* @__PURE__ */ new Set();
|
|
2338
3138
|
async function findPackageManifestPath(root, options = {}) {
|
|
2339
3139
|
for (const name of packageManifestNames) {
|
|
2340
|
-
const candidate =
|
|
3140
|
+
const candidate = join10(root, name);
|
|
2341
3141
|
if (!await pathExists(candidate)) continue;
|
|
2342
3142
|
if (isLegacyPackageManifestName(name) && options.warnLegacy !== false && !warnedLegacyManifestPaths.has(candidate)) {
|
|
2343
3143
|
warnedLegacyManifestPaths.add(candidate);
|
|
@@ -2350,7 +3150,7 @@ async function findPackageManifestPath(root, options = {}) {
|
|
|
2350
3150
|
async function readPackageManifest(root) {
|
|
2351
3151
|
const path = await findPackageManifestPath(root);
|
|
2352
3152
|
if (!path) return void 0;
|
|
2353
|
-
const content = await
|
|
3153
|
+
const content = await readFile10(path, "utf8");
|
|
2354
3154
|
const errors = [];
|
|
2355
3155
|
const parsed = parse2(content, errors, { allowTrailingComma: true, disallowComments: false });
|
|
2356
3156
|
if (errors.length > 0) {
|
|
@@ -2360,7 +3160,7 @@ async function readPackageManifest(root) {
|
|
|
2360
3160
|
return parsePackageManifest(parsed, path);
|
|
2361
3161
|
}
|
|
2362
3162
|
function parsePackageManifest(parsed, path = "package manifest") {
|
|
2363
|
-
if (!
|
|
3163
|
+
if (!isRecord4(parsed)) {
|
|
2364
3164
|
throw new Error(`Invalid package manifest ${path}: expected an object`);
|
|
2365
3165
|
}
|
|
2366
3166
|
if (parsed.schemaVersion === 1) {
|
|
@@ -2388,9 +3188,9 @@ function v1OpenPackViolations(manifest) {
|
|
|
2388
3188
|
}
|
|
2389
3189
|
const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
|
|
2390
3190
|
for (const [index, provide] of provides.entries()) {
|
|
2391
|
-
if (!
|
|
3191
|
+
if (!isRecord4(provide)) continue;
|
|
2392
3192
|
if (provide.type === "fragments") violations.push(`provides[${index}].type=fragments`);
|
|
2393
|
-
for (const key of ["items", "compose", "runtimes"]) {
|
|
3193
|
+
for (const key of ["format", "items", "compose", "runtimes"]) {
|
|
2394
3194
|
if (Object.prototype.hasOwnProperty.call(provide, key)) violations.push(`provides[${index}].${key}`);
|
|
2395
3195
|
}
|
|
2396
3196
|
}
|
|
@@ -2399,13 +3199,14 @@ function v1OpenPackViolations(manifest) {
|
|
|
2399
3199
|
function isLegacyPackageManifestName(name) {
|
|
2400
3200
|
return legacyPackageManifestNames.includes(name);
|
|
2401
3201
|
}
|
|
2402
|
-
function
|
|
3202
|
+
function isRecord4(value) {
|
|
2403
3203
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2404
3204
|
}
|
|
2405
3205
|
|
|
2406
3206
|
// src/source/local.ts
|
|
3207
|
+
import { createHash as createHash3 } from "crypto";
|
|
2407
3208
|
import { readdir, stat as stat3 } from "fs/promises";
|
|
2408
|
-
import { basename as
|
|
3209
|
+
import { basename as basename6, join as join11, relative as relative3, resolve as resolve5 } from "path";
|
|
2409
3210
|
var LocalSourceDriver = class {
|
|
2410
3211
|
name = "local";
|
|
2411
3212
|
async resolve(source) {
|
|
@@ -2425,7 +3226,7 @@ var LocalSourceDriver = class {
|
|
|
2425
3226
|
packageName: manifest?.name,
|
|
2426
3227
|
packageVersion: manifest?.version,
|
|
2427
3228
|
mode: "pinned",
|
|
2428
|
-
sourceHash: await
|
|
3229
|
+
sourceHash: await hashLocalSource(resolvedPath, manifest)
|
|
2429
3230
|
};
|
|
2430
3231
|
}
|
|
2431
3232
|
async list(resolved) {
|
|
@@ -2435,29 +3236,29 @@ var LocalSourceDriver = class {
|
|
|
2435
3236
|
}
|
|
2436
3237
|
const artifacts = [];
|
|
2437
3238
|
const root = resolved.resolvedPath;
|
|
2438
|
-
const instructions = await firstExisting([
|
|
3239
|
+
const instructions = await firstExisting([join11(root, "instructions.md"), join11(root, "AGENTS.md")]);
|
|
2439
3240
|
if (instructions) {
|
|
2440
3241
|
artifacts.push({
|
|
2441
3242
|
type: "instructions",
|
|
2442
|
-
name:
|
|
3243
|
+
name: basename6(instructions),
|
|
2443
3244
|
sourcePath: instructions,
|
|
2444
|
-
relativePath:
|
|
3245
|
+
relativePath: basename6(instructions),
|
|
2445
3246
|
kind: "file",
|
|
2446
3247
|
hash: await hashPath(instructions),
|
|
2447
3248
|
packageName: resolved.packageName,
|
|
2448
3249
|
channel: "managed"
|
|
2449
3250
|
});
|
|
2450
3251
|
}
|
|
2451
|
-
const rulesDir =
|
|
3252
|
+
const rulesDir = join11(root, "rules");
|
|
2452
3253
|
if (await pathExists(rulesDir)) {
|
|
2453
3254
|
for (const entry of await sortedDirEntries(rulesDir)) {
|
|
2454
|
-
const full =
|
|
3255
|
+
const full = join11(rulesDir, entry.name);
|
|
2455
3256
|
if (entry.isFile()) {
|
|
2456
3257
|
artifacts.push({
|
|
2457
3258
|
type: "rules",
|
|
2458
3259
|
name: entry.name,
|
|
2459
3260
|
sourcePath: full,
|
|
2460
|
-
relativePath:
|
|
3261
|
+
relativePath: join11("rules", entry.name),
|
|
2461
3262
|
kind: "file",
|
|
2462
3263
|
hash: await hashPath(full),
|
|
2463
3264
|
packageName: resolved.packageName,
|
|
@@ -2466,16 +3267,16 @@ var LocalSourceDriver = class {
|
|
|
2466
3267
|
}
|
|
2467
3268
|
}
|
|
2468
3269
|
}
|
|
2469
|
-
const fragmentsDir =
|
|
3270
|
+
const fragmentsDir = join11(root, "fragments");
|
|
2470
3271
|
if (await pathExists(fragmentsDir)) {
|
|
2471
3272
|
for (const entry of await sortedDirEntries(fragmentsDir)) {
|
|
2472
|
-
const full =
|
|
3273
|
+
const full = join11(fragmentsDir, entry.name);
|
|
2473
3274
|
if (entry.isFile()) {
|
|
2474
3275
|
artifacts.push({
|
|
2475
3276
|
type: "fragments",
|
|
2476
3277
|
name: entry.name,
|
|
2477
3278
|
sourcePath: full,
|
|
2478
|
-
relativePath:
|
|
3279
|
+
relativePath: join11("fragments", entry.name),
|
|
2479
3280
|
kind: "file",
|
|
2480
3281
|
hash: await hashPath(full),
|
|
2481
3282
|
packageName: resolved.packageName,
|
|
@@ -2484,16 +3285,16 @@ var LocalSourceDriver = class {
|
|
|
2484
3285
|
}
|
|
2485
3286
|
}
|
|
2486
3287
|
}
|
|
2487
|
-
const skillsDir =
|
|
3288
|
+
const skillsDir = join11(root, "skills");
|
|
2488
3289
|
if (await pathExists(skillsDir)) {
|
|
2489
3290
|
for (const entry of await sortedDirEntries(skillsDir)) {
|
|
2490
|
-
const full =
|
|
3291
|
+
const full = join11(skillsDir, entry.name);
|
|
2491
3292
|
if (entry.isDirectory()) {
|
|
2492
3293
|
artifacts.push({
|
|
2493
3294
|
type: "skills",
|
|
2494
3295
|
name: entry.name,
|
|
2495
3296
|
sourcePath: full,
|
|
2496
|
-
relativePath:
|
|
3297
|
+
relativePath: join11("skills", entry.name),
|
|
2497
3298
|
kind: "dir",
|
|
2498
3299
|
hash: await hashPath(full),
|
|
2499
3300
|
packageName: resolved.packageName,
|
|
@@ -2504,7 +3305,7 @@ var LocalSourceDriver = class {
|
|
|
2504
3305
|
type: "skills",
|
|
2505
3306
|
name: entry.name.replace(/\.md$/, ""),
|
|
2506
3307
|
sourcePath: full,
|
|
2507
|
-
relativePath:
|
|
3308
|
+
relativePath: join11("skills", entry.name),
|
|
2508
3309
|
kind: "file",
|
|
2509
3310
|
hash: await hashPath(full),
|
|
2510
3311
|
packageName: resolved.packageName,
|
|
@@ -2514,7 +3315,7 @@ var LocalSourceDriver = class {
|
|
|
2514
3315
|
}
|
|
2515
3316
|
}
|
|
2516
3317
|
for (const type of ["commands", "subagents", "mcp", "hooks", "settings", "plugins"]) {
|
|
2517
|
-
const dir =
|
|
3318
|
+
const dir = join11(root, type);
|
|
2518
3319
|
if (!await pathExists(dir)) continue;
|
|
2519
3320
|
artifacts.push(...await listGenericArtifacts(type, dir, type, resolved.packageName));
|
|
2520
3321
|
}
|
|
@@ -2530,7 +3331,7 @@ var LocalSourceDriver = class {
|
|
|
2530
3331
|
findings.push({ level: "warning", message: "No instructions.md or AGENTS.md found", path: resolved.resolvedPath });
|
|
2531
3332
|
}
|
|
2532
3333
|
for (const artifact of artifacts.filter((item) => item.type === "skills" && item.kind === "dir")) {
|
|
2533
|
-
if (!await pathExists(
|
|
3334
|
+
if (!await pathExists(join11(artifact.sourcePath, "SKILL.md"))) {
|
|
2534
3335
|
findings.push({ level: "warning", message: `Skill directory has no SKILL.md: ${artifact.name}`, path: artifact.sourcePath });
|
|
2535
3336
|
}
|
|
2536
3337
|
}
|
|
@@ -2543,6 +3344,26 @@ var LocalSourceDriver = class {
|
|
|
2543
3344
|
return resolved;
|
|
2544
3345
|
}
|
|
2545
3346
|
};
|
|
3347
|
+
async function hashLocalSource(root, manifest) {
|
|
3348
|
+
if (!manifest) return hashPath(root);
|
|
3349
|
+
const hash = createHash3("sha256").update("local-openpack\0");
|
|
3350
|
+
const manifestPath = await findPackageManifestPath(root, { warnLegacy: false });
|
|
3351
|
+
if (manifestPath) {
|
|
3352
|
+
hash.update("manifest\0");
|
|
3353
|
+
hash.update(relative3(root, manifestPath).replaceAll("\\", "/")).update("\0");
|
|
3354
|
+
hash.update(await hashPath(manifestPath)).update("\0");
|
|
3355
|
+
}
|
|
3356
|
+
const provides = [...manifest.provides].sort((a, b) => `${a.type}\0${a.path}`.localeCompare(`${b.type}\0${b.path}`));
|
|
3357
|
+
for (const provide of provides) {
|
|
3358
|
+
const full = join11(root, provide.path);
|
|
3359
|
+
if (!await pathExists(full)) continue;
|
|
3360
|
+
hash.update("provide\0");
|
|
3361
|
+
hash.update(provide.type).update("\0");
|
|
3362
|
+
hash.update(provide.path.replaceAll("\\", "/")).update("\0");
|
|
3363
|
+
hash.update(await hashPath(full)).update("\0");
|
|
3364
|
+
}
|
|
3365
|
+
return hash.digest("hex");
|
|
3366
|
+
}
|
|
2546
3367
|
async function firstExisting(paths) {
|
|
2547
3368
|
for (const path of paths) {
|
|
2548
3369
|
if (await pathExists(path)) return path;
|
|
@@ -2557,27 +3378,27 @@ async function listFromManifest(root, packageName) {
|
|
|
2557
3378
|
if (!manifest) return [];
|
|
2558
3379
|
const artifacts = [];
|
|
2559
3380
|
for (const provide of manifest.provides) {
|
|
2560
|
-
const full =
|
|
3381
|
+
const full = join11(root, provide.path);
|
|
2561
3382
|
if (!await pathExists(full)) continue;
|
|
2562
3383
|
const stats = await stat3(full);
|
|
2563
3384
|
if (provide.type === "instructions") {
|
|
2564
3385
|
if (stats.isFile()) {
|
|
2565
|
-
artifacts.push(await artifactForFile(provide.type,
|
|
3386
|
+
artifacts.push(await artifactForFile(provide.type, basename6(full), full, provide.path, packageName, provide, manifest, basename6(full)));
|
|
2566
3387
|
}
|
|
2567
3388
|
continue;
|
|
2568
3389
|
}
|
|
2569
3390
|
if (stats.isDirectory()) {
|
|
2570
3391
|
for (const entry of await sortedDirEntries(full)) {
|
|
2571
|
-
const child =
|
|
3392
|
+
const child = join11(full, entry.name);
|
|
2572
3393
|
if ((provide.type === "skills" || provide.type === "plugins" || provide.type === "subagents") && entry.isDirectory()) {
|
|
2573
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child,
|
|
3394
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join11(provide.path, entry.name), packageName, provide, manifest, entry.name));
|
|
2574
3395
|
} else if (entry.isFile()) {
|
|
2575
3396
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
2576
|
-
artifacts.push(await artifactForFile(provide.type, name, child,
|
|
3397
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join11(provide.path, entry.name), packageName, provide, manifest, name));
|
|
2577
3398
|
}
|
|
2578
3399
|
}
|
|
2579
3400
|
} else if (stats.isFile()) {
|
|
2580
|
-
artifacts.push(await artifactForFile(provide.type,
|
|
3401
|
+
artifacts.push(await artifactForFile(provide.type, basename6(full), full, provide.path, packageName, provide, manifest, basename6(full)));
|
|
2581
3402
|
}
|
|
2582
3403
|
}
|
|
2583
3404
|
return artifacts;
|
|
@@ -2585,11 +3406,11 @@ async function listFromManifest(root, packageName) {
|
|
|
2585
3406
|
async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
2586
3407
|
const artifacts = [];
|
|
2587
3408
|
for (const entry of await sortedDirEntries(dir)) {
|
|
2588
|
-
const full =
|
|
3409
|
+
const full = join11(dir, entry.name);
|
|
2589
3410
|
if (entry.isDirectory()) {
|
|
2590
|
-
artifacts.push(await artifactForDir(type, entry.name, full,
|
|
3411
|
+
artifacts.push(await artifactForDir(type, entry.name, full, join11(relativeRoot, entry.name), packageName));
|
|
2591
3412
|
} else if (entry.isFile()) {
|
|
2592
|
-
artifacts.push(await artifactForFile(type, entry.name, full,
|
|
3413
|
+
artifacts.push(await artifactForFile(type, entry.name, full, join11(relativeRoot, entry.name), packageName));
|
|
2593
3414
|
}
|
|
2594
3415
|
}
|
|
2595
3416
|
return artifacts;
|
|
@@ -2603,6 +3424,7 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
|
|
|
2603
3424
|
relativePath,
|
|
2604
3425
|
kind: "file",
|
|
2605
3426
|
hash: await hashPath(sourcePath),
|
|
3427
|
+
format: item.format ?? provide?.format,
|
|
2606
3428
|
packageName,
|
|
2607
3429
|
channel: "managed",
|
|
2608
3430
|
assets: provide?.assets,
|
|
@@ -2621,6 +3443,7 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName,
|
|
|
2621
3443
|
relativePath,
|
|
2622
3444
|
kind: "dir",
|
|
2623
3445
|
hash: await hashPath(sourcePath),
|
|
3446
|
+
format: item.format ?? provide?.format,
|
|
2624
3447
|
packageName,
|
|
2625
3448
|
channel: "managed",
|
|
2626
3449
|
assets: provide?.assets,
|
|
@@ -2634,7 +3457,7 @@ function itemMetadata(provide, itemName) {
|
|
|
2634
3457
|
if (!provide || !("items" in provide) || !provide.items || !itemName) return {};
|
|
2635
3458
|
const item = provide.items[itemName];
|
|
2636
3459
|
if (!item) return {};
|
|
2637
|
-
return { requires: item.requires, compose: item.compose, runtimes: item.runtimes };
|
|
3460
|
+
return { format: item.format, requires: item.requires, compose: item.compose, runtimes: item.runtimes };
|
|
2638
3461
|
}
|
|
2639
3462
|
function provideRuntimes(provide) {
|
|
2640
3463
|
return provide && "runtimes" in provide ? provide.runtimes : void 0;
|
|
@@ -2644,7 +3467,7 @@ function manifestRuntimes(manifest) {
|
|
|
2644
3467
|
}
|
|
2645
3468
|
|
|
2646
3469
|
// src/source/git.ts
|
|
2647
|
-
var
|
|
3470
|
+
var execFileAsync4 = promisify4(execFile4);
|
|
2648
3471
|
var GitSourceDriver = class {
|
|
2649
3472
|
name = "git";
|
|
2650
3473
|
local = new LocalSourceDriver();
|
|
@@ -2665,8 +3488,8 @@ var GitSourceDriver = class {
|
|
|
2665
3488
|
async fetch(resolved) {
|
|
2666
3489
|
return withFilesystemLock(`${resolved.resolvedPath}.lock`, resolved.cacheLockTimeoutMs ?? 3e4, async () => {
|
|
2667
3490
|
const parsed = parseGitSource(resolved.source);
|
|
2668
|
-
await
|
|
2669
|
-
if (!await pathExists(
|
|
3491
|
+
await mkdir8(resolve6(resolved.resolvedPath, ".."), { recursive: true });
|
|
3492
|
+
if (!await pathExists(join12(resolved.resolvedPath, ".git"))) {
|
|
2670
3493
|
if (resolved.frozenLock) {
|
|
2671
3494
|
throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
|
|
2672
3495
|
}
|
|
@@ -2733,20 +3556,20 @@ function parseGitSource(source) {
|
|
|
2733
3556
|
throw new Error(`Invalid git source: ${source}`);
|
|
2734
3557
|
}
|
|
2735
3558
|
function cachePathFor(url, cacheRoot) {
|
|
2736
|
-
const root = cacheRoot ? resolve6(cacheRoot) :
|
|
3559
|
+
const root = cacheRoot ? resolve6(cacheRoot) : join12(homedir2(), ".agentwheel", "cache");
|
|
2737
3560
|
const slug2 = url.replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
|
|
2738
|
-
return
|
|
3561
|
+
return join12(root, slug2 || basename7(url));
|
|
2739
3562
|
}
|
|
2740
3563
|
async function git(args) {
|
|
2741
|
-
return
|
|
3564
|
+
return execFileAsync4("git", args, { maxBuffer: 1024 * 1024 * 10 });
|
|
2742
3565
|
}
|
|
2743
3566
|
async function snapshotCheckout(checkoutPath, commit) {
|
|
2744
|
-
const snapshotPath =
|
|
3567
|
+
const snapshotPath = join12(dirname9(checkoutPath), `${basename7(checkoutPath)}-${commit.slice(0, 12)}`);
|
|
2745
3568
|
if (await pathExists(snapshotPath)) return snapshotPath;
|
|
2746
|
-
const tempPath =
|
|
3569
|
+
const tempPath = join12(dirname9(checkoutPath), `${basename7(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
|
|
2747
3570
|
await rm4(tempPath, { recursive: true, force: true });
|
|
2748
3571
|
await cp2(checkoutPath, tempPath, { recursive: true, dereference: true });
|
|
2749
|
-
await rm4(
|
|
3572
|
+
await rm4(join12(tempPath, ".git"), { recursive: true, force: true });
|
|
2750
3573
|
try {
|
|
2751
3574
|
await rename3(tempPath, snapshotPath);
|
|
2752
3575
|
} catch (error) {
|
|
@@ -2757,12 +3580,12 @@ async function snapshotCheckout(checkoutPath, commit) {
|
|
|
2757
3580
|
return snapshotPath;
|
|
2758
3581
|
}
|
|
2759
3582
|
async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
2760
|
-
await
|
|
3583
|
+
await mkdir8(dirname9(lockPath), { recursive: true });
|
|
2761
3584
|
const started = Date.now();
|
|
2762
3585
|
while (true) {
|
|
2763
3586
|
try {
|
|
2764
|
-
await
|
|
2765
|
-
await
|
|
3587
|
+
await mkdir8(lockPath);
|
|
3588
|
+
await writeFile9(join12(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
2766
3589
|
break;
|
|
2767
3590
|
} catch (error) {
|
|
2768
3591
|
if (!isAlreadyExists2(error)) throw error;
|
|
@@ -2783,14 +3606,14 @@ function isAlreadyExists2(error) {
|
|
|
2783
3606
|
}
|
|
2784
3607
|
|
|
2785
3608
|
// src/source/skillkit.ts
|
|
2786
|
-
import { cp as cp3, mkdir as
|
|
2787
|
-
import { homedir as
|
|
2788
|
-
import { basename as
|
|
3609
|
+
import { cp as cp3, mkdir as mkdir9, readFile as readFile11, rm as rm5 } from "fs/promises";
|
|
3610
|
+
import { homedir as homedir3 } from "os";
|
|
3611
|
+
import { basename as basename9, dirname as dirname11, join as join14, resolve as resolve7 } from "path";
|
|
2789
3612
|
import * as defaultSkillKit from "@skillkit/core";
|
|
2790
3613
|
|
|
2791
3614
|
// src/source/skill-artifacts.ts
|
|
2792
3615
|
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
2793
|
-
import { basename as
|
|
3616
|
+
import { basename as basename8, dirname as dirname10, extname as extname2, join as join13 } from "path";
|
|
2794
3617
|
async function artifactsFromSkillPaths(paths, packageName) {
|
|
2795
3618
|
const artifacts = [];
|
|
2796
3619
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -2812,28 +3635,28 @@ async function discoverSkillPaths(root) {
|
|
|
2812
3635
|
async function artifactFromSkillPath(item, packageName) {
|
|
2813
3636
|
const stats = await stat4(item.path);
|
|
2814
3637
|
if (stats.isDirectory()) {
|
|
2815
|
-
const skillMd =
|
|
3638
|
+
const skillMd = join13(item.path, "SKILL.md");
|
|
2816
3639
|
if (!await pathExists(skillMd)) return void 0;
|
|
2817
|
-
const name = sanitizeSkillName(item.name ??
|
|
3640
|
+
const name = sanitizeSkillName(item.name ?? basename8(item.path));
|
|
2818
3641
|
return {
|
|
2819
3642
|
type: "skills",
|
|
2820
3643
|
name,
|
|
2821
3644
|
sourcePath: item.path,
|
|
2822
|
-
relativePath:
|
|
3645
|
+
relativePath: join13("skills", name),
|
|
2823
3646
|
kind: "dir",
|
|
2824
3647
|
hash: await hashPath(item.path),
|
|
2825
3648
|
packageName,
|
|
2826
3649
|
channel: "managed"
|
|
2827
3650
|
};
|
|
2828
3651
|
}
|
|
2829
|
-
if (stats.isFile() &&
|
|
2830
|
-
const dir =
|
|
2831
|
-
const name = sanitizeSkillName(item.name ??
|
|
3652
|
+
if (stats.isFile() && basename8(item.path).toLowerCase() === "skill.md") {
|
|
3653
|
+
const dir = dirname10(item.path);
|
|
3654
|
+
const name = sanitizeSkillName(item.name ?? basename8(dir));
|
|
2832
3655
|
return {
|
|
2833
3656
|
type: "skills",
|
|
2834
3657
|
name,
|
|
2835
3658
|
sourcePath: dir,
|
|
2836
|
-
relativePath:
|
|
3659
|
+
relativePath: join13("skills", name),
|
|
2837
3660
|
kind: "dir",
|
|
2838
3661
|
hash: await hashPath(dir),
|
|
2839
3662
|
packageName,
|
|
@@ -2841,12 +3664,12 @@ async function artifactFromSkillPath(item, packageName) {
|
|
|
2841
3664
|
};
|
|
2842
3665
|
}
|
|
2843
3666
|
if (stats.isFile() && extname2(item.path).toLowerCase() === ".md") {
|
|
2844
|
-
const name = sanitizeSkillName(item.name ??
|
|
3667
|
+
const name = sanitizeSkillName(item.name ?? basename8(item.path, ".md"));
|
|
2845
3668
|
return {
|
|
2846
3669
|
type: "skills",
|
|
2847
3670
|
name,
|
|
2848
3671
|
sourcePath: item.path,
|
|
2849
|
-
relativePath:
|
|
3672
|
+
relativePath: join13("skills", `${name}.md`),
|
|
2850
3673
|
kind: "file",
|
|
2851
3674
|
hash: await hashPath(item.path),
|
|
2852
3675
|
packageName,
|
|
@@ -2864,7 +3687,7 @@ async function walk(dir, paths) {
|
|
|
2864
3687
|
}
|
|
2865
3688
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2866
3689
|
if (!entry.isDirectory() || entry.name === ".git" || entry.name === "node_modules") continue;
|
|
2867
|
-
await walk(
|
|
3690
|
+
await walk(join13(dir, entry.name), paths);
|
|
2868
3691
|
}
|
|
2869
3692
|
}
|
|
2870
3693
|
function sanitizeSkillName(name) {
|
|
@@ -2886,7 +3709,7 @@ var SkillKitSourceDriver = class {
|
|
|
2886
3709
|
driver: this.name,
|
|
2887
3710
|
source,
|
|
2888
3711
|
resolvedPath,
|
|
2889
|
-
packageName: `skillkit/${
|
|
3712
|
+
packageName: `skillkit/${basename9(resolvedPath)}`,
|
|
2890
3713
|
mode: options.mode ?? "pinned",
|
|
2891
3714
|
sourceHash: await hashPath(resolvedPath)
|
|
2892
3715
|
};
|
|
@@ -2920,7 +3743,7 @@ var SkillKitSourceDriver = class {
|
|
|
2920
3743
|
if (!provider?.clone) {
|
|
2921
3744
|
throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
|
|
2922
3745
|
}
|
|
2923
|
-
await
|
|
3746
|
+
await mkdir9(dirname11(resolved.resolvedPath), { recursive: true });
|
|
2924
3747
|
const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
|
|
2925
3748
|
if (!result.success || !result.path) {
|
|
2926
3749
|
throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
|
|
@@ -2964,9 +3787,9 @@ var SkillKitSourceDriver = class {
|
|
|
2964
3787
|
throw new Error("SkillKit translateSkill API unavailable");
|
|
2965
3788
|
}
|
|
2966
3789
|
for (const skill of this.discover(resolved.resolvedPath)) {
|
|
2967
|
-
const skillMd =
|
|
3790
|
+
const skillMd = join14(skill.path, "SKILL.md");
|
|
2968
3791
|
if (await pathExists(skillMd)) {
|
|
2969
|
-
this.core.translateSkill(await
|
|
3792
|
+
this.core.translateSkill(await readFile11(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
|
|
2970
3793
|
}
|
|
2971
3794
|
}
|
|
2972
3795
|
return resolved;
|
|
@@ -2995,8 +3818,8 @@ function normalizeProviderSource(spec) {
|
|
|
2995
3818
|
return spec;
|
|
2996
3819
|
}
|
|
2997
3820
|
function cachePathFor2(spec, cacheRoot) {
|
|
2998
|
-
const root = cacheRoot ? resolve7(cacheRoot) :
|
|
2999
|
-
return
|
|
3821
|
+
const root = cacheRoot ? resolve7(cacheRoot) : join14(homedir3(), ".agentwheel", "cache");
|
|
3822
|
+
return join14(root, "skillkit", packageSlug(spec));
|
|
3000
3823
|
}
|
|
3001
3824
|
function packageSlug(spec) {
|
|
3002
3825
|
return spec.replace(/^[a-z]+:\/\//i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "source";
|
|
@@ -3009,7 +3832,7 @@ function mapSeverity(severity) {
|
|
|
3009
3832
|
|
|
3010
3833
|
// src/source/vercel-skills.ts
|
|
3011
3834
|
import { stat as stat5 } from "fs/promises";
|
|
3012
|
-
import { basename as
|
|
3835
|
+
import { basename as basename10, join as join15, resolve as resolve8 } from "path";
|
|
3013
3836
|
var VercelSkillsSourceDriver = class {
|
|
3014
3837
|
name = "vercel-skills";
|
|
3015
3838
|
git = new GitSourceDriver();
|
|
@@ -3024,7 +3847,7 @@ var VercelSkillsSourceDriver = class {
|
|
|
3024
3847
|
driver: this.name,
|
|
3025
3848
|
source,
|
|
3026
3849
|
resolvedPath,
|
|
3027
|
-
packageName: `vercel/${
|
|
3850
|
+
packageName: `vercel/${basename10(resolvedPath)}`,
|
|
3028
3851
|
mode: options.mode ?? "pinned",
|
|
3029
3852
|
sourceHash: await hashPath(resolvedPath)
|
|
3030
3853
|
};
|
|
@@ -3045,7 +3868,7 @@ var VercelSkillsSourceDriver = class {
|
|
|
3045
3868
|
driver: "git",
|
|
3046
3869
|
source: parsed.gitSource
|
|
3047
3870
|
});
|
|
3048
|
-
const resolvedPath = parsed.subpath ?
|
|
3871
|
+
const resolvedPath = parsed.subpath ? join15(fetched.resolvedPath, parsed.subpath) : fetched.resolvedPath;
|
|
3049
3872
|
if (!await pathExists(resolvedPath)) {
|
|
3050
3873
|
throw new Error(`Vercel skills subpath not found: ${parsed.subpath}`);
|
|
3051
3874
|
}
|
|
@@ -3134,14 +3957,14 @@ function getSourceDriver(name = "local") {
|
|
|
3134
3957
|
}
|
|
3135
3958
|
|
|
3136
3959
|
// src/staging/staging.ts
|
|
3137
|
-
import { chmod, cp as cp5, mkdir as
|
|
3138
|
-
import { basename as
|
|
3960
|
+
import { chmod, cp as cp5, mkdir as mkdir11, mkdtemp as mkdtemp2, readdir as readdir5, stat as stat8 } from "fs/promises";
|
|
3961
|
+
import { basename as basename13, dirname as dirname14, join as join18, relative as relative5, resolve as resolve10, sep as sep2 } from "path";
|
|
3139
3962
|
import { tmpdir as tmpdir3 } from "os";
|
|
3140
3963
|
|
|
3141
3964
|
// src/compose/markdown.ts
|
|
3142
|
-
import { createHash as
|
|
3143
|
-
import { readdir as readdir3, readFile as
|
|
3144
|
-
import { basename as
|
|
3965
|
+
import { createHash as createHash4 } from "crypto";
|
|
3966
|
+
import { readdir as readdir3, readFile as readFile12, stat as stat6, writeFile as writeFile10 } from "fs/promises";
|
|
3967
|
+
import { basename as basename11, dirname as dirname12, extname as extname3, join as join16, relative as relative4, resolve as resolve9, sep } from "path";
|
|
3145
3968
|
var includePattern = /<!--\s*openpack:include(\?)?\s+([^>]+?)\s*-->/g;
|
|
3146
3969
|
var escapedIncludePattern = /<!--\s*openpack\\:include(\?)?\s+([^>]+?)\s*-->/g;
|
|
3147
3970
|
var generatedPattern = /<!--\s*(?:BEGIN|END)\s+openpack:include\b/;
|
|
@@ -3156,7 +3979,7 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
|
|
|
3156
3979
|
const composedFrom = [];
|
|
3157
3980
|
for (const file of files) {
|
|
3158
3981
|
const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
|
|
3159
|
-
if (result.changed) await
|
|
3982
|
+
if (result.changed) await writeFile10(file, result.content, "utf8");
|
|
3160
3983
|
composedFrom.push(...result.composedFrom);
|
|
3161
3984
|
}
|
|
3162
3985
|
const stagedPath = artifact.stagedPath ?? artifact.sourcePath;
|
|
@@ -3178,7 +4001,7 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
|
|
|
3178
4001
|
}
|
|
3179
4002
|
}
|
|
3180
4003
|
async function expandFile(file, packageRoot, appendEntries, artifactPaths, options) {
|
|
3181
|
-
const raw = await
|
|
4004
|
+
const raw = await readFile12(file, "utf8");
|
|
3182
4005
|
const owner = ownerSelector(packageRoot, file, options.nodeId);
|
|
3183
4006
|
const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
|
|
3184
4007
|
let content = expanded.content;
|
|
@@ -3271,7 +4094,7 @@ async function expandInclude(selector, packageRoot, artifactPaths, options) {
|
|
|
3271
4094
|
if (!stats.isFile()) {
|
|
3272
4095
|
throw new Error(`OpenPack include is not a file: ${displaySelector}`);
|
|
3273
4096
|
}
|
|
3274
|
-
const raw = sourceContent ?? await
|
|
4097
|
+
const raw = sourceContent ?? await readFile12(sourcePath, "utf8");
|
|
3275
4098
|
const { optional: _optional, markers: _markers, chain: _chain, ...childOptions } = options;
|
|
3276
4099
|
const expanded = await expandContent(raw, includePackageRoot, [...options.chain, displaySelector], includeArtifactPaths, {
|
|
3277
4100
|
...childOptions,
|
|
@@ -3347,7 +4170,7 @@ async function listMarkdownFiles(root) {
|
|
|
3347
4170
|
const out = [];
|
|
3348
4171
|
async function walk2(dir) {
|
|
3349
4172
|
for (const entry of (await readdir3(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3350
|
-
const full =
|
|
4173
|
+
const full = join16(dir, entry.name);
|
|
3351
4174
|
if (entry.isDirectory()) {
|
|
3352
4175
|
await walk2(full);
|
|
3353
4176
|
} else if (entry.isFile() && extname3(entry.name).toLowerCase() === ".md") {
|
|
@@ -3361,7 +4184,7 @@ async function listMarkdownFiles(root) {
|
|
|
3361
4184
|
function composeEntriesForFile(artifact, file) {
|
|
3362
4185
|
if (!artifact.compose?.length) return [];
|
|
3363
4186
|
if (artifact.kind === "file") return [resolve9(artifact.stagedPath ?? artifact.sourcePath), resolve9(file)].every(Boolean) && resolve9(artifact.stagedPath ?? artifact.sourcePath) === resolve9(file) ? artifact.compose : [];
|
|
3364
|
-
return
|
|
4187
|
+
return basename11(file) === "SKILL.md" && dirname12(file) === resolve9(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
|
|
3365
4188
|
}
|
|
3366
4189
|
function orderedForExpansion(artifacts) {
|
|
3367
4190
|
return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
|
|
@@ -3378,7 +4201,7 @@ function applyReplacements(content, replacements) {
|
|
|
3378
4201
|
return out + content.slice(cursor);
|
|
3379
4202
|
}
|
|
3380
4203
|
function relativeSelector(root, file) {
|
|
3381
|
-
return
|
|
4204
|
+
return relative4(root, file).replaceAll("\\", "/");
|
|
3382
4205
|
}
|
|
3383
4206
|
function ownerSelector(root, file, nodeId) {
|
|
3384
4207
|
const selector = relativeSelector(root, file);
|
|
@@ -3392,7 +4215,7 @@ function cleanSelector(value) {
|
|
|
3392
4215
|
return value.trim().replace(/\s+/g, " ");
|
|
3393
4216
|
}
|
|
3394
4217
|
function sha256(content) {
|
|
3395
|
-
return
|
|
4218
|
+
return createHash4("sha256").update(content).digest("hex");
|
|
3396
4219
|
}
|
|
3397
4220
|
function uniqueComposedFrom(entries) {
|
|
3398
4221
|
if (entries.length === 0) return [];
|
|
@@ -3407,8 +4230,8 @@ function artifactPathMap(artifacts) {
|
|
|
3407
4230
|
}
|
|
3408
4231
|
|
|
3409
4232
|
// src/staging/customize.ts
|
|
3410
|
-
import { cp as cp4, mkdir as
|
|
3411
|
-
import { dirname as
|
|
4233
|
+
import { cp as cp4, mkdir as mkdir10, readdir as readdir4, readFile as readFile13, writeFile as writeFile11 } from "fs/promises";
|
|
4234
|
+
import { dirname as dirname13, join as join17 } from "path";
|
|
3412
4235
|
async function applyCustomizations(artifacts, options) {
|
|
3413
4236
|
let next = [...artifacts];
|
|
3414
4237
|
next = await applyReplacements2(next, options, "override", installableArtifactTypes());
|
|
@@ -3424,16 +4247,16 @@ async function applyFragmentCustomizations(artifacts, options) {
|
|
|
3424
4247
|
return next.sort((a, b) => `${a.type}:${a.name}:${a.channel}`.localeCompare(`${b.type}:${b.name}:${b.channel}`));
|
|
3425
4248
|
}
|
|
3426
4249
|
async function applyInstructionOverlay(artifacts, options) {
|
|
3427
|
-
const overlayPath =
|
|
4250
|
+
const overlayPath = join17(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
|
|
3428
4251
|
if (!await pathExists(overlayPath)) return artifacts;
|
|
3429
4252
|
const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
|
|
3430
4253
|
if (index < 0) return artifacts;
|
|
3431
4254
|
const artifact = artifacts[index];
|
|
3432
|
-
const managed = await
|
|
3433
|
-
const local = await
|
|
3434
|
-
const composedPath =
|
|
3435
|
-
await
|
|
3436
|
-
await
|
|
4255
|
+
const managed = await readFile13(artifact.stagedPath ?? artifact.sourcePath, "utf8");
|
|
4256
|
+
const local = await readFile13(overlayPath, "utf8");
|
|
4257
|
+
const composedPath = join17(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
|
|
4258
|
+
await mkdir10(dirname13(composedPath), { recursive: true });
|
|
4259
|
+
await writeFile11(
|
|
3437
4260
|
composedPath,
|
|
3438
4261
|
[
|
|
3439
4262
|
"<!-- BEGIN agentwheel managed: upstream -->",
|
|
@@ -3459,19 +4282,19 @@ async function applyInstructionOverlay(artifacts, options) {
|
|
|
3459
4282
|
return [...artifacts.slice(0, index), updated, ...artifacts.slice(index + 1)];
|
|
3460
4283
|
}
|
|
3461
4284
|
async function applyAdditions(artifacts, options) {
|
|
3462
|
-
const additionsRoot =
|
|
3463
|
-
const rulesRoot =
|
|
4285
|
+
const additionsRoot = join17(options.workspaceRoot, ".agentwheel", "additions");
|
|
4286
|
+
const rulesRoot = join17(additionsRoot, "rules");
|
|
3464
4287
|
if (!await pathExists(rulesRoot)) return artifacts;
|
|
3465
4288
|
const additions = [];
|
|
3466
4289
|
for (const entry of await sortedDirEntries2(rulesRoot)) {
|
|
3467
|
-
const full =
|
|
4290
|
+
const full = join17(rulesRoot, entry.name);
|
|
3468
4291
|
if (!entry.isFile()) continue;
|
|
3469
4292
|
additions.push({
|
|
3470
4293
|
type: "rules",
|
|
3471
4294
|
name: entry.name,
|
|
3472
4295
|
sourcePath: full,
|
|
3473
4296
|
stagedPath: full,
|
|
3474
|
-
relativePath:
|
|
4297
|
+
relativePath: join17("additions", "rules", entry.name),
|
|
3475
4298
|
kind: "file",
|
|
3476
4299
|
hash: await hashPath(full),
|
|
3477
4300
|
packageName: options.packageName,
|
|
@@ -3495,17 +4318,17 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
|
|
|
3495
4318
|
);
|
|
3496
4319
|
}
|
|
3497
4320
|
for (const type of artifactTypes) {
|
|
3498
|
-
const typeRoot =
|
|
4321
|
+
const typeRoot = join17(root, type);
|
|
3499
4322
|
if (!await pathExists(typeRoot)) continue;
|
|
3500
4323
|
for (const entry of await sortedDirEntries2(typeRoot)) {
|
|
3501
4324
|
const artifactMapKey = `${type}:${entry.name}`;
|
|
3502
4325
|
if (seen.has(artifactMapKey)) continue;
|
|
3503
4326
|
seen.add(artifactMapKey);
|
|
3504
|
-
const full =
|
|
4327
|
+
const full = join17(typeRoot, entry.name);
|
|
3505
4328
|
const artifactKind = entry.isDirectory() ? "dir" : "file";
|
|
3506
4329
|
const existing = byKey.get(artifactMapKey);
|
|
3507
|
-
const stagedPath =
|
|
3508
|
-
await
|
|
4330
|
+
const stagedPath = join17(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
|
|
4331
|
+
await mkdir10(dirname13(stagedPath), { recursive: true });
|
|
3509
4332
|
await cp4(full, stagedPath, { recursive: artifactKind === "dir", dereference: true });
|
|
3510
4333
|
byKey.set(artifactMapKey, {
|
|
3511
4334
|
...existing,
|
|
@@ -3513,7 +4336,7 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
|
|
|
3513
4336
|
name: entry.name,
|
|
3514
4337
|
sourcePath: full,
|
|
3515
4338
|
stagedPath,
|
|
3516
|
-
relativePath: existing?.relativePath ??
|
|
4339
|
+
relativePath: existing?.relativePath ?? join17(type, entry.name),
|
|
3517
4340
|
kind: artifactKind,
|
|
3518
4341
|
hash: await hashPath(stagedPath),
|
|
3519
4342
|
packageName,
|
|
@@ -3528,13 +4351,13 @@ function replacementRoots(options, channel) {
|
|
|
3528
4351
|
const stateDir = channel === "override" ? "overrides" : "ejected";
|
|
3529
4352
|
const roots = [];
|
|
3530
4353
|
if (options.graphNodeId) {
|
|
3531
|
-
roots.push({ root:
|
|
4354
|
+
roots.push({ root: join17(options.workspaceRoot, ".agentwheel", stateDir, ...options.graphNodeId.split("/")), kind: "node" });
|
|
3532
4355
|
}
|
|
3533
4356
|
if (options.packageName && options.packageVersion) {
|
|
3534
|
-
roots.push({ root:
|
|
4357
|
+
roots.push({ root: join17(options.workspaceRoot, ".agentwheel", stateDir, ...`${options.packageName}@${options.packageVersion}`.split("/")), kind: "version" });
|
|
3535
4358
|
}
|
|
3536
4359
|
if (options.packageName) {
|
|
3537
|
-
roots.push({ root:
|
|
4360
|
+
roots.push({ root: join17(options.workspaceRoot, ".agentwheel", stateDir, ...options.packageName.split("/")), kind: "package" });
|
|
3538
4361
|
}
|
|
3539
4362
|
return roots;
|
|
3540
4363
|
}
|
|
@@ -3552,6 +4375,13 @@ async function sortedDirEntries2(path) {
|
|
|
3552
4375
|
function artifactSelectorKey(artifact) {
|
|
3553
4376
|
return `${artifact.type}/${artifact.name}`;
|
|
3554
4377
|
}
|
|
4378
|
+
function artifactSelectorAliases(artifact) {
|
|
4379
|
+
const primary = artifactSelectorKey(artifact);
|
|
4380
|
+
if (artifact.type !== "subagents") return [primary];
|
|
4381
|
+
const baseName = subagentBaseName(artifact.name);
|
|
4382
|
+
if (baseName === artifact.name) return [primary];
|
|
4383
|
+
return [primary, `subagents/${baseName}`];
|
|
4384
|
+
}
|
|
3555
4385
|
function normalizeArtifactSelectors(select, legacySkills) {
|
|
3556
4386
|
const selected = [
|
|
3557
4387
|
...select ?? [],
|
|
@@ -3564,12 +4394,12 @@ function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
|
3564
4394
|
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
3565
4395
|
if (!selected?.length) return artifacts;
|
|
3566
4396
|
const selectedSet = new Set(selected);
|
|
3567
|
-
const available = new Set(artifacts.
|
|
4397
|
+
const available = new Set(artifacts.flatMap(artifactSelectorAliases));
|
|
3568
4398
|
const missing = selected.filter((selector) => !available.has(selector));
|
|
3569
4399
|
if (missing.length > 0) {
|
|
3570
4400
|
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
3571
4401
|
}
|
|
3572
|
-
return artifacts.filter((artifact) => artifact.required || selectedSet.has(
|
|
4402
|
+
return artifacts.filter((artifact) => artifact.required || artifactSelectorAliases(artifact).some((selector) => selectedSet.has(selector)));
|
|
3573
4403
|
}
|
|
3574
4404
|
function splitSelectorList(value) {
|
|
3575
4405
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
@@ -3587,6 +4417,9 @@ function parseArtifactSelector(value) {
|
|
|
3587
4417
|
}
|
|
3588
4418
|
return `${parsedType.data}/${name}`;
|
|
3589
4419
|
}
|
|
4420
|
+
function subagentBaseName(name) {
|
|
4421
|
+
return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
|
|
4422
|
+
}
|
|
3590
4423
|
|
|
3591
4424
|
// src/staging/staging.ts
|
|
3592
4425
|
async function stageSource(driver, source, options = {}) {
|
|
@@ -3601,12 +4434,16 @@ async function stageResolvedSourceRaw(driver, resolved) {
|
|
|
3601
4434
|
return stageResolvedArtifactsRaw(resolved, artifacts);
|
|
3602
4435
|
}
|
|
3603
4436
|
async function stageResolvedArtifactsRaw(resolved, artifacts) {
|
|
3604
|
-
const root = await mkdtemp2(
|
|
4437
|
+
const root = await mkdtemp2(join18(tmpdir3(), "agentwheel-stage-"));
|
|
3605
4438
|
const stagedArtifacts = [];
|
|
3606
4439
|
for (const artifact of artifacts) {
|
|
3607
|
-
const stagedPath =
|
|
3608
|
-
await
|
|
3609
|
-
await cp5(artifact.sourcePath, stagedPath, {
|
|
4440
|
+
const stagedPath = join18(root, artifact.relativePath);
|
|
4441
|
+
await mkdir11(dirname14(stagedPath), { recursive: true });
|
|
4442
|
+
await cp5(artifact.sourcePath, stagedPath, {
|
|
4443
|
+
recursive: artifact.kind === "dir",
|
|
4444
|
+
dereference: true,
|
|
4445
|
+
filter: (path) => !isIgnoredGeneratedEntry(basename13(path))
|
|
4446
|
+
});
|
|
3610
4447
|
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
3611
4448
|
stagedArtifacts.push({
|
|
3612
4449
|
...artifact,
|
|
@@ -3633,12 +4470,14 @@ async function renderStagedBundle(bundle, options = {}) {
|
|
|
3633
4470
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, options.select, options.skills);
|
|
3634
4471
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(options.select, options.skills) ?? []);
|
|
3635
4472
|
const runtimeArtifacts = options.adapter ? filterArtifactsByRuntime(selectedArtifacts, options.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
3636
|
-
const
|
|
4473
|
+
const codexRenderedArtifacts = await renderCodexSubagents(runtimeArtifacts, root, options.adapter);
|
|
4474
|
+
const renderedArtifacts = await renderCopilotArtifacts(codexRenderedArtifacts, root, options.adapter);
|
|
4475
|
+
const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(renderedArtifacts, {
|
|
3637
4476
|
workspaceRoot: options.workspaceRoot,
|
|
3638
4477
|
adapter: options.adapter,
|
|
3639
4478
|
stageRoot: root,
|
|
3640
4479
|
packageName: resolved.packageName
|
|
3641
|
-
}) :
|
|
4480
|
+
}) : renderedArtifacts;
|
|
3642
4481
|
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3643
4482
|
return {
|
|
3644
4483
|
root,
|
|
@@ -3662,6 +4501,7 @@ async function renderStagedBundle(bundle, options = {}) {
|
|
|
3662
4501
|
relativePath: artifact.relativePath,
|
|
3663
4502
|
kind: artifact.kind,
|
|
3664
4503
|
hash: artifact.hash,
|
|
4504
|
+
format: artifact.format,
|
|
3665
4505
|
composedFrom: artifact.composedFrom
|
|
3666
4506
|
}))
|
|
3667
4507
|
}
|
|
@@ -3683,16 +4523,16 @@ async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
|
3683
4523
|
}
|
|
3684
4524
|
for (const asset of artifact.assets) {
|
|
3685
4525
|
const source = resolvePackagePath(packageRoot, asset.from);
|
|
3686
|
-
const dest =
|
|
4526
|
+
const dest = join18(stagedPath, asset.into);
|
|
3687
4527
|
await copyAsset(asset, source, dest);
|
|
3688
4528
|
}
|
|
3689
4529
|
}
|
|
3690
4530
|
async function copyAsset(asset, source, dest) {
|
|
3691
4531
|
const sourceStats = await stat8(source);
|
|
3692
4532
|
if (sourceStats.isFile()) {
|
|
3693
|
-
if (matchesAny(
|
|
3694
|
-
await
|
|
3695
|
-
await copyAssetFile(source,
|
|
4533
|
+
if (matchesAny(basename13(source), asset.include)) {
|
|
4534
|
+
await mkdir11(dest, { recursive: true });
|
|
4535
|
+
await copyAssetFile(source, join18(dest, basename13(source)), asset);
|
|
3696
4536
|
}
|
|
3697
4537
|
return;
|
|
3698
4538
|
}
|
|
@@ -3700,19 +4540,19 @@ async function copyAsset(asset, source, dest) {
|
|
|
3700
4540
|
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
3701
4541
|
}
|
|
3702
4542
|
if (!asset.include?.length) {
|
|
3703
|
-
await
|
|
4543
|
+
await mkdir11(dirname14(dest), { recursive: true });
|
|
3704
4544
|
await cp5(source, dest, { recursive: true, dereference: true });
|
|
3705
4545
|
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
3706
4546
|
return;
|
|
3707
4547
|
}
|
|
3708
4548
|
for (const file of await listFiles(source)) {
|
|
3709
|
-
const rel =
|
|
3710
|
-
if (!matchesAny(rel, asset.include) && !matchesAny(
|
|
3711
|
-
await copyAssetFile(file,
|
|
4549
|
+
const rel = relative5(source, file).replaceAll("\\", "/");
|
|
4550
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename13(file), asset.include)) continue;
|
|
4551
|
+
await copyAssetFile(file, join18(dest, rel), asset);
|
|
3712
4552
|
}
|
|
3713
4553
|
}
|
|
3714
4554
|
async function copyAssetFile(source, dest, asset) {
|
|
3715
|
-
await
|
|
4555
|
+
await mkdir11(dirname14(dest), { recursive: true });
|
|
3716
4556
|
await cp5(source, dest, { dereference: true });
|
|
3717
4557
|
if (asset.mode === "copy") await chmod(dest, 420);
|
|
3718
4558
|
}
|
|
@@ -3728,7 +4568,7 @@ async function listFiles(root) {
|
|
|
3728
4568
|
const out = [];
|
|
3729
4569
|
async function walk2(dir) {
|
|
3730
4570
|
for (const entry of (await readdir5(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3731
|
-
const full =
|
|
4571
|
+
const full = join18(dir, entry.name);
|
|
3732
4572
|
if (entry.isDirectory()) {
|
|
3733
4573
|
await walk2(full);
|
|
3734
4574
|
} else if (entry.isFile()) {
|
|
@@ -3747,7 +4587,7 @@ async function normalizeCopiedModes(path) {
|
|
|
3747
4587
|
}
|
|
3748
4588
|
if (!stats.isDirectory()) return;
|
|
3749
4589
|
for (const entry of await readdir5(path, { withFileTypes: true })) {
|
|
3750
|
-
await normalizeCopiedModes(
|
|
4590
|
+
await normalizeCopiedModes(join18(path, entry.name));
|
|
3751
4591
|
}
|
|
3752
4592
|
}
|
|
3753
4593
|
function matchesAny(path, patterns) {
|
|
@@ -3760,9 +4600,9 @@ function matchesGlob(path, pattern) {
|
|
|
3760
4600
|
}
|
|
3761
4601
|
|
|
3762
4602
|
// src/model/workspace.ts
|
|
3763
|
-
import { readFile as
|
|
3764
|
-
import { homedir as
|
|
3765
|
-
import { dirname as
|
|
4603
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
4604
|
+
import { homedir as homedir4 } from "os";
|
|
4605
|
+
import { dirname as dirname15, join as join19, resolve as resolve11 } from "path";
|
|
3766
4606
|
import { z as z6 } from "zod";
|
|
3767
4607
|
var workspacePackageSchema = z6.object({
|
|
3768
4608
|
name: z6.string().min(1),
|
|
@@ -3772,17 +4612,20 @@ var workspacePackageSchema = z6.object({
|
|
|
3772
4612
|
adapterConfig: z6.string().min(1).optional(),
|
|
3773
4613
|
adapterModule: z6.string().min(1).optional(),
|
|
3774
4614
|
adapterCodeHash: z6.string().min(16).optional(),
|
|
4615
|
+
installationType: installationTypeSchema.optional(),
|
|
3775
4616
|
mode: z6.enum(["pinned", "tracking"]).default("pinned"),
|
|
3776
4617
|
requestedRef: z6.string().min(1).optional(),
|
|
3777
4618
|
select: z6.array(z6.string().min(1)).optional(),
|
|
3778
4619
|
skills: z6.array(z6.string().min(1)).optional(),
|
|
3779
|
-
aliases: z6.record(z6.string(), z6.string().min(1)).optional()
|
|
4620
|
+
aliases: z6.record(z6.string(), z6.string().min(1)).optional(),
|
|
4621
|
+
overrides: z6.array(z6.string().min(1)).optional()
|
|
3780
4622
|
});
|
|
3781
4623
|
var workspaceProfileRuntimeSchema = z6.object({
|
|
3782
4624
|
agent: z6.string().min(1).optional(),
|
|
3783
4625
|
adapter: z6.string().min(1).default("openclaw"),
|
|
3784
4626
|
adapterConfig: z6.string().min(1).optional(),
|
|
3785
4627
|
adapterModule: z6.string().min(1).optional(),
|
|
4628
|
+
installationType: installationTypeSchema.optional(),
|
|
3786
4629
|
targetRoot: z6.string().min(1).optional(),
|
|
3787
4630
|
executePlugins: z6.boolean().optional()
|
|
3788
4631
|
});
|
|
@@ -3802,6 +4645,7 @@ var workspaceTrustSchema = z6.object({
|
|
|
3802
4645
|
var workspaceAgentSchema = z6.object({
|
|
3803
4646
|
adapter: z6.string().min(1),
|
|
3804
4647
|
root: z6.string().min(1),
|
|
4648
|
+
installationType: installationTypeSchema.optional(),
|
|
3805
4649
|
transport: z6.enum(["local", "ssh"]).default("local"),
|
|
3806
4650
|
host: z6.string().min(1).optional(),
|
|
3807
4651
|
user: z6.string().min(1).optional(),
|
|
@@ -3827,12 +4671,12 @@ var workspaceConfigSchema = z6.object({
|
|
|
3827
4671
|
agents: z6.record(z6.string(), workspaceAgentSchema).default({})
|
|
3828
4672
|
});
|
|
3829
4673
|
function workspaceConfigPath(workspaceRoot) {
|
|
3830
|
-
return
|
|
4674
|
+
return join19(workspaceRoot, ".agentwheel", "config.json");
|
|
3831
4675
|
}
|
|
3832
4676
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
3833
4677
|
const path = workspaceConfigPath(workspaceRoot);
|
|
3834
4678
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
3835
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
4679
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile14(path, "utf8")));
|
|
3836
4680
|
}
|
|
3837
4681
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
3838
4682
|
await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
|
|
@@ -3844,14 +4688,14 @@ function upsertPackage(config, entry) {
|
|
|
3844
4688
|
packages.sort((a, b) => a.name.localeCompare(b.name));
|
|
3845
4689
|
return { schemaVersion: 1, packages, bootstrapSkills: parsed.bootstrapSkills, registry: parsed.registry ?? {}, trust: parsed.trust ?? {}, profiles: parsed.profiles ?? {}, agents: parsed.agents ?? {} };
|
|
3846
4690
|
}
|
|
3847
|
-
function globalWorkspaceConfigPath(globalRoot =
|
|
3848
|
-
return
|
|
4691
|
+
function globalWorkspaceConfigPath(globalRoot = homedir4()) {
|
|
4692
|
+
return join19(globalRoot, ".agentwheel", "config.json");
|
|
3849
4693
|
}
|
|
3850
4694
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
3851
4695
|
let current = resolve11(start);
|
|
3852
4696
|
while (true) {
|
|
3853
4697
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
3854
|
-
const parent =
|
|
4698
|
+
const parent = dirname15(current);
|
|
3855
4699
|
if (parent === current) return resolve11(start);
|
|
3856
4700
|
current = parent;
|
|
3857
4701
|
}
|
|
@@ -3878,8 +4722,8 @@ function mergeWorkspaceConfig(global, project) {
|
|
|
3878
4722
|
});
|
|
3879
4723
|
}
|
|
3880
4724
|
function resolveConfigPath(path, baseRoot) {
|
|
3881
|
-
if (path.startsWith("~/")) return resolve11(
|
|
3882
|
-
if (path === "~") return
|
|
4725
|
+
if (path.startsWith("~/")) return resolve11(homedir4(), path.slice(2));
|
|
4726
|
+
if (path === "~") return homedir4();
|
|
3883
4727
|
return path.startsWith("/") ? resolve11(path) : resolve11(baseRoot, path);
|
|
3884
4728
|
}
|
|
3885
4729
|
function emptyWorkspaceConfig() {
|
|
@@ -3887,7 +4731,7 @@ function emptyWorkspaceConfig() {
|
|
|
3887
4731
|
}
|
|
3888
4732
|
async function readConfigPath(path) {
|
|
3889
4733
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
3890
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
4734
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile14(path, "utf8")));
|
|
3891
4735
|
}
|
|
3892
4736
|
function mergeWorkspaceTrust(global, project) {
|
|
3893
4737
|
return {
|
|
@@ -3902,23 +4746,23 @@ function sortedUnique2(values) {
|
|
|
3902
4746
|
}
|
|
3903
4747
|
|
|
3904
4748
|
// src/lifecycle/customization.ts
|
|
3905
|
-
import { appendFile, cp as cp6, mkdir as
|
|
3906
|
-
import { dirname as
|
|
4749
|
+
import { appendFile, cp as cp6, mkdir as mkdir12, rm as rm7 } from "fs/promises";
|
|
4750
|
+
import { dirname as dirname17, join as join22 } from "path";
|
|
3907
4751
|
|
|
3908
4752
|
// src/resolve/graph.ts
|
|
3909
|
-
import { createHash as
|
|
3910
|
-
import { mkdtemp as mkdtemp3, readdir as readdir6, readFile as
|
|
4753
|
+
import { createHash as createHash5 } from "crypto";
|
|
4754
|
+
import { mkdtemp as mkdtemp3, readdir as readdir6, readFile as readFile16, stat as stat10 } from "fs/promises";
|
|
3911
4755
|
import { tmpdir as tmpdir4 } from "os";
|
|
3912
|
-
import { basename as
|
|
4756
|
+
import { basename as basename14, extname as extname4, join as join21 } from "path";
|
|
3913
4757
|
|
|
3914
4758
|
// src/resolve/identity.ts
|
|
3915
|
-
import { homedir as
|
|
4759
|
+
import { homedir as homedir6 } from "os";
|
|
3916
4760
|
import { resolve as resolve13 } from "path";
|
|
3917
4761
|
|
|
3918
4762
|
// src/registry/client.ts
|
|
3919
|
-
import { readFile as
|
|
3920
|
-
import { homedir as
|
|
3921
|
-
import { dirname as
|
|
4763
|
+
import { readFile as readFile15, rm as rm6, stat as stat9 } from "fs/promises";
|
|
4764
|
+
import { homedir as homedir5 } from "os";
|
|
4765
|
+
import { dirname as dirname16, join as join20, resolve as resolve12 } from "path";
|
|
3922
4766
|
import { fileURLToPath } from "url";
|
|
3923
4767
|
|
|
3924
4768
|
// src/model/registry.ts
|
|
@@ -4016,7 +4860,7 @@ var RegistryClient = class {
|
|
|
4016
4860
|
}
|
|
4017
4861
|
async readCache() {
|
|
4018
4862
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
4019
|
-
return registryCacheSchema.parse(JSON.parse(await
|
|
4863
|
+
return registryCacheSchema.parse(JSON.parse(await readFile15(this.cachePath, "utf8")));
|
|
4020
4864
|
}
|
|
4021
4865
|
isExpired(cache, ttlMs) {
|
|
4022
4866
|
return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
|
|
@@ -4035,10 +4879,10 @@ var RegistryClient = class {
|
|
|
4035
4879
|
if (await pathExists(filePath)) {
|
|
4036
4880
|
const fullPath = resolve12(filePath);
|
|
4037
4881
|
const stats = await stat9(fullPath);
|
|
4038
|
-
return
|
|
4882
|
+
return readFile15(stats.isDirectory() ? join20(fullPath, "index.json") : fullPath, "utf8");
|
|
4039
4883
|
}
|
|
4040
|
-
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot:
|
|
4041
|
-
return
|
|
4884
|
+
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join20(dirname16(this.cachePath), "registry-repos") }));
|
|
4885
|
+
return readFile15(join20(resolved.resolvedPath, "index.json"), "utf8");
|
|
4042
4886
|
}
|
|
4043
4887
|
warnCompatibility(entries) {
|
|
4044
4888
|
for (const entry of entries) {
|
|
@@ -4052,10 +4896,13 @@ var RegistryClient = class {
|
|
|
4052
4896
|
}
|
|
4053
4897
|
};
|
|
4054
4898
|
async function resolvePackageSource(source, workspaceRoot, options = {}) {
|
|
4055
|
-
const { isExplicitSource } = await import("./identify-
|
|
4899
|
+
const { isExplicitSource } = await import("./identify-TXIDGMNL.js");
|
|
4056
4900
|
if (await isExplicitSource(source)) return { source };
|
|
4057
4901
|
const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
|
|
4058
4902
|
if (!entry) {
|
|
4903
|
+
if (options.offline) {
|
|
4904
|
+
throw new Error(`Offline cannot refresh registry indexes (entry not found in cache: ${source}). Run without --offline first.`);
|
|
4905
|
+
}
|
|
4059
4906
|
throw new Error(`Registry entry not found: ${source}. Use an explicit path/git/skillkit/vercel source to bypass the registry.`);
|
|
4060
4907
|
}
|
|
4061
4908
|
return { source: entry.source, registryEntry: entry };
|
|
@@ -4070,7 +4917,7 @@ function mergeIndexes(indexes) {
|
|
|
4070
4917
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
4071
4918
|
}
|
|
4072
4919
|
function defaultRegistryCachePath() {
|
|
4073
|
-
return
|
|
4920
|
+
return join20(homedir5(), ".agentwheel", "registry-cache.json");
|
|
4074
4921
|
}
|
|
4075
4922
|
function sameSources(a, b) {
|
|
4076
4923
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -4139,8 +4986,8 @@ function localSourcePath(source) {
|
|
|
4139
4986
|
return source.startsWith("local:") ? source.slice("local:".length) : source;
|
|
4140
4987
|
}
|
|
4141
4988
|
function resolveLocalPath(path, declaringPackageRoot) {
|
|
4142
|
-
if (path === "~") return
|
|
4143
|
-
if (path.startsWith("~/")) return resolve13(
|
|
4989
|
+
if (path === "~") return homedir6();
|
|
4990
|
+
if (path.startsWith("~/")) return resolve13(homedir6(), path.slice(2));
|
|
4144
4991
|
if (path.startsWith("/")) return resolve13(path);
|
|
4145
4992
|
return resolve13(declaringPackageRoot, path);
|
|
4146
4993
|
}
|
|
@@ -4299,7 +5146,7 @@ function compareSemver(a, b) {
|
|
|
4299
5146
|
var cacheLocks = /* @__PURE__ */ new Map();
|
|
4300
5147
|
async function resolveDependencyGraph(roots, options) {
|
|
4301
5148
|
if (roots.length === 0) throw new Error("At least one graph root is required.");
|
|
4302
|
-
const graphRoot = await mkdtemp3(
|
|
5149
|
+
const graphRoot = await mkdtemp3(join21(tmpdir4(), "agentwheel-graph-"));
|
|
4303
5150
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
4304
5151
|
const nodesByKey = /* @__PURE__ */ new Map();
|
|
4305
5152
|
const rootResults = [];
|
|
@@ -4315,6 +5162,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
4315
5162
|
requiredBy: `workspace:${rootId}`,
|
|
4316
5163
|
rootId,
|
|
4317
5164
|
aliases: root.aliases,
|
|
5165
|
+
overrides: root.overrides,
|
|
4318
5166
|
useLock: root.useLock ?? options.lockedResolution,
|
|
4319
5167
|
depth: 0,
|
|
4320
5168
|
optional: false,
|
|
@@ -4346,7 +5194,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
4346
5194
|
generatedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString()
|
|
4347
5195
|
};
|
|
4348
5196
|
}
|
|
4349
|
-
function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = []) {
|
|
5197
|
+
function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges = [], namespacing = [], overrides = []) {
|
|
4350
5198
|
const roots = graph.roots.map((root) => ({
|
|
4351
5199
|
rootId: root.rootId,
|
|
4352
5200
|
source: root.source,
|
|
@@ -4354,7 +5202,8 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4354
5202
|
graphNodeId: root.graphNodeId,
|
|
4355
5203
|
mode: root.mode,
|
|
4356
5204
|
selected: root.selected,
|
|
4357
|
-
aliases: root.aliases
|
|
5205
|
+
aliases: root.aliases,
|
|
5206
|
+
overrides: root.overrides
|
|
4358
5207
|
}));
|
|
4359
5208
|
return {
|
|
4360
5209
|
version: 1,
|
|
@@ -4366,6 +5215,7 @@ function createGraphLock(graph, artifacts = [], targetFingerprint, includeEdges
|
|
|
4366
5215
|
includeEdges,
|
|
4367
5216
|
artifacts,
|
|
4368
5217
|
namespacing,
|
|
5218
|
+
overrides,
|
|
4369
5219
|
plainNameIncumbents: []
|
|
4370
5220
|
}
|
|
4371
5221
|
};
|
|
@@ -4462,7 +5312,8 @@ Run without ${lockLabel === "Offline" ? "--offline" : "--frozen-lock"} first.`
|
|
|
4462
5312
|
graphNodeId: state.node.id,
|
|
4463
5313
|
mode: state.node.mode,
|
|
4464
5314
|
selected: state.node.selected,
|
|
4465
|
-
aliases: requirement.aliases
|
|
5315
|
+
aliases: requirement.aliases,
|
|
5316
|
+
overrides: requirement.overrides
|
|
4466
5317
|
});
|
|
4467
5318
|
}
|
|
4468
5319
|
if (requirement.parentId && requirement.alias) {
|
|
@@ -4657,7 +5508,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
|
|
|
4657
5508
|
const file = stack.shift();
|
|
4658
5509
|
if (scanned.has(file)) continue;
|
|
4659
5510
|
scanned.add(file);
|
|
4660
|
-
const content = await
|
|
5511
|
+
const content = await readFile16(file, "utf8");
|
|
4661
5512
|
for (const include of extractOpenPackIncludeSelectors(content)) {
|
|
4662
5513
|
await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
|
|
4663
5514
|
}
|
|
@@ -4700,7 +5551,7 @@ async function listMarkdownFiles2(root) {
|
|
|
4700
5551
|
const out = [];
|
|
4701
5552
|
async function walk2(dir) {
|
|
4702
5553
|
for (const entry of (await readdir6(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4703
|
-
const full =
|
|
5554
|
+
const full = join21(dir, entry.name);
|
|
4704
5555
|
if (entry.isDirectory()) {
|
|
4705
5556
|
await walk2(full);
|
|
4706
5557
|
} else if (entry.isFile() && extname4(entry.name).toLowerCase() === ".md") {
|
|
@@ -4735,7 +5586,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
4735
5586
|
const promise = (async () => {
|
|
4736
5587
|
const driver = getSourceDriver(normalized.driver);
|
|
4737
5588
|
const resolved = await driver.resolve(normalized.source, {
|
|
4738
|
-
cacheRoot: options.cacheRoot ??
|
|
5589
|
+
cacheRoot: options.cacheRoot ?? join21(options.workspaceRoot, ".agentwheel", "cache"),
|
|
4739
5590
|
mode,
|
|
4740
5591
|
ref: refOverride ?? normalized.requestedRef,
|
|
4741
5592
|
frozenLock: hardLockedCheckout
|
|
@@ -4745,7 +5596,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
4745
5596
|
const exported = await driver.export(translated);
|
|
4746
5597
|
const manifest = await readPackageManifest(exported.resolvedPath);
|
|
4747
5598
|
const artifacts = await driver.list(exported);
|
|
4748
|
-
const name = manifest?.name ?? exported.packageName ??
|
|
5599
|
+
const name = manifest?.name ?? exported.packageName ?? basename14(exported.resolvedPath);
|
|
4749
5600
|
const version = manifest?.version ?? exported.packageVersion ?? "0.0.0";
|
|
4750
5601
|
const sourceHash = exported.sourceHash ?? await hashPath(exported.resolvedPath);
|
|
4751
5602
|
return {
|
|
@@ -4921,7 +5772,7 @@ function detectDirectCollisions(nodes) {
|
|
|
4921
5772
|
}
|
|
4922
5773
|
}
|
|
4923
5774
|
function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
|
|
4924
|
-
const digest =
|
|
5775
|
+
const digest = createHash5("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
|
|
4925
5776
|
return `${name}@${version}+${digest}`;
|
|
4926
5777
|
}
|
|
4927
5778
|
function sortedUnique3(values) {
|
|
@@ -4942,8 +5793,8 @@ async function mapLimit(items, limit, fn) {
|
|
|
4942
5793
|
|
|
4943
5794
|
// src/lifecycle/customization.ts
|
|
4944
5795
|
async function remember(workspaceRoot, runtime, text) {
|
|
4945
|
-
const overlayPath =
|
|
4946
|
-
await
|
|
5796
|
+
const overlayPath = join22(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
|
|
5797
|
+
await mkdir12(dirname17(overlayPath), { recursive: true });
|
|
4947
5798
|
await appendFile(overlayPath, `${text.trim()}
|
|
4948
5799
|
`, "utf8");
|
|
4949
5800
|
return { overlayPath };
|
|
@@ -4966,8 +5817,8 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
4966
5817
|
throw new Error(`Artifact not found: ${item}`);
|
|
4967
5818
|
}
|
|
4968
5819
|
const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
|
|
4969
|
-
const ejectedPath =
|
|
4970
|
-
await
|
|
5820
|
+
const ejectedPath = join22(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
|
|
5821
|
+
await mkdir12(dirname17(ejectedPath), { recursive: true });
|
|
4971
5822
|
await rm7(ejectedPath, { recursive: true, force: true });
|
|
4972
5823
|
await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
4973
5824
|
return {
|
|
@@ -5009,7 +5860,7 @@ async function stageEjectCandidate(workspaceRoot, pkg) {
|
|
|
5009
5860
|
const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
|
|
5010
5861
|
const bundle = await stageSource(driver, normalized.source, {
|
|
5011
5862
|
adapter,
|
|
5012
|
-
cacheRoot:
|
|
5863
|
+
cacheRoot: join22(workspaceRoot, ".agentwheel", "cache"),
|
|
5013
5864
|
mode: pkg.mode,
|
|
5014
5865
|
ref: normalized.requestedRef ?? pkg.requestedRef
|
|
5015
5866
|
});
|
|
@@ -5059,9 +5910,9 @@ function ejectCommands(candidates, parsed) {
|
|
|
5059
5910
|
import { rm as rm8 } from "fs/promises";
|
|
5060
5911
|
|
|
5061
5912
|
// src/lifecycle/source-plan.ts
|
|
5062
|
-
import { createHash as
|
|
5063
|
-
import { mkdir as
|
|
5064
|
-
import { dirname as
|
|
5913
|
+
import { createHash as createHash7 } from "crypto";
|
|
5914
|
+
import { mkdir as mkdir14 } from "fs/promises";
|
|
5915
|
+
import { dirname as dirname19, join as join25, resolve as resolve14 } from "path";
|
|
5065
5916
|
|
|
5066
5917
|
// src/resolve/graph-diff.ts
|
|
5067
5918
|
function diffGraphLocks(previous, next) {
|
|
@@ -5069,7 +5920,8 @@ function diffGraphLocks(previous, next) {
|
|
|
5069
5920
|
return [
|
|
5070
5921
|
...diffNodes(previous.canonical.nodes, next.canonical.nodes),
|
|
5071
5922
|
...diffIncludeEdges(previous.canonical.includeEdges, next.canonical.includeEdges),
|
|
5072
|
-
...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing)
|
|
5923
|
+
...diffNamespacing(previous.canonical.namespacing, next.canonical.namespacing),
|
|
5924
|
+
...diffOverrides(previous.canonical.overrides, next.canonical.overrides)
|
|
5073
5925
|
];
|
|
5074
5926
|
}
|
|
5075
5927
|
function diffNodes(previous, next) {
|
|
@@ -5131,6 +5983,23 @@ function diffNamespacing(previous, next) {
|
|
|
5131
5983
|
}
|
|
5132
5984
|
return lines.sort((a, b) => a.localeCompare(b));
|
|
5133
5985
|
}
|
|
5986
|
+
function diffOverrides(previous, next) {
|
|
5987
|
+
const previousByKey = new Map(previous.map((decision) => [overrideKey(decision), decision]));
|
|
5988
|
+
const nextByKey = new Map(next.map((decision) => [overrideKey(decision), decision]));
|
|
5989
|
+
const lines = [];
|
|
5990
|
+
for (const [key, decision] of nextByKey) {
|
|
5991
|
+
const old = previousByKey.get(key);
|
|
5992
|
+
if (!old) {
|
|
5993
|
+
lines.push(`ADDED override ${formatOverride(decision)}`);
|
|
5994
|
+
} else if (old.graphNodeId !== decision.graphNodeId || old.installName !== decision.installName) {
|
|
5995
|
+
lines.push(`CHANGED override ${decision.selector} ${old.graphNodeId}:${old.type}/${old.name} -> ${decision.graphNodeId}:${decision.type}/${decision.name}`);
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
for (const [key, decision] of previousByKey) {
|
|
5999
|
+
if (!nextByKey.has(key)) lines.push(`REMOVED override ${formatOverride(decision)}`);
|
|
6000
|
+
}
|
|
6001
|
+
return lines.sort((a, b) => a.localeCompare(b));
|
|
6002
|
+
}
|
|
5134
6003
|
function stableNodeKey(node) {
|
|
5135
6004
|
return `${node.normalizedSource}\0${node.name}`;
|
|
5136
6005
|
}
|
|
@@ -5150,20 +6019,26 @@ function formatIncludeEdge(edge) {
|
|
|
5150
6019
|
function namespaceKey(decision) {
|
|
5151
6020
|
return `${decision.graphNodeId}\0${decision.type}\0${decision.name}`;
|
|
5152
6021
|
}
|
|
6022
|
+
function overrideKey(decision) {
|
|
6023
|
+
return `${decision.rootId}\0${decision.selector}\0${decision.overriddenGraphNodeId}\0${decision.type}\0${decision.name}`;
|
|
6024
|
+
}
|
|
5153
6025
|
function formatNamespace(decision) {
|
|
5154
6026
|
return `${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`;
|
|
5155
6027
|
}
|
|
6028
|
+
function formatOverride(decision) {
|
|
6029
|
+
return `${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`;
|
|
6030
|
+
}
|
|
5156
6031
|
function short(hash) {
|
|
5157
6032
|
return hash.slice(0, 12);
|
|
5158
6033
|
}
|
|
5159
6034
|
|
|
5160
6035
|
// src/resolve/render.ts
|
|
5161
|
-
import { createHash as
|
|
5162
|
-
import { readFile as
|
|
6036
|
+
import { createHash as createHash6 } from "crypto";
|
|
6037
|
+
import { readFile as readFile17, mkdtemp as mkdtemp4 } from "fs/promises";
|
|
5163
6038
|
import { tmpdir as tmpdir5 } from "os";
|
|
5164
|
-
import { join as
|
|
6039
|
+
import { join as join23 } from "path";
|
|
5165
6040
|
async function renderGraphForTarget(graph, targetContext = {}) {
|
|
5166
|
-
const root = await mkdtemp4(
|
|
6041
|
+
const root = await mkdtemp4(join23(tmpdir5(), "agentwheel-render-"));
|
|
5167
6042
|
const artifacts = [];
|
|
5168
6043
|
const stagedNodes = /* @__PURE__ */ new Map();
|
|
5169
6044
|
const includeEdges = /* @__PURE__ */ new Map();
|
|
@@ -5235,7 +6110,9 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
5235
6110
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, rawNode.node.selected);
|
|
5236
6111
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
|
|
5237
6112
|
const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(selectedArtifacts, targetContext.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
5238
|
-
const
|
|
6113
|
+
const codexRenderedArtifacts = await renderCodexSubagents(runtimeArtifacts, staged.root, targetContext.adapter);
|
|
6114
|
+
const runtimeRenderedArtifacts = await renderCopilotArtifacts(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
6115
|
+
const renderedArtifacts = targetContext.workspaceRoot && targetContext.adapter ? await applyCustomizations(runtimeRenderedArtifacts, {
|
|
5239
6116
|
workspaceRoot: targetContext.workspaceRoot,
|
|
5240
6117
|
adapter: targetContext.adapter,
|
|
5241
6118
|
stageRoot: staged.root,
|
|
@@ -5243,7 +6120,7 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
5243
6120
|
packageVersion: rawNode.resolved.packageVersion,
|
|
5244
6121
|
graphNodeId: rawNode.node.id,
|
|
5245
6122
|
packageNameAmbiguous: ambiguousPackageNames.has(rawNode.node.name)
|
|
5246
|
-
}) :
|
|
6123
|
+
}) : runtimeRenderedArtifacts;
|
|
5247
6124
|
const installableArtifacts = renderedArtifacts.filter((artifact) => rawNode.depth === 0 || artifact.type !== "fragments");
|
|
5248
6125
|
artifacts.push(...installableArtifacts.map((artifact) => ({
|
|
5249
6126
|
...artifact,
|
|
@@ -5254,13 +6131,13 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
5254
6131
|
owners: [...rawNode.node.requiredBy].sort((a, b) => a.localeCompare(b))
|
|
5255
6132
|
})));
|
|
5256
6133
|
}
|
|
5257
|
-
const { artifacts: namedArtifacts, namespacing } = assignInstallNames(graph, artifacts);
|
|
6134
|
+
const { artifacts: namedArtifacts, namespacing, overrides } = assignInstallNames(graph, artifacts);
|
|
5258
6135
|
const sortedArtifacts = namedArtifacts.sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
|
|
5259
6136
|
return {
|
|
5260
6137
|
root,
|
|
5261
6138
|
nodes: graph.nodes,
|
|
5262
6139
|
artifacts: sortedArtifacts,
|
|
5263
|
-
graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing)
|
|
6140
|
+
graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing, overrides)
|
|
5264
6141
|
};
|
|
5265
6142
|
}
|
|
5266
6143
|
function aliasEdgeMap(graph) {
|
|
@@ -5277,7 +6154,7 @@ async function artifactContentMap(artifacts) {
|
|
|
5277
6154
|
const out = /* @__PURE__ */ new Map();
|
|
5278
6155
|
for (const artifact of artifacts) {
|
|
5279
6156
|
if (artifact.kind !== "file") continue;
|
|
5280
|
-
out.set(artifact.relativePath.replaceAll("\\", "/"), await
|
|
6157
|
+
out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile17(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
|
|
5281
6158
|
}
|
|
5282
6159
|
return out;
|
|
5283
6160
|
}
|
|
@@ -5291,7 +6168,7 @@ function filterArtifactsByRuntime2(artifacts, adapterName, selectedSet) {
|
|
|
5291
6168
|
});
|
|
5292
6169
|
}
|
|
5293
6170
|
function sha2562(content) {
|
|
5294
|
-
return
|
|
6171
|
+
return createHash6("sha256").update(content).digest("hex");
|
|
5295
6172
|
}
|
|
5296
6173
|
function assignInstallNames(graph, artifacts) {
|
|
5297
6174
|
const aliases = workspaceAliases(graph);
|
|
@@ -5304,7 +6181,8 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5304
6181
|
decisions.set(decisionKey(updated), namespaceDecision(updated, "alias"));
|
|
5305
6182
|
return updated;
|
|
5306
6183
|
});
|
|
5307
|
-
const
|
|
6184
|
+
const { artifacts: withOverrides, overrides } = applyWorkspaceOverrides(graph, withAliases);
|
|
6185
|
+
const collisionGroups = [...groupBy(withOverrides, (artifact) => `${artifact.type}\0${artifact.installName}`).values()].filter((group) => group.length > 1);
|
|
5308
6186
|
const toRename = /* @__PURE__ */ new Set();
|
|
5309
6187
|
for (const group of collisionGroups) {
|
|
5310
6188
|
if (group.some((artifact) => decisions.has(decisionKey(artifact)))) throw installNameCollisionError(group);
|
|
@@ -5315,11 +6193,11 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5315
6193
|
}
|
|
5316
6194
|
}
|
|
5317
6195
|
const used = /* @__PURE__ */ new Map();
|
|
5318
|
-
for (const artifact of
|
|
6196
|
+
for (const artifact of withOverrides) {
|
|
5319
6197
|
if (toRename.has(artifact)) continue;
|
|
5320
6198
|
used.set(`${artifact.type}\0${artifact.installName}`, artifact);
|
|
5321
6199
|
}
|
|
5322
|
-
const out =
|
|
6200
|
+
const out = withOverrides.filter((artifact) => !toRename.has(artifact));
|
|
5323
6201
|
for (const group of groupBy([...toRename], (artifact) => `${artifact.type}\0${artifact.name}`).values()) {
|
|
5324
6202
|
const renamed = namespaceTransitiveGroup(group, used);
|
|
5325
6203
|
for (const artifact of renamed) {
|
|
@@ -5328,7 +6206,52 @@ function assignInstallNames(graph, artifacts) {
|
|
|
5328
6206
|
out.push(artifact);
|
|
5329
6207
|
}
|
|
5330
6208
|
}
|
|
5331
|
-
|
|
6209
|
+
const finalInstallNames = new Map(out.map((artifact) => [decisionKey(artifact), artifact.installName]));
|
|
6210
|
+
const finalOverrides = overrides.map((override) => ({
|
|
6211
|
+
...override,
|
|
6212
|
+
installName: finalInstallNames.get(`${override.graphNodeId}\0${override.type}\0${override.name}`) ?? override.installName
|
|
6213
|
+
}));
|
|
6214
|
+
return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
|
|
6215
|
+
}
|
|
6216
|
+
function applyWorkspaceOverrides(graph, artifacts) {
|
|
6217
|
+
const directives = workspaceOverrides(graph);
|
|
6218
|
+
if (directives.length === 0) return { artifacts, overrides: [] };
|
|
6219
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
6220
|
+
let remaining = [...artifacts];
|
|
6221
|
+
const decisions = [];
|
|
6222
|
+
for (const directive of directives) {
|
|
6223
|
+
const matched = remaining.filter((artifact) => overrideMatchesArtifact(directive.selector, artifact, nodeById.get(artifact.graphNodeId)));
|
|
6224
|
+
const losers = matched.filter((artifact) => !directive.reachable.has(artifact.graphNodeId));
|
|
6225
|
+
if (matched.length === 0) {
|
|
6226
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} did not match any rendered artifact.`);
|
|
6227
|
+
}
|
|
6228
|
+
if (losers.length === 0) {
|
|
6229
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched only artifacts inside the replacing root.`);
|
|
6230
|
+
}
|
|
6231
|
+
if (losers.length > 1) {
|
|
6232
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} matched multiple artifacts: ${losers.map((artifact) => artifact.logicalSelector).sort().join(", ")}`);
|
|
6233
|
+
}
|
|
6234
|
+
const loser = losers[0];
|
|
6235
|
+
const winners = remaining.filter((artifact) => artifact !== loser && directive.reachable.has(artifact.graphNodeId) && artifact.type === loser.type && artifact.name === loser.name);
|
|
6236
|
+
if (winners.length === 0) {
|
|
6237
|
+
throw new Error(`Workspace override ${directive.selector} from ${directive.rootId} has no selected replacement for ${loser.type}/${loser.name}.`);
|
|
6238
|
+
}
|
|
6239
|
+
if (winners.length > 1) {
|
|
6240
|
+
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(", ")}`);
|
|
6241
|
+
}
|
|
6242
|
+
const winner = winners[0];
|
|
6243
|
+
remaining = remaining.filter((artifact) => artifact !== loser);
|
|
6244
|
+
decisions.push({
|
|
6245
|
+
rootId: directive.rootId,
|
|
6246
|
+
selector: directive.selector,
|
|
6247
|
+
graphNodeId: winner.graphNodeId,
|
|
6248
|
+
overriddenGraphNodeId: loser.graphNodeId,
|
|
6249
|
+
type: winner.type,
|
|
6250
|
+
name: winner.name,
|
|
6251
|
+
installName: winner.installName
|
|
6252
|
+
});
|
|
6253
|
+
}
|
|
6254
|
+
return { artifacts: remaining, overrides: decisions };
|
|
5332
6255
|
}
|
|
5333
6256
|
function namespaceTransitiveGroup(group, used) {
|
|
5334
6257
|
const sorted = [...group].sort((a, b) => a.logicalSelector.localeCompare(b.logicalSelector));
|
|
@@ -5366,6 +6289,16 @@ function workspaceAliases(graph) {
|
|
|
5366
6289
|
}
|
|
5367
6290
|
return aliases;
|
|
5368
6291
|
}
|
|
6292
|
+
function workspaceOverrides(graph) {
|
|
6293
|
+
const overrides = [];
|
|
6294
|
+
for (const root of graph.roots) {
|
|
6295
|
+
const reachable = reachableNodeIds(graph, root.graphNodeId);
|
|
6296
|
+
for (const selector of root.overrides ?? []) {
|
|
6297
|
+
overrides.push({ rootId: root.rootId, selector, reachable });
|
|
6298
|
+
}
|
|
6299
|
+
}
|
|
6300
|
+
return overrides;
|
|
6301
|
+
}
|
|
5369
6302
|
function validateAliasScopes(graph, artifacts, aliases) {
|
|
5370
6303
|
for (const alias of aliases) {
|
|
5371
6304
|
const matching = artifacts.filter((artifact) => {
|
|
@@ -5382,6 +6315,35 @@ function aliasMatchesArtifact(selector, artifact, node) {
|
|
|
5382
6315
|
const artifactSelector = `${artifact.type}/${artifact.name}`;
|
|
5383
6316
|
return selector === `${artifact.graphNodeId}:${artifactSelector}` || node !== void 0 && selector === `${node.name}@${node.version}:${artifactSelector}` || node !== void 0 && selector === `${node.name}:${artifactSelector}`;
|
|
5384
6317
|
}
|
|
6318
|
+
function overrideMatchesArtifact(selector, artifact, node) {
|
|
6319
|
+
const sourceSeparator = selector.lastIndexOf("::");
|
|
6320
|
+
if (sourceSeparator >= 0) {
|
|
6321
|
+
const sourceSelector = selector.slice(0, sourceSeparator).trim();
|
|
6322
|
+
const artifactSelector = selector.slice(sourceSeparator + 2).trim();
|
|
6323
|
+
return artifactSelector === `${artifact.type}/${artifact.name}` && sourceMatchesArtifact(sourceSelector, node);
|
|
6324
|
+
}
|
|
6325
|
+
return aliasMatchesArtifact(selector, artifact, node);
|
|
6326
|
+
}
|
|
6327
|
+
function sourceMatchesArtifact(selector, node) {
|
|
6328
|
+
if (!node || selector.length === 0) return false;
|
|
6329
|
+
if (selector === node.source || selector === node.normalizedSource || selector === node.name || selector === node.id) return true;
|
|
6330
|
+
const normalized = node.normalizedSource.toLowerCase();
|
|
6331
|
+
const source = node.source.toLowerCase();
|
|
6332
|
+
const value = selector.toLowerCase();
|
|
6333
|
+
if (value === source || value === normalized || value === node.name.toLowerCase() || value === node.id.toLowerCase()) return true;
|
|
6334
|
+
const github = /^github:([^#]+?)(?:#(.+))?$/.exec(value);
|
|
6335
|
+
if (github) {
|
|
6336
|
+
const repo = github[1].replace(/\.git$/i, "");
|
|
6337
|
+
const ref = github[2];
|
|
6338
|
+
const prefix = `git:https://github.com/${repo}.git#`;
|
|
6339
|
+
return ref ? normalized.includes(`${prefix}${ref}`) : normalized.includes(prefix);
|
|
6340
|
+
}
|
|
6341
|
+
if (!value.includes(":") && value.includes("/")) {
|
|
6342
|
+
const repo = value.replace(/\.git$/i, "");
|
|
6343
|
+
return normalized.includes(`github.com/${repo}.git#`) || source.includes(`github.com/${repo}.git`);
|
|
6344
|
+
}
|
|
6345
|
+
return false;
|
|
6346
|
+
}
|
|
5385
6347
|
function reachableNodeIds(graph, rootNodeId) {
|
|
5386
6348
|
const reachable = /* @__PURE__ */ new Set();
|
|
5387
6349
|
const queue = [rootNodeId];
|
|
@@ -5455,9 +6417,9 @@ function lockArtifactFor(artifact) {
|
|
|
5455
6417
|
}
|
|
5456
6418
|
|
|
5457
6419
|
// src/lifecycle/trust.ts
|
|
5458
|
-
import { mkdir as
|
|
5459
|
-
import { homedir as
|
|
5460
|
-
import { dirname as
|
|
6420
|
+
import { mkdir as mkdir13, readFile as readFile18 } from "fs/promises";
|
|
6421
|
+
import { homedir as homedir7 } from "os";
|
|
6422
|
+
import { dirname as dirname18, join as join24 } from "path";
|
|
5461
6423
|
import { z as z8 } from "zod";
|
|
5462
6424
|
var trustStoreSchema = z8.object({
|
|
5463
6425
|
version: z8.literal(1),
|
|
@@ -5531,14 +6493,14 @@ function sortedUnique4(values) {
|
|
|
5531
6493
|
}
|
|
5532
6494
|
async function readTrustStore(path) {
|
|
5533
6495
|
if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
|
|
5534
|
-
return trustStoreSchema.parse(JSON.parse(await
|
|
6496
|
+
return trustStoreSchema.parse(JSON.parse(await readFile18(path, "utf8")));
|
|
5535
6497
|
}
|
|
5536
6498
|
async function writeTrustStore(path, store) {
|
|
5537
|
-
await
|
|
6499
|
+
await mkdir13(dirname18(path), { recursive: true });
|
|
5538
6500
|
await writeJsonAtomic(path, trustStoreSchema.parse(store));
|
|
5539
6501
|
}
|
|
5540
6502
|
function defaultTrustStorePath() {
|
|
5541
|
-
return process.env.AGENTWHEEL_TRUST_STORE ??
|
|
6503
|
+
return process.env.AGENTWHEEL_TRUST_STORE ?? join24(homedir7(), ".agentwheel", "trust.json");
|
|
5542
6504
|
}
|
|
5543
6505
|
|
|
5544
6506
|
// src/lifecycle/source-plan.ts
|
|
@@ -5553,7 +6515,17 @@ async function createGraphSourcePlan(options) {
|
|
|
5553
6515
|
warnings.push(message);
|
|
5554
6516
|
options.warn?.(message);
|
|
5555
6517
|
};
|
|
5556
|
-
const
|
|
6518
|
+
const installationType = options.installationType ?? resolveInstallationTypeForAdapterTarget(options.adapter);
|
|
6519
|
+
const targetFingerprint = computeTargetFingerprint(options.targetFingerprintParts ?? {
|
|
6520
|
+
adapter: options.adapter.name,
|
|
6521
|
+
installationType,
|
|
6522
|
+
targetRoot: options.targetRoot,
|
|
6523
|
+
transport: transport.kind,
|
|
6524
|
+
transportDescription: transport.description
|
|
6525
|
+
});
|
|
6526
|
+
const stateKey = options.stateKey ?? stateKeyFor(options.adapter.name, { installationType, targetFingerprint });
|
|
6527
|
+
const installRoot = installRootForAdapterInstallationType(options.adapter, options.targetRoot, installationType, transport.kind === "ssh");
|
|
6528
|
+
const recoveredPendingApply = options.readOnly === true ? false : await recoverPendingApplyIfSafe(installRoot, options.adapter.name, transport, { installationType, stateKey });
|
|
5557
6529
|
const workspaceConfig = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: options.globalRoot });
|
|
5558
6530
|
const trustPolicy = {
|
|
5559
6531
|
...normalizeTrustPolicy(workspaceConfig.trust),
|
|
@@ -5561,18 +6533,12 @@ async function createGraphSourcePlan(options) {
|
|
|
5561
6533
|
};
|
|
5562
6534
|
const lockMode = options.frozenLock === true || options.offline === true;
|
|
5563
6535
|
const lockLabel = options.offline === true ? "Offline" : options.frozenLock === true ? "Frozen lock" : options.lockedResolution === true ? "Locked install" : "Fresh resolve";
|
|
5564
|
-
const targetFingerprint = computeTargetFingerprint(options.targetFingerprintParts ?? {
|
|
5565
|
-
adapter: options.adapter.name,
|
|
5566
|
-
targetRoot: options.targetRoot,
|
|
5567
|
-
transport: transport.kind,
|
|
5568
|
-
transportDescription: transport.description
|
|
5569
|
-
});
|
|
5570
6536
|
const graphLockPath = pathForGraphLock(workspaceRoot, options.targetKey ?? "default", options.adapter.name, targetFingerprint);
|
|
5571
6537
|
const previousLock = await readExistingGraphLock(graphLockPath);
|
|
5572
6538
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
5573
6539
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
5574
6540
|
workspaceRoot,
|
|
5575
|
-
cacheRoot:
|
|
6541
|
+
cacheRoot: join25(workspaceRoot, ".agentwheel", "cache"),
|
|
5576
6542
|
registryClient,
|
|
5577
6543
|
noDeps: options.noDeps,
|
|
5578
6544
|
lockedResolution: options.lockedResolution,
|
|
@@ -5594,13 +6560,20 @@ async function createGraphSourcePlan(options) {
|
|
|
5594
6560
|
targetFingerprint
|
|
5595
6561
|
});
|
|
5596
6562
|
const desiredArtifacts = desiredArtifactsFromGraphBundle(bundle);
|
|
6563
|
+
const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
|
|
6564
|
+
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
5597
6565
|
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
5598
6566
|
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
5599
|
-
const manifest = await readInstallManifest(
|
|
6567
|
+
const manifest = await readInstallManifest(resolvedInstallRoot, options.adapter.name, transport, { installationType: resolvedInstallationType, stateKey });
|
|
5600
6568
|
const plan = await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
|
|
5601
6569
|
baseRevision: manifest?.revision ?? null,
|
|
5602
6570
|
graphLockDigest,
|
|
5603
|
-
workspaceOwner: workspaceOwnerId(workspaceRoot)
|
|
6571
|
+
workspaceOwner: workspaceOwnerId(workspaceRoot),
|
|
6572
|
+
installationType: resolvedInstallationType,
|
|
6573
|
+
stateKey,
|
|
6574
|
+
forceDrift: options.forceDrift,
|
|
6575
|
+
forceConflict: options.forceConflict,
|
|
6576
|
+
replaceConflict: options.replaceConflict
|
|
5604
6577
|
});
|
|
5605
6578
|
return {
|
|
5606
6579
|
plan,
|
|
@@ -5635,28 +6608,38 @@ function desiredArtifactFromResolved(artifact) {
|
|
|
5635
6608
|
}
|
|
5636
6609
|
};
|
|
5637
6610
|
}
|
|
5638
|
-
async function recoverPendingApplyIfSafe(targetRoot, adapter, transport) {
|
|
5639
|
-
if (!await readApplyJournal(targetRoot, adapter, transport)) return false;
|
|
6611
|
+
async function recoverPendingApplyIfSafe(targetRoot, adapter, transport, scope) {
|
|
6612
|
+
if (!await readApplyJournal(targetRoot, adapter, transport, scope)) return false;
|
|
5640
6613
|
try {
|
|
5641
|
-
await recoverPendingApply(targetRoot, adapter, transport);
|
|
6614
|
+
await recoverPendingApply(targetRoot, adapter, transport, scope);
|
|
5642
6615
|
return true;
|
|
5643
6616
|
} catch (error) {
|
|
5644
6617
|
const message = error instanceof Error ? error.message : String(error);
|
|
5645
6618
|
throw new Error(`Pending apply journal for ${adapter} at ${targetRoot} could not be recovered automatically: ${message}`);
|
|
5646
6619
|
}
|
|
5647
6620
|
}
|
|
6621
|
+
function resolveInstallationTypeForAdapterTarget(adapter) {
|
|
6622
|
+
const supported = /* @__PURE__ */ new Set();
|
|
6623
|
+
for (const registry of Object.values(adapter.targets)) {
|
|
6624
|
+
for (const [installationType, target] of Object.entries(registry ?? {})) {
|
|
6625
|
+
if (target.enabled) supported.add(installationType);
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
if (supported.size === 1) return [...supported][0];
|
|
6629
|
+
return "local";
|
|
6630
|
+
}
|
|
5648
6631
|
async function readExistingGraphLock(path) {
|
|
5649
6632
|
if (!await pathExists(path)) return void 0;
|
|
5650
6633
|
return readGraphLock(path);
|
|
5651
6634
|
}
|
|
5652
6635
|
function pathForGraphLock(workspaceRoot, targetKey, adapter, targetFingerprint) {
|
|
5653
|
-
return
|
|
6636
|
+
return join25(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
|
|
5654
6637
|
}
|
|
5655
6638
|
function sanitizePathSegment(value) {
|
|
5656
6639
|
return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
|
|
5657
6640
|
}
|
|
5658
6641
|
function digestGraphLock(lock) {
|
|
5659
|
-
return
|
|
6642
|
+
return createHash7("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
|
|
5660
6643
|
}
|
|
5661
6644
|
function workspaceOwnerId(workspaceRoot) {
|
|
5662
6645
|
return `workspace-root:${resolve14(workspaceRoot)}`;
|
|
@@ -5713,128 +6696,8 @@ Type yes to continue: `);
|
|
|
5713
6696
|
${sources.map((source) => `- ${source}`).join("\n")}`);
|
|
5714
6697
|
}
|
|
5715
6698
|
|
|
5716
|
-
// src/lifecycle/profile.ts
|
|
5717
|
-
async function syncProfile(options) {
|
|
5718
|
-
const config = await readMergedWorkspaceConfig(options.workspaceRoot);
|
|
5719
|
-
const profile = config.profiles[options.profile];
|
|
5720
|
-
if (!profile) {
|
|
5721
|
-
throw new Error(`Unknown profile: ${options.profile}`);
|
|
5722
|
-
}
|
|
5723
|
-
const packages = options.source ? [await packageFromSource(options.source, options)] : config.packages;
|
|
5724
|
-
if (packages.length === 0) {
|
|
5725
|
-
throw new Error("Profile sync needs a source argument or configured packages.");
|
|
5726
|
-
}
|
|
5727
|
-
const results = [];
|
|
5728
|
-
for (const runtime of profile.runtimes) {
|
|
5729
|
-
const target = resolveProfileRuntime(runtime, config, options.workspaceRoot);
|
|
5730
|
-
const adapter = await resolveAdapter({
|
|
5731
|
-
adapter: target.adapter,
|
|
5732
|
-
adapterConfig: runtime.adapterConfig,
|
|
5733
|
-
adapterModule: runtime.adapterModule,
|
|
5734
|
-
allowAdapterCode: options.allowAdapterCode,
|
|
5735
|
-
baseDir: options.workspaceRoot,
|
|
5736
|
-
warn: options.warn
|
|
5737
|
-
});
|
|
5738
|
-
const selected = normalizeArtifactSelectors(options.select, options.skills);
|
|
5739
|
-
const graphPlan = await createGraphSourcePlan({
|
|
5740
|
-
roots: packages.map((pkg) => ({
|
|
5741
|
-
rootId: pkg.name,
|
|
5742
|
-
source: pkg.source,
|
|
5743
|
-
mode: options.mode ?? pkg.mode,
|
|
5744
|
-
ref: pkg.requestedRef,
|
|
5745
|
-
select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
5746
|
-
aliases: pkg.aliases
|
|
5747
|
-
})),
|
|
5748
|
-
targetRoot: target.targetRoot,
|
|
5749
|
-
workspaceRoot: options.workspaceRoot,
|
|
5750
|
-
adapter,
|
|
5751
|
-
transport: target.transport,
|
|
5752
|
-
targetKey: runtime.agent ?? adapter.name,
|
|
5753
|
-
targetFingerprintParts: {
|
|
5754
|
-
adapter: adapter.name,
|
|
5755
|
-
adapterConfig: runtime.adapterConfig,
|
|
5756
|
-
adapterModule: runtime.adapterModule,
|
|
5757
|
-
adapterCodeHash: adapter.programmatic?.hash,
|
|
5758
|
-
targetRoot: target.targetRoot,
|
|
5759
|
-
transport: target.transport.kind
|
|
5760
|
-
},
|
|
5761
|
-
noDeps: options.noDeps,
|
|
5762
|
-
lockedResolution: options.lockedResolution,
|
|
5763
|
-
frozenLock: options.frozenLock,
|
|
5764
|
-
offline: options.offline,
|
|
5765
|
-
yes: options.yes,
|
|
5766
|
-
trustPatterns: options.trustPatterns ?? [],
|
|
5767
|
-
readOnly: options.readOnly,
|
|
5768
|
-
isTTY: options.isTTY,
|
|
5769
|
-
warn: options.warn
|
|
5770
|
-
});
|
|
5771
|
-
try {
|
|
5772
|
-
results.push({
|
|
5773
|
-
runtime: adapter.name,
|
|
5774
|
-
targetRoot: target.targetRoot,
|
|
5775
|
-
transport: target.transport.kind,
|
|
5776
|
-
packageName: packages.map((pkg) => pkg.name).join(","),
|
|
5777
|
-
plan: graphPlan.plan
|
|
5778
|
-
});
|
|
5779
|
-
if (!options.dryRun) {
|
|
5780
|
-
await applyCombinedInstallPlan(graphPlan.plan, {
|
|
5781
|
-
executePlugins: runtime.executePlugins ?? options.executePlugins,
|
|
5782
|
-
transport: target.transport,
|
|
5783
|
-
graphLockDigest: graphPlan.graphLockDigest,
|
|
5784
|
-
graphLock: { path: graphPlan.graphLockPath, lock: graphPlan.bundle.graphLock }
|
|
5785
|
-
});
|
|
5786
|
-
}
|
|
5787
|
-
} finally {
|
|
5788
|
-
await rm8(graphPlan.bundle.root, { recursive: true, force: true });
|
|
5789
|
-
}
|
|
5790
|
-
}
|
|
5791
|
-
return results;
|
|
5792
|
-
}
|
|
5793
|
-
function resolveProfileRuntime(runtime, config, workspaceRoot) {
|
|
5794
|
-
if (runtime.agent) {
|
|
5795
|
-
const agent = config.agents[runtime.agent];
|
|
5796
|
-
if (!agent) throw new Error(`Unknown agent in profile: ${runtime.agent}`);
|
|
5797
|
-
const target = {
|
|
5798
|
-
agentName: runtime.agent,
|
|
5799
|
-
adapter: agent.adapter,
|
|
5800
|
-
targetRoot: agent.transport === "ssh" ? agent.root : resolveConfigPath(agent.root, workspaceRoot),
|
|
5801
|
-
workspaceRoot,
|
|
5802
|
-
transport: agent.transport,
|
|
5803
|
-
ssh: agent.transport === "ssh" ? {
|
|
5804
|
-
host: agent.host ?? "",
|
|
5805
|
-
user: agent.user,
|
|
5806
|
-
port: agent.port,
|
|
5807
|
-
identityFile: agent.identityFile ? resolveConfigPath(agent.identityFile, workspaceRoot) : void 0
|
|
5808
|
-
} : void 0,
|
|
5809
|
-
source: "agent"
|
|
5810
|
-
};
|
|
5811
|
-
return { adapter: target.adapter, targetRoot: target.targetRoot, transport: transportForTarget(target) };
|
|
5812
|
-
}
|
|
5813
|
-
return {
|
|
5814
|
-
adapter: runtime.adapter,
|
|
5815
|
-
targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot,
|
|
5816
|
-
transport: localTransport
|
|
5817
|
-
};
|
|
5818
|
-
}
|
|
5819
|
-
async function packageFromSource(source, options) {
|
|
5820
|
-
const resolved = await resolvePackageSource(source, options.workspaceRoot, {
|
|
5821
|
-
offline: options.frozenLock === true || options.offline === true,
|
|
5822
|
-
warn: options.warn
|
|
5823
|
-
});
|
|
5824
|
-
const driver = options.driver ?? inferSourceDriverName(resolved.source);
|
|
5825
|
-
return {
|
|
5826
|
-
name: resolved.registryEntry?.name ?? source,
|
|
5827
|
-
source: resolved.source,
|
|
5828
|
-
driver,
|
|
5829
|
-
adapter: "openclaw",
|
|
5830
|
-
mode: options.mode ?? "pinned",
|
|
5831
|
-
select: options.select,
|
|
5832
|
-
skills: options.skills
|
|
5833
|
-
};
|
|
5834
|
-
}
|
|
5835
|
-
|
|
5836
6699
|
// src/runtime/target.ts
|
|
5837
|
-
import { basename as
|
|
6700
|
+
import { basename as basename15, dirname as dirname20, join as join26, resolve as resolve15 } from "path";
|
|
5838
6701
|
var runtimeMarkers = [
|
|
5839
6702
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
5840
6703
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -5848,6 +6711,7 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
5848
6711
|
const targetRoot = resolve15(request.targetRoot);
|
|
5849
6712
|
return {
|
|
5850
6713
|
adapter: request.adapter ?? "openclaw",
|
|
6714
|
+
installationType: request.installationType,
|
|
5851
6715
|
targetRoot,
|
|
5852
6716
|
workspaceRoot: targetRoot,
|
|
5853
6717
|
transport: "local",
|
|
@@ -5857,14 +6721,15 @@ async function resolveRuntimeTarget(request = {}) {
|
|
|
5857
6721
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
5858
6722
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
5859
6723
|
if (request.agent) {
|
|
5860
|
-
return targetFromAgent(request.agent, config, workspaceRoot);
|
|
6724
|
+
return targetFromAgent(request.agent, config, workspaceRoot, request.installationType);
|
|
5861
6725
|
}
|
|
5862
6726
|
const detected = await detectRuntimeTarget(cwd, request.adapter);
|
|
5863
6727
|
if (detected) {
|
|
5864
|
-
return { ...detected, workspaceRoot: await findWorkspaceRoot(detected.targetRoot), transport: "local", source: "auto-detect" };
|
|
6728
|
+
return { ...detected, installationType: request.installationType, workspaceRoot: await findWorkspaceRoot(detected.targetRoot), transport: "local", source: "auto-detect" };
|
|
5865
6729
|
}
|
|
5866
6730
|
return {
|
|
5867
6731
|
adapter: request.adapter ?? "openclaw",
|
|
6732
|
+
installationType: request.installationType,
|
|
5868
6733
|
targetRoot: cwd,
|
|
5869
6734
|
workspaceRoot,
|
|
5870
6735
|
transport: "local",
|
|
@@ -5877,12 +6742,44 @@ async function resolveAllRuntimeTargets(request = {}) {
|
|
|
5877
6742
|
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
5878
6743
|
const workspaceRoot = await findWorkspaceRoot(cwd);
|
|
5879
6744
|
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
5880
|
-
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot));
|
|
6745
|
+
const targets = Object.entries(config.agents).map(([name]) => targetFromAgent(name, config, workspaceRoot, request.installationType));
|
|
5881
6746
|
if (targets.length === 0) {
|
|
5882
6747
|
throw new Error("No agents configured. Add agents to .agentwheel/config.json or pass --target-root.");
|
|
5883
6748
|
}
|
|
5884
6749
|
return targets;
|
|
5885
6750
|
}
|
|
6751
|
+
async function resolveProfileRuntimeTargets(request) {
|
|
6752
|
+
const cwd = resolve15(request.cwd ?? process.cwd());
|
|
6753
|
+
const workspaceRoot = request.targetRoot ? resolve15(request.targetRoot) : await findWorkspaceRoot(cwd);
|
|
6754
|
+
const config = await readMergedWorkspaceConfig(workspaceRoot, { globalRoot: request.globalRoot });
|
|
6755
|
+
const profile = config.profiles[request.profile];
|
|
6756
|
+
if (!profile) {
|
|
6757
|
+
throw new Error(`Unknown profile: ${request.profile}`);
|
|
6758
|
+
}
|
|
6759
|
+
return profile.runtimes.map((runtime) => resolveProfileRuntimeTarget(runtime, config, workspaceRoot, request.installationType));
|
|
6760
|
+
}
|
|
6761
|
+
function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installationType) {
|
|
6762
|
+
if (runtime.agent) {
|
|
6763
|
+
const target = targetFromAgent(runtime.agent, config, workspaceRoot, installationType ?? runtime.installationType);
|
|
6764
|
+
return {
|
|
6765
|
+
...target,
|
|
6766
|
+
adapterConfig: runtime.adapterConfig,
|
|
6767
|
+
adapterModule: runtime.adapterModule,
|
|
6768
|
+
targetKey: runtime.agent,
|
|
6769
|
+
source: "profile"
|
|
6770
|
+
};
|
|
6771
|
+
}
|
|
6772
|
+
return {
|
|
6773
|
+
adapter: runtime.adapter,
|
|
6774
|
+
adapterConfig: runtime.adapterConfig,
|
|
6775
|
+
adapterModule: runtime.adapterModule,
|
|
6776
|
+
installationType: installationType ?? runtime.installationType,
|
|
6777
|
+
targetRoot: runtime.targetRoot ? resolveConfigPath(runtime.targetRoot, workspaceRoot) : workspaceRoot,
|
|
6778
|
+
workspaceRoot,
|
|
6779
|
+
transport: "local",
|
|
6780
|
+
source: "profile"
|
|
6781
|
+
};
|
|
6782
|
+
}
|
|
5886
6783
|
async function resolveAllDetectedRuntimeTargets(request = {}) {
|
|
5887
6784
|
if (request.agent) return [await resolveRuntimeTarget(request)];
|
|
5888
6785
|
const scanRoot = runtimeScanRoot(request);
|
|
@@ -5892,6 +6789,7 @@ async function resolveAllDetectedRuntimeTargets(request = {}) {
|
|
|
5892
6789
|
}
|
|
5893
6790
|
return Promise.all(matches.map(async (match) => ({
|
|
5894
6791
|
...match,
|
|
6792
|
+
installationType: request.installationType,
|
|
5895
6793
|
workspaceRoot: await findWorkspaceRoot(match.targetRoot),
|
|
5896
6794
|
transport: "local",
|
|
5897
6795
|
source: "auto-detect"
|
|
@@ -5910,23 +6808,25 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
|
5910
6808
|
for (const marker of runtimeMarkers) {
|
|
5911
6809
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
5912
6810
|
for (const dir of marker.dirs) {
|
|
5913
|
-
if (
|
|
5914
|
-
matches.push({ adapter: marker.adapter, targetRoot:
|
|
5915
|
-
} else if (await pathExists(
|
|
6811
|
+
if (basename15(root) === dir) {
|
|
6812
|
+
matches.push({ adapter: marker.adapter, targetRoot: dirname20(root) });
|
|
6813
|
+
} else if (await pathExists(join26(root, dir))) {
|
|
5916
6814
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
5917
6815
|
}
|
|
5918
6816
|
}
|
|
5919
6817
|
}
|
|
5920
6818
|
return dedupeTargets(matches);
|
|
5921
6819
|
}
|
|
5922
|
-
function targetFromAgent(name, config, workspaceRoot) {
|
|
6820
|
+
function targetFromAgent(name, config, workspaceRoot, installationType) {
|
|
5923
6821
|
const agent = config.agents[name];
|
|
5924
6822
|
if (!agent) {
|
|
5925
6823
|
throw new Error(`Unknown agent: ${name}`);
|
|
5926
6824
|
}
|
|
5927
6825
|
return {
|
|
5928
6826
|
agentName: name,
|
|
6827
|
+
targetKey: name,
|
|
5929
6828
|
adapter: agent.adapter,
|
|
6829
|
+
installationType: installationType ?? agent.installationType,
|
|
5930
6830
|
targetRoot: agent.transport === "ssh" ? agent.root : resolveConfigPath(agent.root, workspaceRoot),
|
|
5931
6831
|
workspaceRoot,
|
|
5932
6832
|
transport: agent.transport,
|
|
@@ -5949,13 +6849,119 @@ function dedupeTargets(matches) {
|
|
|
5949
6849
|
function runtimeScanRoot(request) {
|
|
5950
6850
|
const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
5951
6851
|
if (request.targetRoot) return root;
|
|
5952
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
6852
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename15(root))) ? dirname20(root) : root;
|
|
6853
|
+
}
|
|
6854
|
+
|
|
6855
|
+
// src/lifecycle/profile.ts
|
|
6856
|
+
async function syncProfile(options) {
|
|
6857
|
+
const config = await readMergedWorkspaceConfig(options.workspaceRoot);
|
|
6858
|
+
const profile = config.profiles[options.profile];
|
|
6859
|
+
if (!profile) {
|
|
6860
|
+
throw new Error(`Unknown profile: ${options.profile}`);
|
|
6861
|
+
}
|
|
6862
|
+
const packages = options.source ? [await packageFromSource(options.source, options)] : config.packages;
|
|
6863
|
+
if (packages.length === 0) {
|
|
6864
|
+
throw new Error("Profile sync needs a source argument or configured packages.");
|
|
6865
|
+
}
|
|
6866
|
+
const results = [];
|
|
6867
|
+
for (const runtime of profile.runtimes) {
|
|
6868
|
+
const target = resolveProfileRuntimeTarget(runtime, config, options.workspaceRoot, options.installationType);
|
|
6869
|
+
const transport = transportForTarget(target);
|
|
6870
|
+
const adapter = await resolveAdapter({
|
|
6871
|
+
adapter: target.adapter,
|
|
6872
|
+
adapterConfig: target.adapterConfig,
|
|
6873
|
+
adapterModule: target.adapterModule,
|
|
6874
|
+
allowAdapterCode: options.allowAdapterCode,
|
|
6875
|
+
baseDir: options.workspaceRoot,
|
|
6876
|
+
warn: options.warn
|
|
6877
|
+
});
|
|
6878
|
+
const installationType = target.installationType ?? defaultInstallationType;
|
|
6879
|
+
resolveInstallationTypeForAdapter(adapter, installationType);
|
|
6880
|
+
const selected = normalizeArtifactSelectors(options.select, options.skills);
|
|
6881
|
+
const graphPlan = await createGraphSourcePlan({
|
|
6882
|
+
roots: packages.map((pkg) => ({
|
|
6883
|
+
rootId: pkg.name,
|
|
6884
|
+
source: pkg.source,
|
|
6885
|
+
mode: options.mode ?? pkg.mode,
|
|
6886
|
+
ref: pkg.requestedRef,
|
|
6887
|
+
select: selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
6888
|
+
aliases: pkg.aliases,
|
|
6889
|
+
overrides: pkg.overrides
|
|
6890
|
+
})),
|
|
6891
|
+
targetRoot: target.targetRoot,
|
|
6892
|
+
workspaceRoot: options.workspaceRoot,
|
|
6893
|
+
adapter,
|
|
6894
|
+
transport,
|
|
6895
|
+
targetKey: target.targetKey ?? target.agentName ?? adapter.name,
|
|
6896
|
+
targetFingerprintParts: {
|
|
6897
|
+
adapter: adapter.name,
|
|
6898
|
+
installationType,
|
|
6899
|
+
adapterConfig: target.adapterConfig,
|
|
6900
|
+
adapterModule: target.adapterModule,
|
|
6901
|
+
adapterCodeHash: adapter.programmatic?.hash,
|
|
6902
|
+
agentName: target.agentName,
|
|
6903
|
+
targetRoot: target.targetRoot,
|
|
6904
|
+
transport: transport.kind,
|
|
6905
|
+
ssh: target.ssh
|
|
6906
|
+
},
|
|
6907
|
+
installationType,
|
|
6908
|
+
noDeps: options.noDeps,
|
|
6909
|
+
lockedResolution: options.lockedResolution,
|
|
6910
|
+
frozenLock: options.frozenLock,
|
|
6911
|
+
offline: options.offline,
|
|
6912
|
+
yes: options.yes,
|
|
6913
|
+
trustPatterns: options.trustPatterns ?? [],
|
|
6914
|
+
readOnly: options.readOnly,
|
|
6915
|
+
isTTY: options.isTTY,
|
|
6916
|
+
warn: options.warn,
|
|
6917
|
+
forceDrift: options.forceDrift,
|
|
6918
|
+
forceConflict: options.forceConflict,
|
|
6919
|
+
replaceConflict: options.replaceConflict
|
|
6920
|
+
});
|
|
6921
|
+
try {
|
|
6922
|
+
results.push({
|
|
6923
|
+
runtime: adapter.name,
|
|
6924
|
+
targetRoot: installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh"),
|
|
6925
|
+
transport: transport.kind,
|
|
6926
|
+
packageName: packages.map((pkg) => pkg.name).join(","),
|
|
6927
|
+
plan: graphPlan.plan
|
|
6928
|
+
});
|
|
6929
|
+
if (!options.dryRun) {
|
|
6930
|
+
await applyCombinedInstallPlan(graphPlan.plan, {
|
|
6931
|
+
executePlugins: runtime.executePlugins ?? options.executePlugins,
|
|
6932
|
+
transport,
|
|
6933
|
+
graphLockDigest: graphPlan.graphLockDigest,
|
|
6934
|
+
graphLock: { path: graphPlan.graphLockPath, lock: graphPlan.bundle.graphLock }
|
|
6935
|
+
});
|
|
6936
|
+
}
|
|
6937
|
+
} finally {
|
|
6938
|
+
await rm8(graphPlan.bundle.root, { recursive: true, force: true });
|
|
6939
|
+
}
|
|
6940
|
+
}
|
|
6941
|
+
return results;
|
|
6942
|
+
}
|
|
6943
|
+
async function packageFromSource(source, options) {
|
|
6944
|
+
const resolved = await resolvePackageSource(source, options.workspaceRoot, {
|
|
6945
|
+
offline: options.frozenLock === true || options.offline === true,
|
|
6946
|
+
warn: options.warn
|
|
6947
|
+
});
|
|
6948
|
+
const driver = options.driver ?? inferSourceDriverName(resolved.source);
|
|
6949
|
+
return {
|
|
6950
|
+
name: resolved.registryEntry?.name ?? source,
|
|
6951
|
+
source: resolved.source,
|
|
6952
|
+
driver,
|
|
6953
|
+
adapter: "openclaw",
|
|
6954
|
+
installationType: options.installationType,
|
|
6955
|
+
mode: options.mode ?? "pinned",
|
|
6956
|
+
select: options.select,
|
|
6957
|
+
skills: options.skills
|
|
6958
|
+
};
|
|
5953
6959
|
}
|
|
5954
6960
|
|
|
5955
6961
|
// src/cli/update-check.ts
|
|
5956
|
-
import { mkdir as
|
|
5957
|
-
import { homedir as
|
|
5958
|
-
import { dirname as
|
|
6962
|
+
import { mkdir as mkdir15, readFile as readFile19, writeFile as writeFile12 } from "fs/promises";
|
|
6963
|
+
import { homedir as homedir8 } from "os";
|
|
6964
|
+
import { dirname as dirname21, join as join27 } from "path";
|
|
5959
6965
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
5960
6966
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
5961
6967
|
var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
|
|
@@ -5963,7 +6969,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
5963
6969
|
if (isDisabled(options)) return;
|
|
5964
6970
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
5965
6971
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
5966
|
-
const cachePath = options.cachePath ??
|
|
6972
|
+
const cachePath = options.cachePath ?? join27(homedir8(), ".agentwheel", "update-check.json");
|
|
5967
6973
|
try {
|
|
5968
6974
|
const cached = await readCache(cachePath);
|
|
5969
6975
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -6000,7 +7006,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
6000
7006
|
}
|
|
6001
7007
|
async function readCache(path) {
|
|
6002
7008
|
try {
|
|
6003
|
-
const parsed = JSON.parse(await
|
|
7009
|
+
const parsed = JSON.parse(await readFile19(path, "utf8"));
|
|
6004
7010
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
6005
7011
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
6006
7012
|
} catch {
|
|
@@ -6008,8 +7014,8 @@ async function readCache(path) {
|
|
|
6008
7014
|
}
|
|
6009
7015
|
}
|
|
6010
7016
|
async function writeCache(path, cache) {
|
|
6011
|
-
await
|
|
6012
|
-
await
|
|
7017
|
+
await mkdir15(dirname21(path), { recursive: true });
|
|
7018
|
+
await writeFile12(path, `${JSON.stringify(cache, null, 2)}
|
|
6013
7019
|
`, "utf8");
|
|
6014
7020
|
}
|
|
6015
7021
|
function warnIfNewer(latest, current, stderr = process.stderr) {
|
|
@@ -6155,13 +7161,13 @@ function isCrossPackageSelector(value) {
|
|
|
6155
7161
|
}
|
|
6156
7162
|
|
|
6157
7163
|
// src/model/package-migrate.ts
|
|
6158
|
-
import { readFile as
|
|
6159
|
-
import { join as
|
|
7164
|
+
import { readFile as readFile20, rename as rename4, writeFile as writeFile13 } from "fs/promises";
|
|
7165
|
+
import { join as join29, resolve as resolve17 } from "path";
|
|
6160
7166
|
import { applyEdits, modify, parse as parse3 } from "jsonc-parser";
|
|
6161
7167
|
async function migratePackageManifest(root) {
|
|
6162
7168
|
const packageRoot = resolve17(root);
|
|
6163
7169
|
for (const name of openPackManifestNames) {
|
|
6164
|
-
const path =
|
|
7170
|
+
const path = join29(packageRoot, name);
|
|
6165
7171
|
if (await pathExists(path)) {
|
|
6166
7172
|
return { changed: false, to: path, message: `Package already uses ${name}.` };
|
|
6167
7173
|
}
|
|
@@ -6170,18 +7176,18 @@ async function migratePackageManifest(root) {
|
|
|
6170
7176
|
if (!legacyName) {
|
|
6171
7177
|
throw new Error(`No legacy package manifest found at ${packageRoot}`);
|
|
6172
7178
|
}
|
|
6173
|
-
const from =
|
|
7179
|
+
const from = join29(packageRoot, legacyName);
|
|
6174
7180
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
6175
|
-
const to =
|
|
6176
|
-
const content = await
|
|
7181
|
+
const to = join29(packageRoot, toName);
|
|
7182
|
+
const content = await readFile20(from, "utf8");
|
|
6177
7183
|
const updated = updateSchemaVersion(content);
|
|
6178
7184
|
await rename4(from, to);
|
|
6179
|
-
await
|
|
7185
|
+
await writeFile13(to, updated, "utf8");
|
|
6180
7186
|
return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
|
|
6181
7187
|
}
|
|
6182
7188
|
async function firstExistingLegacyManifest(root) {
|
|
6183
7189
|
for (const name of legacyPackageManifestNames) {
|
|
6184
|
-
if (await pathExists(
|
|
7190
|
+
if (await pathExists(join29(root, name))) return name;
|
|
6185
7191
|
}
|
|
6186
7192
|
return void 0;
|
|
6187
7193
|
}
|
|
@@ -6199,20 +7205,20 @@ function updateSchemaVersion(content) {
|
|
|
6199
7205
|
|
|
6200
7206
|
// src/cli/version.ts
|
|
6201
7207
|
import { readFileSync } from "fs";
|
|
6202
|
-
import { dirname as
|
|
7208
|
+
import { dirname as dirname22, join as join30 } from "path";
|
|
6203
7209
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6204
7210
|
var FALLBACK_VERSION = "0.0.0";
|
|
6205
7211
|
function resolveCliVersion() {
|
|
6206
|
-
let dir =
|
|
7212
|
+
let dir = dirname22(fileURLToPath2(import.meta.url));
|
|
6207
7213
|
while (true) {
|
|
6208
7214
|
try {
|
|
6209
|
-
const pkg = JSON.parse(readFileSync(
|
|
7215
|
+
const pkg = JSON.parse(readFileSync(join30(dir, "package.json"), "utf8"));
|
|
6210
7216
|
if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
|
|
6211
7217
|
return pkg.version;
|
|
6212
7218
|
}
|
|
6213
7219
|
} catch {
|
|
6214
7220
|
}
|
|
6215
|
-
const parent =
|
|
7221
|
+
const parent = dirname22(dir);
|
|
6216
7222
|
if (parent === dir) return FALLBACK_VERSION;
|
|
6217
7223
|
dir = parent;
|
|
6218
7224
|
}
|
|
@@ -6228,7 +7234,7 @@ Core flow:
|
|
|
6228
7234
|
$ agentwheel plan
|
|
6229
7235
|
$ agentwheel install
|
|
6230
7236
|
`);
|
|
6231
|
-
program.command("init").description("initialize an agentwheel workspace or package").argument("[kind]", "workspace or package", "workspace").option("--target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
|
|
7237
|
+
program.command("init").description("initialize an agentwheel workspace or package").argument("[kind]", "workspace or package", "workspace").option("-t, --target-root <path>", "workspace root", process.cwd()).option("--fleet-example", "scaffold example agents and profiles in workspace config", false).action(async (kind, options) => {
|
|
6232
7238
|
const root = normalizeTargetRoot(options.targetRoot);
|
|
6233
7239
|
if (kind === "package") {
|
|
6234
7240
|
await initPackage(root);
|
|
@@ -6246,28 +7252,29 @@ program.command("init").description("initialize an agentwheel workspace or packa
|
|
|
6246
7252
|
if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
|
|
6247
7253
|
console.log(nextInstallNudge());
|
|
6248
7254
|
});
|
|
6249
|
-
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"
|
|
6250
|
-
const
|
|
6251
|
-
const
|
|
7255
|
+
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("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "workspace root").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) => {
|
|
7256
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7257
|
+
const targetRoot = normalizeTargetRoot(normalizedOptions.targetRoot ?? process.cwd());
|
|
7258
|
+
const entry = await packageEntryFromSource(source, targetRoot, normalizedOptions);
|
|
6252
7259
|
await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
|
|
6253
7260
|
console.log(`Added ${entry.name}. Preview: agentwheel plan - Apply: agentwheel install`);
|
|
6254
7261
|
});
|
|
6255
|
-
program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).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) => {
|
|
7262
|
+
program.command("list").description("list artifacts exposed by a package source").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).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) => {
|
|
6256
7263
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
6257
7264
|
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
6258
7265
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
6259
7266
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
6260
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
7267
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join31(targetRoot, ".agentwheel", "cache") }))));
|
|
6261
7268
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
6262
7269
|
for (const artifact of artifacts) {
|
|
6263
7270
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
6264
7271
|
}
|
|
6265
7272
|
});
|
|
6266
|
-
program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
|
|
7273
|
+
program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
|
|
6267
7274
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
6268
7275
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
6269
7276
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
6270
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
7277
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join31(targetRoot, ".agentwheel", "cache") }))));
|
|
6271
7278
|
const result = await driver.scan(resolved);
|
|
6272
7279
|
if (result.findings.length === 0) {
|
|
6273
7280
|
console.log("Scan ok: no findings");
|
|
@@ -6278,69 +7285,77 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
6278
7285
|
}
|
|
6279
7286
|
if (!result.ok) process.exitCode = 1;
|
|
6280
7287
|
});
|
|
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) => {
|
|
7288
|
+
program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
6282
7289
|
await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
|
|
6283
7290
|
});
|
|
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) => {
|
|
7291
|
+
program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
|
|
6285
7292
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6286
7293
|
});
|
|
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) => {
|
|
7294
|
+
program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
6288
7295
|
console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
|
|
6289
7296
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
6290
7297
|
});
|
|
6291
|
-
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter").option("--target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
6292
|
-
const
|
|
7298
|
+
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
7299
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7300
|
+
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
6293
7301
|
for (const target of targets) {
|
|
6294
|
-
await runConfiguredGraphPackages(target, { ...
|
|
7302
|
+
await runConfiguredGraphPackages(target, { ...normalizedOptions, scope: name }, { mode: "update" });
|
|
6295
7303
|
}
|
|
6296
7304
|
});
|
|
6297
7305
|
program.command("deps").description("inspect the OpenPack dependency graph").addCommand(
|
|
6298
|
-
new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").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("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
6299
|
-
const
|
|
7306
|
+
new Command("tree").description("print the OpenPack dependency graph").argument("[source]", "optional package source to resolve").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--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("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
7307
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(source, options) });
|
|
7308
|
+
const targets = await resolveCliTargets(normalizedOptions);
|
|
6300
7309
|
for (const target of targets) {
|
|
6301
7310
|
if (source) {
|
|
6302
|
-
for (const result of await buildGraphPlansForTarget(target, source,
|
|
7311
|
+
for (const result of await buildGraphPlansForTarget(target, source, normalizedOptions, { mode: "install" })) {
|
|
6303
7312
|
console.log(formatDependencyTree(result.graph).join("\n"));
|
|
6304
7313
|
for (const decision of result.bundle.graphLock.canonical.namespacing) {
|
|
6305
7314
|
console.log(`NAMESPACE ${decision.graphNodeId}:${decision.type}/${decision.name} -> ${decision.type}/${decision.installName} (${decision.reason})`);
|
|
6306
7315
|
}
|
|
7316
|
+
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
7317
|
+
console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
7318
|
+
}
|
|
6307
7319
|
await rm9(result.bundle.root, { recursive: true, force: true });
|
|
6308
7320
|
}
|
|
6309
7321
|
continue;
|
|
6310
7322
|
}
|
|
6311
|
-
const { lock } = await readTargetGraphLock(target,
|
|
7323
|
+
const { lock } = await readTargetGraphLock(target, normalizedOptions);
|
|
6312
7324
|
console.log(formatLockDependencyTree(lock));
|
|
6313
7325
|
}
|
|
6314
7326
|
})
|
|
6315
7327
|
).addCommand(
|
|
6316
|
-
new Command("why").description("explain why an artifact is installed").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").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).action(async (selector, options) => {
|
|
6317
|
-
const
|
|
7328
|
+
new Command("why").description("explain why an artifact is installed").argument("<selector>", "installed path, type/installName, or graphNodeId:type/name").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).action(async (selector, options) => {
|
|
7329
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7330
|
+
const targets = await resolveCliTargets(normalizedOptions);
|
|
6318
7331
|
for (const target of targets) {
|
|
6319
|
-
const { lock, adapter } = await readTargetGraphLock(target,
|
|
6320
|
-
const
|
|
7332
|
+
const { lock, adapter } = await readTargetGraphLock(target, normalizedOptions);
|
|
7333
|
+
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
7334
|
+
const state = installStateForTarget(target, adapter, normalizedOptions, installationType);
|
|
7335
|
+
const manifest = await readInstallManifest(state.installRoot, adapter.name, transportForTarget(target), state);
|
|
6321
7336
|
console.log(formatDepsWhy(lock, manifest, selector));
|
|
6322
7337
|
}
|
|
6323
7338
|
})
|
|
6324
7339
|
);
|
|
6325
7340
|
program.command("registry").description("manage optional registry indexes").addCommand(
|
|
6326
|
-
new Command("update").description("refresh the local registry cache").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
|
|
7341
|
+
new Command("update").description("refresh the local registry cache").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (options) => {
|
|
6327
7342
|
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
|
|
6328
7343
|
const index = await client.getIndex({ refresh: true });
|
|
6329
7344
|
console.log(`Registry refreshed: ${index.entries.length} entries from ${index.sources.join(", ")}`);
|
|
6330
7345
|
})
|
|
6331
7346
|
).addCommand(
|
|
6332
|
-
new Command("list").description("list available registry entries").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
|
|
7347
|
+
new Command("list").description("list available registry entries").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (options) => {
|
|
6333
7348
|
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
|
|
6334
7349
|
printRegistryEntries((await client.getIndex()).entries);
|
|
6335
7350
|
})
|
|
6336
7351
|
).addCommand(
|
|
6337
|
-
new Command("search").description("search registry entries").argument("<query>", "search query").option("--target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
7352
|
+
new Command("search").description("search registry entries").argument("<query>", "search query").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
|
|
6338
7353
|
const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot), warn: (message) => console.warn(message) });
|
|
6339
7354
|
printRegistryEntries(await client.search(query));
|
|
6340
7355
|
})
|
|
6341
7356
|
);
|
|
6342
7357
|
program.command("trust").description("manage persisted source trust decisions").addCommand(
|
|
6343
|
-
new Command("forget").description("forget a persisted trusted source pattern").argument("<pattern>", "trusted source glob to revoke").option("--target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
|
|
7358
|
+
new Command("forget").description("forget a persisted trusted source pattern").argument("<pattern>", "trusted source glob to revoke").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (pattern, options) => {
|
|
6344
7359
|
const removed = await forgetTrustedSources(normalizeTargetRoot(options.targetRoot), pattern);
|
|
6345
7360
|
if (removed.length === 0) {
|
|
6346
7361
|
console.log(`No persisted trust matched ${pattern}.`);
|
|
@@ -6367,114 +7382,128 @@ program.command("package").description("validate and migrate OpenPack packages")
|
|
|
6367
7382
|
console.log(result.message);
|
|
6368
7383
|
})
|
|
6369
7384
|
);
|
|
6370
|
-
program.command("remember").description("append text to the local instructions overlay").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
|
|
7385
|
+
program.command("remember").description("append text to the local instructions overlay").requiredOption("--runtime <runtime>", "runtime/adapter name").option("-t, --target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
|
|
6371
7386
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
6372
7387
|
const result = await remember(targetRoot, options.runtime, text);
|
|
6373
7388
|
console.log(`Remembered in ${result.overlayPath}.`);
|
|
6374
7389
|
console.log(nextInstallNudge());
|
|
6375
7390
|
});
|
|
6376
|
-
program.command("eject").description("copy a managed artifact into local ownership").argument("<item>", "package/type/name").option("--target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
|
|
7391
|
+
program.command("eject").description("copy a managed artifact into local ownership").argument("<item>", "package/type/name").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (item, options) => {
|
|
6377
7392
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
6378
7393
|
const result = await ejectArtifact(targetRoot, item);
|
|
6379
7394
|
console.log(`Ejected ${item} to ${result.ejectedPath}.`);
|
|
6380
7395
|
console.log(nextInstallNudge());
|
|
6381
7396
|
});
|
|
6382
|
-
program.command("uninstall").description("remove configured packages or managed runtime files").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter").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("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--keep-files", "remove from config and manifest but leave runtime files unmanaged", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
|
|
7397
|
+
program.command("uninstall").description("remove configured packages or managed runtime files").argument("[package]", "configured package name or source to remove from the ownership graph").option("--adapter <adapter>", "adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--dry-run", "show removals without writing", false).option("--force", "remove drifted managed files too", false).option("--keep-files", "remove from config and manifest but leave runtime files unmanaged", false).option("--select <type/name>", "uninstall only selected artifact type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "uninstall only selected skill name (repeatable or comma-separated)", collectSkillOption, []).option("--frozen-lock", "resolve remaining packages strictly from the existing graph lock and cached sources", false).option("--offline", "resolve remaining packages strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources while resolving remaining packages", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (packageName, options) => {
|
|
6383
7398
|
if (options.keepFiles && options.force) {
|
|
6384
7399
|
throw new Error("--keep-files cannot be combined with --force.");
|
|
6385
7400
|
}
|
|
6386
|
-
const
|
|
7401
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7402
|
+
const targets = await resolveCliTargets(normalizedOptions);
|
|
6387
7403
|
for (const target of targets) {
|
|
6388
7404
|
if (packageName) {
|
|
6389
|
-
await uninstallConfiguredPackage(target, packageName,
|
|
7405
|
+
await uninstallConfiguredPackage(target, packageName, normalizedOptions);
|
|
6390
7406
|
continue;
|
|
6391
7407
|
}
|
|
6392
|
-
if (
|
|
7408
|
+
if (normalizedOptions.keepFiles) {
|
|
6393
7409
|
throw new Error("--keep-files requires a configured package name or source.");
|
|
6394
7410
|
}
|
|
6395
|
-
const adapter = await resolveAdapterForTarget(target,
|
|
7411
|
+
const adapter = await resolveAdapterForTarget(target, normalizedOptions);
|
|
6396
7412
|
const transport = transportForTarget(target);
|
|
6397
|
-
const
|
|
7413
|
+
const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
7414
|
+
const state = installStateForTarget(target, adapter, normalizedOptions, installationType);
|
|
7415
|
+
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
6398
7416
|
if (!manifest) {
|
|
6399
|
-
console.log(`No install manifest for ${adapter.name} at ${
|
|
7417
|
+
console.log(`No install manifest for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
6400
7418
|
continue;
|
|
6401
7419
|
}
|
|
6402
7420
|
const plan = filterUninstallPlanBySelection(await createUninstallPlan(manifest), selectedArtifactsFromOptions(options));
|
|
6403
7421
|
console.log(formatPlan(plan));
|
|
6404
|
-
const result = await uninstall(plan, { dryRun:
|
|
6405
|
-
if (!
|
|
7422
|
+
const result = await uninstall(plan, { dryRun: normalizedOptions.dryRun, force: normalizedOptions.force, transport });
|
|
7423
|
+
if (!normalizedOptions.dryRun) {
|
|
6406
7424
|
if (transport.kind !== "local" && adapter.programmatic?.uninstall) {
|
|
6407
7425
|
throw new Error(`Cannot execute programmatic adapter uninstall over ${transport.description}.`);
|
|
6408
7426
|
}
|
|
6409
|
-
await adapter.programmatic?.uninstall?.({ targetRoot:
|
|
7427
|
+
await adapter.programmatic?.uninstall?.({ targetRoot: state.installRoot, adapterName: adapter.name });
|
|
6410
7428
|
console.log(formatUninstallResult(result));
|
|
6411
7429
|
}
|
|
6412
7430
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
6413
7431
|
}
|
|
6414
7432
|
});
|
|
6415
|
-
program.command("status").description("show configured packages and runtime install state").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).action(async (options) => {
|
|
6416
|
-
const
|
|
7433
|
+
program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
|
|
7434
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7435
|
+
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
6417
7436
|
for (const target of targets) {
|
|
6418
|
-
await printStatus(target,
|
|
7437
|
+
await printStatus(target, normalizedOptions);
|
|
6419
7438
|
}
|
|
6420
7439
|
});
|
|
6421
7440
|
async function runInstallCommand(nameOrSource, options, behavior) {
|
|
7441
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
|
|
6422
7442
|
if (options.profile) {
|
|
6423
|
-
const target = await resolveRuntimeTarget({
|
|
7443
|
+
const target = await resolveRuntimeTarget({
|
|
7444
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7445
|
+
adapter: normalizedOptions.adapter,
|
|
7446
|
+
installationType: normalizedOptions.installationType,
|
|
7447
|
+
agent: normalizedOptions.agent
|
|
7448
|
+
});
|
|
6424
7449
|
const results = await syncProfile({
|
|
6425
7450
|
workspaceRoot: target.workspaceRoot,
|
|
6426
7451
|
profile: options.profile,
|
|
6427
7452
|
source: nameOrSource,
|
|
6428
|
-
driver:
|
|
6429
|
-
mode:
|
|
6430
|
-
select: selectedArtifactsFromOptions(
|
|
7453
|
+
driver: normalizedOptions.driver,
|
|
7454
|
+
mode: normalizedOptions.mode,
|
|
7455
|
+
select: selectedArtifactsFromOptions(normalizedOptions),
|
|
7456
|
+
installationType: normalizedOptions.installationType,
|
|
6431
7457
|
dryRun: !behavior.apply,
|
|
6432
|
-
executePlugins:
|
|
6433
|
-
allowAdapterCode:
|
|
6434
|
-
|
|
7458
|
+
executePlugins: normalizedOptions.executePlugins,
|
|
7459
|
+
allowAdapterCode: normalizedOptions.allowAdapterCode,
|
|
7460
|
+
forceDrift: normalizedOptions.forceDrift,
|
|
7461
|
+
forceConflict: normalizedOptions.forceConflict,
|
|
7462
|
+
replaceConflict: normalizedOptions.replaceConflict,
|
|
7463
|
+
noDeps: noDepsFromOptions(normalizedOptions),
|
|
6435
7464
|
lockedResolution: true,
|
|
6436
|
-
frozenLock:
|
|
6437
|
-
offline:
|
|
6438
|
-
yes:
|
|
6439
|
-
trustPatterns:
|
|
7465
|
+
frozenLock: normalizedOptions.frozenLock,
|
|
7466
|
+
offline: normalizedOptions.offline,
|
|
7467
|
+
yes: normalizedOptions.yes,
|
|
7468
|
+
trustPatterns: normalizedOptions.trust ?? [],
|
|
6440
7469
|
readOnly: !behavior.apply,
|
|
6441
7470
|
isTTY: process.stdin.isTTY === true,
|
|
6442
7471
|
warn: (message) => console.warn(message)
|
|
6443
7472
|
});
|
|
6444
7473
|
for (const result of results) {
|
|
6445
|
-
console.log(`Profile ${
|
|
7474
|
+
console.log(`Profile ${normalizedOptions.profile} / ${result.runtime} / ${result.packageName} at ${result.targetRoot} (${result.transport}):`);
|
|
6446
7475
|
console.log(formatPlan(result.plan));
|
|
6447
7476
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
6448
7477
|
}
|
|
6449
7478
|
if (behavior.apply) console.log("Applied.");
|
|
6450
7479
|
return;
|
|
6451
7480
|
}
|
|
6452
|
-
const targets = await resolveCliTargets(
|
|
7481
|
+
const targets = await resolveCliTargets(normalizedOptions);
|
|
6453
7482
|
for (const target of targets) {
|
|
7483
|
+
const targetOptions = optionsForResolvedTarget(normalizedOptions, target);
|
|
6454
7484
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
6455
|
-
const configured = nameOrSource ?
|
|
7485
|
+
const configured = nameOrSource ? findConfiguredPackageForTarget(config.packages, nameOrSource, targetOptions, target) : void 0;
|
|
6456
7486
|
let source;
|
|
6457
7487
|
let scope = configured?.name;
|
|
6458
7488
|
let extraPackage;
|
|
6459
7489
|
if (nameOrSource && !configured) {
|
|
6460
7490
|
try {
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), entry));
|
|
6465
|
-
} else {
|
|
6466
|
-
source = nameOrSource;
|
|
6467
|
-
extraPackage = entry;
|
|
7491
|
+
let entry = await packageEntryFromSource(nameOrSource, target.workspaceRoot, { ...targetOptions, adapter: targetOptions.adapter ?? target.adapter });
|
|
7492
|
+
if (targetOptions.multiAdapterSource) {
|
|
7493
|
+
entry = packageEntryWithAdapterSuffix(entry);
|
|
6468
7494
|
}
|
|
7495
|
+
scope = entry.name;
|
|
7496
|
+
source = nameOrSource;
|
|
7497
|
+
extraPackage = entry;
|
|
6469
7498
|
} catch (error) {
|
|
6470
7499
|
throw teachingInstallError(nameOrSource, error);
|
|
6471
7500
|
}
|
|
6472
7501
|
}
|
|
6473
|
-
for (const result of await buildGraphPlansForTarget(target, source, { ...
|
|
7502
|
+
for (const result of await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage }, { mode: "install" })) {
|
|
6474
7503
|
console.log(formatGraphPlan(result));
|
|
6475
7504
|
if (behavior.apply) {
|
|
6476
7505
|
await applyCombinedInstallPlan(result.plan, {
|
|
6477
|
-
executePlugins:
|
|
7506
|
+
executePlugins: targetOptions.executePlugins,
|
|
6478
7507
|
transport: transportForTarget(target),
|
|
6479
7508
|
graphLockDigest: result.graphLockDigest,
|
|
6480
7509
|
graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
|
|
@@ -6484,6 +7513,9 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
6484
7513
|
await rm9(result.bundle.root, { recursive: true, force: true });
|
|
6485
7514
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
6486
7515
|
}
|
|
7516
|
+
if (behavior.apply && extraPackage && !targetOptions.onlySource) {
|
|
7517
|
+
await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), extraPackage));
|
|
7518
|
+
}
|
|
6487
7519
|
}
|
|
6488
7520
|
}
|
|
6489
7521
|
async function packageEntryFromSource(source, targetRoot, options) {
|
|
@@ -6504,23 +7536,26 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
6504
7536
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
6505
7537
|
workspaceRoot: targetRoot,
|
|
6506
7538
|
adapter,
|
|
6507
|
-
cacheRoot:
|
|
7539
|
+
cacheRoot: join31(targetRoot, ".agentwheel", "cache"),
|
|
6508
7540
|
mode: options.mode,
|
|
6509
7541
|
frozenLock: lockMode,
|
|
6510
7542
|
select: selectedArtifacts
|
|
6511
7543
|
});
|
|
7544
|
+
const installationType = resolveInstallationTypeForArtifacts(adapter, bundle.artifacts.map((artifact) => artifact.type), options.installationType);
|
|
6512
7545
|
try {
|
|
6513
7546
|
return {
|
|
6514
7547
|
name: options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source,
|
|
6515
7548
|
source: resolvedSource,
|
|
6516
7549
|
driver: driverName,
|
|
6517
7550
|
adapter: adapter.name,
|
|
7551
|
+
installationType,
|
|
6518
7552
|
adapterConfig: options.adapterConfig,
|
|
6519
7553
|
adapterModule: options.adapterModule,
|
|
6520
7554
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
6521
7555
|
mode: options.mode ?? "pinned",
|
|
6522
7556
|
requestedRef: bundle.source.requestedRef,
|
|
6523
|
-
select: selectedArtifacts
|
|
7557
|
+
select: selectedArtifacts,
|
|
7558
|
+
overrides: overrideArtifactsFromOptions(options)
|
|
6524
7559
|
};
|
|
6525
7560
|
} finally {
|
|
6526
7561
|
await rm9(bundle.root, { recursive: true, force: true });
|
|
@@ -6529,9 +7564,17 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
6529
7564
|
function findConfiguredPackage(packages, value) {
|
|
6530
7565
|
return packages.find((pkg) => pkg.name === value || pkg.source === value);
|
|
6531
7566
|
}
|
|
7567
|
+
function findConfiguredPackageForTarget(packages, value, options, target) {
|
|
7568
|
+
const matches = packages.filter((pkg) => pkg.name === value || pkg.source === value);
|
|
7569
|
+
return options.multiAdapterSource ? matches.find((pkg) => pkg.adapter === target.adapter) : matches[0];
|
|
7570
|
+
}
|
|
6532
7571
|
function noDepsFromOptions(options) {
|
|
6533
7572
|
return options.noDeps === true || options.deps === false;
|
|
6534
7573
|
}
|
|
7574
|
+
function packageEntryWithAdapterSuffix(entry) {
|
|
7575
|
+
const suffix = `-${entry.adapter}`;
|
|
7576
|
+
return entry.name.endsWith(suffix) ? entry : { ...entry, name: `${entry.name}${suffix}` };
|
|
7577
|
+
}
|
|
6535
7578
|
function teachingInstallError(input, cause) {
|
|
6536
7579
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
6537
7580
|
return new Error(
|
|
@@ -6545,28 +7588,103 @@ Resolver error: ${message}`
|
|
|
6545
7588
|
function nextInstallNudge() {
|
|
6546
7589
|
return "Preview: agentwheel plan - Apply: agentwheel install";
|
|
6547
7590
|
}
|
|
6548
|
-
async function resolveCliTargets(options) {
|
|
6549
|
-
|
|
7591
|
+
async function resolveCliTargets(options, behavior = {}) {
|
|
7592
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
7593
|
+
if (normalizedOptions.all && normalizedOptions.allDetected) {
|
|
6550
7594
|
throw new Error("Choose either --all for configured agents or --all-detected for detected runtime directories.");
|
|
6551
7595
|
}
|
|
6552
|
-
if (
|
|
6553
|
-
|
|
7596
|
+
if (normalizedOptions.profile && (normalizedOptions.all || normalizedOptions.allDetected || normalizedOptions.agent)) {
|
|
7597
|
+
throw new Error("--profile cannot be combined with --all, --all-detected, or --agent.");
|
|
7598
|
+
}
|
|
7599
|
+
const adapters2 = adapterListFromOption(normalizedOptions.adapter);
|
|
7600
|
+
if (adapters2.length > 1) {
|
|
7601
|
+
if (normalizedOptions.all || normalizedOptions.allDetected || normalizedOptions.agent || normalizedOptions.profile) {
|
|
7602
|
+
throw new Error("--adapter <a,b> cannot be combined with --all, --all-detected, --agent, or --profile. Use a profile for mixed configured targets.");
|
|
7603
|
+
}
|
|
7604
|
+
const targets = [];
|
|
7605
|
+
for (const adapter of adapters2) {
|
|
7606
|
+
targets.push(await resolveRuntimeTarget({
|
|
7607
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7608
|
+
adapter,
|
|
7609
|
+
installationType: normalizedOptions.installationType
|
|
7610
|
+
}));
|
|
7611
|
+
}
|
|
7612
|
+
return targets;
|
|
7613
|
+
}
|
|
7614
|
+
if (normalizedOptions.profile) {
|
|
7615
|
+
return resolveProfileRuntimeTargets({
|
|
7616
|
+
cwd: process.cwd(),
|
|
7617
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7618
|
+
installationType: normalizedOptions.installationType,
|
|
7619
|
+
profile: normalizedOptions.profile
|
|
7620
|
+
});
|
|
7621
|
+
}
|
|
7622
|
+
if (normalizedOptions.all) {
|
|
7623
|
+
if (behavior.preferAllProfile && !normalizedOptions.agent) {
|
|
7624
|
+
const profileTargets = await tryResolveProfileAllRuntimeTargets(normalizedOptions);
|
|
7625
|
+
if (profileTargets) return profileTargets;
|
|
7626
|
+
}
|
|
7627
|
+
return resolveAllRuntimeTargets({
|
|
7628
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7629
|
+
adapter: normalizedOptions.adapter,
|
|
7630
|
+
installationType: normalizedOptions.installationType,
|
|
7631
|
+
agent: normalizedOptions.agent,
|
|
7632
|
+
all: normalizedOptions.all
|
|
7633
|
+
});
|
|
6554
7634
|
}
|
|
6555
|
-
if (
|
|
6556
|
-
return resolveAllDetectedRuntimeTargets({
|
|
7635
|
+
if (normalizedOptions.allDetected) {
|
|
7636
|
+
return resolveAllDetectedRuntimeTargets({
|
|
7637
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7638
|
+
adapter: normalizedOptions.adapter,
|
|
7639
|
+
installationType: normalizedOptions.installationType,
|
|
7640
|
+
agent: normalizedOptions.agent,
|
|
7641
|
+
allDetected: normalizedOptions.allDetected
|
|
7642
|
+
});
|
|
6557
7643
|
}
|
|
6558
|
-
return [await resolveRuntimeTarget({
|
|
7644
|
+
return [await resolveRuntimeTarget({
|
|
7645
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
7646
|
+
adapter: normalizedOptions.adapter,
|
|
7647
|
+
installationType: normalizedOptions.installationType,
|
|
7648
|
+
agent: normalizedOptions.agent
|
|
7649
|
+
})];
|
|
7650
|
+
}
|
|
7651
|
+
async function tryResolveProfileAllRuntimeTargets(options) {
|
|
7652
|
+
try {
|
|
7653
|
+
return await resolveProfileRuntimeTargets({
|
|
7654
|
+
cwd: process.cwd(),
|
|
7655
|
+
targetRoot: options.targetRoot,
|
|
7656
|
+
installationType: options.installationType,
|
|
7657
|
+
profile: "all"
|
|
7658
|
+
});
|
|
7659
|
+
} catch (error) {
|
|
7660
|
+
if (error instanceof Error && error.message === "Unknown profile: all") return void 0;
|
|
7661
|
+
throw error;
|
|
7662
|
+
}
|
|
7663
|
+
}
|
|
7664
|
+
function optionsForResolvedTarget(options, target) {
|
|
7665
|
+
return adapterListFromOption(options.adapter).length > 1 ? { ...options, adapter: target.adapter, multiAdapterSource: true } : options;
|
|
6559
7666
|
}
|
|
6560
7667
|
async function resolveAdapterForTarget(target, options) {
|
|
7668
|
+
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
6561
7669
|
return resolveAdapter({
|
|
6562
7670
|
adapter: target.adapter,
|
|
6563
|
-
adapterConfig:
|
|
6564
|
-
adapterModule:
|
|
6565
|
-
allowAdapterCode:
|
|
7671
|
+
adapterConfig: adapterOptions.adapterConfig,
|
|
7672
|
+
adapterModule: adapterOptions.adapterModule,
|
|
7673
|
+
allowAdapterCode: adapterOptions.allowAdapterCode,
|
|
6566
7674
|
baseDir: target.workspaceRoot,
|
|
6567
7675
|
warn: (message) => console.warn(message)
|
|
6568
7676
|
});
|
|
6569
7677
|
}
|
|
7678
|
+
function adapterOptionsForTarget(target, options) {
|
|
7679
|
+
return {
|
|
7680
|
+
adapterConfig: options.adapterConfig ?? target.adapterConfig,
|
|
7681
|
+
adapterModule: options.adapterModule ?? target.adapterModule,
|
|
7682
|
+
allowAdapterCode: options.allowAdapterCode
|
|
7683
|
+
};
|
|
7684
|
+
}
|
|
7685
|
+
function targetKeyForTarget(target, adapterName) {
|
|
7686
|
+
return target.targetKey ?? target.agentName ?? adapterName ?? target.source;
|
|
7687
|
+
}
|
|
6570
7688
|
async function runConfiguredGraphPackages(target, options, behavior) {
|
|
6571
7689
|
const results = await buildGraphPlansForTarget(target, void 0, options, behavior);
|
|
6572
7690
|
for (const result of results) {
|
|
@@ -6586,37 +7704,39 @@ async function runConfiguredGraphPackages(target, options, behavior) {
|
|
|
6586
7704
|
}
|
|
6587
7705
|
}
|
|
6588
7706
|
async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
7707
|
+
const targetOptions = optionsForResolvedTarget(options, target);
|
|
6589
7708
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
6590
7709
|
const groups = /* @__PURE__ */ new Map();
|
|
6591
|
-
const selectedArtifacts = selectedArtifactsFromOptions(
|
|
6592
|
-
const scopedPackage =
|
|
6593
|
-
const scopedRootId = scopedPackage?.name ?? (source ?
|
|
6594
|
-
if (
|
|
6595
|
-
if (!source || !
|
|
7710
|
+
const selectedArtifacts = selectedArtifactsFromOptions(targetOptions);
|
|
7711
|
+
const scopedPackage = targetOptions.scope ? findConfiguredPackage(config.packages, targetOptions.scope) : void 0;
|
|
7712
|
+
const scopedRootId = scopedPackage?.name ?? (source ? targetOptions.scope : void 0);
|
|
7713
|
+
if (targetOptions.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${targetOptions.scope}`);
|
|
7714
|
+
if (!source || !targetOptions.onlySource) {
|
|
6596
7715
|
for (const pkg of config.packages) {
|
|
6597
|
-
const group = graphGroupForPackage(groups, target, pkg,
|
|
7716
|
+
const group = graphGroupForPackage(groups, target, pkg, targetOptions);
|
|
6598
7717
|
group.packages.push(pkg);
|
|
6599
7718
|
}
|
|
6600
7719
|
}
|
|
6601
7720
|
if (source) {
|
|
7721
|
+
let entry = targetOptions.extraPackage ?? await packageEntryFromSource(source, target.workspaceRoot, targetOptions);
|
|
7722
|
+
if (targetOptions.multiAdapterSource) {
|
|
7723
|
+
entry = packageEntryWithAdapterSuffix(entry);
|
|
7724
|
+
}
|
|
6602
7725
|
const sourceTarget = target;
|
|
7726
|
+
const sourceInstallationType = targetOptions.installationType ?? entry.installationType ?? sourceTarget.installationType ?? "local";
|
|
7727
|
+
const sourceAdapterOptions = adapterOptionsForTarget(sourceTarget, targetOptions);
|
|
6603
7728
|
const key = graphGroupKey(sourceTarget, {
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
allowAdapterCode: options.allowAdapterCode
|
|
7729
|
+
installationType: sourceInstallationType,
|
|
7730
|
+
...sourceAdapterOptions
|
|
6607
7731
|
});
|
|
6608
7732
|
const group = groups.get(key) ?? {
|
|
6609
7733
|
target: sourceTarget,
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
adapterModule: options.adapterModule,
|
|
6613
|
-
allowAdapterCode: options.allowAdapterCode
|
|
6614
|
-
},
|
|
7734
|
+
installationType: sourceInstallationType,
|
|
7735
|
+
adapterOptions: sourceAdapterOptions,
|
|
6615
7736
|
packages: [],
|
|
6616
7737
|
extraRoots: [],
|
|
6617
7738
|
extraPackages: []
|
|
6618
7739
|
};
|
|
6619
|
-
const entry = options.extraPackage ?? await packageEntryFromSource(source, target.workspaceRoot, options);
|
|
6620
7740
|
group.extraPackages.push(entry);
|
|
6621
7741
|
groups.set(key, group);
|
|
6622
7742
|
}
|
|
@@ -6629,19 +7749,21 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
6629
7749
|
const adapter = await resolveAdapterForTarget(group.target, group.adapterOptions);
|
|
6630
7750
|
const transport = transportForTarget(group.target);
|
|
6631
7751
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
6632
|
-
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source ===
|
|
7752
|
+
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
6633
7753
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
6634
7754
|
const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
|
|
6635
7755
|
const roots = [
|
|
6636
7756
|
...allPackages.map((pkg) => {
|
|
6637
7757
|
const updateThisPackage = behavior.mode === "update" && pkg.mode === "tracking" && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
|
|
7758
|
+
const packageIsScoped = scopedRootId ? pkg.name === scopedRootId || pkg.source === targetOptions.scope : true;
|
|
6638
7759
|
return {
|
|
6639
7760
|
rootId: pkg.name,
|
|
6640
7761
|
source: pkg.source,
|
|
6641
7762
|
mode: pkg.mode,
|
|
6642
7763
|
ref: pkg.requestedRef,
|
|
6643
|
-
select: selectedArtifacts
|
|
7764
|
+
select: selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
6644
7765
|
aliases: pkg.aliases,
|
|
7766
|
+
overrides: pkg.overrides,
|
|
6645
7767
|
useLock: behavior.mode === "install" ? true : !updateThisPackage
|
|
6646
7768
|
};
|
|
6647
7769
|
}),
|
|
@@ -6650,7 +7772,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
6650
7772
|
if (behavior.mode === "update") {
|
|
6651
7773
|
const changed = roots.filter((root) => root.useLock === false);
|
|
6652
7774
|
if (changed.length === 0) {
|
|
6653
|
-
const label =
|
|
7775
|
+
const label = targetOptions.scope ? ` ${targetOptions.scope}` : "";
|
|
6654
7776
|
console.log(`No tracking packages to update${label}.`);
|
|
6655
7777
|
continue;
|
|
6656
7778
|
}
|
|
@@ -6662,19 +7784,24 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
6662
7784
|
workspaceRoot: group.target.workspaceRoot,
|
|
6663
7785
|
adapter,
|
|
6664
7786
|
transport,
|
|
6665
|
-
targetKey: group.target
|
|
6666
|
-
targetFingerprintParts: targetFingerprintParts(group.target, adapter, group.adapterOptions),
|
|
6667
|
-
|
|
7787
|
+
targetKey: targetKeyForTarget(group.target, adapter.name),
|
|
7788
|
+
targetFingerprintParts: targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType),
|
|
7789
|
+
installationType: group.installationType,
|
|
7790
|
+
noDeps: noDepsFromOptions(targetOptions),
|
|
6668
7791
|
lockedResolution: behavior.mode === "install",
|
|
6669
|
-
frozenLock:
|
|
6670
|
-
offline:
|
|
6671
|
-
yes:
|
|
6672
|
-
trustPatterns:
|
|
6673
|
-
readOnly:
|
|
6674
|
-
isTTY: process.stdin.isTTY === true
|
|
7792
|
+
frozenLock: targetOptions.frozenLock,
|
|
7793
|
+
offline: targetOptions.offline,
|
|
7794
|
+
yes: targetOptions.yes,
|
|
7795
|
+
trustPatterns: targetOptions.trust ?? [],
|
|
7796
|
+
readOnly: targetOptions.dryRun === true,
|
|
7797
|
+
isTTY: process.stdin.isTTY === true,
|
|
7798
|
+
forceDrift: targetOptions.forceDrift,
|
|
7799
|
+
forceConflict: targetOptions.forceConflict,
|
|
7800
|
+
replaceConflict: targetOptions.replaceConflict
|
|
6675
7801
|
});
|
|
6676
7802
|
if (behavior.mode === "install" && scopedRootId) {
|
|
6677
|
-
const
|
|
7803
|
+
const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
|
|
7804
|
+
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
6678
7805
|
results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
|
|
6679
7806
|
} else {
|
|
6680
7807
|
results.push(result);
|
|
@@ -6761,7 +7888,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
|
|
|
6761
7888
|
artifactType: entry.artifactType,
|
|
6762
7889
|
artifactName: entry.artifactName,
|
|
6763
7890
|
kind: entry.kind,
|
|
6764
|
-
destPath: operation?.destPath ??
|
|
7891
|
+
destPath: operation?.destPath ?? join31(targetRoot, entry.path),
|
|
6765
7892
|
relativeDestPath: entry.path,
|
|
6766
7893
|
desiredHash: entry.sourceHash,
|
|
6767
7894
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -6797,13 +7924,15 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
6797
7924
|
for (const pkg of removed) {
|
|
6798
7925
|
const removedTarget = targetForPackage(target, pkg, options);
|
|
6799
7926
|
const removedAdapterOptions = {
|
|
6800
|
-
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
6801
|
-
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
7927
|
+
adapterConfig: options.adapterConfig ?? removedTarget.adapterConfig ?? pkg.adapterConfig,
|
|
7928
|
+
adapterModule: options.adapterModule ?? removedTarget.adapterModule ?? pkg.adapterModule,
|
|
6802
7929
|
allowAdapterCode: options.allowAdapterCode
|
|
6803
7930
|
};
|
|
7931
|
+
const removedInstallationType = options.installationType ?? pkg.installationType ?? removedTarget.installationType ?? "local";
|
|
6804
7932
|
const adapter = await resolveAdapterForTarget(removedTarget, removedAdapterOptions);
|
|
6805
7933
|
const transport = transportForTarget(removedTarget);
|
|
6806
|
-
const
|
|
7934
|
+
const removedState = installStateForTarget(removedTarget, adapter, removedAdapterOptions, removedInstallationType);
|
|
7935
|
+
const manifest = await readInstallManifest(removedState.installRoot, adapter.name, transport, removedState);
|
|
6807
7936
|
if (!manifest) {
|
|
6808
7937
|
console.log(`No install manifest for ${adapter.name} at ${removedTarget.targetRoot}`);
|
|
6809
7938
|
continue;
|
|
@@ -6822,14 +7951,16 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
6822
7951
|
mode: pkg2.mode,
|
|
6823
7952
|
ref: pkg2.requestedRef,
|
|
6824
7953
|
select: normalizeArtifactSelectors(pkg2.select, pkg2.skills),
|
|
6825
|
-
aliases: pkg2.aliases
|
|
7954
|
+
aliases: pkg2.aliases,
|
|
7955
|
+
overrides: pkg2.overrides
|
|
6826
7956
|
})),
|
|
6827
7957
|
targetRoot: remainingGroup.target.targetRoot,
|
|
6828
7958
|
workspaceRoot: remainingGroup.target.workspaceRoot,
|
|
6829
7959
|
adapter: remainingAdapter,
|
|
6830
7960
|
transport,
|
|
6831
|
-
targetKey: remainingGroup.target
|
|
6832
|
-
targetFingerprintParts: targetFingerprintParts(remainingGroup.target, remainingAdapter, remainingGroup.adapterOptions),
|
|
7961
|
+
targetKey: targetKeyForTarget(remainingGroup.target, remainingAdapter.name),
|
|
7962
|
+
targetFingerprintParts: targetFingerprintParts(remainingGroup.target, remainingAdapter, remainingGroup.adapterOptions, remainingGroup.installationType),
|
|
7963
|
+
installationType: remainingGroup.installationType,
|
|
6833
7964
|
lockedResolution: true,
|
|
6834
7965
|
frozenLock: options.frozenLock,
|
|
6835
7966
|
offline: options.offline,
|
|
@@ -6848,9 +7979,9 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
6848
7979
|
const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
|
|
6849
7980
|
removeGraphLockPath: graphLockPathForTarget(
|
|
6850
7981
|
removedTarget.workspaceRoot,
|
|
6851
|
-
removedTarget
|
|
7982
|
+
targetKeyForTarget(removedTarget, adapter.name),
|
|
6852
7983
|
adapter.name,
|
|
6853
|
-
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions)
|
|
7984
|
+
targetFingerprintParts(removedTarget, adapter, removedAdapterOptions, removedInstallationType)
|
|
6854
7985
|
)
|
|
6855
7986
|
};
|
|
6856
7987
|
const result = await uninstall(plan, {
|
|
@@ -6873,16 +8004,18 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
6873
8004
|
}
|
|
6874
8005
|
function graphGroupForPackage(groups, target, pkg, options) {
|
|
6875
8006
|
const packageTarget = targetForPackage(target, pkg, options);
|
|
8007
|
+
const installationType = options.installationType ?? pkg.installationType ?? packageTarget.installationType ?? "local";
|
|
6876
8008
|
const adapterOptions = {
|
|
6877
|
-
adapterConfig: options.adapterConfig ?? pkg.adapterConfig,
|
|
6878
|
-
adapterModule: options.adapterModule ?? pkg.adapterModule,
|
|
8009
|
+
adapterConfig: options.adapterConfig ?? packageTarget.adapterConfig ?? pkg.adapterConfig,
|
|
8010
|
+
adapterModule: options.adapterModule ?? packageTarget.adapterModule ?? pkg.adapterModule,
|
|
6879
8011
|
allowAdapterCode: options.allowAdapterCode
|
|
6880
8012
|
};
|
|
6881
|
-
const key = graphGroupKey(packageTarget, adapterOptions);
|
|
8013
|
+
const key = graphGroupKey(packageTarget, { ...adapterOptions, installationType });
|
|
6882
8014
|
const existing = groups.get(key);
|
|
6883
8015
|
if (existing) return existing;
|
|
6884
8016
|
const created = {
|
|
6885
8017
|
target: packageTarget,
|
|
8018
|
+
installationType,
|
|
6886
8019
|
adapterOptions,
|
|
6887
8020
|
packages: [],
|
|
6888
8021
|
extraRoots: [],
|
|
@@ -6892,11 +8025,13 @@ function graphGroupForPackage(groups, target, pkg, options) {
|
|
|
6892
8025
|
return created;
|
|
6893
8026
|
}
|
|
6894
8027
|
function targetForPackage(target, pkg, options) {
|
|
6895
|
-
|
|
8028
|
+
const installationType = options.installationType ?? pkg.installationType ?? target.installationType;
|
|
8029
|
+
return options.adapter || target.source !== "cwd" ? { ...target, installationType } : { ...target, adapter: pkg.adapter, installationType };
|
|
6896
8030
|
}
|
|
6897
8031
|
function graphGroupKey(target, options) {
|
|
6898
8032
|
return JSON.stringify({
|
|
6899
8033
|
adapter: target.adapter,
|
|
8034
|
+
installationType: options.installationType ?? target.installationType ?? "local",
|
|
6900
8035
|
targetRoot: target.targetRoot,
|
|
6901
8036
|
transport: target.transport,
|
|
6902
8037
|
agentName: target.agentName,
|
|
@@ -6904,9 +8039,10 @@ function graphGroupKey(target, options) {
|
|
|
6904
8039
|
adapterModule: options.adapterModule
|
|
6905
8040
|
});
|
|
6906
8041
|
}
|
|
6907
|
-
function targetFingerprintParts(target, adapter, options) {
|
|
8042
|
+
function targetFingerprintParts(target, adapter, options, installationType) {
|
|
6908
8043
|
return {
|
|
6909
8044
|
adapter: adapter.name,
|
|
8045
|
+
installationType: installationType ?? target.installationType ?? "local",
|
|
6910
8046
|
adapterConfig: options.adapterConfig,
|
|
6911
8047
|
adapterModule: options.adapterModule,
|
|
6912
8048
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
@@ -6916,13 +8052,27 @@ function targetFingerprintParts(target, adapter, options) {
|
|
|
6916
8052
|
ssh: target.ssh
|
|
6917
8053
|
};
|
|
6918
8054
|
}
|
|
8055
|
+
function installStateForTarget(target, adapter, options, installationType) {
|
|
8056
|
+
const targetFingerprint = targetFingerprintDigest(target, adapter, options, installationType);
|
|
8057
|
+
return {
|
|
8058
|
+
installationType,
|
|
8059
|
+
stateKey: stateKeyFor(adapter.name, { installationType, targetFingerprint }),
|
|
8060
|
+
installRoot: installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh")
|
|
8061
|
+
};
|
|
8062
|
+
}
|
|
8063
|
+
function targetFingerprintDigest(target, adapter, options, installationType) {
|
|
8064
|
+
const fingerprintInput = targetFingerprintParts(target, adapter, options, installationType);
|
|
8065
|
+
return computeTargetFingerprint(fingerprintInput);
|
|
8066
|
+
}
|
|
6919
8067
|
async function readTargetGraphLock(target, options) {
|
|
6920
|
-
const
|
|
8068
|
+
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
8069
|
+
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
8070
|
+
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
6921
8071
|
const path = graphLockPathForTarget(
|
|
6922
8072
|
target.workspaceRoot,
|
|
6923
|
-
target
|
|
8073
|
+
targetKeyForTarget(target, adapter.name),
|
|
6924
8074
|
adapter.name,
|
|
6925
|
-
targetFingerprintParts(target, adapter,
|
|
8075
|
+
targetFingerprintParts(target, adapter, adapterOptions, installationType)
|
|
6926
8076
|
);
|
|
6927
8077
|
if (!await pathExists(path)) {
|
|
6928
8078
|
throw new Error(`No graph lock for ${adapter.name} at ${target.targetRoot}: ${path}`);
|
|
@@ -6931,9 +8081,12 @@ async function readTargetGraphLock(target, options) {
|
|
|
6931
8081
|
}
|
|
6932
8082
|
async function printStatus(target, options) {
|
|
6933
8083
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
6934
|
-
const
|
|
8084
|
+
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
8085
|
+
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
6935
8086
|
const transport = transportForTarget(target);
|
|
6936
|
-
|
|
8087
|
+
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
8088
|
+
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
8089
|
+
console.log(`Status for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
6937
8090
|
if (config.packages.length === 0) {
|
|
6938
8091
|
console.log(`Configured packages: none at ${target.workspaceRoot}`);
|
|
6939
8092
|
return;
|
|
@@ -6942,10 +8095,10 @@ async function printStatus(target, options) {
|
|
|
6942
8095
|
for (const pkg of config.packages) {
|
|
6943
8096
|
console.log(`- ${pkg.name} (${pkg.mode}) ${pkg.source}`);
|
|
6944
8097
|
}
|
|
6945
|
-
const manifest = await readInstallManifest(
|
|
8098
|
+
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
6946
8099
|
console.log(manifest ? `Install manifest: ${manifest.entries.length} entries, revision ${manifest.revision}` : "Install manifest: missing");
|
|
6947
8100
|
try {
|
|
6948
|
-
const { path, lock } = await readTargetGraphLock(target,
|
|
8101
|
+
const { path, lock } = await readTargetGraphLock(target, adapterOptions);
|
|
6949
8102
|
console.log(`Graph lock: ${path}`);
|
|
6950
8103
|
console.log(`Locked graph: ${lock.canonical.roots.length} roots, ${lock.canonical.nodes.length} nodes, ${lock.canonical.artifacts.length} artifacts`);
|
|
6951
8104
|
} catch {
|
|
@@ -6958,7 +8111,7 @@ async function printPendingInstallWork(target, options) {
|
|
|
6958
8111
|
try {
|
|
6959
8112
|
results = await buildGraphPlansForTarget(target, void 0, { ...options, dryRun: true }, { mode: "install" });
|
|
6960
8113
|
const operations = results.flatMap((result) => result.plan.operations);
|
|
6961
|
-
const pending = operations.filter(
|
|
8114
|
+
const pending = operations.filter(isPendingInstallOperation);
|
|
6962
8115
|
if (pending.length === 0) {
|
|
6963
8116
|
console.log("Pending install work: none");
|
|
6964
8117
|
return;
|
|
@@ -6985,9 +8138,67 @@ function collectSkillOption(value, previous) {
|
|
|
6985
8138
|
function collectTrustOption(value, previous) {
|
|
6986
8139
|
return [...previous, value];
|
|
6987
8140
|
}
|
|
8141
|
+
function collectOverrideOption(value, previous) {
|
|
8142
|
+
return [...previous, ...splitSelectorList(value)];
|
|
8143
|
+
}
|
|
8144
|
+
function normalizeRuntimeScopeOptions(options, behavior = {}) {
|
|
8145
|
+
if (options.user && options.local) {
|
|
8146
|
+
throw new Error("Choose either --user or --local.");
|
|
8147
|
+
}
|
|
8148
|
+
const shortcutType = options.user ? "user" : options.local ? "local" : void 0;
|
|
8149
|
+
if (shortcutType && options.installationType && options.installationType !== shortcutType) {
|
|
8150
|
+
throw new Error(`--${shortcutType} conflicts with --installation-type ${options.installationType}.`);
|
|
8151
|
+
}
|
|
8152
|
+
let targetRoot = options.targetRoot ? normalizeCliPath(options.targetRoot) : void 0;
|
|
8153
|
+
let installationType = options.installationType ?? shortcutType;
|
|
8154
|
+
if (!installationType && targetRoot) {
|
|
8155
|
+
installationType = isHomePath(targetRoot) ? "user" : "local";
|
|
8156
|
+
}
|
|
8157
|
+
const canDefaultTargetRoot = !options.agent && !options.all && !options.allDetected && !options.profile;
|
|
8158
|
+
if (!targetRoot && canDefaultTargetRoot && (options.user || installationType === "user" || behavior.defaultUser)) {
|
|
8159
|
+
targetRoot = homedir9();
|
|
8160
|
+
}
|
|
8161
|
+
if (!installationType && behavior.defaultUser) {
|
|
8162
|
+
installationType = "user";
|
|
8163
|
+
}
|
|
8164
|
+
return {
|
|
8165
|
+
...options,
|
|
8166
|
+
installationType,
|
|
8167
|
+
targetRoot
|
|
8168
|
+
};
|
|
8169
|
+
}
|
|
8170
|
+
function shouldDefaultUserInstall(nameOrSource, options) {
|
|
8171
|
+
return Boolean(
|
|
8172
|
+
nameOrSource && options.adapter && !options.installationType && !options.user && !options.local && !options.targetRoot && !options.agent && !options.all && !options.allDetected && !options.profile && looksLikeSourceSpecifier(nameOrSource)
|
|
8173
|
+
);
|
|
8174
|
+
}
|
|
8175
|
+
function looksLikeSourceSpecifier(value) {
|
|
8176
|
+
return value.includes(":") || value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value === "~" || value.startsWith("~/");
|
|
8177
|
+
}
|
|
8178
|
+
function normalizeCliPath(value) {
|
|
8179
|
+
if (value === "~") return homedir9();
|
|
8180
|
+
if (value.startsWith("~/")) return resolve18(homedir9(), value.slice(2));
|
|
8181
|
+
return resolve18(value);
|
|
8182
|
+
}
|
|
8183
|
+
function isHomePath(path) {
|
|
8184
|
+
return resolve18(path) === resolve18(homedir9());
|
|
8185
|
+
}
|
|
8186
|
+
function adapterListFromOption(adapter) {
|
|
8187
|
+
if (!adapter) return [];
|
|
8188
|
+
const adapters2 = splitSelectorList(adapter);
|
|
8189
|
+
const unique = [...new Set(adapters2)];
|
|
8190
|
+
if (unique.length !== adapters2.length) {
|
|
8191
|
+
throw new Error(`Duplicate adapter in --adapter: ${adapter}`);
|
|
8192
|
+
}
|
|
8193
|
+
return unique;
|
|
8194
|
+
}
|
|
6988
8195
|
function selectedArtifactsFromOptions(options) {
|
|
6989
8196
|
return normalizeArtifactSelectors(options.select, options.skills ?? options.skill);
|
|
6990
8197
|
}
|
|
8198
|
+
function overrideArtifactsFromOptions(options) {
|
|
8199
|
+
const values = options.overrides ?? options.override;
|
|
8200
|
+
return values && values.length > 0 ? values : void 0;
|
|
8201
|
+
}
|
|
6991
8202
|
function filterUninstallPlanBySelection(plan, selected) {
|
|
6992
8203
|
if (!selected?.length) return plan;
|
|
6993
8204
|
const requested = normalizeArtifactSelectors(selected) ?? [];
|
|
@@ -7013,10 +8224,10 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
7013
8224
|
};
|
|
7014
8225
|
}
|
|
7015
8226
|
async function initPackage(root) {
|
|
7016
|
-
await
|
|
7017
|
-
await
|
|
7018
|
-
await
|
|
7019
|
-
const manifestPath =
|
|
8227
|
+
await mkdir16(join31(root, "instructions"), { recursive: true });
|
|
8228
|
+
await mkdir16(join31(root, "rules"), { recursive: true });
|
|
8229
|
+
await mkdir16(join31(root, "skills"), { recursive: true });
|
|
8230
|
+
const manifestPath = join31(root, "openpack.json");
|
|
7020
8231
|
const manifest = {
|
|
7021
8232
|
schemaVersion: 2,
|
|
7022
8233
|
name: "example/agentwheel-package",
|
|
@@ -7027,18 +8238,19 @@ async function initPackage(root) {
|
|
|
7027
8238
|
{ type: "skills", path: "skills" }
|
|
7028
8239
|
]
|
|
7029
8240
|
};
|
|
7030
|
-
await
|
|
8241
|
+
await writeFile14(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
7031
8242
|
`, "utf8");
|
|
7032
|
-
await
|
|
8243
|
+
await writeFile14(join31(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
7033
8244
|
}
|
|
7034
8245
|
async function defaultBootstrapPackage(_root) {
|
|
7035
|
-
const packageRoot = await findAgentwheelPackageRoot(
|
|
8246
|
+
const packageRoot = await findAgentwheelPackageRoot(dirname23(fileURLToPath3(import.meta.url)));
|
|
7036
8247
|
if (!packageRoot) return void 0;
|
|
7037
8248
|
return {
|
|
7038
8249
|
name: "agentwheel",
|
|
7039
8250
|
source: packageRoot,
|
|
7040
8251
|
driver: "local",
|
|
7041
8252
|
adapter: "openclaw",
|
|
8253
|
+
installationType: "local",
|
|
7042
8254
|
mode: "tracking",
|
|
7043
8255
|
select: ["skills/agentwheel"]
|
|
7044
8256
|
};
|
|
@@ -7055,7 +8267,7 @@ function withFleetExample(config) {
|
|
|
7055
8267
|
},
|
|
7056
8268
|
"remote-codex": config.agents["remote-codex"] ?? {
|
|
7057
8269
|
adapter: "codex",
|
|
7058
|
-
root: "/
|
|
8270
|
+
root: "/workspace/agent-runtime",
|
|
7059
8271
|
transport: "ssh",
|
|
7060
8272
|
host: "remote-host.example",
|
|
7061
8273
|
user: "administrator",
|
|
@@ -7078,7 +8290,7 @@ async function findAgentwheelPackageRoot(start) {
|
|
|
7078
8290
|
let current = resolve18(start);
|
|
7079
8291
|
while (true) {
|
|
7080
8292
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
7081
|
-
const parent =
|
|
8293
|
+
const parent = dirname23(current);
|
|
7082
8294
|
if (parent === current) return void 0;
|
|
7083
8295
|
current = parent;
|
|
7084
8296
|
}
|
|
@@ -7108,7 +8320,9 @@ async function main() {
|
|
|
7108
8320
|
});
|
|
7109
8321
|
await program.parseAsync();
|
|
7110
8322
|
}
|
|
7111
|
-
|
|
8323
|
+
try {
|
|
8324
|
+
await main();
|
|
8325
|
+
} catch (error) {
|
|
7112
8326
|
console.error(error instanceof Error ? error.message : String(error));
|
|
7113
8327
|
process.exitCode = 1;
|
|
7114
|
-
}
|
|
8328
|
+
}
|