agentwheel 0.20.18 → 0.20.20

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-45WVSUJA.js";
41
41
  import {
42
42
  CURRENT_WORKSPACE_SCHEMA_VERSION,
43
43
  GovernedMutation,
@@ -56,6 +56,7 @@ import {
56
56
  findExistingWorkspaceRoot,
57
57
  findWorkspaceRoot,
58
58
  fleetIdSchema,
59
+ getAdapter,
59
60
  globalWorkspaceConfigPath,
60
61
  installManifestPath,
61
62
  installRootForAdapterInstallationType,
@@ -74,6 +75,7 @@ import {
74
75
  packageItemRequireSchema,
75
76
  packageItemSuggestSchema,
76
77
  packageSupersedesEntrySchema,
78
+ packagesForTarget,
77
79
  parseSemver,
78
80
  readApplyJournal,
79
81
  readMergedWorkspaceConfig,
@@ -98,25 +100,26 @@ import {
98
100
  workspaceSelectionImportSchema,
99
101
  writeApplyJournal,
100
102
  writeWorkspaceConfig
101
- } from "./chunk-FP26KSBM.js";
103
+ } from "./chunk-FGFPAZ4J.js";
102
104
  import {
103
105
  inferSourceDriverName
104
- } from "./chunk-U2GRG4UI.js";
106
+ } from "./chunk-QABA4WUP.js";
105
107
  import {
106
108
  hashPath,
107
109
  isAlreadyExists,
108
110
  isIgnoredGeneratedEntry,
109
111
  listFiles,
110
112
  pathExists,
113
+ removeOnFailure,
111
114
  withFilesystemLock,
112
115
  writeJsonAtomic
113
- } from "./chunk-GE7F76AC.js";
116
+ } from "./chunk-BNIF3FUR.js";
114
117
 
115
118
  // src/cli/index.ts
116
119
  import { createHash as createHash16 } from "crypto";
117
120
  import { existsSync } from "fs";
118
- import { mkdir as mkdir16, mkdtemp as mkdtemp4, realpath as realpath3, rm as rm10, writeFile as writeFile13 } from "fs/promises";
119
- import { homedir as homedir12, tmpdir as tmpdir5 } from "os";
121
+ import { mkdir as mkdir16, mkdtemp as mkdtemp3, realpath as realpath3, rm as rm10, writeFile as writeFile13 } from "fs/promises";
122
+ import { homedir as homedir12, tmpdir as tmpdir4 } from "os";
120
123
  import { basename as basename21, dirname as dirname23, isAbsolute as isAbsolute5, join as join46, relative as relative11, resolve as resolve27 } from "path";
121
124
  import { fileURLToPath as fileURLToPath3 } from "url";
122
125
  import { Command } from "commander";
@@ -124,185 +127,6 @@ import { Command } from "commander";
124
127
  // src/adapters/resolve.ts
125
128
  import { resolve as resolve2 } from "path";
126
129
 
127
- // src/adapters/claude.ts
128
- var claudeAdapter = {
129
- name: "claude",
130
- displayName: "Claude Code",
131
- targets: {
132
- instructions: {
133
- local: { enabled: true, dest: "CLAUDE.md", mode: "managed-block" },
134
- user: { enabled: true, root: "home", dest: ".claude/CLAUDE.md", mode: "managed-block" }
135
- },
136
- rules: {
137
- local: { enabled: true, dest: ".claude/rules", formats: ["markdown-rule", "claude-markdown-rule"] },
138
- user: { enabled: true, root: "home", dest: ".claude/rules", formats: ["markdown-rule", "claude-markdown-rule"] }
139
- },
140
- skills: {
141
- local: { enabled: true, dest: ".claude/skills" },
142
- user: { enabled: true, root: "home", dest: ".claude/skills" }
143
- },
144
- commands: {
145
- local: { enabled: true, dest: ".claude/commands" },
146
- user: { enabled: true, root: "home", dest: ".claude/commands" }
147
- },
148
- subagents: {
149
- local: { enabled: true, dest: ".claude/agents" },
150
- user: { enabled: true, root: "home", dest: ".claude/agents" }
151
- },
152
- plugins: {
153
- local: { enabled: true, dest: ".agentwheel/plugins/claude", semantic: "claude-plugin" },
154
- user: { enabled: true, root: "home", dest: ".agentwheel/plugins/claude", semantic: "claude-plugin" }
155
- },
156
- mcp: {
157
- local: { enabled: true, dest: ".mcp.json", merge: "json-deep" }
158
- },
159
- hooks: {
160
- local: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" },
161
- user: { enabled: true, root: "home", dest: ".claude/settings.json", merge: "json-deep" }
162
- },
163
- settings: {
164
- local: { enabled: true, dest: ".claude/settings.json", merge: "json-deep" },
165
- user: { enabled: true, root: "home", dest: ".claude/settings.json", merge: "json-deep" }
166
- }
167
- }
168
- };
169
-
170
- // src/adapters/copilot.ts
171
- var copilotAdapter = {
172
- name: "copilot",
173
- displayName: "GitHub Copilot CLI",
174
- targets: {
175
- instructions: {
176
- local: { enabled: true, dest: ".github/copilot-instructions.md", mode: "managed-block" },
177
- user: { enabled: true, root: "home", dest: ".copilot/copilot-instructions.md", mode: "managed-block" }
178
- },
179
- rules: {
180
- local: { enabled: true, dest: ".github/instructions", formats: ["markdown-rule", "copilot-instruction-rule"], semantic: "copilot-instruction" },
181
- user: { enabled: true, root: "home", dest: ".copilot/instructions", formats: ["markdown-rule", "copilot-instruction-rule"], semantic: "copilot-instruction" }
182
- },
183
- commands: {
184
- local: { enabled: true, dest: ".github/prompts", semantic: "copilot-prompt" }
185
- },
186
- skills: {
187
- local: { enabled: true, dest: ".github/skills" },
188
- user: { enabled: true, root: "home", dest: ".copilot/skills" }
189
- },
190
- plugins: {
191
- user: { enabled: true, root: "home", dest: ".agentwheel/plugins/copilot", semantic: "copilot-plugin" }
192
- },
193
- subagents: {
194
- local: { enabled: true, dest: ".github/agents", semantic: "copilot-agent" },
195
- user: { enabled: true, root: "home", dest: ".copilot/agents", semantic: "copilot-agent" }
196
- },
197
- mcp: {
198
- local: { enabled: true, dest: ".github/mcp.json", merge: "json-deep" },
199
- user: { enabled: true, root: "home", dest: ".copilot/mcp-config.json", merge: "json-deep" }
200
- },
201
- hooks: {
202
- local: { enabled: true, dest: ".github/hooks" },
203
- user: { enabled: true, root: "home", dest: ".copilot/hooks" }
204
- },
205
- settings: {
206
- local: { enabled: true, dest: ".github/settings.json", merge: "json-deep" },
207
- user: { enabled: true, root: "home", dest: ".copilot/settings.json", merge: "json-deep" }
208
- }
209
- }
210
- };
211
-
212
- // src/adapters/codex.ts
213
- var codexAdapter = {
214
- name: "codex",
215
- displayName: "Codex CLI",
216
- targets: {
217
- instructions: {
218
- local: { enabled: true, dest: "AGENTS.md", mode: "managed-block" },
219
- user: { enabled: true, root: "home", dest: ".codex/AGENTS.md", mode: "managed-block" }
220
- },
221
- skills: {
222
- local: { enabled: true, dest: ".agents/skills" },
223
- user: { enabled: true, root: "home", dest: ".agents/skills" }
224
- },
225
- plugins: {
226
- local: { enabled: true, dest: ".agentwheel/plugins/codex", semantic: "codex-plugin" },
227
- user: { enabled: true, root: "home", dest: ".agentwheel/plugins/codex", semantic: "codex-plugin" }
228
- },
229
- subagents: {
230
- local: { enabled: true, dest: ".codex/agents", semantic: "codex-subagent" },
231
- user: { enabled: true, root: "home", dest: ".codex/agents", semantic: "codex-subagent" }
232
- },
233
- mcp: {
234
- local: { enabled: true, dest: ".codex/config.toml", merge: "codex-toml-mcp" },
235
- user: { enabled: true, root: "home", dest: ".codex/config.toml", merge: "codex-toml-mcp" }
236
- },
237
- hooks: {
238
- local: { enabled: true, dest: ".codex/hooks.json", merge: "json-deep" },
239
- user: { enabled: true, root: "home", dest: ".codex/hooks.json", merge: "json-deep" }
240
- }
241
- }
242
- };
243
-
244
- // src/adapters/hermes.ts
245
- var hermesAdapter = {
246
- name: "hermes",
247
- displayName: "Hermes",
248
- targets: {
249
- instructions: {
250
- local: { enabled: true, dest: "AGENTS.md", mode: "managed-block" },
251
- // Hermes documents SOUL.md as the user-level instruction surface.
252
- user: { enabled: true, root: "home", dest: ".hermes/SOUL.md", mode: "managed-block" }
253
- },
254
- skills: {
255
- user: { enabled: true, root: "home", dest: ".hermes/skills" }
256
- },
257
- mcp: {
258
- user: { enabled: true, root: "home", dest: ".hermes/config.yaml", merge: "yaml-deep" }
259
- },
260
- settings: {
261
- user: { enabled: true, root: "home", dest: ".hermes/config.yaml", merge: "yaml-deep" }
262
- },
263
- plugins: {
264
- user: { enabled: true, root: "home", dest: ".agentwheel/plugins/hermes", semantic: "hermes-plugin" }
265
- }
266
- }
267
- };
268
-
269
- // src/adapters/openclaw.ts
270
- var openClawAdapter = {
271
- name: "openclaw",
272
- displayName: "OpenClaw",
273
- targets: {
274
- instructions: {
275
- user: { enabled: true, root: "home", dest: ".openclaw/workspace/AGENTS.md", mode: "managed-block" }
276
- },
277
- skills: {
278
- local: { enabled: true, dest: "skills" },
279
- user: { enabled: true, root: "home", dest: ".openclaw/skills" }
280
- },
281
- subagents: {
282
- user: { enabled: true, root: "home", dest: ".openclaw/workspace-subagents", semantic: "openclaw-subagent" }
283
- },
284
- mcp: {
285
- user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "openclaw-json-deep" }
286
- },
287
- settings: {
288
- user: { enabled: true, root: "home", dest: ".openclaw/openclaw.json", merge: "openclaw-json-deep" }
289
- },
290
- plugins: {
291
- local: { enabled: true, dest: ".openclaw/plugins", formats: ["openclaw-plugin", "openclaw-clawhub-plugin"], semantic: "openclaw-plugin" }
292
- }
293
- }
294
- };
295
-
296
- // src/adapters/index.ts
297
- var adapters = [openClawAdapter, claudeAdapter, codexAdapter, hermesAdapter, copilotAdapter];
298
- function getAdapter(name) {
299
- const adapter = adapters.find((candidate) => candidate.name === name);
300
- if (!adapter) {
301
- throw new Error(`Unknown adapter: ${name}`);
302
- }
303
- return adapter;
304
- }
305
-
306
130
  // src/adapters/programmatic.ts
