agentwheel 0.14.13 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.js +1426 -264
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
// src/cli/index.ts
|
|
12
12
|
import { createHash as createHash11 } from "crypto";
|
|
13
13
|
import { existsSync } from "fs";
|
|
14
|
-
import { mkdir as
|
|
14
|
+
import { mkdir as mkdir23, rm as rm11, writeFile as writeFile22 } from "fs/promises";
|
|
15
15
|
import { homedir as homedir9 } from "os";
|
|
16
|
-
import { dirname as
|
|
16
|
+
import { dirname as dirname32, join as join44, resolve as resolve22 } from "path";
|
|
17
17
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
18
18
|
import { Command } from "commander";
|
|
19
19
|
|
|
@@ -810,13 +810,13 @@ async function spawnWithInput(command, args, input) {
|
|
|
810
810
|
if (result.stderr) throw new Error(result.stderr);
|
|
811
811
|
}
|
|
812
812
|
function waitForProcess(child, label) {
|
|
813
|
-
return new Promise((
|
|
813
|
+
return new Promise((resolve23, reject) => {
|
|
814
814
|
const stderr = [];
|
|
815
815
|
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
816
816
|
child.on("error", reject);
|
|
817
817
|
child.on("close", (code) => {
|
|
818
818
|
const message = Buffer.concat(stderr).toString("utf8");
|
|
819
|
-
if (code === 0)
|
|
819
|
+
if (code === 0) resolve23({ stderr: "" });
|
|
820
820
|
else reject(new Error(`${label} exited ${code}${message ? `: ${message}` : ""}`));
|
|
821
821
|
});
|
|
822
822
|
});
|
|
@@ -2592,6 +2592,7 @@ import { basename as basename8, join as join16, relative as relative3 } from "pa
|
|
|
2592
2592
|
// src/staging/codex-subagents.ts
|
|
2593
2593
|
import { mkdir as mkdir9, readFile as readFile11, writeFile as writeFile11 } from "fs/promises";
|
|
2594
2594
|
import { basename as basename4, dirname as dirname12, join as join6 } from "path";
|
|
2595
|
+
import { parse as parseYaml } from "yaml";
|
|
2595
2596
|
var requiredCodexAgentFields = ["name", "description", "developer_instructions"];
|
|
2596
2597
|
async function renderCodexSubagents(artifacts, stageRoot, adapter) {
|
|
2597
2598
|
if (adapter?.name !== "codex") return artifacts;
|
|
@@ -2664,24 +2665,36 @@ function markdownToCodexAgentToml(agentName, markdown) {
|
|
|
2664
2665
|
const parsed = splitFrontmatter(markdown);
|
|
2665
2666
|
const description = parsed.description ?? firstMeaningfulMarkdownLine(parsed.body) ?? `Custom Codex subagent ${agentName}.`;
|
|
2666
2667
|
const developerInstructions = parsed.body.trim().length > 0 ? parsed.body.trimEnd() : description;
|
|
2667
|
-
|
|
2668
|
+
const lines = [
|
|
2668
2669
|
`name = ${tomlString(agentName)}`,
|
|
2669
|
-
`description = ${tomlString(description)}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2670
|
+
`description = ${tomlString(description)}`
|
|
2671
|
+
];
|
|
2672
|
+
if (parsed.model) lines.push(`model = ${tomlString(parsed.model)}`);
|
|
2673
|
+
if (parsed.modelReasoningEffort) lines.push(`model_reasoning_effort = ${tomlString(parsed.modelReasoningEffort)}`);
|
|
2674
|
+
lines.push(`developer_instructions = ${tomlMultilineString(developerInstructions)}`, "");
|
|
2675
|
+
return lines.join("\n");
|
|
2673
2676
|
}
|
|
2674
2677
|
function splitFrontmatter(markdown) {
|
|
2675
2678
|
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(markdown);
|
|
2676
2679
|
if (!match) return { body: markdown };
|
|
2677
2680
|
const frontmatter = match[1] ?? "";
|
|
2678
2681
|
const body = markdown.slice(match[0].length);
|
|
2679
|
-
const
|
|
2682
|
+
const metadata = parseYaml(frontmatter);
|
|
2680
2683
|
return {
|
|
2681
2684
|
body,
|
|
2682
|
-
description:
|
|
2685
|
+
description: optionalFrontmatterString(metadata, "description"),
|
|
2686
|
+
model: optionalFrontmatterString(metadata, "model"),
|
|
2687
|
+
modelReasoningEffort: optionalFrontmatterString(metadata, "model_reasoning_effort")
|
|
2683
2688
|
};
|
|
2684
2689
|
}
|
|
2690
|
+
function optionalFrontmatterString(metadata, key) {
|
|
2691
|
+
const value = metadata?.[key];
|
|
2692
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
2693
|
+
if (typeof value !== "string") {
|
|
2694
|
+
throw new Error(`Codex subagent frontmatter field '${key}' must be a string.`);
|
|
2695
|
+
}
|
|
2696
|
+
return value.trim();
|
|
2697
|
+
}
|
|
2685
2698
|
function firstMeaningfulMarkdownLine(markdown) {
|
|
2686
2699
|
for (const rawLine of markdown.split(/\r?\n/)) {
|
|
2687
2700
|
const line = rawLine.trim();
|
|
@@ -5250,14 +5263,14 @@ async function writeMermaidAsset(response) {
|
|
|
5250
5263
|
"content-type": "application/javascript; charset=utf-8",
|
|
5251
5264
|
"cache-control": "public, max-age=3600"
|
|
5252
5265
|
});
|
|
5253
|
-
await new Promise((
|
|
5266
|
+
await new Promise((resolve23) => {
|
|
5254
5267
|
const stream = createReadStream(assetPath);
|
|
5255
5268
|
stream.on("error", () => {
|
|
5256
5269
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
5257
5270
|
else response.destroy();
|
|
5258
|
-
|
|
5271
|
+
resolve23();
|
|
5259
5272
|
});
|
|
5260
|
-
stream.on("end",
|
|
5273
|
+
stream.on("end", resolve23);
|
|
5261
5274
|
stream.pipe(response);
|
|
5262
5275
|
});
|
|
5263
5276
|
}
|
|
@@ -5284,14 +5297,14 @@ function escapeHtml2(value) {
|
|
|
5284
5297
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
5285
5298
|
}
|
|
5286
5299
|
function listen(server, port, bind) {
|
|
5287
|
-
return new Promise((
|
|
5300
|
+
return new Promise((resolve23, reject) => {
|
|
5288
5301
|
const onError = (error) => {
|
|
5289
5302
|
server.off("listening", onListening);
|
|
5290
5303
|
reject(error);
|
|
5291
5304
|
};
|
|
5292
5305
|
const onListening = () => {
|
|
5293
5306
|
server.off("error", onError);
|
|
5294
|
-
|
|
5307
|
+
resolve23();
|
|
5295
5308
|
};
|
|
5296
5309
|
server.once("error", onError);
|
|
5297
5310
|
server.once("listening", onListening);
|
|
@@ -5299,7 +5312,7 @@ function listen(server, port, bind) {
|
|
|
5299
5312
|
});
|
|
5300
5313
|
}
|
|
5301
5314
|
function waitForShutdown(server, beforeClose) {
|
|
5302
|
-
return new Promise((
|
|
5315
|
+
return new Promise((resolve23) => {
|
|
5303
5316
|
let closing = false;
|
|
5304
5317
|
const shutdown = () => {
|
|
5305
5318
|
if (closing) return;
|
|
@@ -5307,7 +5320,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
5307
5320
|
process.off("SIGINT", shutdown);
|
|
5308
5321
|
process.off("SIGTERM", shutdown);
|
|
5309
5322
|
beforeClose();
|
|
5310
|
-
server.close(() =>
|
|
5323
|
+
server.close(() => resolve23());
|
|
5311
5324
|
};
|
|
5312
5325
|
process.once("SIGINT", shutdown);
|
|
5313
5326
|
process.once("SIGTERM", shutdown);
|
|
@@ -5317,7 +5330,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
5317
5330
|
process.off("SIGINT", shutdown);
|
|
5318
5331
|
process.off("SIGTERM", shutdown);
|
|
5319
5332
|
beforeClose();
|
|
5320
|
-
|
|
5333
|
+
resolve23();
|
|
5321
5334
|
});
|
|
5322
5335
|
});
|
|
5323
5336
|
}
|
|
@@ -5837,7 +5850,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
5837
5850
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
5838
5851
|
}
|
|
5839
5852
|
async function delay(ms) {
|
|
5840
|
-
await new Promise((
|
|
5853
|
+
await new Promise((resolve23) => setTimeout(resolve23, ms));
|
|
5841
5854
|
}
|
|
5842
5855
|
function parseClawHubSource(source) {
|
|
5843
5856
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -5931,9 +5944,9 @@ var GitSourceDriver = class {
|
|
|
5931
5944
|
throw new Error(`Frozen lock requires cached git checkout at ${resolved.resolvedPath}`);
|
|
5932
5945
|
}
|
|
5933
5946
|
await rm6(resolved.resolvedPath, { recursive: true, force: true });
|
|
5934
|
-
await git(["clone",
|
|
5947
|
+
await git(["clone", parsed.url, resolved.resolvedPath]);
|
|
5935
5948
|
} else if (!resolved.frozenLock) {
|
|
5936
|
-
await git(["-C", resolved.resolvedPath, "fetch", "--prune", "origin"]);
|
|
5949
|
+
await git(["-C", resolved.resolvedPath, "fetch", "--tags", "--prune", "origin"]);
|
|
5937
5950
|
}
|
|
5938
5951
|
const ref = resolved.requestedRef ?? parsed.ref ?? "HEAD";
|
|
5939
5952
|
if (ref === "HEAD") {
|
|
@@ -6029,7 +6042,7 @@ async function withFilesystemLock(lockPath, timeoutMs, fn) {
|
|
|
6029
6042
|
if (Date.now() - started > timeoutMs) {
|
|
6030
6043
|
throw new Error(`Timed out waiting for git cache lock at ${lockPath}`);
|
|
6031
6044
|
}
|
|
6032
|
-
await new Promise((
|
|
6045
|
+
await new Promise((resolve23) => setTimeout(resolve23, 50));
|
|
6033
6046
|
}
|
|
6034
6047
|
}
|
|
6035
6048
|
try {
|
|
@@ -6538,8 +6551,8 @@ function getSourceDriver(name = "local") {
|
|
|
6538
6551
|
}
|
|
6539
6552
|
|
|
6540
6553
|
// src/staging/staging.ts
|
|
6541
|
-
import { chmod, cp as cp5, mkdir as
|
|
6542
|
-
import { basename as
|
|
6554
|
+
import { chmod, cp as cp5, mkdir as mkdir18, mkdtemp as mkdtemp3, readdir as readdir5, stat as stat8 } from "fs/promises";
|
|
6555
|
+
import { basename as basename19, dirname as dirname23, join as join29, relative as relative7, resolve as resolve12, sep as sep2 } from "path";
|
|
6543
6556
|
import { tmpdir as tmpdir4 } from "os";
|
|
6544
6557
|
|
|
6545
6558
|
// src/compose/markdown.ts
|
|
@@ -6953,6 +6966,57 @@ async function sortedDirEntries2(path) {
|
|
|
6953
6966
|
return (await readdir4(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
|
|
6954
6967
|
}
|
|
6955
6968
|
|
|
6969
|
+
// src/staging/claude-subagents.ts
|
|
6970
|
+
import { mkdir as mkdir17, readFile as readFile21, writeFile as writeFile19 } from "fs/promises";
|
|
6971
|
+
import { basename as basename18, dirname as dirname22, join as join28 } from "path";
|
|
6972
|
+
async function renderClaudeSubagents(artifacts, stageRoot, adapter) {
|
|
6973
|
+
if (adapter?.name !== "claude") return artifacts;
|
|
6974
|
+
const names = /* @__PURE__ */ new Set();
|
|
6975
|
+
const rendered = [];
|
|
6976
|
+
for (const artifact of artifacts) {
|
|
6977
|
+
if (artifact.type !== "subagents") {
|
|
6978
|
+
rendered.push(artifact);
|
|
6979
|
+
continue;
|
|
6980
|
+
}
|
|
6981
|
+
const next = await renderClaudeSubagent(artifact, stageRoot);
|
|
6982
|
+
if (names.has(next.name)) {
|
|
6983
|
+
throw new Error(`Claude subagents produce duplicate agent name '${next.name}'.`);
|
|
6984
|
+
}
|
|
6985
|
+
names.add(next.name);
|
|
6986
|
+
rendered.push(next);
|
|
6987
|
+
}
|
|
6988
|
+
return rendered;
|
|
6989
|
+
}
|
|
6990
|
+
async function renderClaudeSubagent(artifact, stageRoot) {
|
|
6991
|
+
const sourcePath = artifact.stagedPath ?? artifact.sourcePath;
|
|
6992
|
+
const agentName = claudeAgentName(artifact);
|
|
6993
|
+
const markdownPath = artifact.kind === "dir" ? join28(sourcePath, "AGENTS.md") : sourcePath;
|
|
6994
|
+
if (artifact.kind === "dir" && !await pathExists(markdownPath)) {
|
|
6995
|
+
throw new Error(`Claude subagent directory ${artifact.relativePath} must contain AGENTS.md.`);
|
|
6996
|
+
}
|
|
6997
|
+
if (artifact.kind === "file" && !artifact.name.toLowerCase().endsWith(".md") && !sourcePath.toLowerCase().endsWith(".md")) {
|
|
6998
|
+
throw new Error(`Claude subagent ${artifact.relativePath} must be a .md file or directory containing AGENTS.md.`);
|
|
6999
|
+
}
|
|
7000
|
+
const content = await readFile21(markdownPath, "utf8");
|
|
7001
|
+
const renderedPath = join28(stageRoot, ".agentwheel-rendered", "claude-subagents", `${agentName}.md`);
|
|
7002
|
+
await mkdir17(dirname22(renderedPath), { recursive: true });
|
|
7003
|
+
await writeFile19(renderedPath, content.endsWith("\n") ? content : `${content}
|
|
7004
|
+
`, "utf8");
|
|
7005
|
+
return {
|
|
7006
|
+
...artifact,
|
|
7007
|
+
name: `${agentName}.md`,
|
|
7008
|
+
sourcePath: renderedPath,
|
|
7009
|
+
stagedPath: renderedPath,
|
|
7010
|
+
relativePath: join28("subagents", `${agentName}.md`),
|
|
7011
|
+
kind: "file",
|
|
7012
|
+
hash: await hashPath(renderedPath)
|
|
7013
|
+
};
|
|
7014
|
+
}
|
|
7015
|
+
function claudeAgentName(artifact) {
|
|
7016
|
+
const raw = artifact.kind === "dir" ? artifact.name : basename18(artifact.name);
|
|
7017
|
+
return raw.replace(/\.agent\.md$/i, "").replace(/\.md$/i, "");
|
|
7018
|
+
}
|
|
7019
|
+
|
|
6956
7020
|
// src/staging/staging.ts
|
|
6957
7021
|
async function stageSource(driver, source, options = {}) {
|
|
6958
7022
|
return renderStagedBundle(await stageSourceRaw(driver, source, options), options);
|
|
@@ -6966,15 +7030,15 @@ async function stageResolvedSourceRaw(driver, resolved) {
|
|
|
6966
7030
|
return stageResolvedArtifactsRaw(resolved, artifacts);
|
|
6967
7031
|
}
|
|
6968
7032
|
async function stageResolvedArtifactsRaw(resolved, artifacts) {
|
|
6969
|
-
const root = await mkdtemp3(
|
|
7033
|
+
const root = await mkdtemp3(join29(tmpdir4(), "agentwheel-stage-"));
|
|
6970
7034
|
const stagedArtifacts = [];
|
|
6971
7035
|
for (const artifact of artifacts) {
|
|
6972
|
-
const stagedPath =
|
|
6973
|
-
await
|
|
7036
|
+
const stagedPath = join29(root, artifact.relativePath);
|
|
7037
|
+
await mkdir18(dirname23(stagedPath), { recursive: true });
|
|
6974
7038
|
await cp5(artifact.sourcePath, stagedPath, {
|
|
6975
7039
|
recursive: artifact.kind === "dir",
|
|
6976
7040
|
dereference: true,
|
|
6977
|
-
filter: (path) => !isIgnoredGeneratedEntry(
|
|
7041
|
+
filter: (path) => !isIgnoredGeneratedEntry(basename19(path))
|
|
6978
7042
|
});
|
|
6979
7043
|
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
6980
7044
|
stagedArtifacts.push({
|
|
@@ -7002,7 +7066,8 @@ async function renderStagedBundle(bundle, options = {}) {
|
|
|
7002
7066
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, options.select, options.skills);
|
|
7003
7067
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(options.select, options.skills) ?? []);
|
|
7004
7068
|
const runtimeArtifacts = options.adapter ? filterArtifactsByRuntime(selectedArtifacts, options.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
7005
|
-
const
|
|
7069
|
+
const claudeRenderedArtifacts = await renderClaudeSubagents(runtimeArtifacts, root, options.adapter);
|
|
7070
|
+
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, root, options.adapter);
|
|
7006
7071
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, root, options.adapter);
|
|
7007
7072
|
const renderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, root, options.adapter);
|
|
7008
7073
|
const finalArtifacts = options.workspaceRoot && options.adapter ? await applyCustomizations(renderedArtifacts, {
|
|
@@ -7056,16 +7121,16 @@ async function composeAssets(artifact, packageRoot, stagedPath) {
|
|
|
7056
7121
|
}
|
|
7057
7122
|
for (const asset of artifact.assets) {
|
|
7058
7123
|
const source = resolvePackagePath(packageRoot, asset.from);
|
|
7059
|
-
const dest =
|
|
7124
|
+
const dest = join29(stagedPath, asset.into);
|
|
7060
7125
|
await copyAsset(asset, source, dest);
|
|
7061
7126
|
}
|
|
7062
7127
|
}
|
|
7063
7128
|
async function copyAsset(asset, source, dest) {
|
|
7064
7129
|
const sourceStats = await stat8(source);
|
|
7065
7130
|
if (sourceStats.isFile()) {
|
|
7066
|
-
if (matchesAny(
|
|
7067
|
-
await
|
|
7068
|
-
await copyAssetFile(source,
|
|
7131
|
+
if (matchesAny(basename19(source), asset.include)) {
|
|
7132
|
+
await mkdir18(dest, { recursive: true });
|
|
7133
|
+
await copyAssetFile(source, join29(dest, basename19(source)), asset);
|
|
7069
7134
|
}
|
|
7070
7135
|
return;
|
|
7071
7136
|
}
|
|
@@ -7073,19 +7138,19 @@ async function copyAsset(asset, source, dest) {
|
|
|
7073
7138
|
throw new Error(`Asset include source is not a file or directory: ${source}`);
|
|
7074
7139
|
}
|
|
7075
7140
|
if (!asset.include?.length) {
|
|
7076
|
-
await
|
|
7141
|
+
await mkdir18(dirname23(dest), { recursive: true });
|
|
7077
7142
|
await cp5(source, dest, { recursive: true, dereference: true });
|
|
7078
7143
|
if (asset.mode === "copy") await normalizeCopiedModes(dest);
|
|
7079
7144
|
return;
|
|
7080
7145
|
}
|
|
7081
7146
|
for (const file of await listFiles(source)) {
|
|
7082
7147
|
const rel = relative7(source, file).replaceAll("\\", "/");
|
|
7083
|
-
if (!matchesAny(rel, asset.include) && !matchesAny(
|
|
7084
|
-
await copyAssetFile(file,
|
|
7148
|
+
if (!matchesAny(rel, asset.include) && !matchesAny(basename19(file), asset.include)) continue;
|
|
7149
|
+
await copyAssetFile(file, join29(dest, rel), asset);
|
|
7085
7150
|
}
|
|
7086
7151
|
}
|
|
7087
7152
|
async function copyAssetFile(source, dest, asset) {
|
|
7088
|
-
await
|
|
7153
|
+
await mkdir18(dirname23(dest), { recursive: true });
|
|
7089
7154
|
await cp5(source, dest, { dereference: true });
|
|
7090
7155
|
if (asset.mode === "copy") await chmod(dest, 420);
|
|
7091
7156
|
}
|
|
@@ -7101,7 +7166,7 @@ async function listFiles(root) {
|
|
|
7101
7166
|
const out = [];
|
|
7102
7167
|
async function walk2(dir) {
|
|
7103
7168
|
for (const entry of (await readdir5(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
7104
|
-
const full =
|
|
7169
|
+
const full = join29(dir, entry.name);
|
|
7105
7170
|
if (entry.isDirectory()) {
|
|
7106
7171
|
await walk2(full);
|
|
7107
7172
|
} else if (entry.isFile()) {
|
|
@@ -7120,7 +7185,7 @@ async function normalizeCopiedModes(path) {
|
|
|
7120
7185
|
}
|
|
7121
7186
|
if (!stats.isDirectory()) return;
|
|
7122
7187
|
for (const entry of await readdir5(path, { withFileTypes: true })) {
|
|
7123
|
-
await normalizeCopiedModes(
|
|
7188
|
+
await normalizeCopiedModes(join29(path, entry.name));
|
|
7124
7189
|
}
|
|
7125
7190
|
}
|
|
7126
7191
|
function matchesAny(path, patterns) {
|
|
@@ -7133,10 +7198,108 @@ function matchesGlob(path, pattern) {
|
|
|
7133
7198
|
}
|
|
7134
7199
|
|
|
7135
7200
|
// src/model/workspace.ts
|
|
7136
|
-
import { readFile as
|
|
7201
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
7137
7202
|
import { homedir as homedir4 } from "os";
|
|
7138
|
-
import { dirname as
|
|
7203
|
+
import { dirname as dirname24, join as join30, resolve as resolve13 } from "path";
|
|
7139
7204
|
import { z as z6 } from "zod";
|
|
7205
|
+
|
|
7206
|
+
// src/resolve/semver.ts
|
|
7207
|
+
var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
7208
|
+
function parseSemver(value) {
|
|
7209
|
+
const match = semverPattern.exec(value.trim());
|
|
7210
|
+
if (!match) return void 0;
|
|
7211
|
+
return {
|
|
7212
|
+
major: Number(match[1]),
|
|
7213
|
+
minor: Number(match[2]),
|
|
7214
|
+
patch: Number(match[3]),
|
|
7215
|
+
prerelease: match[4]
|
|
7216
|
+
};
|
|
7217
|
+
}
|
|
7218
|
+
function satisfiesVersionRange(version, range) {
|
|
7219
|
+
if (!range || range.trim() === "" || range.trim() === "*") return true;
|
|
7220
|
+
const parsedVersion = parseSemver(version);
|
|
7221
|
+
const trimmed = range.trim();
|
|
7222
|
+
if (!parsedVersion) {
|
|
7223
|
+
return trimmed === "*" || trimmed === version;
|
|
7224
|
+
}
|
|
7225
|
+
const comparators = parseRange(trimmed);
|
|
7226
|
+
if (!comparators) return trimmed === version;
|
|
7227
|
+
return comparators.every((comparator) => compareWith(parsedVersion, comparator));
|
|
7228
|
+
}
|
|
7229
|
+
function semverMajorOrVersion(version) {
|
|
7230
|
+
const parsed = parseSemver(version);
|
|
7231
|
+
return parsed ? String(parsed.major) : version;
|
|
7232
|
+
}
|
|
7233
|
+
function compareSemverStrings(a, b) {
|
|
7234
|
+
const parsedA = parseSemver(a);
|
|
7235
|
+
const parsedB = parseSemver(b);
|
|
7236
|
+
if (!parsedA || !parsedB) return a.localeCompare(b);
|
|
7237
|
+
return compareSemver(parsedA, parsedB);
|
|
7238
|
+
}
|
|
7239
|
+
function isSupportedVersionRange(range) {
|
|
7240
|
+
const trimmed = range.trim();
|
|
7241
|
+
return trimmed === "*" || parseRange(trimmed) !== void 0;
|
|
7242
|
+
}
|
|
7243
|
+
function parseRange(range) {
|
|
7244
|
+
if (range === "*") return [];
|
|
7245
|
+
if (range.startsWith("^")) {
|
|
7246
|
+
const base = parseSemver(range.slice(1));
|
|
7247
|
+
if (!base) return void 0;
|
|
7248
|
+
return [
|
|
7249
|
+
{ op: ">=", version: base },
|
|
7250
|
+
{ op: "<", version: caretUpperBound(base) }
|
|
7251
|
+
];
|
|
7252
|
+
}
|
|
7253
|
+
if (range.startsWith("~")) {
|
|
7254
|
+
const base = parseSemver(range.slice(1));
|
|
7255
|
+
if (!base) return void 0;
|
|
7256
|
+
return [
|
|
7257
|
+
{ op: ">=", version: base },
|
|
7258
|
+
{ op: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } }
|
|
7259
|
+
];
|
|
7260
|
+
}
|
|
7261
|
+
const parts = range.split(/\s+/).filter(Boolean);
|
|
7262
|
+
const comparators = [];
|
|
7263
|
+
for (const part of parts) {
|
|
7264
|
+
const match = /^(>=|<=|>|<|=)?(.+)$/.exec(part);
|
|
7265
|
+
if (!match) return void 0;
|
|
7266
|
+
const version = parseSemver(match[2] ?? "");
|
|
7267
|
+
if (!version) return void 0;
|
|
7268
|
+
comparators.push({ op: match[1] ?? "=", version });
|
|
7269
|
+
}
|
|
7270
|
+
return comparators;
|
|
7271
|
+
}
|
|
7272
|
+
function caretUpperBound(version) {
|
|
7273
|
+
if (version.major > 0) return { major: version.major + 1, minor: 0, patch: 0 };
|
|
7274
|
+
if (version.minor > 0) return { major: 0, minor: version.minor + 1, patch: 0 };
|
|
7275
|
+
return { major: 0, minor: 0, patch: version.patch + 1 };
|
|
7276
|
+
}
|
|
7277
|
+
function compareWith(version, comparator) {
|
|
7278
|
+
const order = compareSemver(version, comparator.version);
|
|
7279
|
+
switch (comparator.op) {
|
|
7280
|
+
case "=":
|
|
7281
|
+
return order === 0;
|
|
7282
|
+
case ">":
|
|
7283
|
+
return order > 0;
|
|
7284
|
+
case ">=":
|
|
7285
|
+
return order >= 0;
|
|
7286
|
+
case "<":
|
|
7287
|
+
return order < 0;
|
|
7288
|
+
case "<=":
|
|
7289
|
+
return order <= 0;
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
function compareSemver(a, b) {
|
|
7293
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
7294
|
+
if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
|
|
7295
|
+
}
|
|
7296
|
+
if (a.prerelease === b.prerelease) return 0;
|
|
7297
|
+
if (!a.prerelease) return 1;
|
|
7298
|
+
if (!b.prerelease) return -1;
|
|
7299
|
+
return a.prerelease.localeCompare(b.prerelease);
|
|
7300
|
+
}
|
|
7301
|
+
|
|
7302
|
+
// src/model/workspace.ts
|
|
7140
7303
|
var artifactSelectorListSchema = z6.array(z6.string().min(1));
|
|
7141
7304
|
var workspaceSelectionImportSchema = z6.object({
|
|
7142
7305
|
export: z6.string().min(1),
|
|
@@ -7177,6 +7340,9 @@ var workspacePackageBaseSchema = z6.object({
|
|
|
7177
7340
|
adapterCodeHash: z6.string().min(16).optional(),
|
|
7178
7341
|
installationType: installationTypeSchema.optional(),
|
|
7179
7342
|
mode: z6.enum(["pinned", "tracking"]).default("pinned"),
|
|
7343
|
+
version: z6.string().min(1).refine(isSupportedVersionRange, {
|
|
7344
|
+
message: "Version policy must be an exact semver, ~range, ^range, comparator range, or *"
|
|
7345
|
+
}).optional(),
|
|
7180
7346
|
requestedRef: z6.string().min(1).optional(),
|
|
7181
7347
|
select: z6.array(z6.string().min(1)).optional(),
|
|
7182
7348
|
skills: z6.array(z6.string().min(1)).optional(),
|
|
@@ -7212,9 +7378,45 @@ var workspaceProfileRuntimeSchema = z6.object({
|
|
|
7212
7378
|
reloadRuntimes: z6.boolean().optional(),
|
|
7213
7379
|
reloadCommands: commandListSchema
|
|
7214
7380
|
});
|
|
7215
|
-
var
|
|
7216
|
-
|
|
7381
|
+
var workspaceProfileMemberSchema = z6.object({
|
|
7382
|
+
id: z6.string().min(1).regex(/^[a-z0-9][a-z0-9._-]*$/i),
|
|
7383
|
+
workspace: z6.string().min(1),
|
|
7384
|
+
profile: z6.string().min(1),
|
|
7385
|
+
transport: z6.enum(["local", "ssh"]).default("local"),
|
|
7386
|
+
host: z6.string().min(1).optional(),
|
|
7387
|
+
user: z6.string().min(1).optional(),
|
|
7388
|
+
port: z6.number().int().positive().optional(),
|
|
7389
|
+
identityFile: z6.string().min(1).optional(),
|
|
7390
|
+
refreshTtlSeconds: z6.number().int().positive().optional()
|
|
7391
|
+
}).strict().superRefine((member, ctx) => {
|
|
7392
|
+
if (member.transport === "ssh" && !member.host) {
|
|
7393
|
+
ctx.addIssue({ code: "custom", path: ["host"], message: "SSH profile members require host" });
|
|
7394
|
+
}
|
|
7395
|
+
if (member.transport === "ssh" && !member.workspace.startsWith("/")) {
|
|
7396
|
+
ctx.addIssue({ code: "custom", path: ["workspace"], message: "SSH profile member workspaces must be absolute" });
|
|
7397
|
+
}
|
|
7398
|
+
});
|
|
7399
|
+
var workspaceLeafProfileSchema = z6.object({
|
|
7400
|
+
runtimes: z6.array(workspaceProfileRuntimeSchema).min(1),
|
|
7401
|
+
members: z6.never().optional()
|
|
7402
|
+
}).strict();
|
|
7403
|
+
var workspaceCompositeProfileSchema = z6.object({
|
|
7404
|
+
members: z6.array(workspaceProfileMemberSchema).min(1),
|
|
7405
|
+
runtimes: z6.never().optional(),
|
|
7406
|
+
refreshTtlSeconds: z6.number().int().positive().default(86400)
|
|
7407
|
+
}).strict().superRefine((profile, ctx) => {
|
|
7408
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7409
|
+
for (const [index, member] of profile.members.entries()) {
|
|
7410
|
+
if (seen.has(member.id)) {
|
|
7411
|
+
ctx.addIssue({ code: "custom", path: ["members", index, "id"], message: "Composite profile member ids must be unique" });
|
|
7412
|
+
}
|
|
7413
|
+
seen.add(member.id);
|
|
7414
|
+
}
|
|
7217
7415
|
});
|
|
7416
|
+
var workspaceProfileSchema = z6.union([
|
|
7417
|
+
workspaceLeafProfileSchema,
|
|
7418
|
+
workspaceCompositeProfileSchema
|
|
7419
|
+
]);
|
|
7218
7420
|
var workspaceRegistrySchema = z6.object({
|
|
7219
7421
|
sources: z6.array(z6.string().min(1)).optional(),
|
|
7220
7422
|
ttlSeconds: z6.number().int().positive().optional()
|
|
@@ -7269,12 +7471,12 @@ var workspaceConfigSchema = z6.discriminatedUnion("schemaVersion", [
|
|
|
7269
7471
|
workspaceConfigV2Schema
|
|
7270
7472
|
]);
|
|
7271
7473
|
function workspaceConfigPath(workspaceRoot) {
|
|
7272
|
-
return
|
|
7474
|
+
return join30(workspaceRoot, ".agentwheel", "config.json");
|
|
7273
7475
|
}
|
|
7274
7476
|
async function readWorkspaceConfig(workspaceRoot) {
|
|
7275
7477
|
const path = workspaceConfigPath(workspaceRoot);
|
|
7276
7478
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
7277
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
7479
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile22(path, "utf8")));
|
|
7278
7480
|
}
|
|
7279
7481
|
async function writeWorkspaceConfig(workspaceRoot, config) {
|
|
7280
7482
|
await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
|
|
@@ -7287,13 +7489,13 @@ function upsertPackage(config, entry) {
|
|
|
7287
7489
|
return workspaceConfigSchema.parse({ ...parsed, packages });
|
|
7288
7490
|
}
|
|
7289
7491
|
function globalWorkspaceConfigPath(globalRoot = homedir4()) {
|
|
7290
|
-
return
|
|
7492
|
+
return join30(globalRoot, ".agentwheel", "config.json");
|
|
7291
7493
|
}
|
|
7292
7494
|
async function findWorkspaceRoot(start = process.cwd()) {
|
|
7293
7495
|
let current = resolve13(start);
|
|
7294
7496
|
while (true) {
|
|
7295
7497
|
if (await pathExists(workspaceConfigPath(current))) return current;
|
|
7296
|
-
const parent =
|
|
7498
|
+
const parent = dirname24(current);
|
|
7297
7499
|
if (parent === current) return resolve13(start);
|
|
7298
7500
|
current = parent;
|
|
7299
7501
|
}
|
|
@@ -7330,9 +7532,12 @@ function resolveConfigPath(path, baseRoot) {
|
|
|
7330
7532
|
function emptyWorkspaceConfig() {
|
|
7331
7533
|
return { schemaVersion: 1, packages: [], registry: {}, trust: {}, profiles: {}, agents: {} };
|
|
7332
7534
|
}
|
|
7535
|
+
function isCompositeWorkspaceProfile(profile) {
|
|
7536
|
+
return "members" in profile && Array.isArray(profile.members);
|
|
7537
|
+
}
|
|
7333
7538
|
async function readConfigPath(path) {
|
|
7334
7539
|
if (!await pathExists(path)) return emptyWorkspaceConfig();
|
|
7335
|
-
return workspaceConfigSchema.parse(JSON.parse(await
|
|
7540
|
+
return workspaceConfigSchema.parse(JSON.parse(await readFile22(path, "utf8")));
|
|
7336
7541
|
}
|
|
7337
7542
|
function mergeWorkspaceTrust(global, project) {
|
|
7338
7543
|
return {
|
|
@@ -7347,18 +7552,18 @@ function sortedUnique2(values) {
|
|
|
7347
7552
|
}
|
|
7348
7553
|
|
|
7349
7554
|
// src/lifecycle/customization.ts
|
|
7350
|
-
import { appendFile, cp as cp6, mkdir as
|
|
7351
|
-
import { dirname as
|
|
7555
|
+
import { appendFile, cp as cp6, mkdir as mkdir19, rm as rm9 } from "fs/promises";
|
|
7556
|
+
import { dirname as dirname26, join as join33 } from "path";
|
|
7352
7557
|
|
|
7353
7558
|
// src/resolve/graph.ts
|
|
7354
7559
|
import { createHash as createHash8 } from "crypto";
|
|
7355
|
-
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as
|
|
7560
|
+
import { mkdtemp as mkdtemp4, readdir as readdir6, readFile as readFile25, stat as stat10 } from "fs/promises";
|
|
7356
7561
|
import { tmpdir as tmpdir5 } from "os";
|
|
7357
|
-
import { basename as
|
|
7562
|
+
import { basename as basename20, extname as extname4, join as join32 } from "path";
|
|
7358
7563
|
|
|
7359
7564
|
// src/model/workspace-composition.ts
|
|
7360
7565
|
import { createHash as createHash7 } from "crypto";
|
|
7361
|
-
import { readFile as
|
|
7566
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
7362
7567
|
import { z as z7 } from "zod";
|
|
7363
7568
|
var selectionSourceConfigSchema = z7.object({
|
|
7364
7569
|
schemaVersion: z7.literal(2),
|
|
@@ -7377,7 +7582,7 @@ async function resolveSelectionImport(sourceRoot, sourceDriver, selection) {
|
|
|
7377
7582
|
}
|
|
7378
7583
|
let raw;
|
|
7379
7584
|
try {
|
|
7380
|
-
raw = JSON.parse(await
|
|
7585
|
+
raw = JSON.parse(await readFile23(path, "utf8"));
|
|
7381
7586
|
} catch (error) {
|
|
7382
7587
|
const message = error instanceof Error ? error.message : String(error);
|
|
7383
7588
|
throw new Error(`Selection import '${parsedSelection.export}' cannot parse ${path}: ${message}`);
|
|
@@ -7479,9 +7684,9 @@ import { homedir as homedir6 } from "os";
|
|
|
7479
7684
|
import { resolve as resolve15 } from "path";
|
|
7480
7685
|
|
|
7481
7686
|
// src/registry/client.ts
|
|
7482
|
-
import { readFile as
|
|
7687
|
+
import { readFile as readFile24, rm as rm8, stat as stat9 } from "fs/promises";
|
|
7483
7688
|
import { homedir as homedir5 } from "os";
|
|
7484
|
-
import { dirname as
|
|
7689
|
+
import { dirname as dirname25, join as join31, resolve as resolve14 } from "path";
|
|
7485
7690
|
import { fileURLToPath } from "url";
|
|
7486
7691
|
|
|
7487
7692
|
// src/model/registry.ts
|
|
@@ -7585,7 +7790,7 @@ var RegistryClient = class {
|
|
|
7585
7790
|
}
|
|
7586
7791
|
async readCache() {
|
|
7587
7792
|
if (!await pathExists(this.cachePath)) return void 0;
|
|
7588
|
-
return registryCacheSchema.parse(JSON.parse(await
|
|
7793
|
+
return registryCacheSchema.parse(JSON.parse(await readFile24(this.cachePath, "utf8")));
|
|
7589
7794
|
}
|
|
7590
7795
|
isExpired(cache, ttlMs) {
|
|
7591
7796
|
return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
|
|
@@ -7604,10 +7809,10 @@ var RegistryClient = class {
|
|
|
7604
7809
|
if (await pathExists(filePath)) {
|
|
7605
7810
|
const fullPath = resolve14(filePath);
|
|
7606
7811
|
const stats = await stat9(fullPath);
|
|
7607
|
-
return
|
|
7812
|
+
return readFile24(stats.isDirectory() ? join31(fullPath, "index.json") : fullPath, "utf8");
|
|
7608
7813
|
}
|
|
7609
|
-
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot:
|
|
7610
|
-
return
|
|
7814
|
+
const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join31(dirname25(this.cachePath), "registry-repos") }));
|
|
7815
|
+
return readFile24(join31(resolved.resolvedPath, "index.json"), "utf8");
|
|
7611
7816
|
}
|
|
7612
7817
|
warnCompatibility(entries) {
|
|
7613
7818
|
for (const entry of entries) {
|
|
@@ -7645,7 +7850,7 @@ function mergeIndexes(indexes) {
|
|
|
7645
7850
|
return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
7646
7851
|
}
|
|
7647
7852
|
function defaultRegistryCachePath() {
|
|
7648
|
-
return
|
|
7853
|
+
return join31(homedir5(), ".agentwheel", "registry-cache.json");
|
|
7649
7854
|
}
|
|
7650
7855
|
function sameSources(a, b) {
|
|
7651
7856
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
@@ -7805,97 +8010,11 @@ function normalizeLiteralProviderSpec(source, prefix) {
|
|
|
7805
8010
|
return `${prefix}${spec}`;
|
|
7806
8011
|
}
|
|
7807
8012
|
|
|
7808
|
-
// src/resolve/semver.ts
|
|
7809
|
-
var semverPattern = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
7810
|
-
function parseSemver(value) {
|
|
7811
|
-
const match = semverPattern.exec(value.trim());
|
|
7812
|
-
if (!match) return void 0;
|
|
7813
|
-
return {
|
|
7814
|
-
major: Number(match[1]),
|
|
7815
|
-
minor: Number(match[2]),
|
|
7816
|
-
patch: Number(match[3]),
|
|
7817
|
-
prerelease: match[4]
|
|
7818
|
-
};
|
|
7819
|
-
}
|
|
7820
|
-
function satisfiesVersionRange(version, range) {
|
|
7821
|
-
if (!range || range.trim() === "" || range.trim() === "*") return true;
|
|
7822
|
-
const parsedVersion = parseSemver(version);
|
|
7823
|
-
const trimmed = range.trim();
|
|
7824
|
-
if (!parsedVersion) {
|
|
7825
|
-
return trimmed === "*" || trimmed === version;
|
|
7826
|
-
}
|
|
7827
|
-
const comparators = parseRange(trimmed);
|
|
7828
|
-
if (!comparators) return trimmed === version;
|
|
7829
|
-
return comparators.every((comparator) => compareWith(parsedVersion, comparator));
|
|
7830
|
-
}
|
|
7831
|
-
function semverMajorOrVersion(version) {
|
|
7832
|
-
const parsed = parseSemver(version);
|
|
7833
|
-
return parsed ? String(parsed.major) : version;
|
|
7834
|
-
}
|
|
7835
|
-
function parseRange(range) {
|
|
7836
|
-
if (range === "*") return [];
|
|
7837
|
-
if (range.startsWith("^")) {
|
|
7838
|
-
const base = parseSemver(range.slice(1));
|
|
7839
|
-
if (!base) return void 0;
|
|
7840
|
-
return [
|
|
7841
|
-
{ op: ">=", version: base },
|
|
7842
|
-
{ op: "<", version: caretUpperBound(base) }
|
|
7843
|
-
];
|
|
7844
|
-
}
|
|
7845
|
-
if (range.startsWith("~")) {
|
|
7846
|
-
const base = parseSemver(range.slice(1));
|
|
7847
|
-
if (!base) return void 0;
|
|
7848
|
-
return [
|
|
7849
|
-
{ op: ">=", version: base },
|
|
7850
|
-
{ op: "<", version: { major: base.major, minor: base.minor + 1, patch: 0 } }
|
|
7851
|
-
];
|
|
7852
|
-
}
|
|
7853
|
-
const parts = range.split(/\s+/).filter(Boolean);
|
|
7854
|
-
const comparators = [];
|
|
7855
|
-
for (const part of parts) {
|
|
7856
|
-
const match = /^(>=|<=|>|<|=)?(.+)$/.exec(part);
|
|
7857
|
-
if (!match) return void 0;
|
|
7858
|
-
const version = parseSemver(match[2] ?? "");
|
|
7859
|
-
if (!version) return void 0;
|
|
7860
|
-
comparators.push({ op: match[1] ?? "=", version });
|
|
7861
|
-
}
|
|
7862
|
-
return comparators;
|
|
7863
|
-
}
|
|
7864
|
-
function caretUpperBound(version) {
|
|
7865
|
-
if (version.major > 0) return { major: version.major + 1, minor: 0, patch: 0 };
|
|
7866
|
-
if (version.minor > 0) return { major: 0, minor: version.minor + 1, patch: 0 };
|
|
7867
|
-
return { major: 0, minor: 0, patch: version.patch + 1 };
|
|
7868
|
-
}
|
|
7869
|
-
function compareWith(version, comparator) {
|
|
7870
|
-
const order = compareSemver(version, comparator.version);
|
|
7871
|
-
switch (comparator.op) {
|
|
7872
|
-
case "=":
|
|
7873
|
-
return order === 0;
|
|
7874
|
-
case ">":
|
|
7875
|
-
return order > 0;
|
|
7876
|
-
case ">=":
|
|
7877
|
-
return order >= 0;
|
|
7878
|
-
case "<":
|
|
7879
|
-
return order < 0;
|
|
7880
|
-
case "<=":
|
|
7881
|
-
return order <= 0;
|
|
7882
|
-
}
|
|
7883
|
-
}
|
|
7884
|
-
function compareSemver(a, b) {
|
|
7885
|
-
for (const key of ["major", "minor", "patch"]) {
|
|
7886
|
-
if (a[key] !== b[key]) return a[key] > b[key] ? 1 : -1;
|
|
7887
|
-
}
|
|
7888
|
-
if (a.prerelease === b.prerelease) return 0;
|
|
7889
|
-
if (!a.prerelease) return 1;
|
|
7890
|
-
if (!b.prerelease) return -1;
|
|
7891
|
-
return a.prerelease.localeCompare(b.prerelease);
|
|
7892
|
-
}
|
|
7893
|
-
|
|
7894
8013
|
// src/resolve/graph.ts
|
|
7895
8014
|
var cacheLocks = /* @__PURE__ */ new Map();
|
|
7896
8015
|
async function resolveDependencyGraph(roots, options) {
|
|
7897
8016
|
if (roots.length === 0) throw new Error("At least one graph root is required.");
|
|
7898
|
-
const graphRoot = await mkdtemp4(
|
|
8017
|
+
const graphRoot = await mkdtemp4(join32(tmpdir5(), "agentwheel-graph-"));
|
|
7899
8018
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
7900
8019
|
const nodesByKey = /* @__PURE__ */ new Map();
|
|
7901
8020
|
const rootResults = [];
|
|
@@ -7908,6 +8027,7 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
7908
8027
|
select: root.select,
|
|
7909
8028
|
selection: root.selection,
|
|
7910
8029
|
mode: root.mode ?? "pinned",
|
|
8030
|
+
version: root.version,
|
|
7911
8031
|
ref: root.ref,
|
|
7912
8032
|
declaringPackageRoot: options.workspaceRoot,
|
|
7913
8033
|
requiredBy: `workspace:${rootId}`,
|
|
@@ -8414,7 +8534,7 @@ async function collectIncludeNeeds(artifact, artifactsByRelativePath) {
|
|
|
8414
8534
|
const file = stack.shift();
|
|
8415
8535
|
if (scanned.has(file)) continue;
|
|
8416
8536
|
scanned.add(file);
|
|
8417
|
-
const content = await
|
|
8537
|
+
const content = await readFile25(file, "utf8");
|
|
8418
8538
|
for (const include of extractOpenPackIncludeSelectors(content)) {
|
|
8419
8539
|
await collectIncludeSelector(include.raw, include.optional, artifactsByRelativePath, scanned, stack, needs);
|
|
8420
8540
|
}
|
|
@@ -8457,7 +8577,7 @@ async function listMarkdownFiles2(root) {
|
|
|
8457
8577
|
const out = [];
|
|
8458
8578
|
async function walk2(dir) {
|
|
8459
8579
|
for (const entry of (await readdir6(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
8460
|
-
const full =
|
|
8580
|
+
const full = join32(dir, entry.name);
|
|
8461
8581
|
if (entry.isDirectory()) {
|
|
8462
8582
|
await walk2(full);
|
|
8463
8583
|
} else if (entry.isFile() && extname4(entry.name).toLowerCase() === ".md") {
|
|
@@ -8492,7 +8612,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
8492
8612
|
const promise = (async () => {
|
|
8493
8613
|
const driver = getSourceDriver(normalized.driver);
|
|
8494
8614
|
const resolved = await driver.resolve(normalized.source, {
|
|
8495
|
-
cacheRoot: options.cacheRoot ??
|
|
8615
|
+
cacheRoot: options.cacheRoot ?? join32(options.workspaceRoot, ".agentwheel", "cache"),
|
|
8496
8616
|
mode,
|
|
8497
8617
|
ref: refOverride ?? normalized.requestedRef,
|
|
8498
8618
|
frozenLock: hardLockedCheckout
|
|
@@ -8502,7 +8622,7 @@ async function fetchPackage(normalized, mode, options, fetchCache, refOverride)
|
|
|
8502
8622
|
const exported = await driver.export(translated);
|
|
8503
8623
|
const manifest = await readPackageManifest(exported.resolvedPath);
|
|
8504
8624
|
const artifacts = await driver.list(exported);
|
|
8505
|
-
const name = manifest?.name ?? exported.packageName ??
|
|
8625
|
+
const name = manifest?.name ?? exported.packageName ?? basename20(exported.resolvedPath);
|
|
8506
8626
|
const version = manifest?.version ?? exported.packageVersion ?? "0.0.0";
|
|
8507
8627
|
const sourceHash = exported.sourceHash ?? await hashPath(exported.resolvedPath);
|
|
8508
8628
|
return {
|
|
@@ -8609,8 +8729,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
8609
8729
|
async function withCachePathLock(path, fn) {
|
|
8610
8730
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
8611
8731
|
let release = () => void 0;
|
|
8612
|
-
const current = previous.then(() => new Promise((
|
|
8613
|
-
release =
|
|
8732
|
+
const current = previous.then(() => new Promise((resolve23) => {
|
|
8733
|
+
release = resolve23;
|
|
8614
8734
|
}));
|
|
8615
8735
|
cacheLocks.set(path, current);
|
|
8616
8736
|
await previous;
|
|
@@ -8699,8 +8819,8 @@ async function mapLimit(items, limit, fn) {
|
|
|
8699
8819
|
|
|
8700
8820
|
// src/lifecycle/customization.ts
|
|
8701
8821
|
async function remember(workspaceRoot, runtime, text) {
|
|
8702
|
-
const overlayPath =
|
|
8703
|
-
await
|
|
8822
|
+
const overlayPath = join33(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
|
|
8823
|
+
await mkdir19(dirname26(overlayPath), { recursive: true });
|
|
8704
8824
|
await appendFile(overlayPath, `${text.trim()}
|
|
8705
8825
|
`, "utf8");
|
|
8706
8826
|
return { overlayPath };
|
|
@@ -8723,8 +8843,8 @@ async function ejectArtifact(workspaceRoot, item) {
|
|
|
8723
8843
|
throw new Error(`Artifact not found: ${item}`);
|
|
8724
8844
|
}
|
|
8725
8845
|
const ejectedIdentity = parsed.packageIdentity === parsed.packageName ? parsed.packageIdentity : candidate.nodeId === parsed.packageIdentity ? candidate.nodeId : `${candidate.packageName}@${candidate.packageVersion}`;
|
|
8726
|
-
const ejectedPath =
|
|
8727
|
-
await
|
|
8846
|
+
const ejectedPath = join33(workspaceRoot, ".agentwheel", "ejected", ...ejectedIdentity.split("/"), parsed.type, parsed.name);
|
|
8847
|
+
await mkdir19(dirname26(ejectedPath), { recursive: true });
|
|
8728
8848
|
await rm9(ejectedPath, { recursive: true, force: true });
|
|
8729
8849
|
await cp6(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
|
|
8730
8850
|
return {
|
|
@@ -8766,7 +8886,7 @@ async function stageEjectCandidate(workspaceRoot, pkg) {
|
|
|
8766
8886
|
const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
|
|
8767
8887
|
const bundle = await stageSource(driver, normalized.source, {
|
|
8768
8888
|
adapter,
|
|
8769
|
-
cacheRoot:
|
|
8889
|
+
cacheRoot: join33(workspaceRoot, ".agentwheel", "cache"),
|
|
8770
8890
|
mode: pkg.mode,
|
|
8771
8891
|
ref: normalized.requestedRef ?? pkg.requestedRef
|
|
8772
8892
|
});
|
|
@@ -8817,8 +8937,8 @@ import { rm as rm10 } from "fs/promises";
|
|
|
8817
8937
|
|
|
8818
8938
|
// src/lifecycle/source-plan.ts
|
|
8819
8939
|
import { createHash as createHash10 } from "crypto";
|
|
8820
|
-
import { mkdir as
|
|
8821
|
-
import { dirname as
|
|
8940
|
+
import { mkdir as mkdir21 } from "fs/promises";
|
|
8941
|
+
import { dirname as dirname28, join as join36 } from "path";
|
|
8822
8942
|
|
|
8823
8943
|
// src/resolve/graph-diff.ts
|
|
8824
8944
|
function diffGraphLocks(previous, next) {
|
|
@@ -8980,11 +9100,11 @@ function formatSelectionImport(root) {
|
|
|
8980
9100
|
|
|
8981
9101
|
// src/resolve/render.ts
|
|
8982
9102
|
import { createHash as createHash9 } from "crypto";
|
|
8983
|
-
import { readFile as
|
|
9103
|
+
import { readFile as readFile26, mkdtemp as mkdtemp5 } from "fs/promises";
|
|
8984
9104
|
import { tmpdir as tmpdir6 } from "os";
|
|
8985
|
-
import { join as
|
|
9105
|
+
import { join as join34 } from "path";
|
|
8986
9106
|
async function renderGraphForTarget(graph, targetContext = {}) {
|
|
8987
|
-
const root = await mkdtemp5(
|
|
9107
|
+
const root = await mkdtemp5(join34(tmpdir6(), "agentwheel-render-"));
|
|
8988
9108
|
const artifacts = [];
|
|
8989
9109
|
const stagedNodes = /* @__PURE__ */ new Map();
|
|
8990
9110
|
const includeEdges = /* @__PURE__ */ new Map();
|
|
@@ -9056,7 +9176,8 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
9056
9176
|
const selectedArtifacts = filterArtifactsBySelection(expandedArtifacts, rawNode.node.selected);
|
|
9057
9177
|
const runtimeSelectedSet = new Set(normalizeArtifactSelectors(rawNode.node.selected) ?? []);
|
|
9058
9178
|
const runtimeArtifacts = targetContext.adapter ? filterArtifactsByRuntime2(selectedArtifacts, targetContext.adapter.name, runtimeSelectedSet) : selectedArtifacts;
|
|
9059
|
-
const
|
|
9179
|
+
const claudeRenderedArtifacts = await renderClaudeSubagents(runtimeArtifacts, staged.root, targetContext.adapter);
|
|
9180
|
+
const codexRenderedArtifacts = await renderCodexSubagents(claudeRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9060
9181
|
const openClawRenderedArtifacts = await renderOpenClawSubagents(codexRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9061
9182
|
const runtimeRenderedArtifacts = await renderCopilotArtifacts(openClawRenderedArtifacts, staged.root, targetContext.adapter);
|
|
9062
9183
|
const renderedArtifacts = targetContext.workspaceRoot && targetContext.adapter ? await applyCustomizations(runtimeRenderedArtifacts, {
|
|
@@ -9105,7 +9226,7 @@ async function artifactContentMap(artifacts) {
|
|
|
9105
9226
|
const out = /* @__PURE__ */ new Map();
|
|
9106
9227
|
for (const artifact of artifacts) {
|
|
9107
9228
|
if (artifact.kind !== "file") continue;
|
|
9108
|
-
out.set(artifact.relativePath.replaceAll("\\", "/"), await
|
|
9229
|
+
out.set(artifact.relativePath.replaceAll("\\", "/"), await readFile26(artifact.stagedPath ?? artifact.sourcePath, "utf8"));
|
|
9109
9230
|
}
|
|
9110
9231
|
return out;
|
|
9111
9232
|
}
|
|
@@ -9368,9 +9489,9 @@ function lockArtifactFor(artifact) {
|
|
|
9368
9489
|
}
|
|
9369
9490
|
|
|
9370
9491
|
// src/lifecycle/trust.ts
|
|
9371
|
-
import { mkdir as
|
|
9492
|
+
import { mkdir as mkdir20, readFile as readFile27 } from "fs/promises";
|
|
9372
9493
|
import { homedir as homedir7 } from "os";
|
|
9373
|
-
import { dirname as
|
|
9494
|
+
import { dirname as dirname27, join as join35 } from "path";
|
|
9374
9495
|
import { z as z9 } from "zod";
|
|
9375
9496
|
var trustStoreSchema = z9.object({
|
|
9376
9497
|
version: z9.literal(1),
|
|
@@ -9444,14 +9565,14 @@ function sortedUnique5(values) {
|
|
|
9444
9565
|
}
|
|
9445
9566
|
async function readTrustStore(path) {
|
|
9446
9567
|
if (!await pathExists(path)) return { version: 1, acceptedSources: [] };
|
|
9447
|
-
return trustStoreSchema.parse(JSON.parse(await
|
|
9568
|
+
return trustStoreSchema.parse(JSON.parse(await readFile27(path, "utf8")));
|
|
9448
9569
|
}
|
|
9449
9570
|
async function writeTrustStore(path, store) {
|
|
9450
|
-
await
|
|
9571
|
+
await mkdir20(dirname27(path), { recursive: true });
|
|
9451
9572
|
await writeJsonAtomic(path, trustStoreSchema.parse(store));
|
|
9452
9573
|
}
|
|
9453
9574
|
function defaultTrustStorePath() {
|
|
9454
|
-
return process.env.AGENTWHEEL_TRUST_STORE ??
|
|
9575
|
+
return process.env.AGENTWHEEL_TRUST_STORE ?? join35(homedir7(), ".agentwheel", "trust.json");
|
|
9455
9576
|
}
|
|
9456
9577
|
|
|
9457
9578
|
// src/lifecycle/ownership.ts
|
|
@@ -9623,7 +9744,7 @@ async function createGraphSourcePlan(options) {
|
|
|
9623
9744
|
const registryClient = new RegistryClient({ workspaceRoot, offline: lockMode, offlineLabel: lockLabel, warn });
|
|
9624
9745
|
const graph = await resolveDependencyGraph(options.roots, {
|
|
9625
9746
|
workspaceRoot,
|
|
9626
|
-
cacheRoot:
|
|
9747
|
+
cacheRoot: join36(workspaceRoot, ".agentwheel", "cache"),
|
|
9627
9748
|
registryClient,
|
|
9628
9749
|
noDeps: options.noDeps,
|
|
9629
9750
|
includeSuggestions: options.includeSuggestions,
|
|
@@ -9729,7 +9850,7 @@ async function readExistingGraphLock(path) {
|
|
|
9729
9850
|
return readGraphLock(path);
|
|
9730
9851
|
}
|
|
9731
9852
|
function pathForGraphLock(workspaceRoot, targetKey2, adapter, targetFingerprint) {
|
|
9732
|
-
return
|
|
9853
|
+
return join36(workspaceRoot, ".agentwheel", "locks", sanitizePathSegment(targetKey2), sanitizePathSegment(adapter), `${targetFingerprint}.graph-lock.json`);
|
|
9733
9854
|
}
|
|
9734
9855
|
function sanitizePathSegment(value) {
|
|
9735
9856
|
return value.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "default";
|
|
@@ -9861,7 +9982,7 @@ function targetLabel(target) {
|
|
|
9861
9982
|
}
|
|
9862
9983
|
|
|
9863
9984
|
// src/runtime/target.ts
|
|
9864
|
-
import { basename as
|
|
9985
|
+
import { basename as basename21, dirname as dirname29, join as join37, resolve as resolve17 } from "path";
|
|
9865
9986
|
var runtimeMarkers = [
|
|
9866
9987
|
{ adapter: "openclaw", dirs: [".openclaw", ".clawdbot", ".moltbot"] },
|
|
9867
9988
|
{ adapter: "claude", dirs: [".claude"] },
|
|
@@ -9920,6 +10041,9 @@ async function resolveProfileRuntimeTargets(request) {
|
|
|
9920
10041
|
if (!profile) {
|
|
9921
10042
|
throw new Error(`Unknown profile: ${request.profile}`);
|
|
9922
10043
|
}
|
|
10044
|
+
if (isCompositeWorkspaceProfile(profile)) {
|
|
10045
|
+
throw new Error(`Profile '${request.profile}' is composite and has no direct runtime targets.`);
|
|
10046
|
+
}
|
|
9923
10047
|
return profile.runtimes.map((runtime) => resolveProfileRuntimeTarget(runtime, config, workspaceRoot, request.installationType));
|
|
9924
10048
|
}
|
|
9925
10049
|
function resolveProfileRuntimeTarget(runtime, config, workspaceRoot, installationType) {
|
|
@@ -9978,9 +10102,9 @@ async function detectRuntimeTargets(cwd = process.cwd(), adapterFilter) {
|
|
|
9978
10102
|
for (const marker of runtimeMarkers) {
|
|
9979
10103
|
if (adapterFilter && marker.adapter !== adapterFilter) continue;
|
|
9980
10104
|
for (const dir of marker.dirs) {
|
|
9981
|
-
if (
|
|
9982
|
-
matches.push({ adapter: marker.adapter, targetRoot:
|
|
9983
|
-
} else if (await pathExists(
|
|
10105
|
+
if (basename21(root) === dir) {
|
|
10106
|
+
matches.push({ adapter: marker.adapter, targetRoot: dirname29(root) });
|
|
10107
|
+
} else if (await pathExists(join37(root, dir))) {
|
|
9984
10108
|
matches.push({ adapter: marker.adapter, targetRoot: root });
|
|
9985
10109
|
}
|
|
9986
10110
|
}
|
|
@@ -10022,7 +10146,7 @@ function dedupeTargets(matches) {
|
|
|
10022
10146
|
function runtimeScanRoot(request) {
|
|
10023
10147
|
const root = resolve17(request.targetRoot ?? request.cwd ?? process.cwd());
|
|
10024
10148
|
if (request.targetRoot) return root;
|
|
10025
|
-
return runtimeMarkers.some((marker) => marker.dirs.includes(
|
|
10149
|
+
return runtimeMarkers.some((marker) => marker.dirs.includes(basename21(root))) ? dirname29(root) : root;
|
|
10026
10150
|
}
|
|
10027
10151
|
|
|
10028
10152
|
// src/lifecycle/profile.ts
|
|
@@ -10032,6 +10156,9 @@ async function syncProfile(options) {
|
|
|
10032
10156
|
if (!profile) {
|
|
10033
10157
|
throw new Error(`Unknown profile: ${options.profile}`);
|
|
10034
10158
|
}
|
|
10159
|
+
if (isCompositeWorkspaceProfile(profile)) {
|
|
10160
|
+
throw new Error(`Composite profile '${options.profile}' must be executed through member delegation.`);
|
|
10161
|
+
}
|
|
10035
10162
|
const packages = options.source ? [await packageFromSource(options.source, options)] : config.packages;
|
|
10036
10163
|
if (packages.length === 0) {
|
|
10037
10164
|
throw new Error("Profile sync needs a source argument or configured packages.");
|
|
@@ -10059,6 +10186,7 @@ async function syncProfile(options) {
|
|
|
10059
10186
|
rootId: pkg.name,
|
|
10060
10187
|
source: pkg.source,
|
|
10061
10188
|
mode: options.mode ?? pkg.mode,
|
|
10189
|
+
version: pkg.version,
|
|
10062
10190
|
ref: pkg.requestedRef,
|
|
10063
10191
|
select: pkg.selection ? void 0 : selected ?? normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
10064
10192
|
selection: pkg.selection,
|
|
@@ -10320,9 +10448,9 @@ function shellQuoteArg(value) {
|
|
|
10320
10448
|
}
|
|
10321
10449
|
|
|
10322
10450
|
// src/cli/update-check.ts
|
|
10323
|
-
import { mkdir as
|
|
10451
|
+
import { mkdir as mkdir22, readFile as readFile28, writeFile as writeFile20 } from "fs/promises";
|
|
10324
10452
|
import { homedir as homedir8 } from "os";
|
|
10325
|
-
import { dirname as
|
|
10453
|
+
import { dirname as dirname30, join as join38 } from "path";
|
|
10326
10454
|
var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10327
10455
|
var DEFAULT_TIMEOUT_MS = 300;
|
|
10328
10456
|
var REGISTRY_URL = "https://registry.npmjs.org/agentwheel";
|
|
@@ -10330,7 +10458,7 @@ async function maybeCheckForUpdate(options) {
|
|
|
10330
10458
|
if (isDisabled(options)) return;
|
|
10331
10459
|
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
10332
10460
|
const ttlMs = options.ttlMs ?? DEFAULT_TTL_MS;
|
|
10333
|
-
const cachePath = options.cachePath ??
|
|
10461
|
+
const cachePath = options.cachePath ?? join38(homedir8(), ".agentwheel", "update-check.json");
|
|
10334
10462
|
try {
|
|
10335
10463
|
const cached = await readCache(cachePath);
|
|
10336
10464
|
if (cached && now.getTime() - Date.parse(cached.checkedAt) < ttlMs) {
|
|
@@ -10367,7 +10495,7 @@ async function fetchLatestVersion(fetchImpl, timeoutMs) {
|
|
|
10367
10495
|
}
|
|
10368
10496
|
async function readCache(path) {
|
|
10369
10497
|
try {
|
|
10370
|
-
const parsed = JSON.parse(await
|
|
10498
|
+
const parsed = JSON.parse(await readFile28(path, "utf8"));
|
|
10371
10499
|
if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "string") return void 0;
|
|
10372
10500
|
return { checkedAt: parsed.checkedAt, latest: parsed.latest };
|
|
10373
10501
|
} catch {
|
|
@@ -10375,8 +10503,8 @@ async function readCache(path) {
|
|
|
10375
10503
|
}
|
|
10376
10504
|
}
|
|
10377
10505
|
async function writeCache(path, cache) {
|
|
10378
|
-
await
|
|
10379
|
-
await
|
|
10506
|
+
await mkdir22(dirname30(path), { recursive: true });
|
|
10507
|
+
await writeFile20(path, `${JSON.stringify(cache, null, 2)}
|
|
10380
10508
|
`, "utf8");
|
|
10381
10509
|
}
|
|
10382
10510
|
function warnIfNewer(latest, current, stderr = process.stderr) {
|
|
@@ -10539,13 +10667,13 @@ function isCrossPackageSelector(value) {
|
|
|
10539
10667
|
}
|
|
10540
10668
|
|
|
10541
10669
|
// src/model/package-migrate.ts
|
|
10542
|
-
import { readFile as
|
|
10543
|
-
import { join as
|
|
10670
|
+
import { readFile as readFile29, rename as rename4, writeFile as writeFile21 } from "fs/promises";
|
|
10671
|
+
import { join as join40, resolve as resolve19 } from "path";
|
|
10544
10672
|
import { applyEdits, modify, parse as parse5 } from "jsonc-parser";
|
|
10545
10673
|
async function migratePackageManifest(root) {
|
|
10546
10674
|
const packageRoot = resolve19(root);
|
|
10547
10675
|
for (const name of openPackManifestNames) {
|
|
10548
|
-
const path =
|
|
10676
|
+
const path = join40(packageRoot, name);
|
|
10549
10677
|
if (await pathExists(path)) {
|
|
10550
10678
|
return { changed: false, to: path, message: `Package already uses ${name}.` };
|
|
10551
10679
|
}
|
|
@@ -10554,18 +10682,18 @@ async function migratePackageManifest(root) {
|
|
|
10554
10682
|
if (!legacyName) {
|
|
10555
10683
|
throw new Error(`No legacy package manifest found at ${packageRoot}`);
|
|
10556
10684
|
}
|
|
10557
|
-
const from =
|
|
10685
|
+
const from = join40(packageRoot, legacyName);
|
|
10558
10686
|
const toName = legacyName.endsWith(".jsonc") ? "openpack.jsonc" : "openpack.json";
|
|
10559
|
-
const to =
|
|
10560
|
-
const content = await
|
|
10687
|
+
const to = join40(packageRoot, toName);
|
|
10688
|
+
const content = await readFile29(from, "utf8");
|
|
10561
10689
|
const updated = updateSchemaVersion(content);
|
|
10562
10690
|
await rename4(from, to);
|
|
10563
|
-
await
|
|
10691
|
+
await writeFile21(to, updated, "utf8");
|
|
10564
10692
|
return { changed: true, from, to, message: `Migrated ${legacyName} to ${toName}.` };
|
|
10565
10693
|
}
|
|
10566
10694
|
async function firstExistingLegacyManifest(root) {
|
|
10567
10695
|
for (const name of legacyPackageManifestNames) {
|
|
10568
|
-
if (await pathExists(
|
|
10696
|
+
if (await pathExists(join40(root, name))) return name;
|
|
10569
10697
|
}
|
|
10570
10698
|
return void 0;
|
|
10571
10699
|
}
|
|
@@ -10583,25 +10711,613 @@ function updateSchemaVersion(content) {
|
|
|
10583
10711
|
|
|
10584
10712
|
// src/cli/version.ts
|
|
10585
10713
|
import { readFileSync } from "fs";
|
|
10586
|
-
import { dirname as
|
|
10714
|
+
import { dirname as dirname31, join as join41 } from "path";
|
|
10587
10715
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10588
10716
|
var FALLBACK_VERSION = "0.0.0";
|
|
10589
10717
|
function resolveCliVersion() {
|
|
10590
|
-
let dir =
|
|
10718
|
+
let dir = dirname31(fileURLToPath2(import.meta.url));
|
|
10591
10719
|
while (true) {
|
|
10592
10720
|
try {
|
|
10593
|
-
const pkg = JSON.parse(readFileSync(
|
|
10721
|
+
const pkg = JSON.parse(readFileSync(join41(dir, "package.json"), "utf8"));
|
|
10594
10722
|
if (pkg.name === "agentwheel" && typeof pkg.version === "string") {
|
|
10595
10723
|
return pkg.version;
|
|
10596
10724
|
}
|
|
10597
10725
|
} catch {
|
|
10598
10726
|
}
|
|
10599
|
-
const parent =
|
|
10727
|
+
const parent = dirname31(dir);
|
|
10600
10728
|
if (parent === dir) return FALLBACK_VERSION;
|
|
10601
10729
|
dir = parent;
|
|
10602
10730
|
}
|
|
10603
10731
|
}
|
|
10604
10732
|
|
|
10733
|
+
// src/version/policy.ts
|
|
10734
|
+
import { execFile as execFile5 } from "child_process";
|
|
10735
|
+
import { readFile as readFile30 } from "fs/promises";
|
|
10736
|
+
import { join as join42, resolve as resolve20 } from "path";
|
|
10737
|
+
import { promisify as promisify5 } from "util";
|
|
10738
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
10739
|
+
import { z as z10 } from "zod";
|
|
10740
|
+
var execFileAsync5 = promisify5(execFile5);
|
|
10741
|
+
var DEFAULT_VERSION_REFRESH_TTL_SECONDS = 86400;
|
|
10742
|
+
var cachedVersionSchema = z10.object({
|
|
10743
|
+
version: z10.string().min(1),
|
|
10744
|
+
ref: z10.string().min(1)
|
|
10745
|
+
});
|
|
10746
|
+
var versionCacheEntrySchema = z10.object({
|
|
10747
|
+
checkedAt: z10.string().datetime(),
|
|
10748
|
+
versions: z10.array(cachedVersionSchema)
|
|
10749
|
+
});
|
|
10750
|
+
var versionCacheSchema = z10.object({
|
|
10751
|
+
schemaVersion: z10.literal(1),
|
|
10752
|
+
sources: z10.record(z10.string(), versionCacheEntrySchema)
|
|
10753
|
+
});
|
|
10754
|
+
async function discoverPackageVersions(pkg, workspaceRoot, options = {}) {
|
|
10755
|
+
const now = (options.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
10756
|
+
const ttlSeconds = options.ttlSeconds ?? DEFAULT_VERSION_REFRESH_TTL_SECONDS;
|
|
10757
|
+
const cachePath = versionCachePath(workspaceRoot);
|
|
10758
|
+
const cache = await readVersionCache(cachePath);
|
|
10759
|
+
const cached = cache.sources[pkg.source];
|
|
10760
|
+
const cachedAgeMs = cached ? now.getTime() - new Date(cached.checkedAt).getTime() : Number.POSITIVE_INFINITY;
|
|
10761
|
+
const cachedFresh = cachedAgeMs <= ttlSeconds * 1e3;
|
|
10762
|
+
if (options.offline || cached && cachedFresh && !options.forceRefresh) {
|
|
10763
|
+
return availabilityFromVersions(pkg, cached?.versions ?? [], {
|
|
10764
|
+
checkedAt: cached?.checkedAt ?? null,
|
|
10765
|
+
stale: !cachedFresh,
|
|
10766
|
+
refreshed: false,
|
|
10767
|
+
error: !cached && options.offline ? "No cached version index is available offline." : void 0
|
|
10768
|
+
});
|
|
10769
|
+
}
|
|
10770
|
+
try {
|
|
10771
|
+
const versions = await discoverVersionsFromSource(pkg, workspaceRoot);
|
|
10772
|
+
const checkedAt = now.toISOString();
|
|
10773
|
+
await writeJsonAtomic(cachePath, {
|
|
10774
|
+
schemaVersion: 1,
|
|
10775
|
+
sources: {
|
|
10776
|
+
...cache.sources,
|
|
10777
|
+
[pkg.source]: { checkedAt, versions }
|
|
10778
|
+
}
|
|
10779
|
+
});
|
|
10780
|
+
return availabilityFromVersions(pkg, versions, {
|
|
10781
|
+
checkedAt,
|
|
10782
|
+
stale: false,
|
|
10783
|
+
refreshed: true
|
|
10784
|
+
});
|
|
10785
|
+
} catch (error) {
|
|
10786
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
10787
|
+
return availabilityFromVersions(pkg, cached?.versions ?? [], {
|
|
10788
|
+
checkedAt: cached?.checkedAt ?? null,
|
|
10789
|
+
stale: true,
|
|
10790
|
+
refreshed: false,
|
|
10791
|
+
error: message
|
|
10792
|
+
});
|
|
10793
|
+
}
|
|
10794
|
+
}
|
|
10795
|
+
async function effectiveTrackingRef(pkg, workspaceRoot, options = {}) {
|
|
10796
|
+
if (pkg.mode !== "tracking" || !pkg.version) return { ref: pkg.requestedRef };
|
|
10797
|
+
const availability = await discoverPackageVersions(pkg, workspaceRoot, options);
|
|
10798
|
+
const driverName = pkg.driver === "local" ? inferSourceDriverName(pkg.source) : pkg.driver;
|
|
10799
|
+
return {
|
|
10800
|
+
ref: driverName === "local" ? pkg.requestedRef : availability.latestAllowedRef ?? pkg.requestedRef,
|
|
10801
|
+
availability
|
|
10802
|
+
};
|
|
10803
|
+
}
|
|
10804
|
+
function availabilityFromVersions(pkg, versions, state) {
|
|
10805
|
+
const sorted = [...versions].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10806
|
+
const policy = pkg.version ?? "*";
|
|
10807
|
+
const latestOverall = sorted[0] ?? null;
|
|
10808
|
+
const latestAllowed = sorted.find((candidate) => satisfiesVersionRange(candidate.version, policy)) ?? null;
|
|
10809
|
+
return {
|
|
10810
|
+
source: pkg.source,
|
|
10811
|
+
policy,
|
|
10812
|
+
checkedAt: state.checkedAt,
|
|
10813
|
+
stale: state.stale,
|
|
10814
|
+
refreshed: state.refreshed,
|
|
10815
|
+
latestAllowed: latestAllowed?.version ?? null,
|
|
10816
|
+
latestAllowedRef: latestAllowed?.ref ?? null,
|
|
10817
|
+
latestOverall: latestOverall?.version ?? null,
|
|
10818
|
+
latestOverallRef: latestOverall?.ref ?? null,
|
|
10819
|
+
versions: sorted,
|
|
10820
|
+
...state.error ? { error: state.error } : {}
|
|
10821
|
+
};
|
|
10822
|
+
}
|
|
10823
|
+
async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
10824
|
+
const driverName = pkg.driver === "local" ? inferSourceDriverName(pkg.source) : pkg.driver;
|
|
10825
|
+
if (driverName === "git") {
|
|
10826
|
+
const tagged = await discoverGitTags(pkg.source, pkg.version);
|
|
10827
|
+
if (tagged.length > 0) return tagged;
|
|
10828
|
+
}
|
|
10829
|
+
if (driverName === "local") {
|
|
10830
|
+
const root = resolve20(workspaceRoot, pkg.source);
|
|
10831
|
+
const manifest2 = await readPackageManifest(root);
|
|
10832
|
+
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
10833
|
+
try {
|
|
10834
|
+
const { stdout } = await execFileAsync5("git", ["-C", root, "remote", "get-url", "origin"]);
|
|
10835
|
+
return uniqueVersions([
|
|
10836
|
+
...await discoverGitTagsFromUrl(stdout.trim(), pkg.version, root),
|
|
10837
|
+
...current
|
|
10838
|
+
]);
|
|
10839
|
+
} catch {
|
|
10840
|
+
return current;
|
|
10841
|
+
}
|
|
10842
|
+
}
|
|
10843
|
+
const driver = getSourceDriver(driverName);
|
|
10844
|
+
const resolved = await driver.resolve(pkg.source, {
|
|
10845
|
+
cacheRoot: join42(workspaceRoot, ".agentwheel", "cache"),
|
|
10846
|
+
mode: "tracking",
|
|
10847
|
+
ref: pkg.requestedRef
|
|
10848
|
+
});
|
|
10849
|
+
const fetched = await driver.fetch(resolved);
|
|
10850
|
+
const manifest = await readPackageManifest(fetched.resolvedPath);
|
|
10851
|
+
const version = manifest?.version ?? fetched.packageVersion;
|
|
10852
|
+
return version ? [{ version, ref: fetched.requestedRef ?? pkg.requestedRef ?? "HEAD" }] : [];
|
|
10853
|
+
}
|
|
10854
|
+
async function discoverGitTags(source, policy) {
|
|
10855
|
+
const url = gitUrlFromSource(source);
|
|
10856
|
+
const localRoot = url.startsWith("/") ? url : void 0;
|
|
10857
|
+
return discoverGitTagsFromUrl(url, policy, localRoot);
|
|
10858
|
+
}
|
|
10859
|
+
async function discoverGitTagsFromUrl(url, policy, localRoot) {
|
|
10860
|
+
const { stdout } = await execFileAsync5("git", ["ls-remote", "--tags", "--refs", url], {
|
|
10861
|
+
maxBuffer: 10 * 1024 * 1024
|
|
10862
|
+
});
|
|
10863
|
+
const byVersion = /* @__PURE__ */ new Map();
|
|
10864
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
10865
|
+
const match = /^[0-9a-f]+\s+refs\/tags\/(.+)$/.exec(line.trim());
|
|
10866
|
+
if (!match) continue;
|
|
10867
|
+
const tag = match[1];
|
|
10868
|
+
if (!parseSemver(tag)) continue;
|
|
10869
|
+
const version = tag.replace(/^v/, "");
|
|
10870
|
+
const incumbent = byVersion.get(version);
|
|
10871
|
+
if (!incumbent || tag.startsWith("v")) byVersion.set(version, { version, ref: tag });
|
|
10872
|
+
}
|
|
10873
|
+
const candidates = [...byVersion.values()].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10874
|
+
const valid = [];
|
|
10875
|
+
let foundOverall = false;
|
|
10876
|
+
let foundAllowed = false;
|
|
10877
|
+
for (const candidate of candidates) {
|
|
10878
|
+
const manifestVersion = await manifestVersionAtRef(url, candidate.ref, localRoot);
|
|
10879
|
+
if (manifestVersion !== candidate.version) continue;
|
|
10880
|
+
valid.push(candidate);
|
|
10881
|
+
foundOverall = true;
|
|
10882
|
+
if (satisfiesVersionRange(candidate.version, policy)) foundAllowed = true;
|
|
10883
|
+
if (foundOverall && foundAllowed) break;
|
|
10884
|
+
}
|
|
10885
|
+
return valid;
|
|
10886
|
+
}
|
|
10887
|
+
function uniqueVersions(versions) {
|
|
10888
|
+
const byVersion = /* @__PURE__ */ new Map();
|
|
10889
|
+
for (const version of versions) {
|
|
10890
|
+
if (!byVersion.has(version.version)) byVersion.set(version.version, version);
|
|
10891
|
+
}
|
|
10892
|
+
return [...byVersion.values()].sort((a, b) => compareSemverStrings(b.version, a.version));
|
|
10893
|
+
}
|
|
10894
|
+
async function manifestVersionAtRef(url, ref, localRoot) {
|
|
10895
|
+
if (localRoot) {
|
|
10896
|
+
for (const name of ["openpack.json", "openpack.jsonc"]) {
|
|
10897
|
+
try {
|
|
10898
|
+
const { stdout } = await execFileAsync5("git", ["-C", localRoot, "show", `${ref}:${name}`], {
|
|
10899
|
+
maxBuffer: 1024 * 1024
|
|
10900
|
+
});
|
|
10901
|
+
const parsed = parseJsonc(stdout);
|
|
10902
|
+
if (typeof parsed?.version === "string") return parsed.version.replace(/^v/, "");
|
|
10903
|
+
} catch {
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
return null;
|
|
10907
|
+
}
|
|
10908
|
+
const repository = githubRepositoryFromUrl(url);
|
|
10909
|
+
if (!repository) return null;
|
|
10910
|
+
for (const name of ["openpack.json", "openpack.jsonc"]) {
|
|
10911
|
+
const response = await fetch(
|
|
10912
|
+
`https://raw.githubusercontent.com/${repository}/${encodeURIComponent(ref)}/${name}`,
|
|
10913
|
+
{ headers: { "user-agent": "agentwheel-version-discovery" } }
|
|
10914
|
+
);
|
|
10915
|
+
if (!response.ok) continue;
|
|
10916
|
+
const parsed = parseJsonc(await response.text());
|
|
10917
|
+
if (typeof parsed?.version === "string") return parsed.version.replace(/^v/, "");
|
|
10918
|
+
}
|
|
10919
|
+
return null;
|
|
10920
|
+
}
|
|
10921
|
+
function githubRepositoryFromUrl(url) {
|
|
10922
|
+
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+\/[^/#]+?)(?:\.git)?$/.exec(url);
|
|
10923
|
+
return match?.[1] ?? null;
|
|
10924
|
+
}
|
|
10925
|
+
function gitUrlFromSource(source) {
|
|
10926
|
+
if (source.startsWith("github:")) {
|
|
10927
|
+
const repo = source.slice("github:".length).split("#", 1)[0];
|
|
10928
|
+
if (!repo.includes("/")) throw new Error(`Invalid GitHub source: ${source}`);
|
|
10929
|
+
return `https://github.com/${repo}.git`;
|
|
10930
|
+
}
|
|
10931
|
+
if (source.startsWith("git:")) {
|
|
10932
|
+
const rest = source.slice("git:".length);
|
|
10933
|
+
const hashIndex = rest.lastIndexOf("#");
|
|
10934
|
+
return hashIndex >= 0 ? rest.slice(0, hashIndex) : rest;
|
|
10935
|
+
}
|
|
10936
|
+
throw new Error(`Version discovery does not support Git source: ${source}`);
|
|
10937
|
+
}
|
|
10938
|
+
function versionCachePath(workspaceRoot) {
|
|
10939
|
+
return join42(workspaceRoot, ".agentwheel", "cache", "version-index.json");
|
|
10940
|
+
}
|
|
10941
|
+
async function readVersionCache(path) {
|
|
10942
|
+
if (!await pathExists(path)) return { schemaVersion: 1, sources: {} };
|
|
10943
|
+
try {
|
|
10944
|
+
return versionCacheSchema.parse(JSON.parse(await readFile30(path, "utf8")));
|
|
10945
|
+
} catch {
|
|
10946
|
+
return { schemaVersion: 1, sources: {} };
|
|
10947
|
+
}
|
|
10948
|
+
}
|
|
10949
|
+
|
|
10950
|
+
// src/profile/members.ts
|
|
10951
|
+
import { execFile as execFile6 } from "child_process";
|
|
10952
|
+
import { readFile as readFile31 } from "fs/promises";
|
|
10953
|
+
import { join as join43, resolve as resolve21 } from "path";
|
|
10954
|
+
import { promisify as promisify6 } from "util";
|
|
10955
|
+
import { z as z12 } from "zod";
|
|
10956
|
+
|
|
10957
|
+
// src/status/report.ts
|
|
10958
|
+
import { z as z11 } from "zod";
|
|
10959
|
+
var statusHealthSchema = z11.enum([
|
|
10960
|
+
"PASS",
|
|
10961
|
+
"WARN",
|
|
10962
|
+
"FAIL",
|
|
10963
|
+
"STALE",
|
|
10964
|
+
"DEGRADED",
|
|
10965
|
+
"INCOMPATIBLE",
|
|
10966
|
+
"BUSY"
|
|
10967
|
+
]);
|
|
10968
|
+
var statusPackageSchema = z11.object({
|
|
10969
|
+
name: z11.string().min(1),
|
|
10970
|
+
source: z11.string().min(1),
|
|
10971
|
+
mode: z11.enum(["pinned", "tracking"]),
|
|
10972
|
+
policy: z11.string().min(1),
|
|
10973
|
+
installed: z11.string().nullable(),
|
|
10974
|
+
locked: z11.string().nullable(),
|
|
10975
|
+
latestAllowed: z11.string().nullable(),
|
|
10976
|
+
latestOverall: z11.string().nullable(),
|
|
10977
|
+
availability: z11.enum(["FRESH", "STALE", "UNKNOWN"]),
|
|
10978
|
+
checkedAt: z11.string().nullable(),
|
|
10979
|
+
error: z11.string().optional(),
|
|
10980
|
+
updateAvailableAllowed: z11.boolean(),
|
|
10981
|
+
updateAvailableOverall: z11.boolean()
|
|
10982
|
+
});
|
|
10983
|
+
var statusArtifactSchema = z11.object({
|
|
10984
|
+
selector: z11.string().min(1),
|
|
10985
|
+
type: z11.string().min(1),
|
|
10986
|
+
name: z11.string().min(1),
|
|
10987
|
+
installName: z11.string().min(1),
|
|
10988
|
+
packageName: z11.string().nullable(),
|
|
10989
|
+
packageVersion: z11.string().nullable(),
|
|
10990
|
+
hash: z11.string().min(16),
|
|
10991
|
+
installed: z11.boolean()
|
|
10992
|
+
});
|
|
10993
|
+
var statusTargetSchema = z11.object({
|
|
10994
|
+
adapter: z11.string().min(1),
|
|
10995
|
+
installationType: z11.string().min(1),
|
|
10996
|
+
targetRoot: z11.string().min(1),
|
|
10997
|
+
health: statusHealthSchema,
|
|
10998
|
+
manifestRevision: z11.string().nullable(),
|
|
10999
|
+
manifestEntryCount: z11.number().int().nonnegative(),
|
|
11000
|
+
graphLockPath: z11.string().nullable(),
|
|
11001
|
+
packageCount: z11.number().int().nonnegative(),
|
|
11002
|
+
artifactCount: z11.number().int().nonnegative(),
|
|
11003
|
+
pendingCount: z11.number().int().nonnegative(),
|
|
11004
|
+
driftCount: z11.number().int().nonnegative(),
|
|
11005
|
+
conflictCount: z11.number().int().nonnegative(),
|
|
11006
|
+
error: z11.string().optional(),
|
|
11007
|
+
packages: z11.array(statusPackageSchema),
|
|
11008
|
+
artifacts: z11.array(statusArtifactSchema)
|
|
11009
|
+
});
|
|
11010
|
+
var statusReportSchema = z11.lazy(() => z11.object({
|
|
11011
|
+
schemaVersion: z11.literal(1),
|
|
11012
|
+
command: z11.literal("status"),
|
|
11013
|
+
agentwheelVersion: z11.string().min(1),
|
|
11014
|
+
generatedAt: z11.string().datetime(),
|
|
11015
|
+
workspace: z11.string().min(1),
|
|
11016
|
+
profile: z11.string().nullable(),
|
|
11017
|
+
health: statusHealthSchema,
|
|
11018
|
+
repository: z11.object({
|
|
11019
|
+
available: z11.boolean(),
|
|
11020
|
+
branch: z11.string().nullable(),
|
|
11021
|
+
head: z11.string().nullable(),
|
|
11022
|
+
upstream: z11.string().nullable(),
|
|
11023
|
+
ahead: z11.number().int().nonnegative(),
|
|
11024
|
+
behind: z11.number().int().nonnegative(),
|
|
11025
|
+
dirtyCount: z11.number().int().nonnegative(),
|
|
11026
|
+
error: z11.string().optional()
|
|
11027
|
+
}),
|
|
11028
|
+
targets: z11.array(statusTargetSchema),
|
|
11029
|
+
members: z11.array(z11.object({
|
|
11030
|
+
id: z11.string().min(1),
|
|
11031
|
+
transport: z11.enum(["local", "ssh"]),
|
|
11032
|
+
workspace: z11.string().min(1),
|
|
11033
|
+
profile: z11.string().min(1),
|
|
11034
|
+
health: statusHealthSchema,
|
|
11035
|
+
agentwheelVersion: z11.string().nullable(),
|
|
11036
|
+
checkedAt: z11.string().nullable(),
|
|
11037
|
+
stale: z11.boolean(),
|
|
11038
|
+
error: z11.string().optional(),
|
|
11039
|
+
report: statusReportSchema.optional()
|
|
11040
|
+
}))
|
|
11041
|
+
}));
|
|
11042
|
+
var HEALTH_RANK = {
|
|
11043
|
+
PASS: 0,
|
|
11044
|
+
WARN: 1,
|
|
11045
|
+
STALE: 2,
|
|
11046
|
+
DEGRADED: 3,
|
|
11047
|
+
BUSY: 4,
|
|
11048
|
+
INCOMPATIBLE: 5,
|
|
11049
|
+
FAIL: 6
|
|
11050
|
+
};
|
|
11051
|
+
function worstStatusHealth(values) {
|
|
11052
|
+
return values.reduce(
|
|
11053
|
+
(worst, value) => HEALTH_RANK[value] > HEALTH_RANK[worst] ? value : worst,
|
|
11054
|
+
"PASS"
|
|
11055
|
+
);
|
|
11056
|
+
}
|
|
11057
|
+
function blocksCompositeApply(health) {
|
|
11058
|
+
return !["PASS", "WARN"].includes(health);
|
|
11059
|
+
}
|
|
11060
|
+
|
|
11061
|
+
// src/profile/members.ts
|
|
11062
|
+
var execFileAsync6 = promisify6(execFile6);
|
|
11063
|
+
var memberCacheSchema = z12.object({
|
|
11064
|
+
schemaVersion: z12.literal(1),
|
|
11065
|
+
checkedAt: z12.string().datetime(),
|
|
11066
|
+
report: statusReportSchema
|
|
11067
|
+
});
|
|
11068
|
+
async function collectCompositeMembers(options) {
|
|
11069
|
+
const chain = [...options.chain ?? [], compositeKey(options.workspaceRoot, options.profileName)];
|
|
11070
|
+
const results = [];
|
|
11071
|
+
for (const member of options.members) {
|
|
11072
|
+
results.push(await collectMember(member, options, chain));
|
|
11073
|
+
}
|
|
11074
|
+
return results;
|
|
11075
|
+
}
|
|
11076
|
+
async function collectMember(member, options, chain) {
|
|
11077
|
+
const cachePath = memberCachePath(options.workspaceRoot, options.profileName, member.id);
|
|
11078
|
+
const cached = await readMemberCache(cachePath);
|
|
11079
|
+
const ttlSeconds = member.refreshTtlSeconds ?? options.profileTtlSeconds;
|
|
11080
|
+
const ageMs = cached ? Date.now() - new Date(cached.checkedAt).getTime() : Number.POSITIVE_INFINITY;
|
|
11081
|
+
const fresh = ageMs <= ttlSeconds * 1e3;
|
|
11082
|
+
if (options.offline || cached && fresh && !options.refresh) {
|
|
11083
|
+
if (!cached) {
|
|
11084
|
+
return memberFailure(member, "STALE", "No cached member status is available offline.");
|
|
11085
|
+
}
|
|
11086
|
+
return memberFromReport(member, cached.report, {
|
|
11087
|
+
checkedAt: cached.checkedAt,
|
|
11088
|
+
stale: options.offline || !fresh,
|
|
11089
|
+
health: options.offline || !fresh ? worstStatusHealth([cached.report.health, "STALE"]) : cached.report.health
|
|
11090
|
+
});
|
|
11091
|
+
}
|
|
11092
|
+
try {
|
|
11093
|
+
const report = await invokeMemberStatus(member, options.workspaceRoot, chain, {
|
|
11094
|
+
refresh: options.refresh || !fresh,
|
|
11095
|
+
offline: false
|
|
11096
|
+
}, options.cliEntry);
|
|
11097
|
+
const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11098
|
+
await writeJsonAtomic(cachePath, { schemaVersion: 1, checkedAt, report });
|
|
11099
|
+
const versionHealth = report.agentwheelVersion === options.cliVersion ? "PASS" : "WARN";
|
|
11100
|
+
return memberFromReport(member, report, {
|
|
11101
|
+
checkedAt,
|
|
11102
|
+
stale: false,
|
|
11103
|
+
health: worstStatusHealth([report.health, versionHealth])
|
|
11104
|
+
});
|
|
11105
|
+
} catch (error) {
|
|
11106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11107
|
+
const incompatible = /Incompatible member status protocol|Unknown option.*--json|Unknown command.*status/i.test(message);
|
|
11108
|
+
if (cached) {
|
|
11109
|
+
return memberFromReport(member, cached.report, {
|
|
11110
|
+
checkedAt: cached.checkedAt,
|
|
11111
|
+
stale: true,
|
|
11112
|
+
health: incompatible ? "INCOMPATIBLE" : "DEGRADED",
|
|
11113
|
+
error: message
|
|
11114
|
+
});
|
|
11115
|
+
}
|
|
11116
|
+
return memberFailure(member, incompatible ? "INCOMPATIBLE" : "FAIL", message);
|
|
11117
|
+
}
|
|
11118
|
+
}
|
|
11119
|
+
async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEntry = process.argv[1]) {
|
|
11120
|
+
const args = ["--no-update-check", "status", "--profile", member.profile, "--json"];
|
|
11121
|
+
if (options.refresh) args.push("--refresh");
|
|
11122
|
+
if (options.offline) args.push("--offline");
|
|
11123
|
+
const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11124
|
+
let stdout = "";
|
|
11125
|
+
let stderr = "";
|
|
11126
|
+
try {
|
|
11127
|
+
if (member.transport === "local") {
|
|
11128
|
+
const workspace = resolve21(parentWorkspace, member.workspace);
|
|
11129
|
+
const result = await execFileAsync6(process.execPath, [cliEntry, ...args], {
|
|
11130
|
+
cwd: workspace,
|
|
11131
|
+
env,
|
|
11132
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11133
|
+
});
|
|
11134
|
+
stdout = result.stdout;
|
|
11135
|
+
stderr = result.stderr;
|
|
11136
|
+
} else {
|
|
11137
|
+
const sshArgs = sshArguments(member);
|
|
11138
|
+
const remoteArgs = [
|
|
11139
|
+
`cd ${shellQuote(member.workspace)}`,
|
|
11140
|
+
"&&",
|
|
11141
|
+
`AGENTWHEEL_COMPOSITE_CHAIN=${shellQuote(JSON.stringify(chain))}`,
|
|
11142
|
+
"agentwheel",
|
|
11143
|
+
...args.map(shellQuote)
|
|
11144
|
+
];
|
|
11145
|
+
const result = await execFileAsync6("ssh", [...sshArgs, remoteArgs.join(" ")], {
|
|
11146
|
+
env,
|
|
11147
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11148
|
+
});
|
|
11149
|
+
stdout = result.stdout;
|
|
11150
|
+
stderr = result.stderr;
|
|
11151
|
+
}
|
|
11152
|
+
} catch (error) {
|
|
11153
|
+
if (typeof error === "object" && error !== null) {
|
|
11154
|
+
stdout = "stdout" in error ? String(error.stdout ?? "") : "";
|
|
11155
|
+
stderr = "stderr" in error ? String(error.stderr ?? "") : "";
|
|
11156
|
+
}
|
|
11157
|
+
if (!stdout.trim()) throw error;
|
|
11158
|
+
}
|
|
11159
|
+
try {
|
|
11160
|
+
return statusReportSchema.parse(JSON.parse(stdout));
|
|
11161
|
+
} catch (error) {
|
|
11162
|
+
const detail = stderr.trim() || (error instanceof Error ? error.message : String(error));
|
|
11163
|
+
throw new Error(`Incompatible member status protocol for ${member.id}: ${detail}`);
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
11167
|
+
const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
|
|
11168
|
+
try {
|
|
11169
|
+
if (member.transport === "local") {
|
|
11170
|
+
const result2 = await execFileAsync6(
|
|
11171
|
+
process.execPath,
|
|
11172
|
+
[process.argv[1], "--no-update-check", ...args],
|
|
11173
|
+
{
|
|
11174
|
+
cwd: resolve21(parentWorkspace, member.workspace),
|
|
11175
|
+
env,
|
|
11176
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11177
|
+
}
|
|
11178
|
+
);
|
|
11179
|
+
return { stdout: result2.stdout, stderr: result2.stderr };
|
|
11180
|
+
}
|
|
11181
|
+
const remoteArgs = [
|
|
11182
|
+
`cd ${shellQuote(member.workspace)}`,
|
|
11183
|
+
"&&",
|
|
11184
|
+
`AGENTWHEEL_COMPOSITE_CHAIN=${shellQuote(JSON.stringify(chain))}`,
|
|
11185
|
+
"agentwheel",
|
|
11186
|
+
"--no-update-check",
|
|
11187
|
+
...args.map(shellQuote)
|
|
11188
|
+
];
|
|
11189
|
+
const result = await execFileAsync6("ssh", [...sshArguments(member), remoteArgs.join(" ")], {
|
|
11190
|
+
env,
|
|
11191
|
+
maxBuffer: 20 * 1024 * 1024
|
|
11192
|
+
});
|
|
11193
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
11194
|
+
} catch (error) {
|
|
11195
|
+
const detail = commandErrorDetail(error);
|
|
11196
|
+
if (/lock|busy|timed out waiting/i.test(detail)) {
|
|
11197
|
+
throw new Error(`BUSY ${member.id}: ${detail}`);
|
|
11198
|
+
}
|
|
11199
|
+
throw new Error(`Member ${member.id} command failed: ${detail}`);
|
|
11200
|
+
}
|
|
11201
|
+
}
|
|
11202
|
+
function sshArguments(member) {
|
|
11203
|
+
const destination = member.user ? `${member.user}@${member.host}` : member.host;
|
|
11204
|
+
return [
|
|
11205
|
+
...member.port ? ["-p", String(member.port)] : [],
|
|
11206
|
+
...member.identityFile ? ["-i", member.identityFile] : [],
|
|
11207
|
+
"--",
|
|
11208
|
+
destination
|
|
11209
|
+
];
|
|
11210
|
+
}
|
|
11211
|
+
function shellQuote(value) {
|
|
11212
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
11213
|
+
}
|
|
11214
|
+
function commandErrorDetail(error) {
|
|
11215
|
+
if (typeof error === "object" && error !== null) {
|
|
11216
|
+
const stderr = "stderr" in error ? String(error.stderr).trim() : "";
|
|
11217
|
+
if (stderr) return stderr;
|
|
11218
|
+
}
|
|
11219
|
+
return error instanceof Error ? error.message : String(error);
|
|
11220
|
+
}
|
|
11221
|
+
function memberFromReport(member, report, state) {
|
|
11222
|
+
return {
|
|
11223
|
+
id: member.id,
|
|
11224
|
+
transport: member.transport,
|
|
11225
|
+
workspace: member.workspace,
|
|
11226
|
+
profile: member.profile,
|
|
11227
|
+
health: state.health,
|
|
11228
|
+
agentwheelVersion: report.agentwheelVersion,
|
|
11229
|
+
checkedAt: state.checkedAt,
|
|
11230
|
+
stale: state.stale,
|
|
11231
|
+
...state.error ? { error: state.error } : {},
|
|
11232
|
+
report
|
|
11233
|
+
};
|
|
11234
|
+
}
|
|
11235
|
+
function memberFailure(member, health, error) {
|
|
11236
|
+
return {
|
|
11237
|
+
id: member.id,
|
|
11238
|
+
transport: member.transport,
|
|
11239
|
+
workspace: member.workspace,
|
|
11240
|
+
profile: member.profile,
|
|
11241
|
+
health,
|
|
11242
|
+
agentwheelVersion: null,
|
|
11243
|
+
checkedAt: null,
|
|
11244
|
+
stale: true,
|
|
11245
|
+
error
|
|
11246
|
+
};
|
|
11247
|
+
}
|
|
11248
|
+
function memberCachePath(workspaceRoot, profileName, memberId) {
|
|
11249
|
+
return join43(workspaceRoot, ".agentwheel", "cache", "member-status", profileName, `${memberId}.json`);
|
|
11250
|
+
}
|
|
11251
|
+
async function readMemberCache(path) {
|
|
11252
|
+
if (!await pathExists(path)) return void 0;
|
|
11253
|
+
try {
|
|
11254
|
+
return memberCacheSchema.parse(JSON.parse(await readFile31(path, "utf8")));
|
|
11255
|
+
} catch {
|
|
11256
|
+
return void 0;
|
|
11257
|
+
}
|
|
11258
|
+
}
|
|
11259
|
+
function parseCompositeChain() {
|
|
11260
|
+
const value = process.env.AGENTWHEEL_COMPOSITE_CHAIN;
|
|
11261
|
+
if (!value) return [];
|
|
11262
|
+
try {
|
|
11263
|
+
return z12.array(z12.string()).parse(JSON.parse(value));
|
|
11264
|
+
} catch {
|
|
11265
|
+
throw new Error("Invalid AGENTWHEEL_COMPOSITE_CHAIN protocol value.");
|
|
11266
|
+
}
|
|
11267
|
+
}
|
|
11268
|
+
function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
11269
|
+
const key = compositeKey(workspaceRoot, profileName);
|
|
11270
|
+
if (chain.includes(key)) {
|
|
11271
|
+
throw new Error(`Composite profile cycle detected: ${[...chain, key].join(" -> ")}`);
|
|
11272
|
+
}
|
|
11273
|
+
}
|
|
11274
|
+
function compositeKey(workspaceRoot, profileName) {
|
|
11275
|
+
return `${resolve21(workspaceRoot)}#${profileName}`;
|
|
11276
|
+
}
|
|
11277
|
+
|
|
11278
|
+
// src/status/repository.ts
|
|
11279
|
+
import { execFile as execFile7 } from "child_process";
|
|
11280
|
+
import { promisify as promisify7 } from "util";
|
|
11281
|
+
var execFileAsync7 = promisify7(execFile7);
|
|
11282
|
+
async function collectRepositoryStatus(workspaceRoot) {
|
|
11283
|
+
try {
|
|
11284
|
+
const { stdout } = await execFileAsync7(
|
|
11285
|
+
"git",
|
|
11286
|
+
["-C", workspaceRoot, "status", "--porcelain=v2", "--branch"],
|
|
11287
|
+
{ maxBuffer: 10 * 1024 * 1024 }
|
|
11288
|
+
);
|
|
11289
|
+
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
11290
|
+
const branch = valueAfter(lines, "# branch.head ");
|
|
11291
|
+
const head = valueAfter(lines, "# branch.oid ");
|
|
11292
|
+
const upstream = valueAfter(lines, "# branch.upstream ");
|
|
11293
|
+
const ab = valueAfter(lines, "# branch.ab ");
|
|
11294
|
+
const match = ab ? /^\+(\d+)\s+-(\d+)$/.exec(ab) : void 0;
|
|
11295
|
+
return {
|
|
11296
|
+
available: true,
|
|
11297
|
+
branch: branch === "(detached)" ? null : branch,
|
|
11298
|
+
head: head === "(initial)" ? null : head,
|
|
11299
|
+
upstream,
|
|
11300
|
+
ahead: match ? Number(match[1]) : 0,
|
|
11301
|
+
behind: match ? Number(match[2]) : 0,
|
|
11302
|
+
dirtyCount: lines.filter((line) => !line.startsWith("# ")).length
|
|
11303
|
+
};
|
|
11304
|
+
} catch (error) {
|
|
11305
|
+
return {
|
|
11306
|
+
available: false,
|
|
11307
|
+
branch: null,
|
|
11308
|
+
head: null,
|
|
11309
|
+
upstream: null,
|
|
11310
|
+
ahead: 0,
|
|
11311
|
+
behind: 0,
|
|
11312
|
+
dirtyCount: 0,
|
|
11313
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11314
|
+
};
|
|
11315
|
+
}
|
|
11316
|
+
}
|
|
11317
|
+
function valueAfter(lines, prefix) {
|
|
11318
|
+
return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length).trim() ?? null;
|
|
11319
|
+
}
|
|
11320
|
+
|
|
10605
11321
|
// src/cli/index.ts
|
|
10606
11322
|
var CLI_VERSION = resolveCliVersion();
|
|
10607
11323
|
var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
|
|
@@ -10633,7 +11349,7 @@ program.command("init").description("initialize an agentwheel workspace or packa
|
|
|
10633
11349
|
if (bootstrapPackage) console.log("Auto-added the agentwheel bootstrap skill for openclaw.");
|
|
10634
11350
|
console.log(nextInstallNudge());
|
|
10635
11351
|
});
|
|
10636
|
-
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
|
|
11352
|
+
program.command("add").description("add a package to .agentwheel/config.json without touching runtimes").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, vercel-skills, mcp-registry, or clawhub)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "workspace root").option("--mode <mode>", "pinned or tracking", "pinned").option("--version <range>", "root package version policy (exact, ~, ^, or *)").option("--name <name>", "package alias").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots on future installs", false).option("--suggestion <alias>", "include one suggested companion alias on future installs (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "allow this package to replace a colliding artifact (repeatable)", collectOverrideOption, []).action(async (source, options) => {
|
|
10637
11353
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
10638
11354
|
const targetRoot = normalizeTargetRoot(normalizedOptions.targetRoot ?? process.cwd());
|
|
10639
11355
|
const entry = await packageEntryFromSource(source, targetRoot, normalizedOptions);
|
|
@@ -10645,7 +11361,7 @@ program.command("list").description("list artifacts exposed by a package source"
|
|
|
10645
11361
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
10646
11362
|
const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
|
|
10647
11363
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
10648
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
11364
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join44(targetRoot, ".agentwheel", "cache") }))));
|
|
10649
11365
|
const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
|
|
10650
11366
|
for (const artifact of artifacts) {
|
|
10651
11367
|
console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
|
|
@@ -10655,7 +11371,7 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
10655
11371
|
const targetRoot = normalizeTargetRoot(options.targetRoot);
|
|
10656
11372
|
const resolvedInput = await resolvePackageSource(source, targetRoot);
|
|
10657
11373
|
const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
|
|
10658
|
-
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot:
|
|
11374
|
+
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join44(targetRoot, ".agentwheel", "cache") }))));
|
|
10659
11375
|
const result = await driver.scan(resolved);
|
|
10660
11376
|
if (result.findings.length === 0) {
|
|
10661
11377
|
console.log("Scan ok: no findings");
|
|
@@ -10666,13 +11382,13 @@ program.command("scan").description("scan a package source for validation findin
|
|
|
10666
11382
|
}
|
|
10667
11383
|
if (!result.ok) process.exitCode = 1;
|
|
10668
11384
|
});
|
|
10669
|
-
program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
11385
|
+
program.command("plan").description("preview what install would reconcile without writing").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--dry-run", "accepted for symmetry; plan never writes", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
10670
11386
|
await runInstallCommand(source, { ...options, dryRun: true }, { apply: false });
|
|
10671
11387
|
});
|
|
10672
|
-
program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
|
|
11388
|
+
program.command("install").description("install configured packages into runtime targets").argument("[name-or-source]", "configured package name/source or package source to add and install").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--format <fmt>", "output format: human|json|mermaid|html", "human").option("--json", "print the resolved plan as JSON", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).addHelpText("after", "\nScoped install never removes files owned only by other configured packages; run a full install to reconcile those removals.\n").action(async (source, options) => {
|
|
10673
11389
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
10674
11390
|
});
|
|
10675
|
-
program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
|
|
11391
|
+
program.command("serve").description("serve a read-only live dashboard for the resolved install plan").argument("[name-or-source]", "configured package name/source or package source to preview").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--profile <name>", "workspace runtime profile").option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "for source previews, allow the source to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--force-drift", "replace drifted managed artifacts during install planning", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).option("--bind <addr>", "interface to bind", "127.0.0.1").option("--port <n>", "TCP port (0 selects an ephemeral port)", "8765").option("--interval <seconds>", "background re-render cadence in seconds", "60").option("--once", "render once and skip the background re-render loop", false).action(async (source, options) => {
|
|
10676
11392
|
await servePlanDashboard({
|
|
10677
11393
|
bind: options.bind,
|
|
10678
11394
|
port: parseServePort(options.port),
|
|
@@ -10681,11 +11397,11 @@ program.command("serve").description("serve a read-only live dashboard for the r
|
|
|
10681
11397
|
buildReport: () => buildPlanReport(source, options)
|
|
10682
11398
|
});
|
|
10683
11399
|
});
|
|
10684
|
-
program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
11400
|
+
program.command("sync", { hidden: true }).argument("[name-or-source]", "configured package name/source or package source").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--all-detected", "run for every runtime directory detected in the target root", false).option("--mode <mode>", "pinned or tracking").option("--select <type/name>", "select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--override <source-or-package::type/name>", "when adding a source, allow it to replace a colliding artifact (repeatable)", collectOverrideOption, []).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--only-source", "with a source argument, exclude configured workspace packages", false).option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (source, options) => {
|
|
10685
11401
|
console.error("warning: 'agentwheel sync' is deprecated and will be removed in 0.10. Use 'agentwheel install'.");
|
|
10686
11402
|
await runInstallCommand(source, options, { apply: !options.dryRun });
|
|
10687
11403
|
});
|
|
10688
|
-
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dependency <name-or-source>", "update one tracking dependency while keeping unrelated graph nodes locked (repeatable)", collectDependencyOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
11404
|
+
program.command("update").description("re-resolve tracking packages, then apply the result").argument("[name]", "configured package name or source to update").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "workspace root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plans without writing", false).option("--force-drift", "replace drifted managed artifacts", false).option("--force-conflict", "adopt unmanaged destinations when their content already matches the desired artifact", false).option("--replace-conflict", "replace unmanaged destinations even when their content differs", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--reload-runtimes", "run configured runtime reload commands after executed semantic plugin changes", false).option("--restart-runtimes", "alias for --reload-runtimes", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).option("--select <type/name>", "temporarily select an artifact by type/name (repeatable or comma-separated)", collectSelectOption, []).option("--skill <name>", "temporarily select a skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--dependency <name-or-source>", "update one tracking dependency while keeping unrelated graph nodes locked (repeatable)", collectDependencyOption, []).option("--with-suggestions", "include suggested companion artifacts for selected roots", false).option("--suggestion <alias>", "include one suggested companion alias (repeatable or comma-separated)", collectSuggestionOption, []).option("--no-deps", "resolve only root sources and ignore requires with a warning").option("--frozen-lock", "resolve strictly from the existing graph lock and cached sources", false).option("--offline", "resolve strictly from graph locks and local caches", false).option("--refresh", "refresh available package versions even when the version-index TTL is fresh", false).option("--yes", "trust all new transitive sources", false).option("--trust <pattern>", "pre-approve a transitive source glob (repeatable)", collectTrustOption, []).action(async (name, options) => {
|
|
10689
11405
|
if (name && options.dependency.length > 0) throw new Error("A package argument cannot be combined with --dependency.");
|
|
10690
11406
|
if (options.dependency.length > 0 && (options.select.length > 0 || options.skill.length > 0)) {
|
|
10691
11407
|
throw new Error("--dependency cannot be combined with --select or --skill; package selections remain unchanged.");
|
|
@@ -10694,6 +11410,11 @@ program.command("update").description("re-resolve tracking packages, then apply
|
|
|
10694
11410
|
throw new Error("--dependency cannot be combined with --frozen-lock or --offline.");
|
|
10695
11411
|
}
|
|
10696
11412
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
11413
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
11414
|
+
if (composite) {
|
|
11415
|
+
await runCompositeUpdate(composite.workspaceRoot, composite.name, composite.profile, name, normalizedOptions);
|
|
11416
|
+
return;
|
|
11417
|
+
}
|
|
10697
11418
|
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
10698
11419
|
for (const target of targets) {
|
|
10699
11420
|
await runConfiguredGraphPackages(target, { ...normalizedOptions, scope: name }, { mode: "update" });
|
|
@@ -10889,9 +11610,25 @@ program.command("uninstall").description("remove configured packages or managed
|
|
|
10889
11610
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
10890
11611
|
}
|
|
10891
11612
|
});
|
|
10892
|
-
program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
|
|
11613
|
+
program.command("status").description("show configured packages and runtime install state").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").option("--json", "print the versioned status protocol as JSON", false).option("--offline", "use cached version and member status even when stale", false).option("--refresh", "refresh package and member status regardless of TTL", false).action(async (options) => {
|
|
10893
11614
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
11615
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
11616
|
+
if (composite) {
|
|
11617
|
+
const report = await collectCompositeStatus(composite.workspaceRoot, composite.name, composite.profile, normalizedOptions);
|
|
11618
|
+
if (options.json) console.log(JSON.stringify(report, null, 2));
|
|
11619
|
+
else printStatusReport(report);
|
|
11620
|
+
if (!["PASS", "WARN"].includes(report.health)) process.exitCode = 1;
|
|
11621
|
+
return;
|
|
11622
|
+
}
|
|
10894
11623
|
const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
|
|
11624
|
+
if (options.json) {
|
|
11625
|
+
const targetReports = [];
|
|
11626
|
+
for (const target of targets) targetReports.push(await collectTargetStatus(target, normalizedOptions));
|
|
11627
|
+
const report = await statusReport(targets[0]?.workspaceRoot ?? process.cwd(), options.profile ?? null, targetReports);
|
|
11628
|
+
console.log(JSON.stringify(report, null, 2));
|
|
11629
|
+
if (!["PASS", "WARN"].includes(report.health)) process.exitCode = 1;
|
|
11630
|
+
return;
|
|
11631
|
+
}
|
|
10895
11632
|
for (const target of targets) {
|
|
10896
11633
|
await printStatus(target, normalizedOptions);
|
|
10897
11634
|
}
|
|
@@ -10907,7 +11644,7 @@ journalCommand.command("list").description("show pending apply journals for reso
|
|
|
10907
11644
|
if (!journal) continue;
|
|
10908
11645
|
pending += 1;
|
|
10909
11646
|
console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
|
|
10910
|
-
console.log(` journal: ${
|
|
11647
|
+
console.log(` journal: ${join44(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
|
|
10911
11648
|
console.log(` stateKey: ${state.state.stateKey}`);
|
|
10912
11649
|
console.log(` createdAt: ${journal.createdAt}`);
|
|
10913
11650
|
console.log(` updatedAt: ${journal.updatedAt}`);
|
|
@@ -10941,6 +11678,21 @@ program.command("doctor").description("check agentwheel runtime setup and compan
|
|
|
10941
11678
|
async function runInstallCommand(nameOrSource, options, behavior) {
|
|
10942
11679
|
const normalizedOptions = normalizeRuntimeScopeOptions(options, { defaultUser: shouldDefaultUserInstall(nameOrSource, options) });
|
|
10943
11680
|
const outputFormat = effectivePlanOutputFormat(normalizedOptions);
|
|
11681
|
+
const composite = await resolveSelectedCompositeProfile(normalizedOptions);
|
|
11682
|
+
if (composite) {
|
|
11683
|
+
if (outputFormat !== "human") {
|
|
11684
|
+
throw new Error("Composite profile plan/install currently requires human output; use status --json for the versioned member protocol.");
|
|
11685
|
+
}
|
|
11686
|
+
await runCompositeInstall(
|
|
11687
|
+
composite.workspaceRoot,
|
|
11688
|
+
composite.name,
|
|
11689
|
+
composite.profile,
|
|
11690
|
+
nameOrSource,
|
|
11691
|
+
normalizedOptions,
|
|
11692
|
+
behavior
|
|
11693
|
+
);
|
|
11694
|
+
return;
|
|
11695
|
+
}
|
|
10944
11696
|
if (outputFormat !== "human") {
|
|
10945
11697
|
const report = await buildPlanReport(nameOrSource, normalizedOptions);
|
|
10946
11698
|
if (report.targets.some((target) => target.hasBlockingChanges)) process.exitCode = 1;
|
|
@@ -11160,11 +11912,32 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
11160
11912
|
baseDir: targetRoot,
|
|
11161
11913
|
warn: options.warn ?? ((message) => console.warn(message))
|
|
11162
11914
|
});
|
|
11915
|
+
const provisionalName = options.name ?? resolvedInput.registryEntry?.name ?? source;
|
|
11916
|
+
const initialVersion = options.mode === "tracking" && options.version ? await effectiveTrackingRef({
|
|
11917
|
+
name: provisionalName,
|
|
11918
|
+
source: resolvedSource,
|
|
11919
|
+
driver: driverName,
|
|
11920
|
+
adapter: adapter.name,
|
|
11921
|
+
installationType: options.installationType,
|
|
11922
|
+
mode: "tracking",
|
|
11923
|
+
version: options.version
|
|
11924
|
+
}, targetRoot, { offline: options.offline }) : void 0;
|
|
11925
|
+
if (initialVersion?.availability?.error || initialVersion?.availability?.stale) {
|
|
11926
|
+
throw new Error(
|
|
11927
|
+
`Cannot select an initial version for ${provisionalName}: ${initialVersion.availability?.error ?? "version metadata is stale"}`
|
|
11928
|
+
);
|
|
11929
|
+
}
|
|
11930
|
+
if (initialVersion?.availability && !initialVersion.availability.latestAllowedRef) {
|
|
11931
|
+
throw new Error(
|
|
11932
|
+
`No available version of ${provisionalName} satisfies ${options.version}; latest overall is ${initialVersion.availability.latestOverall ?? "unknown"}.`
|
|
11933
|
+
);
|
|
11934
|
+
}
|
|
11163
11935
|
const bundle = await stageSource(driver, resolvedSource, {
|
|
11164
11936
|
workspaceRoot: targetRoot,
|
|
11165
11937
|
adapter,
|
|
11166
|
-
cacheRoot:
|
|
11938
|
+
cacheRoot: join44(targetRoot, ".agentwheel", "cache"),
|
|
11167
11939
|
mode: options.mode,
|
|
11940
|
+
ref: initialVersion?.ref,
|
|
11168
11941
|
frozenLock: lockMode,
|
|
11169
11942
|
select: selectedArtifacts
|
|
11170
11943
|
});
|
|
@@ -11180,6 +11953,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
|
|
|
11180
11953
|
adapterModule: options.adapterModule,
|
|
11181
11954
|
adapterCodeHash: adapter.programmatic?.hash,
|
|
11182
11955
|
mode: options.mode ?? "pinned",
|
|
11956
|
+
version: options.version,
|
|
11183
11957
|
requestedRef: bundle.source.requestedRef,
|
|
11184
11958
|
select: selectedArtifacts,
|
|
11185
11959
|
withSuggestions: options.withSuggestions === true ? true : void 0,
|
|
@@ -11396,28 +12170,61 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11396
12170
|
const allPackages = [...group.packages, ...group.extraPackages];
|
|
11397
12171
|
const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
|
|
11398
12172
|
if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
|
|
12173
|
+
const groupGraphLockPath = graphLockPathForTarget(
|
|
12174
|
+
group.target.workspaceRoot,
|
|
12175
|
+
targetKeyForTarget(group.target, adapter.name),
|
|
12176
|
+
adapter.name,
|
|
12177
|
+
targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType)
|
|
12178
|
+
);
|
|
12179
|
+
const previousGroupLock = await (await pathExists(groupGraphLockPath) ? readGraphLock(groupGraphLockPath) : void 0);
|
|
11399
12180
|
const dependencyUpdateRootNames = /* @__PURE__ */ new Set();
|
|
11400
12181
|
if (scopedDependencyUpdate) {
|
|
11401
|
-
const graphLockPath = graphLockPathForTarget(
|
|
11402
|
-
group.target.workspaceRoot,
|
|
11403
|
-
targetKeyForTarget(group.target, adapter.name),
|
|
11404
|
-
adapter.name,
|
|
11405
|
-
targetFingerprintParts(group.target, adapter, group.adapterOptions, group.installationType)
|
|
11406
|
-
);
|
|
11407
|
-
const previousLock = await (await pathExists(graphLockPath) ? readGraphLock(graphLockPath) : void 0);
|
|
11408
12182
|
for (const pkg of group.packages) {
|
|
11409
|
-
const root =
|
|
12183
|
+
const root = previousGroupLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
11410
12184
|
if (root?.mode !== "tracking") continue;
|
|
11411
|
-
const node =
|
|
12185
|
+
const node = previousGroupLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId);
|
|
11412
12186
|
if (node && dependencyUpdateSelectors.some((selector) => dependencyUpdateSelectorMatchesRoot(root, node, selector))) {
|
|
11413
12187
|
dependencyUpdateRootNames.add(pkg.name);
|
|
11414
12188
|
}
|
|
11415
12189
|
}
|
|
11416
12190
|
}
|
|
11417
12191
|
const updateScope = behavior.mode === "update" ? scopedPackage ? /* @__PURE__ */ new Set([scopedPackage.name]) : void 0 : void 0;
|
|
12192
|
+
const versionSelections = /* @__PURE__ */ new Map();
|
|
12193
|
+
const lockedVersionRefs = /* @__PURE__ */ new Map();
|
|
12194
|
+
const versionPolicyUpdateNames = /* @__PURE__ */ new Set();
|
|
12195
|
+
for (const pkg of allPackages) {
|
|
12196
|
+
if (pkg.mode !== "tracking" || !pkg.version) continue;
|
|
12197
|
+
const previousRoot = previousGroupLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
12198
|
+
const previousNode = previousRoot ? previousGroupLock?.canonical.nodes.find((candidate) => candidate.id === previousRoot.graphNodeId) : void 0;
|
|
12199
|
+
const policyRequiresResolution = !previousNode || !satisfiesVersionRange(previousNode.version, pkg.version);
|
|
12200
|
+
if (!policyRequiresResolution && previousNode?.requestedRef) {
|
|
12201
|
+
lockedVersionRefs.set(pkg.name, previousNode.requestedRef);
|
|
12202
|
+
}
|
|
12203
|
+
const packageUpdateSelected = !updateScope || updateScope.has(pkg.name);
|
|
12204
|
+
if (behavior.mode === "update" && packageUpdateSelected || behavior.mode === "install" && policyRequiresResolution) {
|
|
12205
|
+
const selection = await effectiveTrackingRef(pkg, group.target.workspaceRoot, {
|
|
12206
|
+
offline: targetOptions.offline,
|
|
12207
|
+
forceRefresh: targetOptions.refresh
|
|
12208
|
+
});
|
|
12209
|
+
if (selection.availability?.error || selection.availability?.stale) {
|
|
12210
|
+
throw new Error(
|
|
12211
|
+
`Cannot resolve ${pkg.name} with stale version metadata: ${selection.availability?.error ?? "version index TTL expired"}`
|
|
12212
|
+
);
|
|
12213
|
+
}
|
|
12214
|
+
versionSelections.set(pkg.name, selection);
|
|
12215
|
+
if (policyRequiresResolution) versionPolicyUpdateNames.add(pkg.name);
|
|
12216
|
+
if (!selection.availability?.latestAllowed) {
|
|
12217
|
+
targetOptions.warn?.(
|
|
12218
|
+
`No available version of ${pkg.name} satisfies ${pkg.version}; latest overall is ${selection.availability?.latestOverall ?? "unknown"}.`
|
|
12219
|
+
);
|
|
12220
|
+
}
|
|
12221
|
+
}
|
|
12222
|
+
}
|
|
11418
12223
|
const roots = [
|
|
11419
12224
|
...allPackages.map((pkg) => {
|
|
11420
|
-
const
|
|
12225
|
+
const versionSelection = versionSelections.get(pkg.name);
|
|
12226
|
+
const versionAllowsUpdate = !pkg.version || Boolean(versionSelection?.availability?.latestAllowed);
|
|
12227
|
+
const updateThisPackage = behavior.mode === "update" && !scopedDependencyUpdate && pkg.mode === "tracking" && versionAllowsUpdate && (!updateScope || updateScope.has(pkg.name) || updateScope.has(pkg.source));
|
|
11421
12228
|
const packageIsScoped = scopedRootId ? pkg.name === scopedRootId || pkg.source === targetOptions.scope : true;
|
|
11422
12229
|
if (pkg.selection && selectedArtifacts && packageIsScoped) {
|
|
11423
12230
|
throw new Error(`--select/--skill cannot override imported selection for configured package '${pkg.name}'.`);
|
|
@@ -11426,14 +12233,15 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11426
12233
|
rootId: pkg.name,
|
|
11427
12234
|
source: pkg.source,
|
|
11428
12235
|
mode: pkg.mode,
|
|
11429
|
-
|
|
12236
|
+
version: pkg.version,
|
|
12237
|
+
ref: versionSelection?.ref ?? lockedVersionRefs.get(pkg.name) ?? pkg.requestedRef,
|
|
11430
12238
|
select: pkg.selection ? void 0 : selectedArtifacts && packageIsScoped ? selectedArtifacts : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
11431
12239
|
selection: pkg.selection,
|
|
11432
12240
|
aliases: pkg.aliases,
|
|
11433
12241
|
overrides: pkg.overrides,
|
|
11434
12242
|
includeSuggestions: targetOptions.withSuggestions === true || pkg.withSuggestions === true,
|
|
11435
12243
|
suggestionAliases: packageSuggestionAliases(pkg, targetOptions),
|
|
11436
|
-
useLock: behavior.mode === "install" ?
|
|
12244
|
+
useLock: behavior.mode === "install" ? !versionPolicyUpdateNames.has(pkg.name) : scopedDependencyUpdate ? !dependencyUpdateRootNames.has(pkg.name) : !updateThisPackage
|
|
11437
12245
|
};
|
|
11438
12246
|
}),
|
|
11439
12247
|
...group.extraRoots
|
|
@@ -11474,7 +12282,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
11474
12282
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
11475
12283
|
const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
|
|
11476
12284
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
11477
|
-
results.push(scopeInstallPlanToRoot(result, scopedRootId, manifest));
|
|
12285
|
+
results.push(behavior.mode === "update" && previousGroupLock ? scopeUpdatePlanToRoot(result, scopedRootId, previousGroupLock, manifest) : scopeInstallPlanToRoot(result, scopedRootId, manifest));
|
|
11478
12286
|
} else if (scopedDependencyUpdate) {
|
|
11479
12287
|
const state = installStateForTarget(group.target, adapter, group.adapterOptions, group.installationType);
|
|
11480
12288
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
@@ -11678,6 +12486,44 @@ function scopeInstallPlanToRoot(result, rootId, manifest) {
|
|
|
11678
12486
|
}
|
|
11679
12487
|
};
|
|
11680
12488
|
}
|
|
12489
|
+
function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
12490
|
+
const scoped = scopeInstallPlanToRoot(result, rootId, manifest);
|
|
12491
|
+
const selectedCurrentNodeIds = graphRootClosure(scoped.bundle.graphLock, rootId);
|
|
12492
|
+
const selectedPreviousNodeIds = graphRootClosure(previousLock, rootId);
|
|
12493
|
+
const graphLock = preserveUnrelatedGraphPackages(
|
|
12494
|
+
scoped.bundle.graphLock,
|
|
12495
|
+
previousLock,
|
|
12496
|
+
selectedCurrentNodeIds,
|
|
12497
|
+
selectedPreviousNodeIds,
|
|
12498
|
+
/* @__PURE__ */ new Set([rootId])
|
|
12499
|
+
);
|
|
12500
|
+
const graphLockDigest = createHash11("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
12501
|
+
return {
|
|
12502
|
+
...scoped,
|
|
12503
|
+
bundle: { ...scoped.bundle, graphLock },
|
|
12504
|
+
graphLockDigest,
|
|
12505
|
+
graphDiff: diffGraphLocks(previousLock, graphLock),
|
|
12506
|
+
plan: {
|
|
12507
|
+
...scoped.plan,
|
|
12508
|
+
graphLockDigest
|
|
12509
|
+
}
|
|
12510
|
+
};
|
|
12511
|
+
}
|
|
12512
|
+
function graphRootClosure(lock, rootId) {
|
|
12513
|
+
const root = lock.canonical.roots.find((candidate) => candidate.rootId === rootId);
|
|
12514
|
+
if (!root) return /* @__PURE__ */ new Set();
|
|
12515
|
+
const selected = /* @__PURE__ */ new Set();
|
|
12516
|
+
const queue = [root.graphNodeId];
|
|
12517
|
+
while (queue.length > 0) {
|
|
12518
|
+
const nodeId = queue.shift();
|
|
12519
|
+
if (selected.has(nodeId)) continue;
|
|
12520
|
+
selected.add(nodeId);
|
|
12521
|
+
for (const edge of lock.canonical.edges) {
|
|
12522
|
+
if (edge.from === nodeId) queue.push(edge.to);
|
|
12523
|
+
}
|
|
12524
|
+
}
|
|
12525
|
+
return selected;
|
|
12526
|
+
}
|
|
11681
12527
|
function transformOutOfScopeOperation(operation, entry, targetRoot, scopeDescription) {
|
|
11682
12528
|
if (operation.action === "skip") return [operation];
|
|
11683
12529
|
if (operation.action === "update" || operation.action === "drift") {
|
|
@@ -11723,7 +12569,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
|
|
|
11723
12569
|
artifactType: entry.artifactType,
|
|
11724
12570
|
artifactName: entry.artifactName,
|
|
11725
12571
|
kind: entry.kind,
|
|
11726
|
-
destPath: operation?.destPath ??
|
|
12572
|
+
destPath: operation?.destPath ?? join44(targetRoot, entry.path),
|
|
11727
12573
|
relativeDestPath: entry.path,
|
|
11728
12574
|
desiredHash: entry.sourceHash,
|
|
11729
12575
|
currentHash: operation?.currentHash ?? entry.hash,
|
|
@@ -11785,6 +12631,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
11785
12631
|
rootId: pkg2.name,
|
|
11786
12632
|
source: pkg2.source,
|
|
11787
12633
|
mode: pkg2.mode,
|
|
12634
|
+
version: pkg2.version,
|
|
11788
12635
|
ref: pkg2.requestedRef,
|
|
11789
12636
|
select: pkg2.selection ? void 0 : normalizeArtifactSelectors(pkg2.select, pkg2.skills),
|
|
11790
12637
|
selection: pkg2.selection,
|
|
@@ -11921,31 +12768,341 @@ async function readTargetGraphLock(target, options) {
|
|
|
11921
12768
|
return { adapter, path, lock: await readGraphLock(path) };
|
|
11922
12769
|
}
|
|
11923
12770
|
async function printStatus(target, options) {
|
|
12771
|
+
const report = await collectTargetStatus(target, options);
|
|
12772
|
+
printTargetStatus(report);
|
|
12773
|
+
}
|
|
12774
|
+
async function collectTargetStatus(target, options) {
|
|
11924
12775
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
11925
12776
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
11926
|
-
const adapter = await resolveAdapterForTarget(target, adapterOptions);
|
|
12777
|
+
const adapter = await resolveAdapterForTarget(target, { ...adapterOptions, warn: () => void 0 });
|
|
11927
12778
|
const transport = transportForTarget(target);
|
|
11928
12779
|
const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
|
|
11929
12780
|
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
11930
|
-
console.log(`Status for ${adapter.name}/${installationType} at ${state.installRoot}`);
|
|
11931
|
-
if (config.packages.length === 0) {
|
|
11932
|
-
console.log(`Configured packages: none at ${target.workspaceRoot}`);
|
|
11933
|
-
return;
|
|
11934
|
-
}
|
|
11935
|
-
console.log("Configured packages:");
|
|
11936
|
-
for (const pkg of config.packages) {
|
|
11937
|
-
console.log(`- ${pkg.name} (${pkg.mode}) ${pkg.source}`);
|
|
11938
|
-
}
|
|
11939
12781
|
const manifest = await readInstallManifest(state.installRoot, adapter.name, transport, state);
|
|
11940
|
-
|
|
12782
|
+
let graphLockPath = null;
|
|
12783
|
+
let graphLock;
|
|
11941
12784
|
try {
|
|
11942
|
-
const
|
|
11943
|
-
|
|
11944
|
-
|
|
12785
|
+
const result = await readTargetGraphLock(target, adapterOptions);
|
|
12786
|
+
graphLockPath = result.path;
|
|
12787
|
+
graphLock = result.lock;
|
|
11945
12788
|
} catch {
|
|
11946
|
-
|
|
12789
|
+
graphLock = void 0;
|
|
12790
|
+
}
|
|
12791
|
+
const packages = [];
|
|
12792
|
+
for (const pkg of config.packages) {
|
|
12793
|
+
const root = graphLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
|
|
12794
|
+
const node = root ? graphLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId) : void 0;
|
|
12795
|
+
const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
|
|
12796
|
+
forceRefresh: options.refresh,
|
|
12797
|
+
offline: options.offline
|
|
12798
|
+
});
|
|
12799
|
+
const installed = node && manifest?.entries.some((entry) => "graphNodeId" in entry && entry.graphNodeId === node.id) ? node.version : null;
|
|
12800
|
+
const locked = node?.version ?? null;
|
|
12801
|
+
const baseline = installed ?? locked;
|
|
12802
|
+
packages.push({
|
|
12803
|
+
name: pkg.name,
|
|
12804
|
+
source: pkg.source,
|
|
12805
|
+
mode: pkg.mode,
|
|
12806
|
+
policy: pkg.version ?? "*",
|
|
12807
|
+
installed,
|
|
12808
|
+
locked,
|
|
12809
|
+
latestAllowed: availability.latestAllowed,
|
|
12810
|
+
latestOverall: availability.latestOverall,
|
|
12811
|
+
availability: availability.stale ? "STALE" : availability.checkedAt ? "FRESH" : "UNKNOWN",
|
|
12812
|
+
checkedAt: availability.checkedAt,
|
|
12813
|
+
...availability.error ? { error: availability.error } : {},
|
|
12814
|
+
updateAvailableAllowed: Boolean(
|
|
12815
|
+
baseline && availability.latestAllowed && compareSemverStrings(availability.latestAllowed, baseline) > 0
|
|
12816
|
+
),
|
|
12817
|
+
updateAvailableOverall: Boolean(
|
|
12818
|
+
baseline && availability.latestOverall && compareSemverStrings(availability.latestOverall, baseline) > 0
|
|
12819
|
+
)
|
|
12820
|
+
});
|
|
12821
|
+
}
|
|
12822
|
+
const pending = await collectPendingInstallWork(target, options);
|
|
12823
|
+
const artifacts = (graphLock?.canonical.artifacts ?? []).map((artifact) => {
|
|
12824
|
+
const node = graphLock?.canonical.nodes.find((candidate) => candidate.id === artifact.graphNodeId);
|
|
12825
|
+
const installed = manifest?.entries.some((entry) => {
|
|
12826
|
+
if (!("graphNodeId" in entry) || entry.graphNodeId !== artifact.graphNodeId) return false;
|
|
12827
|
+
if ("logicalSelector" in entry && entry.logicalSelector) return entry.logicalSelector === artifact.logicalSelector;
|
|
12828
|
+
return entry.artifactType === artifact.type && entry.artifactName === artifact.name;
|
|
12829
|
+
}) ?? false;
|
|
12830
|
+
return {
|
|
12831
|
+
selector: artifact.logicalSelector,
|
|
12832
|
+
type: artifact.type,
|
|
12833
|
+
name: artifact.name,
|
|
12834
|
+
installName: artifact.installName,
|
|
12835
|
+
packageName: node?.name ?? null,
|
|
12836
|
+
packageVersion: node?.version ?? null,
|
|
12837
|
+
hash: artifact.hash,
|
|
12838
|
+
installed
|
|
12839
|
+
};
|
|
12840
|
+
});
|
|
12841
|
+
const health = [];
|
|
12842
|
+
if (!manifest || !graphLock) health.push("FAIL");
|
|
12843
|
+
if (pending.error) health.push("DEGRADED");
|
|
12844
|
+
if (pending.driftCount > 0 || pending.conflictCount > 0) health.push("FAIL");
|
|
12845
|
+
else if (pending.pendingCount > 0) health.push("WARN");
|
|
12846
|
+
if (packages.some((pkg) => pkg.availability === "STALE" || pkg.error)) health.push("DEGRADED");
|
|
12847
|
+
else if (packages.some((pkg) => pkg.updateAvailableAllowed || pkg.updateAvailableOverall)) health.push("WARN");
|
|
12848
|
+
return {
|
|
12849
|
+
adapter: adapter.name,
|
|
12850
|
+
installationType,
|
|
12851
|
+
targetRoot: state.installRoot,
|
|
12852
|
+
health: worstStatusHealth(health),
|
|
12853
|
+
manifestRevision: manifest?.revision ?? null,
|
|
12854
|
+
manifestEntryCount: manifest?.entries.length ?? 0,
|
|
12855
|
+
graphLockPath,
|
|
12856
|
+
packageCount: packages.length,
|
|
12857
|
+
artifactCount: graphLock?.canonical.artifacts.length ?? 0,
|
|
12858
|
+
pendingCount: pending.pendingCount,
|
|
12859
|
+
driftCount: pending.driftCount,
|
|
12860
|
+
conflictCount: pending.conflictCount,
|
|
12861
|
+
...pending.error ? { error: pending.error } : {},
|
|
12862
|
+
packages,
|
|
12863
|
+
artifacts
|
|
12864
|
+
};
|
|
12865
|
+
}
|
|
12866
|
+
async function resolveSelectedCompositeProfile(options) {
|
|
12867
|
+
const workspaceRoot = await findWorkspaceRoot(options.targetRoot ?? process.cwd());
|
|
12868
|
+
const config = await readMergedWorkspaceConfig(workspaceRoot);
|
|
12869
|
+
const name = options.profile ?? (options.all && config.profiles.all ? "all" : void 0);
|
|
12870
|
+
if (!name) return void 0;
|
|
12871
|
+
const profile = config.profiles[name];
|
|
12872
|
+
if (!profile) throw new Error(`Unknown profile: ${name}`);
|
|
12873
|
+
if (!isCompositeWorkspaceProfile(profile)) return void 0;
|
|
12874
|
+
const chain = parseCompositeChain();
|
|
12875
|
+
assertNoCompositeCycle(workspaceRoot, name, chain);
|
|
12876
|
+
return { workspaceRoot, name, profile };
|
|
12877
|
+
}
|
|
12878
|
+
async function collectCompositeStatus(workspaceRoot, profileName, profile, options) {
|
|
12879
|
+
const chain = parseCompositeChain();
|
|
12880
|
+
const members = await collectCompositeMembers({
|
|
12881
|
+
cliVersion: CLI_VERSION,
|
|
12882
|
+
workspaceRoot,
|
|
12883
|
+
profileName,
|
|
12884
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
12885
|
+
members: profile.members,
|
|
12886
|
+
refresh: options.refresh,
|
|
12887
|
+
offline: options.offline,
|
|
12888
|
+
chain
|
|
12889
|
+
});
|
|
12890
|
+
const repository = await collectRepositoryStatus(workspaceRoot);
|
|
12891
|
+
const repositoryHealth = repository.available && repository.ahead === 0 && repository.behind === 0 && repository.dirtyCount === 0 ? "PASS" : "WARN";
|
|
12892
|
+
return {
|
|
12893
|
+
schemaVersion: 1,
|
|
12894
|
+
command: "status",
|
|
12895
|
+
agentwheelVersion: CLI_VERSION,
|
|
12896
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12897
|
+
workspace: workspaceRoot,
|
|
12898
|
+
profile: profileName,
|
|
12899
|
+
health: worstStatusHealth([...members.map((member) => member.health), repositoryHealth]),
|
|
12900
|
+
repository,
|
|
12901
|
+
targets: [],
|
|
12902
|
+
members
|
|
12903
|
+
};
|
|
12904
|
+
}
|
|
12905
|
+
async function runCompositeUpdate(workspaceRoot, profileName, profile, packageName, options) {
|
|
12906
|
+
const preflight = await collectCompositeStatus(workspaceRoot, profileName, profile, options);
|
|
12907
|
+
printStatusReport(preflight);
|
|
12908
|
+
const blockers = preflight.members.filter((member) => blocksCompositeApply(member.health));
|
|
12909
|
+
if (blockers.length > 0) {
|
|
12910
|
+
throw new Error(
|
|
12911
|
+
`Composite update blocked before member execution: ` + blockers.map((member) => `${member.id}=${member.health}`).join(", ")
|
|
12912
|
+
);
|
|
12913
|
+
}
|
|
12914
|
+
const incomingChain = parseCompositeChain();
|
|
12915
|
+
const memberChain = [...incomingChain, compositeKey(workspaceRoot, profileName)];
|
|
12916
|
+
for (const member of profile.members) {
|
|
12917
|
+
if (!options.dryRun) {
|
|
12918
|
+
const before = preflight.members.find((candidate) => candidate.id === member.id);
|
|
12919
|
+
const revalidated = await collectCompositeMembers({
|
|
12920
|
+
cliVersion: CLI_VERSION,
|
|
12921
|
+
workspaceRoot,
|
|
12922
|
+
profileName,
|
|
12923
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
12924
|
+
members: [member],
|
|
12925
|
+
refresh: true,
|
|
12926
|
+
chain: incomingChain
|
|
12927
|
+
});
|
|
12928
|
+
const current = revalidated[0];
|
|
12929
|
+
if (blocksCompositeApply(current.health)) {
|
|
12930
|
+
throw new Error(`Composite update stopped before ${member.id}: revalidation is ${current.health}.`);
|
|
12931
|
+
}
|
|
12932
|
+
if (statusRevisionSignature(before?.report) !== statusRevisionSignature(current.report)) {
|
|
12933
|
+
throw new Error(`Composite update stopped before ${member.id}: member revision changed after preflight.`);
|
|
12934
|
+
}
|
|
12935
|
+
}
|
|
12936
|
+
const args = compositeUpdateArguments(member.profile, packageName, options);
|
|
12937
|
+
console.log(`${options.dryRun ? "Plan" : "Update"} member ${member.id}:`);
|
|
12938
|
+
const result = await runMemberAgentwheel(member, workspaceRoot, args, memberChain);
|
|
12939
|
+
if (result.stdout.trim()) console.log(result.stdout.trimEnd());
|
|
12940
|
+
if (result.stderr.trim()) console.error(result.stderr.trimEnd());
|
|
11947
12941
|
}
|
|
11948
|
-
|
|
12942
|
+
}
|
|
12943
|
+
async function runCompositeInstall(workspaceRoot, profileName, profile, nameOrSource, options, behavior) {
|
|
12944
|
+
const preflight = await collectCompositeStatus(workspaceRoot, profileName, profile, options);
|
|
12945
|
+
printStatusReport(preflight);
|
|
12946
|
+
const blockers = preflight.members.filter((member) => blocksCompositeApply(member.health));
|
|
12947
|
+
if (blockers.length > 0) {
|
|
12948
|
+
throw new Error(
|
|
12949
|
+
`Composite ${behavior.apply ? "install" : "plan"} blocked before member execution: ` + blockers.map((member) => `${member.id}=${member.health}`).join(", ")
|
|
12950
|
+
);
|
|
12951
|
+
}
|
|
12952
|
+
const incomingChain = parseCompositeChain();
|
|
12953
|
+
const memberChain = [...incomingChain, compositeKey(workspaceRoot, profileName)];
|
|
12954
|
+
for (const member of profile.members) {
|
|
12955
|
+
if (behavior.apply) {
|
|
12956
|
+
const before = preflight.members.find((candidate) => candidate.id === member.id);
|
|
12957
|
+
const revalidated = await collectCompositeMembers({
|
|
12958
|
+
cliVersion: CLI_VERSION,
|
|
12959
|
+
workspaceRoot,
|
|
12960
|
+
profileName,
|
|
12961
|
+
profileTtlSeconds: profile.refreshTtlSeconds,
|
|
12962
|
+
members: [member],
|
|
12963
|
+
refresh: true,
|
|
12964
|
+
chain: incomingChain
|
|
12965
|
+
});
|
|
12966
|
+
const current = revalidated[0];
|
|
12967
|
+
if (blocksCompositeApply(current.health)) {
|
|
12968
|
+
throw new Error(`Composite install stopped before ${member.id}: revalidation is ${current.health}.`);
|
|
12969
|
+
}
|
|
12970
|
+
if (statusRevisionSignature(before?.report) !== statusRevisionSignature(current.report)) {
|
|
12971
|
+
throw new Error(`Composite install stopped before ${member.id}: member revision changed after preflight.`);
|
|
12972
|
+
}
|
|
12973
|
+
}
|
|
12974
|
+
const args = compositeInstallArguments(member.profile, nameOrSource, options, behavior.apply);
|
|
12975
|
+
console.log(`${behavior.apply ? "Install" : "Plan"} member ${member.id}:`);
|
|
12976
|
+
const result = await runMemberAgentwheel(member, workspaceRoot, args, memberChain);
|
|
12977
|
+
if (result.stdout.trim()) console.log(result.stdout.trimEnd());
|
|
12978
|
+
if (result.stderr.trim()) console.error(result.stderr.trimEnd());
|
|
12979
|
+
}
|
|
12980
|
+
}
|
|
12981
|
+
function compositeInstallArguments(profile, nameOrSource, options, apply) {
|
|
12982
|
+
const args = [apply ? "install" : "plan"];
|
|
12983
|
+
if (nameOrSource) args.push(nameOrSource);
|
|
12984
|
+
args.push("--profile", profile);
|
|
12985
|
+
if (options.refresh) args.push("--refresh");
|
|
12986
|
+
if (options.forceDrift) args.push("--force-drift");
|
|
12987
|
+
if (options.forceConflict) args.push("--force-conflict");
|
|
12988
|
+
if (options.replaceConflict) args.push("--replace-conflict");
|
|
12989
|
+
if (options.executePlugins) args.push("--execute-plugins");
|
|
12990
|
+
if (shouldReloadRuntimes(options)) args.push("--reload-runtimes");
|
|
12991
|
+
if (options.noDeps) args.push("--no-deps");
|
|
12992
|
+
if (options.frozenLock) args.push("--frozen-lock");
|
|
12993
|
+
if (options.onlySource) args.push("--only-source");
|
|
12994
|
+
for (const selection of options.select ?? []) args.push("--select", selection);
|
|
12995
|
+
for (const skill of options.skill ?? []) args.push("--skill", skill);
|
|
12996
|
+
for (const trust of options.trust ?? []) args.push("--trust", trust);
|
|
12997
|
+
if (options.yes) args.push("--yes");
|
|
12998
|
+
return args;
|
|
12999
|
+
}
|
|
13000
|
+
function compositeUpdateArguments(profile, packageName, options) {
|
|
13001
|
+
const args = ["update"];
|
|
13002
|
+
if (packageName) args.push(packageName);
|
|
13003
|
+
args.push("--profile", profile);
|
|
13004
|
+
if (options.dryRun) args.push("--dry-run");
|
|
13005
|
+
if (options.refresh) args.push("--refresh");
|
|
13006
|
+
if (options.forceDrift) args.push("--force-drift");
|
|
13007
|
+
if (options.forceConflict) args.push("--force-conflict");
|
|
13008
|
+
if (options.replaceConflict) args.push("--replace-conflict");
|
|
13009
|
+
if (options.executePlugins) args.push("--execute-plugins");
|
|
13010
|
+
if (shouldReloadRuntimes(options)) args.push("--reload-runtimes");
|
|
13011
|
+
if (options.noDeps) args.push("--no-deps");
|
|
13012
|
+
if (options.frozenLock) args.push("--frozen-lock");
|
|
13013
|
+
for (const dependency of options.dependency ?? []) args.push("--dependency", dependency);
|
|
13014
|
+
for (const trust of options.trust ?? []) args.push("--trust", trust);
|
|
13015
|
+
if (options.yes) args.push("--yes");
|
|
13016
|
+
return args;
|
|
13017
|
+
}
|
|
13018
|
+
function statusRevisionSignature(report) {
|
|
13019
|
+
if (!report) return "missing";
|
|
13020
|
+
return JSON.stringify({
|
|
13021
|
+
profile: report.profile,
|
|
13022
|
+
repository: {
|
|
13023
|
+
head: report.repository.head,
|
|
13024
|
+
ahead: report.repository.ahead,
|
|
13025
|
+
behind: report.repository.behind,
|
|
13026
|
+
dirtyCount: report.repository.dirtyCount
|
|
13027
|
+
},
|
|
13028
|
+
targets: report.targets.map((target) => ({
|
|
13029
|
+
adapter: target.adapter,
|
|
13030
|
+
installationType: target.installationType,
|
|
13031
|
+
targetRoot: target.targetRoot,
|
|
13032
|
+
manifestRevision: target.manifestRevision,
|
|
13033
|
+
graphLockPath: target.graphLockPath,
|
|
13034
|
+
packages: target.packages.map((pkg) => ({
|
|
13035
|
+
name: pkg.name,
|
|
13036
|
+
installed: pkg.installed,
|
|
13037
|
+
locked: pkg.locked,
|
|
13038
|
+
latestAllowed: pkg.latestAllowed,
|
|
13039
|
+
latestOverall: pkg.latestOverall
|
|
13040
|
+
}))
|
|
13041
|
+
})),
|
|
13042
|
+
members: report.members.map((member) => ({
|
|
13043
|
+
id: member.id,
|
|
13044
|
+
report: statusRevisionSignature(member.report)
|
|
13045
|
+
}))
|
|
13046
|
+
});
|
|
13047
|
+
}
|
|
13048
|
+
async function statusReport(workspace, profile, targets) {
|
|
13049
|
+
const repository = await collectRepositoryStatus(workspace);
|
|
13050
|
+
const repositoryHealth = repository.available && repository.ahead === 0 && repository.behind === 0 && repository.dirtyCount === 0 ? "PASS" : "WARN";
|
|
13051
|
+
return {
|
|
13052
|
+
schemaVersion: 1,
|
|
13053
|
+
command: "status",
|
|
13054
|
+
agentwheelVersion: CLI_VERSION,
|
|
13055
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13056
|
+
workspace,
|
|
13057
|
+
profile,
|
|
13058
|
+
health: worstStatusHealth([...targets.map((target) => target.health), repositoryHealth]),
|
|
13059
|
+
repository,
|
|
13060
|
+
targets,
|
|
13061
|
+
members: []
|
|
13062
|
+
};
|
|
13063
|
+
}
|
|
13064
|
+
function printStatusReport(report) {
|
|
13065
|
+
console.log(`Status ${report.health} for profile ${report.profile ?? "(direct)"} at ${report.workspace}`);
|
|
13066
|
+
console.log(
|
|
13067
|
+
`Repository: ${report.repository.available ? report.repository.branch ?? "detached" : "unavailable"}; ahead=${report.repository.ahead}; behind=${report.repository.behind}; dirty=${report.repository.dirtyCount}`
|
|
13068
|
+
);
|
|
13069
|
+
if (report.members.length > 0) {
|
|
13070
|
+
console.log("MEMBER TRANSPORT PROFILE VERSION HEALTH CACHE");
|
|
13071
|
+
for (const member of report.members) {
|
|
13072
|
+
console.log([
|
|
13073
|
+
member.id,
|
|
13074
|
+
member.transport,
|
|
13075
|
+
member.profile,
|
|
13076
|
+
member.agentwheelVersion ?? "unknown",
|
|
13077
|
+
member.health,
|
|
13078
|
+
member.stale ? "stale" : "fresh"
|
|
13079
|
+
].join(" "));
|
|
13080
|
+
if (member.error) console.log(` ${member.error}`);
|
|
13081
|
+
}
|
|
13082
|
+
}
|
|
13083
|
+
for (const target of report.targets) printTargetStatus(target);
|
|
13084
|
+
}
|
|
13085
|
+
function printTargetStatus(target) {
|
|
13086
|
+
console.log(`Status for ${target.adapter}/${target.installationType} at ${target.targetRoot} (health: ${target.health})`);
|
|
13087
|
+
console.log(target.manifestRevision ? `Install manifest: ${target.manifestEntryCount} entries, revision ${target.manifestRevision}` : "Install manifest: missing");
|
|
13088
|
+
console.log(target.graphLockPath ? `Graph lock: ${target.graphLockPath} (${target.artifactCount} artifacts)` : "Graph lock: missing");
|
|
13089
|
+
console.log("PACKAGE MODE POLICY INSTALLED LOCKED LATEST ALLOWED LATEST OVERALL STATUS");
|
|
13090
|
+
for (const pkg of target.packages) {
|
|
13091
|
+
console.log([
|
|
13092
|
+
pkg.name,
|
|
13093
|
+
pkg.mode,
|
|
13094
|
+
pkg.policy,
|
|
13095
|
+
pkg.installed ?? "-",
|
|
13096
|
+
pkg.locked ?? "-",
|
|
13097
|
+
pkg.latestAllowed ?? "-",
|
|
13098
|
+
pkg.latestOverall ?? "-",
|
|
13099
|
+
pkg.availability
|
|
13100
|
+
].join(" "));
|
|
13101
|
+
}
|
|
13102
|
+
console.log(`Artifacts: ${target.artifactCount} locked, ${target.artifacts.filter((artifact) => artifact.installed).length} installed`);
|
|
13103
|
+
if (target.error) console.log(`Pending install work: unavailable (${target.error})`);
|
|
13104
|
+
else if (target.pendingCount === 0) console.log("Pending install work: none");
|
|
13105
|
+
else console.log(`Pending install work: ${target.pendingCount} (drift=${target.driftCount}, conflict=${target.conflictCount})`);
|
|
11949
13106
|
}
|
|
11950
13107
|
async function journalStateForTarget(target, options) {
|
|
11951
13108
|
const adapterOptions = adapterOptionsForTarget(target, options);
|
|
@@ -11955,25 +13112,30 @@ async function journalStateForTarget(target, options) {
|
|
|
11955
13112
|
const state = installStateForTarget(target, adapter, adapterOptions, installationType);
|
|
11956
13113
|
return { adapter, transport, installationType, installRoot: state.installRoot, state };
|
|
11957
13114
|
}
|
|
11958
|
-
async function
|
|
13115
|
+
async function collectPendingInstallWork(target, options) {
|
|
11959
13116
|
let results = [];
|
|
11960
13117
|
try {
|
|
11961
|
-
results = await buildGraphPlansForTarget(
|
|
13118
|
+
results = await buildGraphPlansForTarget(
|
|
13119
|
+
target,
|
|
13120
|
+
void 0,
|
|
13121
|
+
{ ...options, dryRun: true, warn: options.warn ?? (() => void 0) },
|
|
13122
|
+
{ mode: "install" }
|
|
13123
|
+
);
|
|
11962
13124
|
const operations = results.flatMap((result) => result.plan.operations);
|
|
11963
13125
|
const pending = operations.filter(isPendingInstallOperation);
|
|
11964
|
-
|
|
11965
|
-
console.log("Pending install work: none");
|
|
11966
|
-
return;
|
|
11967
|
-
}
|
|
11968
|
-
const counts = [...pending.reduce((map, operation) => {
|
|
13126
|
+
const counts = Object.fromEntries([...pending.reduce((map, operation) => {
|
|
11969
13127
|
map.set(operation.action, (map.get(operation.action) ?? 0) + 1);
|
|
11970
13128
|
return map;
|
|
11971
|
-
}, /* @__PURE__ */ new Map())]
|
|
11972
|
-
|
|
11973
|
-
|
|
13129
|
+
}, /* @__PURE__ */ new Map())]);
|
|
13130
|
+
return {
|
|
13131
|
+
pendingCount: pending.length,
|
|
13132
|
+
driftCount: counts.drift ?? 0,
|
|
13133
|
+
conflictCount: counts.conflict ?? 0,
|
|
13134
|
+
counts
|
|
13135
|
+
};
|
|
11974
13136
|
} catch (error) {
|
|
11975
13137
|
const message = error instanceof Error ? error.message : String(error);
|
|
11976
|
-
|
|
13138
|
+
return { pendingCount: 0, driftCount: 0, conflictCount: 0, counts: {}, error: message };
|
|
11977
13139
|
} finally {
|
|
11978
13140
|
await Promise.all(results.map((result) => rm11(result.bundle.root, { recursive: true, force: true })));
|
|
11979
13141
|
}
|
|
@@ -11992,12 +13154,12 @@ async function printDoctor(target, options) {
|
|
|
11992
13154
|
const requestedSkills = doctorSkillRequests(target, options);
|
|
11993
13155
|
const skills = [];
|
|
11994
13156
|
for (const request of requestedSkills) {
|
|
11995
|
-
const skillPath =
|
|
13157
|
+
const skillPath = join44(state.installRoot, targetMapping.dest, request.name);
|
|
11996
13158
|
const exists = await pathExists(skillPath);
|
|
11997
13159
|
const manifestEntry = manifest?.entries.find((entry) => {
|
|
11998
13160
|
if (entry.artifactType !== "skills") return false;
|
|
11999
13161
|
const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
|
|
12000
|
-
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path ===
|
|
13162
|
+
return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join44(targetMapping.dest, request.name);
|
|
12001
13163
|
});
|
|
12002
13164
|
const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
|
|
12003
13165
|
skills.push({
|
|
@@ -12077,7 +13239,7 @@ function doctorSkillLabel(name) {
|
|
|
12077
13239
|
return `${name} skill`;
|
|
12078
13240
|
}
|
|
12079
13241
|
function isSyncwheelWorkspace(targetRoot) {
|
|
12080
|
-
return existsSync(
|
|
13242
|
+
return existsSync(join44(targetRoot, ".syncwheel", "manifest.json"));
|
|
12081
13243
|
}
|
|
12082
13244
|
function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
|
|
12083
13245
|
const args = [
|
|
@@ -12166,11 +13328,11 @@ function looksLikeSourceSpecifier(value) {
|
|
|
12166
13328
|
}
|
|
12167
13329
|
function normalizeCliPath(value) {
|
|
12168
13330
|
if (value === "~") return homedir9();
|
|
12169
|
-
if (value.startsWith("~/")) return
|
|
12170
|
-
return
|
|
13331
|
+
if (value.startsWith("~/")) return resolve22(homedir9(), value.slice(2));
|
|
13332
|
+
return resolve22(value);
|
|
12171
13333
|
}
|
|
12172
13334
|
function isHomePath(path) {
|
|
12173
|
-
return
|
|
13335
|
+
return resolve22(path) === resolve22(homedir9());
|
|
12174
13336
|
}
|
|
12175
13337
|
function adapterListFromOption(adapter) {
|
|
12176
13338
|
if (!adapter) return [];
|
|
@@ -12225,10 +13387,10 @@ function filterUninstallPlanBySelection(plan, selected) {
|
|
|
12225
13387
|
};
|
|
12226
13388
|
}
|
|
12227
13389
|
async function initPackage(root) {
|
|
12228
|
-
await
|
|
12229
|
-
await
|
|
12230
|
-
await
|
|
12231
|
-
const manifestPath =
|
|
13390
|
+
await mkdir23(join44(root, "instructions"), { recursive: true });
|
|
13391
|
+
await mkdir23(join44(root, "rules"), { recursive: true });
|
|
13392
|
+
await mkdir23(join44(root, "skills"), { recursive: true });
|
|
13393
|
+
const manifestPath = join44(root, "openpack.json");
|
|
12232
13394
|
const manifest = {
|
|
12233
13395
|
schemaVersion: 2,
|
|
12234
13396
|
name: "example/agentwheel-package",
|
|
@@ -12239,12 +13401,12 @@ async function initPackage(root) {
|
|
|
12239
13401
|
{ type: "skills", path: "skills" }
|
|
12240
13402
|
]
|
|
12241
13403
|
};
|
|
12242
|
-
await
|
|
13404
|
+
await writeFile22(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
12243
13405
|
`, "utf8");
|
|
12244
|
-
await
|
|
13406
|
+
await writeFile22(join44(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
12245
13407
|
}
|
|
12246
13408
|
async function defaultBootstrapPackage(_root) {
|
|
12247
|
-
const packageRoot = await findAgentwheelPackageRoot(
|
|
13409
|
+
const packageRoot = await findAgentwheelPackageRoot(dirname32(fileURLToPath3(import.meta.url)));
|
|
12248
13410
|
if (!packageRoot) return void 0;
|
|
12249
13411
|
return {
|
|
12250
13412
|
name: "agentwheel",
|
|
@@ -12288,10 +13450,10 @@ function withFleetExample(config) {
|
|
|
12288
13450
|
};
|
|
12289
13451
|
}
|
|
12290
13452
|
async function findAgentwheelPackageRoot(start) {
|
|
12291
|
-
let current =
|
|
13453
|
+
let current = resolve22(start);
|
|
12292
13454
|
while (true) {
|
|
12293
13455
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
12294
|
-
const parent =
|
|
13456
|
+
const parent = dirname32(current);
|
|
12295
13457
|
if (parent === current) return void 0;
|
|
12296
13458
|
current = parent;
|
|
12297
13459
|
}
|