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/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import {
37
37
  uninstall,
38
38
  withManifestRevision,
39
39
  writeInstallManifest
40
- } from "./chunk-A44XYLUT.js";
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-FP26KSBM.js";
101
+ } from "./chunk-BHT57MK5.js";
102
102
  import {
103
103
  inferSourceDriverName
104
- } from "./chunk-U2GRG4UI.js";
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-GE7F76AC.js";
114
+ } from "./chunk-BNIF3FUR.js";
114
115
 
115
116
  // src/cli/index.ts
116
- import { createHash as createHash15 } from "crypto";
117
+ import { createHash as createHash16 } from "crypto";
117
118
  import { existsSync } from "fs";
118
- import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath2, rm as rm10, writeFile as writeFile13 } from "fs/promises";
119
- import { homedir as homedir12, tmpdir as tmpdir5 } from "os";
120
- import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute4, join as join46, relative as relative10, resolve as resolve26 } from "path";
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((resolve27) => {
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
- resolve27();
3122
+ resolve28();
3122
3123
  });
3123
- stream.on("end", resolve27);
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3148
3149
  }
3149
3150
  function listen(server, port, bind) {
3150
- return new Promise((resolve27, reject) => {
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
- resolve27();
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((resolve27) => {
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(() => resolve27());
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
- resolve27();
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((resolve27) => setTimeout(resolve27, ms));
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((resolve27) => setTimeout(resolve27, 50));
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 snapshotPath = snapshot.path;
4142
- const manifest = await readPackageManifest(snapshotPath);
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: snapshotPath,
4201
+ resolvedPath: packageRoot,
4146
4202
  packageName: manifest?.name,
4147
4203
  packageVersion: manifest?.version,
4148
4204
  resolvedCommit,
4149
- sourceHash: await hashPath(snapshotPath),
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 parseGitSource(source) {
4168
- if (source.startsWith("github:")) {
4169
- const rest = source.slice("github:".length);
4170
- const [repo, ref] = rest.split("#", 2);
4171
- if (!repo.includes("/")) throw new Error(`Invalid GitHub source: ${source}`);
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
- return renderStagedBundle(await stageSourceRaw(driver, source, options), options);
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(tmpdir2(), "agentwheel-stage-"));
5402
+ async function stageResolvedArtifactsRaw(resolved, artifacts, parentDir = tmpdir2()) {
5403
+ const root = await mkdtemp(join27(parentDir, "agentwheel-stage-"));
5357
5404
  const stagedArtifacts = [];
5358
- for (const artifact of artifacts) {
5359
- const stagedPath = join27(root, artifact.relativePath);
5360
- await mkdir11(dirname13(stagedPath), { recursive: true });
5361
- await cp3(artifact.sourcePath, stagedPath, {
5362
- recursive: artifact.kind === "dir",
5363
- dereference: true,
5364
- filter: (path) => !isIgnoredGeneratedEntry(basename16(path))
5365
- });
5366
- await composeAssets(artifact, resolved.resolvedPath, stagedPath);
5367
- stagedArtifacts.push({
5368
- ...artifact,
5369
- stagedPath,
5370
- hash: await hashPath(stagedPath),
5371
- channel: artifact.channel ?? "managed"
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 { mkdtemp as mkdtemp2, readdir as readdir7, readFile as readFile17, stat as stat11 } from "fs/promises";
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-O54QXPQR.js");
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 = parseGitSource2(trimmed);
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 = `git:${url}#${requestedRef}`;
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((resolve27) => {
6780
- release = resolve27;
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 posix2, resolve as resolve16 } from "path";
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 mkdtemp3 } from "fs/promises";
7206
- import { tmpdir as tmpdir4 } from "os";
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 mkdtemp3(join31(tmpdir4(), "agentwheel-render-"));
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" ? posix.normalize(path) : resolve15(path);
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
- const desiredArtifacts = filterArtifactsByAdapterTargets(
8275
- desiredArtifactsFromGraphBundle(bundle),
8276
- options.adapter,
8277
- installationType,
8278
- { warn }
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
- const manifest = priorState.manifest;
8290
- const basePlan = options.retireExactMcp ? await createExactMcpRetirementPlan(
8291
- desiredArtifacts,
8292
- options.adapter,
8293
- options.targetRoot,
8294
- manifest,
8295
- transport,
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
- ) : await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
8304
- baseRevision: manifest?.revision ?? null,
8305
- graphLockDigest,
8306
- workspaceOwner,
8307
- installationType: resolvedInstallationType,
8308
- stateKey,
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
- workspaceRoot,
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
- globalRoot: options.globalRoot,
8366
+ installationType: resolvedInstallationType,
8330
8367
  stateKey,
8331
- migratingStateKey: priorState.migration?.fromStateKey,
8332
- plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
8333
- plannedOperations: plan.operations
8368
+ forceDrift: options.forceDrift,
8369
+ forceConflict: options.forceConflict,
8370
+ replaceConflict: options.replaceConflict,
8371
+ stateMigration: priorState.migration,
8372
+ warn
8334
8373
  });
8335
- plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
8336
- }
8337
- return {
8338
- plan,
8339
- graph,
8340
- bundle,
8341
- desiredArtifacts,
8342
- graphLockPath,
8343
- graphLockDigest,
8344
- targetFingerprint,
8345
- warnings,
8346
- newTransitiveSources: trustEvaluation.promptSources,
8347
- graphDiff,
8348
- recoveredPendingApply,
8349
- previousManifest: priorState.manifest,
8350
- previousGraphLock: priorState.graphLock,
8351
- foreignStateObservations
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" ? posix2.normalize(path) : resolve16(path);
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
- parseWorkspaceOwner(check.workspaceOwner)?.fleetId ? "Reconcile the owners with an explicit agentwheel fleet normalize operation before planning this fleet." : "Re-run from the owning workspace, or pass --force-foreign-state to plan against it anyway."
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 currentHash = await verifiedEntryHash(entry, destPath, transport);
9861
- if (request.expectedHash && currentHash !== request.expectedHash) {
9862
- throw new Error(`Current hash precondition failed for ${entry.path}: expected ${request.expectedHash}, found ${currentHash}`);
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: currentHash,
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(`${label} ownership retirement requires an Agentwheel v2 install manifest.`);
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 posix3, relative as relative8, resolve as resolve22 } from "path";
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" ? posix3.normalize(item.manifest.targetRoot) === posix3.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
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.canonical.artifacts.some((artifact) => artifact.graphNodeId === nodeId && artifact.logicalSelector === selector && artifact.hash === entry.sourceHash))) {
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 resolve23 } from "path";
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 = resolve23(workspaceRoot, pkg.source);
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 resolve24 } from "path";
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 = resolve24(parentWorkspace, member.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: resolve24(parentWorkspace, member.workspace),
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 `${resolve24(workspaceRoot)}#${profileName}`;
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 createHash11 } from "crypto";
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: createHash11("sha256").update(bytes).digest("hex")
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 createHash11("sha256").update(JSON.stringify({ enriched, vercel })).digest("hex");
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 createHash12 } from "crypto";
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 = createHash12("sha256").update(bytes).digest("hex");
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 createHash13 } from "crypto";
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: createHash13("sha256").update(content).digest("hex"),
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 createHash14 } from "crypto";
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 isAbsolute3, join as join45, relative as relative9, resolve as resolve25 } from "path";
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 = normalizeRequest2(request);
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 = normalizeRequest2(request);
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 = normalizeRequest2(request);
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 normalizeRequest2(request) {
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: resolve25(request.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 || !isAbsolute3(root)) throw new Error(`Invalid orphaned owner root '${value}'; expected an absolute workspace path.`);
12227
- return resolve25(root);
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" ? resolve25(request.globalRoot ?? homedir11()) : (await showRegisteredFleet(request.from.slice("fleet:".length), { globalRoot: request.globalRoot })).root;
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 (resolve25(destinationState.graphLockPath) === resolve25(graph.path)) {
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 (resolve25(legacyState.graphLockPath) !== resolve25(graph.path)) {
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(resolve25(process.env.AGENTWHEEL_TEST_HOME || process.env.HOME || homedir11()));
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 (resolve25(parsed.targetRoot) !== resolve25(root)) {
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 (resolve25(expectedPath) !== resolve25(path)) {
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 (resolve25(expectedPath) !== resolve25(path)) {
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 = relative9(root, path).split(/[\\/]/);
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 (resolve25(stableState.graphLockPath) !== resolve25(graph.path)) return false;
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 = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, agent.installationType, false));
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 = resolve25(installRootForAdapterInstallationType(adapter, targetRoot, installationType, false));
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 (resolve25(graph.path) === resolve25(stable.graphLockPath)) {
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 (resolve25(graph.path) !== resolve25(released.graphLockPath)) {
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) => resolve25(candidate.path) === resolve25(state.manifestPath));
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 || resolve25(manifest.manifest.targetRoot) !== resolve25(state.installRoot)) {
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 || resolve25(manifest2.targetRoot) !== state.installRoot) {
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 (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
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 = resolve25(root);
13060
- const candidate = resolve25(normalizedRoot, entryPath);
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 = resolve25(manifest.targetRoot);
13152
- const candidate = resolve25(root, entry.path);
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 createHash14("sha256").update(value).digest("hex");
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").requiredOption("--from-workspace-root <path>", "current owning workspace root").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 current artifact hash; required when applying").option("--expected-revision <sha256>", "expected install manifest revision; required when applying").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) => {
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 (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
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 realpath2(dirname23(requestedReport)), basename21(requestedReport));
14000
- const reportInsideTarget = relative10(resolve26(target.targetRoot), reportPath);
14001
- if (reportInsideTarget === "" || !reportInsideTarget.startsWith("..") && !isAbsolute4(reportInsideTarget)) {
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
- const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
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
- } finally {
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 (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
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 || resolve26(target.workspaceRoot) !== resolve26(fleet.root)) {
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
- for (const result of await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage, reportFormat: outputFormat }, { mode: "install" })) {
14404
- if (!behavior.quiet) console.log(formatGraphPlan(result));
14405
- if (behavior.apply) {
14406
- declareMutationPath(result.graphLockPath);
14407
- if (extraPackage && !targetOptions.onlySource) declareMutationPath(workspaceConfigPath(target.workspaceRoot));
14408
- const transport = transportForTarget(target);
14409
- const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
14410
- await applyCombinedInstallPlan(result.plan, {
14411
- executePlugins,
14412
- transport,
14413
- graphLockDigest: result.graphLockDigest,
14414
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
14415
- });
14416
- const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
14417
- enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
14418
- executePlugins
14419
- });
14420
- if (!behavior.quiet) {
14421
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
14422
- if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
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
- await rm10(result.bundle.root, { recursive: true, force: true });
14426
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
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
- for (const result of results) {
14878
- console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
14879
- console.log(formatGraphPlan(result));
14880
- if (!options.dryRun) {
14881
- declareMutationPath(result.graphLockPath);
14882
- const transport = transportForTarget(target);
14883
- const executePlugins = target.executePlugins ?? options.executePlugins;
14884
- await applyCombinedInstallPlan(result.plan, {
14885
- executePlugins,
14886
- transport,
14887
- graphLockDigest: result.graphLockDigest,
14888
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
14889
- });
14890
- const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
14891
- enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
14892
- executePlugins
14893
- });
14894
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
14895
- if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
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
- await rm10(result.bundle.root, { recursive: true, force: true });
14898
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 = createHash15("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
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 result2 = await createGraphSourcePlan({
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 = result2;
15764
- remainingDesired = desiredArtifactsFromGraphBundle(result2.bundle);
15765
- renderedRoot = result2.bundle.root;
16375
+ remainingGraphPlan = result;
16376
+ remainingDesired = desiredArtifactsFromGraphBundle(result.bundle);
16377
+ renderedRoot = result.bundle.root;
15766
16378
  }
15767
- const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
15768
- graphLockDigest: remainingGraphPlan?.graphLockDigest
15769
- });
15770
- const transitionPlan = remainingGraphPlan?.plan;
15771
- const plan = {
15772
- ...uninstallPlan,
15773
- stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
15774
- stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
15775
- targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
15776
- };
15777
- console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
15778
- console.log(formatPlan(plan));
15779
- const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
15780
- removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
15781
- };
15782
- if (!options.dryRun) {
15783
- declareMutationPath(workspaceConfigPath(target.workspaceRoot));
15784
- const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
15785
- declareMutationPath(declarativeGraphPath);
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
- if (!options.dryRun) {
15799
- console.log(formatUninstallResult(result));
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 ? createHash15("sha256").update(canonicalGraphLockJson(stableLock)).digest("hex") : null,
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 ? createHash15("sha256").update(JSON.stringify(foreignObservations.map((observation) => [
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 = resolve26(workspaceRoot, member.workspace);
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 resolve26(homedir12(), value.slice(2));
16692
- return resolve26(value);
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 resolve26(path) === resolve26(homedir12());
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 = resolve26(start);
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);