307
131
  import { stat, readFile, writeFile } from "fs/promises";
308
132
  import { tmpdir } from "os";
@@ -4079,6 +3903,58 @@ function errorMessage3(error) {
4079
3903
  return error instanceof Error ? error.message : String(error);
4080
3904
  }
4081
3905
 
3906
+ // src/source/git-source.ts
3907
+ import { posix } from "path";
3908
+ var SUBPATH_SEPARATOR = "//";
3909
+ function parseGitSource(source) {
3910
+ if (source.startsWith("github:")) {
3911
+ const rest = source.slice("github:".length);
3912
+ const hashIndex = rest.indexOf("#");
3913
+ const repo = hashIndex >= 0 ? rest.slice(0, hashIndex) : rest;
3914
+ if (!repo.includes("/")) throw new Error(`Invalid GitHub source: ${source}`);
3915
+ return {
3916
+ url: `https://github.com/${repo.replace(/\.git$/i, "")}.git`,
3917
+ ...splitFragment(hashIndex >= 0 ? rest.slice(hashIndex + 1) : void 0, source)
3918
+ };
3919
+ }
3920
+ if (source.startsWith("git:")) {
3921
+ const rest = source.slice("git:".length);
3922
+ const hashIndex = rest.lastIndexOf("#");
3923
+ if (hashIndex < 0) return { url: rest };
3924
+ return { url: rest.slice(0, hashIndex), ...splitFragment(rest.slice(hashIndex + 1), source) };
3925
+ }
3926
+ throw new Error(`Invalid git source: ${source}`);
3927
+ }
3928
+ function formatGitSource(url, ref, subpath) {
3929
+ return subpath ? `git:${url}#${ref}${SUBPATH_SEPARATOR}${subpath}` : `git:${url}#${ref}`;
3930
+ }
3931
+ function normalizeGitSubpath(subpath, source) {
3932
+ if (subpath === void 0) return void 0;
3933
+ if (subpath.includes("\\")) throw new Error(`Git source subpath must use forward slashes: ${source}`);
3934
+ if (subpath.startsWith("/")) throw new Error(`Git source subpath must be relative to the repository: ${source}`);
3935
+ const normalized = posix.normalize(subpath).replace(/\/+$/g, "");
3936
+ if (normalized === "." || normalized === "") return void 0;
3937
+ if (normalized === ".." || normalized.startsWith("../")) {
3938
+ throw new Error(`Git source subpath escapes the repository: ${source}`);
3939
+ }
3940
+ return normalized;
3941
+ }
3942
+ function resolveGitRelativeSubpath(declaringSubpath, relative12, source) {
3943
+ const joined = posix.normalize(posix.join(declaringSubpath ?? ".", relative12));
3944
+ if (joined === ".." || joined.startsWith("../")) {
3945
+ throw new Error(`Relative dependency '${relative12}' escapes the git repository it is declared in: ${source}`);
3946
+ }
3947
+ return normalizeGitSubpath(joined, source);
3948
+ }
3949
+ function splitFragment(fragment, source) {
3950
+ if (fragment === void 0) return {};
3951
+ const separator = fragment.indexOf(SUBPATH_SEPARATOR);
3952
+ if (separator < 0) return fragment ? { ref: fragment } : {};
3953
+ const ref = fragment.slice(0, separator);
3954
+ const subpath = normalizeGitSubpath(fragment.slice(separator + SUBPATH_SEPARATOR.length), source);
3955
+ return { ...ref ? { ref } : {}, ...subpath ? { subpath } : {} };
3956
+ }
3957
+
4082
3958
  // src/source/git.ts
4083
3959
  var execFileAsync = promisify(execFile);
4084
3960
  var GitSourceDriver = class {
@@ -4138,15 +4014,18 @@ var GitSourceDriver = class {
4138
4014
  return { path, leasePath };
4139
4015
  }
4140
4016
  );
4141
- const snapshotPath = snapshot.path;
4142
- const manifest = await readPackageManifest(snapshotPath);
4017
+ const packageRoot = parsed.subpath ? join19(snapshot.path, parsed.subpath) : snapshot.path;
4018
+ if (parsed.subpath && !await isDirectory(packageRoot)) {
4019
+ throw new Error(`Git source subpath '${parsed.subpath}' does not exist at ${resolvedCommit} in ${parsed.url}`);
4020
+ }
4021
+ const manifest = await readPackageManifest(packageRoot);
4143
4022
  return {
4144
4023
  ...resolved,
4145
- resolvedPath: snapshotPath,
4024
+ resolvedPath: packageRoot,
4146
4025
  packageName: manifest?.name,
4147
4026
  packageVersion: manifest?.version,
4148
4027
  resolvedCommit,
4149
- sourceHash: await hashPath(snapshotPath),
4028
+ sourceHash: await hashPath(packageRoot),
4150
4029
  cacheLeasePath: snapshot.leasePath
4151
4030
  };
4152
4031
  }, "git cache");
@@ -4164,22 +4043,12 @@ var GitSourceDriver = class {
4164
4043
  return resolved;
4165
4044
  }
4166
4045
  };
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 };
4046
+ async function isDirectory(path) {
4047
+ try {
4048
+ return (await stat4(path)).isDirectory();
4049
+ } catch {
4050
+ return false;
4181
4051
  }
4182
- throw new Error(`Invalid git source: ${source}`);
4183
4052
  }
