agentwheel 0.20.17 → 0.20.19
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 +35 -0
- package/dist/{apply-ZGCJLZMZ.js → apply-QJHTL7IK.js} +3 -3
- package/dist/{chunk-FP26KSBM.js → chunk-BHT57MK5.js} +3 -3
- package/dist/{chunk-GE7F76AC.js → chunk-BNIF3FUR.js} +9 -0
- package/dist/{chunk-A44XYLUT.js → chunk-L37KQVRS.js} +2 -2
- package/dist/{chunk-U2GRG4UI.js → chunk-QABA4WUP.js} +1 -1
- package/dist/{identify-O54QXPQR.js → identify-M6IA4RS7.js} +2 -2
- package/dist/index.js +1019 -361
- package/dist/{transaction-4KBNHDQX.js → transaction-JHZJWBII.js} +2 -2
- package/openpack.json +1 -1
- package/package.json +1 -1
- package/skills/agentwheel/SKILL.md +8 -1
- package/skills/agentwheel-discovery/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
uninstall,
|
|
38
38
|
withManifestRevision,
|
|
39
39
|
writeInstallManifest
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-L37KQVRS.js";
|
|
41
41
|
import {
|
|
42
42
|
CURRENT_WORKSPACE_SCHEMA_VERSION,
|
|
43
43
|
GovernedMutation,
|
|
@@ -98,26 +98,27 @@ import {
|
|
|
98
98
|
workspaceSelectionImportSchema,
|
|
99
99
|
writeApplyJournal,
|
|
100
100
|
writeWorkspaceConfig
|
|
101
|
-
} from "./chunk-
|
|
101
|
+
} from "./chunk-BHT57MK5.js";
|
|
102
102
|
import {
|
|
103
103
|
inferSourceDriverName
|
|
104
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-QABA4WUP.js";
|
|
105
105
|
import {
|
|
106
106
|
hashPath,
|
|
107
107
|
isAlreadyExists,
|
|
108
108
|
isIgnoredGeneratedEntry,
|
|
109
109
|
listFiles,
|
|
110
110
|
pathExists,
|
|
111
|
+
removeOnFailure,
|
|
111
112
|
withFilesystemLock,
|
|
112
113
|
writeJsonAtomic
|
|
113
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-BNIF3FUR.js";
|
|
114
115
|
|
|
115
116
|
// src/cli/index.ts
|
|
116
|
-
import { createHash as
|
|
117
|
+
import { createHash as createHash16 } from "crypto";
|
|
117
118
|
import { existsSync } from "fs";
|
|
118
|
-
import { mkdir as mkdir16, mkdtemp as
|
|
119
|
-
import { homedir as homedir12, tmpdir as
|
|
120
|
-
import { basename as basename21, dirname as dirname23, isAbsolute as
|
|
119
|
+
import { mkdir as mkdir16, mkdtemp as mkdtemp3, realpath as realpath3, rm as rm10, writeFile as writeFile13 } from "fs/promises";
|
|
120
|
+
import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
|
|
121
|
+
import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute5, join as join46, relative as relative11, resolve as resolve27 } from "path";
|
|
121
122
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
122
123
|
import { Command } from "commander";
|
|
123
124
|
|
|
@@ -3113,14 +3114,14 @@ async function writeMermaidAsset(response) {
|
|
|
3113
3114
|
"content-type": "application/javascript; charset=utf-8",
|
|
3114
3115
|
"cache-control": "public, max-age=3600"
|
|
3115
3116
|
});
|
|
3116
|
-
await new Promise((
|
|
3117
|
+
await new Promise((resolve28) => {
|
|
3117
3118
|
const stream = createReadStream(assetPath);
|
|
3118
3119
|
stream.on("error", () => {
|
|
3119
3120
|
if (!response.headersSent) writeText(response, 404, "Mermaid asset unavailable.\n");
|
|
3120
3121
|
else response.destroy();
|
|
3121
|
-
|
|
3122
|
+
resolve28();
|
|
3122
3123
|
});
|
|
3123
|
-
stream.on("end",
|
|
3124
|
+
stream.on("end", resolve28);
|
|
3124
3125
|
stream.pipe(response);
|
|
3125
3126
|
});
|
|
3126
3127
|
}
|
|
@@ -3147,14 +3148,14 @@ function escapeHtml2(value) {
|
|
|
3147
3148
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3148
3149
|
}
|
|
3149
3150
|
function listen(server, port, bind) {
|
|
3150
|
-
return new Promise((
|
|
3151
|
+
return new Promise((resolve28, reject) => {
|
|
3151
3152
|
const onError = (error) => {
|
|
3152
3153
|
server.off("listening", onListening);
|
|
3153
3154
|
reject(error);
|
|
3154
3155
|
};
|
|
3155
3156
|
const onListening = () => {
|
|
3156
3157
|
server.off("error", onError);
|
|
3157
|
-
|
|
3158
|
+
resolve28();
|
|
3158
3159
|
};
|
|
3159
3160
|
server.once("error", onError);
|
|
3160
3161
|
server.once("listening", onListening);
|
|
@@ -3162,7 +3163,7 @@ function listen(server, port, bind) {
|
|
|
3162
3163
|
});
|
|
3163
3164
|
}
|
|
3164
3165
|
function waitForShutdown(server, beforeClose) {
|
|
3165
|
-
return new Promise((
|
|
3166
|
+
return new Promise((resolve28) => {
|
|
3166
3167
|
let closing = false;
|
|
3167
3168
|
const shutdown = () => {
|
|
3168
3169
|
if (closing) return;
|
|
@@ -3170,7 +3171,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3170
3171
|
process.off("SIGINT", shutdown);
|
|
3171
3172
|
process.off("SIGTERM", shutdown);
|
|
3172
3173
|
beforeClose();
|
|
3173
|
-
server.close(() =>
|
|
3174
|
+
server.close(() => resolve28());
|
|
3174
3175
|
};
|
|
3175
3176
|
process.once("SIGINT", shutdown);
|
|
3176
3177
|
process.once("SIGTERM", shutdown);
|
|
@@ -3180,7 +3181,7 @@ function waitForShutdown(server, beforeClose) {
|
|
|
3180
3181
|
process.off("SIGINT", shutdown);
|
|
3181
3182
|
process.off("SIGTERM", shutdown);
|
|
3182
3183
|
beforeClose();
|
|
3183
|
-
|
|
3184
|
+
resolve28();
|
|
3184
3185
|
});
|
|
3185
3186
|
});
|
|
3186
3187
|
}
|
|
@@ -3733,7 +3734,7 @@ async function fetchClawHubPackage(fetchImpl, packageName) {
|
|
|
3733
3734
|
throw new Error(`ClawHub lookup failed for ${packageName}`);
|
|
3734
3735
|
}
|
|
3735
3736
|
async function delay(ms) {
|
|
3736
|
-
await new Promise((
|
|
3737
|
+
await new Promise((resolve28) => setTimeout(resolve28, ms));
|
|
3737
3738
|
}
|
|
3738
3739
|
function parseClawHubSource(source) {
|
|
3739
3740
|
if (!source.startsWith(sourcePrefix)) {
|
|
@@ -3910,7 +3911,7 @@ async function withGitCacheMaintenanceLock(cacheRoot, timeoutMs, fn) {
|
|
|
3910
3911
|
if (Date.now() - started > timeoutMs) {
|
|
3911
3912
|
throw new Error(`Timed out waiting for git cache maintenance lock at ${lockPath}`);
|
|
3912
3913
|
}
|
|
3913
|
-
await new Promise((
|
|
3914
|
+
await new Promise((resolve28) => setTimeout(resolve28, 50));
|
|
3914
3915
|
}
|
|
3915
3916
|
}
|
|
3916
3917
|
try {
|
|
@@ -4079,6 +4080,58 @@ function errorMessage3(error) {
|
|
|
4079
4080
|
return error instanceof Error ? error.message : String(error);
|
|
4080
4081
|
}
|
|
4081
4082
|
|
|
4083
|
+
// src/source/git-source.ts
|
|
4084
|
+
import { posix } from "path";
|
|
4085
|
+
var SUBPATH_SEPARATOR = "//";
|
|
4086
|
+
function parseGitSource(source) {
|
|
4087
|
+
if (source.startsWith("github:")) {
|
|
4088
|
+
const rest = source.slice("github:".length);
|
|
4089
|
+
const hashIndex = rest.indexOf("#");
|
|
4090
|
+
const repo = hashIndex >= 0 ? rest.slice(0, hashIndex) : rest;
|
|
4091
|
+
if (!repo.includes("/")) throw new Error(`Invalid GitHub source: ${source}`);
|
|
4092
|
+
return {
|
|
4093
|
+
url: `https://github.com/${repo.replace(/\.git$/i, "")}.git`,
|
|
4094
|
+
...splitFragment(hashIndex >= 0 ? rest.slice(hashIndex + 1) : void 0, source)
|
|
4095
|
+
};
|
|
4096
|
+
}
|
|
4097
|
+
if (source.startsWith("git:")) {
|
|
4098
|
+
const rest = source.slice("git:".length);
|
|
4099
|
+
const hashIndex = rest.lastIndexOf("#");
|
|
4100
|
+
if (hashIndex < 0) return { url: rest };
|
|
4101
|
+
return { url: rest.slice(0, hashIndex), ...splitFragment(rest.slice(hashIndex + 1), source) };
|
|
4102
|
+
}
|
|
4103
|
+
throw new Error(`Invalid git source: ${source}`);
|
|
4104
|
+
}
|
|
4105
|
+
function formatGitSource(url, ref, subpath) {
|
|
4106
|
+
return subpath ? `git:${url}#${ref}${SUBPATH_SEPARATOR}${subpath}` : `git:${url}#${ref}`;
|
|
4107
|
+
}
|
|
4108
|
+
function normalizeGitSubpath(subpath, source) {
|
|
4109
|
+
if (subpath === void 0) return void 0;
|
|
4110
|
+
if (subpath.includes("\\")) throw new Error(`Git source subpath must use forward slashes: ${source}`);
|
|
4111
|
+
if (subpath.startsWith("/")) throw new Error(`Git source subpath must be relative to the repository: ${source}`);
|
|
4112
|
+
const normalized = posix.normalize(subpath).replace(/\/+$/g, "");
|
|
4113
|
+
if (normalized === "." || normalized === "") return void 0;
|
|
4114
|
+
if (normalized === ".." || normalized.startsWith("../")) {
|
|
4115
|
+
throw new Error(`Git source subpath escapes the repository: ${source}`);
|
|
4116
|
+
}
|
|
4117
|
+
return normalized;
|
|
4118
|
+
}
|
|
4119
|
+
function resolveGitRelativeSubpath(declaringSubpath, relative12, source) {
|
|
4120
|
+
const joined = posix.normalize(posix.join(declaringSubpath ?? ".", relative12));
|
|
4121
|
+
if (joined === ".." || joined.startsWith("../")) {
|
|
4122
|
+
throw new Error(`Relative dependency '${relative12}' escapes the git repository it is declared in: ${source}`);
|
|
4123
|
+
}
|
|
4124
|
+
return normalizeGitSubpath(joined, source);
|
|
4125
|
+
}
|
|
4126
|
+
function splitFragment(fragment, source) {
|
|
4127
|
+
if (fragment === void 0) return {};
|
|
4128
|
+
const separator = fragment.indexOf(SUBPATH_SEPARATOR);
|
|
4129
|
+
if (separator < 0) return fragment ? { ref: fragment } : {};
|
|
4130
|
+
const ref = fragment.slice(0, separator);
|
|
4131
|
+
const subpath = normalizeGitSubpath(fragment.slice(separator + SUBPATH_SEPARATOR.length), source);
|
|
4132
|
+
return { ...ref ? { ref } : {}, ...subpath ? { subpath } : {} };
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4082
4135
|
// src/source/git.ts
|
|
4083
4136
|
var execFileAsync = promisify(execFile);
|
|
4084
4137
|
var GitSourceDriver = class {
|
|
@@ -4138,15 +4191,18 @@ var GitSourceDriver = class {
|
|
|
4138
4191
|
return { path, leasePath };
|
|
4139
4192
|
}
|
|
4140
4193
|
);
|
|
4141
|
-
const
|
|
4142
|
-
|
|
4194
|
+
const packageRoot = parsed.subpath ? join19(snapshot.path, parsed.subpath) : snapshot.path;
|
|
4195
|
+
if (parsed.subpath && !await isDirectory(packageRoot)) {
|
|
4196
|
+
throw new Error(`Git source subpath '${parsed.subpath}' does not exist at ${resolvedCommit} in ${parsed.url}`);
|
|
4197
|
+
}
|
|
4198
|
+
const manifest = await readPackageManifest(packageRoot);
|
|
4143
4199
|
return {
|
|
4144
4200
|
...resolved,
|
|
4145
|
-
resolvedPath:
|
|
4201
|
+
resolvedPath: packageRoot,
|
|
4146
4202
|
packageName: manifest?.name,
|
|
4147
4203
|
packageVersion: manifest?.version,
|
|
4148
4204
|
resolvedCommit,
|
|
4149
|
-
sourceHash: await hashPath(
|
|
4205
|
+
sourceHash: await hashPath(packageRoot),
|
|
4150
4206
|
cacheLeasePath: snapshot.leasePath
|
|
4151
4207
|
};
|
|
4152
4208
|
}, "git cache");
|
|
@@ -4164,22 +4220,12 @@ var GitSourceDriver = class {
|
|
|
4164
4220
|
return resolved;
|
|
4165
4221
|
}
|
|
4166
4222
|
};
|
|
4167
|
-
function
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
return { url: `https://github.com/${repo}.git`, ref };
|
|
4173
|
-
}
|
|
4174
|
-
if (source.startsWith("git:")) {
|
|
4175
|
-
const rest = source.slice("git:".length);
|
|
4176
|
-
const hashIndex = rest.lastIndexOf("#");
|
|
4177
|
-
if (hashIndex >= 0) {
|
|
4178
|
-
return { url: rest.slice(0, hashIndex), ref: rest.slice(hashIndex + 1) };
|
|
4179
|
-
}
|
|
4180
|
-
return { url: rest };
|
|
4223
|
+
async function isDirectory(path) {
|
|
4224
|
+
try {
|
|
4225
|
+
return (await stat4(path)).isDirectory();
|
|
4226
|
+
} catch {
|
|
4227
|
+
return false;
|
|
4181
4228
|
}
|
|
4182
|
-
throw new Error(`Invalid git source: ${source}`);
|
|
4183
4229
|
}
|
|
4184
4230
|
function cachePathFor2(url, cacheRoot) {
|
|
4185
4231
|
const root = cacheRoot ? resolve5(cacheRoot) : join19(homedir2(), ".agentwheel", "cache");
|
|
@@ -5337,7 +5383,8 @@ function claudeAgentName(artifact) {
|
|
|
5337
5383
|
|
|
5338
5384
|
// src/staging/staging.ts
|
|
5339
5385
|
async function stageSource(driver, source, options = {}) {
|
|
5340
|
-
|
|
5386
|
+
const raw = await stageSourceRaw(driver, source, options);
|
|
5387
|
+
return removeOnFailure(raw.root, () => renderStagedBundle(raw, options));
|
|
5341
5388
|
}
|
|
5342
5389
|
async function stageSourceRaw(driver, source, options = {}) {
|
|
5343
5390
|
const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
|
|
@@ -5352,25 +5399,27 @@ async function stageResolvedSourceRaw(driver, resolved) {
|
|
|
5352
5399
|
const artifacts = await driver.list(resolved);
|
|
5353
5400
|
return stageResolvedArtifactsRaw(resolved, artifacts);
|
|
5354
5401
|
}
|
|
5355
|
-
async function stageResolvedArtifactsRaw(resolved, artifacts) {
|
|
5356
|
-
const root = await mkdtemp(join27(
|
|
5402
|
+
async function stageResolvedArtifactsRaw(resolved, artifacts, parentDir = tmpdir2()) {
|
|
5403
|
+
const root = await mkdtemp(join27(parentDir, "agentwheel-stage-"));
|
|
5357
5404
|
const stagedArtifacts = [];
|
|
5358
|
-
|
|
5359
|
-
const
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5405
|
+
await removeOnFailure(root, async () => {
|
|
5406
|
+
for (const artifact of artifacts) {
|
|
5407
|
+
const stagedPath = join27(root, artifact.relativePath);
|
|
5408
|
+
await mkdir11(dirname13(stagedPath), { recursive: true });
|
|
5409
|
+
await cp3(artifact.sourcePath, stagedPath, {
|
|
5410
|
+
recursive: artifact.kind === "dir",
|
|
5411
|
+
dereference: true,
|
|
5412
|
+
filter: (path) => !isIgnoredGeneratedEntry(basename16(path))
|
|
5413
|
+
});
|
|
5414
|
+
await composeAssets(artifact, resolved.resolvedPath, stagedPath);
|
|
5415
|
+
stagedArtifacts.push({
|
|
5416
|
+
...artifact,
|
|
5417
|
+
stagedPath,
|
|
5418
|
+
hash: await hashPath(stagedPath),
|
|
5419
|
+
channel: artifact.channel ?? "managed"
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
5422
|
+
});
|
|
5374
5423
|
return {
|
|
5375
5424
|
root,
|
|
5376
5425
|
source: resolved,
|
|
@@ -5527,8 +5576,7 @@ import { dirname as dirname15, join as join30, relative as relative6 } from "pat
|
|
|
5527
5576
|
|
|
5528
5577
|
// src/resolve/graph.ts
|
|
5529
5578
|
import { createHash as createHash6 } from "crypto";
|
|
5530
|
-
import {
|
|
5531
|
-
import { tmpdir as tmpdir3 } from "os";
|
|
5579
|
+
import { readdir as readdir7, readFile as readFile17, stat as stat11 } from "fs/promises";
|
|
5532
5580
|
import { basename as basename17, extname as extname4, join as join29 } from "path";
|
|
5533
5581
|
|
|
5534
5582
|
// src/model/workspace-composition.ts
|
|
@@ -5789,7 +5837,7 @@ var RegistryClient = class {
|
|
|
5789
5837
|
}
|
|
5790
5838
|
};
|
|
5791
5839
|
async function resolvePackageSource(source, workspaceRoot, options = {}) {
|
|
5792
|
-
const { isExplicitSource } = await import("./identify-
|
|
5840
|
+
const { isExplicitSource } = await import("./identify-M6IA4RS7.js");
|
|
5793
5841
|
if (await isExplicitSource(source)) return { source };
|
|
5794
5842
|
const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
|
|
5795
5843
|
if (!entry) {
|
|
@@ -5831,6 +5879,17 @@ async function normalizeDependencySource(source, options) {
|
|
|
5831
5879
|
if (isBareRegistryName(trimmed)) {
|
|
5832
5880
|
return normalizeRegistrySource(trimmed, options);
|
|
5833
5881
|
}
|
|
5882
|
+
if (options.declaringGitSource && (trimmed === "." || trimmed === ".." || trimmed.startsWith("./") || trimmed.startsWith("../"))) {
|
|
5883
|
+
const declaring = options.declaringGitSource;
|
|
5884
|
+
const subpath = resolveGitRelativeSubpath(declaring.subpath, trimmed, source);
|
|
5885
|
+
const normalizedSource = formatGitSource(declaring.url, declaring.commit, subpath);
|
|
5886
|
+
return {
|
|
5887
|
+
source: normalizedSource,
|
|
5888
|
+
normalizedSource,
|
|
5889
|
+
driver: "git",
|
|
5890
|
+
requestedRef: declaring.commit
|
|
5891
|
+
};
|
|
5892
|
+
}
|
|
5834
5893
|
if (isLocalSource(trimmed)) {
|
|
5835
5894
|
const path = localSourcePath(trimmed);
|
|
5836
5895
|
const resolvedPath = resolveLocalPath(path, options.declaringPackageRoot);
|
|
@@ -5841,10 +5900,10 @@ async function normalizeDependencySource(source, options) {
|
|
|
5841
5900
|
};
|
|
5842
5901
|
}
|
|
5843
5902
|
if (trimmed.startsWith("github:") || trimmed.startsWith("git:")) {
|
|
5844
|
-
const parsed =
|
|
5903
|
+
const parsed = parseGitSource(trimmed);
|
|
5845
5904
|
const requestedRef = options.ref ?? parsed.ref ?? "HEAD";
|
|
5846
5905
|
const url = normalizeGitRemoteUrl(parsed.url, options.declaringPackageRoot);
|
|
5847
|
-
const normalizedSource =
|
|
5906
|
+
const normalizedSource = formatGitSource(url, requestedRef, parsed.subpath);
|
|
5848
5907
|
return {
|
|
5849
5908
|
source: normalizedSource,
|
|
5850
5909
|
normalizedSource,
|
|
@@ -5921,20 +5980,6 @@ async function normalizeRegistrySource(name, options) {
|
|
|
5921
5980
|
normalizedSource: `registry:${name}:${resolved.normalizedSource}`
|
|
5922
5981
|
};
|
|
5923
5982
|
}
|
|
5924
|
-
function parseGitSource2(source) {
|
|
5925
|
-
if (source.startsWith("github:")) {
|
|
5926
|
-
const rest2 = source.slice("github:".length);
|
|
5927
|
-
const [repo, ref] = rest2.split("#", 2);
|
|
5928
|
-
if (!repo.includes("/")) throw new Error(`Invalid GitHub dependency source: ${source}`);
|
|
5929
|
-
return { url: `https://github.com/${repo.replace(/\.git$/i, "")}.git`, ref };
|
|
5930
|
-
}
|
|
5931
|
-
const rest = source.slice("git:".length);
|
|
5932
|
-
const hashIndex = rest.lastIndexOf("#");
|
|
5933
|
-
if (hashIndex >= 0) {
|
|
5934
|
-
return { url: rest.slice(0, hashIndex), ref: rest.slice(hashIndex + 1) };
|
|
5935
|
-
}
|
|
5936
|
-
return { url: rest };
|
|
5937
|
-
}
|
|
5938
5983
|
function normalizeGitRemoteUrl(url, declaringPackageRoot) {
|
|
5939
5984
|
if (url.startsWith("./") || url.startsWith("../") || url.startsWith("/") || url === "~" || url.startsWith("~/")) {
|
|
5940
5985
|
return resolveLocalPath(url, declaringPackageRoot);
|
|
@@ -5986,7 +6031,6 @@ var SnapshotConflictError = class extends Error {
|
|
|
5986
6031
|
var cacheLocks = /* @__PURE__ */ new Map();
|
|
5987
6032
|
async function resolveDependencyGraph(roots, options) {
|
|
5988
6033
|
if (roots.length === 0) throw new Error("At least one graph root is required.");
|
|
5989
|
-
const graphRoot = await mkdtemp2(join29(tmpdir3(), "agentwheel-graph-"));
|
|
5990
6034
|
const fetchCache = /* @__PURE__ */ new Map();
|
|
5991
6035
|
const nodesByKey = /* @__PURE__ */ new Map();
|
|
5992
6036
|
const nodesBySource = /* @__PURE__ */ new Map();
|
|
@@ -6052,7 +6096,6 @@ async function resolveDependencyGraph(roots, options) {
|
|
|
6052
6096
|
}
|
|
6053
6097
|
detectDirectCollisions(rawNodes);
|
|
6054
6098
|
return {
|
|
6055
|
-
root: graphRoot,
|
|
6056
6099
|
roots: rootResults.sort((a, b) => a.rootId.localeCompare(b.rootId)),
|
|
6057
6100
|
nodes: rawNodes.map((state) => state.node),
|
|
6058
6101
|
rawNodes,
|
|
@@ -6094,6 +6137,7 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
|
|
|
6094
6137
|
let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
|
|
6095
6138
|
let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
|
|
6096
6139
|
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
6140
|
+
declaringGitSource: requirement.declaringGitSource,
|
|
6097
6141
|
workspaceRoot: options.workspaceRoot,
|
|
6098
6142
|
ref: requirement.ref,
|
|
6099
6143
|
registryClient: options.registryClient
|
|
@@ -6101,6 +6145,7 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
|
|
|
6101
6145
|
if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
|
|
6102
6146
|
const declared = await normalizeDependencySource(requirement.source, {
|
|
6103
6147
|
declaringPackageRoot: requirement.declaringPackageRoot,
|
|
6148
|
+
declaringGitSource: requirement.declaringGitSource,
|
|
6104
6149
|
workspaceRoot: options.workspaceRoot,
|
|
6105
6150
|
ref: requirement.ref,
|
|
6106
6151
|
registryClient: options.registryClient
|
|
@@ -6378,6 +6423,7 @@ function dependencyRequirement(state, fetched, alias, dependency, select, chain,
|
|
|
6378
6423
|
useLock: dependency.mode !== "tracking" || options.lockedResolution === true && !updateClosure,
|
|
6379
6424
|
updateClosure,
|
|
6380
6425
|
declaringPackageRoot: fetched.resolved.resolvedPath,
|
|
6426
|
+
declaringGitSource: declaringGitSource(fetched),
|
|
6381
6427
|
requiredBy: state.node.id,
|
|
6382
6428
|
alias,
|
|
6383
6429
|
parentId: state.node.id,
|
|
@@ -6776,8 +6822,8 @@ function verifyIntegrity(integrity, sourceHash, label) {
|
|
|
6776
6822
|
async function withCachePathLock(path, fn) {
|
|
6777
6823
|
const previous = cacheLocks.get(path) ?? Promise.resolve();
|
|
6778
6824
|
let release = () => void 0;
|
|
6779
|
-
const current = previous.then(() => new Promise((
|
|
6780
|
-
release =
|
|
6825
|
+
const current = previous.then(() => new Promise((resolve28) => {
|
|
6826
|
+
release = resolve28;
|
|
6781
6827
|
}));
|
|
6782
6828
|
cacheLocks.set(path, current);
|
|
6783
6829
|
await previous;
|
|
@@ -6885,6 +6931,15 @@ async function mapLimit(items, limit, fn) {
|
|
|
6885
6931
|
if (failure !== void 0) throw failure;
|
|
6886
6932
|
return out;
|
|
6887
6933
|
}
|
|
6934
|
+
function declaringGitSource(fetched) {
|
|
6935
|
+
if (fetched.resolved.driver !== "git" || !fetched.resolved.resolvedCommit) return void 0;
|
|
6936
|
+
const parsed = parseGitSource(fetched.resolved.source);
|
|
6937
|
+
return {
|
|
6938
|
+
url: normalizeGitRemoteUrl(parsed.url, fetched.resolved.resolvedPath),
|
|
6939
|
+
commit: fetched.resolved.resolvedCommit,
|
|
6940
|
+
subpath: parsed.subpath
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6888
6943
|
|
|
6889
6944
|
// src/lifecycle/customization.ts
|
|
6890
6945
|
async function remember(workspaceRoot, runtime, text) {
|
|
@@ -7039,7 +7094,7 @@ import { rm as rm7 } from "fs/promises";
|
|
|
7039
7094
|
// src/lifecycle/source-plan.ts
|
|
7040
7095
|
import { createHash as createHash8 } from "crypto";
|
|
7041
7096
|
import { mkdir as mkdir14, readdir as readdir9 } from "fs/promises";
|
|
7042
|
-
import { basename as basename18, dirname as dirname17, join as join33, posix as
|
|
7097
|
+
import { basename as basename18, dirname as dirname17, join as join33, posix as posix3, resolve as resolve16 } from "path";
|
|
7043
7098
|
|
|
7044
7099
|
// src/resolve/graph-diff.ts
|
|
7045
7100
|
function diffGraphLocks(previous, next) {
|
|
@@ -7202,11 +7257,14 @@ function formatSelectionImport(root) {
|
|
|
7202
7257
|
|
|
7203
7258
|
// src/resolve/render.ts
|
|
7204
7259
|
import { createHash as createHash7 } from "crypto";
|
|
7205
|
-
import { readFile as readFile18, mkdtemp as
|
|
7206
|
-
import { tmpdir as
|
|
7260
|
+
import { readFile as readFile18, mkdtemp as mkdtemp2 } from "fs/promises";
|
|
7261
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
7207
7262
|
import { join as join31 } from "path";
|
|
7208
7263
|
async function renderGraphForTarget(graph, targetContext = {}) {
|
|
7209
|
-
const root = await
|
|
7264
|
+
const root = await mkdtemp2(join31(tmpdir3(), "agentwheel-render-"));
|
|
7265
|
+
return removeOnFailure(root, () => renderGraphIntoRoot(root, graph, targetContext));
|
|
7266
|
+
}
|
|
7267
|
+
async function renderGraphIntoRoot(root, graph, targetContext) {
|
|
7210
7268
|
const artifacts = [];
|
|
7211
7269
|
const stagedNodes = /* @__PURE__ */ new Map();
|
|
7212
7270
|
const includeEdges = /* @__PURE__ */ new Map();
|
|
@@ -7214,7 +7272,7 @@ async function renderGraphForTarget(graph, targetContext = {}) {
|
|
|
7214
7272
|
for (const rawNode of graph.rawNodes) {
|
|
7215
7273
|
const ownsCompositionRules = rawNode.depth === 0 && rawNode.manifest?.schemaVersion === 3 && Boolean(rawNode.manifest.compositionRules?.length);
|
|
7216
7274
|
if (rawNode.node.selected.length === 0 && !ownsCompositionRules) continue;
|
|
7217
|
-
const rawBundle = await stageResolvedArtifactsRaw(rawNode.resolved, rawNode.artifacts);
|
|
7275
|
+
const rawBundle = await stageResolvedArtifactsRaw(rawNode.resolved, rawNode.artifacts, root);
|
|
7218
7276
|
const fragmentCustomized = targetContext.workspaceRoot && targetContext.adapter ? await applyFragmentCustomizations(rawBundle.artifacts, {
|
|
7219
7277
|
workspaceRoot: targetContext.workspaceRoot,
|
|
7220
7278
|
adapter: targetContext.adapter,
|
|
@@ -8081,7 +8139,7 @@ function containsPath(parent, candidate) {
|
|
|
8081
8139
|
}
|
|
8082
8140
|
|
|
8083
8141
|
// src/model/target-state.ts
|
|
8084
|
-
import { posix, resolve as resolve15 } from "path";
|
|
8142
|
+
import { posix as posix2, resolve as resolve15 } from "path";
|
|
8085
8143
|
var targetEvolutionFields = /* @__PURE__ */ new Set([
|
|
8086
8144
|
"adapterCodeHash",
|
|
8087
8145
|
"adapterConfig",
|
|
@@ -8114,7 +8172,7 @@ function persistentTargetParts(value) {
|
|
|
8114
8172
|
);
|
|
8115
8173
|
}
|
|
8116
8174
|
function normalizeInstallRoot(path, transportKind) {
|
|
8117
|
-
return transportKind === "ssh" ?
|
|
8175
|
+
return transportKind === "ssh" ? posix2.normalize(path) : resolve15(path);
|
|
8118
8176
|
}
|
|
8119
8177
|
function targetEndpoint(value, transportKind) {
|
|
8120
8178
|
if (transportKind !== "ssh") return { kind: "local" };
|
|
@@ -8271,85 +8329,87 @@ async function createGraphSourcePlan(options) {
|
|
|
8271
8329
|
noDeps: options.noDeps,
|
|
8272
8330
|
warn
|
|
8273
8331
|
});
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
|
|
8281
|
-
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
8282
|
-
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
8283
|
-
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
8284
|
-
if (normalizeInstallRoot2(resolvedInstallRoot, transport.kind) !== normalizeInstallRoot2(installRoot, transport.kind)) {
|
|
8285
|
-
throw new Error(
|
|
8286
|
-
`Adapter ${options.adapter.name} resolved a different install root after rendering; target state identity cannot be proven.`
|
|
8332
|
+
return removeOnFailure(bundle.root, async () => {
|
|
8333
|
+
const desiredArtifacts = filterArtifactsByAdapterTargets(
|
|
8334
|
+
desiredArtifactsFromGraphBundle(bundle),
|
|
8335
|
+
options.adapter,
|
|
8336
|
+
installationType,
|
|
8337
|
+
{ warn }
|
|
8287
8338
|
);
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
{
|
|
8297
|
-
installationType: resolvedInstallationType,
|
|
8298
|
-
stateKey,
|
|
8299
|
-
workspaceOwner,
|
|
8300
|
-
expectedFromWorkspaceOwner: options.expectedFromWorkspaceOwner,
|
|
8301
|
-
graphLockDigest
|
|
8339
|
+
const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
|
|
8340
|
+
const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
|
|
8341
|
+
const graphLockDigest = digestGraphLock(bundle.graphLock);
|
|
8342
|
+
const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
|
|
8343
|
+
if (normalizeInstallRoot2(resolvedInstallRoot, transport.kind) !== normalizeInstallRoot2(installRoot, transport.kind)) {
|
|
8344
|
+
throw new Error(
|
|
8345
|
+
`Adapter ${options.adapter.name} resolved a different install root after rendering; target state identity cannot be proven.`
|
|
8346
|
+
);
|
|
8302
8347
|
}
|
|
8303
|
-
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8307
|
-
|
|
8308
|
-
|
|
8309
|
-
forceDrift: options.forceDrift,
|
|
8310
|
-
forceConflict: options.forceConflict,
|
|
8311
|
-
replaceConflict: options.replaceConflict,
|
|
8312
|
-
stateMigration: priorState.migration,
|
|
8313
|
-
warn
|
|
8314
|
-
});
|
|
8315
|
-
const plan = priorState.migration && !basePlan.stateMigration ? { ...basePlan, stateMigration: priorState.migration } : basePlan;
|
|
8316
|
-
plan.targetStateFilePreconditions = {
|
|
8317
|
-
graphLockPath,
|
|
8318
|
-
graphLockRevision: stableLock ? digestGraphLock(stableLock) : null,
|
|
8319
|
-
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
8320
|
-
};
|
|
8321
|
-
let foreignStateObservations = [];
|
|
8322
|
-
if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
|
|
8323
|
-
foreignStateObservations = await assertNoForeignWorkspaceState({
|
|
8324
|
-
installRoot: resolvedInstallRoot,
|
|
8325
|
-
adapter: options.adapter.name,
|
|
8348
|
+
const manifest = priorState.manifest;
|
|
8349
|
+
const basePlan = options.retireExactMcp ? await createExactMcpRetirementPlan(
|
|
8350
|
+
desiredArtifacts,
|
|
8351
|
+
options.adapter,
|
|
8352
|
+
options.targetRoot,
|
|
8353
|
+
manifest,
|
|
8326
8354
|
transport,
|
|
8327
|
-
|
|
8355
|
+
{
|
|
8356
|
+
installationType: resolvedInstallationType,
|
|
8357
|
+
stateKey,
|
|
8358
|
+
workspaceOwner,
|
|
8359
|
+
expectedFromWorkspaceOwner: options.expectedFromWorkspaceOwner,
|
|
8360
|
+
graphLockDigest
|
|
8361
|
+
}
|
|
8362
|
+
) : await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
|
|
8363
|
+
baseRevision: manifest?.revision ?? null,
|
|
8364
|
+
graphLockDigest,
|
|
8328
8365
|
workspaceOwner,
|
|
8329
|
-
|
|
8366
|
+
installationType: resolvedInstallationType,
|
|
8330
8367
|
stateKey,
|
|
8331
|
-
|
|
8332
|
-
|
|
8333
|
-
|
|
8368
|
+
forceDrift: options.forceDrift,
|
|
8369
|
+
forceConflict: options.forceConflict,
|
|
8370
|
+
replaceConflict: options.replaceConflict,
|
|
8371
|
+
stateMigration: priorState.migration,
|
|
8372
|
+
warn
|
|
8334
8373
|
});
|
|
8335
|
-
plan
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8374
|
+
const plan = priorState.migration && !basePlan.stateMigration ? { ...basePlan, stateMigration: priorState.migration } : basePlan;
|
|
8375
|
+
plan.targetStateFilePreconditions = {
|
|
8376
|
+
graphLockPath,
|
|
8377
|
+
graphLockRevision: stableLock ? digestGraphLock(stableLock) : null,
|
|
8378
|
+
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
8379
|
+
};
|
|
8380
|
+
let foreignStateObservations = [];
|
|
8381
|
+
if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
|
|
8382
|
+
foreignStateObservations = await assertNoForeignWorkspaceState({
|
|
8383
|
+
installRoot: resolvedInstallRoot,
|
|
8384
|
+
adapter: options.adapter.name,
|
|
8385
|
+
transport,
|
|
8386
|
+
workspaceRoot,
|
|
8387
|
+
workspaceOwner,
|
|
8388
|
+
globalRoot: options.globalRoot,
|
|
8389
|
+
stateKey,
|
|
8390
|
+
migratingStateKey: priorState.migration?.fromStateKey,
|
|
8391
|
+
plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
|
|
8392
|
+
plannedOperations: plan.operations
|
|
8393
|
+
});
|
|
8394
|
+
plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
|
|
8395
|
+
}
|
|
8396
|
+
return {
|
|
8397
|
+
plan,
|
|
8398
|
+
graph,
|
|
8399
|
+
bundle,
|
|
8400
|
+
desiredArtifacts,
|
|
8401
|
+
graphLockPath,
|
|
8402
|
+
graphLockDigest,
|
|
8403
|
+
targetFingerprint,
|
|
8404
|
+
warnings,
|
|
8405
|
+
newTransitiveSources: trustEvaluation.promptSources,
|
|
8406
|
+
graphDiff,
|
|
8407
|
+
recoveredPendingApply,
|
|
8408
|
+
previousManifest: priorState.manifest,
|
|
8409
|
+
previousGraphLock: priorState.graphLock,
|
|
8410
|
+
foreignStateObservations
|
|
8411
|
+
};
|
|
8412
|
+
});
|
|
8353
8413
|
}
|
|
8354
8414
|
async function assertNoForeignWorkspaceStateForPlan(plan, options) {
|
|
8355
8415
|
if (!plan.stateKey) throw new Error(`Foreign-state validation requires a state key for ${plan.adapter}.`);
|
|
@@ -8708,7 +8768,7 @@ function stableJson2(value) {
|
|
|
8708
8768
|
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort((left, right) => left.localeCompare(right)).map((key) => `${JSON.stringify(key)}:${stableJson2(record[key])}`).join(",")}}`;
|
|
8709
8769
|
}
|
|
8710
8770
|
function normalizeInstallRoot2(path, transportKind) {
|
|
8711
|
-
return transportKind === "ssh" ?
|
|
8771
|
+
return transportKind === "ssh" ? posix3.normalize(path) : resolve16(path);
|
|
8712
8772
|
}
|
|
8713
8773
|
var workspaceOwnerPrefix2 = "workspace-root:";
|
|
8714
8774
|
async function assertNoForeignWorkspaceState(check) {
|
|
@@ -8785,6 +8845,7 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8785
8845
|
}
|
|
8786
8846
|
const foreign = [...foreignByOwner.values()];
|
|
8787
8847
|
if (foreign.length === 0) return observations.sort((left, right) => left.path.localeCompare(right.path));
|
|
8848
|
+
const currentOwner = parseWorkspaceOwner(check.workspaceOwner);
|
|
8788
8849
|
throw new Error([
|
|
8789
8850
|
`Refusing to plan ${check.adapter} at ${check.installRoot}.`,
|
|
8790
8851
|
"This runtime root already carries Agentwheel state owned by another workspace, at paths this run would install:",
|
|
@@ -8795,7 +8856,10 @@ async function assertNoForeignWorkspaceState(check) {
|
|
|
8795
8856
|
`Current workspace: ${check.workspaceRoot} (state key ${check.stateKey})`,
|
|
8796
8857
|
"Agentwheel keys install state by target fingerprint, so this run cannot read that manifest and",
|
|
8797
8858
|
"would report those paths as unmanaged conflicts or drift.",
|
|
8798
|
-
|
|
8859
|
+
...currentOwner?.fleetId ? [
|
|
8860
|
+
"Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet.",
|
|
8861
|
+
...currentOwner.root !== root ? ["For this nested workspace, adopt legacy fingerprint-keyed claims with agentwheel ownership adopt-legacy."] : []
|
|
8862
|
+
] : ["Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."]
|
|
8799
8863
|
].join("\n"));
|
|
8800
8864
|
}
|
|
8801
8865
|
async function exactForeignKeepOperation(check, collision, operations) {
|
|
@@ -9781,6 +9845,7 @@ function resolveCliVersion() {
|
|
|
9781
9845
|
|
|
9782
9846
|
// src/lifecycle/ownership.ts
|
|
9783
9847
|
import { resolve as resolve20 } from "path";
|
|
9848
|
+
var unknownWorkspaceOwner = "workspace:unknown";
|
|
9784
9849
|
async function planArtifactOwnershipHandoff(request) {
|
|
9785
9850
|
return validateOwnershipHandoff(request, request.transport ?? localTransport);
|
|
9786
9851
|
}
|
|
@@ -9799,6 +9864,9 @@ async function applyArtifactOwnershipHandoff(request) {
|
|
|
9799
9864
|
throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
|
|
9800
9865
|
}
|
|
9801
9866
|
const plan = await validateOwnershipHandoff(request, transport);
|
|
9867
|
+
if (plan.drifted && !request.expectedRuntimeHash) {
|
|
9868
|
+
throw new Error("Applying a drifted ownership handoff requires expectedRuntimeHash from a reviewed dry-run.");
|
|
9869
|
+
}
|
|
9802
9870
|
const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
|
|
9803
9871
|
if (!manifest || manifest.version !== 2) {
|
|
9804
9872
|
throw new Error("Ownership handoff requires an Agentwheel v2 install manifest.");
|
|
@@ -9829,6 +9897,13 @@ async function applyArtifactOwnershipHandoff(request) {
|
|
|
9829
9897
|
async function validateOwnershipHandoff(request, transport) {
|
|
9830
9898
|
if (request.expectedHash) assertSha256(request.expectedHash, "expected artifact hash");
|
|
9831
9899
|
if (request.expectedRevision) assertSha256(request.expectedRevision, "expected manifest revision");
|
|
9900
|
+
if (request.expectedRuntimeHash) {
|
|
9901
|
+
assertSha256(request.expectedRuntimeHash, "expected runtime hash");
|
|
9902
|
+
if (request.carryDrift !== true) throw new Error("An expected runtime hash is only valid with carryDrift.");
|
|
9903
|
+
}
|
|
9904
|
+
if (request.fromWorkspaceRoot === void 0 === (request.fromUnknownOwner !== true)) {
|
|
9905
|
+
throw new Error("Ownership handoff requires exactly one of fromWorkspaceRoot or fromUnknownOwner.");
|
|
9906
|
+
}
|
|
9832
9907
|
const scope = { installationType: request.installationType, stateKey: request.stateKey };
|
|
9833
9908
|
const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
|
|
9834
9909
|
if (!manifest) throw new Error(`No install manifest for ${request.adapter} at ${request.targetRoot}`);
|
|
@@ -9846,7 +9921,7 @@ async function validateOwnershipHandoff(request, transport) {
|
|
|
9846
9921
|
);
|
|
9847
9922
|
}
|
|
9848
9923
|
const entry = matches[0];
|
|
9849
|
-
const fromOwner = workspaceOwnerForRoot(request.fromWorkspaceRoot);
|
|
9924
|
+
const fromOwner = request.fromUnknownOwner === true ? unknownWorkspaceOwner : workspaceOwnerForRoot(request.fromWorkspaceRoot);
|
|
9850
9925
|
const toOwner = workspaceOwnerForRoot(request.toWorkspaceRoot, request.toFleetId);
|
|
9851
9926
|
if (fromOwner === toOwner) throw new Error("Ownership handoff requires different workspace roots.");
|
|
9852
9927
|
if (entry.workspaceOwner !== fromOwner) {
|
|
@@ -9857,9 +9932,14 @@ async function validateOwnershipHandoff(request, transport) {
|
|
|
9857
9932
|
}
|
|
9858
9933
|
const destPath = containedArtifactPath(request.targetRoot, entry.path);
|
|
9859
9934
|
if (!await transport.pathExists(destPath)) throw new Error(`Managed artifact is missing: ${entry.path}`);
|
|
9860
|
-
const
|
|
9861
|
-
|
|
9862
|
-
|
|
9935
|
+
const runtimeHash = await verifiedEntryHash(entry, destPath, transport, request.carryDrift === true);
|
|
9936
|
+
const drifted = runtimeHash !== entry.hash;
|
|
9937
|
+
if (drifted) {
|
|
9938
|
+
if (request.expectedRuntimeHash && runtimeHash !== request.expectedRuntimeHash) {
|
|
9939
|
+
throw new Error(`Runtime hash precondition failed for ${entry.path}: expected ${request.expectedRuntimeHash}, found ${runtimeHash}`);
|
|
9940
|
+
}
|
|
9941
|
+
} else if (request.expectedRuntimeHash) {
|
|
9942
|
+
throw new Error(`Runtime hash precondition failed for ${entry.path}: the artifact is no longer drifted`);
|
|
9863
9943
|
}
|
|
9864
9944
|
return {
|
|
9865
9945
|
adapter: request.adapter,
|
|
@@ -9868,13 +9948,15 @@ async function validateOwnershipHandoff(request, transport) {
|
|
|
9868
9948
|
targetRoot: request.targetRoot,
|
|
9869
9949
|
selector: `${request.artifactType}/${request.artifactName}`,
|
|
9870
9950
|
path: entry.path,
|
|
9871
|
-
artifactHash:
|
|
9951
|
+
artifactHash: entry.hash,
|
|
9952
|
+
runtimeHash,
|
|
9953
|
+
drifted,
|
|
9872
9954
|
manifestRevision: manifest.revision,
|
|
9873
9955
|
fromOwner,
|
|
9874
9956
|
toOwner
|
|
9875
9957
|
};
|
|
9876
9958
|
}
|
|
9877
|
-
async function verifiedEntryHash(entry, destPath, transport) {
|
|
9959
|
+
async function verifiedEntryHash(entry, destPath, transport, carryFileDrift = false) {
|
|
9878
9960
|
if (entry.semanticPlugin) throw new Error(`Ownership handoff cannot verify semantic plugin state at ${destPath}`);
|
|
9879
9961
|
if (entry.mode === "managed-block") {
|
|
9880
9962
|
const selector = managedInstructionSelector(entry.logicalSelector, entry.artifactType, entry.artifactName);
|
|
@@ -9892,7 +9974,7 @@ async function verifiedEntryHash(entry, destPath, transport) {
|
|
|
9892
9974
|
return entry.hash;
|
|
9893
9975
|
}
|
|
9894
9976
|
const currentHash = await transport.hashPath(destPath);
|
|
9895
|
-
if (currentHash !== entry.hash) {
|
|
9977
|
+
if (currentHash !== entry.hash && !carryFileDrift) {
|
|
9896
9978
|
throw new Error(`Managed artifact is drifted at ${destPath}: manifest ${entry.hash}, current ${currentHash}`);
|
|
9897
9979
|
}
|
|
9898
9980
|
return currentHash;
|
|
@@ -10107,7 +10189,7 @@ async function requireManifest(request, stateKey, transport, label) {
|
|
|
10107
10189
|
stateKey
|
|
10108
10190
|
});
|
|
10109
10191
|
if (!manifest) throw new Error(`No ${label} install manifest for state key ${stateKey}.`);
|
|
10110
|
-
if (manifest.version !== 2) throw new Error(
|
|
10192
|
+
if (manifest.version !== 2) throw new Error(`The ${label} install manifest must be an Agentwheel v2 manifest.`);
|
|
10111
10193
|
const actualStateKey = stateKeyFor(manifest.adapter, {
|
|
10112
10194
|
installationType: manifest.installationType,
|
|
10113
10195
|
stateKey: manifest.stateKey
|
|
@@ -10214,10 +10296,15 @@ function canonicalJson(value) {
|
|
|
10214
10296
|
return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
10215
10297
|
}
|
|
10216
10298
|
|
|
10299
|
+
// src/lifecycle/ownership-adopt-legacy.ts
|
|
10300
|
+
import { createHash as createHash11 } from "crypto";
|
|
10301
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
10302
|
+
import { isAbsolute as isAbsolute3, relative as relative9, resolve as resolve23 } from "path";
|
|
10303
|
+
|
|
10217
10304
|
// src/lifecycle/legacy-recovery-plan.ts
|
|
10218
10305
|
import { createHash as createHash10 } from "crypto";
|
|
10219
|
-
import { readdir as readdir10 } from "fs/promises";
|
|
10220
|
-
import { basename as basename20, dirname as dirname21, join as join39, posix as
|
|
10306
|
+
import { lstat as lstat3, readdir as readdir10 } from "fs/promises";
|
|
10307
|
+
import { basename as basename20, dirname as dirname21, join as join39, posix as posix4, relative as relative8, resolve as resolve22 } from "path";
|
|
10221
10308
|
async function inventoryHistoricalLocks(fleetRoot) {
|
|
10222
10309
|
const root = join39(fleetRoot, ".agentwheel", "locks");
|
|
10223
10310
|
const found = [];
|
|
@@ -10228,15 +10315,38 @@ async function inventoryHistoricalLocks(fleetRoot) {
|
|
|
10228
10315
|
})) {
|
|
10229
10316
|
const path = join39(dir, entry.name);
|
|
10230
10317
|
if (entry.isDirectory()) await visit(path);
|
|
10231
|
-
else if (entry.isFile() && entry.name.endsWith(".graph-lock.json"))
|
|
10232
|
-
const lock = await readGraphLock(path);
|
|
10233
|
-
found.push({ path, digest: sha2564(canonicalGraphLockJson(lock)), lock });
|
|
10234
|
-
}
|
|
10318
|
+
else if (entry.isFile() && entry.name.endsWith(".graph-lock.json")) found.push(await historicalLock(path));
|
|
10235
10319
|
}
|
|
10236
10320
|
}
|
|
10237
10321
|
await visit(root);
|
|
10238
10322
|
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
10239
10323
|
}
|
|
10324
|
+
async function findLegacyNamedLocks(workspaceRoot, adapter, fingerprint) {
|
|
10325
|
+
const root = join39(workspaceRoot, ".agentwheel", "locks");
|
|
10326
|
+
const found = [];
|
|
10327
|
+
for (const target of await readdir10(root, { withFileTypes: true }).catch((error) => {
|
|
10328
|
+
if (error.code === "ENOENT") return [];
|
|
10329
|
+
throw error;
|
|
10330
|
+
})) {
|
|
10331
|
+
if (!target.isDirectory()) continue;
|
|
10332
|
+
const path = join39(root, target.name, adapter, `${fingerprint}.graph-lock.json`);
|
|
10333
|
+
const stats = await lstat3(path).catch((error) => {
|
|
10334
|
+
if (error.code === "ENOENT") return void 0;
|
|
10335
|
+
throw error;
|
|
10336
|
+
});
|
|
10337
|
+
if (!stats?.isFile()) continue;
|
|
10338
|
+
const item = await historicalLock(path);
|
|
10339
|
+
if (item.lock.canonical.targetFingerprint === fingerprint) found.push(item);
|
|
10340
|
+
}
|
|
10341
|
+
return found.sort((a, b) => a.path.localeCompare(b.path));
|
|
10342
|
+
}
|
|
10343
|
+
function lockCoversEntry(lock, entry) {
|
|
10344
|
+
return lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === entry.graphNodeId && artifact.logicalSelector === entry.logicalSelector && artifact.hash === entry.sourceHash);
|
|
10345
|
+
}
|
|
10346
|
+
async function historicalLock(path) {
|
|
10347
|
+
const lock = await readGraphLock(path);
|
|
10348
|
+
return { path, digest: sha2564(canonicalGraphLockJson(lock)), lock };
|
|
10349
|
+
}
|
|
10240
10350
|
async function planLegacyRecovery(input) {
|
|
10241
10351
|
const { graphPlan, installRoot, targetKey: targetKey2, adapter, installationType, historicalLocks, transport } = input;
|
|
10242
10352
|
assertCompleteGraphPlan(graphPlan);
|
|
@@ -10352,7 +10462,7 @@ function claimFor(item, entry, locks, binding) {
|
|
|
10352
10462
|
const matches = locks.filter((candidate) => candidate.digest === digest2);
|
|
10353
10463
|
const evidence = [];
|
|
10354
10464
|
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
10355
|
-
const sameRoot = binding.transportKind === "ssh" ?
|
|
10465
|
+
const sameRoot = binding.transportKind === "ssh" ? posix4.normalize(item.manifest.targetRoot) === posix4.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
|
|
10356
10466
|
const inTargetDir = matches.filter((candidate) => resolve22(dirname21(candidate.path)) === resolve22(binding.targetLockDir));
|
|
10357
10467
|
const boundLocks = inTargetDir.filter((candidate) => lockBindsState(candidate, item, binding));
|
|
10358
10468
|
if (owner !== binding.expectedOwner) evidence.push("workspace owner differs from selected Fleet target");
|
|
@@ -10367,7 +10477,7 @@ function claimFor(item, entry, locks, binding) {
|
|
|
10367
10477
|
if (inTargetDir.length && !boundLocks.length) evidence.push("graph-lock fingerprint, filename, or state key does not bind claim");
|
|
10368
10478
|
if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector))) {
|
|
10369
10479
|
evidence.push("historical graph-lock selector/node does not cover claim");
|
|
10370
|
-
} else if (boundLocks.length && !boundLocks.some((candidate) => candidate.lock
|
|
10480
|
+
} else if (boundLocks.length && !boundLocks.some((candidate) => lockCoversEntry(candidate.lock, { graphNodeId: nodeId, logicalSelector: selector, sourceHash: entry.sourceHash }))) {
|
|
10371
10481
|
evidence.push("historical graph-lock artifact hash differs from manifest source hash");
|
|
10372
10482
|
}
|
|
10373
10483
|
const foreign = owner !== binding.expectedOwner || !sameRoot || item.manifest.version !== 2 || item.manifest.installationType !== binding.installationType || item.manifest.version === 2 && (item.manifest.stateKey !== item.stateKey || item.manifest.adapter !== binding.adapter) || matches.length > 0 && inTargetDir.length === 0;
|
|
@@ -10466,10 +10576,394 @@ function sha2564(value) {
|
|
|
10466
10576
|
return createHash10("sha256").update(value).digest("hex");
|
|
10467
10577
|
}
|
|
10468
10578
|
|
|
10579
|
+
// src/lifecycle/ownership-adopt-legacy.ts
|
|
10580
|
+
var absentRevision = "absent";
|
|
10581
|
+
function desiredCoverageFromPlan(plan) {
|
|
10582
|
+
return plan.operations.filter((operation) => operation.desiredHash && operation.action !== "remove" && operation.action !== "keep").map((operation) => ({
|
|
10583
|
+
path: operation.relativeDestPath,
|
|
10584
|
+
artifactType: operation.artifactType,
|
|
10585
|
+
artifactName: operation.artifactName,
|
|
10586
|
+
kind: operation.kind,
|
|
10587
|
+
...operation.packageName ? { packageName: operation.packageName } : {}
|
|
10588
|
+
})).sort((left, right) => left.path.localeCompare(right.path));
|
|
10589
|
+
}
|
|
10590
|
+
async function planAdoptLegacyOwnership(request) {
|
|
10591
|
+
return observe2(request, request.transport ?? localTransport);
|
|
10592
|
+
}
|
|
10593
|
+
async function applyAdoptLegacyOwnership(request) {
|
|
10594
|
+
assertApplyPreconditions2(request);
|
|
10595
|
+
const transport = request.transport ?? localTransport;
|
|
10596
|
+
const targetRoot = resolve23(request.targetRoot);
|
|
10597
|
+
const installationType = request.installationType.trim();
|
|
10598
|
+
const lock = await acquireApplyLock(targetRoot, request.adapter, transport, {}, {
|
|
10599
|
+
installationType,
|
|
10600
|
+
stateKey: request.destinationStateKey
|
|
10601
|
+
});
|
|
10602
|
+
try {
|
|
10603
|
+
await assertNoPendingJournals(targetRoot, request.adapter, transport);
|
|
10604
|
+
const current = await observe2(request, transport);
|
|
10605
|
+
assertExpected(request.planDigest, current.planDigest, "plan digest");
|
|
10606
|
+
assertExpected(request.expectedSourceRevision, current.source.revision, "source manifest revision");
|
|
10607
|
+
assertExpected(request.expectedDestinationRevision, current.destination.revision ?? absentRevision, "destination manifest revision");
|
|
10608
|
+
assertExpected(request.expectedInventoryRevision, current.manifestInventoryRevision, "manifest inventory revision");
|
|
10609
|
+
const identity = { targetRoot, adapter: request.adapter, installationType };
|
|
10610
|
+
const source = await requireManifest(identity, current.source.stateKey, transport, "source");
|
|
10611
|
+
const destination = await readDestination(identity, current.destination.stateKey, transport);
|
|
10612
|
+
const selectedByDigest = new Map(current.selected.map((entry) => [entry.sourceEntryDigest, entry]));
|
|
10613
|
+
const adopted = [];
|
|
10614
|
+
const retained = [];
|
|
10615
|
+
for (const entry of source.entries) {
|
|
10616
|
+
const selected = selectedByDigest.get(entryDigest(entry));
|
|
10617
|
+
if (!selected) retained.push(entry);
|
|
10618
|
+
else if (selected.action === "adopt") adopted.push({ ...entry, workspaceOwner: current.destination.owner });
|
|
10619
|
+
}
|
|
10620
|
+
if (source.entries.length - retained.length !== current.selected.length) {
|
|
10621
|
+
throw new Error("Source manifest selection changed while locked; replan required.");
|
|
10622
|
+
}
|
|
10623
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10624
|
+
if (adopted.length > 0) {
|
|
10625
|
+
declareMutationPath(current.destination.manifestPath);
|
|
10626
|
+
const base = destination ?? {
|
|
10627
|
+
version: 2,
|
|
10628
|
+
adapter: request.adapter,
|
|
10629
|
+
installationType,
|
|
10630
|
+
stateKey: current.destination.stateKey,
|
|
10631
|
+
targetRoot,
|
|
10632
|
+
generatedAt: now,
|
|
10633
|
+
revision: "pending-apply-0000",
|
|
10634
|
+
legacy: false,
|
|
10635
|
+
entries: []
|
|
10636
|
+
};
|
|
10637
|
+
await commitMetadata({
|
|
10638
|
+
...base,
|
|
10639
|
+
entries: [...base.entries, ...adopted].sort((left, right) => left.path.localeCompare(right.path))
|
|
10640
|
+
}, destination?.revision ?? null, now, transport);
|
|
10641
|
+
}
|
|
10642
|
+
declareMutationPath(current.source.manifestPath);
|
|
10643
|
+
await commitMetadata({ ...source, entries: retained }, source.revision, now, transport);
|
|
10644
|
+
return {
|
|
10645
|
+
...current,
|
|
10646
|
+
applied: true,
|
|
10647
|
+
adoptedEntries: adopted.length,
|
|
10648
|
+
retiredCoveredEntries: current.selected.length - adopted.length,
|
|
10649
|
+
sourceManifestRemoved: retained.length === 0,
|
|
10650
|
+
retainedSourceEntries: retained.length
|
|
10651
|
+
};
|
|
10652
|
+
} finally {
|
|
10653
|
+
await lock.release();
|
|
10654
|
+
}
|
|
10655
|
+
}
|
|
10656
|
+
async function commitMetadata(manifest, baseRevision, now, transport) {
|
|
10657
|
+
const mutation = mutationMetadataForApplyJournal();
|
|
10658
|
+
const journal = {
|
|
10659
|
+
version: mutation ? 2 : 1,
|
|
10660
|
+
...mutation ? { mutation } : {},
|
|
10661
|
+
mode: "uninstall",
|
|
10662
|
+
adapter: manifest.adapter,
|
|
10663
|
+
installationType: manifest.installationType,
|
|
10664
|
+
stateKey: manifest.stateKey,
|
|
10665
|
+
targetRoot: manifest.targetRoot,
|
|
10666
|
+
baseRevision,
|
|
10667
|
+
createdAt: now,
|
|
10668
|
+
updatedAt: now,
|
|
10669
|
+
operations: [],
|
|
10670
|
+
completed: [],
|
|
10671
|
+
manifest
|
|
10672
|
+
};
|
|
10673
|
+
await writeApplyJournal(journal, transport);
|
|
10674
|
+
await commitManifestMetadataJournal(journal, transport);
|
|
10675
|
+
}
|
|
10676
|
+
async function observe2(request, transport) {
|
|
10677
|
+
if (transport.kind === "ssh") {
|
|
10678
|
+
throw new Error("Legacy ownership adoption does not support SSH targets; legacy SSH state does not prove its endpoint.");
|
|
10679
|
+
}
|
|
10680
|
+
const input = await normalizeRequest2(request);
|
|
10681
|
+
const { targetRoot, adapter, installationType } = input;
|
|
10682
|
+
await assertNoPendingJournals(targetRoot, adapter, transport);
|
|
10683
|
+
const identity = { targetRoot, adapter, installationType };
|
|
10684
|
+
if (!await readInstallManifest(targetRoot, adapter, transport, { installationType, stateKey: input.sourceStateKey })) {
|
|
10685
|
+
throw new Error(`Nothing to adopt: no source install manifest for state key ${input.sourceStateKey}.`);
|
|
10686
|
+
}
|
|
10687
|
+
const source = await requireManifest(identity, input.sourceStateKey, transport, "source");
|
|
10688
|
+
const destination = await readDestination(identity, input.destinationStateKey, transport);
|
|
10689
|
+
if (!destination && await pathExists(request.destinationGraphLockPath)) {
|
|
10690
|
+
throw new Error(
|
|
10691
|
+
`Destination has a stable graph lock without a stable manifest at ${request.destinationGraphLockPath}; reconcile it before adopting legacy ownership.`
|
|
10692
|
+
);
|
|
10693
|
+
}
|
|
10694
|
+
const desiredByPath = /* @__PURE__ */ new Map();
|
|
10695
|
+
for (const item of request.desiredCoverage) desiredByPath.set(item.path, [...desiredByPath.get(item.path) ?? [], item]);
|
|
10696
|
+
const { entries: selectedEntries, retained, provenance } = await selectLegacySource(
|
|
10697
|
+
input,
|
|
10698
|
+
input.sourceStateKey,
|
|
10699
|
+
input.fingerprint,
|
|
10700
|
+
source,
|
|
10701
|
+
desiredByPath
|
|
10702
|
+
);
|
|
10703
|
+
const selectedPaths = new Set(selectedEntries.map((entry) => entry.path));
|
|
10704
|
+
const selected = [];
|
|
10705
|
+
for (const entry of selectedEntries) {
|
|
10706
|
+
const runtimePath = containedArtifactPath(targetRoot, entry.path);
|
|
10707
|
+
const runtimeHash = await transport.pathExists(runtimePath) ? await transport.hashPath(runtimePath) : null;
|
|
10708
|
+
const drift = runtimeHash !== entry.hash;
|
|
10709
|
+
if (drift && request.carryDrift !== true) {
|
|
10710
|
+
throw new Error(
|
|
10711
|
+
runtimeHash === null ? `Managed artifact is missing at ${entry.path}; pass --carry-drift to keep the recorded hash.` : `Managed artifact is drifted at ${entry.path}: manifest ${entry.hash}, current ${runtimeHash}; pass --carry-drift to keep the recorded hash.`
|
|
10712
|
+
);
|
|
10713
|
+
}
|
|
10714
|
+
const atPath = destination?.entries.filter((candidate) => candidate.path === entry.path) ?? [];
|
|
10715
|
+
let destinationEntryDigest;
|
|
10716
|
+
if (atPath.length > 0) {
|
|
10717
|
+
const covering = atPath.length === 1 ? atPath[0] : void 0;
|
|
10718
|
+
if (!covering || !coversSourceEntry(covering, entry, input.destinationOwner)) {
|
|
10719
|
+
throw new Error(`Destination state at ${entry.path} is not an exact copy of the source claim owned by this workspace.`);
|
|
10720
|
+
}
|
|
10721
|
+
if (!drift) await verifyManifestEntryRuntime(targetRoot, covering, transport);
|
|
10722
|
+
destinationEntryDigest = entryDigest(covering);
|
|
10723
|
+
}
|
|
10724
|
+
selected.push({
|
|
10725
|
+
path: entry.path,
|
|
10726
|
+
action: destinationEntryDigest ? "retire-covered" : "adopt",
|
|
10727
|
+
sourceEntryDigest: entryDigest(entry),
|
|
10728
|
+
...destinationEntryDigest ? { destinationEntryDigest } : {},
|
|
10729
|
+
recordedHash: entry.hash,
|
|
10730
|
+
runtimeHash,
|
|
10731
|
+
drift
|
|
10732
|
+
});
|
|
10733
|
+
}
|
|
10734
|
+
const remainingDuplicates = [];
|
|
10735
|
+
const selectedByPath = new Map(selectedEntries.map((entry) => [entry.path, entry]));
|
|
10736
|
+
for (const item of await listInstallManifests(targetRoot, adapter, transport)) {
|
|
10737
|
+
if (item.stateKey === input.destinationStateKey) continue;
|
|
10738
|
+
const duplicatePaths = [];
|
|
10739
|
+
for (const entry of item.manifest.entries) {
|
|
10740
|
+
if (!selectedPaths.has(entry.path)) continue;
|
|
10741
|
+
const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
|
|
10742
|
+
if (owner !== input.sourceOwner) {
|
|
10743
|
+
throw new Error(`${entry.path} is also claimed by ${owner} in ${item.fileName}; legacy ownership cannot be adopted.`);
|
|
10744
|
+
}
|
|
10745
|
+
if (item.stateKey !== input.sourceStateKey) duplicatePaths.push(entry.path);
|
|
10746
|
+
}
|
|
10747
|
+
if (duplicatePaths.length === 0) continue;
|
|
10748
|
+
await assertRetirableDuplicate(input, item, duplicatePaths, selectedByPath, desiredByPath, transport);
|
|
10749
|
+
for (const path of duplicatePaths) remainingDuplicates.push({ stateKey: item.stateKey, path });
|
|
10750
|
+
}
|
|
10751
|
+
const withoutDigest = {
|
|
10752
|
+
adapter,
|
|
10753
|
+
installationType,
|
|
10754
|
+
targetRoot,
|
|
10755
|
+
source: {
|
|
10756
|
+
stateKey: input.sourceStateKey,
|
|
10757
|
+
manifestPath: installManifestPath(targetRoot, adapter, { installationType, stateKey: input.sourceStateKey }),
|
|
10758
|
+
revision: source.revision,
|
|
10759
|
+
owner: input.sourceOwner,
|
|
10760
|
+
class: input.sourceClass,
|
|
10761
|
+
provenanceLock: { path: provenance.path, digest: provenance.digest }
|
|
10762
|
+
},
|
|
10763
|
+
destination: {
|
|
10764
|
+
stateKey: input.destinationStateKey,
|
|
10765
|
+
manifestPath: installManifestPath(targetRoot, adapter, { installationType, stateKey: input.destinationStateKey }),
|
|
10766
|
+
revision: destination?.revision ?? null,
|
|
10767
|
+
owner: input.destinationOwner,
|
|
10768
|
+
workspaceRoot: input.workspaceRoot
|
|
10769
|
+
},
|
|
10770
|
+
manifestInventoryRevision: await computeInstallManifestInventoryRevision(targetRoot, adapter, transport),
|
|
10771
|
+
selected: selected.sort((left, right) => left.path.localeCompare(right.path)),
|
|
10772
|
+
retained: retained.sort((left, right) => left.path.localeCompare(right.path) || left.owner.localeCompare(right.owner)),
|
|
10773
|
+
remainingDuplicates: remainingDuplicates.sort((left, right) => left.stateKey.localeCompare(right.stateKey) || left.path.localeCompare(right.path)),
|
|
10774
|
+
// identity only, so a newer package revision doesn't invalidate a reviewed plan
|
|
10775
|
+
desiredCoverage: request.desiredCoverage.filter((item) => selectedPaths.has(item.path)).map(({ path, artifactType, artifactName, kind }) => ({ path, artifactType, artifactName, kind })).sort((left, right) => left.path.localeCompare(right.path) || left.artifactType.localeCompare(right.artifactType) || left.artifactName.localeCompare(right.artifactName))
|
|
10776
|
+
};
|
|
10777
|
+
return { ...withoutDigest, planDigest: createHash11("sha256").update(canonicalJson(withoutDigest)).digest("hex") };
|
|
10778
|
+
}
|
|
10779
|
+
async function normalizeRequest2(request) {
|
|
10780
|
+
if (request.destinationFleetId !== void 0) {
|
|
10781
|
+
throw new Error("Legacy ownership adoption targets a nested workspace; a Fleet target uses fleet normalize or ownership retire-stale.");
|
|
10782
|
+
}
|
|
10783
|
+
const targetRoot = resolve23(request.targetRoot);
|
|
10784
|
+
const installationType = request.installationType.trim();
|
|
10785
|
+
const sourceStateKey = exactStateKey(request.adapter, request.sourceStateKey, installationType, "source");
|
|
10786
|
+
const destinationStateKey = exactStateKey(request.adapter, request.destinationStateKey, installationType, "destination");
|
|
10787
|
+
const fingerprint = legacyFingerprint(request.adapter, installationType, sourceStateKey);
|
|
10788
|
+
if (!fingerprint) {
|
|
10789
|
+
throw new Error(`The source must be a legacy state key of the form ${request.adapter}.${installationType}.<64-hex target fingerprint>.`);
|
|
10790
|
+
}
|
|
10791
|
+
if (sourceStateKey === destinationStateKey) throw new Error("Source and destination state keys must differ.");
|
|
10792
|
+
const scope = await resolveWorkspaceOwnershipScope(request.workspaceRoot, { globalRoot: request.globalRoot });
|
|
10793
|
+
const destinationOwner = workspaceOwnerForRoot(scope.root, scope.fleetId);
|
|
10794
|
+
const workspaceRoot = await realpath2(request.workspaceRoot);
|
|
10795
|
+
const sourceRoot = resolve23(request.fromWorkspaceRoot);
|
|
10796
|
+
const sourceOwner = workspaceOwnerForRoot(sourceRoot);
|
|
10797
|
+
let sourceClass;
|
|
10798
|
+
if (sourceRoot === workspaceRoot || sourceRoot === resolve23(request.workspaceRoot)) {
|
|
10799
|
+
if (sourceOwner === destinationOwner) {
|
|
10800
|
+
throw new Error(`${sourceOwner} is already the destination owner; ordinary install migration handles its legacy state.`);
|
|
10801
|
+
}
|
|
10802
|
+
sourceClass = "own-legacy-owner";
|
|
10803
|
+
} else {
|
|
10804
|
+
const sourceForms = [sourceRoot, ...await realpath2(sourceRoot).then((path) => [path], () => [])];
|
|
10805
|
+
const workspaceForms = [workspaceRoot, resolve23(request.workspaceRoot), scope.root];
|
|
10806
|
+
if (sourceForms.some((source) => workspaceForms.some((workspace) => containsPath2(source, workspace) || containsPath2(workspace, source)))) {
|
|
10807
|
+
throw new Error(`Source root ${sourceRoot} contains or is contained by the destination workspace; it cannot be adopted as a foreign root.`);
|
|
10808
|
+
}
|
|
10809
|
+
for (const fleet of await listRegisteredFleets({ globalRoot: request.globalRoot })) {
|
|
10810
|
+
const fleetForms = [resolve23(fleet.root), ...await realpath2(fleet.root).then((path) => [path], () => [])];
|
|
10811
|
+
if (sourceForms.some((source) => fleetForms.some((root) => containsPath2(root, source)))) {
|
|
10812
|
+
throw new Error(`Source root ${sourceRoot} is inside registered Fleet '${fleet.id}'; use fleet normalize or ownership retire-stale.`);
|
|
10813
|
+
}
|
|
10814
|
+
}
|
|
10815
|
+
sourceClass = "foreign-root";
|
|
10816
|
+
}
|
|
10817
|
+
return {
|
|
10818
|
+
targetRoot,
|
|
10819
|
+
adapter: request.adapter,
|
|
10820
|
+
installationType,
|
|
10821
|
+
sourceStateKey,
|
|
10822
|
+
destinationStateKey,
|
|
10823
|
+
fingerprint,
|
|
10824
|
+
sourceRoot,
|
|
10825
|
+
sourceOwner,
|
|
10826
|
+
workspaceRoot,
|
|
10827
|
+
destinationOwner,
|
|
10828
|
+
sourceClass
|
|
10829
|
+
};
|
|
10830
|
+
}
|
|
10831
|
+
function legacyFingerprint(adapter, installationType, stateKey) {
|
|
10832
|
+
const prefix = `${adapter}.${installationType}.`;
|
|
10833
|
+
const fingerprint = stateKey.startsWith(prefix) ? stateKey.slice(prefix.length) : "";
|
|
10834
|
+
return /^[a-f0-9]{64}$/u.test(fingerprint) ? fingerprint : void 0;
|
|
10835
|
+
}
|
|
10836
|
+
async function selectLegacySource(input, stateKey, fingerprint, source, desiredByPath) {
|
|
10837
|
+
if (source.adapterCode) {
|
|
10838
|
+
throw new Error(`The install manifest for ${stateKey} records programmatic adapter code; legacy ownership adoption refuses it.`);
|
|
10839
|
+
}
|
|
10840
|
+
if (source.entries.some((entry) => entry.workspaceOwner === input.destinationOwner)) {
|
|
10841
|
+
throw new Error(
|
|
10842
|
+
`Source state ${stateKey} has entries already owned by the destination owner; run a normal install to migrate them first.`
|
|
10843
|
+
);
|
|
10844
|
+
}
|
|
10845
|
+
for (const entry of source.entries) {
|
|
10846
|
+
if (entry.workspaceOwner === input.sourceOwner) assertPlainEntry(entry, input.targetRoot);
|
|
10847
|
+
}
|
|
10848
|
+
const entries = [];
|
|
10849
|
+
const retained = [];
|
|
10850
|
+
for (const entry of source.entries) {
|
|
10851
|
+
if (entry.workspaceOwner !== input.sourceOwner) {
|
|
10852
|
+
retained.push({ path: entry.path, owner: entry.workspaceOwner, reason: "other-owner" });
|
|
10853
|
+
continue;
|
|
10854
|
+
}
|
|
10855
|
+
const desired = desiredByPath.get(entry.path) ?? [];
|
|
10856
|
+
if (desired.length === 0) {
|
|
10857
|
+
retained.push({ path: entry.path, owner: entry.workspaceOwner, reason: "not-desired" });
|
|
10858
|
+
continue;
|
|
10859
|
+
}
|
|
10860
|
+
if (!desired.some((item) => desiredCovers(item, entry))) {
|
|
10861
|
+
throw new Error(`The current graph plans a different artifact at ${entry.path}; legacy ownership cannot be adopted there.`);
|
|
10862
|
+
}
|
|
10863
|
+
entries.push(entry);
|
|
10864
|
+
}
|
|
10865
|
+
if (entries.length === 0) {
|
|
10866
|
+
throw new Error(`Nothing to adopt: no entry owned by ${input.sourceOwner} in ${stateKey} is desired by this workspace.`);
|
|
10867
|
+
}
|
|
10868
|
+
const paths = /* @__PURE__ */ new Set();
|
|
10869
|
+
for (const entry of entries) {
|
|
10870
|
+
if (paths.has(entry.path)) throw new Error(`Source state ${stateKey} has more than one entry at ${entry.path}.`);
|
|
10871
|
+
paths.add(entry.path);
|
|
10872
|
+
}
|
|
10873
|
+
return { entries, retained, provenance: await resolveProvenance(input, fingerprint, entries) };
|
|
10874
|
+
}
|
|
10875
|
+
async function assertRetirableDuplicate(input, item, paths, selectedByPath, desiredByPath, transport) {
|
|
10876
|
+
const refuse = (path, reason) => new Error(
|
|
10877
|
+
`${path} is also claimed by ${input.sourceOwner} in ${item.fileName}, which a follow-up adopt-legacy run could not retire: ${reason}`
|
|
10878
|
+
);
|
|
10879
|
+
const fingerprint = legacyFingerprint(input.adapter, input.installationType, item.stateKey);
|
|
10880
|
+
if (!fingerprint) throw refuse(paths[0], `${item.stateKey} is not a legacy fingerprint state key for ${input.adapter}/${input.installationType}.`);
|
|
10881
|
+
let duplicates;
|
|
10882
|
+
try {
|
|
10883
|
+
const identity = { targetRoot: input.targetRoot, adapter: input.adapter, installationType: input.installationType };
|
|
10884
|
+
const manifest = await requireManifest(identity, item.stateKey, transport, "duplicate source");
|
|
10885
|
+
duplicates = (await selectLegacySource(input, item.stateKey, fingerprint, manifest, desiredByPath)).entries;
|
|
10886
|
+
} catch (error) {
|
|
10887
|
+
throw refuse(paths[0], error instanceof Error ? error.message : String(error));
|
|
10888
|
+
}
|
|
10889
|
+
for (const path of paths) {
|
|
10890
|
+
const duplicate = duplicates.find((entry) => entry.path === path);
|
|
10891
|
+
if (!duplicate || !sameRecordedArtifact(duplicate, selectedByPath.get(path))) {
|
|
10892
|
+
throw refuse(path, "its artifact identity or recorded hashes differ from the adopted entry.");
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
async function resolveProvenance(input, fingerprint, entries) {
|
|
10897
|
+
const locks = await findLegacyNamedLocks(input.sourceRoot, input.adapter, fingerprint);
|
|
10898
|
+
if (locks.length === 0) {
|
|
10899
|
+
throw new Error(
|
|
10900
|
+
`Source root ${input.sourceRoot} has no legacy-named graph lock for ${input.adapter}/${fingerprint}; provenance cannot be proven.`
|
|
10901
|
+
);
|
|
10902
|
+
}
|
|
10903
|
+
if (input.sourceClass === "own-legacy-owner") return locks[0];
|
|
10904
|
+
if (locks.length !== 1) {
|
|
10905
|
+
throw new Error(`Source root ${input.sourceRoot} must have exactly one legacy-named graph lock for ${fingerprint}, found ${locks.length}.`);
|
|
10906
|
+
}
|
|
10907
|
+
const lock = locks[0];
|
|
10908
|
+
for (const entry of entries) {
|
|
10909
|
+
if (entry.graphLockDigest !== lock.digest) {
|
|
10910
|
+
throw new Error(`Recorded graph-lock digest for ${entry.path} does not match ${lock.path}.`);
|
|
10911
|
+
}
|
|
10912
|
+
if (!lockCoversEntry(lock.lock, entry)) {
|
|
10913
|
+
throw new Error(`Legacy graph lock ${lock.path} does not cover ${entry.path} with its recorded node, selector, and source hash.`);
|
|
10914
|
+
}
|
|
10915
|
+
}
|
|
10916
|
+
return lock;
|
|
10917
|
+
}
|
|
10918
|
+
async function readDestination(identity, stateKey, transport) {
|
|
10919
|
+
if (!await readInstallManifest(identity.targetRoot, identity.adapter, transport, { installationType: identity.installationType, stateKey })) {
|
|
10920
|
+
return void 0;
|
|
10921
|
+
}
|
|
10922
|
+
return requireManifest(identity, stateKey, transport, "destination");
|
|
10923
|
+
}
|
|
10924
|
+
function assertPlainEntry(entry, targetRoot) {
|
|
10925
|
+
if (entry.mode || entry.mergeStrategy || entry.semanticPlugin || entry.semanticCommand || entry.executed) {
|
|
10926
|
+
throw new Error(`Legacy ownership adoption supports only plain file or directory entries; ${entry.path} is not one.`);
|
|
10927
|
+
}
|
|
10928
|
+
containedArtifactPath(targetRoot, entry.path);
|
|
10929
|
+
}
|
|
10930
|
+
function desiredCovers(item, entry) {
|
|
10931
|
+
return item.artifactType === entry.artifactType && item.artifactName === entry.artifactName && item.kind === entry.kind && (!item.packageName || !entry.packageName || item.packageName === entry.packageName);
|
|
10932
|
+
}
|
|
10933
|
+
function coversSourceEntry(destination, source, owner) {
|
|
10934
|
+
return destination.workspaceOwner === owner && !destination.mode && !destination.mergeStrategy && !destination.semanticPlugin && sameRecordedArtifact(destination, source);
|
|
10935
|
+
}
|
|
10936
|
+
function sameRecordedArtifact(left, right) {
|
|
10937
|
+
return left.artifactType === right.artifactType && left.artifactName === right.artifactName && left.kind === right.kind && left.hash === right.hash && left.sourceHash === right.sourceHash;
|
|
10938
|
+
}
|
|
10939
|
+
async function assertNoPendingJournals(targetRoot, adapter, transport) {
|
|
10940
|
+
const pending = await listAllApplyJournals(targetRoot, adapter, transport);
|
|
10941
|
+
if (pending.length > 0) {
|
|
10942
|
+
throw new Error(`Cannot adopt legacy ownership while runtime apply journal(s) are pending: ${pending.map((item) => item.path).join(", ")}`);
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
function assertApplyPreconditions2(request) {
|
|
10946
|
+
for (const [label, value, allowAbsent] of [
|
|
10947
|
+
["plan digest", request.planDigest, false],
|
|
10948
|
+
["source manifest revision", request.expectedSourceRevision, false],
|
|
10949
|
+
["destination manifest revision", request.expectedDestinationRevision, true],
|
|
10950
|
+
["manifest inventory revision", request.expectedInventoryRevision, false]
|
|
10951
|
+
]) {
|
|
10952
|
+
if (allowAbsent && value === absentRevision) continue;
|
|
10953
|
+
if (!value || !/^[a-f0-9]{64}$/.test(value)) {
|
|
10954
|
+
throw new Error(`Applying legacy ownership adoption requires a reviewed lowercase SHA-256 ${label}${allowAbsent ? " or 'absent'" : ""}.`);
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10957
|
+
}
|
|
10958
|
+
function containsPath2(parent, candidate) {
|
|
10959
|
+
const path = relative9(parent, candidate);
|
|
10960
|
+
return path === "" || !path.startsWith("..") && !isAbsolute3(path);
|
|
10961
|
+
}
|
|
10962
|
+
|
|
10469
10963
|
// src/version/policy.ts
|
|
10470
10964
|
import { execFile as execFile3 } from "child_process";
|
|
10471
10965
|
import { readFile as readFile23 } from "fs/promises";
|
|
10472
|
-
import { join as join40, resolve as
|
|
10966
|
+
import { join as join40, resolve as resolve24 } from "path";
|
|
10473
10967
|
import { promisify as promisify3 } from "util";
|
|
10474
10968
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
10475
10969
|
import { z as z5 } from "zod";
|
|
@@ -10563,7 +11057,7 @@ async function discoverVersionsFromSource(pkg, workspaceRoot) {
|
|
|
10563
11057
|
if (tagged.length > 0) return tagged;
|
|
10564
11058
|
}
|
|
10565
11059
|
if (driverName === "local") {
|
|
10566
|
-
const root =
|
|
11060
|
+
const root = resolve24(workspaceRoot, pkg.source);
|
|
10567
11061
|
const manifest2 = await readPackageManifest(root);
|
|
10568
11062
|
const current = manifest2 ? [{ version: manifest2.version, ref: pkg.requestedRef ?? root }] : [];
|
|
10569
11063
|
try {
|
|
@@ -10686,7 +11180,7 @@ async function readVersionCache(path) {
|
|
|
10686
11180
|
// src/profile/members.ts
|
|
10687
11181
|
import { execFile as execFile4 } from "child_process";
|
|
10688
11182
|
import { readFile as readFile24 } from "fs/promises";
|
|
10689
|
-
import { join as join41, resolve as
|
|
11183
|
+
import { join as join41, resolve as resolve25 } from "path";
|
|
10690
11184
|
import { promisify as promisify4 } from "util";
|
|
10691
11185
|
import { z as z7 } from "zod";
|
|
10692
11186
|
|
|
@@ -10861,7 +11355,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
|
|
|
10861
11355
|
let stderr = "";
|
|
10862
11356
|
try {
|
|
10863
11357
|
if (member.transport === "local") {
|
|
10864
|
-
const workspace =
|
|
11358
|
+
const workspace = resolve25(parentWorkspace, member.workspace);
|
|
10865
11359
|
const result = await execFileAsync4(process.execPath, [cliEntry, ...args], {
|
|
10866
11360
|
cwd: workspace,
|
|
10867
11361
|
env: env2,
|
|
@@ -10907,7 +11401,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
|
|
|
10907
11401
|
process.execPath,
|
|
10908
11402
|
[process.argv[1], ...memberCommandArgs(member, args)],
|
|
10909
11403
|
{
|
|
10910
|
-
cwd:
|
|
11404
|
+
cwd: resolve25(parentWorkspace, member.workspace),
|
|
10911
11405
|
env: env2,
|
|
10912
11406
|
maxBuffer: 20 * 1024 * 1024
|
|
10913
11407
|
}
|
|
@@ -11013,7 +11507,7 @@ function assertNoCompositeCycle(workspaceRoot, profileName, chain) {
|
|
|
11013
11507
|
}
|
|
11014
11508
|
}
|
|
11015
11509
|
function compositeKey(workspaceRoot, profileName) {
|
|
11016
|
-
return `${
|
|
11510
|
+
return `${resolve25(workspaceRoot)}#${profileName}`;
|
|
11017
11511
|
}
|
|
11018
11512
|
|
|
11019
11513
|
// src/status/repository.ts
|
|
@@ -11060,7 +11554,7 @@ function valueAfter(lines, prefix) {
|
|
|
11060
11554
|
}
|
|
11061
11555
|
|
|
11062
11556
|
// src/catalogue/client.ts
|
|
11063
|
-
import { createHash as
|
|
11557
|
+
import { createHash as createHash12 } from "crypto";
|
|
11064
11558
|
import { readFile as readFile25, rm as rm8 } from "fs/promises";
|
|
11065
11559
|
import { homedir as homedir9 } from "os";
|
|
11066
11560
|
import { join as join42 } from "path";
|
|
@@ -11335,7 +11829,7 @@ var CatalogueClient = class {
|
|
|
11335
11829
|
}
|
|
11336
11830
|
return {
|
|
11337
11831
|
value: schema.parse(value),
|
|
11338
|
-
digest:
|
|
11832
|
+
digest: createHash12("sha256").update(bytes).digest("hex")
|
|
11339
11833
|
};
|
|
11340
11834
|
}
|
|
11341
11835
|
};
|
|
@@ -11346,7 +11840,7 @@ function sameSources2(a, b) {
|
|
|
11346
11840
|
return a.length === b.length && a.every((source, index) => source === b[index]);
|
|
11347
11841
|
}
|
|
11348
11842
|
function catalogueContentHash(enriched, vercel) {
|
|
11349
|
-
return
|
|
11843
|
+
return createHash12("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
|
|
11350
11844
|
}
|
|
11351
11845
|
|
|
11352
11846
|
// src/search/index.ts
|
|
@@ -11708,7 +12202,7 @@ function includesToken(normalizedText, token) {
|
|
|
11708
12202
|
}
|
|
11709
12203
|
|
|
11710
12204
|
// src/semantic/index.ts
|
|
11711
|
-
import { createHash as
|
|
12205
|
+
import { createHash as createHash13 } from "crypto";
|
|
11712
12206
|
import { homedir as homedir10 } from "os";
|
|
11713
12207
|
import { join as join43 } from "path";
|
|
11714
12208
|
import { env, pipeline } from "@huggingface/transformers";
|
|
@@ -11798,7 +12292,7 @@ var SemanticSearchClient = class {
|
|
|
11798
12292
|
if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
|
|
11799
12293
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
11800
12294
|
if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
|
|
11801
|
-
const digest2 =
|
|
12295
|
+
const digest2 = createHash13("sha256").update(bytes).digest("hex");
|
|
11802
12296
|
if (digest2 !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
|
|
11803
12297
|
return bytes;
|
|
11804
12298
|
}
|
|
@@ -11888,7 +12382,7 @@ function ensureTrailingSlash(value) {
|
|
|
11888
12382
|
}
|
|
11889
12383
|
|
|
11890
12384
|
// src/trial/skill.ts
|
|
11891
|
-
import { createHash as
|
|
12385
|
+
import { createHash as createHash14 } from "crypto";
|
|
11892
12386
|
import { readFile as readFile26, stat as stat13 } from "fs/promises";
|
|
11893
12387
|
import { join as join44 } from "path";
|
|
11894
12388
|
import { parse as parseYaml2 } from "yaml";
|
|
@@ -11918,7 +12412,7 @@ async function createSkillTrial(driver, resolved, selectors) {
|
|
|
11918
12412
|
skill: {
|
|
11919
12413
|
name: artifact.name,
|
|
11920
12414
|
relativePath: artifact.relativePath,
|
|
11921
|
-
sha256:
|
|
12415
|
+
sha256: createHash14("sha256").update(content).digest("hex"),
|
|
11922
12416
|
frontmatter,
|
|
11923
12417
|
content
|
|
11924
12418
|
}
|
|
@@ -11937,12 +12431,12 @@ function readSkillFrontmatter(content, artifact) {
|
|
|
11937
12431
|
}
|
|
11938
12432
|
|
|
11939
12433
|
// src/lifecycle/fleet-normalize.ts
|
|
11940
|
-
import { createHash as
|
|
12434
|
+
import { createHash as createHash15 } from "crypto";
|
|
11941
12435
|
import { readFile as readFile27, readdir as readdir11, rm as rm9 } from "fs/promises";
|
|
11942
12436
|
import { homedir as homedir11 } from "os";
|
|
11943
|
-
import { dirname as dirname22, isAbsolute as
|
|
12437
|
+
import { dirname as dirname22, isAbsolute as isAbsolute4, join as join45, relative as relative10, resolve as resolve26 } from "path";
|
|
11944
12438
|
async function planFleetNormalization(request) {
|
|
11945
|
-
const normalizedRequest =
|
|
12439
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
11946
12440
|
const destinationScope = await resolveWorkspaceScope({
|
|
11947
12441
|
fleet: normalizedRequest.destinationFleet,
|
|
11948
12442
|
globalRoot: normalizedRequest.globalRoot
|
|
@@ -12014,7 +12508,7 @@ async function applyFleetNormalization(request, hooks = {}) {
|
|
|
12014
12508
|
if (!request.apply) throw new Error("Fleet normalization is dry-run by default; pass --apply with --plan-digest <digest>.");
|
|
12015
12509
|
if (!request.planDigest) throw new Error("Applying fleet normalization requires --plan-digest <digest> from a reviewed dry-run.");
|
|
12016
12510
|
assertDigest(request.planDigest);
|
|
12017
|
-
const normalizedRequest =
|
|
12511
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
12018
12512
|
const roots = await normalizationRoots(normalizedRequest);
|
|
12019
12513
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
12020
12514
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -12153,7 +12647,7 @@ async function assertNormalizationPostconditions(journal) {
|
|
|
12153
12647
|
}
|
|
12154
12648
|
}
|
|
12155
12649
|
async function recoverFleetNormalization(request) {
|
|
12156
|
-
const normalizedRequest =
|
|
12650
|
+
const normalizedRequest = normalizeRequest3(request);
|
|
12157
12651
|
const roots = await normalizationRoots(normalizedRequest);
|
|
12158
12652
|
const ordered = [.../* @__PURE__ */ new Set([roots.source, roots.destination])].sort((a, b) => a.localeCompare(b));
|
|
12159
12653
|
return withOrderedLocks(ordered, async () => {
|
|
@@ -12196,7 +12690,7 @@ async function assertSourceFleetPostcondition(request, source, selectedPackages)
|
|
|
12196
12690
|
);
|
|
12197
12691
|
}
|
|
12198
12692
|
}
|
|
12199
|
-
function
|
|
12693
|
+
function normalizeRequest3(request) {
|
|
12200
12694
|
const packages = request.packages === void 0 ? void 0 : sortedUnique6(request.packages);
|
|
12201
12695
|
if (request.packages !== void 0 && packages?.length === 0) throw new Error("--package requires at least one package name.");
|
|
12202
12696
|
const artifacts = request.artifacts === void 0 ? void 0 : sortedUnique6(request.artifacts.map((artifact) => normalizeArtifactSelector(artifact)));
|
|
@@ -12218,17 +12712,17 @@ function normalizeRequest2(request) {
|
|
|
12218
12712
|
...profile ? { profile } : {},
|
|
12219
12713
|
...agent ? { agent } : {},
|
|
12220
12714
|
...orphanedOwnerRoots ? { orphanedOwnerRoots } : {},
|
|
12221
|
-
...request.globalRoot ? { globalRoot:
|
|
12715
|
+
...request.globalRoot ? { globalRoot: resolve26(request.globalRoot) } : {}
|
|
12222
12716
|
};
|
|
12223
12717
|
}
|
|
12224
12718
|
function normalizeOrphanedOwnerRoot(value) {
|
|
12225
12719
|
const root = value.trim();
|
|
12226
|
-
if (!root || !
|
|
12227
|
-
return
|
|
12720
|
+
if (!root || !isAbsolute4(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
|
|
12721
|
+
return resolve26(root);
|
|
12228
12722
|
}
|
|
12229
12723
|
async function normalizationRoots(request) {
|
|
12230
12724
|
const destination = await showRegisteredFleet(request.destinationFleet, { globalRoot: request.globalRoot });
|
|
12231
|
-
const source = request.from === "user" ?
|
|
12725
|
+
const source = request.from === "user" ? resolve26(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
|
|
12232
12726
|
return { source, destination: destination.root };
|
|
12233
12727
|
}
|
|
12234
12728
|
async function withOrderedLocks(roots, fn) {
|
|
@@ -12443,7 +12937,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
12443
12937
|
let normalizedGraphCount = 0;
|
|
12444
12938
|
for (const graph of graphCandidates) {
|
|
12445
12939
|
const destinationState = await targetStateForGraph(fleet, graph, fleet.fleetId, allowOrphanedRetiredPackages);
|
|
12446
|
-
if (
|
|
12940
|
+
if (resolve26(destinationState.graphLockPath) === resolve26(graph.path)) {
|
|
12447
12941
|
const destinationManifest = (await collectManifestPaths([destinationState.manifestPath]))[0];
|
|
12448
12942
|
if (destinationManifest?.manifest.entries.some((entry) => entry.workspaceOwner === fleetOwner && entryMatchesPackages(entry, selected))) {
|
|
12449
12943
|
assertCorrelatedGraphManifest(
|
|
@@ -12458,7 +12952,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
|
|
|
12458
12952
|
}
|
|
12459
12953
|
}
|
|
12460
12954
|
const legacyState = await legacyTargetStateForGraph(fleet, graph, allowOrphanedRetiredPackages);
|
|
12461
|
-
if (
|
|
12955
|
+
if (resolve26(legacyState.graphLockPath) !== resolve26(graph.path)) {
|
|
12462
12956
|
throw new Error(`Legacy graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12463
12957
|
}
|
|
12464
12958
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
@@ -12633,7 +13127,7 @@ function targetKeysForAgent(scope, name) {
|
|
|
12633
13127
|
async function runtimeRoots(scope) {
|
|
12634
13128
|
const roots = /* @__PURE__ */ new Set([scope.root]);
|
|
12635
13129
|
if (scope.config.packages.some((pkg) => pkg.installationType === "user") || Object.values(scope.config.agents).some((agent) => agent.installationType === "user")) {
|
|
12636
|
-
roots.add(
|
|
13130
|
+
roots.add(resolve26(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
|
|
12637
13131
|
}
|
|
12638
13132
|
for (const [name, agent] of Object.entries(scope.config.agents)) {
|
|
12639
13133
|
if (agent.transport === "ssh") throw new Error(`Installed-state normalization cannot inspect SSH agent '${name}' locally.`);
|
|
@@ -12670,14 +13164,14 @@ async function collectManifests(roots) {
|
|
|
12670
13164
|
}
|
|
12671
13165
|
continue;
|
|
12672
13166
|
}
|
|
12673
|
-
if (
|
|
13167
|
+
if (resolve26(parsed.targetRoot) !== resolve26(root)) {
|
|
12674
13168
|
throw new Error(`Install manifest target root does not match its enumerated runtime root: ${path}`);
|
|
12675
13169
|
}
|
|
12676
13170
|
const expectedPath = installManifestPath(parsed.targetRoot, parsed.adapter, {
|
|
12677
13171
|
installationType: parsed.installationType,
|
|
12678
13172
|
stateKey: parsed.stateKey
|
|
12679
13173
|
});
|
|
12680
|
-
if (
|
|
13174
|
+
if (resolve26(expectedPath) !== resolve26(path)) {
|
|
12681
13175
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
12682
13176
|
}
|
|
12683
13177
|
found.set(path, { path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -12698,7 +13192,7 @@ async function collectManifestPaths(paths) {
|
|
|
12698
13192
|
installationType: parsed.installationType,
|
|
12699
13193
|
stateKey: parsed.stateKey
|
|
12700
13194
|
});
|
|
12701
|
-
if (
|
|
13195
|
+
if (resolve26(expectedPath) !== resolve26(path)) {
|
|
12702
13196
|
throw new Error(`Install manifest state identity does not match its canonical path: ${path}`);
|
|
12703
13197
|
}
|
|
12704
13198
|
found.push({ path, raw, manifest: { ...parsed, revision: computeManifestRevision(raw), legacy: false } });
|
|
@@ -12724,7 +13218,7 @@ async function relevantGraphLocks(workspaceRoot, selected, targetKeys, include)
|
|
|
12724
13218
|
if (!path.endsWith(".graph-lock.json")) continue;
|
|
12725
13219
|
const lock = await readGraphLock(path);
|
|
12726
13220
|
if (!lock.canonical.roots.some((candidate2) => selected.has(candidate2.rootId))) continue;
|
|
12727
|
-
const parts =
|
|
13221
|
+
const parts = relative10(root, path).split(/[\\/]/);
|
|
12728
13222
|
if (parts.length !== 3) throw new Error(`Graph lock path is not canonical: ${path}`);
|
|
12729
13223
|
const [targetKey2, adapter] = parts;
|
|
12730
13224
|
if (targetKeys && !targetKeys.has(targetKey2)) continue;
|
|
@@ -12766,7 +13260,7 @@ async function hasRelevantLegacyManifestCandidate(scope, graph, selected) {
|
|
|
12766
13260
|
const legacyManifest = (await collectManifestPaths([legacyState.manifestPath]))[0];
|
|
12767
13261
|
if (legacyManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected))) return true;
|
|
12768
13262
|
const stableState = await targetStateForGraph(scope, graph, scope.fleetId ?? null);
|
|
12769
|
-
if (
|
|
13263
|
+
if (resolve26(stableState.graphLockPath) !== resolve26(graph.path)) return false;
|
|
12770
13264
|
const stableManifest = (await collectManifestPaths([stableState.manifestPath]))[0];
|
|
12771
13265
|
return stableManifest?.manifest.entries.some((entry) => entryMatchesGraphPackages(entry, graph.lock, selected)) ?? false;
|
|
12772
13266
|
}
|
|
@@ -12787,7 +13281,7 @@ async function legacyManifestPathForConfiguredTarget(scope, graph) {
|
|
|
12787
13281
|
baseDir: scope.root
|
|
12788
13282
|
});
|
|
12789
13283
|
const targetRoot = resolveConfigPath(agent.root, scope.root);
|
|
12790
|
-
const installRoot =
|
|
13284
|
+
const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
|
|
12791
13285
|
const stateKey = stateKeyFor(adapter.name, {
|
|
12792
13286
|
installationType: agent.installationType,
|
|
12793
13287
|
stateKey: agent.stateKey,
|
|
@@ -12855,7 +13349,7 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
12855
13349
|
ssh: void 0,
|
|
12856
13350
|
...agent?.stateKey ? { stateKey: agent.stateKey } : {}
|
|
12857
13351
|
};
|
|
12858
|
-
const installRoot =
|
|
13352
|
+
const installRoot = resolve26(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
|
|
12859
13353
|
const targetIdentity = resolveTargetStateIdentity({
|
|
12860
13354
|
targetFingerprintParts: fingerprintParts,
|
|
12861
13355
|
workspaceRoot: scope.root,
|
|
@@ -12887,16 +13381,16 @@ async function targetStateForGraph(scope, graph, identityFleetId = scope.fleetId
|
|
|
12887
13381
|
async function installedTargetStateForGraph(scope, graph, manifests, owners, selected, identityFleetId = scope.fleetId ?? null) {
|
|
12888
13382
|
const stable = await targetStateForGraph(scope, graph, identityFleetId);
|
|
12889
13383
|
let state;
|
|
12890
|
-
if (
|
|
13384
|
+
if (resolve26(graph.path) === resolve26(stable.graphLockPath)) {
|
|
12891
13385
|
state = stable;
|
|
12892
13386
|
} else {
|
|
12893
13387
|
const released = await releasedTargetStateForGraph(scope, graph, identityFleetId);
|
|
12894
|
-
if (
|
|
13388
|
+
if (resolve26(graph.path) !== resolve26(released.graphLockPath)) {
|
|
12895
13389
|
throw new Error(`Graph lock target identity is stale or noncanonical: ${graph.path}`);
|
|
12896
13390
|
}
|
|
12897
13391
|
state = released;
|
|
12898
13392
|
}
|
|
12899
|
-
const manifest = manifests.find((candidate) =>
|
|
13393
|
+
const manifest = manifests.find((candidate) => resolve26(candidate.path) === resolve26(state.manifestPath));
|
|
12900
13394
|
if (!manifest) {
|
|
12901
13395
|
throw new Error(`Graph lock is not covered by its canonical install manifest: ${state.manifestPath}`);
|
|
12902
13396
|
}
|
|
@@ -12928,7 +13422,7 @@ async function releasedTargetStateForGraph(scope, graph, identityFleetId = scope
|
|
|
12928
13422
|
};
|
|
12929
13423
|
}
|
|
12930
13424
|
function assertCorrelatedGraphManifest(graph, state, manifest, owners, selected) {
|
|
12931
|
-
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey ||
|
|
13425
|
+
if (manifest.manifest.adapter !== state.adapter || manifest.manifest.installationType !== state.installationType || manifest.manifest.stateKey !== state.stateKey || resolve26(manifest.manifest.targetRoot) !== resolve26(state.installRoot)) {
|
|
12932
13426
|
throw new Error(`Install manifest identity does not match graph target state: ${manifest.path}`);
|
|
12933
13427
|
}
|
|
12934
13428
|
const entries = manifest.manifest.entries.filter((entry) => entryMatchesGraphPackages(entry, graph.lock, selected));
|
|
@@ -12985,7 +13479,7 @@ function destinationGraphFromSource(source, targetFingerprint) {
|
|
|
12985
13479
|
function mergeDestinationManifest(existing, state, generatedAt, desiredEntries, replaceOwners = /* @__PURE__ */ new Set()) {
|
|
12986
13480
|
if (existing) {
|
|
12987
13481
|
const manifest2 = existing.manifest;
|
|
12988
|
-
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey ||
|
|
13482
|
+
if (manifest2.adapter !== state.adapter || manifest2.installationType !== state.installationType || manifest2.stateKey !== state.stateKey || resolve26(manifest2.targetRoot) !== state.installRoot) {
|
|
12989
13483
|
throw new Error(`Destination manifest identity diverges from the derived target: ${existing.path}`);
|
|
12990
13484
|
}
|
|
12991
13485
|
}
|
|
@@ -13045,7 +13539,7 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
13045
13539
|
const content = await readFile27(path, "utf8");
|
|
13046
13540
|
if (hasMergeRemovalContent(entry.mergeRemoval)) {
|
|
13047
13541
|
assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
|
|
13048
|
-
} else if (
|
|
13542
|
+
} else if (resolve26(sourcePath) === resolve26(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
|
|
13049
13543
|
assertExactMergeContribution({}, entry.mergeStrategy, content);
|
|
13050
13544
|
} else {
|
|
13051
13545
|
throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
|
|
@@ -13056,8 +13550,8 @@ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath)
|
|
|
13056
13550
|
await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
|
|
13057
13551
|
}
|
|
13058
13552
|
function renderedEntryPathForRoot(root, entryPath) {
|
|
13059
|
-
const normalizedRoot =
|
|
13060
|
-
const candidate =
|
|
13553
|
+
const normalizedRoot = resolve26(root);
|
|
13554
|
+
const candidate = resolve26(normalizedRoot, entryPath);
|
|
13061
13555
|
if (candidate !== normalizedRoot && !candidate.startsWith(`${normalizedRoot}/`)) {
|
|
13062
13556
|
throw new Error(`Install manifest entry escapes its target root: ${entryPath}`);
|
|
13063
13557
|
}
|
|
@@ -13148,8 +13642,8 @@ function ownerMatchesGraphPackage(lock, owner, selected) {
|
|
|
13148
13642
|
return ownerMatchesPackage(owner, selected) || lock.canonical.roots.some((root) => selected.has(root.rootId) && root.graphNodeId === owner);
|
|
13149
13643
|
}
|
|
13150
13644
|
function renderedEntryPath(manifest, entry) {
|
|
13151
|
-
const root =
|
|
13152
|
-
const candidate =
|
|
13645
|
+
const root = resolve26(manifest.targetRoot);
|
|
13646
|
+
const candidate = resolve26(root, entry.path);
|
|
13153
13647
|
if (candidate !== root && !candidate.startsWith(`${root}/`)) {
|
|
13154
13648
|
throw new Error(`Install manifest entry escapes its target root: ${entry.path}`);
|
|
13155
13649
|
}
|
|
@@ -13503,7 +13997,7 @@ function sortValue(value) {
|
|
|
13503
13997
|
return value;
|
|
13504
13998
|
}
|
|
13505
13999
|
function sha2565(value) {
|
|
13506
|
-
return
|
|
14000
|
+
return createHash15("sha256").update(value).digest("hex");
|
|
13507
14001
|
}
|
|
13508
14002
|
function assertDigest(value) {
|
|
13509
14003
|
if (!/^[a-f0-9]{64}$/.test(value)) throw new Error(`Invalid plan digest: ${value}`);
|
|
@@ -13543,6 +14037,7 @@ var commandGovernance = {
|
|
|
13543
14037
|
"ownership handoff": "governed-unless-dry-run",
|
|
13544
14038
|
"ownership recovery-plan": "incidental-cache",
|
|
13545
14039
|
"ownership retire-stale": "governed-apply",
|
|
14040
|
+
"ownership adopt-legacy": "governed-apply",
|
|
13546
14041
|
"mcp retire": "governed-apply",
|
|
13547
14042
|
eject: "governed-always",
|
|
13548
14043
|
uninstall: "governed-unless-dry-run",
|
|
@@ -13938,10 +14433,13 @@ program.command("remember").description("append text to the local instructions o
|
|
|
13938
14433
|
console.log(nextInstallNudge());
|
|
13939
14434
|
});
|
|
13940
14435
|
var ownershipCommand = program.command("ownership").description("inspect and transfer manifest ownership without rewriting runtime artifacts");
|
|
13941
|
-
ownershipCommand.command("handoff").description("transfer one managed artifact between Agentwheel workspace roots").argument("<selector>", "exact artifact selector in type/name form").
|
|
14436
|
+
ownershipCommand.command("handoff").description("transfer one managed artifact between Agentwheel workspace roots").argument("<selector>", "exact artifact selector in type/name form").option("--from-workspace-root <path>", "current owning workspace root").option("--from-unknown-owner", "hand off an artifact recorded as workspace:unknown", false).requiredOption("--to-workspace-root <path>", "new owning workspace root").option("--to-fleet <id>", "qualify the new owner with a registered fleet id").option("--expected-hash <sha256>", "expected recorded artifact hash; required when applying").option("--expected-revision <sha256>", "expected install manifest revision; required when applying").option("--carry-drift", "accept a file or directory whose runtime differs from the recorded hash; the recorded hash is kept", false).option("--expected-runtime-hash <sha256>", "expected drifted runtime hash; required when applying with --carry-drift").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").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("--profile <name>", "workspace runtime profile (must resolve to one target)").option("--dry-run", "validate all preconditions without writing the manifest", false).action(async (selector, options) => {
|
|
13942
14437
|
if (!options.dryRun && (!options.expectedHash || !options.expectedRevision)) {
|
|
13943
14438
|
throw new Error("Applying an ownership handoff requires --expected-hash and --expected-revision from a reviewed --dry-run.");
|
|
13944
14439
|
}
|
|
14440
|
+
if (Boolean(options.fromWorkspaceRoot) === (options.fromUnknownOwner === true)) {
|
|
14441
|
+
throw new Error("Ownership handoff requires exactly one of --from-workspace-root or --from-unknown-owner.");
|
|
14442
|
+
}
|
|
13945
14443
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
13946
14444
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
13947
14445
|
if (targets.length !== 1) {
|
|
@@ -13962,7 +14460,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13962
14460
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
13963
14461
|
if (options.toFleet) {
|
|
13964
14462
|
const fleet = await showRegisteredFleet(options.toFleet);
|
|
13965
|
-
if (
|
|
14463
|
+
if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
|
|
13966
14464
|
throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
13967
14465
|
}
|
|
13968
14466
|
}
|
|
@@ -13973,11 +14471,13 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13973
14471
|
adapter: adapter.name,
|
|
13974
14472
|
artifactType,
|
|
13975
14473
|
artifactName,
|
|
13976
|
-
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
14474
|
+
...options.fromUnknownOwner === true ? { fromUnknownOwner: true } : { fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot) },
|
|
13977
14475
|
toWorkspaceRoot,
|
|
13978
14476
|
toFleetId: options.toFleet,
|
|
13979
14477
|
expectedHash: options.expectedHash,
|
|
13980
14478
|
expectedRevision: options.expectedRevision,
|
|
14479
|
+
carryDrift: options.carryDrift === true,
|
|
14480
|
+
expectedRuntimeHash: options.expectedRuntimeHash,
|
|
13981
14481
|
transport: transportForTarget(target)
|
|
13982
14482
|
};
|
|
13983
14483
|
const result = options.dryRun ? await planArtifactOwnershipHandoff(request) : await applyArtifactOwnershipHandoff(request);
|
|
@@ -13985,6 +14485,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
|
|
|
13985
14485
|
console.log(`Target: ${result.adapter}/${result.installationType ?? "default"} at ${result.targetRoot}`);
|
|
13986
14486
|
console.log(`Path: ${result.path}`);
|
|
13987
14487
|
console.log(`Hash: ${result.artifactHash}`);
|
|
14488
|
+
if (result.drifted) console.log(`Runtime hash: ${result.runtimeHash} (drift carried; the recorded hash is kept)`);
|
|
13988
14489
|
console.log(`Manifest revision: ${result.manifestRevision}`);
|
|
13989
14490
|
console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
|
|
13990
14491
|
});
|
|
@@ -13996,49 +14497,15 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
|
|
|
13996
14497
|
const adapter = await resolveAdapterForTarget(target, {});
|
|
13997
14498
|
const transport = transportForTarget(target);
|
|
13998
14499
|
const requestedReport = normalizeCliPath(options.report);
|
|
13999
|
-
const reportPath = join46(await
|
|
14000
|
-
const reportInsideTarget =
|
|
14001
|
-
if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !
|
|
14500
|
+
const reportPath = join46(await realpath3(dirname23(requestedReport)), basename21(requestedReport));
|
|
14501
|
+
const reportInsideTarget = relative11(resolve27(target.targetRoot), reportPath);
|
|
14502
|
+
if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute5(reportInsideTarget)) {
|
|
14002
14503
|
throw new Error("Recovery report must be outside the runtime target root.");
|
|
14003
14504
|
}
|
|
14004
14505
|
const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
|
|
14005
14506
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14006
14507
|
if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
|
|
14007
|
-
|
|
14008
|
-
const priorTmpdir = process.env.TMPDIR;
|
|
14009
|
-
try {
|
|
14010
|
-
process.env.TMPDIR = stage;
|
|
14011
|
-
const graphPlan = await createGraphSourcePlan({
|
|
14012
|
-
roots: config.packages.map((pkg) => ({
|
|
14013
|
-
rootId: pkg.name,
|
|
14014
|
-
source: pkg.source,
|
|
14015
|
-
mode: pkg.mode,
|
|
14016
|
-
version: pkg.version,
|
|
14017
|
-
ref: pkg.requestedRef,
|
|
14018
|
-
select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
14019
|
-
selection: pkg.selection,
|
|
14020
|
-
aliases: pkg.aliases,
|
|
14021
|
-
overrides: pkg.overrides,
|
|
14022
|
-
includeSuggestions: pkg.withSuggestions,
|
|
14023
|
-
suggestionAliases: pkg.suggestions,
|
|
14024
|
-
useLock: false
|
|
14025
|
-
})),
|
|
14026
|
-
targetRoot: target.targetRoot,
|
|
14027
|
-
workspaceRoot: target.workspaceRoot,
|
|
14028
|
-
fleetId: target.fleetId,
|
|
14029
|
-
adapter,
|
|
14030
|
-
transport,
|
|
14031
|
-
targetKey: targetKeyForTarget(target, adapter.name),
|
|
14032
|
-
targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptionsForTarget(target, {}), installationType),
|
|
14033
|
-
installationType,
|
|
14034
|
-
stateKey: target.stateKey,
|
|
14035
|
-
readOnly: true,
|
|
14036
|
-
yes: true,
|
|
14037
|
-
freshGraphOnly: true,
|
|
14038
|
-
deferForeignStateCheck: true,
|
|
14039
|
-
cacheRoot: join46(stage, "source-cache"),
|
|
14040
|
-
registryCachePath: join46(stage, "registry.json")
|
|
14041
|
-
});
|
|
14508
|
+
await withFreshReadOnlyGraphPlan(target, adapter, adapterOptionsForTarget(target, {}), installationType, config.packages, async (graphPlan) => {
|
|
14042
14509
|
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
|
|
14043
14510
|
const report = await planLegacyRecovery({
|
|
14044
14511
|
graphPlan,
|
|
@@ -14056,17 +14523,13 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
|
|
|
14056
14523
|
`, { mode: 384, flag: "wx" });
|
|
14057
14524
|
console.log(`Recovery report: ${reportPath}`);
|
|
14058
14525
|
console.log(JSON.stringify(report.counts));
|
|
14059
|
-
}
|
|
14060
|
-
if (priorTmpdir === void 0) delete process.env.TMPDIR;
|
|
14061
|
-
else process.env.TMPDIR = priorTmpdir;
|
|
14062
|
-
await rm10(stage, { recursive: true, force: true });
|
|
14063
|
-
}
|
|
14526
|
+
});
|
|
14064
14527
|
});
|
|
14065
14528
|
ownershipCommand.command("retire-stale").description("retire stale source-manifest ownership already covered by one Fleet manifest").requiredOption("--from-workspace-root <path>", "exact stale workspace owner root").option("--from-fleet-id <id>", "exact stale Fleet owner id for same-Fleet duplicate state").requiredOption("--to-workspace-root <path>", "registered destination Fleet root").requiredOption("--source-state-key <key>", "exact source install-manifest state key").option("--destination-state-key <key>", "exact Fleet-owned destination install-manifest state key (defaults to the stable target state)").requiredOption("--fleet <id>", "registered destination Fleet").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").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 destination Fleet agent").option("--profile <name>", "destination Fleet profile resolving to one target").option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--abandon-incomplete-merge-owner", "explicitly retire incomplete legacy merge metadata without changing runtime bytes", false).option("--apply", "apply the exact reviewed metadata-only retirement", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
14066
14529
|
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
14067
14530
|
const fleet = await showRegisteredFleet(options.fleet);
|
|
14068
14531
|
const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
|
|
14069
|
-
if (
|
|
14532
|
+
if (resolve27(fleet.root) !== resolve27(toWorkspaceRoot)) {
|
|
14070
14533
|
throw new Error(`Destination Fleet '${options.fleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
|
|
14071
14534
|
}
|
|
14072
14535
|
const targets = await resolveCliTargets(normalizedOptions);
|
|
@@ -14074,7 +14537,7 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14074
14537
|
throw new Error(`Stale ownership retirement requires exactly one destination Fleet target, found ${targets.length}.`);
|
|
14075
14538
|
}
|
|
14076
14539
|
const target = targets[0];
|
|
14077
|
-
if (target.fleetId !== options.fleet ||
|
|
14540
|
+
if (target.fleetId !== options.fleet || resolve27(target.workspaceRoot) !== resolve27(fleet.root)) {
|
|
14078
14541
|
throw new Error(`Resolved target does not belong to destination Fleet '${options.fleet}'.`);
|
|
14079
14542
|
}
|
|
14080
14543
|
const adapterOptions = adapterOptionsForTarget(target, normalizedOptions);
|
|
@@ -14146,6 +14609,138 @@ ownershipCommand.command("retire-stale").description("retire stale source-manife
|
|
|
14146
14609
|
console.log(`Plan digest: ${result.planDigest}`);
|
|
14147
14610
|
if (!options.apply) console.log(`Apply: ${applyCommand}`);
|
|
14148
14611
|
});
|
|
14612
|
+
ownershipCommand.command("adopt-legacy").description("move proven legacy-keyed ownership into this workspace's stable target state without rewriting runtime artifacts").requiredOption("--source-state-key <key>", "exact legacy install-manifest state key").requiredOption("--from-workspace-root <path>", "exact plain workspace owner root of the entries to move").option("--agent <name>", "named agent of this workspace").option("--profile <name>", "workspace profile resolving to one target").option("-i, --installation-type <type>", "installation type (for example local or user)").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("--carry-drift", "also move entries whose runtime differs from the recorded hash; the recorded hash is kept", false).option("--plan-digest <sha256>", "reviewed plan digest; required with --apply").option("--expected-source-revision <sha256>", "reviewed source manifest revision; required with --apply").option("--expected-destination-revision <sha256|absent>", "reviewed destination manifest revision; required with --apply").option("--expected-inventory-revision <sha256>", "reviewed runtime manifest inventory revision; required with --apply").option("--apply", "apply the exact reviewed metadata-only adoption", false).option("--json", "print the complete deterministic plan as JSON", false).action(async (options) => {
|
|
14613
|
+
if (Boolean(options.agent) === Boolean(options.profile)) {
|
|
14614
|
+
throw new Error("Legacy ownership adoption requires exactly one of --agent or --profile.");
|
|
14615
|
+
}
|
|
14616
|
+
const normalizedOptions = normalizeRuntimeScopeOptions(options);
|
|
14617
|
+
const requestedInstallationType = normalizedOptions.installationType;
|
|
14618
|
+
const plainInstallOptions = {
|
|
14619
|
+
...normalizedOptions,
|
|
14620
|
+
installationType: void 0,
|
|
14621
|
+
adapterConfig: void 0,
|
|
14622
|
+
adapterModule: void 0
|
|
14623
|
+
};
|
|
14624
|
+
const targets = await resolveCliTargets(plainInstallOptions);
|
|
14625
|
+
if (targets.length !== 1) {
|
|
14626
|
+
throw new Error(`Legacy ownership adoption requires exactly one runtime target, found ${targets.length}.`);
|
|
14627
|
+
}
|
|
14628
|
+
const target = targets[0];
|
|
14629
|
+
if (target.fleetId) {
|
|
14630
|
+
throw new Error("Legacy ownership adoption targets a nested workspace; a Fleet target uses fleet normalize or ownership retire-stale.");
|
|
14631
|
+
}
|
|
14632
|
+
if (target.transport === "ssh") throw new Error("Legacy ownership adoption does not support SSH targets.");
|
|
14633
|
+
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14634
|
+
const groups = /* @__PURE__ */ new Map();
|
|
14635
|
+
for (const pkg of config.packages) graphGroupForPackage(groups, target, pkg, plainInstallOptions).packages.push(pkg);
|
|
14636
|
+
if (groups.size === 0) throw new Error(`No configured source packages in ${target.workspaceRoot}.`);
|
|
14637
|
+
const installationTypes = [...new Set([...groups.values()].map((candidate) => candidate.installationType))];
|
|
14638
|
+
if (installationTypes.length > 1) {
|
|
14639
|
+
throw new Error(
|
|
14640
|
+
`Configured packages use installation types ${installationTypes.join(", ")}, each with its own install state; legacy ownership adoption needs exactly one.`
|
|
14641
|
+
);
|
|
14642
|
+
}
|
|
14643
|
+
if (requestedInstallationType !== void 0 && requestedInstallationType !== installationTypes[0]) {
|
|
14644
|
+
throw new Error(
|
|
14645
|
+
`--installation-type ${requestedInstallationType} does not match installation type ${installationTypes[0]}, which install uses for the configured packages.`
|
|
14646
|
+
);
|
|
14647
|
+
}
|
|
14648
|
+
if (groups.size > 1) {
|
|
14649
|
+
throw new Error(
|
|
14650
|
+
`Configured packages resolve to ${groups.size} adapter configurations for ${installationTypes[0]}, each with its own install state; legacy ownership adoption needs exactly one.`
|
|
14651
|
+
);
|
|
14652
|
+
}
|
|
14653
|
+
const group = [...groups.values()][0];
|
|
14654
|
+
const { installationType, adapterOptions } = group;
|
|
14655
|
+
const requestedAdapterSettings = [
|
|
14656
|
+
{ flag: "--adapter-config", requested: normalizedOptions.adapterConfig, resolved: adapterOptions.adapterConfig, none: "no adapter config" },
|
|
14657
|
+
{ flag: "--adapter-module", requested: normalizedOptions.adapterModule, resolved: adapterOptions.adapterModule, none: "no adapter module" }
|
|
14658
|
+
];
|
|
14659
|
+
for (const { flag, requested, resolved, none } of requestedAdapterSettings) {
|
|
14660
|
+
if (requested === void 0) continue;
|
|
14661
|
+
if (resolved !== void 0 && resolve27(target.workspaceRoot, requested) === resolve27(target.workspaceRoot, resolved)) continue;
|
|
14662
|
+
throw new Error(`${flag} ${requested} does not match what install resolves for the configured packages: ${resolved ?? none}.`);
|
|
14663
|
+
}
|
|
14664
|
+
const adapter = await resolveAdapterForTarget(group.target, adapterOptions);
|
|
14665
|
+
const stable = installStateForTarget(group.target, adapter, adapterOptions, installationType);
|
|
14666
|
+
const stableGraphLockPath = graphLockPathForTarget(
|
|
14667
|
+
group.target.workspaceRoot,
|
|
14668
|
+
targetKeyForTarget(group.target, adapter.name),
|
|
14669
|
+
adapter.name,
|
|
14670
|
+
targetFingerprintParts(group.target, adapter, adapterOptions, installationType),
|
|
14671
|
+
targetIdentityContext(group.target, adapter, installationType)
|
|
14672
|
+
);
|
|
14673
|
+
const desiredCoverage = await withFreshReadOnlyGraphPlan(group.target, adapter, adapterOptions, installationType, group.packages, async (graphPlan) => {
|
|
14674
|
+
if (graphPlan.plan.stateKey !== stable.stateKey || graphPlan.graphLockPath !== stableGraphLockPath) {
|
|
14675
|
+
throw new Error("Current source graph resolved a different target state identity; legacy ownership adoption cannot proceed.");
|
|
14676
|
+
}
|
|
14677
|
+
return desiredCoverageFromPlan(graphPlan.plan);
|
|
14678
|
+
});
|
|
14679
|
+
const request = {
|
|
14680
|
+
targetRoot: stable.installRoot,
|
|
14681
|
+
adapter: adapter.name,
|
|
14682
|
+
installationType,
|
|
14683
|
+
sourceStateKey: options.sourceStateKey,
|
|
14684
|
+
destinationStateKey: stable.stateKey,
|
|
14685
|
+
fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
|
|
14686
|
+
workspaceRoot: target.workspaceRoot,
|
|
14687
|
+
destinationFleetId: target.fleetId,
|
|
14688
|
+
destinationGraphLockPath: stableGraphLockPath,
|
|
14689
|
+
desiredCoverage,
|
|
14690
|
+
carryDrift: options.carryDrift === true,
|
|
14691
|
+
planDigest: options.planDigest,
|
|
14692
|
+
expectedSourceRevision: options.expectedSourceRevision,
|
|
14693
|
+
expectedDestinationRevision: options.expectedDestinationRevision,
|
|
14694
|
+
expectedInventoryRevision: options.expectedInventoryRevision,
|
|
14695
|
+
transport: transportForTarget(target)
|
|
14696
|
+
};
|
|
14697
|
+
const result = options.apply ? await applyAdoptLegacyOwnership(request) : await planAdoptLegacyOwnership(request);
|
|
14698
|
+
const destinationRevision = result.destination.revision ?? "absent";
|
|
14699
|
+
const applyArgs = [
|
|
14700
|
+
"agentwheel",
|
|
14701
|
+
"ownership",
|
|
14702
|
+
"adopt-legacy",
|
|
14703
|
+
"--source-state-key",
|
|
14704
|
+
result.source.stateKey,
|
|
14705
|
+
"--from-workspace-root",
|
|
14706
|
+
request.fromWorkspaceRoot,
|
|
14707
|
+
...options.profile ? ["--profile", options.profile] : ["--agent", options.agent],
|
|
14708
|
+
"--installation-type",
|
|
14709
|
+
installationType,
|
|
14710
|
+
...options.adapterConfig ? ["--adapter-config", options.adapterConfig] : [],
|
|
14711
|
+
...options.adapterModule ? ["--adapter-module", options.adapterModule] : [],
|
|
14712
|
+
...options.allowAdapterCode ? ["--allow-adapter-code"] : [],
|
|
14713
|
+
...request.carryDrift ? ["--carry-drift"] : [],
|
|
14714
|
+
"--plan-digest",
|
|
14715
|
+
result.planDigest,
|
|
14716
|
+
"--expected-source-revision",
|
|
14717
|
+
result.source.revision,
|
|
14718
|
+
"--expected-destination-revision",
|
|
14719
|
+
destinationRevision,
|
|
14720
|
+
"--expected-inventory-revision",
|
|
14721
|
+
result.manifestInventoryRevision,
|
|
14722
|
+
"--apply"
|
|
14723
|
+
];
|
|
14724
|
+
const applyCommand = applyArgs.map(shellQuoteArgument).join(" ");
|
|
14725
|
+
if (options.json) {
|
|
14726
|
+
console.log(JSON.stringify({ ...result, applyCommand }, null, 2));
|
|
14727
|
+
return;
|
|
14728
|
+
}
|
|
14729
|
+
console.log(`${options.apply ? "Adopted legacy ownership" : "Legacy ownership adoption plan"}: ${result.selected.length} entr${result.selected.length === 1 ? "y" : "ies"}`);
|
|
14730
|
+
console.log(`Target: ${result.adapter}/${result.installationType} at ${result.targetRoot}`);
|
|
14731
|
+
console.log(`Source: ${result.source.stateKey} revision ${result.source.revision} (${result.source.class}, ${result.source.owner})`);
|
|
14732
|
+
console.log(`Provenance: ${result.source.provenanceLock.path} (${result.source.provenanceLock.digest})`);
|
|
14733
|
+
console.log(`Destination: ${result.destination.stateKey} revision ${destinationRevision} (${result.destination.owner})`);
|
|
14734
|
+
console.log(`Manifest inventory revision: ${result.manifestInventoryRevision}`);
|
|
14735
|
+
for (const entry of result.selected) {
|
|
14736
|
+
const drift = entry.drift ? ` drift: recorded ${entry.recordedHash}, runtime ${entry.runtimeHash ?? "missing"}` : "";
|
|
14737
|
+
console.log(`- ${entry.path}: ${entry.action}${drift}`);
|
|
14738
|
+
}
|
|
14739
|
+
for (const entry of result.retained) console.log(` retained ${entry.path} (${entry.reason}, ${entry.owner})`);
|
|
14740
|
+
for (const entry of result.remainingDuplicates) console.log(` duplicate claim remains in ${entry.stateKey}: ${entry.path}`);
|
|
14741
|
+
console.log(`Plan digest: ${result.planDigest}`);
|
|
14742
|
+
if (!options.apply) console.log(`Apply from ${target.workspaceRoot}: ${applyCommand}`);
|
|
14743
|
+
});
|
|
14149
14744
|
var mcpCommand = program.command("mcp").description("operate on MCP runtime configuration");
|
|
14150
14745
|
mcpCommand.command("retire").description("remove one exact legacy MCP contribution with explicit state ownership").argument("<package>", "configured package containing exactly one legacy MCP artifact").option("--user", "use the user workspace", false).option("--local", "use the nearest local workspace", false).option("--fleet <id>", "use one registered named fleet").option("-t, --target-root <path>", "explicit runtime/project root").option("--agent <name>", "one named agent from merged config").option("--profile <name>", "workspace runtime profile resolving to one target").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--from-workspace-root <path>", "required previous owner when managed legacy state exists").option("--dry-run", "show the retirement plan without writing (default)", false).option("--apply", "apply the exact reviewed retirement plan", false).option("--json", "print the install plan as JSON", false).action(async (packageName, options) => {
|
|
14151
14746
|
if (options.apply && options.dryRun) throw new Error("--apply cannot be combined with --dry-run.");
|
|
@@ -14400,30 +14995,34 @@ async function runInstallCommand(nameOrSource, options, behavior) {
|
|
|
14400
14995
|
throw teachingInstallError(nameOrSource, error);
|
|
14401
14996
|
}
|
|
14402
14997
|
}
|
|
14403
|
-
|
|
14404
|
-
|
|
14405
|
-
|
|
14406
|
-
|
|
14407
|
-
if (
|
|
14408
|
-
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
executePlugins
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
|
|
14418
|
-
|
|
14419
|
-
|
|
14420
|
-
|
|
14421
|
-
|
|
14422
|
-
if (
|
|
14998
|
+
const results = await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage, reportFormat: outputFormat }, { mode: "install" });
|
|
14999
|
+
try {
|
|
15000
|
+
for (const result of results) {
|
|
15001
|
+
if (!behavior.quiet) console.log(formatGraphPlan(result));
|
|
15002
|
+
if (behavior.apply) {
|
|
15003
|
+
declareMutationPath(result.graphLockPath);
|
|
15004
|
+
if (extraPackage && !targetOptions.onlySource) declareMutationPath(workspaceConfigPath(target.workspaceRoot));
|
|
15005
|
+
const transport = transportForTarget(target);
|
|
15006
|
+
const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
|
|
15007
|
+
await applyCombinedInstallPlan(result.plan, {
|
|
15008
|
+
executePlugins,
|
|
15009
|
+
transport,
|
|
15010
|
+
graphLockDigest: result.graphLockDigest,
|
|
15011
|
+
graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
|
|
15012
|
+
});
|
|
15013
|
+
const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
|
|
15014
|
+
enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
|
|
15015
|
+
executePlugins
|
|
15016
|
+
});
|
|
15017
|
+
if (!behavior.quiet) {
|
|
15018
|
+
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
15019
|
+
if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
|
|
15020
|
+
}
|
|
14423
15021
|
}
|
|
15022
|
+
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
14424
15023
|
}
|
|
14425
|
-
|
|
14426
|
-
|
|
15024
|
+
} finally {
|
|
15025
|
+
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
14427
15026
|
}
|
|
14428
15027
|
if (behavior.apply && extraPackage && !targetOptions.onlySource) {
|
|
14429
15028
|
await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), extraPackage));
|
|
@@ -14874,31 +15473,43 @@ async function runSkillUpdateCommand(skillName, options) {
|
|
|
14874
15473
|
}
|
|
14875
15474
|
async function runConfiguredGraphPackages(target, options, behavior) {
|
|
14876
15475
|
const results = await buildGraphPlansForTarget(target, void 0, options, behavior);
|
|
14877
|
-
|
|
14878
|
-
|
|
14879
|
-
|
|
14880
|
-
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
|
|
14884
|
-
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
15476
|
+
try {
|
|
15477
|
+
for (const result of results) {
|
|
15478
|
+
console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
|
|
15479
|
+
console.log(formatGraphPlan(result));
|
|
15480
|
+
if (!options.dryRun) {
|
|
15481
|
+
declareMutationPath(result.graphLockPath);
|
|
15482
|
+
const transport = transportForTarget(target);
|
|
15483
|
+
const executePlugins = target.executePlugins ?? options.executePlugins;
|
|
15484
|
+
await applyCombinedInstallPlan(result.plan, {
|
|
15485
|
+
executePlugins,
|
|
15486
|
+
transport,
|
|
15487
|
+
graphLockDigest: result.graphLockDigest,
|
|
15488
|
+
graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
|
|
15489
|
+
});
|
|
15490
|
+
const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
|
|
15491
|
+
enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
|
|
15492
|
+
executePlugins
|
|
15493
|
+
});
|
|
15494
|
+
console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
|
|
15495
|
+
if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
|
|
15496
|
+
}
|
|
15497
|
+
if (result.plan.hasBlockingChanges) process.exitCode = 1;
|
|
14896
15498
|
}
|
|
14897
|
-
|
|
14898
|
-
|
|
15499
|
+
} finally {
|
|
15500
|
+
await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
|
|
14899
15501
|
}
|
|
14900
15502
|
}
|
|
14901
15503
|
async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
15504
|
+
const bundleRoots = [];
|
|
15505
|
+
try {
|
|
15506
|
+
return await collectGraphPlansForTarget(target, source, options, behavior, bundleRoots);
|
|
15507
|
+
} catch (error) {
|
|
15508
|
+
await Promise.all(bundleRoots.map((root) => rm10(root, { recursive: true, force: true })));
|
|
15509
|
+
throw error;
|
|
15510
|
+
}
|
|
15511
|
+
}
|
|
15512
|
+
async function collectGraphPlansForTarget(target, source, options, behavior, bundleRoots) {
|
|
14902
15513
|
const targetOptions = optionsForResolvedTarget(options, target);
|
|
14903
15514
|
const config = await readMergedWorkspaceConfig(target.workspaceRoot);
|
|
14904
15515
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -15092,6 +15703,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
|
|
|
15092
15703
|
expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner,
|
|
15093
15704
|
recoverLegacyState: targetOptions.recoverLegacyState
|
|
15094
15705
|
});
|
|
15706
|
+
bundleRoots.push(result.bundle.root);
|
|
15095
15707
|
if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
|
|
15096
15708
|
const manifest = result.previousManifest;
|
|
15097
15709
|
const previousLock = result.previousGraphLock;
|
|
@@ -15161,7 +15773,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
|
|
|
15161
15773
|
selectedPreviousNodeIds,
|
|
15162
15774
|
selectedRootIds
|
|
15163
15775
|
);
|
|
15164
|
-
const graphLockDigest =
|
|
15776
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15165
15777
|
return {
|
|
15166
15778
|
...result,
|
|
15167
15779
|
bundle: { ...result.bundle, graphLock },
|
|
@@ -15287,7 +15899,7 @@ function operationBelongsToDependencyUpdate(operation, allowedNodeIds, allowedNa
|
|
|
15287
15899
|
function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
15288
15900
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest);
|
|
15289
15901
|
const graphLock = focusedGraphLockForInstall(scoped.bundle.graphLock, rootId, focused);
|
|
15290
|
-
const graphLockDigest =
|
|
15902
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15291
15903
|
return {
|
|
15292
15904
|
...scoped,
|
|
15293
15905
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15298,7 +15910,7 @@ function scopeInstallPlanToArtifact(result, rootId, focused, manifest) {
|
|
|
15298
15910
|
function scopeUpdatePlanToArtifact(result, rootId, focused, previousLock, manifest) {
|
|
15299
15911
|
const scoped = scopePlanOperationsToArtifact(result, rootId, focused, manifest, previousLock);
|
|
15300
15912
|
const graphLock = mergeFocusedArtifactGraphLock(scoped.bundle.graphLock, previousLock, rootId, focused);
|
|
15301
|
-
const graphLockDigest =
|
|
15913
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15302
15914
|
return {
|
|
15303
15915
|
...scoped,
|
|
15304
15916
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15593,7 +16205,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
|
|
|
15593
16205
|
selectedPreviousNodeIds,
|
|
15594
16206
|
/* @__PURE__ */ new Set([rootId])
|
|
15595
16207
|
);
|
|
15596
|
-
const graphLockDigest =
|
|
16208
|
+
const graphLockDigest = createHash16("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
|
|
15597
16209
|
return {
|
|
15598
16210
|
...scoped,
|
|
15599
16211
|
bundle: { ...scoped.bundle, graphLock },
|
|
@@ -15728,7 +16340,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
15728
16340
|
let renderedRoot;
|
|
15729
16341
|
if (remainingGroup && remainingGroup.packages.length > 0) {
|
|
15730
16342
|
const remainingAdapter = await resolveAdapterForTarget(remainingGroup.target, remainingGroup.adapterOptions);
|
|
15731
|
-
const
|
|
16343
|
+
const result = await createGraphSourcePlan({
|
|
15732
16344
|
roots: remainingGroup.packages.map((pkg2) => ({
|
|
15733
16345
|
rootId: pkg2.name,
|
|
15734
16346
|
source: pkg2.source,
|
|
@@ -15760,46 +16372,49 @@ async function uninstallConfiguredPackage(target, packageName, options) {
|
|
|
15760
16372
|
readOnly: options.dryRun === true,
|
|
15761
16373
|
isTTY: process.stdin.isTTY === true
|
|
15762
16374
|
});
|
|
15763
|
-
remainingGraphPlan =
|
|
15764
|
-
remainingDesired = desiredArtifactsFromGraphBundle(
|
|
15765
|
-
renderedRoot =
|
|
16375
|
+
remainingGraphPlan = result;
|
|
16376
|
+
remainingDesired = desiredArtifactsFromGraphBundle(result.bundle);
|
|
16377
|
+
renderedRoot = result.bundle.root;
|
|
15766
16378
|
}
|
|
15767
|
-
|
|
15768
|
-
|
|
15769
|
-
|
|
15770
|
-
|
|
15771
|
-
|
|
15772
|
-
|
|
15773
|
-
|
|
15774
|
-
|
|
15775
|
-
|
|
15776
|
-
|
|
15777
|
-
|
|
15778
|
-
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
15786
|
-
|
|
15787
|
-
const result = await uninstall(plan, {
|
|
15788
|
-
dryRun: options.dryRun,
|
|
15789
|
-
force: options.force,
|
|
15790
|
-
keepFiles: options.keepFiles,
|
|
15791
|
-
transport,
|
|
15792
|
-
...graphLockFinalState,
|
|
15793
|
-
workspaceConfig: {
|
|
15794
|
-
path: workspaceConfigPath(target.workspaceRoot),
|
|
15795
|
-
data: { ...config, packages: remaining }
|
|
16379
|
+
try {
|
|
16380
|
+
const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
|
|
16381
|
+
graphLockDigest: remainingGraphPlan?.graphLockDigest
|
|
16382
|
+
});
|
|
16383
|
+
const transitionPlan = remainingGraphPlan?.plan;
|
|
16384
|
+
const plan = {
|
|
16385
|
+
...uninstallPlan,
|
|
16386
|
+
stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
|
|
16387
|
+
stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
|
|
16388
|
+
targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
|
|
16389
|
+
};
|
|
16390
|
+
console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
|
|
16391
|
+
console.log(formatPlan(plan));
|
|
16392
|
+
const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
|
|
16393
|
+
removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
|
|
16394
|
+
};
|
|
16395
|
+
if (!options.dryRun) {
|
|
16396
|
+
declareMutationPath(workspaceConfigPath(target.workspaceRoot));
|
|
16397
|
+
const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
|
|
16398
|
+
declareMutationPath(declarativeGraphPath);
|
|
15796
16399
|
}
|
|
15797
|
-
|
|
15798
|
-
|
|
15799
|
-
|
|
16400
|
+
const result = await uninstall(plan, {
|
|
16401
|
+
dryRun: options.dryRun,
|
|
16402
|
+
force: options.force,
|
|
16403
|
+
keepFiles: options.keepFiles,
|
|
16404
|
+
transport,
|
|
16405
|
+
...graphLockFinalState,
|
|
16406
|
+
workspaceConfig: {
|
|
16407
|
+
path: workspaceConfigPath(target.workspaceRoot),
|
|
16408
|
+
data: { ...config, packages: remaining }
|
|
16409
|
+
}
|
|
16410
|
+
});
|
|
16411
|
+
if (!options.dryRun) {
|
|
16412
|
+
console.log(formatUninstallResult(result));
|
|
16413
|
+
}
|
|
16414
|
+
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
16415
|
+
} finally {
|
|
16416
|
+
if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
|
|
15800
16417
|
}
|
|
15801
|
-
if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
|
|
15802
|
-
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
15803
16418
|
}
|
|
15804
16419
|
}
|
|
15805
16420
|
function graphGroupForPackage(groups, target, pkg, options) {
|
|
@@ -15855,6 +16470,49 @@ function targetFingerprintParts(target, adapter, options, installationType) {
|
|
|
15855
16470
|
stateKey: target.stateKey
|
|
15856
16471
|
};
|
|
15857
16472
|
}
|
|
16473
|
+
async function withFreshReadOnlyGraphPlan(target, adapter, adapterOptions, installationType, packages, use) {
|
|
16474
|
+
const stage = await mkdtemp3(join46(tmpdir4(), "agentwheel-recovery-"));
|
|
16475
|
+
const priorTmpdir = process.env.TMPDIR;
|
|
16476
|
+
try {
|
|
16477
|
+
process.env.TMPDIR = stage;
|
|
16478
|
+
const graphPlan = await createGraphSourcePlan({
|
|
16479
|
+
roots: packages.map((pkg) => ({
|
|
16480
|
+
rootId: pkg.name,
|
|
16481
|
+
source: pkg.source,
|
|
16482
|
+
mode: pkg.mode,
|
|
16483
|
+
version: pkg.version,
|
|
16484
|
+
ref: pkg.requestedRef,
|
|
16485
|
+
select: pkg.selection ? void 0 : normalizeArtifactSelectors(pkg.select, pkg.skills),
|
|
16486
|
+
selection: pkg.selection,
|
|
16487
|
+
aliases: pkg.aliases,
|
|
16488
|
+
overrides: pkg.overrides,
|
|
16489
|
+
includeSuggestions: pkg.withSuggestions,
|
|
16490
|
+
suggestionAliases: pkg.suggestions,
|
|
16491
|
+
useLock: false
|
|
16492
|
+
})),
|
|
16493
|
+
targetRoot: target.targetRoot,
|
|
16494
|
+
workspaceRoot: target.workspaceRoot,
|
|
16495
|
+
fleetId: target.fleetId,
|
|
16496
|
+
adapter,
|
|
16497
|
+
transport: transportForTarget(target),
|
|
16498
|
+
targetKey: targetKeyForTarget(target, adapter.name),
|
|
16499
|
+
targetFingerprintParts: targetFingerprintParts(target, adapter, adapterOptions, installationType),
|
|
16500
|
+
installationType,
|
|
16501
|
+
stateKey: target.stateKey,
|
|
16502
|
+
readOnly: true,
|
|
16503
|
+
yes: true,
|
|
16504
|
+
freshGraphOnly: true,
|
|
16505
|
+
deferForeignStateCheck: true,
|
|
16506
|
+
cacheRoot: join46(stage, "source-cache"),
|
|
16507
|
+
registryCachePath: join46(stage, "registry.json")
|
|
16508
|
+
});
|
|
16509
|
+
return await use(graphPlan);
|
|
16510
|
+
} finally {
|
|
16511
|
+
if (priorTmpdir === void 0) delete process.env.TMPDIR;
|
|
16512
|
+
else process.env.TMPDIR = priorTmpdir;
|
|
16513
|
+
await rm10(stage, { recursive: true, force: true });
|
|
16514
|
+
}
|
|
16515
|
+
}
|
|
15858
16516
|
function installStateForTarget(target, adapter, options, installationType) {
|
|
15859
16517
|
const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, target.transport === "ssh");
|
|
15860
16518
|
const targetIdentity = resolveTargetStateIdentity({
|
|
@@ -15933,7 +16591,7 @@ async function resolveCliTargetState(target, options) {
|
|
|
15933
16591
|
migration: prior.migration,
|
|
15934
16592
|
targetStateFilePreconditions: {
|
|
15935
16593
|
graphLockPath: stableGraphLockPath,
|
|
15936
|
-
graphLockRevision: stableLock ?
|
|
16594
|
+
graphLockRevision: stableLock ? createHash16("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
|
|
15937
16595
|
sourceLockRevision: computeSourceLockRevision(stableSourceLock)
|
|
15938
16596
|
}
|
|
15939
16597
|
};
|
|
@@ -15956,7 +16614,7 @@ async function collectTargetStatus(target, options) {
|
|
|
15956
16614
|
const pending = await collectPendingInstallWork(target, options);
|
|
15957
16615
|
const foreignObservations = pending.foreignStateObservations;
|
|
15958
16616
|
const effectiveGraphLock = graphLock ?? pending.observedGraphLock;
|
|
15959
|
-
const observedManifestRevision = foreignObservations.length > 0 ?
|
|
16617
|
+
const observedManifestRevision = foreignObservations.length > 0 ? createHash16("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
|
|
15960
16618
|
observation.fileName,
|
|
15961
16619
|
observation.manifestRevision
|
|
15962
16620
|
]).sort())).digest("hex") : null;
|
|
@@ -16203,7 +16861,7 @@ async function assertCompositeMutationSupported(workspaceRoot, profile, readOnly
|
|
|
16203
16861
|
`Composite-profile mutation is refused before member execution because ${member.id} uses ${member.transport}; durable remote policy and recovery preflight is not implemented.`
|
|
16204
16862
|
);
|
|
16205
16863
|
}
|
|
16206
|
-
const memberRoot =
|
|
16864
|
+
const memberRoot = resolve27(workspaceRoot, member.workspace);
|
|
16207
16865
|
const memberPolicy = await mutationPolicyForWorkspace(memberRoot);
|
|
16208
16866
|
if (memberPolicy?.revisioning.mode === "commit-after-verify") {
|
|
16209
16867
|
throw new Error(
|
|
@@ -16688,14 +17346,14 @@ function looksLikeSourceSpecifier(value) {
|
|
|
16688
17346
|
}
|
|
16689
17347
|
function normalizeCliPath(value) {
|
|
16690
17348
|
if (value === "~") return homedir12();
|
|
16691
|
-
if (value.startsWith("~/")) return
|
|
16692
|
-
return
|
|
17349
|
+
if (value.startsWith("~/")) return resolve27(homedir12(), value.slice(2));
|
|
17350
|
+
return resolve27(value);
|
|
16693
17351
|
}
|
|
16694
17352
|
function shellQuoteArgument(value) {
|
|
16695
17353
|
return /^[a-z0-9_./:@%+=,-]+$/i.test(value) ? value : `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
16696
17354
|
}
|
|
16697
17355
|
function isHomePath(path) {
|
|
16698
|
-
return
|
|
17356
|
+
return resolve27(path) === resolve27(homedir12());
|
|
16699
17357
|
}
|
|
16700
17358
|
function adapterListFromOption(adapter) {
|
|
16701
17359
|
if (!adapter) return [];
|
|
@@ -16816,7 +17474,7 @@ function withFleetExample(config) {
|
|
|
16816
17474
|
};
|
|
16817
17475
|
}
|
|
16818
17476
|
async function findAgentwheelPackageRoot(start) {
|
|
16819
|
-
let current =
|
|
17477
|
+
let current = resolve27(start);
|
|
16820
17478
|
while (true) {
|
|
16821
17479
|
if (await findPackageManifestPath(current, { warnLegacy: false })) return current;
|
|
16822
17480
|
const parent = dirname23(current);
|