agentwheel 0.14.3 → 0.14.4
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 +28 -13
- package/dist/index.js +1564 -449
- package/openpack.json +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
} from "./chunk-B3FMBTWC.js";
|
|
10
10
|
|
|
11
11
|
// src/cli/index.ts
|
|
12
|
-
import { mkdir as
|
|
12
|
+
import { mkdir as mkdir17, rm as rm10, writeFile as writeFile16 } from "fs/promises";
|
|
13
13
|
import { homedir as homedir9 } from "os";
|
|
14
|
-
import { dirname as
|
|
14
|
+
import { dirname as dirname25, join as join38, resolve as resolve18 } from "path";
|
|
15
15
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
|
|
@@ -91,8 +91,19 @@ var targetMappingSchema = z2.object({
|
|
|
91
91
|
enabled: z2.boolean().default(true),
|
|
92
92
|
root: z2.enum(["target", "home"]).optional(),
|
|
93
93
|
formats: z2.array(z2.string().min(1)).optional(),
|
|
94
|
-
semantic: z2.enum([
|
|
95
|
-
|
|
94
|
+
semantic: z2.enum([
|
|
95
|
+
"openclaw-plugin",
|
|
96
|
+
"claude-plugin",
|
|
97
|
+
"codex-plugin",
|
|
98
|
+
"hermes-plugin",
|
|
99
|
+
"copilot-plugin",
|
|
100
|
+
"codex-subagent",
|
|
101
|
+
"copilot-instruction",
|
|
102
|
+
"copilot-prompt",
|
|
103
|
+
"copilot-agent"
|
|
104
|
+
]).optional(),
|
|
105
|
+
merge: z2.enum(["json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
|
|
106
|
+
mode: z2.enum(["managed-block"]).optional()
|
|
96
107
|
});
|
|
97
108
|
var targetRegistrySchema = z2.record(installationTypeSchema, targetMappingSchema);
|
|
98
109
|
var targetRegistryInputSchema = z2.union([
|
|
@@ -203,8 +214,8 @@ var claudeAdapter = {
|
|
|
203
214
|
displayName: "Claude Code",
|
|
204
215
|
targets: {
|
|
205
216
|
instructions: {
|
|
206
|
-
local: { enabled: true, dest: "CLAUDE.md" },
|
|
207
|
-
user: { enabled: true, root: "home", dest: ".claude/CLAUDE.md" }
|
|
217
|
+
local: { enabled: true, dest: "CLAUDE.md", mode: "managed-block" },
|
|
218
|
+
user: { enabled: true, root: "home", dest: ".claude/CLAUDE.md", mode: "managed-block" }
|
|
208
219
|
},
|
|
209
220
|
rules: {
|
|
210
221
|
local: { enabled: true, dest: ".claude/rules", formats: ["markdown-rule", "claude-markdown-rule"] },
|
|
@@ -222,6 +233,10 @@ var claudeAdapter = {
|
|
|
222
233
|
local: { enabled: true, dest: ".claude/agents" },
|
|
223
234
|
user: { enabled: true, root: "home", dest: ".claude/agents" }
|
|
224
235
|
},
|
|
236
|
+
plugins: {
|
|
237
|
+
local: { enabled: true, dest: ".agentwheel/plugins/claude", semantic: "claude-plugin" },
|
|
238
|
+
user: { enabled: true, root: "home", dest: ".agentwheel/plugins/claude", semantic: "claude-plugin" }
|
|
239
|
+
},
|
|
225
240
|
mcp: {
|
|
226
241
|
local: { enabled: true, dest: ".mcp.json", merge: "json-deep" }
|
|
227
242
|
},
|
|
@@ -242,8 +257,8 @@ var copilotAdapter = {
|
|
|
242
257
|
displayName: "GitHub Copilot CLI",
|
|
243
258
|
targets: {
|
|
244
259
|
instructions: {
|
|
245
|
-
local: { enabled: true, dest: ".github/copilot-instructions.md" },
|
|
246
|
-
user: { enabled: true, root: "home", dest: ".copilot/copilot-instructions.md" }
|
|
260
|
+
local: { enabled: true, dest: ".github/copilot-instructions.md", mode: "managed-block" },
|
|
261
|
+
user: { enabled: true, root: "home", dest: ".copilot/copilot-instructions.md", mode: "managed-block" }
|
|
247
262
|
},
|
|
248
263
|
rules: {
|
|
249
264
|
local: { enabled: true, dest: ".github/instructions", formats: ["markdown-rule", "copilot-instruction-rule"], semantic: "copilot-instruction" },
|
|
@@ -256,6 +271,9 @@ var copilotAdapter = {
|
|
|
256
271
|
local: { enabled: true, dest: ".github/skills" },
|
|
257
272
|
user: { enabled: true, root: "home", dest: ".copilot/skills" }
|
|
258
273
|
},
|
|
274
|
+
plugins: {
|
|
275
|
+
user: { enabled: true, root: "home", dest: ".agentwheel/plugins/copilot", semantic: "copilot-plugin" }
|
|
276
|
+
},
|
|
259
277
|
subagents: {
|
|
260
278
|
local: { enabled: true, dest: ".github/agents", semantic: "copilot-agent" },
|
|
261
279
|
user: { enabled: true, root: "home", dest: ".copilot/agents", semantic: "copilot-agent" }
|
|
@@ -267,6 +285,10 @@ var copilotAdapter = {
|
|
|
267
285
|
hooks: {
|
|
268
286
|
local: { enabled: true, dest: ".github/hooks" },
|
|
269
287
|
user: { enabled: true, root: "home", dest: ".copilot/hooks" }
|
|
288
|
+
},
|
|
289
|
+
settings: {
|
|
290
|
+
local: { enabled: true, dest: ".github/settings.json", merge: "json-deep" },
|
|
291
|
+
user: { enabled: true, root: "home", dest: ".copilot/settings.json", merge: "json-deep" }
|
|
270
292
|
}
|
|
271
293
|
}
|
|
272
294
|
};
|
|
@@ -277,17 +299,17 @@ var codexAdapter = {
|
|
|
277
299
|
displayName: "Codex CLI",
|
|
278
300
|
targets: {
|
|
279
301
|
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"] }
|
|
302
|
+
local: { enabled: true, dest: "AGENTS.md", mode: "managed-block" },
|
|
303
|
+
user: { enabled: true, root: "home", dest: ".codex/AGENTS.md", mode: "managed-block" }
|
|
286
304
|
},
|
|
287
305
|
skills: {
|
|
288
306
|
local: { enabled: true, dest: ".agents/skills" },
|
|
289
307
|
user: { enabled: true, root: "home", dest: ".agents/skills" }
|
|
290
308
|
},
|
|
309
|
+
plugins: {
|
|
310
|
+
local: { enabled: true, dest: ".agentwheel/plugins/codex", semantic: "codex-plugin" },
|
|
311
|
+
user: { enabled: true, root: "home", dest: ".agentwheel/plugins/codex", semantic: "codex-plugin" }
|
|
312
|
+
},
|
|
291
313
|
subagents: {
|
|
292
314
|
local: { enabled: true, dest: ".codex/agents", semantic: "codex-subagent" },
|
|
293
315
|
user: { enabled: true, root: "home", dest: ".codex/agents", semantic: "codex-subagent" }
|
|
@@ -309,10 +331,21 @@ var hermesAdapter = {
|
|
|
309
331
|
displayName: "Hermes",
|
|
310
332
|
targets: {
|
|
311
333
|
instructions: {
|
|
312
|
-
local: { enabled: true, dest: "AGENTS.md" }
|
|
334
|
+
local: { enabled: true, dest: "AGENTS.md", mode: "managed-block" },
|
|
335
|
+
// Hermes documents SOUL.md as the user-level instruction surface.
|
|
336
|
+
user: { enabled: true, root: "home", dest: ".hermes/SOUL.md", mode: "managed-block" }
|
|
313
337
|
},
|
|
314
338
|
skills: {
|
|
315
339
|
user: { enabled: true, root: "home", dest: ".hermes/skills" }
|
|
340
|
+
},
|
|
341
|
+
mcp: {
|
|
342
|
+
user: { enabled: true, root: "home", dest: ".hermes/config.yaml", merge: "yaml-deep" }
|
|
343
|
+
},
|
|
344
|
+
settings: {
|
|
345
|
+
user: { enabled: true, root: "home", dest: ".hermes/config.yaml", merge: "yaml-deep" }
|
|
346
|
+
},
|
|
347
|
+
plugins: {
|
|
348
|
+
user: { enabled: true, root: "home", dest: ".agentwheel/plugins/hermes", semantic: "hermes-plugin" }
|
|
316
349
|
}
|
|
317
350
|
}
|
|
318
351
|
};
|
|
@@ -322,10 +355,19 @@ var openClawAdapter = {
|
|
|
322
355
|
name: "openclaw",
|
|
323
356
|
displayName: "OpenClaw",
|
|
324
357
|
targets: {
|
|
358
|
+
instructions: {
|
|
359
|
+
user: { enabled: true, root: "home", dest: ".openclaw/workspace/AGENTS.md", mode: "managed-block" }
|
|
360
|
+
},
|
|
325
361
|
skills: {
|
|
326
362
|
local: { enabled: true, dest: "skills" },
|
|
327
363
|
user: { enabled: true, root: "home", dest: ".openclaw/skills" }
|
|
328
364
|
},
|
|
365
|
+
mcp: {
|
|
366
|
+
user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "json-deep" }
|
|
367
|
+
},
|
|
368
|
+
settings: {
|
|
369
|
+
user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "json-deep" }
|
|
370
|
+
},
|
|
329
371
|
plugins: {
|
|
330
372
|
local: { enabled: true, dest: ".openclaw/plugins", formats: ["openclaw-plugin"], semantic: "openclaw-plugin" }
|
|
331
373
|
}
|
|
@@ -411,9 +453,9 @@ async function resolveAdapter(options) {
|
|
|
411
453
|
|
|
412
454
|
// src/install/apply.ts
|
|
413
455
|
import { execFile as execFile3 } from "child_process";
|
|
414
|
-
import { mkdtemp, rm as
|
|
415
|
-
import { tmpdir as
|
|
416
|
-
import { basename as
|
|
456
|
+
import { mkdtemp as mkdtemp2, rm as rm4, writeFile as writeFile8 } from "fs/promises";
|
|
457
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
458
|
+
import { basename as basename3, join as join5 } from "path";
|
|
417
459
|
import { promisify as promisify3 } from "util";
|
|
418
460
|
|
|
419
461
|
// src/model/graph-lock.ts
|
|
@@ -835,6 +877,16 @@ import { resolve as resolve3 } from "path";
|
|
|
835
877
|
import { z as z4 } from "zod";
|
|
836
878
|
var dependencyRoleSchema = z4.enum(["root", "direct", "transitive", "fragment"]);
|
|
837
879
|
var legacyUnownedWorkspaceOwner = "legacy:unowned";
|
|
880
|
+
var semanticPluginSpecSchema = z4.object({
|
|
881
|
+
runtime: z4.enum(["openclaw", "claude", "codex", "copilot", "hermes"]),
|
|
882
|
+
pluginName: z4.string().min(1),
|
|
883
|
+
marketplaceName: z4.string().min(1).optional(),
|
|
884
|
+
stateRoot: z4.string().min(1).optional(),
|
|
885
|
+
installCommands: z4.array(z4.array(z4.string()).min(1)).min(1),
|
|
886
|
+
uninstallCommands: z4.array(z4.array(z4.string()).min(1)).min(1),
|
|
887
|
+
enableCommands: z4.array(z4.array(z4.string()).min(1)).optional(),
|
|
888
|
+
disableCommands: z4.array(z4.array(z4.string()).min(1)).optional()
|
|
889
|
+
});
|
|
838
890
|
var manifestEntryV1Schema = z4.object({
|
|
839
891
|
path: z4.string().min(1),
|
|
840
892
|
artifactType: artifactTypeSchema,
|
|
@@ -846,8 +898,10 @@ var manifestEntryV1Schema = z4.object({
|
|
|
846
898
|
channel: z4.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
|
|
847
899
|
packageName: z4.string().min(1).optional(),
|
|
848
900
|
semanticCommand: z4.array(z4.string()).optional(),
|
|
901
|
+
semanticPlugin: semanticPluginSpecSchema.optional(),
|
|
849
902
|
executed: z4.boolean().optional(),
|
|
850
|
-
mergeStrategy: z4.enum(["json-deep", "codex-toml-mcp"]).optional(),
|
|
903
|
+
mergeStrategy: z4.enum(["json-deep", "yaml-deep", "codex-toml-mcp"]).optional(),
|
|
904
|
+
mode: z4.enum(["managed-block"]).optional(),
|
|
851
905
|
composedFrom: z4.array(composedFromEntrySchema).optional()
|
|
852
906
|
});
|
|
853
907
|
var manifestEntrySchema = manifestEntryV1Schema.extend({
|
|
@@ -1245,6 +1299,65 @@ function isRecord2(value) {
|
|
|
1245
1299
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1246
1300
|
}
|
|
1247
1301
|
|
|
1302
|
+
// src/install/yaml-merge.ts
|
|
1303
|
+
import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile6 } from "fs/promises";
|
|
1304
|
+
import { dirname as dirname7 } from "path";
|
|
1305
|
+
import { parse as parse2, stringify } from "yaml";
|
|
1306
|
+
async function mergeYamlFile(sourcePath, destPath) {
|
|
1307
|
+
const source = parseYamlValue(await readFile7(sourcePath, "utf8"));
|
|
1308
|
+
const current = await pathExists(destPath) ? parseYamlValue(await readFile7(destPath, "utf8")) : {};
|
|
1309
|
+
const merged = deepMerge2(current, source);
|
|
1310
|
+
await mkdir6(dirname7(destPath), { recursive: true });
|
|
1311
|
+
await writeFile6(destPath, stringify(merged), "utf8");
|
|
1312
|
+
}
|
|
1313
|
+
function parseYamlValue(content) {
|
|
1314
|
+
return normalizeYamlValue(parse2(content));
|
|
1315
|
+
}
|
|
1316
|
+
function normalizeYamlValue(value) {
|
|
1317
|
+
if (value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
|
|
1318
|
+
return value;
|
|
1319
|
+
}
|
|
1320
|
+
if (Array.isArray(value)) return value.map(normalizeYamlValue);
|
|
1321
|
+
if (isPlainObject(value)) {
|
|
1322
|
+
const out = {};
|
|
1323
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1324
|
+
out[key] = normalizeYamlValue(child);
|
|
1325
|
+
}
|
|
1326
|
+
return out;
|
|
1327
|
+
}
|
|
1328
|
+
return value === void 0 ? null : String(value);
|
|
1329
|
+
}
|
|
1330
|
+
function deepMerge2(base, incoming) {
|
|
1331
|
+
if (Array.isArray(base) && Array.isArray(incoming)) {
|
|
1332
|
+
return dedupeArray2([...base, ...incoming]);
|
|
1333
|
+
}
|
|
1334
|
+
if (isRecord3(base) && isRecord3(incoming)) {
|
|
1335
|
+
const out = { ...base };
|
|
1336
|
+
for (const [key, value] of Object.entries(incoming)) {
|
|
1337
|
+
out[key] = key in out ? deepMerge2(out[key], value) : value;
|
|
1338
|
+
}
|
|
1339
|
+
return out;
|
|
1340
|
+
}
|
|
1341
|
+
return incoming;
|
|
1342
|
+
}
|
|
1343
|
+
function isPlainObject(value) {
|
|
1344
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1345
|
+
}
|
|
1346
|
+
function isRecord3(value) {
|
|
1347
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1348
|
+
}
|
|
1349
|
+
function dedupeArray2(values) {
|
|
1350
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1351
|
+
const out = [];
|
|
1352
|
+
for (const value of values) {
|
|
1353
|
+
const key = JSON.stringify(value);
|
|
1354
|
+
if (seen.has(key)) continue;
|
|
1355
|
+
seen.add(key);
|
|
1356
|
+
out.push(value);
|
|
1357
|
+
}
|
|
1358
|
+
return out;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1248
1361
|
// src/install/desired.ts
|
|
1249
1362
|
function normalizeOwners(owners) {
|
|
1250
1363
|
const normalized = [...new Set(owners.map((owner) => owner.trim()).filter(Boolean))].sort();
|
|
@@ -1254,6 +1367,189 @@ function normalizeOwners(owners) {
|
|
|
1254
1367
|
return normalized;
|
|
1255
1368
|
}
|
|
1256
1369
|
|
|
1370
|
+
// src/install/instructions-block.ts
|
|
1371
|
+
import { createHash as createHash3 } from "crypto";
|
|
1372
|
+
import { mkdtemp, readFile as readFile8, realpath, rm as rm3, writeFile as writeFile7 } from "fs/promises";
|
|
1373
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1374
|
+
import { basename as basename2, dirname as dirname8, join as join4, relative as relative2 } from "path";
|
|
1375
|
+
var managedInstructionBlockMode = "managed-block";
|
|
1376
|
+
var managedInstructionBanner = "<!-- agentwheel-managed: edit fragments, not this block -->";
|
|
1377
|
+
async function desiredManagedInstructionBlockHash(sourcePath) {
|
|
1378
|
+
const source = await readFile8(sourcePath, "utf8");
|
|
1379
|
+
return hashText(managedBlockBody(source));
|
|
1380
|
+
}
|
|
1381
|
+
async function readManagedInstructionBlockState(destPath, selector, transport) {
|
|
1382
|
+
if (!await transport.pathExists(destPath)) {
|
|
1383
|
+
return { exists: false, hasBlock: false, drifted: false };
|
|
1384
|
+
}
|
|
1385
|
+
const content = await transport.readFile(destPath);
|
|
1386
|
+
const block = findManagedInstructionBlock(content, selector);
|
|
1387
|
+
if (!block) return { exists: true, hasBlock: false, drifted: false };
|
|
1388
|
+
const hash = hashText(block.body);
|
|
1389
|
+
return {
|
|
1390
|
+
exists: true,
|
|
1391
|
+
hasBlock: true,
|
|
1392
|
+
hash,
|
|
1393
|
+
markerHash: block.markerHash,
|
|
1394
|
+
drifted: hash !== block.markerHash
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
async function writeManagedInstructionBlock(sourcePath, destPath, selector, transport, expectedHash) {
|
|
1398
|
+
const source = await readFile8(sourcePath, "utf8");
|
|
1399
|
+
const desired = renderManagedInstructionBlock(selector, source);
|
|
1400
|
+
const existing = await readOptionalText(destPath, transport);
|
|
1401
|
+
const merged = upsertManagedInstructionBlock(existing ?? "", selector, desired.block, expectedHash);
|
|
1402
|
+
await writeTextWithTransport(destPath, merged, transport);
|
|
1403
|
+
return desired.hash;
|
|
1404
|
+
}
|
|
1405
|
+
async function removeManagedInstructionBlock(destPath, selector, transport, expectedHash) {
|
|
1406
|
+
if (!await transport.pathExists(destPath)) return;
|
|
1407
|
+
const existing = await transport.readFile(destPath);
|
|
1408
|
+
const updated = removeManagedBlockFromContent(existing, selector, expectedHash);
|
|
1409
|
+
await writeTextWithTransport(destPath, updated, transport);
|
|
1410
|
+
}
|
|
1411
|
+
async function managedInstructionBlockLanded(destPath, selector, expectedHash, transport) {
|
|
1412
|
+
if (!expectedHash) return false;
|
|
1413
|
+
const state = await readManagedInstructionBlockState(destPath, selector, transport);
|
|
1414
|
+
return state.exists && state.hasBlock && !state.drifted && state.hash === expectedHash;
|
|
1415
|
+
}
|
|
1416
|
+
async function managedInstructionPhysicalKey(destPath, transport) {
|
|
1417
|
+
if (transport.kind !== "local" || !await transport.pathExists(destPath)) return destPath;
|
|
1418
|
+
return realpath(destPath);
|
|
1419
|
+
}
|
|
1420
|
+
async function claudeInstructionBridgesAgents(claudePath, agentsPath, transport) {
|
|
1421
|
+
if (!await transport.pathExists(claudePath)) return false;
|
|
1422
|
+
if (await samePhysicalPath(claudePath, agentsPath, transport)) return true;
|
|
1423
|
+
const claudeContent = await transport.readFile(claudePath);
|
|
1424
|
+
return referencesAgentsMd(claudeContent, claudePath, agentsPath);
|
|
1425
|
+
}
|
|
1426
|
+
function managedInstructionSelector(selector, artifactType, artifactName) {
|
|
1427
|
+
if (!selector) return `${artifactType}/${artifactName}`;
|
|
1428
|
+
const unscoped = selector.split(":").at(-1);
|
|
1429
|
+
return unscoped?.includes("/") ? unscoped : selector;
|
|
1430
|
+
}
|
|
1431
|
+
function renderManagedInstructionBlock(selector, source) {
|
|
1432
|
+
const body = managedBlockBody(source);
|
|
1433
|
+
const hash = hashText(body);
|
|
1434
|
+
return {
|
|
1435
|
+
block: `<!-- BEGIN openpack:include ${selector} sha256:${hash} -->
|
|
1436
|
+
${body}<!-- END openpack:include ${selector} -->
|
|
1437
|
+
`,
|
|
1438
|
+
hash
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function upsertManagedInstructionBlock(content, selector, block, expectedHash) {
|
|
1442
|
+
const existing = findManagedInstructionBlock(content, selector);
|
|
1443
|
+
if (!existing) {
|
|
1444
|
+
if (expectedHash) throw new Error(`Managed instruction block missing for ${selector}`);
|
|
1445
|
+
return appendManagedInstructionBlock(content, block);
|
|
1446
|
+
}
|
|
1447
|
+
assertCleanBlock(existing, selector);
|
|
1448
|
+
if (expectedHash && hashText(existing.body) !== expectedHash) {
|
|
1449
|
+
throw new Error(`Managed instruction block drift detected for ${selector}`);
|
|
1450
|
+
}
|
|
1451
|
+
return `${content.slice(0, existing.start)}${block}${content.slice(existing.end)}`;
|
|
1452
|
+
}
|
|
1453
|
+
function removeManagedBlockFromContent(content, selector, expectedHash) {
|
|
1454
|
+
const existing = findManagedInstructionBlock(content, selector);
|
|
1455
|
+
if (!existing) return content;
|
|
1456
|
+
assertCleanBlock(existing, selector);
|
|
1457
|
+
if (expectedHash && hashText(existing.body) !== expectedHash) {
|
|
1458
|
+
throw new Error(`Managed instruction block drift detected for ${selector}`);
|
|
1459
|
+
}
|
|
1460
|
+
return `${content.slice(0, existing.start)}${content.slice(existing.end)}`;
|
|
1461
|
+
}
|
|
1462
|
+
function findManagedInstructionBlock(content, selector) {
|
|
1463
|
+
const beginPattern = new RegExp(`<!-- BEGIN openpack:include ${escapeRegex(selector)} sha256:([a-f0-9]+) -->\\r?\\n?`);
|
|
1464
|
+
const begin = beginPattern.exec(content);
|
|
1465
|
+
if (!begin || begin.index === void 0) return void 0;
|
|
1466
|
+
const markerHash = begin[1];
|
|
1467
|
+
const bodyStart = begin.index + begin[0].length;
|
|
1468
|
+
const endMarker = `<!-- END openpack:include ${selector} -->`;
|
|
1469
|
+
const endIndex = content.indexOf(endMarker, bodyStart);
|
|
1470
|
+
if (endIndex < 0) {
|
|
1471
|
+
return {
|
|
1472
|
+
start: begin.index,
|
|
1473
|
+
end: content.length,
|
|
1474
|
+
body: content.slice(bodyStart),
|
|
1475
|
+
markerHash
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
let blockEnd = endIndex + endMarker.length;
|
|
1479
|
+
if (content[blockEnd] === "\r" && content[blockEnd + 1] === "\n") blockEnd += 2;
|
|
1480
|
+
else if (content[blockEnd] === "\n") blockEnd += 1;
|
|
1481
|
+
return {
|
|
1482
|
+
start: begin.index,
|
|
1483
|
+
end: blockEnd,
|
|
1484
|
+
body: content.slice(bodyStart, endIndex),
|
|
1485
|
+
markerHash
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function managedBlockBody(source) {
|
|
1489
|
+
return `${managedInstructionBanner}
|
|
1490
|
+
${ensureTrailingNewline(source)}`;
|
|
1491
|
+
}
|
|
1492
|
+
function appendManagedInstructionBlock(content, block) {
|
|
1493
|
+
if (content.length === 0) return block;
|
|
1494
|
+
const separator = content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
|
|
1495
|
+
return `${content}${separator}${block}`;
|
|
1496
|
+
}
|
|
1497
|
+
function assertCleanBlock(block, selector) {
|
|
1498
|
+
const actual = hashText(block.body);
|
|
1499
|
+
if (actual !== block.markerHash) {
|
|
1500
|
+
throw new Error(`Managed instruction block drift detected for ${selector}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
function referencesAgentsMd(content, claudePath, agentsPath) {
|
|
1504
|
+
const claudeDir = dirname8(claudePath);
|
|
1505
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
1506
|
+
const line = rawLine.trim();
|
|
1507
|
+
const atImport = /^@import\s+(.+)$/i.exec(line);
|
|
1508
|
+
const atPath = /^@(.+AGENTS\.md)$/i.exec(line);
|
|
1509
|
+
const referenced = atImport?.[1] ?? atPath?.[1];
|
|
1510
|
+
if (!referenced) continue;
|
|
1511
|
+
const cleaned = referenced.trim().replace(/^["']|["']$/g, "");
|
|
1512
|
+
if (!/AGENTS\.md$/i.test(cleaned)) continue;
|
|
1513
|
+
const candidate = cleaned.startsWith("/") ? cleaned : join4(claudeDir, cleaned);
|
|
1514
|
+
if (relative2(dirname8(agentsPath), candidate).replaceAll("\\", "/") === "AGENTS.md") return true;
|
|
1515
|
+
if (candidate === agentsPath) return true;
|
|
1516
|
+
}
|
|
1517
|
+
return false;
|
|
1518
|
+
}
|
|
1519
|
+
async function samePhysicalPath(left, right, transport) {
|
|
1520
|
+
if (transport.kind !== "local") return false;
|
|
1521
|
+
if (!await transport.pathExists(left) || !await transport.pathExists(right)) return false;
|
|
1522
|
+
return await realpath(left) === await realpath(right);
|
|
1523
|
+
}
|
|
1524
|
+
async function readOptionalText(path, transport) {
|
|
1525
|
+
if (!await transport.pathExists(path)) return void 0;
|
|
1526
|
+
return transport.readFile(path);
|
|
1527
|
+
}
|
|
1528
|
+
async function writeTextWithTransport(path, content, transport) {
|
|
1529
|
+
if (transport.kind === "local") {
|
|
1530
|
+
await transport.writeFileAtomic(path, content);
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const tempRoot = await mkdtemp(join4(tmpdir2(), "agentwheel-instructions-"));
|
|
1534
|
+
const localPath = join4(tempRoot, basename2(path) || "instructions.md");
|
|
1535
|
+
try {
|
|
1536
|
+
await writeFile7(localPath, content, "utf8");
|
|
1537
|
+
await transport.atomicCopy(localPath, path, "file");
|
|
1538
|
+
} finally {
|
|
1539
|
+
await rm3(tempRoot, { recursive: true, force: true });
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
function ensureTrailingNewline(content) {
|
|
1543
|
+
return content.endsWith("\n") ? content : `${content}
|
|
1544
|
+
`;
|
|
1545
|
+
}
|
|
1546
|
+
function hashText(content) {
|
|
1547
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
1548
|
+
}
|
|
1549
|
+
function escapeRegex(value) {
|
|
1550
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1257
1553
|
// src/install/apply.ts
|
|
1258
1554
|
var execFileAsync3 = promisify3(execFile3);
|
|
1259
1555
|
async function applyCombinedInstallPlan(plan, options = {}) {
|
|
@@ -1264,7 +1560,7 @@ async function recoverPendingApply(targetRoot, adapter, transport = localTranspo
|
|
|
1264
1560
|
try {
|
|
1265
1561
|
const journal = await readApplyJournal(targetRoot, adapter, transport, scope);
|
|
1266
1562
|
if (!journal) return void 0;
|
|
1267
|
-
if (journal.operations.some((operation) => operation.action === "plugin" || operation.action === "program")) {
|
|
1563
|
+
if (journal.operations.some((operation) => operation.action === "plugin" || operation.action === "program" || operation.semanticPlugin)) {
|
|
1268
1564
|
throw new Error("Cannot automatically recover a journal containing semantic plugin or programmatic operations");
|
|
1269
1565
|
}
|
|
1270
1566
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -1475,7 +1771,7 @@ async function commitJournalState(journal, transport, entries, now) {
|
|
|
1475
1771
|
await writeInstallManifest(manifest, transport);
|
|
1476
1772
|
}
|
|
1477
1773
|
if (journal.graphLockPath && journal.graphLock) await writeGraphLock(journal.graphLockPath, journal.graphLock);
|
|
1478
|
-
if (journal.graphLockRemovePath) await
|
|
1774
|
+
if (journal.graphLockRemovePath) await rm4(journal.graphLockRemovePath, { force: true });
|
|
1479
1775
|
if (journal.workspaceConfigPath && journal.workspaceConfig) await writeJsonAtomic(journal.workspaceConfigPath, journal.workspaceConfig);
|
|
1480
1776
|
await removeApplyJournal(journal.targetRoot, journal.adapter, transport, {
|
|
1481
1777
|
installationType: journal.installationType,
|
|
@@ -1490,9 +1786,6 @@ async function applyOperation(operation, context) {
|
|
|
1490
1786
|
throw new Error(`Invalid plugin operation missing hash: ${operation.relativeDestPath}`);
|
|
1491
1787
|
}
|
|
1492
1788
|
if (context.executePlugins) {
|
|
1493
|
-
if (!operation.semanticCommand || operation.semanticCommand.length === 0) {
|
|
1494
|
-
throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1495
|
-
}
|
|
1496
1789
|
await executePluginInstall(operation, transport);
|
|
1497
1790
|
}
|
|
1498
1791
|
return manifestEntryForOperation(operation, {
|
|
@@ -1527,10 +1820,27 @@ async function applyOperation(operation, context) {
|
|
|
1527
1820
|
if (!operation.sourcePath || !operation.desiredHash) {
|
|
1528
1821
|
throw new Error(`Invalid operation missing source/hash: ${operation.relativeDestPath}`);
|
|
1529
1822
|
}
|
|
1823
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
1824
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
1825
|
+
const hash2 = await writeManagedInstructionBlock(operation.sourcePath, operation.destPath, selector, transport, operation.manifestHash);
|
|
1826
|
+
if (hash2 !== operation.desiredHash) {
|
|
1827
|
+
throw new Error(`Managed block hash verification failed for ${operation.relativeDestPath}: expected ${operation.desiredHash}, got ${hash2}`);
|
|
1828
|
+
}
|
|
1829
|
+
return manifestEntryForOperation(operation, {
|
|
1830
|
+
now,
|
|
1831
|
+
hash: hash2,
|
|
1832
|
+
sourceHash: operation.desiredHash,
|
|
1833
|
+
graphLockDigest: context.graphLockDigest
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1530
1836
|
if (operation.mergeStrategy === "json-deep") {
|
|
1531
1837
|
await mergeWithTransport(operation.sourcePath, operation.destPath, transport, mergeJsonFile);
|
|
1838
|
+
} else if (operation.mergeStrategy === "yaml-deep") {
|
|
1839
|
+
await mergeWithTransport(operation.sourcePath, operation.destPath, transport, mergeYamlFile);
|
|
1532
1840
|
} else if (operation.mergeStrategy === "codex-toml-mcp") {
|
|
1533
1841
|
await mergeWithTransport(operation.sourcePath, operation.destPath, transport, mergeCodexTomlMcp);
|
|
1842
|
+
} else if (operation.mergeStrategy) {
|
|
1843
|
+
throw new Error(`Merge strategy '${operation.mergeStrategy}' is not implemented by apply.`);
|
|
1534
1844
|
} else {
|
|
1535
1845
|
await transport.atomicCopy(operation.sourcePath, operation.destPath, operation.kind);
|
|
1536
1846
|
}
|
|
@@ -1551,7 +1861,7 @@ async function applyOperation(operation, context) {
|
|
|
1551
1861
|
}
|
|
1552
1862
|
return manifestEntryForOperation(operation, {
|
|
1553
1863
|
now,
|
|
1554
|
-
hash: operation.mergeStrategy && operation.currentHash ? operation.currentHash : operation.desiredHash,
|
|
1864
|
+
hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && operation.currentHash ? operation.currentHash : operation.desiredHash,
|
|
1555
1865
|
sourceHash: operation.desiredHash,
|
|
1556
1866
|
graphLockDigest: context.graphLockDigest
|
|
1557
1867
|
});
|
|
@@ -1568,40 +1878,198 @@ async function applyOperation(operation, context) {
|
|
|
1568
1878
|
});
|
|
1569
1879
|
}
|
|
1570
1880
|
if (operation.action === "remove") {
|
|
1571
|
-
|
|
1881
|
+
if (operation.semanticPlugin) {
|
|
1882
|
+
if (operation.execute !== false) await executePluginUninstall(operation, transport);
|
|
1883
|
+
return void 0;
|
|
1884
|
+
}
|
|
1885
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
1886
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
1887
|
+
await removeManagedInstructionBlock(operation.destPath, selector, transport, operation.manifestHash);
|
|
1888
|
+
} else {
|
|
1889
|
+
await transport.rm(operation.destPath);
|
|
1890
|
+
}
|
|
1572
1891
|
return void 0;
|
|
1573
1892
|
}
|
|
1574
1893
|
return void 0;
|
|
1575
1894
|
}
|
|
1576
1895
|
async function executePluginInstall(operation, transport) {
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
if (!command) {
|
|
1580
|
-
throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1581
|
-
}
|
|
1896
|
+
const commands = semanticInstallCommands(operation);
|
|
1897
|
+
if (commands.length === 0) throw new Error(`Invalid plugin operation missing command: ${operation.relativeDestPath}`);
|
|
1582
1898
|
if (!operation.sourcePath) {
|
|
1583
1899
|
throw new Error(`Invalid plugin operation missing source path: ${operation.relativeDestPath}`);
|
|
1584
1900
|
}
|
|
1901
|
+
if (operation.semanticPlugin?.stateRoot) {
|
|
1902
|
+
await prepareSemanticPluginState(operation, transport);
|
|
1903
|
+
}
|
|
1904
|
+
await executeSemanticCommands(operation, commands, transport, { stageSource: operation.semanticPlugin?.stateRoot ? false : true });
|
|
1905
|
+
}
|
|
1906
|
+
async function executePluginUninstall(operation, transport) {
|
|
1907
|
+
const commands = operation.semanticPlugin?.uninstallCommands ?? [];
|
|
1908
|
+
if (commands.length === 0) throw new Error(`Invalid semantic plugin operation missing uninstall command: ${operation.relativeDestPath}`);
|
|
1909
|
+
await executeSemanticCommands(operation, commands, transport);
|
|
1910
|
+
if (operation.semanticPlugin?.stateRoot) {
|
|
1911
|
+
await transport.rm(operation.semanticPlugin.stateRoot);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
async function prepareSemanticPluginState(operation, transport) {
|
|
1915
|
+
const spec = operation.semanticPlugin;
|
|
1916
|
+
if (!spec?.stateRoot) return;
|
|
1917
|
+
if (!operation.sourcePath) {
|
|
1918
|
+
throw new Error(`Invalid semantic plugin operation missing source path: ${operation.relativeDestPath}`);
|
|
1919
|
+
}
|
|
1920
|
+
if (spec.runtime === "claude") {
|
|
1921
|
+
await prepareClaudeMarketplace(operation, transport);
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (spec.runtime === "codex") {
|
|
1925
|
+
await prepareCodexMarketplace(operation, transport);
|
|
1926
|
+
return;
|
|
1927
|
+
}
|
|
1928
|
+
if (spec.runtime === "copilot") {
|
|
1929
|
+
await prepareCopilotPlugin(operation, transport);
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
if (spec.runtime === "hermes") {
|
|
1933
|
+
await prepareHermesGitShim(operation, transport);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
async function prepareClaudeMarketplace(operation, transport) {
|
|
1937
|
+
const spec = requireSemanticPluginState(operation);
|
|
1938
|
+
const marketplaceName = requireMarketplaceName(operation);
|
|
1939
|
+
const marketplaceRoot = join5(spec.stateRoot, "marketplace");
|
|
1940
|
+
await transport.rm(spec.stateRoot);
|
|
1941
|
+
await transport.atomicCopy(requireSourcePath(operation), join5(marketplaceRoot, "plugins", spec.pluginName), operation.kind);
|
|
1942
|
+
await transport.writeJsonAtomic(join5(marketplaceRoot, ".claude-plugin", "marketplace.json"), {
|
|
1943
|
+
name: marketplaceName,
|
|
1944
|
+
owner: { name: "Agentwheel" },
|
|
1945
|
+
plugins: [
|
|
1946
|
+
{
|
|
1947
|
+
name: spec.pluginName,
|
|
1948
|
+
source: `./plugins/${spec.pluginName}`,
|
|
1949
|
+
description: `Installed by Agentwheel${operation.packageName ? ` from ${operation.packageName}` : ""}`
|
|
1950
|
+
}
|
|
1951
|
+
]
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
async function prepareCodexMarketplace(operation, transport) {
|
|
1955
|
+
const spec = requireSemanticPluginState(operation);
|
|
1956
|
+
const marketplaceName = requireMarketplaceName(operation);
|
|
1957
|
+
const marketplaceRoot = join5(spec.stateRoot, "marketplace");
|
|
1958
|
+
await transport.rm(spec.stateRoot);
|
|
1959
|
+
await transport.atomicCopy(requireSourcePath(operation), join5(marketplaceRoot, "plugins", spec.pluginName), operation.kind);
|
|
1960
|
+
await transport.writeJsonAtomic(join5(marketplaceRoot, ".agents", "plugins", "marketplace.json"), {
|
|
1961
|
+
name: marketplaceName,
|
|
1962
|
+
interface: {
|
|
1963
|
+
displayName: "Agentwheel"
|
|
1964
|
+
},
|
|
1965
|
+
plugins: [
|
|
1966
|
+
{
|
|
1967
|
+
name: spec.pluginName,
|
|
1968
|
+
source: {
|
|
1969
|
+
source: "local",
|
|
1970
|
+
path: `./plugins/${spec.pluginName}`
|
|
1971
|
+
},
|
|
1972
|
+
policy: {
|
|
1973
|
+
installation: "AVAILABLE",
|
|
1974
|
+
authentication: "ON_INSTALL"
|
|
1975
|
+
},
|
|
1976
|
+
category: "Agentwheel"
|
|
1977
|
+
}
|
|
1978
|
+
]
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
async function prepareCopilotPlugin(operation, transport) {
|
|
1982
|
+
const spec = requireSemanticPluginState(operation);
|
|
1983
|
+
await transport.rm(spec.stateRoot);
|
|
1984
|
+
await transport.atomicCopy(requireSourcePath(operation), join5(spec.stateRoot, "plugin"), operation.kind);
|
|
1985
|
+
}
|
|
1986
|
+
async function prepareHermesGitShim(operation, transport) {
|
|
1987
|
+
const spec = requireSemanticPluginState(operation);
|
|
1988
|
+
const repoRoot = join5(spec.stateRoot, "repo");
|
|
1989
|
+
await transport.rm(spec.stateRoot);
|
|
1990
|
+
await transport.atomicCopy(requireSourcePath(operation), repoRoot, operation.kind);
|
|
1991
|
+
if (!transport.execFile) {
|
|
1992
|
+
throw new Error(`Cannot prepare Hermes plugin git shim over ${transport.description}: transport does not support remote commands.`);
|
|
1993
|
+
}
|
|
1994
|
+
await transport.execFile("git", ["init", repoRoot], { cwd: operation.destPath });
|
|
1995
|
+
await transport.execFile("git", ["-C", repoRoot, "add", "-A"], { cwd: operation.destPath });
|
|
1996
|
+
await transport.execFile("git", [
|
|
1997
|
+
"-C",
|
|
1998
|
+
repoRoot,
|
|
1999
|
+
"-c",
|
|
2000
|
+
"user.name=agentwheel",
|
|
2001
|
+
"-c",
|
|
2002
|
+
"user.email=agentwheel@example.invalid",
|
|
2003
|
+
"commit",
|
|
2004
|
+
"-m",
|
|
2005
|
+
`agentwheel plugin ${operation.desiredHash ?? "unknown"}`
|
|
2006
|
+
], { cwd: operation.destPath });
|
|
2007
|
+
}
|
|
2008
|
+
function requireSemanticPluginState(operation) {
|
|
2009
|
+
const spec = operation.semanticPlugin;
|
|
2010
|
+
if (!spec?.stateRoot) throw new Error(`Invalid semantic plugin operation missing state root: ${operation.relativeDestPath}`);
|
|
2011
|
+
return spec;
|
|
2012
|
+
}
|
|
2013
|
+
function requireSourcePath(operation) {
|
|
2014
|
+
if (!operation.sourcePath) throw new Error(`Invalid semantic plugin operation missing source path: ${operation.relativeDestPath}`);
|
|
2015
|
+
return operation.sourcePath;
|
|
2016
|
+
}
|
|
2017
|
+
function requireMarketplaceName(operation) {
|
|
2018
|
+
const marketplaceName = operation.semanticPlugin?.marketplaceName;
|
|
2019
|
+
if (!marketplaceName) throw new Error(`Invalid semantic plugin operation missing marketplace name: ${operation.relativeDestPath}`);
|
|
2020
|
+
return marketplaceName;
|
|
2021
|
+
}
|
|
2022
|
+
async function executeSemanticCommands(operation, commands, transport, options = {}) {
|
|
1585
2023
|
if (transport.kind === "local") {
|
|
1586
|
-
|
|
2024
|
+
for (const [command, ...args] of commands) {
|
|
2025
|
+
if (!command) throw new Error(`Invalid semantic plugin command for ${operation.relativeDestPath}`);
|
|
2026
|
+
await execFileAsync3(command, args);
|
|
2027
|
+
}
|
|
1587
2028
|
return;
|
|
1588
2029
|
}
|
|
1589
2030
|
if (!transport.execFile) {
|
|
1590
|
-
throw new Error(`Cannot execute semantic plugin
|
|
2031
|
+
throw new Error(`Cannot execute semantic plugin command over ${transport.description}: transport does not support remote commands.`);
|
|
2032
|
+
}
|
|
2033
|
+
if (!options.stageSource) {
|
|
2034
|
+
for (const [command, ...args] of commands) {
|
|
2035
|
+
if (!command) throw new Error(`Invalid semantic plugin command for ${operation.relativeDestPath}`);
|
|
2036
|
+
await transport.execFile(command, args, { cwd: operation.destPath });
|
|
2037
|
+
}
|
|
2038
|
+
return;
|
|
2039
|
+
}
|
|
2040
|
+
if (!operation.sourcePath) {
|
|
2041
|
+
throw new Error(`Invalid semantic plugin operation missing source path: ${operation.relativeDestPath}`);
|
|
1591
2042
|
}
|
|
1592
|
-
const stagingRoot =
|
|
1593
|
-
const remoteSourcePath =
|
|
2043
|
+
const stagingRoot = join5(operation.destPath, ".agentwheel", "plugin-staging", `${process.pid}-${Date.now()}`);
|
|
2044
|
+
const remoteSourcePath = join5(stagingRoot, basename3(operation.sourcePath));
|
|
1594
2045
|
try {
|
|
1595
2046
|
await transport.atomicCopy(operation.sourcePath, remoteSourcePath, operation.kind);
|
|
1596
|
-
const
|
|
1597
|
-
|
|
2047
|
+
for (const [command, ...args] of commands) {
|
|
2048
|
+
if (!command) throw new Error(`Invalid semantic plugin command for ${operation.relativeDestPath}`);
|
|
2049
|
+
const remoteArgs = args.map((arg) => arg === operation.sourcePath ? remoteSourcePath : arg);
|
|
2050
|
+
await transport.execFile(command, remoteArgs, { cwd: operation.destPath });
|
|
2051
|
+
}
|
|
1598
2052
|
} finally {
|
|
1599
2053
|
await transport.rm(stagingRoot);
|
|
1600
2054
|
}
|
|
1601
2055
|
}
|
|
2056
|
+
function semanticInstallCommands(operation) {
|
|
2057
|
+
if (operation.semanticPlugin) return operation.semanticPlugin.installCommands;
|
|
2058
|
+
return operation.semanticCommand ? [operation.semanticCommand] : [];
|
|
2059
|
+
}
|
|
1602
2060
|
async function entryForCompletedOperation(operation, transport, now, graphLockDigest) {
|
|
1603
2061
|
if (operation.action === "remove") return void 0;
|
|
1604
2062
|
if (operation.action === "create" || operation.action === "update") {
|
|
2063
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
2064
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
2065
|
+
const state = await readManagedInstructionBlockState(operation.destPath, selector, transport);
|
|
2066
|
+
return manifestEntryForOperation(operation, {
|
|
2067
|
+
now,
|
|
2068
|
+
hash: state.hash ?? requireDesiredHash(operation),
|
|
2069
|
+
sourceHash: requireDesiredHash(operation),
|
|
2070
|
+
graphLockDigest
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
1605
2073
|
return manifestEntryForOperation(operation, {
|
|
1606
2074
|
now,
|
|
1607
2075
|
hash: await transport.hashPath(operation.destPath),
|
|
@@ -1612,7 +2080,7 @@ async function entryForCompletedOperation(operation, transport, now, graphLockDi
|
|
|
1612
2080
|
if (operation.action === "skip") {
|
|
1613
2081
|
return manifestEntryForOperation(operation, {
|
|
1614
2082
|
now,
|
|
1615
|
-
hash: operation.mergeStrategy && operation.currentHash ? operation.currentHash : requireDesiredHash(operation),
|
|
2083
|
+
hash: (operation.mergeStrategy || operation.mode === managedInstructionBlockMode) && operation.currentHash ? operation.currentHash : requireDesiredHash(operation),
|
|
1616
2084
|
sourceHash: requireDesiredHash(operation),
|
|
1617
2085
|
graphLockDigest
|
|
1618
2086
|
});
|
|
@@ -1639,8 +2107,10 @@ function manifestEntryForOperation(operation, values) {
|
|
|
1639
2107
|
channel: operation.channel,
|
|
1640
2108
|
packageName: operation.packageName,
|
|
1641
2109
|
semanticCommand: operation.semanticCommand,
|
|
2110
|
+
semanticPlugin: operation.semanticPlugin,
|
|
1642
2111
|
executed: values.executed ?? operation.execute,
|
|
1643
2112
|
mergeStrategy: operation.mergeStrategy,
|
|
2113
|
+
mode: operation.mode,
|
|
1644
2114
|
composedFrom: operation.composedFrom,
|
|
1645
2115
|
graphLockDigest: operation.graphLockDigest ?? values.graphLockDigest
|
|
1646
2116
|
};
|
|
@@ -1656,15 +2126,27 @@ async function assertBaseRevision(plan, transport) {
|
|
|
1656
2126
|
}
|
|
1657
2127
|
}
|
|
1658
2128
|
function isJournaledMutation(operation) {
|
|
2129
|
+
if (operation.semanticPlugin && (operation.action === "plugin" || operation.action === "remove")) return false;
|
|
1659
2130
|
return operation.action === "create" || operation.action === "update" || operation.action === "remove";
|
|
1660
2131
|
}
|
|
1661
2132
|
function operationNeedsSource(operation) {
|
|
1662
2133
|
return operation.action === "create" || operation.action === "update";
|
|
1663
2134
|
}
|
|
1664
2135
|
async function operationLanded(operation, transport) {
|
|
1665
|
-
if (operation.action === "remove")
|
|
2136
|
+
if (operation.action === "remove") {
|
|
2137
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
2138
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
2139
|
+
const state = await readManagedInstructionBlockState(operation.destPath, selector, transport);
|
|
2140
|
+
return !state.exists || !state.hasBlock;
|
|
2141
|
+
}
|
|
2142
|
+
return !await transport.pathExists(operation.destPath);
|
|
2143
|
+
}
|
|
1666
2144
|
if (operation.action !== "create" && operation.action !== "update") return false;
|
|
1667
2145
|
if (!await transport.pathExists(operation.destPath)) return false;
|
|
2146
|
+
if (operation.mode === managedInstructionBlockMode) {
|
|
2147
|
+
const selector = managedInstructionSelector(operation.logicalSelector, operation.artifactType, operation.artifactName);
|
|
2148
|
+
return managedInstructionBlockLanded(operation.destPath, selector, operation.desiredHash, transport);
|
|
2149
|
+
}
|
|
1668
2150
|
if (operation.mergeStrategy) return true;
|
|
1669
2151
|
if (!operation.desiredHash) return false;
|
|
1670
2152
|
return await transport.hashPath(operation.destPath) === operation.desiredHash;
|
|
@@ -1684,25 +2166,25 @@ async function mergeWithTransport(sourcePath, destPath, transport, merge) {
|
|
|
1684
2166
|
await merge(sourcePath, destPath);
|
|
1685
2167
|
return;
|
|
1686
2168
|
}
|
|
1687
|
-
const tempRoot = await
|
|
1688
|
-
const localDest =
|
|
2169
|
+
const tempRoot = await mkdtemp2(join5(tmpdir3(), "agentwheel-merge-"));
|
|
2170
|
+
const localDest = join5(tempRoot, basename3(destPath) || "merged");
|
|
1689
2171
|
try {
|
|
1690
2172
|
if (await transport.pathExists(destPath)) {
|
|
1691
|
-
await
|
|
2173
|
+
await writeFile8(localDest, await transport.readFile(destPath), "utf8");
|
|
1692
2174
|
}
|
|
1693
2175
|
await merge(sourcePath, localDest);
|
|
1694
2176
|
await transport.atomicCopy(localDest, destPath, "file");
|
|
1695
2177
|
} finally {
|
|
1696
|
-
await
|
|
2178
|
+
await rm4(tempRoot, { recursive: true, force: true });
|
|
1697
2179
|
}
|
|
1698
2180
|
}
|
|
1699
2181
|
|
|
1700
2182
|
// src/install/plan.ts
|
|
1701
|
-
import { join as
|
|
2183
|
+
import { basename as basename7, join as join15, relative as relative3 } from "path";
|
|
1702
2184
|
|
|
1703
2185
|
// src/staging/codex-subagents.ts
|
|
1704
|
-
import { mkdir as
|
|
1705
|
-
import { basename as
|
|
2186
|
+
import { mkdir as mkdir7, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
|
|
2187
|
+
import { basename as basename4, dirname as dirname9, join as join6 } from "path";
|
|
1706
2188
|
var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
|
|
1707
2189
|
async function renderCodexSubagents(artifacts, stageRoot, adapter) {
|
|
1708
2190
|
if (adapter?.name !== "codex") return artifacts;
|
|
@@ -1725,42 +2207,42 @@ async function renderCodexSubagents(artifacts, stageRoot, adapter) {
|
|
|
1725
2207
|
async function renderCodexSubagent(artifact, stageRoot) {
|
|
1726
2208
|
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
1727
2209
|
const agentName = codexAgentName(artifact);
|
|
1728
|
-
const renderedPath =
|
|
2210
|
+
const renderedPath = join6(stageRoot, ".agentwheel-rendered", "codex-subagents", `${agentName}.toml`);
|
|
1729
2211
|
const lowerSourcePath = sourcePath.toLowerCase();
|
|
1730
2212
|
if (artifact.kind === "file" && (artifact.name.toLowerCase().endsWith(".toml") || lowerSourcePath.endsWith(".toml"))) {
|
|
1731
|
-
const content = await
|
|
2213
|
+
const content = await readFile9(sourcePath, "utf8");
|
|
1732
2214
|
validateCodexAgentToml(content, sourcePath);
|
|
1733
2215
|
return {
|
|
1734
2216
|
...artifact,
|
|
1735
2217
|
name: agentName,
|
|
1736
|
-
relativePath:
|
|
2218
|
+
relativePath: join6("subagents", `${agentName}.toml`),
|
|
1737
2219
|
kind: "file",
|
|
1738
2220
|
hash: await hashPath(sourcePath)
|
|
1739
2221
|
};
|
|
1740
2222
|
}
|
|
1741
|
-
const markdownPath = artifact.kind === "dir" ?
|
|
2223
|
+
const markdownPath = artifact.kind === "dir" ? join6(sourcePath, "AGENTS.md") : sourcePath;
|
|
1742
2224
|
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
1743
2225
|
throw new Error(`Codex subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
1744
2226
|
}
|
|
1745
2227
|
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !lowerSourcePath.endsWith(".md")) {
|
|
1746
2228
|
throw new Error(`Codex subagent ${artifact.relativePath} must be a .toml file, .md file, or directory containing AGENTS.md.`);
|
|
1747
2229
|
}
|
|
1748
|
-
const markdown = await
|
|
2230
|
+
const markdown = await readFile9(markdownPath, "utf8");
|
|
1749
2231
|
const toml = markdownToCodexAgentToml(agentName, markdown);
|
|
1750
|
-
await
|
|
1751
|
-
await
|
|
2232
|
+
await mkdir7(dirname9(renderedPath), { recursive: true });
|
|
2233
|
+
await writeFile9(renderedPath, toml, "utf8");
|
|
1752
2234
|
return {
|
|
1753
2235
|
...artifact,
|
|
1754
2236
|
name: agentName,
|
|
1755
2237
|
sourcePath: renderedPath,
|
|
1756
2238
|
stagedPath: renderedPath,
|
|
1757
|
-
relativePath:
|
|
2239
|
+
relativePath: join6("subagents", `${agentName}.toml`),
|
|
1758
2240
|
kind: "file",
|
|
1759
2241
|
hash: await hashPath(renderedPath)
|
|
1760
2242
|
};
|
|
1761
2243
|
}
|
|
1762
2244
|
function codexAgentName(artifact) {
|
|
1763
|
-
const raw = artifact.kind === "dir" ? artifact.name :
|
|
2245
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename4(artifact.name);
|
|
1764
2246
|
return raw.replace(/\.toml$/i, "").replace(/\.md$/i, "");
|
|
1765
2247
|
}
|
|
1766
2248
|
function validateCodexAgentToml(content, path) {
|
|
@@ -1816,8 +2298,8 @@ function escapeRegExp(value) {
|
|
|
1816
2298
|
}
|
|
1817
2299
|
|
|
1818
2300
|
// src/staging/copilot-artifacts.ts
|
|
1819
|
-
import { mkdir as
|
|
1820
|
-
import { basename as
|
|
2301
|
+
import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile10 } from "fs/promises";
|
|
2302
|
+
import { basename as basename5, dirname as dirname10, join as join7 } from "path";
|
|
1821
2303
|
async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
|
|
1822
2304
|
if (adapter?.name !== "copilot") return artifacts;
|
|
1823
2305
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -1839,29 +2321,29 @@ async function renderCopilotArtifacts(artifacts, stageRoot, adapter) {
|
|
|
1839
2321
|
async function renderCopilotSubagent(artifact, stageRoot) {
|
|
1840
2322
|
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
1841
2323
|
const agentName = copilotAgentName(artifact);
|
|
1842
|
-
const renderedPath =
|
|
1843
|
-
const markdownPath = artifact.kind === "dir" ?
|
|
2324
|
+
const renderedPath = join7(stageRoot, ".agentwheel-rendered", "copilot-subagents", `${agentName}.agent.md`);
|
|
2325
|
+
const markdownPath = artifact.kind === "dir" ? join7(sourcePath, "AGENTS.md") : sourcePath;
|
|
1844
2326
|
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
1845
2327
|
throw new Error(`Copilot subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
1846
2328
|
}
|
|
1847
2329
|
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md")) {
|
|
1848
2330
|
throw new Error(`Copilot subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
|
|
1849
2331
|
}
|
|
1850
|
-
const markdown = await
|
|
1851
|
-
await
|
|
1852
|
-
await
|
|
2332
|
+
const markdown = await readFile10(markdownPath, "utf8");
|
|
2333
|
+
await mkdir8(dirname10(renderedPath), { recursive: true });
|
|
2334
|
+
await writeFile10(renderedPath, ensureCopilotAgentDescription(agentName, markdown), "utf8");
|
|
1853
2335
|
return {
|
|
1854
2336
|
...artifact,
|
|
1855
2337
|
name: `${agentName}.agent.md`,
|
|
1856
2338
|
sourcePath: renderedPath,
|
|
1857
2339
|
stagedPath: renderedPath,
|
|
1858
|
-
relativePath:
|
|
2340
|
+
relativePath: join7("subagents", `${agentName}.agent.md`),
|
|
1859
2341
|
kind: "file",
|
|
1860
2342
|
hash: await hashPath(renderedPath)
|
|
1861
2343
|
};
|
|
1862
2344
|
}
|
|
1863
2345
|
function copilotAgentName(artifact) {
|
|
1864
|
-
const raw = artifact.kind === "dir" ? artifact.name :
|
|
2346
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename5(artifact.name);
|
|
1865
2347
|
return raw.replace(/\.agent\.md$/i, "").replace(/\.md$/i, "");
|
|
1866
2348
|
}
|
|
1867
2349
|
function ensureCopilotAgentDescription(agentName, markdown) {
|
|
@@ -1901,14 +2383,322 @@ function yamlString(value) {
|
|
|
1901
2383
|
return JSON.stringify(value);
|
|
1902
2384
|
}
|
|
1903
2385
|
|
|
2386
|
+
// src/targets/plugins/claude.ts
|
|
2387
|
+
import { join as join9 } from "path";
|
|
2388
|
+
|
|
2389
|
+
// src/targets/plugins/common.ts
|
|
2390
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
2391
|
+
import { join as join8 } from "path";
|
|
2392
|
+
import { parseDocument } from "yaml";
|
|
2393
|
+
function pluginStateRoot(request) {
|
|
2394
|
+
return join8(
|
|
2395
|
+
request.targetRoot,
|
|
2396
|
+
".agentwheel",
|
|
2397
|
+
"plugins",
|
|
2398
|
+
request.runtime,
|
|
2399
|
+
request.installationType,
|
|
2400
|
+
safeNameSegment(request.packageName ?? "package"),
|
|
2401
|
+
safeNameSegment(request.installName)
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
function agentwheelMarketplaceName(packageName, pluginName) {
|
|
2405
|
+
return `agentwheel-${safeNameSegment(packageName ?? "package")}-${safeNameSegment(pluginName)}`;
|
|
2406
|
+
}
|
|
2407
|
+
function safeNameSegment(value) {
|
|
2408
|
+
const normalized = value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2409
|
+
return normalized.length > 0 ? normalized : "unnamed";
|
|
2410
|
+
}
|
|
2411
|
+
async function jsonPluginName(root, relativeManifestPath, fallback) {
|
|
2412
|
+
const manifestPath = join8(root, relativeManifestPath);
|
|
2413
|
+
if (!await pathExists(manifestPath)) return fallback;
|
|
2414
|
+
const parsed = JSON.parse(await readFile11(manifestPath, "utf8"));
|
|
2415
|
+
return typeof parsed.name === "string" && parsed.name.trim().length > 0 ? parsed.name.trim() : fallback;
|
|
2416
|
+
}
|
|
2417
|
+
async function yamlPluginName(root, relativeManifestPaths, fallback) {
|
|
2418
|
+
for (const relativeManifestPath of relativeManifestPaths) {
|
|
2419
|
+
const manifestPath = join8(root, relativeManifestPath);
|
|
2420
|
+
if (!await pathExists(manifestPath)) continue;
|
|
2421
|
+
const document = parseDocument(await readFile11(manifestPath, "utf8"));
|
|
2422
|
+
const parsed = document.toJSON();
|
|
2423
|
+
if (!isRecord4(parsed)) continue;
|
|
2424
|
+
for (const key of ["name", "module", "package"]) {
|
|
2425
|
+
const value = parsed[key];
|
|
2426
|
+
if (typeof value === "string" && value.trim().length > 0) return value.trim();
|
|
2427
|
+
}
|
|
2428
|
+
const packageValue = parsed.package;
|
|
2429
|
+
if (isRecord4(packageValue) && typeof packageValue.name === "string" && packageValue.name.trim().length > 0) {
|
|
2430
|
+
return packageValue.name.trim();
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
return fallback;
|
|
2434
|
+
}
|
|
2435
|
+
function isRecord4(value) {
|
|
2436
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// src/targets/plugins/claude.ts
|
|
2440
|
+
async function claudePluginSpec(request) {
|
|
2441
|
+
const pluginName = await jsonPluginName(request.sourcePath, ".claude-plugin/plugin.json", request.installName);
|
|
2442
|
+
const marketplaceName = agentwheelMarketplaceName(request.artifact.packageName, pluginName);
|
|
2443
|
+
const stateRoot = pluginStateRoot({
|
|
2444
|
+
targetRoot: request.targetRoot,
|
|
2445
|
+
runtime: "claude",
|
|
2446
|
+
installationType: request.installationType,
|
|
2447
|
+
packageName: request.artifact.packageName,
|
|
2448
|
+
installName: request.installName
|
|
2449
|
+
});
|
|
2450
|
+
const marketplaceRoot = join9(stateRoot, "marketplace");
|
|
2451
|
+
const scope = claudeScope(request.installationType);
|
|
2452
|
+
const selector = `${pluginName}@${marketplaceName}`;
|
|
2453
|
+
return {
|
|
2454
|
+
runtime: "claude",
|
|
2455
|
+
pluginName,
|
|
2456
|
+
marketplaceName,
|
|
2457
|
+
stateRoot,
|
|
2458
|
+
installCommands: [
|
|
2459
|
+
["claude", "plugin", "marketplace", "add", marketplaceRoot, "--scope", scope],
|
|
2460
|
+
["claude", "plugin", "install", selector, "--scope", scope]
|
|
2461
|
+
],
|
|
2462
|
+
uninstallCommands: [
|
|
2463
|
+
["claude", "plugin", "uninstall", selector, "--scope", scope],
|
|
2464
|
+
["claude", "plugin", "marketplace", "remove", marketplaceName, "--scope", scope]
|
|
2465
|
+
]
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
function claudeScope(installationType) {
|
|
2469
|
+
if (installationType === "user" || installationType === "local") return installationType;
|
|
2470
|
+
if (installationType === "project") return "project";
|
|
2471
|
+
throw new Error(`Claude plugins support installation types user, local, or project; got '${installationType}'.`);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
// src/targets/plugins/codex.ts
|
|
2475
|
+
import { join as join10 } from "path";
|
|
2476
|
+
async function codexPluginSpec(request) {
|
|
2477
|
+
const pluginName = await jsonPluginName(request.sourcePath, ".codex-plugin/plugin.json", request.installName);
|
|
2478
|
+
const marketplaceName = agentwheelMarketplaceName(request.artifact.packageName, pluginName);
|
|
2479
|
+
const stateRoot = pluginStateRoot({
|
|
2480
|
+
targetRoot: request.targetRoot,
|
|
2481
|
+
runtime: "codex",
|
|
2482
|
+
installationType: request.installationType,
|
|
2483
|
+
packageName: request.artifact.packageName,
|
|
2484
|
+
installName: request.installName
|
|
2485
|
+
});
|
|
2486
|
+
const marketplaceRoot = join10(stateRoot, "marketplace");
|
|
2487
|
+
const selector = `${pluginName}@${marketplaceName}`;
|
|
2488
|
+
return {
|
|
2489
|
+
runtime: "codex",
|
|
2490
|
+
pluginName,
|
|
2491
|
+
marketplaceName,
|
|
2492
|
+
stateRoot,
|
|
2493
|
+
installCommands: [
|
|
2494
|
+
["codex", "plugin", "marketplace", "add", marketplaceRoot, "--json"],
|
|
2495
|
+
["codex", "plugin", "add", selector, "--json"]
|
|
2496
|
+
],
|
|
2497
|
+
uninstallCommands: [
|
|
2498
|
+
["codex", "plugin", "remove", selector, "--json"],
|
|
2499
|
+
["codex", "plugin", "marketplace", "remove", marketplaceName, "--json"]
|
|
2500
|
+
]
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
// src/targets/plugins/copilot.ts
|
|
2505
|
+
import { join as join11 } from "path";
|
|
2506
|
+
async function copilotPluginSpec(request) {
|
|
2507
|
+
if (request.installationType !== "user") {
|
|
2508
|
+
throw new Error("Copilot plugins are persistent user-level installs only; pass --installation-type user.");
|
|
2509
|
+
}
|
|
2510
|
+
const pluginName = await jsonPluginName(request.sourcePath, "plugin.json", request.installName);
|
|
2511
|
+
const stateRoot = pluginStateRoot({
|
|
2512
|
+
targetRoot: request.targetRoot,
|
|
2513
|
+
runtime: "copilot",
|
|
2514
|
+
installationType: request.installationType,
|
|
2515
|
+
packageName: request.artifact.packageName,
|
|
2516
|
+
installName: request.installName
|
|
2517
|
+
});
|
|
2518
|
+
const pluginRoot = join11(stateRoot, "plugin");
|
|
2519
|
+
return {
|
|
2520
|
+
runtime: "copilot",
|
|
2521
|
+
pluginName,
|
|
2522
|
+
stateRoot,
|
|
2523
|
+
installCommands: [["copilot", "plugin", "install", pluginRoot]],
|
|
2524
|
+
uninstallCommands: [["copilot", "plugin", "uninstall", pluginName]]
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
// src/targets/plugins/hermes.ts
|
|
2529
|
+
import { join as join12 } from "path";
|
|
2530
|
+
async function hermesPluginSpec(request) {
|
|
2531
|
+
if (request.installationType !== "user") {
|
|
2532
|
+
throw new Error("Hermes plugins are user-level installs only; pass --installation-type user.");
|
|
2533
|
+
}
|
|
2534
|
+
const pluginName = await yamlPluginName(request.sourcePath, ["plugin.yaml", "plugin.yml"], request.installName);
|
|
2535
|
+
const stateRoot = pluginStateRoot({
|
|
2536
|
+
targetRoot: request.targetRoot,
|
|
2537
|
+
runtime: "hermes",
|
|
2538
|
+
installationType: request.installationType,
|
|
2539
|
+
packageName: request.artifact.packageName,
|
|
2540
|
+
installName: request.installName
|
|
2541
|
+
});
|
|
2542
|
+
const repoRoot = join12(stateRoot, "repo");
|
|
2543
|
+
return {
|
|
2544
|
+
runtime: "hermes",
|
|
2545
|
+
pluginName,
|
|
2546
|
+
stateRoot,
|
|
2547
|
+
installCommands: [["hermes", "plugins", "install", "--force", "--enable", `file://${repoRoot}`]],
|
|
2548
|
+
uninstallCommands: [["hermes", "plugins", "remove", pluginName]]
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
|
|
1904
2552
|
// src/targets/plugins/openclaw.ts
|
|
2553
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
2554
|
+
import { join as join13 } from "path";
|
|
1905
2555
|
function openClawPluginInstallCommand(request) {
|
|
1906
2556
|
return ["openclaw", "plugins", "install", "--force", request.path];
|
|
1907
2557
|
}
|
|
2558
|
+
function openClawPluginUninstallCommand(pluginName) {
|
|
2559
|
+
return ["openclaw", "plugins", "uninstall", pluginName, "--force"];
|
|
2560
|
+
}
|
|
2561
|
+
async function openClawPluginSpec(request) {
|
|
2562
|
+
const pluginName = await openClawPluginName(request.path, request.fallbackPluginName);
|
|
2563
|
+
return {
|
|
2564
|
+
runtime: "openclaw",
|
|
2565
|
+
pluginName,
|
|
2566
|
+
installCommands: [openClawPluginInstallCommand({ path: request.path, dryRun: true })],
|
|
2567
|
+
uninstallCommands: [openClawPluginUninstallCommand(pluginName)]
|
|
2568
|
+
};
|
|
2569
|
+
}
|
|
2570
|
+
async function openClawPluginName(root, fallback) {
|
|
2571
|
+
for (const manifestName of ["plugin.json", "openclaw.plugin.json"]) {
|
|
2572
|
+
const manifestPath = join13(root, manifestName);
|
|
2573
|
+
if (!await pathExists(manifestPath)) continue;
|
|
2574
|
+
const parsed = JSON.parse(await readFile12(manifestPath, "utf8"));
|
|
2575
|
+
if (typeof parsed.name === "string" && parsed.name.trim().length > 0) return parsed.name.trim();
|
|
2576
|
+
}
|
|
2577
|
+
return fallback;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// src/targets/plugins/index.ts
|
|
2581
|
+
async function semanticPluginSpecForArtifact(request) {
|
|
2582
|
+
if (request.semantic === "openclaw-plugin") {
|
|
2583
|
+
return openClawPluginSpec({
|
|
2584
|
+
path: request.sourcePath,
|
|
2585
|
+
fallbackPluginName: request.installName
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
if (request.semantic === "claude-plugin") {
|
|
2589
|
+
return claudePluginSpec(request);
|
|
2590
|
+
}
|
|
2591
|
+
if (request.semantic === "codex-plugin") {
|
|
2592
|
+
return codexPluginSpec(request);
|
|
2593
|
+
}
|
|
2594
|
+
if (request.semantic === "copilot-plugin") {
|
|
2595
|
+
return copilotPluginSpec(request);
|
|
2596
|
+
}
|
|
2597
|
+
if (request.semantic === "hermes-plugin") {
|
|
2598
|
+
return hermesPluginSpec(request);
|
|
2599
|
+
}
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
// src/validation/artifacts.ts
|
|
2604
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
2605
|
+
import { basename as basename6, join as join14 } from "path";
|
|
2606
|
+
import { parseDocument as parseDocument2 } from "yaml";
|
|
2607
|
+
|
|
2608
|
+
// src/model/selection.ts
|
|
2609
|
+
function artifactSelectorKey(artifact) {
|
|
2610
|
+
return `${artifact.type}/${artifact.name}`;
|
|
2611
|
+
}
|
|
2612
|
+
function artifactSelectorAliases(artifact) {
|
|
2613
|
+
const primary = artifactSelectorKey(artifact);
|
|
2614
|
+
if (artifact.type !== "subagents") return [primary];
|
|
2615
|
+
const baseName = subagentBaseName(artifact.name);
|
|
2616
|
+
if (baseName === artifact.name) return [primary];
|
|
2617
|
+
return [primary, `subagents/${baseName}`];
|
|
2618
|
+
}
|
|
2619
|
+
function normalizeArtifactSelectors(select, legacySkills) {
|
|
2620
|
+
const selected = [
|
|
2621
|
+
...select ?? [],
|
|
2622
|
+
...(legacySkills ?? []).map((name) => `skills/${name}`)
|
|
2623
|
+
].flatMap(splitSelectorList);
|
|
2624
|
+
if (selected.length === 0) return void 0;
|
|
2625
|
+
return [...new Set(selected.map(parseArtifactSelector))];
|
|
2626
|
+
}
|
|
2627
|
+
function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
2628
|
+
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
2629
|
+
if (!selected?.length) return artifacts;
|
|
2630
|
+
const selectedSet = new Set(selected);
|
|
2631
|
+
const available = new Set(artifacts.flatMap(artifactSelectorAliases));
|
|
2632
|
+
const missing = selected.filter((selector) => !available.has(selector));
|
|
2633
|
+
if (missing.length > 0) {
|
|
2634
|
+
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
2635
|
+
}
|
|
2636
|
+
return artifacts.filter((artifact) => artifact.required || artifactSelectorAliases(artifact).some((selector) => selectedSet.has(selector)));
|
|
2637
|
+
}
|
|
2638
|
+
function splitSelectorList(value) {
|
|
2639
|
+
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2640
|
+
}
|
|
2641
|
+
function parseArtifactSelector(value) {
|
|
2642
|
+
const slash = value.indexOf("/");
|
|
2643
|
+
if (slash <= 0 || slash === value.length - 1) {
|
|
2644
|
+
throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
|
|
2645
|
+
}
|
|
2646
|
+
const type = value.slice(0, slash);
|
|
2647
|
+
const name = value.slice(slash + 1);
|
|
2648
|
+
const parsedType = artifactTypeSchema.safeParse(type);
|
|
2649
|
+
if (!parsedType.success) {
|
|
2650
|
+
throw new Error(`Invalid artifact selector type: ${type}`);
|
|
2651
|
+
}
|
|
2652
|
+
return `${parsedType.data}/${name}`;
|
|
2653
|
+
}
|
|
2654
|
+
function subagentBaseName(name) {
|
|
2655
|
+
return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
|
|
2656
|
+
}
|
|
1908
2657
|
|
|
1909
2658
|
// src/validation/artifacts.ts
|
|
1910
|
-
|
|
1911
|
-
|
|
2659
|
+
var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
|
|
2660
|
+
var pluginFormats = ["claude-plugin", "codex-plugin", "hermes-plugin", "copilot-plugin", "openclaw-plugin"];
|
|
2661
|
+
async function filterArtifactsByInstallFormat(artifacts, adapter, installationType, options = {}) {
|
|
2662
|
+
const selectedSet = new Set(normalizeArtifactSelectors(options.selected ?? []) ?? []);
|
|
2663
|
+
const kept = [];
|
|
2664
|
+
const skipped = [];
|
|
2665
|
+
let installableCount = 0;
|
|
2666
|
+
for (const artifact of artifacts) {
|
|
2667
|
+
if (artifact.type === "fragments") {
|
|
2668
|
+
kept.push(artifact);
|
|
2669
|
+
continue;
|
|
2670
|
+
}
|
|
2671
|
+
const target = targetMappingForArtifact(adapter, artifact.type, installationType);
|
|
2672
|
+
if (!target?.enabled) {
|
|
2673
|
+
if (artifact.type === "rules") {
|
|
2674
|
+
skipped.push({
|
|
2675
|
+
artifact,
|
|
2676
|
+
compatibility: {
|
|
2677
|
+
compatible: false,
|
|
2678
|
+
knownIncompatible: true,
|
|
2679
|
+
expected: behavioralRuleFormats,
|
|
2680
|
+
message: "target does not support behavioral Markdown rules"
|
|
2681
|
+
}
|
|
2682
|
+
});
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
kept.push(artifact);
|
|
2686
|
+
continue;
|
|
2687
|
+
}
|
|
2688
|
+
const compatibility = await artifactFormatCompatibility(artifact, target);
|
|
2689
|
+
if (compatibility.knownIncompatible) {
|
|
2690
|
+
skipped.push({ artifact, compatibility });
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
kept.push(artifact);
|
|
2694
|
+
installableCount += 1;
|
|
2695
|
+
}
|
|
2696
|
+
if (installableCount === 0 && skipped.length > 0) return artifacts;
|
|
2697
|
+
for (const item of skipped) {
|
|
2698
|
+
options.warn?.(formatSkipWarning(item.artifact, item.compatibility, adapter, installationType, selectedSet));
|
|
2699
|
+
}
|
|
2700
|
+
return kept;
|
|
2701
|
+
}
|
|
1912
2702
|
async function validateArtifactsForInstall(artifacts, adapter, installationType) {
|
|
1913
2703
|
const issues = [];
|
|
1914
2704
|
for (const artifact of artifacts) {
|
|
@@ -1928,17 +2718,18 @@ async function validateArtifactsForInstall(artifacts, adapter, installationType)
|
|
|
1928
2718
|
}
|
|
1929
2719
|
async function validateArtifact(artifact, target) {
|
|
1930
2720
|
const issues = [];
|
|
1931
|
-
const
|
|
1932
|
-
|
|
2721
|
+
const compatibility = await artifactFormatCompatibility(artifact, target);
|
|
2722
|
+
const format = compatibility.format;
|
|
2723
|
+
if (compatibility.expected?.length) {
|
|
1933
2724
|
if (!format) {
|
|
1934
2725
|
issues.push({
|
|
1935
2726
|
artifact,
|
|
1936
|
-
message: `format is unknown; expected one of: ${
|
|
2727
|
+
message: `format is unknown; expected one of: ${compatibility.expected.join(", ")}`
|
|
1937
2728
|
});
|
|
1938
|
-
} else if (!
|
|
2729
|
+
} else if (!compatibility.compatible) {
|
|
1939
2730
|
issues.push({
|
|
1940
2731
|
artifact,
|
|
1941
|
-
message: `format '${format}' is not compatible; expected one of: ${
|
|
2732
|
+
message: compatibility.message ?? `format '${format}' is not compatible; expected one of: ${compatibility.expected.join(", ")}`
|
|
1942
2733
|
});
|
|
1943
2734
|
}
|
|
1944
2735
|
}
|
|
@@ -1946,9 +2737,31 @@ async function validateArtifact(artifact, target) {
|
|
|
1946
2737
|
issues.push(...await validateGenericStructure(artifact, target));
|
|
1947
2738
|
return issues;
|
|
1948
2739
|
}
|
|
2740
|
+
async function artifactFormatCompatibility(artifact, target) {
|
|
2741
|
+
const format = artifact.format ?? await inferArtifactFormat(artifact, target) ?? semanticDefaultFormat(artifact, target);
|
|
2742
|
+
const expected = expectedFormats(artifact, target);
|
|
2743
|
+
if (!expected?.length) return { compatible: true, knownIncompatible: false, format };
|
|
2744
|
+
if (!format) return { compatible: false, knownIncompatible: false, expected };
|
|
2745
|
+
if (artifact.type === "rules" && target.formats?.length && target.formats.every((item) => !behavioralRuleFormats.includes(item))) {
|
|
2746
|
+
return {
|
|
2747
|
+
compatible: false,
|
|
2748
|
+
knownIncompatible: true,
|
|
2749
|
+
format,
|
|
2750
|
+
expected,
|
|
2751
|
+
message: `target does not support behavioral Markdown rules; expected one of: ${expected.join(", ")}`
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2754
|
+
const compatible = expected.includes(format);
|
|
2755
|
+
return {
|
|
2756
|
+
compatible,
|
|
2757
|
+
knownIncompatible: !compatible,
|
|
2758
|
+
format,
|
|
2759
|
+
expected
|
|
2760
|
+
};
|
|
2761
|
+
}
|
|
1949
2762
|
async function inferArtifactFormat(artifact, target) {
|
|
1950
2763
|
if (artifact.type === "rules") {
|
|
1951
|
-
if (hasExtension(artifact, ".rules")) return "codex-command-policy";
|
|
2764
|
+
if (artifact.format === "codex-command-policy" || hasExtension(artifact, ".rules")) return "codex-command-policy";
|
|
1952
2765
|
if (hasExtension(artifact, ".md") || hasExtension(artifact, ".markdown")) return "markdown-rule";
|
|
1953
2766
|
return void 0;
|
|
1954
2767
|
}
|
|
@@ -1957,26 +2770,51 @@ async function inferArtifactFormat(artifact, target) {
|
|
|
1957
2770
|
}
|
|
1958
2771
|
return void 0;
|
|
1959
2772
|
}
|
|
1960
|
-
function semanticDefaultFormat(target) {
|
|
1961
|
-
if (
|
|
2773
|
+
function semanticDefaultFormat(artifact, target) {
|
|
2774
|
+
if (artifact.type === "plugins" && isPluginFormat(target.semantic)) return target.semantic;
|
|
1962
2775
|
return void 0;
|
|
1963
2776
|
}
|
|
2777
|
+
function expectedFormats(artifact, target) {
|
|
2778
|
+
if (artifact.type === "rules") {
|
|
2779
|
+
const declared = target.formats?.filter((format) => behavioralRuleFormats.includes(format));
|
|
2780
|
+
if (target.formats?.length) return declared?.length ? declared : behavioralRuleFormats;
|
|
2781
|
+
return behavioralRuleFormats;
|
|
2782
|
+
}
|
|
2783
|
+
if (artifact.type === "plugins") {
|
|
2784
|
+
if (isPluginFormat(target.semantic)) return [target.semantic];
|
|
2785
|
+
const declared = target.formats?.filter((format) => pluginFormats.includes(format));
|
|
2786
|
+
if (target.formats?.length) return declared?.length ? declared : pluginFormats;
|
|
2787
|
+
}
|
|
2788
|
+
return target.formats;
|
|
2789
|
+
}
|
|
1964
2790
|
async function validateKnownFormat(artifact, format, target) {
|
|
1965
2791
|
if (!format) return [];
|
|
1966
|
-
if (format === "codex-command-policy") return validateCodexCommandPolicyRule(artifact);
|
|
1967
2792
|
if (format === "markdown-rule" || format === "claude-markdown-rule" || format === "copilot-instruction-rule") {
|
|
1968
2793
|
return validateMarkdownRule(artifact, format);
|
|
1969
2794
|
}
|
|
1970
2795
|
if (format === "openclaw-plugin" || target.semantic === "openclaw-plugin") {
|
|
1971
2796
|
return validateOpenClawPlugin(artifact);
|
|
1972
2797
|
}
|
|
2798
|
+
if (format === "claude-plugin" || target.semantic === "claude-plugin") {
|
|
2799
|
+
return validateJsonPluginDescriptor(artifact, ".claude-plugin/plugin.json", "Claude");
|
|
2800
|
+
}
|
|
2801
|
+
if (format === "codex-plugin" || target.semantic === "codex-plugin") {
|
|
2802
|
+
return validateJsonPluginDescriptor(artifact, ".codex-plugin/plugin.json", "Codex");
|
|
2803
|
+
}
|
|
2804
|
+
if (format === "copilot-plugin" || target.semantic === "copilot-plugin") {
|
|
2805
|
+
return validateJsonPluginDescriptor(artifact, "plugin.json", "Copilot");
|
|
2806
|
+
}
|
|
2807
|
+
if (format === "hermes-plugin" || target.semantic === "hermes-plugin") {
|
|
2808
|
+
return validateHermesPlugin(artifact);
|
|
2809
|
+
}
|
|
2810
|
+
if (pluginFormats.includes(format)) return validatePluginArtifact(artifact, format);
|
|
1973
2811
|
return [];
|
|
1974
2812
|
}
|
|
1975
2813
|
async function validateGenericStructure(artifact, target) {
|
|
1976
2814
|
const issues = [];
|
|
1977
2815
|
if (artifact.type === "skills") {
|
|
1978
2816
|
if (artifact.kind === "dir") {
|
|
1979
|
-
const skillMd =
|
|
2817
|
+
const skillMd = join14(artifactPath(artifact), "SKILL.md");
|
|
1980
2818
|
if (!await pathExists(skillMd)) {
|
|
1981
2819
|
issues.push({ artifact, message: "skill directory must contain SKILL.md" });
|
|
1982
2820
|
} else {
|
|
@@ -1992,6 +2830,10 @@ async function validateGenericStructure(artifact, target) {
|
|
|
1992
2830
|
const parsed = await parseJsonObjectArtifact(artifact);
|
|
1993
2831
|
if (!parsed.ok) issues.push({ artifact, message: parsed.message });
|
|
1994
2832
|
}
|
|
2833
|
+
if (target.merge === "yaml-deep") {
|
|
2834
|
+
const parsed = await parseYamlObjectArtifact(artifact);
|
|
2835
|
+
if (!parsed.ok) issues.push({ artifact, message: parsed.message });
|
|
2836
|
+
}
|
|
1995
2837
|
if (target.merge === "codex-toml-mcp") {
|
|
1996
2838
|
const parsed = await parseJsonObjectArtifact(artifact);
|
|
1997
2839
|
if (!parsed.ok) {
|
|
@@ -2005,7 +2847,7 @@ async function validateGenericStructure(artifact, target) {
|
|
|
2005
2847
|
async function validateSkillFrontmatter(artifact, skillMdPath) {
|
|
2006
2848
|
let content;
|
|
2007
2849
|
try {
|
|
2008
|
-
content = await
|
|
2850
|
+
content = await readFile13(skillMdPath, "utf8");
|
|
2009
2851
|
} catch (error) {
|
|
2010
2852
|
return [{ artifact, message: `could not read SKILL.md: ${errorMessage(error)}` }];
|
|
2011
2853
|
}
|
|
@@ -2031,33 +2873,6 @@ async function validateSkillFrontmatter(artifact, skillMdPath) {
|
|
|
2031
2873
|
}
|
|
2032
2874
|
return issues;
|
|
2033
2875
|
}
|
|
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
2876
|
function validateMarkdownRule(artifact, format) {
|
|
2062
2877
|
const label = format === "copilot-instruction-rule" ? "Copilot instruction rules" : format === "claude-markdown-rule" ? "Claude markdown rules" : "Markdown rules";
|
|
2063
2878
|
const issues = [];
|
|
@@ -2072,6 +2887,72 @@ function validateMarkdownRule(artifact, format) {
|
|
|
2072
2887
|
}
|
|
2073
2888
|
return issues;
|
|
2074
2889
|
}
|
|
2890
|
+
function validatePluginArtifact(artifact, format) {
|
|
2891
|
+
const issues = [];
|
|
2892
|
+
if (artifact.type !== "plugins") {
|
|
2893
|
+
issues.push({ artifact, message: `${format} format is only valid for plugins artifacts` });
|
|
2894
|
+
}
|
|
2895
|
+
if (artifact.kind !== "dir") {
|
|
2896
|
+
issues.push({ artifact, message: `${format} plugins must be directory artifacts` });
|
|
2897
|
+
}
|
|
2898
|
+
return issues;
|
|
2899
|
+
}
|
|
2900
|
+
async function validateJsonPluginDescriptor(artifact, relativeManifestPath, label) {
|
|
2901
|
+
const generic = validatePluginArtifact(artifact, `${label.toLowerCase()}-plugin`);
|
|
2902
|
+
if (generic.length > 0) return generic;
|
|
2903
|
+
const manifestPath = join14(artifactPath(artifact), relativeManifestPath);
|
|
2904
|
+
if (!await pathExists(manifestPath)) {
|
|
2905
|
+
return [{ artifact, message: `${label} plugins must contain ${relativeManifestPath}` }];
|
|
2906
|
+
}
|
|
2907
|
+
const parsed = await parseJsonPluginManifest(manifestPath, label);
|
|
2908
|
+
return parsed.ok ? [] : [{ artifact, message: parsed.message }];
|
|
2909
|
+
}
|
|
2910
|
+
async function validateHermesPlugin(artifact) {
|
|
2911
|
+
const generic = validatePluginArtifact(artifact, "hermes-plugin");
|
|
2912
|
+
if (generic.length > 0) return generic;
|
|
2913
|
+
const root = artifactPath(artifact);
|
|
2914
|
+
const manifestPaths = [join14(root, "plugin.yaml"), join14(root, "plugin.yml")];
|
|
2915
|
+
const manifestPath = await firstExistingPath(manifestPaths);
|
|
2916
|
+
if (!manifestPath) {
|
|
2917
|
+
return [{ artifact, message: "Hermes plugins must contain plugin.yaml or plugin.yml" }];
|
|
2918
|
+
}
|
|
2919
|
+
try {
|
|
2920
|
+
const document = parseDocument2(await readFile13(manifestPath, "utf8"));
|
|
2921
|
+
if (document.errors.length > 0) {
|
|
2922
|
+
return [{ artifact, message: `Hermes ${basename6(manifestPath)} must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` }];
|
|
2923
|
+
}
|
|
2924
|
+
const parsed = document.toJSON();
|
|
2925
|
+
if (!isUnknownRecord(parsed)) {
|
|
2926
|
+
return [{ artifact, message: `Hermes ${basename6(manifestPath)} must contain a YAML object` }];
|
|
2927
|
+
}
|
|
2928
|
+
if (!hasStringField(parsed, "name") && !hasStringField(parsed, "module") && !hasStringField(parsed, "package") && !hasNestedPackageName(parsed)) {
|
|
2929
|
+
return [{ artifact, message: `Hermes ${basename6(manifestPath)} must declare a non-empty name, module, or package name` }];
|
|
2930
|
+
}
|
|
2931
|
+
return [];
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
return [{ artifact, message: `Hermes ${basename6(manifestPath)} must be valid YAML: ${errorMessage(error)}` }];
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
async function firstExistingPath(paths) {
|
|
2937
|
+
for (const path of paths) {
|
|
2938
|
+
if (await pathExists(path)) return path;
|
|
2939
|
+
}
|
|
2940
|
+
return void 0;
|
|
2941
|
+
}
|
|
2942
|
+
async function parseJsonPluginManifest(manifestPath, label) {
|
|
2943
|
+
try {
|
|
2944
|
+
const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
|
|
2945
|
+
if (!isRecord5(parsed)) {
|
|
2946
|
+
return { ok: false, message: `${label} ${basename6(manifestPath)} must be a JSON object` };
|
|
2947
|
+
}
|
|
2948
|
+
if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
|
|
2949
|
+
return { ok: false, message: `${label} ${basename6(manifestPath)} must declare a non-empty name` };
|
|
2950
|
+
}
|
|
2951
|
+
return { ok: true, name: parsed.name.trim() };
|
|
2952
|
+
} catch (error) {
|
|
2953
|
+
return { ok: false, message: `${label} ${basename6(manifestPath)} must be valid JSON: ${errorMessage(error)}` };
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2075
2956
|
async function validateOpenClawPlugin(artifact) {
|
|
2076
2957
|
const issues = [];
|
|
2077
2958
|
if (artifact.type !== "plugins") {
|
|
@@ -2097,15 +2978,15 @@ async function validateOpenClawPlugin(artifact) {
|
|
|
2097
2978
|
}
|
|
2098
2979
|
async function openClawPluginManifestPaths(artifact) {
|
|
2099
2980
|
const root = artifactPath(artifact);
|
|
2100
|
-
const candidates = [
|
|
2981
|
+
const candidates = [join14(root, "plugin.json"), join14(root, "openclaw.plugin.json")];
|
|
2101
2982
|
const existing = await Promise.all(candidates.map(async (candidate) => await pathExists(candidate) ? candidate : void 0));
|
|
2102
2983
|
return existing.filter((candidate) => candidate !== void 0);
|
|
2103
2984
|
}
|
|
2104
2985
|
async function parseOpenClawPluginManifest(manifestPath) {
|
|
2105
|
-
const manifestName =
|
|
2986
|
+
const manifestName = basename6(manifestPath);
|
|
2106
2987
|
try {
|
|
2107
|
-
const parsed = JSON.parse(await
|
|
2108
|
-
if (!
|
|
2988
|
+
const parsed = JSON.parse(await readFile13(manifestPath, "utf8"));
|
|
2989
|
+
if (!isRecord5(parsed)) {
|
|
2109
2990
|
return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be a JSON object` };
|
|
2110
2991
|
}
|
|
2111
2992
|
if (typeof parsed.name !== "string" || parsed.name.trim().length === 0) {
|
|
@@ -2116,32 +2997,39 @@ async function parseOpenClawPluginManifest(manifestPath) {
|
|
|
2116
2997
|
return { ok: false, path: manifestPath, message: `OpenClaw ${manifestName} must be valid JSON: ${errorMessage(error)}` };
|
|
2117
2998
|
}
|
|
2118
2999
|
}
|
|
2119
|
-
async function
|
|
2120
|
-
if (artifact.kind !== "file")
|
|
3000
|
+
async function parseJsonObjectArtifact(artifact) {
|
|
3001
|
+
if (artifact.kind !== "file") {
|
|
3002
|
+
return { ok: false, message: "merge artifacts must be JSON files" };
|
|
3003
|
+
}
|
|
2121
3004
|
try {
|
|
2122
|
-
|
|
3005
|
+
const parsed = JSON.parse(await readFile13(artifactPath(artifact), "utf8"));
|
|
3006
|
+
if (!isRecord5(parsed)) return { ok: false, message: "merge artifacts must contain a JSON object" };
|
|
3007
|
+
return { ok: true, value: parsed };
|
|
2123
3008
|
} catch (error) {
|
|
2124
|
-
|
|
2125
|
-
return void 0;
|
|
3009
|
+
return { ok: false, message: `merge artifact must be valid JSON: ${errorMessage(error)}` };
|
|
2126
3010
|
}
|
|
2127
3011
|
}
|
|
2128
|
-
async function
|
|
3012
|
+
async function parseYamlObjectArtifact(artifact) {
|
|
2129
3013
|
if (artifact.kind !== "file") {
|
|
2130
|
-
return { ok: false, message: "merge artifacts must be
|
|
3014
|
+
return { ok: false, message: "merge artifacts must be YAML files" };
|
|
2131
3015
|
}
|
|
2132
3016
|
try {
|
|
2133
|
-
const
|
|
2134
|
-
if (
|
|
3017
|
+
const document = parseDocument2(await readFile13(artifactPath(artifact), "utf8"));
|
|
3018
|
+
if (document.errors.length > 0) {
|
|
3019
|
+
return { ok: false, message: `merge artifact must be valid YAML: ${document.errors[0]?.message ?? "parse error"}` };
|
|
3020
|
+
}
|
|
3021
|
+
const parsed = document.toJSON();
|
|
3022
|
+
if (!isUnknownRecord(parsed)) return { ok: false, message: "merge artifacts must contain a YAML object" };
|
|
2135
3023
|
return { ok: true, value: parsed };
|
|
2136
3024
|
} catch (error) {
|
|
2137
|
-
return { ok: false, message: `merge artifact must be valid
|
|
3025
|
+
return { ok: false, message: `merge artifact must be valid YAML: ${errorMessage(error)}` };
|
|
2138
3026
|
}
|
|
2139
3027
|
}
|
|
2140
3028
|
function extractMcpServers2(source) {
|
|
2141
|
-
const raw =
|
|
3029
|
+
const raw = isRecord5(source.mcpServers) ? source.mcpServers : source;
|
|
2142
3030
|
const servers = {};
|
|
2143
3031
|
for (const [name, value] of Object.entries(raw)) {
|
|
2144
|
-
if (
|
|
3032
|
+
if (isRecord5(value)) servers[name] = value;
|
|
2145
3033
|
}
|
|
2146
3034
|
return servers;
|
|
2147
3035
|
}
|
|
@@ -2153,11 +3041,33 @@ function artifactLabel(artifact) {
|
|
|
2153
3041
|
return `${owner}${artifact.type}/${artifact.name}`;
|
|
2154
3042
|
}
|
|
2155
3043
|
function hasExtension(artifact, extension) {
|
|
2156
|
-
return
|
|
3044
|
+
return basename6(artifact.name).toLowerCase().endsWith(extension) || basename6(artifactPath(artifact)).toLowerCase().endsWith(extension);
|
|
2157
3045
|
}
|
|
2158
|
-
function
|
|
3046
|
+
function isPluginFormat(value) {
|
|
3047
|
+
return value !== void 0 && pluginFormats.includes(value);
|
|
3048
|
+
}
|
|
3049
|
+
function isRecord5(value) {
|
|
3050
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3051
|
+
}
|
|
3052
|
+
function isUnknownRecord(value) {
|
|
2159
3053
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2160
3054
|
}
|
|
3055
|
+
function hasStringField(value, field) {
|
|
3056
|
+
const raw = value[field];
|
|
3057
|
+
return typeof raw === "string" && raw.trim().length > 0;
|
|
3058
|
+
}
|
|
3059
|
+
function hasNestedPackageName(value) {
|
|
3060
|
+
const raw = value.package;
|
|
3061
|
+
return isUnknownRecord(raw) && hasStringField(raw, "name");
|
|
3062
|
+
}
|
|
3063
|
+
function formatSkipWarning(artifact, compatibility, adapter, installationType, selectedSet) {
|
|
3064
|
+
const selector = artifactSelectorKey(artifact);
|
|
3065
|
+
const reason = selectedSet.has(selector) ? "selected but format-incompatible" : "format-incompatible";
|
|
3066
|
+
const expected = compatibility.expected?.join(", ") ?? "supported format";
|
|
3067
|
+
const format = compatibility.format ?? "unknown";
|
|
3068
|
+
const details = compatibility.message ?? `${adapter.name}/${installationType} expects ${expected}, got ${format}`;
|
|
3069
|
+
return `skip ${selector} (${reason}: ${details})`;
|
|
3070
|
+
}
|
|
2161
3071
|
function errorMessage(error) {
|
|
2162
3072
|
return error instanceof Error ? error.message : String(error);
|
|
2163
3073
|
}
|
|
@@ -2165,17 +3075,18 @@ function errorMessage(error) {
|
|
|
2165
3075
|
// src/install/plan.ts
|
|
2166
3076
|
async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot, manifest, transport = localTransport, options = {}) {
|
|
2167
3077
|
const requestedInstallationType = options.installationType ?? defaultInstallationType;
|
|
2168
|
-
const
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2171
|
-
|
|
3078
|
+
const installableArtifacts = await filterArtifactsByInstallFormat(desiredArtifacts, adapter, requestedInstallationType, { warn: options.warn });
|
|
3079
|
+
const installationType = resolveInstallationTypeForArtifacts(adapter, installableArtifacts.map((artifact) => artifact.type), requestedInstallationType);
|
|
3080
|
+
const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, installableArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
3081
|
+
await validateArtifactsForInstall(installableArtifacts, adapter, installationType);
|
|
3082
|
+
for (const artifact of installableArtifacts) {
|
|
2172
3083
|
if (artifact.meta.dependencyRole !== "root" && isGuardedMergeTarget(artifact.type)) {
|
|
2173
3084
|
throw new Error(`Dependency-provided ${artifact.type} artifacts cannot be installed until per-subentry ownership exists: ${artifact.type}/${artifact.name}`);
|
|
2174
3085
|
}
|
|
2175
3086
|
}
|
|
2176
3087
|
const desired = [];
|
|
2177
|
-
for (const artifact of
|
|
2178
|
-
const op = operationForArtifact(artifact, adapter, installRoot, installationType, artifact.meta);
|
|
3088
|
+
for (const artifact of installableArtifacts) {
|
|
3089
|
+
const op = await operationForArtifact(artifact, adapter, installRoot, installationType, artifact.meta);
|
|
2179
3090
|
if (op) {
|
|
2180
3091
|
desired.push(op);
|
|
2181
3092
|
}
|
|
@@ -2188,20 +3099,21 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2188
3099
|
if (workspaceOwner) {
|
|
2189
3100
|
for (const op of desiredOps) op.workspaceOwner = workspaceOwner;
|
|
2190
3101
|
}
|
|
2191
|
-
|
|
2192
|
-
const
|
|
3102
|
+
const preparedOps = await prepareManagedBlockOperations(desiredOps, adapter, targetRoot, transport, options);
|
|
3103
|
+
for (const op of preparedOps) assertOperationContained(op, targetRoot);
|
|
3104
|
+
const migration = await migrateManifestForPlan(manifest, preparedOps, targetRoot, transport);
|
|
2193
3105
|
const effectiveEntries = migration.entries;
|
|
2194
3106
|
const manifestByPath = new Map(effectiveEntries.map((entry) => [entry.path, entry]));
|
|
2195
|
-
const { desired, collisions } = await splitCollisionOperations(
|
|
3107
|
+
const { desired, collisions } = await splitCollisionOperations(preparedOps, manifestByPath, transport);
|
|
2196
3108
|
const operations = [];
|
|
2197
3109
|
operations.push(...collisions);
|
|
2198
3110
|
for (const op of desired.values()) {
|
|
2199
3111
|
if (op.action === "plugin") {
|
|
2200
3112
|
const existing2 = manifestByPath.get(op.relativeDestPath);
|
|
2201
|
-
if (existing2 && existing2.
|
|
2202
|
-
operations.push({ ...op, action: "skip", manifestHash: existing2.hash, reason: "plugin already
|
|
3113
|
+
if (existing2 && existing2.sourceHash === op.desiredHash && existing2.executed === true) {
|
|
3114
|
+
operations.push({ ...op, action: "skip", manifestHash: existing2.hash, reason: "semantic plugin already executed" });
|
|
2203
3115
|
} else {
|
|
2204
|
-
operations.push(op);
|
|
3116
|
+
operations.push(existing2 && existing2.sourceHash === op.desiredHash ? { ...op, manifestHash: existing2.hash, reason: "semantic plugin pending execution" } : op);
|
|
2205
3117
|
}
|
|
2206
3118
|
continue;
|
|
2207
3119
|
}
|
|
@@ -2214,6 +3126,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2214
3126
|
}
|
|
2215
3127
|
continue;
|
|
2216
3128
|
}
|
|
3129
|
+
if (op.mode === managedInstructionBlockMode) {
|
|
3130
|
+
operations.push(...await planManagedBlockOperation(op, manifestByPath, transport, options, workspaceOwner, targetRoot));
|
|
3131
|
+
continue;
|
|
3132
|
+
}
|
|
2217
3133
|
if (op.mergeStrategy) {
|
|
2218
3134
|
const existing2 = manifestByPath.get(op.relativeDestPath);
|
|
2219
3135
|
const exists2 = await transport.pathExists(op.destPath);
|
|
@@ -2225,7 +3141,7 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2225
3141
|
if (existing2 && existing2.sourceHash === op.desiredHash) {
|
|
2226
3142
|
operations.push({ ...op, action: "skip", currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
|
|
2227
3143
|
} else {
|
|
2228
|
-
operations.push({ ...op, action: "update", currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing
|
|
3144
|
+
operations.push({ ...op, action: "update", currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing destination" });
|
|
2229
3145
|
}
|
|
2230
3146
|
continue;
|
|
2231
3147
|
}
|
|
@@ -2301,9 +3217,10 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2301
3217
|
}
|
|
2302
3218
|
for (const entry of effectiveEntries) {
|
|
2303
3219
|
if (desired.has(entry.path)) continue;
|
|
2304
|
-
const
|
|
2305
|
-
|
|
2306
|
-
|
|
3220
|
+
const semanticPlugin = entry.semanticPlugin;
|
|
3221
|
+
const destPath = semanticPlugin ? targetRoot : join15(targetRoot, entry.path);
|
|
3222
|
+
if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
|
|
3223
|
+
const currentHash = semanticPlugin ? entry.hash : await currentEntryHash(entry, destPath, transport);
|
|
2307
3224
|
if (workspaceOwner && !entryOwnedByWorkspace(entry, workspaceOwner)) {
|
|
2308
3225
|
operations.push(keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, void 0, currentHash));
|
|
2309
3226
|
continue;
|
|
@@ -2321,6 +3238,11 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2321
3238
|
reason: "managed stale destination changed outside agentwheel",
|
|
2322
3239
|
channel: entry.channel,
|
|
2323
3240
|
packageName: entry.packageName,
|
|
3241
|
+
semanticCommand: entry.semanticCommand,
|
|
3242
|
+
semanticPlugin,
|
|
3243
|
+
execute: entry.executed,
|
|
3244
|
+
mergeStrategy: entry.mergeStrategy,
|
|
3245
|
+
mode: entry.mode,
|
|
2324
3246
|
composedFrom: entry.composedFrom,
|
|
2325
3247
|
...operationMetadataFromEntry(entry),
|
|
2326
3248
|
...options.forceDrift ? { action: "remove", reason: "force removing drifted stale managed destination" } : {}
|
|
@@ -2338,6 +3260,11 @@ async function createPlanFromOperations(desiredOps, adapter, targetRoot, manifes
|
|
|
2338
3260
|
reason: "artifact removed from source",
|
|
2339
3261
|
channel: entry.channel,
|
|
2340
3262
|
packageName: entry.packageName,
|
|
3263
|
+
semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
|
|
3264
|
+
semanticPlugin,
|
|
3265
|
+
execute: entry.executed,
|
|
3266
|
+
mergeStrategy: entry.mergeStrategy,
|
|
3267
|
+
mode: entry.mode,
|
|
2341
3268
|
composedFrom: entry.composedFrom,
|
|
2342
3269
|
...operationMetadataFromEntry(entry)
|
|
2343
3270
|
});
|
|
@@ -2407,11 +3334,131 @@ async function migrateManifestForPlan(manifest, desired, targetRoot, transport)
|
|
|
2407
3334
|
}
|
|
2408
3335
|
};
|
|
2409
3336
|
}
|
|
3337
|
+
async function prepareManagedBlockOperations(desiredOps, adapter, targetRoot, transport, options) {
|
|
3338
|
+
const prepared = [];
|
|
3339
|
+
const physicalInstructionPaths = /* @__PURE__ */ new Map();
|
|
3340
|
+
for (const op of desiredOps) {
|
|
3341
|
+
if (op.mode !== managedInstructionBlockMode) {
|
|
3342
|
+
prepared.push(op);
|
|
3343
|
+
continue;
|
|
3344
|
+
}
|
|
3345
|
+
if (!op.sourcePath) {
|
|
3346
|
+
throw new Error(`Managed block instruction operation missing source: ${op.relativeDestPath}`);
|
|
3347
|
+
}
|
|
3348
|
+
const managedOp = {
|
|
3349
|
+
...op,
|
|
3350
|
+
desiredHash: await desiredManagedInstructionBlockHash(op.sourcePath)
|
|
3351
|
+
};
|
|
3352
|
+
if (await shouldSkipClaudeBridge(adapter, managedOp, targetRoot, transport)) {
|
|
3353
|
+
continue;
|
|
3354
|
+
}
|
|
3355
|
+
await warnOnCopilotDoubleRead(adapter, managedOp, targetRoot, transport, options);
|
|
3356
|
+
const physicalKey = await managedInstructionPhysicalKey(managedOp.destPath, transport);
|
|
3357
|
+
const incumbent = physicalInstructionPaths.get(physicalKey);
|
|
3358
|
+
if (incumbent) {
|
|
3359
|
+
if (incumbent.desiredHash !== managedOp.desiredHash) {
|
|
3360
|
+
options.warn?.(`Multiple instruction targets resolve to ${managedOp.relativeDestPath}; using ${incumbent.relativeDestPath} and skipping duplicate with different content.`);
|
|
3361
|
+
}
|
|
3362
|
+
continue;
|
|
3363
|
+
}
|
|
3364
|
+
physicalInstructionPaths.set(physicalKey, managedOp);
|
|
3365
|
+
prepared.push(managedOp);
|
|
3366
|
+
}
|
|
3367
|
+
return prepared;
|
|
3368
|
+
}
|
|
3369
|
+
async function shouldSkipClaudeBridge(adapter, op, targetRoot, transport) {
|
|
3370
|
+
if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename7(op.destPath).toLowerCase() !== "claude.md") {
|
|
3371
|
+
return false;
|
|
3372
|
+
}
|
|
3373
|
+
const agentsPath = join15(targetRoot, "AGENTS.md");
|
|
3374
|
+
return claudeInstructionBridgesAgents(op.destPath, agentsPath, transport);
|
|
3375
|
+
}
|
|
3376
|
+
async function warnOnCopilotDoubleRead(adapter, op, targetRoot, transport, options) {
|
|
3377
|
+
if (adapter.name !== "claude" || op.artifactType !== "instructions" || basename7(op.destPath).toLowerCase() !== "claude.md") return;
|
|
3378
|
+
const agentsPath = join15(targetRoot, "AGENTS.md");
|
|
3379
|
+
if (!await transport.pathExists(agentsPath)) return;
|
|
3380
|
+
if (await claudeInstructionBridgesAgents(op.destPath, agentsPath, transport)) return;
|
|
3381
|
+
options.warn?.("CLAUDE.md and AGENTS.md are separate instruction files; if Copilot is active it may read the managed instructions twice.");
|
|
3382
|
+
}
|
|
3383
|
+
async function planManagedBlockOperation(op, manifestByPath, transport, options, workspaceOwner, targetRoot) {
|
|
3384
|
+
const existing = manifestByPath.get(op.relativeDestPath);
|
|
3385
|
+
if (existing && workspaceOwner && !entryOwnedByWorkspace(existing, workspaceOwner)) {
|
|
3386
|
+
return [keepForeignManifestEntryOperation(existing, targetRoot, workspaceOwner, op)];
|
|
3387
|
+
}
|
|
3388
|
+
const selector = managedInstructionSelector(op.logicalSelector, op.artifactType, op.artifactName);
|
|
3389
|
+
const state = await readManagedInstructionBlockState(op.destPath, selector, transport);
|
|
3390
|
+
if (!state.exists) {
|
|
3391
|
+
return [{ ...op, action: "create", reason: "managed instruction destination missing" }];
|
|
3392
|
+
}
|
|
3393
|
+
if (!existing) {
|
|
3394
|
+
if (state.hasBlock && !state.drifted && state.hash === op.desiredHash && options.forceConflict) {
|
|
3395
|
+
return [{
|
|
3396
|
+
...op,
|
|
3397
|
+
action: "skip",
|
|
3398
|
+
currentHash: state.hash,
|
|
3399
|
+
reason: "force adopting unmanaged managed-block destination with matching hash"
|
|
3400
|
+
}];
|
|
3401
|
+
}
|
|
3402
|
+
if (options.replaceConflict) {
|
|
3403
|
+
return [{
|
|
3404
|
+
...op,
|
|
3405
|
+
action: "update",
|
|
3406
|
+
currentHash: state.hash,
|
|
3407
|
+
reason: "force adopting managed-block destination"
|
|
3408
|
+
}];
|
|
3409
|
+
}
|
|
3410
|
+
return [{
|
|
3411
|
+
...op,
|
|
3412
|
+
action: "conflict",
|
|
3413
|
+
currentHash: state.hash,
|
|
3414
|
+
reason: "destination exists but is not managed"
|
|
3415
|
+
}];
|
|
3416
|
+
}
|
|
3417
|
+
if (!state.hasBlock || state.drifted || state.hash !== existing.hash) {
|
|
3418
|
+
const composedFromDiff = changedComposedSelectors(op.composedFrom, existing.composedFrom);
|
|
3419
|
+
if (options.forceDrift) {
|
|
3420
|
+
return [{
|
|
3421
|
+
...op,
|
|
3422
|
+
action: "update",
|
|
3423
|
+
currentHash: state.hash,
|
|
3424
|
+
manifestHash: existing.hash,
|
|
3425
|
+
reason: reasonWithComposedDiff("force replacing drifted managed instruction block", op.composedFrom, existing.composedFrom),
|
|
3426
|
+
composedFromDiff
|
|
3427
|
+
}];
|
|
3428
|
+
}
|
|
3429
|
+
return [{
|
|
3430
|
+
...op,
|
|
3431
|
+
action: "drift",
|
|
3432
|
+
currentHash: state.hash,
|
|
3433
|
+
manifestHash: existing.hash,
|
|
3434
|
+
reason: state.hasBlock ? "managed instruction block changed outside agentwheel" : "managed instruction block missing from destination",
|
|
3435
|
+
blockedDesiredHash: op.desiredHash,
|
|
3436
|
+
blockedReason: blockedDriftReason(op.composedFrom, existing.composedFrom),
|
|
3437
|
+
composedFromDiff
|
|
3438
|
+
}];
|
|
3439
|
+
}
|
|
3440
|
+
if (existing.sourceHash === op.desiredHash) {
|
|
3441
|
+
return [{ ...op, action: "skip", currentHash: state.hash, manifestHash: existing.hash, reason: "managed instruction block already up to date" }];
|
|
3442
|
+
}
|
|
3443
|
+
return [{
|
|
3444
|
+
...op,
|
|
3445
|
+
action: "update",
|
|
3446
|
+
currentHash: state.hash,
|
|
3447
|
+
manifestHash: existing.hash,
|
|
3448
|
+
reason: reasonWithComposedDiff("managed instruction source changed", op.composedFrom, existing.composedFrom)
|
|
3449
|
+
}];
|
|
3450
|
+
}
|
|
3451
|
+
async function currentEntryHash(entry, destPath, transport) {
|
|
3452
|
+
if (entry.mode !== managedInstructionBlockMode) return transport.hashPath(destPath);
|
|
3453
|
+
const selector = managedInstructionSelector("logicalSelector" in entry ? entry.logicalSelector : void 0, entry.artifactType, entry.artifactName);
|
|
3454
|
+
const state = await readManagedInstructionBlockState(destPath, selector, transport);
|
|
3455
|
+
return state.drifted ? state.markerHash : state.hash;
|
|
3456
|
+
}
|
|
2410
3457
|
async function canStrictlyAdoptLegacyEntry(entry, op, targetRoot, transport) {
|
|
2411
3458
|
if (entry.artifactType !== op.artifactType || entry.artifactName !== op.artifactName) return false;
|
|
2412
3459
|
if (!op.desiredHash || entry.sourceHash !== op.desiredHash) return false;
|
|
2413
3460
|
if (!packageIdentityMatches(entry, op)) return false;
|
|
2414
|
-
const destPath =
|
|
3461
|
+
const destPath = join15(targetRoot, entry.path);
|
|
2415
3462
|
if (!await transport.pathExists(destPath)) return false;
|
|
2416
3463
|
return await transport.hashPath(destPath) === entry.hash;
|
|
2417
3464
|
}
|
|
@@ -2501,7 +3548,8 @@ function adoptLegacyEntry(entry, op) {
|
|
|
2501
3548
|
refCount: owners.length,
|
|
2502
3549
|
workspaceOwner: op.workspaceOwner ?? legacyUnownedWorkspaceOwner,
|
|
2503
3550
|
graphLockDigest: op.graphLockDigest,
|
|
2504
|
-
composedFrom: op.composedFrom ?? entry.composedFrom
|
|
3551
|
+
composedFrom: op.composedFrom ?? entry.composedFrom,
|
|
3552
|
+
mode: op.mode
|
|
2505
3553
|
};
|
|
2506
3554
|
}
|
|
2507
3555
|
function operationMetadataFromDesired(artifact, meta) {
|
|
@@ -2541,7 +3589,7 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
|
|
|
2541
3589
|
artifactType: entry.artifactType,
|
|
2542
3590
|
artifactName: entry.artifactName,
|
|
2543
3591
|
kind: entry.kind,
|
|
2544
|
-
destPath: operation?.destPath ??
|
|
3592
|
+
destPath: operation?.destPath ?? join15(targetRoot, entry.path),
|
|
2545
3593
|
relativeDestPath: entry.path,
|
|
2546
3594
|
desiredHash: entry.sourceHash,
|
|
2547
3595
|
currentHash: currentHash ?? operation?.currentHash ?? entry.hash,
|
|
@@ -2550,8 +3598,10 @@ function keepForeignManifestEntryOperation(entry, targetRoot, workspaceOwner, op
|
|
|
2550
3598
|
channel: entry.channel,
|
|
2551
3599
|
packageName: entry.packageName,
|
|
2552
3600
|
semanticCommand: entry.semanticCommand,
|
|
3601
|
+
semanticPlugin: entry.semanticPlugin,
|
|
2553
3602
|
execute: entry.executed,
|
|
2554
3603
|
mergeStrategy: entry.mergeStrategy,
|
|
3604
|
+
mode: entry.mode,
|
|
2555
3605
|
composedFrom: entry.composedFrom,
|
|
2556
3606
|
preserveInManifest: true,
|
|
2557
3607
|
...operationMetadataFromEntry(entry)
|
|
@@ -2563,7 +3613,7 @@ function normalizeOperationOwners(op) {
|
|
|
2563
3613
|
function isGuardedMergeTarget(type) {
|
|
2564
3614
|
return type === "mcp" || type === "hooks" || type === "settings" || type === "plugins";
|
|
2565
3615
|
}
|
|
2566
|
-
function operationForArtifact(artifact, adapter, targetRoot, installationType, meta) {
|
|
3616
|
+
async function operationForArtifact(artifact, adapter, targetRoot, installationType, meta) {
|
|
2567
3617
|
if (artifact.type === "fragments") return void 0;
|
|
2568
3618
|
const target = targetMappingForArtifact(adapter, artifact.type, installationType);
|
|
2569
3619
|
if (!target?.enabled) {
|
|
@@ -2576,27 +3626,38 @@ function operationForArtifact(artifact, adapter, targetRoot, installationType, m
|
|
|
2576
3626
|
assertSafeInstallName(rawInstallName, `${artifact.type}/${artifact.name}`);
|
|
2577
3627
|
const installName = semanticInstallName(artifact, target.semantic, rawInstallName);
|
|
2578
3628
|
assertSafeInstallName(installName, `${artifact.type}/${artifact.name}`);
|
|
2579
|
-
if (artifact.type === "plugins"
|
|
3629
|
+
if (artifact.type === "plugins") {
|
|
2580
3630
|
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
kind: artifact.kind,
|
|
3631
|
+
const semanticPlugin = await semanticPluginSpecForArtifact({
|
|
3632
|
+
semantic: target.semantic,
|
|
3633
|
+
artifact,
|
|
3634
|
+
installName,
|
|
2586
3635
|
sourcePath,
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
3636
|
+
targetRoot,
|
|
3637
|
+
installationType
|
|
3638
|
+
});
|
|
3639
|
+
if (semanticPlugin) {
|
|
3640
|
+
return {
|
|
3641
|
+
action: "plugin",
|
|
3642
|
+
artifactType: artifact.type,
|
|
3643
|
+
artifactName: artifact.name,
|
|
3644
|
+
kind: artifact.kind,
|
|
3645
|
+
sourcePath,
|
|
3646
|
+
destPath: targetRoot,
|
|
3647
|
+
relativeDestPath: `plugins/${installName}`,
|
|
3648
|
+
desiredHash: artifact.hash,
|
|
3649
|
+
reason: "semantic plugin install planned",
|
|
3650
|
+
channel: artifact.channel ?? "managed",
|
|
3651
|
+
packageName: artifact.packageName,
|
|
3652
|
+
semanticCommand: semanticPlugin.installCommands[0],
|
|
3653
|
+
semanticPlugin,
|
|
3654
|
+
composedFrom: metadata.composedFrom,
|
|
3655
|
+
...metadata
|
|
3656
|
+
};
|
|
3657
|
+
}
|
|
2597
3658
|
}
|
|
2598
3659
|
if (artifact.type === "subagents" && target.semantic === "codex-subagent") {
|
|
2599
|
-
const destPath2 =
|
|
3660
|
+
const destPath2 = join15(targetRoot, target.dest, `${installName.replace(/\.toml$/i, "")}.toml`);
|
|
2600
3661
|
return {
|
|
2601
3662
|
action: "create",
|
|
2602
3663
|
artifactType: artifact.type,
|
|
@@ -2604,7 +3665,7 @@ function operationForArtifact(artifact, adapter, targetRoot, installationType, m
|
|
|
2604
3665
|
kind: "file",
|
|
2605
3666
|
sourcePath: artifact.stagedPath ?? artifact.sourcePath,
|
|
2606
3667
|
destPath: destPath2,
|
|
2607
|
-
relativeDestPath:
|
|
3668
|
+
relativeDestPath: relative3(targetRoot, destPath2).replaceAll("\\", "/"),
|
|
2608
3669
|
desiredHash: artifact.hash,
|
|
2609
3670
|
reason: "destination missing",
|
|
2610
3671
|
channel: artifact.channel ?? "managed",
|
|
@@ -2614,7 +3675,7 @@ function operationForArtifact(artifact, adapter, targetRoot, installationType, m
|
|
|
2614
3675
|
installName: installName.replace(/\.toml$/i, "")
|
|
2615
3676
|
};
|
|
2616
3677
|
}
|
|
2617
|
-
const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ?
|
|
3678
|
+
const destPath = artifact.type === "instructions" || artifact.type === "settings" || isFileTarget(target.dest) ? join15(targetRoot, target.dest) : join15(targetRoot, target.dest, installName);
|
|
2618
3679
|
return {
|
|
2619
3680
|
action: "create",
|
|
2620
3681
|
artifactType: artifact.type,
|
|
@@ -2622,12 +3683,13 @@ function operationForArtifact(artifact, adapter, targetRoot, installationType, m
|
|
|
2622
3683
|
kind: artifact.kind,
|
|
2623
3684
|
sourcePath: artifact.stagedPath ?? artifact.sourcePath,
|
|
2624
3685
|
destPath,
|
|
2625
|
-
relativeDestPath:
|
|
3686
|
+
relativeDestPath: relative3(targetRoot, destPath).replaceAll("\\", "/"),
|
|
2626
3687
|
desiredHash: artifact.hash,
|
|
2627
3688
|
reason: "destination missing",
|
|
2628
3689
|
channel: artifact.channel ?? "managed",
|
|
2629
3690
|
packageName: artifact.packageName,
|
|
2630
3691
|
mergeStrategy: target.merge,
|
|
3692
|
+
mode: target.mode,
|
|
2631
3693
|
composedFrom: metadata.composedFrom,
|
|
2632
3694
|
...metadata,
|
|
2633
3695
|
installName
|
|
@@ -2651,7 +3713,7 @@ function withExtension(name, targetExtension, knownExtensions) {
|
|
|
2651
3713
|
return `${base}${targetExtension}`;
|
|
2652
3714
|
}
|
|
2653
3715
|
function isFileTarget(dest) {
|
|
2654
|
-
return /\.(json|jsonc|toml|md)$/i.test(dest);
|
|
3716
|
+
return /\.(json|jsonc|ya?ml|toml|md)$/i.test(dest);
|
|
2655
3717
|
}
|
|
2656
3718
|
function reasonWithComposedDiff(reason, desired, current) {
|
|
2657
3719
|
const changed = changedComposedSelectors(desired, current);
|
|
@@ -2692,13 +3754,14 @@ function isPendingInstallOperation(operation) {
|
|
|
2692
3754
|
}
|
|
2693
3755
|
|
|
2694
3756
|
// src/install/uninstall.ts
|
|
2695
|
-
import { join as
|
|
3757
|
+
import { join as join16 } from "path";
|
|
2696
3758
|
async function createUninstallPlan(manifest, transport = localTransport) {
|
|
2697
3759
|
const operations = [];
|
|
2698
3760
|
for (const entry of manifest.entries) {
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
|
|
3761
|
+
const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
|
|
3762
|
+
const destPath = semanticPlugin ? manifest.targetRoot : join16(manifest.targetRoot, entry.path);
|
|
3763
|
+
if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
|
|
3764
|
+
const currentHash = semanticPlugin ? entry.hash : await currentEntryHash2(entry, destPath, transport);
|
|
2702
3765
|
if (currentHash !== entry.hash) {
|
|
2703
3766
|
operations.push({
|
|
2704
3767
|
action: "keep",
|
|
@@ -2714,7 +3777,10 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2714
3777
|
channel: entry.channel,
|
|
2715
3778
|
packageName: entry.packageName,
|
|
2716
3779
|
semanticCommand: entry.semanticCommand,
|
|
3780
|
+
semanticPlugin,
|
|
3781
|
+
execute: entry.executed,
|
|
2717
3782
|
mergeStrategy: entry.mergeStrategy,
|
|
3783
|
+
mode: entry.mode,
|
|
2718
3784
|
composedFrom: entry.composedFrom,
|
|
2719
3785
|
...operationMetadataFromEntry2(entry)
|
|
2720
3786
|
});
|
|
@@ -2732,8 +3798,11 @@ async function createUninstallPlan(manifest, transport = localTransport) {
|
|
|
2732
3798
|
reason: "uninstall managed artifact",
|
|
2733
3799
|
channel: entry.channel,
|
|
2734
3800
|
packageName: entry.packageName,
|
|
2735
|
-
semanticCommand: entry.semanticCommand,
|
|
3801
|
+
semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
|
|
3802
|
+
semanticPlugin,
|
|
3803
|
+
execute: entry.executed,
|
|
2736
3804
|
mergeStrategy: entry.mergeStrategy,
|
|
3805
|
+
mode: entry.mode,
|
|
2737
3806
|
composedFrom: entry.composedFrom,
|
|
2738
3807
|
...operationMetadataFromEntry2(entry)
|
|
2739
3808
|
});
|
|
@@ -2760,9 +3829,10 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2760
3829
|
);
|
|
2761
3830
|
const operations = [];
|
|
2762
3831
|
for (const entry of manifest.entries) {
|
|
2763
|
-
const
|
|
2764
|
-
|
|
2765
|
-
|
|
3832
|
+
const semanticPlugin = "semanticPlugin" in entry ? entry.semanticPlugin : void 0;
|
|
3833
|
+
const destPath = semanticPlugin ? manifest.targetRoot : join16(manifest.targetRoot, entry.path);
|
|
3834
|
+
if (!semanticPlugin && !await transport.pathExists(destPath)) continue;
|
|
3835
|
+
const currentHash = semanticPlugin ? entry.hash : await currentEntryHash2(entry, destPath, transport);
|
|
2766
3836
|
const remainingOwners = ownersByPath.get(entry.path) ?? [];
|
|
2767
3837
|
if (remainingOwners.length > 0) {
|
|
2768
3838
|
operations.push({
|
|
@@ -2779,7 +3849,10 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2779
3849
|
channel: entry.channel,
|
|
2780
3850
|
packageName: entry.packageName,
|
|
2781
3851
|
semanticCommand: entry.semanticCommand,
|
|
3852
|
+
semanticPlugin,
|
|
3853
|
+
execute: entry.executed,
|
|
2782
3854
|
mergeStrategy: entry.mergeStrategy,
|
|
3855
|
+
mode: entry.mode,
|
|
2783
3856
|
composedFrom: entry.composedFrom,
|
|
2784
3857
|
...operationMetadataFromEntry2(entry, remainingOwners),
|
|
2785
3858
|
graphLockDigest: options.graphLockDigest,
|
|
@@ -2802,7 +3875,10 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2802
3875
|
channel: entry.channel,
|
|
2803
3876
|
packageName: entry.packageName,
|
|
2804
3877
|
semanticCommand: entry.semanticCommand,
|
|
3878
|
+
semanticPlugin,
|
|
3879
|
+
execute: entry.executed,
|
|
2805
3880
|
mergeStrategy: entry.mergeStrategy,
|
|
3881
|
+
mode: entry.mode,
|
|
2806
3882
|
composedFrom: entry.composedFrom,
|
|
2807
3883
|
...operationMetadataFromEntry2(entry),
|
|
2808
3884
|
graphLockDigest: options.graphLockDigest
|
|
@@ -2821,8 +3897,11 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2821
3897
|
reason: "owner set became empty",
|
|
2822
3898
|
channel: entry.channel,
|
|
2823
3899
|
packageName: entry.packageName,
|
|
2824
|
-
semanticCommand: entry.semanticCommand,
|
|
3900
|
+
semanticCommand: semanticPlugin?.uninstallCommands[0] ?? entry.semanticCommand,
|
|
3901
|
+
semanticPlugin,
|
|
3902
|
+
execute: entry.executed,
|
|
2825
3903
|
mergeStrategy: entry.mergeStrategy,
|
|
3904
|
+
mode: entry.mode,
|
|
2826
3905
|
composedFrom: entry.composedFrom,
|
|
2827
3906
|
...operationMetadataFromEntry2(entry, [])
|
|
2828
3907
|
});
|
|
@@ -2841,6 +3920,12 @@ async function createOwnershipUninstallPlan(manifest, remainingDesired, adapter,
|
|
|
2841
3920
|
graphLockDigest: options.graphLockDigest
|
|
2842
3921
|
};
|
|
2843
3922
|
}
|
|
3923
|
+
async function currentEntryHash2(entry, destPath, transport) {
|
|
3924
|
+
if (entry.mode !== managedInstructionBlockMode) return transport.hashPath(destPath);
|
|
3925
|
+
const selector = managedInstructionSelector("logicalSelector" in entry ? entry.logicalSelector : void 0, entry.artifactType, entry.artifactName);
|
|
3926
|
+
const state = await readManagedInstructionBlockState(destPath, selector, transport);
|
|
3927
|
+
return state.drifted ? state.markerHash : state.hash;
|
|
3928
|
+
}
|
|
2844
3929
|
function operationMetadataFromEntry2(entry, ownersOverride) {
|
|
2845
3930
|
if ("owners" in entry) {
|
|
2846
3931
|
return {
|
|
@@ -2887,7 +3972,7 @@ function formatPlan(plan) {
|
|
|
2887
3972
|
}
|
|
2888
3973
|
for (const operation of sortedPlanOperations(plan.operations)) {
|
|
2889
3974
|
const source = operation.sourcePath ? `${operation.sourcePath} -> ` : "";
|
|
2890
|
-
const command = operation
|
|
3975
|
+
const command = formatSemanticCommandSuffix(operation);
|
|
2891
3976
|
const blocked = operation.blockedReason ? `; ${operation.blockedReason}` : "";
|
|
2892
3977
|
lines.push(`${labels[operation.action].padEnd(8)} ${channelLabels[operation.channel].padEnd(8)} ${operation.artifactType}/${operation.artifactName} ${source}${operation.relativeDestPath} (${operation.reason}${blocked})${command}`);
|
|
2893
3978
|
}
|
|
@@ -2897,6 +3982,18 @@ function formatPlan(plan) {
|
|
|
2897
3982
|
);
|
|
2898
3983
|
return lines.join("\n");
|
|
2899
3984
|
}
|
|
3985
|
+
function formatSemanticCommandSuffix(operation) {
|
|
3986
|
+
const commands = semanticCommandsForOperation(operation);
|
|
3987
|
+
return commands.length > 0 ? ` :: ${commands.map((command) => command.join(" ")).join(" && ")}` : "";
|
|
3988
|
+
}
|
|
3989
|
+
function semanticCommandsForOperation(operation) {
|
|
3990
|
+
if (operation.semanticPlugin) {
|
|
3991
|
+
if (operation.action === "remove" && operation.execute === false) return [];
|
|
3992
|
+
if (operation.action === "remove") return operation.semanticPlugin.uninstallCommands;
|
|
3993
|
+
if (operation.action === "plugin") return operation.semanticPlugin.installCommands;
|
|
3994
|
+
}
|
|
3995
|
+
return operation.semanticCommand ? [operation.semanticCommand] : [];
|
|
3996
|
+
}
|
|
2900
3997
|
function sortedPlanOperations(operations) {
|
|
2901
3998
|
return [...operations].sort((a, b) => {
|
|
2902
3999
|
const destructiveA = a.action === "remove" || a.action === "drift" || a.action === "conflict";
|
|
@@ -3053,15 +4150,15 @@ function ownerChains(lock, nodeId) {
|
|
|
3053
4150
|
|
|
3054
4151
|
// src/source/git.ts
|
|
3055
4152
|
import { execFile as execFile4 } from "child_process";
|
|
3056
|
-
import { cp as cp2, mkdir as
|
|
4153
|
+
import { cp as cp2, mkdir as mkdir9, rename as rename3, rm as rm5, writeFile as writeFile11 } from "fs/promises";
|
|
3057
4154
|
import { homedir as homedir2 } from "os";
|
|
3058
|
-
import { basename as
|
|
4155
|
+
import { basename as basename9, dirname as dirname11, join as join19, resolve as resolve6 } from "path";
|
|
3059
4156
|
import { promisify as promisify4 } from "util";
|
|
3060
4157
|
|
|
3061
4158
|
// src/model/package.ts
|
|
3062
|
-
import { readFile as
|
|
3063
|
-
import { join as
|
|
3064
|
-
import { parse as
|
|
4159
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
4160
|
+
import { join as join17 } from "path";
|
|
4161
|
+
import { parse as parse3, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
|
|
3065
4162
|
import { z as z5 } from "zod";
|
|
3066
4163
|
var legacyArtifactTypeSchema = z5.enum([
|
|
3067
4164
|
"instructions",
|
|
@@ -3137,7 +4234,7 @@ var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNa
|
|
|
3137
4234
|
var warnedLegacyManifestPaths = /* @__PURE__ */ new Set();
|
|
3138
4235
|
async function findPackageManifestPath(root, options = {}) {
|
|
3139
4236
|
for (const name of packageManifestNames) {
|
|
3140
|
-
const candidate =
|
|
4237
|
+
const candidate = join17(root, name);
|
|
3141
4238
|
if (!await pathExists(candidate)) continue;
|
|
3142
4239
|
if (isLegacyPackageManifestName(name) && options.warnLegacy !== false && !warnedLegacyManifestPaths.has(candidate)) {
|
|
3143
4240
|
warnedLegacyManifestPaths.add(candidate);
|
|
@@ -3150,9 +4247,9 @@ async function findPackageManifestPath(root, options = {}) {
|
|
|
3150
4247
|
async function readPackageManifest(root) {
|
|
3151
4248
|
const path = await findPackageManifestPath(root);
|
|
3152
4249
|
if (!path) return void 0;
|
|
3153
|
-
const content = await
|
|
4250
|
+
const content = await readFile14(path, "utf8");
|
|
3154
4251
|
const errors = [];
|
|
3155
|
-
const parsed =
|
|
4252
|
+
const parsed = parse3(content, errors, { allowTrailingComma: true, disallowComments: false });
|
|
3156
4253
|
if (errors.length > 0) {
|
|
3157
4254
|
const details = errors.map((error) => `${printParseErrorCode2(error.error)} at offset ${error.offset}`).join(", ");
|
|
3158
4255
|
throw new Error(`Invalid package manifest ${path}: ${details}`);
|
|
@@ -3160,7 +4257,7 @@ async function readPackageManifest(root) {
|
|
|
3160
4257
|
return parsePackageManifest(parsed, path);
|
|
3161
4258
|
}
|
|
3162
4259
|
function parsePackageManifest(parsed, path = "package manifest") {
|
|
3163
|
-
if (!
|
|
4260
|
+
if (!isRecord6(parsed)) {
|
|
3164
4261
|
throw new Error(`Invalid package manifest ${path}: expected an object`);
|
|
3165
4262
|
}
|
|
3166
4263
|
if (parsed.schemaVersion === 1) {
|
|
@@ -3188,7 +4285,7 @@ function v1OpenPackViolations(manifest) {
|
|
|
3188
4285
|
}
|
|
3189
4286
|
const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
|
|
3190
4287
|
for (const [index, provide] of provides.entries()) {
|
|
3191
|
-
if (!
|
|
4288
|
+
if (!isRecord6(provide)) continue;
|
|
3192
4289
|
if (provide.type === "fragments") violations.push(`provides[${index}].type=fragments`);
|
|
3193
4290
|
for (const key of ["format", "items", "compose", "runtimes"]) {
|
|
3194
4291
|
if (Object.prototype.hasOwnProperty.call(provide, key)) violations.push(`provides[${index}].${key}`);
|
|
@@ -3199,14 +4296,14 @@ function v1OpenPackViolations(manifest) {
|
|
|
3199
4296
|
function isLegacyPackageManifestName(name) {
|
|
3200
4297
|
return legacyPackageManifestNames.includes(name);
|
|
3201
4298
|
}
|
|
3202
|
-
function
|
|
4299
|
+
function isRecord6(value) {
|
|
3203
4300
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3204
4301
|
}
|
|
3205
4302
|
|
|
3206
4303
|
// src/source/local.ts
|
|
3207
|
-
import { createHash as
|
|
4304
|
+
import { createHash as createHash4 } from "crypto";
|
|
3208
4305
|
import { readdir, stat as stat3 } from "fs/promises";
|
|
3209
|
-
import { basename as
|
|
4306
|
+
import { basename as basename8, join as join18, relative as relative4, resolve as resolve5 } from "path";
|
|
3210
4307
|
var LocalSourceDriver = class {
|
|
3211
4308
|
name = "local";
|
|
3212
4309
|
async resolve(source) {
|
|
@@ -3236,29 +4333,29 @@ var LocalSourceDriver = class {
|
|
|
3236
4333
|
}
|
|
3237
4334
|
const artifacts = [];
|
|
3238
4335
|
const root = resolved.resolvedPath;
|
|
3239
|
-
const instructions = await firstExisting([
|
|
4336
|
+
const instructions = await firstExisting([join18(root, "instructions.md"), join18(root, "AGENTS.md")]);
|
|
3240
4337
|
if (instructions) {
|
|
3241
4338
|
artifacts.push({
|
|
3242
4339
|
type: "instructions",
|
|
3243
|
-
name:
|
|
4340
|
+
name: basename8(instructions),
|
|
3244
4341
|
sourcePath: instructions,
|
|
3245
|
-
relativePath:
|
|
4342
|
+
relativePath: basename8(instructions),
|
|
3246
4343
|
kind: "file",
|
|
3247
4344
|
hash: await hashPath(instructions),
|
|
3248
4345
|
packageName: resolved.packageName,
|
|
3249
4346
|
channel: "managed"
|
|
3250
4347
|
});
|
|
3251
4348
|
}
|
|
3252
|
-
const rulesDir =
|
|
4349
|
+
const rulesDir = join18(root, "rules");
|
|
3253
4350
|
if (await pathExists(rulesDir)) {
|
|
3254
4351
|
for (const entry of await sortedDirEntries(rulesDir)) {
|
|
3255
|
-
const full =
|
|
4352
|
+
const full = join18(rulesDir, entry.name);
|
|
3256
4353
|
if (entry.isFile()) {
|
|
3257
4354
|
artifacts.push({
|
|
3258
4355
|
type: "rules",
|
|
3259
4356
|
name: entry.name,
|
|
3260
4357
|
sourcePath: full,
|
|
3261
|
-
relativePath:
|
|
4358
|
+
relativePath: join18("rules", entry.name),
|
|
3262
4359
|
kind: "file",
|
|
3263
4360
|
hash: await hashPath(full),
|
|
3264
4361
|
packageName: resolved.packageName,
|
|
@@ -3267,16 +4364,16 @@ var LocalSourceDriver = class {
|
|
|
3267
4364
|
}
|
|
3268
4365
|
}
|
|
3269
4366
|
}
|
|
3270
|
-
const fragmentsDir =
|
|
4367
|
+
const fragmentsDir = join18(root, "fragments");
|
|
3271
4368
|
if (await pathExists(fragmentsDir)) {
|
|
3272
4369
|
for (const entry of await sortedDirEntries(fragmentsDir)) {
|
|
3273
|
-
const full =
|
|
4370
|
+
const full = join18(fragmentsDir, entry.name);
|
|
3274
4371
|
if (entry.isFile()) {
|
|
3275
4372
|
artifacts.push({
|
|
3276
4373
|
type: "fragments",
|
|
3277
4374
|
name: entry.name,
|
|
3278
4375
|
sourcePath: full,
|
|
3279
|
-
relativePath:
|
|
4376
|
+
relativePath: join18("fragments", entry.name),
|
|
3280
4377
|
kind: "file",
|
|
3281
4378
|
hash: await hashPath(full),
|
|
3282
4379
|
packageName: resolved.packageName,
|
|
@@ -3285,16 +4382,16 @@ var LocalSourceDriver = class {
|
|
|
3285
4382
|
}
|
|
3286
4383
|
}
|
|
3287
4384
|
}
|
|
3288
|
-
const skillsDir =
|
|
4385
|
+
const skillsDir = join18(root, "skills");
|
|
3289
4386
|
if (await pathExists(skillsDir)) {
|
|
3290
4387
|
for (const entry of await sortedDirEntries(skillsDir)) {
|
|
3291
|
-
const full =
|
|
4388
|
+
const full = join18(skillsDir, entry.name);
|
|
3292
4389
|
if (entry.isDirectory()) {
|
|
3293
4390
|
artifacts.push({
|
|
3294
4391
|
type: "skills",
|
|
3295
4392
|
name: entry.name,
|
|
3296
4393
|
sourcePath: full,
|
|
3297
|
-
relativePath:
|
|
4394
|
+
relativePath: join18("skills", entry.name),
|
|
3298
4395
|
kind: "dir",
|
|
3299
4396
|
hash: await hashPath(full),
|
|
3300
4397
|
packageName: resolved.packageName,
|
|
@@ -3305,7 +4402,7 @@ var LocalSourceDriver = class {
|
|
|
3305
4402
|
type: "skills",
|
|
3306
4403
|
name: entry.name.replace(/\.md$/, ""),
|
|
3307
4404
|
sourcePath: full,
|
|
3308
|
-
relativePath:
|
|
4405
|
+
relativePath: join18("skills", entry.name),
|
|
3309
4406
|
kind: "file",
|
|
3310
4407
|
hash: await hashPath(full),
|
|
3311
4408
|
packageName: resolved.packageName,
|
|
@@ -3315,7 +4412,7 @@ var LocalSourceDriver = class {
|
|
|
3315
4412
|
}
|
|
3316
4413
|
}
|
|
3317
4414
|
for (const type of ["commands", "subagents", "mcp", "hooks", "settings", "plugins"]) {
|
|
3318
|
-
const dir =
|
|
4415
|
+
const dir = join18(root, type);
|
|
3319
4416
|
if (!await pathExists(dir)) continue;
|
|
3320
4417
|
artifacts.push(...await listGenericArtifacts(type, dir, type, resolved.packageName));
|
|
3321
4418
|
}
|
|
@@ -3331,7 +4428,7 @@ var LocalSourceDriver = class {
|
|
|
3331
4428
|
findings.push({ level: "warning", message: "No instructions.md or AGENTS.md found", path: resolved.resolvedPath });
|
|
3332
4429
|
}
|
|
3333
4430
|
for (const artifact of artifacts.filter((item) => item.type === "skills" && item.kind === "dir")) {
|
|
3334
|
-
if (!await pathExists(
|
|
4431
|
+
if (!await pathExists(join18(artifact.sourcePath, "SKILL.md"))) {
|
|
3335
4432
|
findings.push({ level: "warning", message: `Skill directory has no SKILL.md: ${artifact.name}`, path: artifact.sourcePath });
|
|
3336
4433
|
}
|
|
3337
4434
|
}
|
|
@@ -3346,16 +4443,16 @@ var LocalSourceDriver = class {
|
|
|
3346
4443
|
};
|
|
3347
4444
|
async function hashLocalSource(root, manifest) {
|
|
3348
4445
|
if (!manifest) return hashPath(root);
|
|
3349
|
-
const hash =
|
|
4446
|
+
const hash = createHash4("sha256").update("local-openpack\0");
|
|
3350
4447
|
const manifestPath = await findPackageManifestPath(root, { warnLegacy: false });
|
|
3351
4448
|
if (manifestPath) {
|
|
3352
4449
|
hash.update("manifest\0");
|
|
3353
|
-
hash.update(
|
|
4450
|
+
hash.update(relative4(root, manifestPath).replaceAll("\\", "/")).update("\0");
|
|
3354
4451
|
hash.update(await hashPath(manifestPath)).update("\0");
|
|
3355
4452
|
}
|
|
3356
4453
|
const provides = [...manifest.provides].sort((a, b) => `${a.type}\0${a.path}`.localeCompare(`${b.type}\0${b.path}`));
|
|
3357
4454
|
for (const provide of provides) {
|
|
3358
|
-
const full =
|
|
4455
|
+
const full = join18(root, provide.path);
|
|
3359
4456
|
if (!await pathExists(full)) continue;
|
|
3360
4457
|
hash.update("provide\0");
|
|
3361
4458
|
hash.update(provide.type).update("\0");
|
|
@@ -3378,27 +4475,27 @@ async function listFromManifest(root, packageName) {
|
|
|
3378
4475
|
if (!manifest) return [];
|
|
3379
4476
|
const artifacts = [];
|
|
3380
4477
|
for (const provide of manifest.provides) {
|
|
3381
|
-
const full =
|
|
4478
|
+
const full = join18(root, provide.path);
|
|
3382
4479
|
if (!await pathExists(full)) continue;
|
|
3383
4480
|
const stats = await stat3(full);
|
|
3384
4481
|
if (provide.type === "instructions") {
|
|
3385
4482
|
if (stats.isFile()) {
|
|
3386
|
-
artifacts.push(await artifactForFile(provide.type,
|
|
4483
|
+
artifacts.push(await artifactForFile(provide.type, basename8(full), full, provide.path, packageName, provide, manifest, basename8(full)));
|
|
3387
4484
|
}
|
|
3388
4485
|
continue;
|
|
3389
4486
|
}
|
|
3390
4487
|
if (stats.isDirectory()) {
|
|
3391
4488
|
for (const entry of await sortedDirEntries(full)) {
|
|
3392
|
-
const child =
|
|
4489
|
+
const child = join18(full, entry.name);
|
|
3393
4490
|
if ((provide.type === "skills" || provide.type === "plugins" || provide.type === "subagents") && entry.isDirectory()) {
|
|
3394
|
-
artifacts.push(await artifactForDir(provide.type, entry.name, child,
|
|
4491
|
+
artifacts.push(await artifactForDir(provide.type, entry.name, child, join18(provide.path, entry.name), packageName, provide, manifest, entry.name));
|
|
3395
4492
|
} else if (entry.isFile()) {
|
|
3396
4493
|
const name = provide.type === "rules" && entry.name.endsWith(".md") ? entry.name : entry.name;
|
|
3397
|
-
artifacts.push(await artifactForFile(provide.type, name, child,
|
|
4494
|
+
artifacts.push(await artifactForFile(provide.type, name, child, join18(provide.path, entry.name), packageName, provide, manifest, name));
|
|
3398
4495
|
}
|
|
3399
4496
|
}
|
|
3400
4497
|
} else if (stats.isFile()) {
|
|
3401
|
-
artifacts.push(await artifactForFile(provide.type,
|
|
4498
|
+
artifacts.push(await artifactForFile(provide.type, basename8(full), full, provide.path, packageName, provide, manifest, basename8(full)));
|
|
3402
4499
|
}
|
|
3403
4500
|
}
|
|
3404
4501
|
return artifacts;
|
|
@@ -3406,11 +4503,11 @@ async function listFromManifest(root, packageName) {
|
|
|
3406
4503
|
async function listGenericArtifacts(type, dir, relativeRoot, packageName) {
|
|
3407
4504
|
const artifacts = [];
|
|
3408
4505
|
for (const entry of await sortedDirEntries(dir)) {
|
|
3409
|
-
const full =
|
|
4506
|
+
const full = join18(dir, entry.name);
|
|
3410
4507
|
if (entry.isDirectory()) {
|
|
3411
|
-
artifacts.push(await artifactForDir(type, entry.name, full,
|
|
4508
|
+
artifacts.push(await artifactForDir(type, entry.name, full, join18(relativeRoot, entry.name), packageName));
|
|
3412
4509
|
} else if (entry.isFile()) {
|
|
3413
|
-
artifacts.push(await artifactForFile(type, entry.name, full,
|
|
4510
|
+
artifacts.push(await artifactForFile(type, entry.name, full, join18(relativeRoot, entry.name), packageName));
|
|
3414
4511
|
}
|
|
3415
4512
|
}
|
|
3416
4513
|
return artifacts;
|
|
@@ -3488,12 +4585,12 @@ var GitSourceDriver = class {
|
|
|
3488
4585
|
async fetch(resolved) {
|
|
3489
4586
|
return withFilesystemLock(`${resolved.resolvedPath}.lock`, resolved.cacheLockTimeoutMs ?? 3e4, async () => {
|
|
3490
4587
|
const parsed = parseGitSource(resolved.source);
|
|
3491
|
-
await
|
|
3492
|
-
if (!await pathExists(
|
|
4588
|
+
await mkdir9(resolve6(resolved.resolvedPath, ".."), { recursive: true });
|
|
4589
|
+
if (!await pathExists(join19(resolved.resolvedPath, ".git"))) {
|
|
3493
4590
|
if (resolved.frozenLock) {
|
|
3494
4591
|
throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
|
|
3495
4592
|
}
|
|
3496
|
-
await
|
|
4593
|
+
await rm5(resolved.resolvedPath, { recursive: true, force: true });
|
|
3497
4594
|
await git(["clone", "--no-tags", parsed.url, resolved.resolvedPath]);
|
|
3498
4595
|
} else if (!resolved.frozenLock) {
|
|
3499
4596
|
await git(["-C", resolved.resolvedPath, "fetch", "--prune", "origin"]);
|
|
@@ -3556,36 +4653,36 @@ function parseGitSource(source) {
|
|
|
3556
4653
|
throw new Error(`Invalid git source: ${source}`);
|
|
3557
4654
|
}
|
|
3558
4655
|
function cachePathFor(url, cacheRoot) {
|
|
3559
|
-
const root = cacheRoot ? resolve6(cacheRoot) :
|
|
4656
|
+
const root = cacheRoot ? resolve6(cacheRoot) : join19(homedir2(), ".agentwheel", "cache");
|
|
3560
4657
|
const slug2 = url.replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
|
|
3561
|
-
return
|
|
4658
|
+
return join19(root, slug2 || basename9(url));
|
|
3562
4659
|
}
|
|
3563
4660
|
async function git(args) {
|
|
3564
4661
|
return execFileAsync4("git", args, { maxBuffer: 1024 * 1024 * 10 });
|
|
3565
4662
|
}
|
|
3566
4663
|
async function snapshotCheckout(checkoutPath, commit) {
|
|
3567
|
-
const snapshotPath =
|
|
4664
|
+
const snapshotPath = join19(dirname11(checkoutPath), `${basename9(checkoutPath)}-${commit.slice(0, 12)}`);
|
|
3568
4665
|
if (await pathExists(snapshotPath)) return snapshotPath;
|
|
3569
|
-
const tempPath =
|
|
3570
|
-
await
|
|
4666
|
+
const tempPath = join19(dirname11(checkoutPath), `${basename9(snapshotPath)}.tmp-${process.pid}-${Date.now()}`);
|
|
4667
|
+
await rm5(tempPath, { recursive: true, force: true });
|
|
3571
4668
|
await cp2(checkoutPath, tempPath, { recursive: true, dereference: true });
|
|
3572
|
-
await
|
|
4669
|
+
await rm5(join19(tempPath, ".git"), { recursive: true, force: true });
|
|
3573
4670
|
try {
|
|
3574
4671
|
await rename3(tempPath, snapshotPath);
|
|
3575
4672
|
} catch (error) {
|
|
3576
4673
|
if (!isAlreadyExists2(error)) throw error;
|
|
3577
|
-
await
|
|
4674
|
+
await rm5(tempPath, { recursive: true, force: true });
|
|
3578
4675
|
return snapshotPath;
|
|
3579
4676
|
}
|
|
3580
4677
|
return snapshotPath;
|
|
3581
4678
|
}
|
|
3582
4679
|
async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
3583
|
-
await
|
|
4680
|
+
await mkdir9(dirname11(lockPath), { recursive: true });
|
|
3584
4681
|
const started = Date.now();
|
|
3585
4682
|
while (true) {
|
|
3586
4683
|
try {
|
|
3587
|
-
await
|
|
3588
|
-
await
|
|
4684
|
+
await mkdir9(lockPath);
|
|
4685
|
+
await writeFile11(join19(lockPath, "owner.json"), JSON.stringify({ pid: process.pid, createdAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
3589
4686
|
break;
|
|
3590
4687
|
} catch (error) {
|
|
3591
4688
|
if (!isAlreadyExists2(error)) throw error;
|
|
@@ -3598,7 +4695,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
|
3598
4695
|
try {
|
|
3599
4696
|
return await fn();
|
|
3600
4697
|
} finally {
|
|
3601
|
-
await
|
|
4698
|
+
await rm5(lockPath, { recursive: true, force: true });
|
|
3602
4699
|
}
|
|
3603
4700
|
}
|
|
3604
4701
|
function isAlreadyExists2(error) {
|
|
@@ -3606,14 +4703,14 @@ function isAlreadyExists2(error) {
|
|
|
3606
4703
|
}
|
|
3607
4704
|
|
|
3608
4705
|
// src/source/skillkit.ts
|
|
3609
|
-
import { cp as cp3, mkdir as
|
|
4706
|
+
import { cp as cp3, mkdir as mkdir10, readFile as readFile15, rm as rm6 } from "fs/promises";
|
|
3610
4707
|
import { homedir as homedir3 } from "os";
|
|
3611
|
-
import { basename as
|
|
4708
|
+
import { basename as basename11, dirname as dirname13, join as join21, resolve as resolve7 } from "path";
|
|
3612
4709
|
import * as defaultSkillKit from "@skillkit/core";
|
|
3613
4710
|
|
|
3614
4711
|
// src/source/skill-artifacts.ts
|
|
3615
4712
|
import { readdir as readdir2, stat as stat4 } from "fs/promises";
|
|
3616
|
-
import { basename as
|
|
4713
|
+
import { basename as basename10, dirname as dirname12, extname as extname2, join as join20 } from "path";
|
|
3617
4714
|
async function artifactsFromSkillPaths(paths, packageName) {
|
|
3618
4715
|
const artifacts = [];
|
|
3619
4716
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3635,28 +4732,28 @@ async function discoverSkillPaths(root) {
|
|
|
3635
4732
|
async function artifactFromSkillPath(item, packageName) {
|
|
3636
4733
|
const stats = await stat4(item.path);
|
|
3637
4734
|
if (stats.isDirectory()) {
|
|
3638
|
-
const skillMd =
|
|
4735
|
+
const skillMd = join20(item.path, "SKILL.md");
|
|
3639
4736
|
if (!await pathExists(skillMd)) return void 0;
|
|
3640
|
-
const name = sanitizeSkillName(item.name ??
|
|
4737
|
+
const name = sanitizeSkillName(item.name ?? basename10(item.path));
|
|
3641
4738
|
return {
|
|
3642
4739
|
type: "skills",
|
|
3643
4740
|
name,
|
|
3644
4741
|
sourcePath: item.path,
|
|
3645
|
-
relativePath:
|
|
4742
|
+
relativePath: join20("skills", name),
|
|
3646
4743
|
kind: "dir",
|
|
3647
4744
|
hash: await hashPath(item.path),
|
|
3648
4745
|
packageName,
|
|
3649
4746
|
channel: "managed"
|
|
3650
4747
|
};
|
|
3651
4748
|
}
|
|
3652
|
-
if (stats.isFile() &&
|
|
3653
|
-
const dir =
|
|
3654
|
-
const name = sanitizeSkillName(item.name ??
|
|
4749
|
+
if (stats.isFile() && basename10(item.path).toLowerCase() === "skill.md") {
|
|
4750
|
+
const dir = dirname12(item.path);
|
|
4751
|
+
const name = sanitizeSkillName(item.name ?? basename10(dir));
|
|
3655
4752
|
return {
|
|
3656
4753
|
type: "skills",
|
|
3657
4754
|
name,
|
|
3658
4755
|
sourcePath: dir,
|
|
3659
|
-
relativePath:
|
|
4756
|
+
relativePath: join20("skills", name),
|
|
3660
4757
|
kind: "dir",
|
|
3661
4758
|
hash: await hashPath(dir),
|
|
3662
4759
|
packageName,
|
|
@@ -3664,12 +4761,12 @@ async function artifactFromSkillPath(item, packageName) {
|
|
|
3664
4761
|
};
|
|
3665
4762
|
}
|
|
3666
4763
|
if (stats.isFile() && extname2(item.path).toLowerCase() === ".md") {
|
|
3667
|
-
const name = sanitizeSkillName(item.name ??
|
|
4764
|
+
const name = sanitizeSkillName(item.name ?? basename10(item.path, ".md"));
|
|
3668
4765
|
return {
|
|
3669
4766
|
type: "skills",
|
|
3670
4767
|
name,
|
|
3671
4768
|
sourcePath: item.path,
|
|
3672
|
-
relativePath:
|
|
4769
|
+
relativePath: join20("skills", `${name}.md`),
|
|
3673
4770
|
kind: "file",
|
|
3674
4771
|
hash: await hashPath(item.path),
|
|
3675
4772
|
packageName,
|
|
@@ -3687,7 +4784,7 @@ async function walk(dir, paths) {
|
|
|
3687
4784
|
}
|
|
3688
4785
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
3689
4786
|
if (!entry.isDirectory() || entry.name === ".git" || entry.name === "node_modules") continue;
|
|
3690
|
-
await walk(
|
|
4787
|
+
await walk(join20(dir, entry.name), paths);
|
|
3691
4788
|
}
|
|
3692
4789
|
}
|
|
3693
4790
|
function sanitizeSkillName(name) {
|
|
@@ -3709,7 +4806,7 @@ var SkillKitSourceDriver = class {
|
|
|
3709
4806
|
driver: this.name,
|
|
3710
4807
|
source,
|
|
3711
4808
|
resolvedPath,
|
|
3712
|
-
packageName: `skillkit/${
|
|
4809
|
+
packageName: `skillkit/${basename11(resolvedPath)}`,
|
|
3713
4810
|
mode: options.mode ?? "pinned",
|
|
3714
4811
|
sourceHash: await hashPath(resolvedPath)
|
|
3715
4812
|
};
|
|
@@ -3743,17 +4840,17 @@ var SkillKitSourceDriver = class {
|
|
|
3743
4840
|
if (!provider?.clone) {
|
|
3744
4841
|
throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
|
|
3745
4842
|
}
|
|
3746
|
-
await
|
|
4843
|
+
await mkdir10(dirname13(resolved.resolvedPath), { recursive: true });
|
|
3747
4844
|
const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
|
|
3748
4845
|
if (!result.success || !result.path) {
|
|
3749
4846
|
throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
|
|
3750
4847
|
}
|
|
3751
4848
|
if (resolve7(result.path) !== resolve7(resolved.resolvedPath)) {
|
|
3752
|
-
await
|
|
4849
|
+
await rm6(resolved.resolvedPath, { recursive: true, force: true });
|
|
3753
4850
|
await cp3(result.path, resolved.resolvedPath, { recursive: true, dereference: true });
|
|
3754
4851
|
}
|
|
3755
4852
|
if (result.tempRoot) {
|
|
3756
|
-
await
|
|
4853
|
+
await rm6(result.tempRoot, { recursive: true, force: true });
|
|
3757
4854
|
}
|
|
3758
4855
|
return {
|
|
3759
4856
|
...resolved,
|
|
@@ -3787,9 +4884,9 @@ var SkillKitSourceDriver = class {
|
|
|
3787
4884
|
throw new Error("SkillKit translateSkill API unavailable");
|
|
3788
4885
|
}
|
|
3789
4886
|
for (const skill of this.discover(resolved.resolvedPath)) {
|
|
3790
|
-
const skillMd =
|
|
4887
|
+
const skillMd = join21(skill.path, "SKILL.md");
|
|
3791
4888
|
if (await pathExists(skillMd)) {
|
|
3792
|
-
this.core.translateSkill(await
|
|
4889
|
+
this.core.translateSkill(await readFile15(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
|
|
3793
4890
|
}
|
|
3794
4891
|
}
|
|
3795
4892
|
return resolved;
|
|
@@ -3818,8 +4915,8 @@ function normalizeProviderSource(spec) {
|
|
|
3818
4915
|
return spec;
|
|
3819
4916
|
}
|
|
3820
4917
|
function cachePathFor2(spec, cacheRoot) {
|
|
3821
|
-
const root = cacheRoot ? resolve7(cacheRoot) :
|
|
3822
|
-
return
|
|
4918
|
+
const root = cacheRoot ? resolve7(cacheRoot) : join21(homedir3(), ".agentwheel", "cache");
|
|
4919
|
+
return join21(root, "skillkit", packageSlug(spec));
|
|
3823
4920
|
}
|
|
3824
4921
|
function packageSlug(spec) {
|
|
3825
4922
|
return spec.replace(/^[a-z]+:\/\//i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "source";
|
|
@@ -3832,7 +4929,7 @@ function mapSeverity(severity) {
|
|
|
3832
4929
|
|
|
3833
4930
|
// src/source/vercel-skills.ts
|
|
3834
4931
|
import { stat as stat5 } from "fs/promises";
|
|
3835
|
-
import { basename as
|
|
4932
|
+
import { basename as basename12, join as join22, resolve as resolve8 } from "path";
|
|
3836
4933
|
var VercelSkillsSourceDriver = class {
|
|
3837
4934
|
name = "vercel-skills";
|
|
3838
4935
|
git = new GitSourceDriver();
|
|
@@ -3847,7 +4944,7 @@ var VercelSkillsSourceDriver = class {
|
|
|
3847
4944
|
driver: this.name,
|
|
3848
4945
|
source,
|
|
3849
4946
|
resolvedPath,
|
|
3850
|
-
packageName: `vercel/${
|
|
4947
|
+
packageName: `vercel/${basename12(resolvedPath)}`,
|
|
3851
4948
|
mode: options.mode ?? "pinned",
|
|
3852
4949
|
sourceHash: await hashPath(resolvedPath)
|
|
3853
4950
|
};
|
|
@@ -3868,7 +4965,7 @@ var VercelSkillsSourceDriver = class {
|
|
|
3868
4965
|
driver: "git",
|
|
3869
4966
|
source: parsed.gitSource
|
|
3870
4967
|
});
|
|
3871
|
-
const resolvedPath = parsed.subpath ?
|
|
4968
|
+
const resolvedPath = parsed.subpath ? join22(fetched.resolvedPath, parsed.subpath) : fetched.resolvedPath;
|
|
3872
4969
|
if (!await pathExists(resolvedPath)) {
|
|
3873
4970
|
throw new Error(`Vercel skills subpath not found: ${parsed.subpath}`);
|
|
3874
4971
|
}
|
|
@@ -3957,14 +5054,14 @@ function getSourceDriver(name = "local") {
|
|
|
3957
5054
|
}
|
|
3958
5055
|
|
|
3959
5056
|
// src/staging/staging.ts
|
|
3960
|
-
import { chmod, cp as cp5, mkdir as
|
|
3961
|
-
import { basename as
|
|
3962
|
-
import { tmpdir as
|
|
5057
|
+
import { chmod, cp as cp5, mkdir as mkdir12, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
|
|
5058
|
+
import { basename as basename15, dirname as dirname16, join as join25, relative as relative6, resolve as resolve10, sep as sep2 } from "path";
|
|
5059
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
3963
5060
|
|
|
3964
5061
|
// src/compose/markdown.ts
|
|
3965
|
-
import { createHash as
|
|
3966
|
-
import { readdir as readdir3, readFile as
|
|
3967
|
-
import { basename as
|
|
5062
|
+
import { createHash as createHash5 } from "crypto";
|
|
5063
|
+
import { readdir as readdir3, readFile as readFile16, stat as stat6, writeFile as writeFile12 } from "fs/promises";
|
|
5064
|
+
import { basename as basename13, dirname as dirname14, extname as extname3, join as join23, relative as relative5, resolve as resolve9, sep } from "path";
|
|
3968
5065
|
var includePattern = /<!--\s*openpack:include(\?)?\s+([^>]+?)\s*-->/g;
|
|
3969
5066
|
var escapedIncludePattern = /<!--\s*openpack\\:include(\?)?\s+([^>]+?)\s*-->/g;
|
|
3970
5067
|
var generatedPattern = /<!--\s*(?:BEGIN|END)\s+openpack:include\b/;
|
|
@@ -3979,7 +5076,7 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
|
|
|
3979
5076
|
const composedFrom = [];
|
|
3980
5077
|
for (const file of files) {
|
|
3981
5078
|
const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
|
|
3982
|
-
if (result.changed) await
|
|
5079
|
+
if (result.changed) await writeFile12(file, result.content, "utf8");
|
|
3983
5080
|
composedFrom.push(...result.composedFrom);
|
|
3984
5081
|
}
|
|
3985
5082
|
const stagedPath = artifact.stagedPath ?? artifact.sourcePath;
|
|
@@ -4001,7 +5098,7 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
|
|
|
4001
5098
|
}
|
|
4002
5099
|
}
|
|
4003
5100
|
async function expandFile(file, packageRoot, appendEntries, artifactPaths, options) {
|
|
4004
|
-
const raw = await
|
|
5101
|
+
const raw = await readFile16(file, "utf8");
|
|
4005
5102
|
const owner = ownerSelector(packageRoot, file, options.nodeId);
|
|
4006
5103
|
const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
|
|
4007
5104
|
let content = expanded.content;
|
|
@@ -4094,7 +5191,7 @@ async function expandInclude(selector, packageRoot, artifactPaths, options) {
|
|
|
4094
5191
|
if (!stats.isFile()) {
|
|
4095
5192
|
throw new Error(`OpenPack include is not a file: ${displaySelector}`);
|
|
4096
5193
|
}
|
|
4097
|
-
const raw = sourceContent ?? await
|
|
5194
|
+
const raw = sourceContent ?? await readFile16(sourcePath, "utf8");
|
|
4098
5195
|
const { optional: _optional, markers: _markers, chain: _chain, ...childOptions } = options;
|
|
4099
5196
|
const expanded = await expandContent(raw, includePackageRoot, [...options.chain, displaySelector], includeArtifactPaths, {
|
|
4100
5197
|
...childOptions,
|
|
@@ -4170,7 +5267,7 @@ async function listMarkdownFiles(root) {
|
|
|
4170
5267
|
const out = [];
|
|
4171
5268
|
async function walk2(dir) {
|
|
4172
5269
|
for (const entry of (await readdir3(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4173
|
-
const full =
|
|
5270
|
+
const full = join23(dir, entry.name);
|
|
4174
5271
|
if (entry.isDirectory()) {
|
|
4175
5272
|
await walk2(full);
|
|
4176
5273
|
} else if (entry.isFile() && extname3(entry.name).toLowerCase() === ".md") {
|
|
@@ -4184,7 +5281,7 @@ async function listMarkdownFiles(root) {
|
|
|
4184
5281
|
function composeEntriesForFile(artifact, file) {
|
|
4185
5282
|
if (!artifact.compose?.length) return [];
|
|
4186
5283
|
if (artifact.kind === "file") return [resolve9(artifact.stagedPath ?? artifact.sourcePath), resolve9(file)].every(Boolean) && resolve9(artifact.stagedPath ?? artifact.sourcePath) === resolve9(file) ? artifact.compose : [];
|
|
4187
|
-
return
|
|
5284
|
+
return basename13(file) === "SKILL.md" && dirname14(file) === resolve9(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
|
|
4188
5285
|
}
|
|
4189
5286
|
function orderedForExpansion(artifacts) {
|
|
4190
5287
|
return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
|
|
@@ -4201,7 +5298,7 @@ function applyReplacements(content, replacements) {
|
|
|
4201
5298
|
return out + content.slice(cursor);
|
|
4202
5299
|
}
|
|
4203
5300
|
function relativeSelector(root, file) {
|
|
4204
|
-
return
|
|
5301
|
+
return relative5(root, file).replaceAll("\\", "/");
|
|
4205
5302
|
}
|
|
4206
5303
|
function ownerSelector(root, file, nodeId) {
|
|
4207
5304
|
const selector = relativeSelector(root, file);
|
|
@@ -4215,7 +5312,7 @@ function cleanSelector(value) {
|
|
|
4215
5312
|
return value.trim().replace(/\s+/g, " ");
|
|
4216
5313
|
}
|
|
4217
5314
|
function sha256(content) {
|
|
4218
|
-
return
|
|
5315
|
+
return createHash5("sha256").update(content).digest("hex");
|
|
4219
5316
|
}
|
|
4220
5317
|
function uniqueComposedFrom(entries) {
|
|
4221
5318
|
if (entries.length === 0) return [];
|
|
@@ -4230,8 +5327,8 @@ function artifactPathMap(artifacts) {
|
|
|
4230
5327
|
}
|
|
4231
5328
|
|
|
4232
5329
|
// src/staging/customize.ts
|
|
4233
|
-
import { cp as cp4, mkdir as
|
|
4234
|
-
import { dirname as
|
|
5330
|
+
import { cp as cp4, mkdir as mkdir11, readdir as readdir4, readFile as readFile17, writeFile as writeFile13 } from "fs/promises";
|
|
5331
|
+
import { dirname as dirname15, join as join24 } from "path";
|
|
4235
5332
|
async function applyCustomizations(artifacts, options) {
|
|
4236
5333
|
let next = [...artifacts];
|
|
4237
5334
|
next = await applyReplacements2(next, options, "override", installableArtifactTypes());
|
|
@@ -4247,16 +5344,16 @@ async function applyFragmentCustomizations(artifacts, options) {
|
|
|
4247
5344
|
return next.sort((a, b) => `${a.type}:${a.name}:${a.channel}`.localeCompare(`${b.type}:${b.name}:${b.channel}`));
|
|
4248
5345
|
}
|
|
4249
5346
|
async function applyInstructionOverlay(artifacts, options) {
|
|
4250
|
-
const overlayPath =
|
|
5347
|
+
const overlayPath = join24(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
|
|
4251
5348
|
if (!await pathExists(overlayPath)) return artifacts;
|
|
4252
5349
|
const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
|
|
4253
5350
|
if (index < 0) return artifacts;
|
|
4254
5351
|
const artifact = artifacts[index];
|
|
4255
|
-
const managed = await
|
|
4256
|
-
const local = await
|
|
4257
|
-
const composedPath =
|
|
4258
|
-
await
|
|
4259
|
-
await
|
|
5352
|
+
const managed = await readFile17(artifact.stagedPath ?? artifact.sourcePath, "utf8");
|
|
5353
|
+
const local = await readFile17(overlayPath, "utf8");
|
|
5354
|
+
const composedPath = join24(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
|
|
5355
|
+
await mkdir11(dirname15(composedPath), { recursive: true });
|
|
5356
|
+
await writeFile13(
|
|
4260
5357
|
composedPath,
|
|
4261
5358
|
[
|
|
4262
5359
|
"<!-- BEGIN agentwheel managed: upstream -->",
|
|
@@ -4282,19 +5379,19 @@ async function applyInstructionOverlay(artifacts, options) {
|
|
|
4282
5379
|
return [...artifacts.slice(0, index), updated, ...artifacts.slice(index + 1)];
|
|
4283
5380
|
}
|
|
4284
5381
|
async function applyAdditions(artifacts, options) {
|
|
4285
|
-
const additionsRoot =
|
|
4286
|
-
const rulesRoot =
|
|
5382
|
+
const additionsRoot = join24(options.workspaceRoot, ".agentwheel", "additions");
|
|
5383
|
+
const rulesRoot = join24(additionsRoot, "rules");
|
|
4287
5384
|
if (!await pathExists(rulesRoot)) return artifacts;
|
|
4288
5385
|
const additions = [];
|
|
4289
5386
|
for (const entry of await sortedDirEntries2(rulesRoot)) {
|
|
4290
|
-
const full =
|
|
5387
|
+
const full = join24(rulesRoot, entry.name);
|
|
4291
5388
|
if (!entry.isFile()) continue;
|
|
4292
5389
|
additions.push({
|
|
4293
5390
|
type: "rules",
|
|
4294
5391
|
name: entry.name,
|
|
4295
5392
|
sourcePath: full,
|
|
4296
5393
|
stagedPath: full,
|
|
4297
|
-
relativePath:
|
|
5394
|
+
relativePath: join24("additions", "rules", entry.name),
|
|
4298
5395
|
kind: "file",
|
|
4299
5396
|
hash: await hashPath(full),
|
|
4300
5397
|
packageName: options.packageName,
|
|
@@ -4318,17 +5415,17 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
|
|
|
4318
5415
|
);
|
|
4319
5416
|
}
|
|
4320
5417
|
for (const type of artifactTypes) {
|
|
4321
|
-
const typeRoot =
|
|
5418
|
+
const typeRoot = join24(root, type);
|
|
4322
5419
|
if (!await pathExists(typeRoot)) continue;
|
|
4323
5420
|
for (const entry of await sortedDirEntries2(typeRoot)) {
|
|
4324
5421
|
const artifactMapKey = `${type}:${entry.name}`;
|
|
4325
5422
|
if (seen.has(artifactMapKey)) continue;
|
|
4326
5423
|
seen.add(artifactMapKey);
|
|
4327
|
-
const full =
|
|
5424
|
+
const full = join24(typeRoot, entry.name);
|
|
4328
5425
|
const artifactKind = entry.isDirectory() ? "dir" : "file";
|
|
4329
5426
|
const existing = byKey.get(artifactMapKey);
|
|
4330
|
-
const stagedPath =
|
|
4331
|
-
await
|
|
5427
|
+
const stagedPath = join24(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
|
|
5428
|
+
await mkdir11(dirname15(stagedPath), { recursive: true });
|
|
4332
5429
|
await cp4(full, stagedPath, { recursive: artifactKind === "dir", dereference: true });
|
|
4333
5430
|
byKey.set(artifactMapKey, {
|
|
4334
5431
|
...existing,
|
|
@@ -4336,7 +5433,7 @@ async function applyReplacements2(artifacts, options, channel, artifactTypes) {
|
|
|
4336
5433
|
name: entry.name,
|
|
4337
5434
|
sourcePath: full,
|
|
4338
5435
|
stagedPath,
|
|
4339
|
-
relativePath: existing?.relativePath ??
|
|
5436
|
+
relativePath: existing?.relativePath ?? join24(type, entry.name),
|
|
4340
5437
|
kind: artifactKind,
|
|
4341
5438
|
hash: await hashPath(stagedPath),
|
|
4342
5439
|
packageName,
|
|
@@ -4351,13 +5448,13 @@ function replacementRoots(options, channel) {
|
|
|
4351
5448
|
const stateDir = channel === "override" ? "overrides" : "ejected";
|
|
4352
5449
|
const roots = [];
|
|
4353
5450
|
if (options.graphNodeId) {
|
|
4354
|
-
roots.push({ root:
|
|
5451
|
+
roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...options.graphNodeId.split("/")), kind: "node" });
|
|
4355
5452
|
}
|
|
4356
5453
|
if (options.packageName && options.packageVersion) {
|
|
4357
|
-
roots.push({ root:
|
|
5454
|
+
roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...`${options.packageName}@${options.packageVersion}`.split("/")), kind: "version" });
|
|
4358
5455
|
}
|
|
4359
5456
|
if (options.packageName) {
|
|
4360
|
-
roots.push({ root:
|
|
5457
|
+
roots.push({ root: join24(options.workspaceRoot, ".agentwheel", stateDir, ...options.packageName.split("/")), kind: "package" });
|
|
4361
5458
|
}
|
|
4362
5459
|
return roots;
|
|
4363
5460
|
}
|
|
@@ -4371,56 +5468,6 @@ async function sortedDirEntries2(path) {
|
|
|
4371
5468
|
return (await readdir4(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
4372
5469
|
}
|
|
4373
5470
|
|
|
4374
|
-
// src/model/selection.ts
|
|
4375
|
-
function artifactSelectorKey(artifact) {
|
|
4376
|
-
return `${artifact.type}/${artifact.name}`;
|
|
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
|
-
}
|
|
4385
|
-
function normalizeArtifactSelectors(select, legacySkills) {
|
|
4386
|
-
const selected = [
|
|
4387
|
-
...select ?? [],
|
|
4388
|
-
...(legacySkills ?? []).map((name) => `skills/${name}`)
|
|
4389
|
-
].flatMap(splitSelectorList);
|
|
4390
|
-
if (selected.length === 0) return void 0;
|
|
4391
|
-
return [...new Set(selected.map(parseArtifactSelector))];
|
|
4392
|
-
}
|
|
4393
|
-
function filterArtifactsBySelection(artifacts, selectors, legacySkills) {
|
|
4394
|
-
const selected = normalizeArtifactSelectors(selectors, legacySkills);
|
|
4395
|
-
if (!selected?.length) return artifacts;
|
|
4396
|
-
const selectedSet = new Set(selected);
|
|
4397
|
-
const available = new Set(artifacts.flatMap(artifactSelectorAliases));
|
|
4398
|
-
const missing = selected.filter((selector) => !available.has(selector));
|
|
4399
|
-
if (missing.length > 0) {
|
|
4400
|
-
throw new Error(`Selected artifact not found in package: ${missing.join(", ")}`);
|
|
4401
|
-
}
|
|
4402
|
-
return artifacts.filter((artifact) => artifact.required || artifactSelectorAliases(artifact).some((selector) => selectedSet.has(selector)));
|
|
4403
|
-
}
|
|
4404
|
-
function splitSelectorList(value) {
|
|
4405
|
-
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
4406
|
-
}
|
|
4407
|
-
function parseArtifactSelector(value) {
|
|
4408
|
-
const slash = value.indexOf("/");
|
|
4409
|
-
if (slash <= 0 || slash === value.length - 1) {
|
|
4410
|
-
throw new Error(`Invalid artifact selector: ${value}. Expected <type>/<name>.`);
|
|
4411
|
-
}
|
|
4412
|
-
const type = value.slice(0, slash);
|
|
4413
|
-
const name = value.slice(slash + 1);
|
|
4414
|
-
const parsedType = artifactTypeSchema.safeParse(type);
|
|
4415
|
-
if (!parsedType.success) {
|
|
4416
|
-
throw new Error(`Invalid artifact selector type: ${type}`);
|
|
4417
|
-
}
|
|
4418
|
-
return `${parsedType.data}/${name}`;
|
|
4419
|
-
}
|
|
4420
|
-
function subagentBaseName(name) {
|
|
4421
|
-
return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
|
|
4422
|
-
}
|
|
4423
|
-
|
|
4424
5471
|
// src/staging/staging.ts
|
|
4425
5472
|
async function stageSource(driver, source, options = {}) {
|
|
4426
5473
|
return renderStagedBundle(await stageSourceRaw(driver, source, options), options);
|
|
@@ -4434,15 +5481,15 @@ async function stageResolvedSourceRaw(driver, resolved) {
|
|
|
4434
5481
|
return stageResolvedArtifactsRaw(resolved, artifacts);
|
|
4435
5482
|
}
|
|
4436
5483
|
async function stageResolvedArtifactsRaw(resolved, artifacts) {
|
|
4437
|
-
const root = await
|
|
5484
|
+
const root = await mkdtemp3(join25(tmpdir4(), "agentwheel-stage-"));
|
|
4438
5485
|
const stagedArtifacts = [];
|
|
4439
5486
|
for (const artifact of artifacts) {
|
|
4440
|
-
const stagedPath =
|
|
4441
|
-
await
|
|
5487
|
+
const stagedPath = join25(root, artifact.relativePath);
|
|
5488
|
+
await mkdir12(dirname16(stagedPath), { recursive: true });
|
|
4442
5489
|
await cp5(artifact.sourcePath, stagedPath, {
|
|
4443
5490
|
recursive: artifact.kind === "dir",
|
|
4444
5491
|
dereference: true,
|
|
4445
|
-
filter: (path) => !isIgnoredGeneratedEntry(
|
|
5492
|
+
filter: (path) => !isIgnoredGeneratedEntry(basename15(path))
|
|
4446
5493
|
});
|
|
4447
5494
|
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
4448
5495
|
stagedArtifacts.push({
|
|
@@ -4523,16 +5570,16 @@ async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
|
4523
5570
|
}
|
|
4524
5571
|
for (const asset of artifact.assets) {
|
|
4525
5572
|
const source = resolvePackagePath(packageRoot, asset.from);
|
|
4526
|
-
const dest =
|
|
5573
|
+
const dest = join25(stagedPath, asset.into);
|
|
4527
5574
|
await copyAsset(asset, source, dest);
|
|
4528
5575
|
}
|
|
4529
5576
|
}
|
|
4530
5577
|
async function copyAsset(asset, source, dest) {
|
|
4531
5578
|
const sourceStats = await stat8(source);
|
|
4532
5579
|
if (sourceStats.isFile()) {
|
|
4533
|
-
if (matchesAny(
|
|
4534
|
-
await
|
|
4535
|
-
await copyAssetFile(source,
|
|
5580
|
+
if (matchesAny(basename15(source), asset.include)) {
|
|
5581
|
+
await mkdir12(dest, { recursive: true });
|
|
5582
|
+
await copyAssetFile(source, join25(dest, basename15(source)), asset);
|
|
4536
5583
|
}
|
|
4537
5584
|
return;
|
|
4538
5585
|
}
|
|
@@ -4540,19 +5587,19 @@ async function copyAsset(asset, source, dest) {
|
|
|
4540
5587
|
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
4541
5588
|
}
|
|
4542
5589
|
if (!asset.include?.length) {
|
|
4543
|
-
await
|
|
5590
|
+
await mkdir12(dirname16(dest), { recursive: true });
|
|
4544
5591
|
await cp5(source, dest, { recursive: true, dereference: true });
|
|
4545
5592
|
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
4546
5593
|
return;
|
|
4547
5594
|
}
|
|
4548
5595
|
for (const file of await listFiles(source)) {
|
|
4549
|
-
const rel =
|
|
4550
|
-
if (!matchesAny(rel, asset.include) && !matchesAny(
|
|
4551
|
-
await copyAssetFile(file,
|
|
5596
|
+
const rel = relative6(source, file).replaceAll("\\", "/");
|
|
5597
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename15(file), asset.include)) continue;
|
|
5598
|
+
await copyAssetFile(file, join25(dest, rel), asset);
|
|
4552
5599
|
}
|
|
4553
5600
|
}
|
|
4554
5601
|
async function copyAssetFile(source, dest, asset) {
|
|
4555
|
-
await
|
|
5602
|
+
await mkdir12(dirname16(dest), { recursive: true });
|
|
4556
5603
|
await cp5(source, dest, { dereference: true });
|
|
4557
5604
|
if (asset.mode === "copy") await chmod(dest, 420);
|
|
4558
5605
|
}
|
|
@@ -4568,7 +5615,7 @@ async function listFiles(root) {
|
|
|
4568
5615
|
const out = [];
|
|
4569
5616
|
async function walk2(dir) {
|
|
4570
5617
|
for (const entry of (await readdir5(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
4571
|
-
const full =
|
|
5618
|
+
const full = join25(dir, entry.name);
|
|
4572
5619
|
if (entry.isDirectory()) {
|
|
4573
5620
|
await walk2(full);
|
|
4574
5621
|
} else if (entry.isFile()) {
|
|
@@ -4587,7 +5634,7 @@ async function normalizeCopiedModes(path) {
|
|
|
4587
5634
|
}
|
|
4588
5635
|
if (!stats.isDirectory()) return;
|
|
4589
5636
|
for (const entry of await readdir5(path, { withFileTypes: true })) {
|
|
4590
|
-
await normalizeCopiedModes(
|
|
5637
|
+
await normalizeCopiedModes(join25(path, entry.name));
|
|
4591
5638
|
}
|
|
4592
5639
|
}
|
|
4593
5640
|
function matchesAny(path, patterns) {
|
|
@@ -4600,9 +5647,9 @@ function matchesGlob(path, pattern) {
|
|
|
4600
5647
|
}
|
|
4601
5648
|
|
|
4602
5649
|
// src/model/workspace.ts
|
|
4603
|
-
import { readFile as
|
|
5650
|
+
import { readFile as readFile18 } from "fs/promises";
|
|
4604
5651
|
import { homedir as homedir4 } from "os";
|
|
4605
|
-
import { dirname as
|
|
5652
|
+
import { dirname as dirname17, join as join26, resolve as resolve11 } from "path";
|
|
4606
5653
|
import { z as z6 } from "zod";
|
|
4607
5654
|
var workspacePackageSchema = z6.object({
|
|
4608
5655
|
name: z6.string().min(1),
|
|
@@ -4671,12 +5718,12 @@ var workspaceConfigSchema = z6.object({
|
|
|
4671
5718
|
agents: z6.record(z6.string(), workspaceAgentSchema).default({})
|
|
4672
5719
|
});
|
|
4673
5720
|
function workspaceConfigPath(workspaceRoot) {
|
|
4674
|
-
return
|
|
5721
|
+
return join26(workspaceRoot, ".agentwheel", "config.json");
|
|
4675
5722
|
}
|
|
4676
5723
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
4677
5724
|
const path = workspaceConfigPath(workspaceRoot);
|
|
4678
5725
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
4679
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
5726
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile18(path, "utf8")));
|
|
4680
5727
|
}
|
|
4681
5728
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
4682
5729
|
await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
|
|
@@ -4689,13 +5736,13 @@ function upsertPackage(config, entry) {
|
|
|
4689
5736
|
return { schemaVersion: 1, packages, bootstrapSkills: parsed.bootstrapSkills, registry: parsed.registry ?? {}, trust: parsed.trust ?? {}, profiles: parsed.profiles ?? {}, agents: parsed.agents ?? {} };
|
|
4690
5737
|
}
|
|
4691
5738
|
function globalWorkspaceConfigPath(globalRoot = homedir4()) {
|
|
4692
|
-
return
|
|
5739
|
+
return join26(globalRoot, ".agentwheel", "config.json");
|
|
4693
5740
|
}
|
|
4694
5741
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
4695
5742
|
let current = resolve11(start);
|
|
4696
5743
|
while (true) {
|
|
4697
5744
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
4698
|
-
const parent =
|
|
5745
|
+
const parent = dirname17(current);
|
|
4699
5746
|
if (parent === current) return resolve11(start);
|
|
4700
5747
|
current = parent;
|
|
4701
5748
|
}
|
|
@@ -4731,7 +5778,7 @@ function emptyWorkspaceConfig() {
|
|
|
4731
5778
|
}
|
|
4732
5779
|
async function readConfigPath(path) {
|
|
4733
5780
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
4734
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
5781
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile18(path, "utf8")));
|
|
4735
5782
|
}
|
|
4736
5783
|
function mergeWorkspaceTrust(global, project) {
|
|
4737
5784
|
return {
|
|
@@ -4746,23 +5793,23 @@ function sortedUnique2(values) {
|
|
|
4746
5793
|
}
|
|
4747
5794
|
|
|
4748
5795
|
// src/lifecycle/customization.ts
|
|
4749
|
-
import { appendFile, cp as cp6, mkdir as
|
|
4750
|
-
import { dirname as
|
|
5796
|
+
import { appendFile, cp as cp6, mkdir as mkdir13, rm as rm8 } from "fs/promises";
|
|
5797
|
+
import { dirname as dirname19, join as join29 } from "path";
|
|
4751
5798
|
|
|
4752
5799
|
// src/resolve/graph.ts
|
|
4753
|
-
import { createHash as
|
|
4754
|
-
import { mkdtemp as
|
|
4755
|
-
import { tmpdir as
|
|
4756
|
-
import { basename as
|
|
5800
|
+
import { createHash as createHash6 } from "crypto";
|
|
5801
|
+
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile20, stat as stat10 } from "fs/promises";
|
|
5802
|
+
import { tmpdir as tmpdir5 } from "os";
|
|
5803
|
+
import { basename as basename16, extname as extname4, join as join28 } from "path";
|
|
4757
5804
|
|
|
4758
5805
|
// src/resolve/identity.ts
|
|
4759
5806
|
import { homedir as homedir6 } from "os";
|
|
4760
5807
|
import { resolve as resolve13 } from "path";
|
|
4761
5808
|
|
|
4762
5809
|
// src/registry/client.ts
|
|
4763
|
-
import { readFile as
|
|
5810
|
+
import { readFile as readFile19, rm as rm7, stat as stat9 } from "fs/promises";
|
|
4764
5811
|
import { homedir as homedir5 } from "os";
|
|
4765
|
-
import { dirname as
|
|
5812
|
+
import { dirname as dirname18, join as join27, resolve as resolve12 } from "path";
|
|
4766
5813
|
import { fileURLToPath } from "url";
|
|
4767
5814
|
|
|
4768
5815
|
// src/model/registry.ts
|
|
@@ -4837,7 +5884,7 @@ var RegistryClient = class {
|
|
|
4837
5884
|
);
|
|
4838
5885
|
}
|
|
4839
5886
|
async clearCache() {
|
|
4840
|
-
await
|
|
5887
|
+
await rm7(this.cachePath, { force: true });
|
|
4841
5888
|
}
|
|
4842
5889
|
async getSources() {
|
|
4843
5890
|
if (this.options.sources?.length) return this.options.sources;
|
|
@@ -4860,7 +5907,7 @@ var RegistryClient = class {
|
|
|
4860
5907
|
}
|
|
4861
5908
|
async readCache() {
|
|
4862
5909
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
4863
|
-
return registryCacheSchema.parse(JSON.parse(await
|
|
5910
|
+
return registryCacheSchema.parse(JSON.parse(await readFile19(this.cachePath, "utf8")));
|
|
4864
5911
|
}
|
|
4865
5912
|
isExpired(cache, ttlMs) {
|
|
4866
5913
|
return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
|
|
@@ -4879,10 +5926,10 @@ var RegistryClient = class {
|
|
|
4879
5926
|
if (await pathExists(filePath)) {
|
|
4880
5927
|
const fullPath = resolve12(filePath);
|
|
4881
5928
|
const stats = await stat9(fullPath);
|
|
4882
|
-
return
|
|
5929
|
+
return readFile19(stats.isDirectory() ? join27(fullPath, "index.json") : fullPath, "utf8");
|
|
4883
5930
|
}
|
|
4884
|
-
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot:
|
|
4885
|
-
return
|
|
5931
|
+
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join27(dirname18(this.cachePath), "registry-repos") }));
|
|
5932
|
+
return readFile19(join27(resolved.resolvedPath, "index.json"), "utf8");
|
|
4886
5933
|
}
|
|
4887
5934
|
warnCompatibility(entries) {
|
|
4888
5935
|
for (const entry of entries) {
|
|
@@ -4917,7 +5964,7 @@ function mergeIndexes(indexes) {
|
|
|
4917
5964
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
4918
5965
|
}
|
|
4919
5966
|
function defaultRegistryCachePath() {
|
|
4920
|
-
return
|
|
5967
|
+
return join27(homedir5(), ".agentwheel", "registry-cache.json");
|
|
4921
5968
|
}
|
|
4922
5969
|
function sameSources(a, b) {
|
|
4923
5970
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -5146,7 +6193,7 @@ function compareSemver(a, b) {
|
|
|
5146
6193
|
var cacheLocks = /* @__PURE__ */ new Map();
|
|
5147
6194
|
async function resolveDependencyGraph(roots, options) {
|
|
5148
6195
|
if (roots.length === 0) throw new Error("At least one graph root is required.");
|
|
5149
|
-
const graphRoot = await
|
|
6196
|
+
const graphRoot = await mkdtemp4(join28(tmpdir5(), "agentwheel-graph-"));
|
|
5150
6197
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
5151
6198
|
const nodesByKey = /* @__PURE__ */ new Map();
|
|
5152
6199
|
const rootResults = [];
|
|
@@ -5508,7 +6555,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
|
|
|
5508
6555
|
const file = stack.shift();
|
|
5509
6556
|
if (scanned.has(file)) continue;
|
|
5510
6557
|
scanned.add(file);
|
|
5511
|
-
const content = await
|
|
6558
|
+
const content = await readFile20(file, "utf8");
|
|
5512
6559
|
for (const include of extractOpenPackIncludeSelectors(content)) {
|
|
5513
6560
|
await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
|
|
5514
6561
|
}
|
|
@@ -5551,7 +6598,7 @@ async function listMarkdownFiles2(root) {
|
|
|
5551
6598
|
const out = [];
|
|
5552
6599
|
async function walk2(dir) {
|
|
5553
6600
|
for (const entry of (await readdir6(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5554
|
-
const full =
|
|
6601
|
+
const full = join28(dir, entry.name);
|
|
5555
6602
|
if (entry.isDirectory()) {
|
|
5556
6603
|
await walk2(full);
|
|
5557
6604
|
} else if (entry.isFile() && extname4(entry.name).toLowerCase() === ".md") {
|
|
@@ -5586,7 +6633,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
5586
6633
|
const promise = (async () => {
|
|
5587
6634
|
const driver = getSourceDriver(normalized.driver);
|
|
5588
6635
|
const resolved = await driver.resolve(normalized.source, {
|
|
5589
|
-
cacheRoot: options.cacheRoot ??
|
|
6636
|
+
cacheRoot: options.cacheRoot ?? join28(options.workspaceRoot, ".agentwheel", "cache"),
|
|
5590
6637
|
mode,
|
|
5591
6638
|
ref: refOverride ?? normalized.requestedRef,
|
|
5592
6639
|
frozenLock: hardLockedCheckout
|
|
@@ -5596,7 +6643,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
5596
6643
|
const exported = await driver.export(translated);
|
|
5597
6644
|
const manifest = await readPackageManifest(exported.resolvedPath);
|
|
5598
6645
|
const artifacts = await driver.list(exported);
|
|
5599
|
-
const name = manifest?.name ?? exported.packageName ??
|
|
6646
|
+
const name = manifest?.name ?? exported.packageName ?? basename16(exported.resolvedPath);
|
|
5600
6647
|
const version = manifest?.version ?? exported.packageVersion ?? "0.0.0";
|
|
5601
6648
|
const sourceHash = exported.sourceHash ?? await hashPath(exported.resolvedPath);
|
|
5602
6649
|
return {
|
|
@@ -5772,7 +6819,7 @@ function detectDirectCollisions(nodes) {
|
|
|
5772
6819
|
}
|
|
5773
6820
|
}
|
|
5774
6821
|
function graphNodeId(name, version, normalizedSource, resolvedCommit, sourceHash) {
|
|
5775
|
-
const digest =
|
|
6822
|
+
const digest = createHash6("sha256").update(normalizedSource).update("\0").update(resolvedCommit ?? sourceHash).digest("hex").slice(0, 12);
|
|
5776
6823
|
return `${name}@${version}+${digest}`;
|
|
5777
6824
|
}
|
|
5778
6825
|
function sortedUnique3(values) {
|
|
@@ -5793,8 +6840,8 @@ async function mapLimit(items, limit, fn) {
|
|
|
5793
6840
|
|
|
5794
6841
|
// src/lifecycle/customization.ts
|
|
5795
6842
|
async function remember(workspaceRoot, runtime, text) {
|
|
5796
|
-
const overlayPath =
|
|
5797
|
-
await
|
|
6843
|
+
const overlayPath = join29(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
|
|
6844
|
+
await mkdir13(dirname19(overlayPath), { recursive: true });
|
|
5798
6845
|
await appendFile(overlayPath, `${text.trim()}
|
|
5799
6846
|
`, "utf8");
|
|
5800
6847
|
return { overlayPath };
|
|
@@ -5817,9 +6864,9 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
5817
6864
|
throw new Error(`Artifact not found: ${item}`);
|
|
5818
6865
|
}
|
|
5819
6866
|
const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
|
|
5820
|
-
const ejectedPath =
|
|
5821
|
-
await
|
|
5822
|
-
await
|
|
6867
|
+
const ejectedPath = join29(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
|
|
6868
|
+
await mkdir13(dirname19(ejectedPath), { recursive: true });
|
|
6869
|
+
await rm8(ejectedPath, { recursive: true, force: true });
|
|
5823
6870
|
await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
5824
6871
|
return {
|
|
5825
6872
|
...parsed,
|
|
@@ -5830,7 +6877,7 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
5830
6877
|
ejectedPath
|
|
5831
6878
|
};
|
|
5832
6879
|
} finally {
|
|
5833
|
-
await Promise.all(candidates.map((candidate) =>
|
|
6880
|
+
await Promise.all(candidates.map((candidate) => rm8(candidate.bundle.root, { recursive: true, force: true })));
|
|
5834
6881
|
}
|
|
5835
6882
|
}
|
|
5836
6883
|
function parseEjectItem(item) {
|
|
@@ -5860,7 +6907,7 @@ async function stageEjectCandidate(workspaceRoot, pkg) {
|
|
|
5860
6907
|
const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
|
|
5861
6908
|
const bundle = await stageSource(driver, normalized.source, {
|
|
5862
6909
|
adapter,
|
|
5863
|
-
cacheRoot:
|
|
6910
|
+
cacheRoot: join29(workspaceRoot, ".agentwheel", "cache"),
|
|
5864
6911
|
mode: pkg.mode,
|
|
5865
6912
|
ref: normalized.requestedRef ?? pkg.requestedRef
|
|
5866
6913
|
});
|
|
@@ -5907,12 +6954,12 @@ function ejectCommands(candidates, parsed) {
|
|
|
5907
6954
|
}
|
|
5908
6955
|
|
|
5909
6956
|
// src/lifecycle/profile.ts
|
|
5910
|
-
import { rm as
|
|
6957
|
+
import { rm as rm9 } from "fs/promises";
|
|
5911
6958
|
|
|
5912
6959
|
// src/lifecycle/source-plan.ts
|
|
5913
|
-
import { createHash as
|
|
5914
|
-
import { mkdir as
|
|
5915
|
-
import { dirname as
|
|
6960
|
+
import { createHash as createHash8 } from "crypto";
|
|
6961
|
+
import { mkdir as mkdir15 } from "fs/promises";
|
|
6962
|
+
import { dirname as dirname21, join as join32, resolve as resolve14 } from "path";
|
|
5916
6963
|
|
|
5917
6964
|
// src/resolve/graph-diff.ts
|
|
5918
6965
|
function diffGraphLocks(previous, next) {
|
|
@@ -6033,12 +7080,12 @@ function short(hash) {
|
|
|
6033
7080
|
}
|
|
6034
7081
|
|
|
6035
7082
|
// src/resolve/render.ts
|
|
6036
|
-
import { createHash as
|
|
6037
|
-
import { readFile as
|
|
6038
|
-
import { tmpdir as
|
|
6039
|
-
import { join as
|
|
7083
|
+
import { createHash as createHash7 } from "crypto";
|
|
7084
|
+
import { readFile as readFile21, mkdtemp as mkdtemp5 } from "fs/promises";
|
|
7085
|
+
import { tmpdir as tmpdir6 } from "os";
|
|
7086
|
+
import { join as join30 } from "path";
|
|
6040
7087
|
async function renderGraphForTarget(graph, targetContext = {}) {
|
|
6041
|
-
const root = await
|
|
7088
|
+
const root = await mkdtemp5(join30(tmpdir6(), "agentwheel-render-"));
|
|
6042
7089
|
const artifacts = [];
|
|
6043
7090
|
const stagedNodes = /* @__PURE__ */ new Map();
|
|
6044
7091
|
const includeEdges = /* @__PURE__ */ new Map();
|
|
@@ -6121,7 +7168,11 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
6121
7168
|
graphNodeId: rawNode.node.id,
|
|
6122
7169
|
packageNameAmbiguous: ambiguousPackageNames.has(rawNode.node.name)
|
|
6123
7170
|
}) : runtimeRenderedArtifacts;
|
|
6124
|
-
const
|
|
7171
|
+
const compatibleArtifacts = targetContext.adapter && targetContext.installationType ? await filterArtifactsByInstallFormat(renderedArtifacts, targetContext.adapter, targetContext.installationType, {
|
|
7172
|
+
selected: rawNode.node.selected,
|
|
7173
|
+
warn: targetContext.warn
|
|
7174
|
+
}) : renderedArtifacts;
|
|
7175
|
+
const installableArtifacts = compatibleArtifacts.filter((artifact) => rawNode.depth === 0 || artifact.type !== "fragments");
|
|
6125
7176
|
artifacts.push(...installableArtifacts.map((artifact) => ({
|
|
6126
7177
|
...artifact,
|
|
6127
7178
|
graphNodeId: rawNode.node.id,
|
|
@@ -6154,7 +7205,7 @@ async function artifactContentMap(artifacts) {
|
|
|
6154
7205
|
const out = /* @__PURE__ */ new Map();
|
|
6155
7206
|
for (const artifact of artifacts) {
|
|
6156
7207
|
if (artifact.kind !== "file") continue;
|
|
6157
|
-
out.set(artifact.relativePath.replaceAll("\\", "/"), await
|
|
7208
|
+
out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile21(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
|
|
6158
7209
|
}
|
|
6159
7210
|
return out;
|
|
6160
7211
|
}
|
|
@@ -6168,7 +7219,7 @@ function filterArtifactsByRuntime2(artifacts, adapterName, selectedSet) {
|
|
|
6168
7219
|
});
|
|
6169
7220
|
}
|
|
6170
7221
|
function sha2562(content) {
|
|
6171
|
-
return
|
|
7222
|
+
return createHash7("sha256").update(content).digest("hex");
|
|
6172
7223
|
}
|
|
6173
7224
|
function assignInstallNames(graph, artifacts) {
|
|
6174
7225
|
const aliases = workspaceAliases(graph);
|
|
@@ -6417,9 +7468,9 @@ function lockArtifactFor(artifact) {
|
|
|
6417
7468
|
}
|
|
6418
7469
|
|
|
6419
7470
|
// src/lifecycle/trust.ts
|
|
6420
|
-
import { mkdir as
|
|
7471
|
+
import { mkdir as mkdir14, readFile as readFile22 } from "fs/promises";
|
|
6421
7472
|
import { homedir as homedir7 } from "os";
|
|
6422
|
-
import { dirname as
|
|
7473
|
+
import { dirname as dirname20, join as join31 } from "path";
|
|
6423
7474
|
import { z as z8 } from "zod";
|
|
6424
7475
|
var trustStoreSchema = z8.object({
|
|
6425
7476
|
version: z8.literal(1),
|
|
@@ -6493,14 +7544,14 @@ function sortedUnique4(values) {
|
|
|
6493
7544
|
}
|
|
6494
7545
|
async function readTrustStore(path) {
|
|
6495
7546
|
if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
|
|
6496
|
-
return trustStoreSchema.parse(JSON.parse(await
|
|
7547
|
+
return trustStoreSchema.parse(JSON.parse(await readFile22(path, "utf8")));
|
|
6497
7548
|
}
|
|
6498
7549
|
async function writeTrustStore(path, store) {
|
|
6499
|
-
await
|
|
7550
|
+
await mkdir14(dirname20(path), { recursive: true });
|
|
6500
7551
|
await writeJsonAtomic(path, trustStoreSchema.parse(store));
|
|
6501
7552
|
}
|
|
6502
7553
|
function defaultTrustStorePath() {
|
|
6503
|
-
return process.env.AGENTWHEEL_TRUST_STORE ??
|
|
7554
|
+
return process.env.AGENTWHEEL_TRUST_STORE ?? join31(homedir7(), ".agentwheel", "trust.json");
|
|
6504
7555
|
}
|
|
6505
7556
|
|
|
6506
7557
|
// src/lifecycle/source-plan.ts
|
|
@@ -6538,7 +7589,7 @@ async function createGraphSourcePlan(options) {
|
|
|
6538
7589
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
6539
7590
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
6540
7591
|
workspaceRoot,
|
|
6541
|
-
cacheRoot:
|
|
7592
|
+
cacheRoot: join32(workspaceRoot, ".agentwheel", "cache"),
|
|
6542
7593
|
registryClient,
|
|
6543
7594
|
noDeps: options.noDeps,
|
|
6544
7595
|
lockedResolution: options.lockedResolution,
|
|
@@ -6557,7 +7608,9 @@ async function createGraphSourcePlan(options) {
|
|
|
6557
7608
|
const bundle = await renderGraphForTarget(graph, {
|
|
6558
7609
|
workspaceRoot,
|
|
6559
7610
|
adapter: options.adapter,
|
|
6560
|
-
|
|
7611
|
+
installationType,
|
|
7612
|
+
targetFingerprint,
|
|
7613
|
+
warn
|
|
6561
7614
|
});
|
|
6562
7615
|
const desiredArtifacts = desiredArtifactsFromGraphBundle(bundle);
|
|
6563
7616
|
const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
|
|
@@ -6573,7 +7626,8 @@ async function createGraphSourcePlan(options) {
|
|
|
6573
7626
|
stateKey,
|
|
6574
7627
|
forceDrift: options.forceDrift,
|
|
6575
7628
|
forceConflict: options.forceConflict,
|
|
6576
|
-
replaceConflict: options.replaceConflict
|
|
7629
|
+
replaceConflict: options.replaceConflict,
|
|
7630
|
+
warn
|
|
6577
7631
|
});
|
|
6578
7632
|
return {
|
|
6579
7633
|
plan,
|
|
@@ -6633,13 +7687,13 @@ async function readExistingGraphLock(path) {
|
|
|
6633
7687
|
return readGraphLock(path);
|
|
6634
7688
|
}
|
|
6635
7689
|
function pathForGraphLock(workspaceRoot, targetKey, adapter, targetFingerprint) {
|
|
6636
|
-
return
|
|
7690
|
+
return join32(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
|
|
6637
7691
|
}
|
|
6638
7692
|
function sanitizePathSegment(value) {
|
|
6639
7693
|
return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
|
|
6640
7694
|
}
|
|
6641
7695
|
function digestGraphLock(lock) {
|
|
6642
|
-
return
|
|
7696
|
+
return createHash8("sha256").update(canonicalGraphLockJson(lock)).digest("hex");
|
|
6643
7697
|
}
|
|
6644
7698
|
function workspaceOwnerId(workspaceRoot) {
|
|
6645
7699
|
return `workspace-root:${resolve14(workspaceRoot)}`;
|
|
@@ -6697,7 +7751,7 @@ ${sources.map((source) => `- ${source}`).join("\n")}`);
|
|
|
6697
7751
|
}
|
|
6698
7752
|
|
|
6699
7753
|
// src/runtime/target.ts
|
|
6700
|
-
import { basename as
|
|
7754
|
+
import { basename as basename17, dirname as dirname22, join as join33, resolve as resolve15 } from "path";
|
|
6701
7755
|
var runtimeMarkers = [
|
|
6702
7756
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
6703
7757
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -6808,9 +7862,9 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
|
6808
7862
|
for (const marker of runtimeMarkers) {
|
|
6809
7863
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
6810
7864
|
for (const dir of marker.dirs) {
|
|
6811
|
-
if (
|
|
6812
|
-
matches.push({ adapter: marker.adapter, targetRoot:
|
|
6813
|
-
} else if (await pathExists(
|
|
7865
|
+
if (basename17(root) === dir) {
|
|
7866
|
+
matches.push({ adapter: marker.adapter, targetRoot: dirname22(root) });
|
|
7867
|
+
} else if (await pathExists(join33(root, dir))) {
|
|
6814
7868
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
6815
7869
|
}
|
|
6816
7870
|
}
|
|
@@ -6849,7 +7903,7 @@ function dedupeTargets(matches) {
|
|
|
6849
7903
|
function runtimeScanRoot(request) {
|
|
6850
7904
|
const root = resolve15(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
6851
7905
|
if (request.targetRoot) return root;
|
|
6852
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
7906
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename17(root))) ? dirname22(root) : root;
|
|
6853
7907
|
}
|
|
6854
7908
|
|
|
6855
7909
|
// src/lifecycle/profile.ts
|
|
@@ -6935,7 +7989,7 @@ async function syncProfile(options) {
|
|
|
6935
7989
|
});
|
|
6936
7990
|
}
|
|
6937
7991
|
} finally {
|
|
6938
|
-
await
|
|
7992
|
+
await rm9(graphPlan.bundle.root, { recursive: true, force: true });
|
|
6939
7993
|
}
|
|
6940
7994
|
}
|
|
6941
7995
|
return results;
|
|
@@ -6959,9 +8013,9 @@ async function packageFromSource(source, options) {
|
|
|
6959
8013
|
}
|
|
6960
8014
|
|
|
6961
8015
|
// src/cli/update-check.ts
|
|
6962
|
-
import { mkdir as
|
|
8016
|
+
import { mkdir as mkdir16, readFile as readFile23, writeFile as writeFile14 } from "fs/promises";
|
|
6963
8017
|
import { homedir as homedir8 } from "os";
|
|
6964
|
-
import { dirname as
|
|
8018
|
+
import { dirname as dirname23, join as join34 } from "path";
|
|
6965
8019
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
6966
8020
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
6967
8021
|
var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
|
|
@@ -6969,7 +8023,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
6969
8023
|
if (isDisabled(options)) return;
|
|
6970
8024
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
6971
8025
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
6972
|
-
const cachePath = options.cachePath ??
|
|
8026
|
+
const cachePath = options.cachePath ?? join34(homedir8(), ".agentwheel", "update-check.json");
|
|
6973
8027
|
try {
|
|
6974
8028
|
const cached = await readCache(cachePath);
|
|
6975
8029
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -7006,7 +8060,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
7006
8060
|
}
|
|
7007
8061
|
async function readCache(path) {
|
|
7008
8062
|
try {
|
|
7009
|
-
const parsed = JSON.parse(await
|
|
8063
|
+
const parsed = JSON.parse(await readFile23(path, "utf8"));
|
|
7010
8064
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
7011
8065
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
7012
8066
|
} catch {
|
|
@@ -7014,8 +8068,8 @@ async function readCache(path) {
|
|
|
7014
8068
|
}
|
|
7015
8069
|
}
|
|
7016
8070
|
async function writeCache(path, cache) {
|
|
7017
|
-
await
|
|
7018
|
-
await
|
|
8071
|
+
await mkdir16(dirname23(path), { recursive: true });
|
|
8072
|
+
await writeFile14(path, `${JSON.stringify(cache, null, 2)}
|
|
7019
8073
|
`, "utf8");
|
|
7020
8074
|
}
|
|
7021
8075
|
function warnIfNewer(latest, current, stderr = process.stderr) {
|
|
@@ -7161,13 +8215,13 @@ function isCrossPackageSelector(value) {
|
|
|
7161
8215
|
}
|
|
7162
8216
|
|
|
7163
8217
|
// src/model/package-migrate.ts
|
|
7164
|
-
import { readFile as
|
|
7165
|
-
import { join as
|
|
7166
|
-
import { applyEdits, modify, parse as
|
|
8218
|
+
import { readFile as readFile24, rename as rename4, writeFile as writeFile15 } from "fs/promises";
|
|
8219
|
+
import { join as join36, resolve as resolve17 } from "path";
|
|
8220
|
+
import { applyEdits, modify, parse as parse4 } from "jsonc-parser";
|
|
7167
8221
|
async function migratePackageManifest(root) {
|
|
7168
8222
|
const packageRoot = resolve17(root);
|
|
7169
8223
|
for (const name of openPackManifestNames) {
|
|
7170
|
-
const path =
|
|
8224
|
+
const path = join36(packageRoot, name);
|
|
7171
8225
|
if (await pathExists(path)) {
|
|
7172
8226
|
return { changed: false, to: path, message: `Package already uses ${name}.` };
|
|
7173
8227
|
}
|
|
@@ -7176,24 +8230,24 @@ async function migratePackageManifest(root) {
|
|
|
7176
8230
|
if (!legacyName) {
|
|
7177
8231
|
throw new Error(`No legacy package manifest found at ${packageRoot}`);
|
|
7178
8232
|
}
|
|
7179
|
-
const from =
|
|
8233
|
+
const from = join36(packageRoot, legacyName);
|
|
7180
8234
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
7181
|
-
const to =
|
|
7182
|
-
const content = await
|
|
8235
|
+
const to = join36(packageRoot, toName);
|
|
8236
|
+
const content = await readFile24(from, "utf8");
|
|
7183
8237
|
const updated = updateSchemaVersion(content);
|
|
7184
8238
|
await rename4(from, to);
|
|
7185
|
-
await
|
|
8239
|
+
await writeFile15(to, updated, "utf8");
|
|
7186
8240
|
return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
|
|
7187
8241
|
}
|
|
7188
8242
|
async function firstExistingLegacyManifest(root) {
|
|
7189
8243
|
for (const name of legacyPackageManifestNames) {
|
|
7190
|
-
if (await pathExists(
|
|
8244
|
+
if (await pathExists(join36(root, name))) return name;
|
|
7191
8245
|
}
|
|
7192
8246
|
return void 0;
|
|
7193
8247
|
}
|
|
7194
8248
|
function updateSchemaVersion(content) {
|
|
7195
8249
|
const errors = [];
|
|
7196
|
-
|
|
8250
|
+
parse4(content, errors, { allowTrailingComma: true, disallowComments: false });
|
|
7197
8251
|
if (errors.length > 0) {
|
|
7198
8252
|
throw new Error("Cannot migrate invalid JSON/JSONC package manifest.");
|
|
7199
8253
|
}
|
|
@@ -7205,20 +8259,20 @@ function updateSchemaVersion(content) {
|
|
|
7205
8259
|
|
|
7206
8260
|
// src/cli/version.ts
|
|
7207
8261
|
import { readFileSync } from "fs";
|
|
7208
|
-
import { dirname as
|
|
8262
|
+
import { dirname as dirname24, join as join37 } from "path";
|
|
7209
8263
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7210
8264
|
var FALLBACK_VERSION = "0.0.0";
|
|
7211
8265
|
function resolveCliVersion() {
|
|
7212
|
-
let dir =
|
|
8266
|
+
let dir = dirname24(fileURLToPath2(import.meta.url));
|
|
7213
8267
|
while (true) {
|
|
7214
8268
|
try {
|
|
7215
|
-
const pkg = JSON.parse(readFileSync(
|
|
8269
|
+
const pkg = JSON.parse(readFileSync(join37(dir, "package.json"), "utf8"));
|
|
7216
8270
|
if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
|
|
7217
8271
|
return pkg.version;
|
|
7218
8272
|
}
|
|
7219
8273
|
} catch {
|
|
7220
8274
|
}
|
|
7221
|
-
const parent =
|
|
8275
|
+
const parent = dirname24(dir);
|
|
7222
8276
|
if (parent === dir) return FALLBACK_VERSION;
|
|
7223
8277
|
dir = parent;
|
|
7224
8278
|
}
|
|
@@ -7226,6 +8280,8 @@ function resolveCliVersion() {
|
|
|
7226
8280
|
|
|
7227
8281
|
// src/cli/index.ts
|
|
7228
8282
|
var CLI_VERSION = resolveCliVersion();
|
|
8283
|
+
var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
|
|
8284
|
+
var COMPANION_SKILL_NAME = "agentwheel";
|
|
7229
8285
|
var program = new Command();
|
|
7230
8286
|
program.name("agentwheel").description("Multi-runtime agent artifact orchestrator").version(CLI_VERSION).showSuggestionAfterError(false).option("--no-update-check", "disable npm version update check", false).addHelpText("after", `
|
|
7231
8287
|
|
|
@@ -7264,7 +8320,7 @@ program.command("list").description("list artifacts exposed by a package source"
|
|
|
7264
8320
|
const selectedArtifacts = selectedArtifactsFromOptions(options);
|
|
7265
8321
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
7266
8322
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
7267
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
8323
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join38(targetRoot, ".agentwheel", "cache") }))));
|
|
7268
8324
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
7269
8325
|
for (const artifact of artifacts) {
|
|
7270
8326
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
@@ -7274,7 +8330,7 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
7274
8330
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
7275
8331
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
7276
8332
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
7277
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
8333
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join38(targetRoot, ".agentwheel", "cache") }))));
|
|
7278
8334
|
const result = await driver.scan(resolved);
|
|
7279
8335
|
if (result.findings.length === 0) {
|
|
7280
8336
|
console.log("Scan ok: no findings");
|
|
@@ -7316,7 +8372,7 @@ program.command("deps").description("inspect the OpenPack dependency graph").add
|
|
|
7316
8372
|
for (const decision of result.bundle.graphLock.canonical.overrides) {
|
|
7317
8373
|
console.log(`OVERRIDE ${decision.graphNodeId}:${decision.type}/${decision.name} replaces ${decision.overriddenGraphNodeId}:${decision.type}/${decision.name} via ${decision.rootId} (${decision.selector})`);
|
|
7318
8374
|
}
|
|
7319
|
-
await
|
|
8375
|
+
await rm10(result.bundle.root, { recursive: true, force: true });
|
|
7320
8376
|
}
|
|
7321
8377
|
continue;
|
|
7322
8378
|
}
|
|
@@ -7437,6 +8493,16 @@ program.command("status").description("show configured packages and runtime inst
|
|
|
7437
8493
|
await printStatus(target, normalizedOptions);
|
|
7438
8494
|
}
|
|
7439
8495
|
});
|
|
8496
|
+
program.command("doctor").description("check agentwheel runtime setup and companion skill guidance").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).action(async (options) => {
|
|
8497
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
8498
|
+
const target = await resolveRuntimeTarget({
|
|
8499
|
+
targetRoot: normalizedOptions.targetRoot,
|
|
8500
|
+
adapter: normalizedOptions.adapter,
|
|
8501
|
+
installationType: normalizedOptions.installationType,
|
|
8502
|
+
agent: normalizedOptions.agent
|
|
8503
|
+
});
|
|
8504
|
+
await printDoctor(target, normalizedOptions);
|
|
8505
|
+
});
|
|
7440
8506
|
async function runInstallCommand(nameOrSource, options, behavior) {
|
|
7441
8507
|
const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
|
|
7442
8508
|
if (options.profile) {
|
|
@@ -7510,7 +8576,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
7510
8576
|
});
|
|
7511
8577
|
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
7512
8578
|
}
|
|
7513
|
-
await
|
|
8579
|
+
await rm10(result.bundle.root, { recursive: true, force: true });
|
|
7514
8580
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
7515
8581
|
}
|
|
7516
8582
|
if (behavior.apply && extraPackage && !targetOptions.onlySource) {
|
|
@@ -7536,7 +8602,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
7536
8602
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
7537
8603
|
workspaceRoot: targetRoot,
|
|
7538
8604
|
adapter,
|
|
7539
|
-
cacheRoot:
|
|
8605
|
+
cacheRoot: join38(targetRoot, ".agentwheel", "cache"),
|
|
7540
8606
|
mode: options.mode,
|
|
7541
8607
|
frozenLock: lockMode,
|
|
7542
8608
|
select: selectedArtifacts
|
|
@@ -7558,7 +8624,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
7558
8624
|
overrides: overrideArtifactsFromOptions(options)
|
|
7559
8625
|
};
|
|
7560
8626
|
} finally {
|
|
7561
|
-
await
|
|
8627
|
+
await rm10(bundle.root, { recursive: true, force: true });
|
|
7562
8628
|
}
|
|
7563
8629
|
}
|
|
7564
8630
|
function findConfiguredPackage(packages, value) {
|
|
@@ -7699,7 +8765,7 @@ async function runConfiguredGraphPackages(target, options, behavior) {
|
|
|
7699
8765
|
});
|
|
7700
8766
|
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
7701
8767
|
}
|
|
7702
|
-
await
|
|
8768
|
+
await rm10(result.bundle.root, { recursive: true, force: true });
|
|
7703
8769
|
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
7704
8770
|
}
|
|
7705
8771
|
}
|
|
@@ -7888,7 +8954,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
|
|
|
7888
8954
|
artifactType: entry.artifactType,
|
|
7889
8955
|
artifactName: entry.artifactName,
|
|
7890
8956
|
kind: entry.kind,
|
|
7891
|
-
destPath: operation?.destPath ??
|
|
8957
|
+
destPath: operation?.destPath ?? join38(targetRoot, entry.path),
|
|
7892
8958
|
relativeDestPath: entry.path,
|
|
7893
8959
|
desiredHash: entry.sourceHash,
|
|
7894
8960
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -7897,6 +8963,7 @@ function keepManifestEntryOperation(entry, targetRoot, rootId, operation, option
|
|
|
7897
8963
|
channel: entry.channel,
|
|
7898
8964
|
packageName: entry.packageName,
|
|
7899
8965
|
semanticCommand: entry.semanticCommand,
|
|
8966
|
+
semanticPlugin: entry.semanticPlugin,
|
|
7900
8967
|
execute: entry.executed,
|
|
7901
8968
|
mergeStrategy: entry.mergeStrategy,
|
|
7902
8969
|
composedFrom: entry.composedFrom,
|
|
@@ -7998,7 +9065,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
7998
9065
|
if (!options.dryRun) {
|
|
7999
9066
|
console.log(formatUninstallResult(result));
|
|
8000
9067
|
}
|
|
8001
|
-
if (renderedRoot) await
|
|
9068
|
+
if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
|
|
8002
9069
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
8003
9070
|
}
|
|
8004
9071
|
}
|
|
@@ -8126,9 +9193,57 @@ async function printPendingInstallWork(target, options) {
|
|
|
8126
9193
|
const message = error instanceof Error ? error.message : String(error);
|
|
8127
9194
|
console.log(`Pending install work: unavailable (${message})`);
|
|
8128
9195
|
} finally {
|
|
8129
|
-
await Promise.all(results.map((result) =>
|
|
9196
|
+
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
8130
9197
|
}
|
|
8131
9198
|
}
|
|
9199
|
+
async function printDoctor(target, options) {
|
|
9200
|
+
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
9201
|
+
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
9202
|
+
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForArtifacts(adapter, ["skills"]);
|
|
9203
|
+
const targetMapping = targetMappingForArtifact(adapter, "skills", installationType);
|
|
9204
|
+
if (!targetMapping?.enabled) {
|
|
9205
|
+
throw new Error(`Adapter ${adapter.name} does not support skills for installation type '${installationType}'.`);
|
|
9206
|
+
}
|
|
9207
|
+
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
|
|
9208
|
+
const companionSkillPath = join38(installRoot, targetMapping.dest, COMPANION_SKILL_NAME);
|
|
9209
|
+
const installed = await pathExists(companionSkillPath);
|
|
9210
|
+
console.log(`Doctor for ${adapter.name}/${installationType} at ${installRoot}`);
|
|
9211
|
+
if (installed) {
|
|
9212
|
+
console.log(`Agentwheel companion skill: installed at ${companionSkillPath}`);
|
|
9213
|
+
return;
|
|
9214
|
+
}
|
|
9215
|
+
console.log(`Agentwheel companion skill: missing at ${companionSkillPath}`);
|
|
9216
|
+
console.log("Suggested commands:");
|
|
9217
|
+
console.log(` ${companionSkillInstallCommand(adapter.name, installationType, options, { dryRun: true })}`);
|
|
9218
|
+
console.log(` ${companionSkillInstallCommand(adapter.name, installationType, options)}`);
|
|
9219
|
+
}
|
|
9220
|
+
function companionSkillInstallCommand(adapter, installationType, options, behavior = {}) {
|
|
9221
|
+
const args = [
|
|
9222
|
+
"agentwheel",
|
|
9223
|
+
"install",
|
|
9224
|
+
COMPANION_SKILL_SOURCE,
|
|
9225
|
+
"--adapter",
|
|
9226
|
+
adapter,
|
|
9227
|
+
...installationTypeCommandArgs(installationType),
|
|
9228
|
+
...targetRootCommandArgs(options),
|
|
9229
|
+
"--skill",
|
|
9230
|
+
COMPANION_SKILL_NAME
|
|
9231
|
+
];
|
|
9232
|
+
if (behavior.dryRun) args.push("--dry-run");
|
|
9233
|
+
return args.map(shellQuoteArg).join(" ");
|
|
9234
|
+
}
|
|
9235
|
+
function installationTypeCommandArgs(installationType) {
|
|
9236
|
+
if (installationType === "user") return ["--user"];
|
|
9237
|
+
if (installationType === "local") return ["--local"];
|
|
9238
|
+
return ["--installation-type", installationType];
|
|
9239
|
+
}
|
|
9240
|
+
function targetRootCommandArgs(options) {
|
|
9241
|
+
return options.targetRoot && options.installationType !== "user" ? ["--target-root", options.targetRoot] : [];
|
|
9242
|
+
}
|
|
9243
|
+
function shellQuoteArg(value) {
|
|
9244
|
+
if (/^[A-Za-z0-9_./:@=-]+$/.test(value)) return value;
|
|
9245
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
9246
|
+
}
|
|
8132
9247
|
function collectSelectOption(value, previous) {
|
|
8133
9248
|
return [...previous, ...splitSelectorList(value)];
|
|
8134
9249
|
}
|
|
@@ -8224,10 +9339,10 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
8224
9339
|
};
|
|
8225
9340
|
}
|
|
8226
9341
|
async function initPackage(root) {
|
|
8227
|
-
await
|
|
8228
|
-
await
|
|
8229
|
-
await
|
|
8230
|
-
const manifestPath =
|
|
9342
|
+
await mkdir17(join38(root, "instructions"), { recursive: true });
|
|
9343
|
+
await mkdir17(join38(root, "rules"), { recursive: true });
|
|
9344
|
+
await mkdir17(join38(root, "skills"), { recursive: true });
|
|
9345
|
+
const manifestPath = join38(root, "openpack.json");
|
|
8231
9346
|
const manifest = {
|
|
8232
9347
|
schemaVersion: 2,
|
|
8233
9348
|
name: "example/agentwheel-package",
|
|
@@ -8238,12 +9353,12 @@ async function initPackage(root) {
|
|
|
8238
9353
|
{ type: "skills", path: "skills" }
|
|
8239
9354
|
]
|
|
8240
9355
|
};
|
|
8241
|
-
await
|
|
9356
|
+
await writeFile16(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
8242
9357
|
`, "utf8");
|
|
8243
|
-
await
|
|
9358
|
+
await writeFile16(join38(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
8244
9359
|
}
|
|
8245
9360
|
async function defaultBootstrapPackage(_root) {
|
|
8246
|
-
const packageRoot = await findAgentwheelPackageRoot(
|
|
9361
|
+
const packageRoot = await findAgentwheelPackageRoot(dirname25(fileURLToPath3(import.meta.url)));
|
|
8247
9362
|
if (!packageRoot) return void 0;
|
|
8248
9363
|
return {
|
|
8249
9364
|
name: "agentwheel",
|
|
@@ -8290,7 +9405,7 @@ async function findAgentwheelPackageRoot(start) {
|
|
|
8290
9405
|
let current = resolve18(start);
|
|
8291
9406
|
while (true) {
|
|
8292
9407
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
8293
|
-
const parent =
|
|
9408
|
+
const parent = dirname25(current);
|
|
8294
9409
|
if (parent === current) return void 0;
|
|
8295
9410
|
current = parent;
|
|
8296
9411
|
}
|