4184
4053
  function cachePathFor2(url, cacheRoot) {
4185
4054
  const root = cacheRoot ? resolve5(cacheRoot) : join19(homedir2(), ".agentwheel", "cache");
@@ -5337,7 +5206,8 @@ function claudeAgentName(artifact) {
5337
5206
 
5338
5207
  // src/staging/staging.ts
5339
5208
  async function stageSource(driver, source, options = {}) {
5340
- return renderStagedBundle(await stageSourceRaw(driver, source, options), options);
5209
+ const raw = await stageSourceRaw(driver, source, options);
5210
+ return removeOnFailure(raw.root, () => renderStagedBundle(raw, options));
5341
5211
  }
5342
5212
  async function stageSourceRaw(driver, source, options = {}) {
5343
5213
  const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
@@ -5352,25 +5222,27 @@ async function stageResolvedSourceRaw(driver, resolved) {
5352
5222
  const artifacts = await driver.list(resolved);
5353
5223
  return stageResolvedArtifactsRaw(resolved, artifacts);
5354
5224
  }
5355
- async function stageResolvedArtifactsRaw(resolved, artifacts) {
5356
- const root = await mkdtemp(join27(tmpdir2(), "agentwheel-stage-"));
5225
+ async function stageResolvedArtifactsRaw(resolved, artifacts, parentDir = tmpdir2()) {
5226
+ const root = await mkdtemp(join27(parentDir, "agentwheel-stage-"));
5357
5227
  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
- }
5228
+ await removeOnFailure(root, async () => {
5229
+ for (const artifact of artifacts) {
5230
+ const stagedPath = join27(root, artifact.relativePath);
5231
+ await mkdir11(dirname13(stagedPath), { recursive: true });
5232
+ await cp3(artifact.sourcePath, stagedPath, {
5233
+ recursive: artifact.kind === "dir",
5234
+ dereference: true,
5235
+ filter: (path) => !isIgnoredGeneratedEntry(basename16(path))
5236
+ });
5237
+ await composeAssets(artifact, resolved.resolvedPath, stagedPath);
5238
+ stagedArtifacts.push({
5239
+ ...artifact,
5240
+ stagedPath,
5241
+ hash: await hashPath(stagedPath),
5242
+ channel: artifact.channel ?? "managed"
5243
+ });
5244
+ }
5245
+ });
5374
5246
  return {
5375
5247
  root,
5376
5248
  source: resolved,
@@ -5527,8 +5399,7 @@ import { dirname as dirname15, join as join30, relative as relative6 } from "pat
5527
5399
 
5528
5400
  // src/resolve/graph.ts
5529
5401
  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";
5402
+ import { readdir as readdir7, readFile as readFile17, stat as stat11 } from "fs/promises";
5532
5403
  import { basename as basename17, extname as extname4, join as join29 } from "path";
5533
5404
 
5534
5405
  // src/model/workspace-composition.ts
@@ -5789,7 +5660,7 @@ var RegistryClient = class {
5789
5660
  }
5790
5661
  };
5791
5662
  async function resolvePackageSource(source, workspaceRoot, options = {}) {
5792
- const { isExplicitSource } = await import("./identify-O54QXPQR.js");
5663
+ const { isExplicitSource } = await import("./identify-M6IA4RS7.js");
5793
5664
  if (await isExplicitSource(source)) return { source };
5794
5665
  const entry = await new RegistryClient({ workspaceRoot, offline: options.offline, warn: options.warn }).resolve(source);
5795
5666
  if (!entry) {
@@ -5831,6 +5702,17 @@ async function normalizeDependencySource(source, options) {
5831
5702
  if (isBareRegistryName(trimmed)) {
5832
5703
  return normalizeRegistrySource(trimmed, options);
5833
5704
  }
5705
+ if (options.declaringGitSource && (trimmed === "." || trimmed === ".." || trimmed.startsWith("./") || trimmed.startsWith("../"))) {
5706
+ const declaring = options.declaringGitSource;
5707
+ const subpath = resolveGitRelativeSubpath(declaring.subpath, trimmed, source);
5708
+ const normalizedSource = formatGitSource(declaring.url, declaring.commit, subpath);
5709
+ return {
5710
+ source: normalizedSource,
5711
+ normalizedSource,
5712
+ driver: "git",
5713
+ requestedRef: declaring.commit
5714
+ };
5715
+ }
5834
5716
  if (isLocalSource(trimmed)) {
5835
5717
  const path = localSourcePath(trimmed);
5836
5718
  const resolvedPath = resolveLocalPath(path, options.declaringPackageRoot);
@@ -5841,10 +5723,10 @@ async function normalizeDependencySource(source, options) {
5841
5723
  };
5842
5724
  }
5843
5725
  if (trimmed.startsWith("github:") || trimmed.startsWith("git:")) {
5844
- const parsed = parseGitSource2(trimmed);
5726
+ const parsed = parseGitSource(trimmed);
5845
5727
  const requestedRef = options.ref ?? parsed.ref ?? "HEAD";
5846
5728
  const url = normalizeGitRemoteUrl(parsed.url, options.declaringPackageRoot);
5847
- const normalizedSource = `git:${url}#${requestedRef}`;
5729
+ const normalizedSource = formatGitSource(url, requestedRef, parsed.subpath);
5848
5730
  return {
5849
5731
  source: normalizedSource,
5850
5732
  normalizedSource,
@@ -5921,20 +5803,6 @@ async function normalizeRegistrySource(name, options) {
5921
5803
  normalizedSource: `registry:${name}:${resolved.normalizedSource}`
5922
5804
  };
5923
5805
  }
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
5806
  function normalizeGitRemoteUrl(url, declaringPackageRoot) {
5939
5807
  if (url.startsWith("./") || url.startsWith("../") || url.startsWith("/") || url === "~" || url.startsWith("~/")) {
5940
5808
  return resolveLocalPath(url, declaringPackageRoot);
@@ -5985,8 +5853,7 @@ var SnapshotConflictError = class extends Error {
5985
5853
  };
5986
5854
  var cacheLocks = /* @__PURE__ */ new Map();
5987
5855
  async function resolveDependencyGraph(roots, options) {
5988
- if (roots.length === 0) throw new Error("At least one graph root is required.");
5989
- const graphRoot = await mkdtemp2(join29(tmpdir3(), "agentwheel-graph-"));
5856
+ if (roots.length === 0 && !options.allowEmptyRoots) throw new Error("At least one graph root is required.");
5990
5857
  const fetchCache = /* @__PURE__ */ new Map();
5991
5858
  const nodesByKey = /* @__PURE__ */ new Map();
5992
5859
  const nodesBySource = /* @__PURE__ */ new Map();
@@ -6052,7 +5919,6 @@ async function resolveDependencyGraph(roots, options) {
6052
5919
  }
6053
5920
  detectDirectCollisions(rawNodes);
6054
5921
  return {
6055
- root: graphRoot,
6056
5922
  roots: rootResults.sort((a, b) => a.rootId.localeCompare(b.rootId)),
6057
5923
  nodes: rawNodes.map((state) => state.node),
6058
5924
  rawNodes,
@@ -6094,6 +5960,7 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
6094
5960
  let lockedByReference = lockedNodeForRequirementReference(requirement, options, lockLabel);
6095
5961
  let normalized = lockedByReference ? normalizedSourceFromLockedNode(lockedByReference.node) : await normalizeDependencySource(requirement.source, {
6096
5962
  declaringPackageRoot: requirement.declaringPackageRoot,
5963
+ declaringGitSource: requirement.declaringGitSource,
6097
5964
  workspaceRoot: options.workspaceRoot,
6098
5965
  ref: requirement.ref,
6099
5966
  registryClient: options.registryClient
@@ -6101,6 +5968,7 @@ async function processRequirement(requirement, options, fetchCache, nodesByKey,
6101
5968
  if (lockedByReference && shouldCheckLockedRootSource(requirement)) {
6102
5969
  const declared = await normalizeDependencySource(requirement.source, {
6103
5970
  declaringPackageRoot: requirement.declaringPackageRoot,
5971
+ declaringGitSource: requirement.declaringGitSource,
6104
5972
  workspaceRoot: options.workspaceRoot,
6105
5973
  ref: requirement.ref,
6106
5974
  registryClient: options.registryClient
@@ -6378,6 +6246,7 @@ function dependencyRequirement(state, fetched, alias, dependency, select, chain,
6378
6246
  useLock: dependency.mode !== "tracking" || options.lockedResolution === true && !updateClosure,
6379
6247
  updateClosure,
6380
6248
  declaringPackageRoot: fetched.resolved.resolvedPath,
6249
+ declaringGitSource: declaringGitSource(fetched),
6381
6250
  requiredBy: state.node.id,
6382
6251
  alias,
6383
6252
  parentId: state.node.id,
@@ -6885,6 +6754,15 @@ async function mapLimit(items, limit, fn) {
6885
6754
  if (failure !== void 0) throw failure;
6886
6755
  return out;
6887
6756
  }
6757
+ function declaringGitSource(fetched) {
6758
+ if (fetched.resolved.driver !== "git" || !fetched.resolved.resolvedCommit) return void 0;
6759
+ const parsed = parseGitSource(fetched.resolved.source);
6760
+ return {
6761
+ url: normalizeGitRemoteUrl(parsed.url, fetched.resolved.resolvedPath),
6762
+ commit: fetched.resolved.resolvedCommit,
6763
+ subpath: parsed.subpath
6764
+ };
6765
+ }
6888
6766
 
6889
6767
  // src/lifecycle/customization.ts
6890
6768
  async function remember(workspaceRoot, runtime, text) {
@@ -7039,7 +6917,7 @@ import { rm as rm7 } from "fs/promises";
7039
6917
  // src/lifecycle/source-plan.ts
7040
6918
  import { createHash as createHash8 } from "crypto";
7041
6919
  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";
6920
+ import { basename as basename18, dirname as dirname17, join as join33, posix as posix3, resolve as resolve16 } from "path";
7043
6921
 
7044
6922
  // src/resolve/graph-diff.ts
7045
6923
  function diffGraphLocks(previous, next) {
@@ -7202,11 +7080,14 @@ function formatSelectionImport(root) {
7202
7080
 
7203
7081
  // src/resolve/render.ts
7204
7082
  import { createHash as createHash7 } from "crypto";
7205
- import { readFile as readFile18, mkdtemp as mkdtemp3 } from "fs/promises";
7206
- import { tmpdir as tmpdir4 } from "os";
7083
+ import { readFile as readFile18, mkdtemp as mkdtemp2 } from "fs/promises";
7084
+ import { tmpdir as tmpdir3 } from "os";
7207
7085
  import { join as join31 } from "path";
7208
7086
  async function renderGraphForTarget(graph, targetContext = {}) {
7209
- const root = await mkdtemp3(join31(tmpdir4(), "agentwheel-render-"));
7087
+ const root = await mkdtemp2(join31(tmpdir3(), "agentwheel-render-"));
7088
+ return removeOnFailure(root, () => renderGraphIntoRoot(root, graph, targetContext));
7089
+ }
7090
+ async function renderGraphIntoRoot(root, graph, targetContext) {
7210
7091
  const artifacts = [];
7211
7092
  const stagedNodes = /* @__PURE__ */ new Map();
7212
7093
  const includeEdges = /* @__PURE__ */ new Map();
@@ -7214,7 +7095,7 @@ async function renderGraphForTarget(graph, targetContext = {}) {
7214
7095
  for (const rawNode of graph.rawNodes) {
7215
7096
  const ownsCompositionRules = rawNode.depth === 0 && rawNode.manifest?.schemaVersion === 3 && Boolean(rawNode.manifest.compositionRules?.length);
7216
7097
  if (rawNode.node.selected.length === 0 && !ownsCompositionRules) continue;
7217
- const rawBundle = await stageResolvedArtifactsRaw(rawNode.resolved, rawNode.artifacts);
7098
+ const rawBundle = await stageResolvedArtifactsRaw(rawNode.resolved, rawNode.artifacts, root);
7218
7099
  const fragmentCustomized = targetContext.workspaceRoot && targetContext.adapter ? await applyFragmentCustomizations(rawBundle.artifacts, {
7219
7100
  workspaceRoot: targetContext.workspaceRoot,
7220
7101
  adapter: targetContext.adapter,
@@ -8081,7 +7962,7 @@ function containsPath(parent, candidate) {
8081
7962
  }
8082
7963
 
8083
7964
  // src/model/target-state.ts
8084
- import { posix, resolve as resolve15 } from "path";
7965
+ import { posix as posix2, resolve as resolve15 } from "path";
8085
7966
  var targetEvolutionFields = /* @__PURE__ */ new Set([
8086
7967
  "adapterCodeHash",
8087
7968
  "adapterConfig",
@@ -8114,7 +7995,7 @@ function persistentTargetParts(value) {
8114
7995
  );
8115
7996
  }
8116
7997
  function normalizeInstallRoot(path, transportKind) {
8117
- return transportKind === "ssh" ? posix.normalize(path) : resolve15(path);
7998
+ return transportKind === "ssh" ? posix2.normalize(path) : resolve15(path);
8118
7999
  }
8119
8000
  function targetEndpoint(value, transportKind) {
8120
8001
  if (transportKind !== "ssh") return { kind: "local" };
@@ -8139,7 +8020,7 @@ function targetEndpoint(value, transportKind) {
8139
8020
 
8140
8021
  // src/lifecycle/source-plan.ts
8141
8022
  async function createGraphSourcePlan(options) {
8142
- if (options.roots.length === 0) {
8023
+ if (options.roots.length === 0 && !options.allowEmptyRoots) {
8143
8024
  throw new Error("At least one source is required for a graph plan.");
8144
8025
  }
8145
8026
  if (options.freshGraphOnly && options.readOnly !== true) {
@@ -8255,7 +8136,8 @@ async function createGraphSourcePlan(options) {
8255
8136
  offline: options.offline,
8256
8137
  previousLock,
8257
8138
  warn,
8258
- runtime: options.adapter.name
8139
+ runtime: options.adapter.name,
8140
+ allowEmptyRoots: options.allowEmptyRoots
8259
8141
  });
8260
8142
  assertFrozenGraph(previousLock, graph, lockMode, lockLabel);
8261
8143
  assertTrustArtifactPolicy(graph, trustPolicy);
@@ -8271,85 +8153,97 @@ async function createGraphSourcePlan(options) {
8271
8153
  noDeps: options.noDeps,
8272
8154
  warn
8273
8155
  });
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.`
8156
+ return removeOnFailure(bundle.root, async () => {
8157
+ const desiredArtifacts = filterArtifactsByAdapterTargets(
8158
+ desiredArtifactsFromGraphBundle(bundle),
8159
+ options.adapter,
8160
+ installationType,
8161
+ { warn }
8287
8162
  );
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
8163
+ const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
8164
+ const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
8165
+ const graphLockDigest = digestGraphLock(bundle.graphLock);
8166
+ const graphDiff = diffGraphLocks(previousLock, bundle.graphLock);
8167
+ if (normalizeInstallRoot2(resolvedInstallRoot, transport.kind) !== normalizeInstallRoot2(installRoot, transport.kind)) {
8168
+ throw new Error(
8169
+ `Adapter ${options.adapter.name} resolved a different install root after rendering; target state identity cannot be proven.`
8170
+ );
8302
8171
  }
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,
8172
+ const manifest = priorState.manifest;
8173
+ const basePlan = options.retireExactMcp ? await createExactMcpRetirementPlan(
8174
+ desiredArtifacts,
8175
+ options.adapter,
8176
+ options.targetRoot,
8177
+ manifest,
8326
8178
  transport,
8327
- workspaceRoot,
8179
+ {
8180
+ installationType: resolvedInstallationType,
8181
+ stateKey,
8182
+ workspaceOwner,
8183
+ expectedFromWorkspaceOwner: options.expectedFromWorkspaceOwner,
8184
+ graphLockDigest
8185
+ }
8186
+ ) : await createCombinedInstallPlan(desiredArtifacts, options.adapter, options.targetRoot, manifest, transport, {
8187
+ baseRevision: manifest?.revision ?? null,
8188
+ graphLockDigest,
8328
8189
  workspaceOwner,
8329
- globalRoot: options.globalRoot,
8190
+ installationType: resolvedInstallationType,
8330
8191
  stateKey,
8331
- migratingStateKey: priorState.migration?.fromStateKey,
8332
- plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
8333
- plannedOperations: plan.operations
8192
+ forceDrift: options.forceDrift,
8193
+ forceConflict: options.forceConflict,
8194
+ replaceConflict: options.replaceConflict,
8195
+ stateMigration: priorState.migration,
8196
+ warn
8334
8197
  });
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
- };
8198
+ const plan = priorState.migration && !basePlan.stateMigration ? { ...basePlan, stateMigration: priorState.migration } : basePlan;
8199
+ if (options.excludedRootIds?.length && previousLock) {
8200
+ const excludedRoots = new Set(options.excludedRootIds);
8201
+ const excludedNodeIds = new Set(previousLock.canonical.roots.filter((root) => excludedRoots.has(root.rootId)).map((root) => root.graphNodeId));
8202
+ for (const operation of plan.operations) {
8203
+ const excludedOwner = operation.owners?.length && operation.owners.every((owner) => excludedRoots.has(owner));
8204
+ if (operation.action === "remove" && (excludedOwner || operation.graphNodeId && excludedNodeIds.has(operation.graphNodeId))) {
8205
+ operation.reason = "excluded by package runtimes";
8206
+ }
8207
+ }
8208
+ }
8209
+ plan.targetStateFilePreconditions = {
8210
+ graphLockPath,
8211
+ graphLockRevision: stableLock ? digestGraphLock(stableLock) : null,
8212
+ sourceLockRevision: computeSourceLockRevision(stableSourceLock)
8213
+ };
8214
+ let foreignStateObservations = [];
8215
+ if (options.forceForeignState !== true && options.deferForeignStateCheck !== true) {
8216
+ foreignStateObservations = await assertNoForeignWorkspaceState({
8217
+ installRoot: resolvedInstallRoot,
8218
+ adapter: options.adapter.name,
8219
+ transport,
8220
+ workspaceRoot,
8221
+ workspaceOwner,
8222
+ globalRoot: options.globalRoot,
8223
+ stateKey,
8224
+ migratingStateKey: priorState.migration?.fromStateKey,
8225
+ plannedPaths: plan.operations.map((operation) => operation.relativeDestPath),
8226
+ plannedOperations: plan.operations
8227
+ });
8228
+ plan.hasBlockingChanges = plan.operations.some((operation) => operation.action === "drift" || operation.action === "conflict");
8229
+ }
8230
+ return {
8231
+ plan,
8232
+ graph,
8233
+ bundle,
8234
+ desiredArtifacts,
8235
+ graphLockPath,
8236
+ graphLockDigest,
8237
+ targetFingerprint,
8238
+ warnings,
8239
+ newTransitiveSources: trustEvaluation.promptSources,
8240
+ graphDiff,
8241
+ recoveredPendingApply,
8242
+ previousManifest: priorState.manifest,
8243
+ previousGraphLock: priorState.graphLock,
8244
+ foreignStateObservations
8245
+ };
8246
+ });
8353
8247
  }
8354
8248
  async function assertNoForeignWorkspaceStateForPlan(plan, options) {
8355
8249
  if (!plan.stateKey) throw new Error(`Foreign-state validation requires a state key for ${plan.adapter}.`);
@@ -8708,7 +8602,7 @@ function stableJson2(value) {
8708
8602
  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
8603
  }
8710
8604
  function normalizeInstallRoot2(path, transportKind) {
8711
- return transportKind === "ssh" ? posix2.normalize(path) : resolve16(path);
8605
+ return transportKind === "ssh" ? posix3.normalize(path) : resolve16(path);
8712
8606
  }
8713
8607
  var workspaceOwnerPrefix2 = "workspace-root:";
8714
8608
  async function assertNoForeignWorkspaceState(check) {
@@ -9196,8 +9090,17 @@ async function syncProfile(options) {
9196
9090
  if (selected && packages.some((pkg) => pkg.selection)) {
9197
9091
  throw new Error("--select/--skill cannot be combined with a package selection import.");
9198
9092
  }
9093
+ if (!options.source) {
9094
+ const adapters = new Set(profile.runtimes.map((runtime) => resolveProfileRuntimeTarget(runtime, config, options.workspaceRoot, options.installationType, options.fleetId).adapter));
9095
+ for (const pkg of packages) {
9096
+ if (pkg.runtimes && !pkg.runtimes.some((runtime) => adapters.has(runtime))) {
9097
+ options.warn?.(`Package '${pkg.name}' matches no runtime in profile '${options.profile}'.`);
9098
+ }
9099
+ }
9100
+ }
9199
9101
  for (const runtime of profile.runtimes) {
9200
9102
  const target = resolveProfileRuntimeTarget(runtime, config, options.workspaceRoot, options.installationType, options.fleetId);
9103
+ const runtimePackages = options.source ? packages : packagesForTarget(packages, target);
9201
9104
  const transport = transportForTarget(target);
9202
9105
  const adapter = await resolveAdapter({
9203
9106
  adapter: target.adapter,
@@ -9210,7 +9113,7 @@ async function syncProfile(options) {
9210
9113
  const installationType = target.installationType ?? defaultInstallationType;
9211
9114
  resolveInstallationTypeForAdapter(adapter, installationType);
9212
9115
  const graphPlan = await createGraphSourcePlan({
9213
- roots: packages.map((pkg) => ({
9116
+ roots: runtimePackages.map((pkg) => ({
9214
9117
  rootId: pkg.name,
9215
9118
  source: pkg.source,
9216
9119
  mode: options.mode ?? pkg.mode,
@@ -9259,14 +9162,16 @@ async function syncProfile(options) {
9259
9162
  forceConflict: options.forceConflict,
9260
9163
  forceForeignState: options.forceForeignState,
9261
9164
  replaceConflict: options.replaceConflict,
9262
- recoverLegacyState: options.recoverLegacyState
9165
+ recoverLegacyState: options.recoverLegacyState,
9166
+ allowEmptyRoots: runtimePackages.length === 0,
9167
+ excludedRootIds: options.source ? [] : packages.filter((pkg) => !runtimePackages.includes(pkg)).map((pkg) => pkg.name)
9263
9168
  });
9264
9169
  try {
9265
9170
  const result = {
9266
9171
  runtime: adapter.name,
9267
9172
  targetRoot: installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh"),
9268
9173
  transport: transport.kind,
9269
- packageName: packages.map((pkg) => pkg.name).join(","),
9174
+ packageName: runtimePackages.map((pkg) => pkg.name).join(",") || "(no packages)",
9270
9175
  plan: graphPlan.plan,
9271
9176
  graphPlan,
9272
9177
  graphLockDigest: graphPlan.graphLockDigest,
@@ -9785,6 +9690,7 @@ function resolveCliVersion() {
9785
9690
 
9786
9691
  // src/lifecycle/ownership.ts
9787
9692
  import { resolve as resolve20 } from "path";
9693
+ var unknownWorkspaceOwner = "workspace:unknown";
9788
9694
  async function planArtifactOwnershipHandoff(request) {
9789
9695
  return validateOwnershipHandoff(request, request.transport ?? localTransport);
9790
9696
  }
@@ -9803,6 +9709,9 @@ async function applyArtifactOwnershipHandoff(request) {
9803
9709
  throw new Error("Cannot hand off ownership while an apply journal is pending. Recover or abort it first.");
9804
9710
  }
9805
9711
  const plan = await validateOwnershipHandoff(request, transport);
9712
+ if (plan.drifted && !request.expectedRuntimeHash) {
9713
+ throw new Error("Applying a drifted ownership handoff requires expectedRuntimeHash from a reviewed dry-run.");
9714
+ }
9806
9715
  const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
9807
9716
  if (!manifest || manifest.version !== 2) {
9808
9717
  throw new Error("Ownership handoff requires an Agentwheel v2 install manifest.");
@@ -9833,6 +9742,13 @@ async function applyArtifactOwnershipHandoff(request) {
9833
9742
  async function validateOwnershipHandoff(request, transport) {
9834
9743
  if (request.expectedHash) assertSha256(request.expectedHash, "expected artifact hash");
9835
9744
  if (request.expectedRevision) assertSha256(request.expectedRevision, "expected manifest revision");
9745
+ if (request.expectedRuntimeHash) {
9746
+ assertSha256(request.expectedRuntimeHash, "expected runtime hash");
9747
+ if (request.carryDrift !== true) throw new Error("An expected runtime hash is only valid with carryDrift.");
9748
+ }
9749
+ if (request.fromWorkspaceRoot === void 0 === (request.fromUnknownOwner !== true)) {
9750
+ throw new Error("Ownership handoff requires exactly one of fromWorkspaceRoot or fromUnknownOwner.");
9751
+ }
9836
9752
  const scope = { installationType: request.installationType, stateKey: request.stateKey };
9837
9753
  const manifest = await readInstallManifest(request.targetRoot, request.adapter, transport, scope);
9838
9754
  if (!manifest) throw new Error(`No install manifest for ${request.adapter} at ${request.targetRoot}`);
@@ -9850,7 +9766,7 @@ async function validateOwnershipHandoff(request, transport) {
9850
9766
  );
9851
9767
  }
9852
9768
  const entry = matches[0];
9853
- const fromOwner = workspaceOwnerForRoot(request.fromWorkspaceRoot);
9769
+ const fromOwner = request.fromUnknownOwner === true ? unknownWorkspaceOwner : workspaceOwnerForRoot(request.fromWorkspaceRoot);
9854
9770
  const toOwner = workspaceOwnerForRoot(request.toWorkspaceRoot, request.toFleetId);
9855
9771
  if (fromOwner === toOwner) throw new Error("Ownership handoff requires different workspace roots.");
9856
9772
  if (entry.workspaceOwner !== fromOwner) {
@@ -9861,9 +9777,14 @@ async function validateOwnershipHandoff(request, transport) {
9861
9777
  }
9862
9778
  const destPath = containedArtifactPath(request.targetRoot, entry.path);
9863
9779
  if (!await transport.pathExists(destPath)) throw new Error(`Managed artifact is missing: ${entry.path}`);
9864
- const currentHash = await verifiedEntryHash(entry, destPath, transport);
9865
- if (request.expectedHash && currentHash !== request.expectedHash) {
9866
- throw new Error(`Current hash precondition failed for ${entry.path}: expected ${request.expectedHash}, found ${currentHash}`);
9780
+ const runtimeHash = await verifiedEntryHash(entry, destPath, transport, request.carryDrift === true);
9781
+ const drifted = runtimeHash !== entry.hash;
9782
+ if (drifted) {
9783
+ if (request.expectedRuntimeHash && runtimeHash !== request.expectedRuntimeHash) {
9784
+ throw new Error(`Runtime hash precondition failed for ${entry.path}: expected ${request.expectedRuntimeHash}, found ${runtimeHash}`);
9785
+ }
9786
+ } else if (request.expectedRuntimeHash) {
9787
+ throw new Error(`Runtime hash precondition failed for ${entry.path}: the artifact is no longer drifted`);
9867
9788
  }
9868
9789
  return {
9869
9790
  adapter: request.adapter,
@@ -9872,13 +9793,15 @@ async function validateOwnershipHandoff(request, transport) {
9872
9793
  targetRoot: request.targetRoot,
9873
9794
  selector: `${request.artifactType}/${request.artifactName}`,
9874
9795
  path: entry.path,
9875
- artifactHash: currentHash,
9796
+ artifactHash: entry.hash,
9797
+ runtimeHash,
9798
+ drifted,
9876
9799
  manifestRevision: manifest.revision,
9877
9800
  fromOwner,
9878
9801
  toOwner
9879
9802
  };
9880
9803
  }
9881
- async function verifiedEntryHash(entry, destPath, transport) {
9804
+ async function verifiedEntryHash(entry, destPath, transport, carryFileDrift = false) {
9882
9805
  if (entry.semanticPlugin) throw new Error(`Ownership handoff cannot verify semantic plugin state at ${destPath}`);
9883
9806
  if (entry.mode === "managed-block") {
9884
9807
  const selector = managedInstructionSelector(entry.logicalSelector, entry.artifactType, entry.artifactName);
@@ -9896,7 +9819,7 @@ async function verifiedEntryHash(entry, destPath, transport) {
9896
9819
  return entry.hash;
9897
9820
  }
9898
9821
  const currentHash = await transport.hashPath(destPath);
9899
- if (currentHash !== entry.hash) {
9822
+ if (currentHash !== entry.hash && !carryFileDrift) {
9900
9823
  throw new Error(`Managed artifact is drifted at ${destPath}: manifest ${entry.hash}, current ${currentHash}`);
9901
9824
  }
9902
9825
  return currentHash;
@@ -10226,7 +10149,7 @@ import { isAbsolute as isAbsolute3, relative as relative9, resolve as resolve23
10226
10149
  // src/lifecycle/legacy-recovery-plan.ts
10227
10150
  import { createHash as createHash10 } from "crypto";
10228
10151
  import { lstat as lstat3, readdir as readdir10 } from "fs/promises";
10229
- import { basename as basename20, dirname as dirname21, join as join39, posix as posix3, relative as relative8, resolve as resolve22 } from "path";
10152
+ import { basename as basename20, dirname as dirname21, join as join39, posix as posix4, relative as relative8, resolve as resolve22 } from "path";
10230
10153
  async function inventoryHistoricalLocks(fleetRoot) {
10231
10154
  const root = join39(fleetRoot, ".agentwheel", "locks");
10232
10155
  const found = [];
@@ -10384,7 +10307,7 @@ function claimFor(item, entry, locks, binding) {
10384
10307
  const matches = locks.filter((candidate) => candidate.digest === digest2);
10385
10308
  const evidence = [];
10386
10309
  const owner = "workspaceOwner" in entry ? entry.workspaceOwner : legacyUnownedWorkspaceOwner;
10387
- const sameRoot = binding.transportKind === "ssh" ? posix3.normalize(item.manifest.targetRoot) === posix3.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
10310
+ const sameRoot = binding.transportKind === "ssh" ? posix4.normalize(item.manifest.targetRoot) === posix4.normalize(binding.installRoot) : resolve22(item.manifest.targetRoot) === resolve22(binding.installRoot);
10388
10311
  const inTargetDir = matches.filter((candidate) => resolve22(dirname21(candidate.path)) === resolve22(binding.targetLockDir));
10389
10312
  const boundLocks = inTargetDir.filter((candidate) => lockBindsState(candidate, item, binding));
10390
10313
  if (owner !== binding.expectedOwner) evidence.push("workspace owner differs from selected Fleet target");
@@ -12390,7 +12313,7 @@ async function planFleetNormalization(request) {
12390
12313
  const destinationDeclaration = canonicalJson2(destinationPackage);
12391
12314
  if (sourceDeclaration !== destinationDeclaration) {
12392
12315
  throw new Error(
12393
- `Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, selections, aliases, overrides, and suggestions must match before normalization.`
12316
+ `Package '${name}' has divergent declarations. Source, driver, adapter, adapter configuration, installation type, version policy, runtime restrictions, selections, aliases, overrides, and suggestions must match before normalization.`
12394
12317
  );
12395
12318
  }
12396
12319
  packages.push({ name, declarationDigest: sha2565(sourceDeclaration) });
@@ -14355,10 +14278,13 @@ program.command("remember").description("append text to the local instructions o
14355
14278
  console.log(nextInstallNudge());
14356
14279
  });
14357
14280
  var ownershipCommand = program.command("ownership").description("inspect and transfer manifest ownership without rewriting runtime artifacts");
14358
- 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) => {
14281
+ 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) => {
14359
14282
  if (!options.dryRun && (!options.expectedHash || !options.expectedRevision)) {
14360
14283
  throw new Error("Applying an ownership handoff requires --expected-hash and --expected-revision from a reviewed --dry-run.");
14361
14284
  }
14285
+ if (Boolean(options.fromWorkspaceRoot) === (options.fromUnknownOwner === true)) {
14286
+ throw new Error("Ownership handoff requires exactly one of --from-workspace-root or --from-unknown-owner.");
14287
+ }
14362
14288
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
14363
14289
  const targets = await resolveCliTargets(normalizedOptions);
14364
14290
  if (targets.length !== 1) {
@@ -14390,11 +14316,13 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
14390
14316
  adapter: adapter.name,
14391
14317
  artifactType,
14392
14318
  artifactName,
14393
- fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
14319
+ ...options.fromUnknownOwner === true ? { fromUnknownOwner: true } : { fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot) },
14394
14320
  toWorkspaceRoot,
14395
14321
  toFleetId: options.toFleet,
14396
14322
  expectedHash: options.expectedHash,
14397
14323
  expectedRevision: options.expectedRevision,
14324
+ carryDrift: options.carryDrift === true,
14325
+ expectedRuntimeHash: options.expectedRuntimeHash,
14398
14326
  transport: transportForTarget(target)
14399
14327
  };
14400
14328
  const result = options.dryRun ? await planArtifactOwnershipHandoff(request) : await applyArtifactOwnershipHandoff(request);
@@ -14402,6 +14330,7 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
14402
14330
  console.log(`Target: ${result.adapter}/${result.installationType ?? "default"} at ${result.targetRoot}`);
14403
14331
  console.log(`Path: ${result.path}`);
14404
14332
  console.log(`Hash: ${result.artifactHash}`);
14333
+ if (result.drifted) console.log(`Runtime hash: ${result.runtimeHash} (drift carried; the recorded hash is kept)`);
14405
14334
  console.log(`Manifest revision: ${result.manifestRevision}`);
14406
14335
  console.log(`Owner: ${result.fromOwner} -> ${result.toOwner}`);
14407
14336
  });
@@ -14420,8 +14349,9 @@ ownershipCommand.command("recovery-plan").description("write a read-only per-pat
14420
14349
  }
14421
14350
  const installationType = target.installationType ?? resolveInstallationTypeForAdapter(adapter, void 0);
14422
14351
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
14423
- if (config.packages.length === 0) throw new Error("Fleet has no configured source packages.");
14424
- await withFreshReadOnlyGraphPlan(target, adapter, adapterOptionsForTarget(target, {}), installationType, config.packages, async (graphPlan) => {
14352
+ const targetPackages = packagesForCliTarget(config.packages, target, {});
14353
+ if (targetPackages.length === 0) throw new Error("Fleet target has no configured source packages.");
14354
+ await withFreshReadOnlyGraphPlan(target, adapter, adapterOptionsForTarget(target, {}), installationType, targetPackages, async (graphPlan) => {
14425
14355
  const installRoot = installRootForAdapterInstallationType(adapter, target.targetRoot, installationType, transport.kind === "ssh");
14426
14356
  const report = await planLegacyRecovery({
14427
14357
  graphPlan,
@@ -14548,7 +14478,7 @@ ownershipCommand.command("adopt-legacy").description("move proven legacy-keyed o
14548
14478
  if (target.transport === "ssh") throw new Error("Legacy ownership adoption does not support SSH targets.");
14549
14479
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
14550
14480
  const groups = /* @__PURE__ */ new Map();
14551
- for (const pkg of config.packages) graphGroupForPackage(groups, target, pkg, plainInstallOptions).packages.push(pkg);
14481
+ for (const pkg of packagesForCliTarget(config.packages, target, plainInstallOptions)) graphGroupForPackage(groups, target, pkg, plainInstallOptions).packages.push(pkg);
14552
14482
  if (groups.size === 0) throw new Error(`No configured source packages in ${target.workspaceRoot}.`);
14553
14483
  const installationTypes = [...new Set([...groups.values()].map((candidate) => candidate.installationType))];
14554
14484
  if (installationTypes.length > 1) {
@@ -14890,7 +14820,7 @@ async function runInstallCommand(nameOrSource, options, behavior) {
14890
14820
  for (const target of targets) {
14891
14821
  const targetOptions = optionsForResolvedTarget(normalizedOptions, target);
14892
14822
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
14893
- const configured = nameOrSource ? findConfiguredPackageForTarget(config.packages, nameOrSource, targetOptions, target) : void 0;
14823
+ const configured = nameOrSource ? findConfiguredPackageForTarget(packagesForCliTarget(config.packages, target, targetOptions), nameOrSource, targetOptions, target) : void 0;
14894
14824
  let source;
14895
14825
  let scope = configured?.name;
14896
14826
  let extraPackage;
@@ -14911,30 +14841,34 @@ async function runInstallCommand(nameOrSource, options, behavior) {
14911
14841
  throw teachingInstallError(nameOrSource, error);
14912
14842
  }
14913
14843
  }
14914
- for (const result of await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage, reportFormat: outputFormat }, { mode: "install" })) {
14915
- if (!behavior.quiet) console.log(formatGraphPlan(result));
14916
- if (behavior.apply) {
14917
- declareMutationPath(result.graphLockPath);
14918
- if (extraPackage && !targetOptions.onlySource) declareMutationPath(workspaceConfigPath(target.workspaceRoot));
14919
- const transport = transportForTarget(target);
14920
- const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
14921
- await applyCombinedInstallPlan(result.plan, {
14922
- executePlugins,
14923
- transport,
14924
- graphLockDigest: result.graphLockDigest,
14925
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
14926
- });
14927
- const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
14928
- enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
14929
- executePlugins
14930
- });
14931
- if (!behavior.quiet) {
14932
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
14933
- if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
14844
+ const results = await buildGraphPlansForTarget(target, source, { ...targetOptions, scope, extraPackage, reportFormat: outputFormat }, { mode: "install" });
14845
+ try {
14846
+ for (const result of results) {
14847
+ if (!behavior.quiet) console.log(formatGraphPlan(result));
14848
+ if (behavior.apply) {
14849
+ declareMutationPath(result.graphLockPath);
14850
+ if (extraPackage && !targetOptions.onlySource) declareMutationPath(workspaceConfigPath(target.workspaceRoot));
14851
+ const transport = transportForTarget(target);
14852
+ const executePlugins = target.executePlugins ?? targetOptions.executePlugins;
14853
+ await applyCombinedInstallPlan(result.plan, {
14854
+ executePlugins,
14855
+ transport,
14856
+ graphLockDigest: result.graphLockDigest,
14857
+ graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
14858
+ });
14859
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
14860
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(targetOptions),
14861
+ executePlugins
14862
+ });
14863
+ if (!behavior.quiet) {
14864
+ console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
14865
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
14866
+ }
14934
14867
  }
14868
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
14935
14869
  }
14936
- await rm10(result.bundle.root, { recursive: true, force: true });
14937
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
14870
+ } finally {
14871
+ await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
14938
14872
  }
14939
14873
  if (behavior.apply && extraPackage && !targetOptions.onlySource) {
14940
14874
  await writeWorkspaceConfig(target.workspaceRoot, upsertPackage(await readWorkspaceConfig(target.workspaceRoot), extraPackage));
@@ -14977,11 +14911,11 @@ async function buildPlanReport(nameOrSource, options) {
14977
14911
  yes: normalizedOptions.yes,
14978
14912
  trustPatterns: normalizedOptions.trust ?? [],
14979
14913
  readOnly: true,
14980
- isTTY: false
14914
+ isTTY: false,
14915
+ warn: collectWarning
14981
14916
  });
14982
14917
  for (const result of results) {
14983
14918
  reportTargets.push(installPlanReportTarget(result.plan, result.graphLockDigest));
14984
- reportWarnings.push(...result.warnings);
14985
14919
  }
14986
14920
  return planReport(reportTargets, reportWarnings);
14987
14921
  }
@@ -14989,7 +14923,7 @@ async function buildPlanReport(nameOrSource, options) {
14989
14923
  for (const target of targets) {
14990
14924
  const targetOptions = optionsForResolvedTarget(normalizedOptions, target);
14991
14925
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
14992
- const configured = nameOrSource ? findConfiguredPackageForTarget(config.packages, nameOrSource, targetOptions, target) : void 0;
14926
+ const configured = nameOrSource ? findConfiguredPackageForTarget(packagesForCliTarget(config.packages, target, targetOptions), nameOrSource, targetOptions, target) : void 0;
14993
14927
  let source;
14994
14928
  let scope = configured?.name;
14995
14929
  let extraPackage;
@@ -15219,13 +15153,13 @@ async function resolveCliTargets(options, behavior = {}) {
15219
15153
  if (normalizedOptions.profile && (normalizedOptions.all || normalizedOptions.allDetected || normalizedOptions.agent)) {
15220
15154
  throw new Error("--profile cannot be combined with --all, --all-detected, or --agent.");
15221
15155
  }
15222
- const adapters2 = adapterListFromOption(normalizedOptions.adapter);
15223
- if (adapters2.length > 1) {
15156
+ const adapters = adapterListFromOption(normalizedOptions.adapter);
15157
+ if (adapters.length > 1) {
15224
15158
  if (normalizedOptions.all || normalizedOptions.allDetected || normalizedOptions.agent || normalizedOptions.profile) {
15225
15159
  throw new Error("--adapter <a,b> cannot be combined with --all, --all-detected, --agent, or --profile. Use a profile for mixed configured targets.");
15226
15160
  }
15227
15161
  const targets = [];
15228
- for (const adapter of adapters2) {
15162
+ for (const adapter of adapters) {
15229
15163
  targets.push(await resolveRuntimeTarget({
15230
15164
  cwd: scopeRoot,
15231
15165
  targetRoot: normalizedOptions.targetRoot,
@@ -15371,7 +15305,7 @@ async function runSkillUpdateCommand(skillName, options) {
15371
15305
  const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
15372
15306
  for (const target of targets) {
15373
15307
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
15374
- const owner = await configuredPackageForSkill(target, config.packages, skillName, normalizedOptions, options.package);
15308
+ const owner = await configuredPackageForSkill(target, packagesForCliTarget(config.packages, target, normalizedOptions), skillName, normalizedOptions, options.package);
15375
15309
  const mode = owner.mode === "tracking" ? "update" : "install";
15376
15310
  console.log(`Skill ${skillName}: ${owner.name} (${mode}).`);
15377
15311
  await runConfiguredGraphPackages(target, {
@@ -15385,49 +15319,67 @@ async function runSkillUpdateCommand(skillName, options) {
15385
15319
  }
15386
15320
  async function runConfiguredGraphPackages(target, options, behavior) {
15387
15321
  const results = await buildGraphPlansForTarget(target, void 0, options, behavior);
15388
- for (const result of results) {
15389
- console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
15390
- console.log(formatGraphPlan(result));
15391
- if (!options.dryRun) {
15392
- declareMutationPath(result.graphLockPath);
15393
- const transport = transportForTarget(target);
15394
- const executePlugins = target.executePlugins ?? options.executePlugins;
15395
- await applyCombinedInstallPlan(result.plan, {
15396
- executePlugins,
15397
- transport,
15398
- graphLockDigest: result.graphLockDigest,
15399
- graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
15400
- });
15401
- const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
15402
- enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
15403
- executePlugins
15404
- });
15405
- console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
15406
- if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
15322
+ try {
15323
+ for (const result of results) {
15324
+ console.log(`${behavior.mode === "update" ? "Update" : "Install"} ${result.plan.adapter} at ${result.plan.targetRoot}:`);
15325
+ console.log(formatGraphPlan(result));
15326
+ if (!options.dryRun) {
15327
+ declareMutationPath(result.graphLockPath);
15328
+ const transport = transportForTarget(target);
15329
+ const executePlugins = target.executePlugins ?? options.executePlugins;
15330
+ await applyCombinedInstallPlan(result.plan, {
15331
+ executePlugins,
15332
+ transport,
15333
+ graphLockDigest: result.graphLockDigest,
15334
+ graphLock: { path: result.graphLockPath, lock: result.bundle.graphLock }
15335
+ });
15336
+ const reloaded = await reloadRuntimeAfterPluginChanges(result.plan, target, transport, {
15337
+ enabled: target.reloadRuntimes ?? shouldReloadRuntimes(options),
15338
+ executePlugins
15339
+ });
15340
+ console.log(`Applied ${result.plan.adapter} at ${result.plan.targetRoot}.`);
15341
+ if (reloaded) console.log(`Reloaded runtime via ${formatReloadCommands(target.reloadCommands)}.`);
15342
+ }
15343
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
15407
15344
  }
15408
- await rm10(result.bundle.root, { recursive: true, force: true });
15409
- if (result.plan.hasBlockingChanges) process.exitCode = 1;
15345
+ } finally {
15346
+ await Promise.all(results.map((result) => rm10(result.bundle.root, { recursive: true, force: true })));
15410
15347
  }
15411
15348
  }
15412
15349
  async function buildGraphPlansForTarget(target, source, options, behavior) {
15350
+ const bundleRoots = [];
15351
+ try {
15352
+ return await collectGraphPlansForTarget(target, source, options, behavior, bundleRoots);
15353
+ } catch (error) {
15354
+ await Promise.all(bundleRoots.map((root) => rm10(root, { recursive: true, force: true })));
15355
+ throw error;
15356
+ }
15357
+ }
15358
+ async function collectGraphPlansForTarget(target, source, options, behavior, bundleRoots) {
15413
15359
  const targetOptions = optionsForResolvedTarget(options, target);
15414
15360
  const config = await readMergedWorkspaceConfig(target.workspaceRoot);
15361
+ const targetPackages = packagesForCliTarget(config.packages, target, targetOptions);
15415
15362
  const groups = /* @__PURE__ */ new Map();
15416
15363
  const selectedArtifacts = selectedArtifactsFromOptions(targetOptions);
15417
15364
  const dependencyUpdateSelectors = sortedUniqueValues(targetOptions.dependency ?? []);
15418
15365
  const scopedDependencyUpdate = behavior.mode === "update" && dependencyUpdateSelectors.length > 0;
15419
- const scopedPackage = targetOptions.scope ? findConfiguredPackage(config.packages, targetOptions.scope) : void 0;
15366
+ const scopedPackage = targetOptions.scope ? findConfiguredPackage(targetPackages, targetOptions.scope) : void 0;
15420
15367
  const scopedRootId = scopedPackage?.name ?? (source ? targetOptions.scope : void 0);
15421
15368
  if (targetOptions.scope && !scopedPackage && !source) throw new Error(`Configured package not found: ${targetOptions.scope}`);
15422
15369
  if (scopedPackage && targetOptions.onlySource) {
15423
15370
  const group = graphGroupForPackage(groups, target, scopedPackage, targetOptions);
15424
15371
  group.packages.push(scopedPackage);
15425
15372
  } else if (!source || !targetOptions.onlySource) {
15426
- for (const pkg of config.packages) {
15373
+ for (const pkg of targetPackages) {
15427
15374
  const group = graphGroupForPackage(groups, target, pkg, targetOptions);
15428
15375
  group.packages.push(pkg);
15429
15376
  }
15430
15377
  }
15378
+ if (!source && !targetOptions.scope) {
15379
+ for (const pkg of config.packages.filter((candidate) => !targetPackages.includes(candidate))) {
15380
+ graphGroupForPackage(groups, target, pkg, targetOptions);
15381
+ }
15382
+ }
15431
15383
  if (source) {
15432
15384
  let entry = targetOptions.extraPackage ?? await packageEntryFromSource(source, target.workspaceRoot, {
15433
15385
  ...targetOptions,
@@ -15467,6 +15419,11 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
15467
15419
  const adapter = await resolveAdapterForTarget(group.target, { ...group.adapterOptions, warn: targetOptions.warn });
15468
15420
  const transport = transportForTarget(group.target);
15469
15421
  const allPackages = [...group.packages, ...group.extraPackages];
15422
+ const excludedRootIds = config.packages.filter((pkg) => !targetPackages.includes(pkg) && graphGroupKey(targetForPackage(target, pkg, targetOptions), {
15423
+ installationType: targetOptions.installationType ?? pkg.installationType ?? target.installationType ?? "local",
15424
+ adapterConfig: targetOptions.adapterConfig ?? target.adapterConfig ?? pkg.adapterConfig,
15425
+ adapterModule: targetOptions.adapterModule ?? target.adapterModule ?? pkg.adapterModule
15426
+ }) === graphGroupKey(group.target, { ...group.adapterOptions, installationType: group.installationType })).map((pkg) => pkg.name);
15470
15427
  const groupHasScope = !scopedRootId || allPackages.some((pkg) => pkg.name === scopedRootId || pkg.source === targetOptions.scope);
15471
15428
  if (behavior.mode === "install" && scopedRootId && !groupHasScope) continue;
15472
15429
  if (behavior.readOnly !== true && targetOptions.dryRun !== true) {
@@ -15565,15 +15522,16 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
15565
15522
  ];
15566
15523
  if (behavior.mode === "update") {
15567
15524
  const changed = roots.filter((root) => root.useLock === false);
15568
- if (changed.length === 0 && !scopedDependencyUpdate) {
15525
+ if (changed.length === 0 && !scopedDependencyUpdate && excludedRootIds.length === 0) {
15569
15526
  const label = targetOptions.scope ? ` ${targetOptions.scope}` : "";
15570
15527
  console.log(`No tracking packages to update${label}.`);
15571
15528
  continue;
15572
15529
  }
15573
15530
  }
15574
- if (roots.length === 0) continue;
15575
15531
  const result = await createGraphSourcePlan({
15576
15532
  roots,
15533
+ allowEmptyRoots: roots.length === 0,
15534
+ excludedRootIds,
15577
15535
  targetRoot: group.target.targetRoot,
15578
15536
  workspaceRoot: group.target.workspaceRoot,
15579
15537
  fleetId: group.target.fleetId,
@@ -15603,6 +15561,7 @@ async function buildGraphPlansForTarget(target, source, options, behavior) {
15603
15561
  expectedFromWorkspaceOwner: targetOptions.expectedFromWorkspaceOwner,
15604
15562
  recoverLegacyState: targetOptions.recoverLegacyState
15605
15563
  });
15564
+ bundleRoots.push(result.bundle.root);
15606
15565
  if ((behavior.mode === "install" || behavior.mode === "update") && scopedRootId) {
15607
15566
  const manifest = result.previousManifest;
15608
15567
  const previousLock = result.previousGraphLock;
@@ -16206,7 +16165,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
16206
16165
  if (removed.length === 0) {
16207
16166
  throw new Error(`Configured package not found: ${packageName}`);
16208
16167
  }
16209
- const remaining = config.packages.filter((pkg) => !removed.includes(pkg));
16168
+ const remaining = packagesForCliTarget(config.packages.filter((pkg) => !removed.includes(pkg)), target, options);
16210
16169
  const groups = /* @__PURE__ */ new Map();
16211
16170
  for (const pkg of remaining) {
16212
16171
  const group = graphGroupForPackage(groups, target, pkg, options);
@@ -16239,7 +16198,7 @@ async function uninstallConfiguredPackage(target, packageName, options) {
16239
16198
  let renderedRoot;
16240
16199
  if (remainingGroup && remainingGroup.packages.length > 0) {
16241
16200
  const remainingAdapter = await resolveAdapterForTarget(remainingGroup.target, remainingGroup.adapterOptions);
16242
- const result2 = await createGraphSourcePlan({
16201
+ const result = await createGraphSourcePlan({
16243
16202
  roots: remainingGroup.packages.map((pkg2) => ({
16244
16203
  rootId: pkg2.name,
16245
16204
  source: pkg2.source,
@@ -16271,46 +16230,49 @@ async function uninstallConfiguredPackage(target, packageName, options) {
16271
16230
  readOnly: options.dryRun === true,
16272
16231
  isTTY: process.stdin.isTTY === true
16273
16232
  });
16274
- remainingGraphPlan = result2;
16275
- remainingDesired = desiredArtifactsFromGraphBundle(result2.bundle);
16276
- renderedRoot = result2.bundle.root;
16233
+ remainingGraphPlan = result;
16234
+ remainingDesired = desiredArtifactsFromGraphBundle(result.bundle);
16235
+ renderedRoot = result.bundle.root;
16277
16236
  }
16278
- const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
16279
- graphLockDigest: remainingGraphPlan?.graphLockDigest
16280
- });
16281
- const transitionPlan = remainingGraphPlan?.plan;
16282
- const plan = {
16283
- ...uninstallPlan,
16284
- stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
16285
- stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
16286
- targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
16287
- };
16288
- console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
16289
- console.log(formatPlan(plan));
16290
- const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
16291
- removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
16292
- };
16293
- if (!options.dryRun) {
16294
- declareMutationPath(workspaceConfigPath(target.workspaceRoot));
16295
- const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
16296
- declareMutationPath(declarativeGraphPath);
16297
- }
16298
- const result = await uninstall(plan, {
16299
- dryRun: options.dryRun,
16300
- force: options.force,
16301
- keepFiles: options.keepFiles,
16302
- transport,
16303
- ...graphLockFinalState,
16304
- workspaceConfig: {
16305
- path: workspaceConfigPath(target.workspaceRoot),
16306
- data: { ...config, packages: remaining }
16237
+ try {
16238
+ const uninstallPlan = await createOwnershipUninstallPlan(manifest, remainingDesired, adapter, transport, {
16239
+ graphLockDigest: remainingGraphPlan?.graphLockDigest
16240
+ });
16241
+ const transitionPlan = remainingGraphPlan?.plan;
16242
+ const plan = {
16243
+ ...uninstallPlan,
16244
+ stateKey: transitionPlan?.stateKey ?? removedState.stableStateKey,
16245
+ stateMigration: transitionPlan?.stateMigration ?? removedState.migration,
16246
+ targetStateFilePreconditions: transitionPlan?.targetStateFilePreconditions ?? removedState.targetStateFilePreconditions
16247
+ };
16248
+ console.log(`Uninstall ${pkg.name} (${adapter.name} at ${removedTarget.targetRoot}):`);
16249
+ console.log(formatPlan(plan));
16250
+ const graphLockFinalState = remainingGraphPlan ? { graphLock: { path: remainingGraphPlan.graphLockPath, lock: remainingGraphPlan.bundle.graphLock } } : {
16251
+ removeGraphLockPath: removedState.graphLockPath ?? removedState.stableGraphLockPath
16252
+ };
16253
+ if (!options.dryRun) {
16254
+ declareMutationPath(workspaceConfigPath(target.workspaceRoot));
16255
+ const declarativeGraphPath = remainingGraphPlan ? remainingGraphPlan.graphLockPath : removedState.graphLockPath ?? removedState.stableGraphLockPath;
16256
+ declareMutationPath(declarativeGraphPath);
16307
16257
  }
16308
- });
16309
- if (!options.dryRun) {
16310
- console.log(formatUninstallResult(result));
16258
+ const result = await uninstall(plan, {
16259
+ dryRun: options.dryRun,
16260
+ force: options.force,
16261
+ keepFiles: options.keepFiles,
16262
+ transport,
16263
+ ...graphLockFinalState,
16264
+ workspaceConfig: {
16265
+ path: workspaceConfigPath(target.workspaceRoot),
16266
+ data: { ...config, packages: remaining }
16267
+ }
16268
+ });
16269
+ if (!options.dryRun) {
16270
+ console.log(formatUninstallResult(result));
16271
+ }
16272
+ if (plan.hasBlockingChanges) process.exitCode = 1;
16273
+ } finally {
16274
+ if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
16311
16275
  }
16312
- if (renderedRoot) await rm10(renderedRoot, { recursive: true, force: true });
16313
- if (plan.hasBlockingChanges) process.exitCode = 1;
16314
16276
  }
16315
16277
  }
16316
16278
  function graphGroupForPackage(groups, target, pkg, options) {
@@ -16339,6 +16301,9 @@ function targetForPackage(target, pkg, options) {
16339
16301
  const installationType = options.installationType ?? pkg.installationType ?? target.installationType;
16340
16302
  return options.adapter || target.source !== "cwd" ? { ...target, installationType } : { ...target, adapter: pkg.adapter, installationType };
16341
16303
  }
16304
+ function packagesForCliTarget(packages, target, options) {
16305
+ return packagesForTarget(packages, target, (pkg) => targetForPackage(target, pkg, options));
16306
+ }
16342
16307
  function graphGroupKey(target, options) {
16343
16308
  return JSON.stringify({
16344
16309
  adapter: target.adapter,
@@ -16367,7 +16332,7 @@ function targetFingerprintParts(target, adapter, options, installationType) {
16367
16332
  };
16368
16333
  }
16369
16334
  async function withFreshReadOnlyGraphPlan(target, adapter, adapterOptions, installationType, packages, use) {
16370
- const stage = await mkdtemp4(join46(tmpdir5(), "agentwheel-recovery-"));
16335
+ const stage = await mkdtemp3(join46(tmpdir4(), "agentwheel-recovery-"));
16371
16336
  const priorTmpdir = process.env.TMPDIR;
16372
16337
  try {
16373
16338
  process.env.TMPDIR = stage;
@@ -16515,7 +16480,7 @@ async function collectTargetStatus(target, options) {
16515
16480
  observation.manifestRevision
16516
16481
  ]).sort())).digest("hex") : null;
16517
16482
  const packages = [];
16518
- for (const pkg of config.packages) {
16483
+ for (const pkg of packagesForCliTarget(config.packages, target, options)) {
16519
16484
  const root = effectiveGraphLock?.canonical.roots.find((candidate) => candidate.rootId === pkg.name);
16520
16485
  const node = root ? effectiveGraphLock?.canonical.nodes.find((candidate) => candidate.id === root.graphNodeId) : void 0;
16521
16486
  const availability = await discoverPackageVersions(pkg, target.workspaceRoot, {
@@ -17253,9 +17218,9 @@ function isHomePath(path) {
17253
17218
  }
17254
17219
  function adapterListFromOption(adapter) {
17255
17220
  if (!adapter) return [];
17256
- const adapters2 = splitSelectorList(adapter);
17257
- const unique = [...new Set(adapters2)];
17258
- if (unique.length !== adapters2.length) {
17221
+ const adapters = splitSelectorList(adapter);
17222
+ const unique = [...new Set(adapters)];
17223
+ if (unique.length !== adapters.length) {
17259
17224
  throw new Error(`Duplicate adapter in --adapter: ${adapter}`);
17260
17225
  }
17261
17226
  return unique;