agentwheel 0.18.5 → 0.19.1

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
@@ -56,6 +56,18 @@ var packageComposeEntrySchema = z.object({
56
56
  markers: z.boolean().optional(),
57
57
  optional: z.boolean().optional()
58
58
  });
59
+ var packageCompositionRuleSchema = z.object({
60
+ target: z.string().min(1),
61
+ include: z.string().min(1),
62
+ exclude: z.array(z.string().min(1)).optional(),
63
+ runtimes: z.array(z.string().min(1)).optional(),
64
+ markers: z.boolean().optional()
65
+ });
66
+ var packageSupersedesEntrySchema = z.object({
67
+ package: z.string().min(1),
68
+ selector: z.string().min(1),
69
+ reason: z.string().min(1)
70
+ });
59
71
  var packageItemRequireObjectSchema = z.object({
60
72
  selector: z.string().min(1),
61
73
  optional: z.boolean().optional(),
@@ -97,6 +109,7 @@ var artifactSchema = z.object({
97
109
  requires: z.array(packageItemRequireSchema).optional(),
98
110
  suggests: z.array(packageItemSuggestSchema).optional(),
99
111
  compose: z.array(packageComposeEntrySchema).optional(),
112
+ supersedes: z.array(packageSupersedesEntrySchema).optional(),
100
113
  runtimes: z.array(z.string().min(1)).optional(),
101
114
  composedFrom: z.array(composedFromEntrySchema).optional()
102
115
  });
@@ -580,7 +593,8 @@ var graphLockArtifactSchema = z4.object({
580
593
  kind: fileKindSchema,
581
594
  hash: z4.string().min(16),
582
595
  channel: z4.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
583
- composedFrom: z4.array(composedFromEntrySchema).optional()
596
+ composedFrom: z4.array(composedFromEntrySchema).optional(),
597
+ supersedes: z4.array(packageSupersedesEntrySchema).optional()
584
598
  });
585
599
  var graphLockPlainNameIncumbentSchema = z4.object({
586
600
  adapter: z4.string().min(1),
@@ -1660,6 +1674,16 @@ import { dirname as dirname9 } from "path";
1660
1674
  import { parse as parse3, stringify as stringify2 } from "yaml";
1661
1675
  var MergeAdoptionMismatchError = class extends Error {
1662
1676
  };
1677
+ function assertExactMergeContribution(contribution, strategy, currentContent) {
1678
+ if (strategy === "codex-toml-mcp") {
1679
+ assertExactMcpMergeContribution(contribution, strategy, currentContent);
1680
+ return;
1681
+ }
1682
+ const mismatch = firstMergeContributionMismatch(parseMergeDestination(currentContent, strategy), contribution);
1683
+ if (mismatch) {
1684
+ throw new MergeAdoptionMismatchError(`exact merge contribution differs or is missing at ${mismatch}`);
1685
+ }
1686
+ }
1663
1687
  function assertExactMcpMergeContribution(removal, strategy, currentContent) {
1664
1688
  if (strategy === "codex-toml-mcp") {
1665
1689
  const mismatched = mismatchedCodexTomlMcpServers(removal, currentContent);
@@ -1766,6 +1790,25 @@ function firstMcpContributionMismatch(current, incoming) {
1766
1790
  }
1767
1791
  return void 0;
1768
1792
  }
1793
+ function firstMergeContributionMismatch(current, contribution, path = "$") {
1794
+ if (isRecord4(contribution)) {
1795
+ if (!isRecord4(current)) return path;
1796
+ for (const [key, value] of Object.entries(contribution)) {
1797
+ if (!(key in current)) return `${path}.${key}`;
1798
+ const mismatch = firstMergeContributionMismatch(current[key], value, `${path}.${key}`);
1799
+ if (mismatch) return mismatch;
1800
+ }
1801
+ return void 0;
1802
+ }
1803
+ if (Array.isArray(contribution)) {
1804
+ if (!Array.isArray(current)) return path;
1805
+ for (const value of contribution) {
1806
+ if (!current.some((candidate) => sameMcpValue(candidate, value))) return path;
1807
+ }
1808
+ return void 0;
1809
+ }
1810
+ return current === contribution ? void 0 : path;
1811
+ }
1769
1812
  function combineMergeValues(existing, incoming) {
1770
1813
  if (isRecord4(existing) && isRecord4(incoming)) {
1771
1814
  const combined = { ...existing };
@@ -5638,7 +5681,7 @@ var legacyArtifactTypeSchema = z6.enum([
5638
5681
  "plugins"
5639
5682
  ]);
5640
5683
  var runtimeListSchema = z6.array(z6.string().min(1));
5641
- var CURRENT_OPENPACK_SCHEMA_VERSION = 2;
5684
+ var CURRENT_OPENPACK_SCHEMA_VERSION = 3;
5642
5685
  var packageProvideBaseSchema = z6.object({
5643
5686
  path: z6.string().min(1),
5644
5687
  format: artifactFormatSchema.optional(),
@@ -5650,6 +5693,7 @@ var packageItemSchema = z6.object({
5650
5693
  requires: z6.array(packageItemRequireSchema).optional(),
5651
5694
  suggests: z6.array(packageItemSuggestSchema).optional(),
5652
5695
  compose: z6.array(packageComposeEntrySchema).optional(),
5696
+ supersedes: z6.array(packageSupersedesEntrySchema).optional(),
5653
5697
  runtimes: runtimeListSchema.optional()
5654
5698
  });
5655
5699
  var packageDependencySchema = z6.object({
@@ -5680,8 +5724,7 @@ var packageManifestV1Schema = z6.object({
5680
5724
  version: z6.string().min(1),
5681
5725
  provides: z6.array(packageProvideV1Schema).min(1)
5682
5726
  });
5683
- var packageManifestV2Schema = z6.object({
5684
- schemaVersion: z6.literal(CURRENT_OPENPACK_SCHEMA_VERSION),
5727
+ var packageManifestModernShape = {
5685
5728
  name: z6.string().min(1),
5686
5729
  version: z6.string().min(1),
5687
5730
  runtimes: runtimeListSchema.optional(),
@@ -5689,7 +5732,8 @@ var packageManifestV2Schema = z6.object({
5689
5732
  suggests: z6.record(z6.string().min(1), packageSuggestionSchema).optional(),
5690
5733
  compose: z6.array(packageComposeEntrySchema).optional(),
5691
5734
  provides: z6.array(packageProvideSchema).default([])
5692
- }).superRefine((manifest, ctx) => {
5735
+ };
5736
+ function requireProvidesOrDependencies(manifest, ctx) {
5693
5737
  const hasProvides = manifest.provides.length > 0;
5694
5738
  const hasRequires = Object.keys(manifest.requires ?? {}).length > 0;
5695
5739
  if (!hasProvides && !hasRequires) {
@@ -5699,8 +5743,17 @@ var packageManifestV2Schema = z6.object({
5699
5743
  message: "OpenPack v2 manifest must declare at least one provides entry or one requires dependency"
5700
5744
  });
5701
5745
  }
5702
- });
5703
- var packageManifestSchema = z6.union([packageManifestV1Schema, packageManifestV2Schema]);
5746
+ }
5747
+ var packageManifestV2Schema = z6.object({
5748
+ schemaVersion: z6.literal(2),
5749
+ ...packageManifestModernShape
5750
+ }).superRefine(requireProvidesOrDependencies);
5751
+ var packageManifestV3Schema = z6.object({
5752
+ schemaVersion: z6.literal(CURRENT_OPENPACK_SCHEMA_VERSION),
5753
+ ...packageManifestModernShape,
5754
+ compositionRules: z6.array(packageCompositionRuleSchema).optional()
5755
+ }).superRefine(requireProvidesOrDependencies);
5756
+ var packageManifestSchema = z6.union([packageManifestV1Schema, packageManifestV2Schema, packageManifestV3Schema]);
5704
5757
  var openPackManifestNames = ["openpack.json", "openpack.jsonc"];
5705
5758
  var legacyPackageManifestNames = ["agentwheel.json", "agentwheel.jsonc"];
5706
5759
  var packageManifestNames = [...openPackManifestNames, ...legacyPackageManifestNames];
@@ -5741,9 +5794,30 @@ function parsePackageManifest(parsed, path = "package manifest") {
5741
5794
  return parseWithSchema(packageManifestV1Schema, parsed, path);
5742
5795
  }
5743
5796
  if (parsed.schemaVersion === 2) {
5797
+ const violations = v2OpenPackViolations(parsed);
5798
+ if (violations.length > 0) {
5799
+ throw new Error(`OpenPack schemaVersion 3 is required for ${violations.join(", ")} in ${path}. Set "schemaVersion": 3.`);
5800
+ }
5744
5801
  return parseWithSchema(packageManifestV2Schema, parsed, path);
5745
5802
  }
5746
- throw new Error(`Invalid package manifest ${path}: schemaVersion must be 1 or 2`);
5803
+ if (parsed.schemaVersion === 3) {
5804
+ return parseWithSchema(packageManifestV3Schema, parsed, path);
5805
+ }
5806
+ throw new Error(`Invalid package manifest ${path}: schemaVersion must be 1, 2, or 3`);
5807
+ }
5808
+ function v2OpenPackViolations(manifest) {
5809
+ const violations = [];
5810
+ if (Object.prototype.hasOwnProperty.call(manifest, "compositionRules")) violations.push("compositionRules");
5811
+ const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
5812
+ for (const [provideIndex, provide] of provides.entries()) {
5813
+ if (!isRecord7(provide) || !isRecord7(provide.items)) continue;
5814
+ for (const [itemName, item] of Object.entries(provide.items)) {
5815
+ if (isRecord7(item) && Object.prototype.hasOwnProperty.call(item, "supersedes")) {
5816
+ violations.push(`provides[${provideIndex}].items.${itemName}.supersedes`);
5817
+ }
5818
+ }
5819
+ }
5820
+ return violations;
5747
5821
  }
5748
5822
  function parseWithSchema(schema, parsed, path) {
5749
5823
  const result = schema.safeParse(parsed);
@@ -5753,7 +5827,7 @@ function parseWithSchema(schema, parsed, path) {
5753
5827
  }
5754
5828
  function v1OpenPackViolations(manifest) {
5755
5829
  const violations = [];
5756
- for (const key of ["requires", "items", "compose", "runtimes"]) {
5830
+ for (const key of ["requires", "items", "compose", "runtimes", "compositionRules", "supersedes"]) {
5757
5831
  if (Object.prototype.hasOwnProperty.call(manifest, key)) violations.push(key);
5758
5832
  }
5759
5833
  const provides = Array.isArray(manifest.provides) ? manifest.provides : [];
@@ -6003,6 +6077,7 @@ async function artifactForFile(type, name, sourcePath, relativePath, packageName
6003
6077
  requires: item.requires,
6004
6078
  suggests: item.suggests,
6005
6079
  compose: item.compose,
6080
+ supersedes: item.supersedes,
6006
6081
  runtimes: item.runtimes ?? provideRuntimes(provide) ?? manifestRuntimes(manifest)
6007
6082
  };
6008
6083
  }
@@ -6023,6 +6098,7 @@ async function artifactForDir(type, name, sourcePath, relativePath, packageName,
6023
6098
  requires: item.requires,
6024
6099
  suggests: item.suggests,
6025
6100
  compose: item.compose,
6101
+ supersedes: item.supersedes,
6026
6102
  runtimes: item.runtimes ?? provideRuntimes(provide) ?? manifestRuntimes(manifest)
6027
6103
  };
6028
6104
  }
@@ -6030,13 +6106,13 @@ function itemMetadata(provide, itemName) {
6030
6106
  if (!provide || !("items" in provide) || !provide.items || !itemName) return {};
6031
6107
  const item = provide.items[itemName];
6032
6108
  if (!item) return {};
6033
- return { format: item.format, requires: item.requires, suggests: item.suggests, compose: item.compose, runtimes: item.runtimes };
6109
+ return { format: item.format, requires: item.requires, suggests: item.suggests, compose: item.compose, supersedes: item.supersedes, runtimes: item.runtimes };
6034
6110
  }
6035
6111
  function provideRuntimes(provide) {
6036
6112
  return provide && "runtimes" in provide ? provide.runtimes : void 0;
6037
6113
  }
6038
6114
  function manifestRuntimes(manifest) {
6039
- return manifest && manifest.schemaVersion === 2 ? manifest.runtimes : void 0;
6115
+ return manifest && manifest.schemaVersion !== 1 ? manifest.runtimes : void 0;
6040
6116
  }
6041
6117
 
6042
6118
  // src/source/clawhub.ts
@@ -7229,7 +7305,14 @@ async function expandMarkdownIncludes(artifacts, packageRoot, options = {}) {
7229
7305
  if (files.length === 0) continue;
7230
7306
  const composedFrom = [];
7231
7307
  for (const file of files) {
7232
- const result = await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
7308
+ const localEntries = composeEntriesForFile(artifact, file).map((entry) => ({
7309
+ entry,
7310
+ packageRoot,
7311
+ artifactPaths,
7312
+ nodeId: options.nodeId
7313
+ }));
7314
+ const externalEntries = isPrimaryMarkdownFile(artifact, file) ? options.additionalComposeEntries?.(artifact) ?? [] : [];
7315
+ const result = await expandFile(file, packageRoot, [...localEntries, ...externalEntries], artifactPaths, options);
7233
7316
  if (result.changed) await writeFile17(file, result.content, "utf8");
7234
7317
  composedFrom.push(...result.composedFrom);
7235
7318
  }
@@ -7247,7 +7330,8 @@ async function validateMarkdownIncludes(artifacts, packageRoot, options = {}) {
7247
7330
  const artifactPaths = artifactPathMap(artifacts);
7248
7331
  for (const artifact of artifacts) {
7249
7332
  for (const file of await markdownFilesForArtifact(artifact)) {
7250
- await expandFile(file, packageRoot, composeEntriesForFile(artifact, file), artifactPaths, options);
7333
+ const entries = composeEntriesForFile(artifact, file).map((entry) => ({ entry, packageRoot, artifactPaths, nodeId: options.nodeId }));
7334
+ await expandFile(file, packageRoot, entries, artifactPaths, options);
7251
7335
  }
7252
7336
  }
7253
7337
  }
@@ -7257,9 +7341,11 @@ async function expandFile(file, packageRoot, appendEntries, artifactPaths, optio
7257
7341
  const expanded = await expandContent(raw, packageRoot, [owner], artifactPaths, options);
7258
7342
  let content = expanded.content;
7259
7343
  const composedFrom = [...expanded.composedFrom];
7260
- for (const entry of appendEntries) {
7261
- const included = await expandInclude(entry.include, packageRoot, artifactPaths, {
7344
+ for (const external of appendEntries) {
7345
+ const { entry } = external;
7346
+ const included = await expandInclude(entry.include, external.packageRoot, external.artifactPaths, {
7262
7347
  ...options,
7348
+ nodeId: external.nodeId,
7263
7349
  optional: entry.optional === true,
7264
7350
  markers: entry.markers !== false,
7265
7351
  chain: [owner]
@@ -7437,6 +7523,10 @@ function composeEntriesForFile(artifact, file) {
7437
7523
  if (artifact.kind === "file") return [resolve11(artifact.stagedPath ?? artifact.sourcePath), resolve11(file)].every(Boolean) && resolve11(artifact.stagedPath ?? artifact.sourcePath) === resolve11(file) ? artifact.compose : [];
7438
7524
  return basename16(file) === "SKILL.md" && dirname21(file) === resolve11(artifact.stagedPath ?? artifact.sourcePath) ? artifact.compose : [];
7439
7525
  }
7526
+ function isPrimaryMarkdownFile(artifact, file) {
7527
+ if (artifact.kind === "file") return resolve11(artifact.stagedPath ?? artifact.sourcePath) === resolve11(file);
7528
+ return basename16(file) === "SKILL.md" && dirname21(file) === resolve11(artifact.stagedPath ?? artifact.sourcePath);
7529
+ }
7440
7530
  function orderedForExpansion(artifacts) {
7441
7531
  return [...artifacts].sort((a, b) => Number(a.type === "fragments") - Number(b.type === "fragments"));
7442
7532
  }
@@ -8923,7 +9013,7 @@ Dependency chain: ${requirement.chain.join(" -> ")}`);
8923
9013
  }
8924
9014
  }
8925
9015
  async function collectDependencyNeeds(state, fetched, options, chain) {
8926
- if (fetched.manifest?.schemaVersion !== 2) return [];
9016
+ if (!fetched.manifest || fetched.manifest.schemaVersion === 1) return [];
8927
9017
  const dependencies = fetched.manifest.requires ?? {};
8928
9018
  const suggestions = fetched.manifest.suggests ?? {};
8929
9019
  const dependencyEntries = Object.entries(dependencies).sort(([a], [b]) => a.localeCompare(b));
@@ -9487,6 +9577,14 @@ function detectDirectCollisions(nodes) {
9487
9577
  for (const [selector, owners] of bySelector) {
9488
9578
  const uniqueOwners = [...new Map(owners.map((owner) => [owner.node.id, owner])).values()];
9489
9579
  if (uniqueOwners.length <= 1) continue;
9580
+ const replacements = uniqueOwners.filter((owner) => {
9581
+ const artifact = owner.artifacts.find((candidate) => artifactSelectorKey(candidate) === selector);
9582
+ return uniqueOwners.every((other) => {
9583
+ if (other === owner) return true;
9584
+ return artifact?.supersedes?.some((entry) => entry.package === other.node.name && entry.selector === selector) === true;
9585
+ });
9586
+ });
9587
+ if (replacements.length === 1) continue;
9490
9588
  throw new Error(
9491
9589
  `Direct dependency artifact collision for ${selector}: ${uniqueOwners.map((owner) => `${owner.node.id} required by ${owner.node.requiredBy.join(", ")}`).join("; ")}. Resolve by aliasing, deselecting one artifact, or overriding the dependency selection.`
9492
9590
  );
@@ -9806,7 +9904,8 @@ async function renderGraphForTarget(graph, targetContext = {}) {
9806
9904
  const includeEdges = /* @__PURE__ */ new Map();
9807
9905
  const ambiguousPackageNames = ambiguousGraphPackageNames(graph);
9808
9906
  for (const rawNode of graph.rawNodes) {
9809
- if (rawNode.node.selected.length === 0) continue;
9907
+ const ownsCompositionRules = rawNode.depth === 0 && rawNode.manifest?.schemaVersion === 3 && Boolean(rawNode.manifest.compositionRules?.length);
9908
+ if (rawNode.node.selected.length === 0 && !ownsCompositionRules) continue;
9810
9909
  const rawBundle = await stageResolvedArtifactsRaw(rawNode.resolved, rawNode.artifacts);
9811
9910
  const fragmentCustomized = targetContext.workspaceRoot && targetContext.adapter ? await applyFragmentCustomizations(rawBundle.artifacts, {
9812
9911
  workspaceRoot: targetContext.workspaceRoot,
@@ -9826,11 +9925,18 @@ async function renderGraphForTarget(graph, targetContext = {}) {
9826
9925
  });
9827
9926
  }
9828
9927
  const aliasEdges = aliasEdgeMap(graph);
9928
+ const compositionRules = collectCompositionRules(stagedNodes);
9829
9929
  for (const staged of [...stagedNodes.values()].sort((a, b) => a.rawNode.node.id.localeCompare(b.rawNode.node.id))) {
9830
9930
  const rawNode = staged.rawNode;
9831
9931
  const expandedArtifacts = await expandMarkdownIncludes(staged.artifacts, staged.root, {
9832
9932
  nodeId: rawNode.node.id,
9833
9933
  originNodeId: rawNode.node.id,
9934
+ additionalComposeEntries: (artifact) => matchingCompositionEntries(
9935
+ artifact,
9936
+ rawNode.node.id,
9937
+ targetContext.adapter?.name,
9938
+ compositionRules
9939
+ ),
9834
9940
  resolveCrossPackageInclude: async (request) => {
9835
9941
  const edge = aliasEdges.get(`${request.fromNodeId}\0${request.alias}`);
9836
9942
  if (!edge) {
@@ -9908,6 +10014,50 @@ async function renderGraphForTarget(graph, targetContext = {}) {
9908
10014
  graphLock: createGraphLock(graph, sortedArtifacts.map(lockArtifactFor), targetContext.targetFingerprint, [...includeEdges.values()], namespacing, overrides)
9909
10015
  };
9910
10016
  }
10017
+ function collectCompositionRules(stagedNodes) {
10018
+ const rules = [];
10019
+ for (const staged of stagedNodes.values()) {
10020
+ if (staged.rawNode.depth !== 0) continue;
10021
+ const manifest = staged.rawNode.manifest;
10022
+ if (!manifest || manifest.schemaVersion !== 3) continue;
10023
+ for (const rule of manifest.compositionRules ?? []) {
10024
+ rules.push({
10025
+ ownerNodeId: staged.rawNode.node.id,
10026
+ ownerPackageName: staged.rawNode.node.name,
10027
+ rule,
10028
+ packageRoot: staged.root,
10029
+ artifactPaths: staged.artifactPaths
10030
+ });
10031
+ }
10032
+ }
10033
+ return rules.sort((a, b) => `${a.ownerNodeId}\0${a.rule.target}\0${a.rule.include}`.localeCompare(`${b.ownerNodeId}\0${b.rule.target}\0${b.rule.include}`));
10034
+ }
10035
+ function matchingCompositionEntries(artifact, targetNodeId, runtime, rules) {
10036
+ const selector = `${artifact.type}/${artifact.name}`;
10037
+ const qualifiedSelector = `${artifact.packageName ?? targetNodeId}:${selector}`;
10038
+ const seen = /* @__PURE__ */ new Set();
10039
+ const entries = [];
10040
+ for (const candidate of rules) {
10041
+ const { rule } = candidate;
10042
+ if (rule.runtimes?.length && (!runtime || !rule.runtimes.includes(runtime))) continue;
10043
+ if (!globMatches(rule.target, selector) && !globMatches(rule.target, qualifiedSelector)) continue;
10044
+ if (rule.exclude?.some((pattern) => globMatches(pattern, selector) || globMatches(pattern, qualifiedSelector))) continue;
10045
+ const key = `${candidate.ownerNodeId}\0${rule.include}`;
10046
+ if (seen.has(key)) continue;
10047
+ seen.add(key);
10048
+ entries.push({
10049
+ entry: { include: rule.include, markers: rule.markers },
10050
+ packageRoot: candidate.packageRoot,
10051
+ artifactPaths: candidate.artifactPaths,
10052
+ nodeId: candidate.ownerNodeId
10053
+ });
10054
+ }
10055
+ return entries;
10056
+ }
10057
+ function globMatches(pattern, value) {
10058
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replaceAll("*", ".*");
10059
+ return new RegExp(`^${escaped}$`).test(value);
10060
+ }
9911
10061
  function aliasEdgeMap(graph) {
9912
10062
  return new Map(graph.edges.map((edge) => [`${edge.from}\0${edge.alias}`, { to: edge.to }]));
9913
10063
  }
@@ -9939,10 +10089,12 @@ function sha2563(content) {
9939
10089
  return createHash10("sha256").update(content).digest("hex");
9940
10090
  }
9941
10091
  function assignInstallNames(graph, artifacts) {
10092
+ const superseded = supersededLogicalSelectors(artifacts);
10093
+ const effectiveArtifacts = artifacts.filter((artifact) => !superseded.has(artifact.logicalSelector));
9942
10094
  const aliases = workspaceAliases(graph);
9943
- validateAliasScopes(graph, artifacts, aliases);
10095
+ validateAliasScopes(graph, effectiveArtifacts, aliases);
9944
10096
  const decisions = /* @__PURE__ */ new Map();
9945
- const withAliases = artifacts.map((artifact) => {
10097
+ const withAliases = effectiveArtifacts.map((artifact) => {
9946
10098
  const alias = aliasForArtifact(artifact, graph, aliases);
9947
10099
  if (!alias) return artifact;
9948
10100
  const updated = { ...artifact, installName: alias };
@@ -9981,6 +10133,23 @@ function assignInstallNames(graph, artifacts) {
9981
10133
  }));
9982
10134
  return { artifacts: out, namespacing: [...decisions.values()], overrides: finalOverrides };
9983
10135
  }
10136
+ function supersededLogicalSelectors(artifacts) {
10137
+ const removed = /* @__PURE__ */ new Set();
10138
+ for (const replacement of artifacts) {
10139
+ for (const declaration of replacement.supersedes ?? []) {
10140
+ const replacementSelector = `${replacement.type}/${replacement.name}`;
10141
+ if (declaration.selector !== replacementSelector) {
10142
+ throw new Error(`Supersedes selector must match the replacement artifact ${replacement.logicalSelector}: ${declaration.selector}`);
10143
+ }
10144
+ const matches = artifacts.filter((candidate) => candidate !== replacement && candidate.packageName === declaration.package && `${candidate.type}/${candidate.name}` === declaration.selector);
10145
+ if (matches.length > 1) {
10146
+ throw new Error(`Supersedes target is ambiguous for ${replacement.logicalSelector}: ${declaration.package}:${declaration.selector}`);
10147
+ }
10148
+ if (matches[0]) removed.add(matches[0].logicalSelector);
10149
+ }
10150
+ }
10151
+ return removed;
10152
+ }
9984
10153
  function applyWorkspaceOverrides(graph, artifacts) {
9985
10154
  const directives = workspaceOverrides(graph);
9986
10155
  if (directives.length === 0) return { artifacts, overrides: [] };
@@ -10180,7 +10349,8 @@ function lockArtifactFor(artifact) {
10180
10349
  kind: artifact.kind,
10181
10350
  hash: artifact.hash,
10182
10351
  channel: artifact.channel ?? "managed",
10183
- composedFrom: artifact.composedFrom
10352
+ composedFrom: artifact.composedFrom,
10353
+ supersedes: artifact.supersedes
10184
10354
  };
10185
10355
  }
10186
10356
 
@@ -10348,8 +10518,8 @@ async function createExactMcpRetirementPlan(desiredArtifacts, adapter, targetRoo
10348
10518
  }
10349
10519
  const removalKeys = Object.keys(operation.mergeRemoval);
10350
10520
  const servers = operation.mergeRemoval.mcpServers;
10351
- if (removalKeys.length !== 1 || removalKeys[0] !== "mcpServers" || !servers || typeof servers !== "object" || Array.isArray(servers) || Object.keys(servers).length !== 1) {
10352
- throw new Error("Exact MCP retirement requires exactly one MCP server and no non-MCP configuration.");
10521
+ if (removalKeys.length !== 1 || removalKeys[0] !== "mcpServers" || !servers || typeof servers !== "object" || Array.isArray(servers) || Object.keys(servers).length === 0) {
10522
+ throw new Error("Exact MCP retirement requires one or more MCP servers and no non-MCP configuration.");
10353
10523
  }
10354
10524
  const entry = manifest?.entries[0];
10355
10525
  if (entry) {
@@ -11336,15 +11506,25 @@ async function validatePackage(root) {
11336
11506
  } catch (error) {
11337
11507
  findings.push({ level: "error", message: error instanceof Error ? error.message : String(error), path: packageRoot });
11338
11508
  }
11339
- if (manifest.schemaVersion === 2 && manifest.compose) {
11509
+ if (manifest.schemaVersion !== 1 && manifest.compose) {
11340
11510
  for (const entry of manifest.compose) {
11341
11511
  await validateManifestComposeInclude(packageRoot, entry.include, entry.optional === true, findings, manifestPath, Object.keys(manifest.requires ?? {}));
11342
11512
  }
11343
11513
  }
11514
+ if (manifest.schemaVersion === 3) {
11515
+ const aliases = Object.keys(manifest.requires ?? {});
11516
+ for (const [index, rule] of (manifest.compositionRules ?? []).entries()) {
11517
+ validateSelector(rule.include, `compositionRules[${index}].include`, findings, manifestPath, { fragmentsOnly: true, aliases });
11518
+ if (!rule.target.startsWith("skills/")) {
11519
+ findings.push({ level: "error", message: `compositionRules[${index}].target: v3 composition rules may target only skills/*`, path: manifestPath });
11520
+ }
11521
+ await validateManifestComposeInclude(packageRoot, rule.include, false, findings, manifestPath, aliases);
11522
+ }
11523
+ }
11344
11524
  return { ok: !findings.some((finding) => finding.level === "error"), manifestPath, findings };
11345
11525
  }
11346
11526
  function validateDeclaredSelectors(manifest, findings, manifestPath) {
11347
- if (manifest.schemaVersion === 2) {
11527
+ if (manifest.schemaVersion !== 1) {
11348
11528
  for (const [alias, dependency] of Object.entries(manifest.requires ?? {})) {
11349
11529
  if (!alias.trim()) {
11350
11530
  findings.push({ level: "error", message: "Dependency alias must be non-empty", path: manifestPath });
@@ -11365,13 +11545,19 @@ function validateDeclaredSelectors(manifest, findings, manifestPath) {
11365
11545
  for (const [provideIndex, provide] of manifest.provides.entries()) {
11366
11546
  if (!("items" in provide) || !provide.items) continue;
11367
11547
  for (const [itemName, item] of Object.entries(provide.items)) {
11548
+ for (const declaration of item.supersedes ?? []) {
11549
+ const expected = `${provide.type}/${itemName}`;
11550
+ if (declaration.selector !== expected) {
11551
+ findings.push({ level: "error", message: `provides[${provideIndex}].items.${itemName}.supersedes: selector must equal ${expected}`, path: manifestPath });
11552
+ }
11553
+ }
11368
11554
  for (const requirement of item.requires ?? []) {
11369
11555
  const selector = typeof requirement === "string" ? requirement : requirement.selector;
11370
- validateSelector(selector, `provides[${provideIndex}].items.${itemName}.requires`, findings, manifestPath, { aliases: manifest.schemaVersion === 2 ? Object.keys(manifest.requires ?? {}) : [] });
11556
+ validateSelector(selector, `provides[${provideIndex}].items.${itemName}.requires`, findings, manifestPath, { aliases: manifest.schemaVersion !== 1 ? Object.keys(manifest.requires ?? {}) : [] });
11371
11557
  }
11372
11558
  for (const suggestion of item.suggests ?? []) {
11373
11559
  const alias = typeof suggestion === "string" ? suggestion : suggestion.alias;
11374
- if (manifest.schemaVersion === 2 && !Object.keys(manifest.suggests ?? {}).includes(alias)) {
11560
+ if (manifest.schemaVersion !== 1 && !Object.keys(manifest.suggests ?? {}).includes(alias)) {
11375
11561
  findings.push({ level: "error", message: `provides[${provideIndex}].items.${itemName}.suggests: suggestion alias not declared: ${alias}`, path: manifestPath });
11376
11562
  }
11377
11563
  for (const selector of typeof suggestion === "string" ? [] : suggestion.select ?? []) {
@@ -11380,7 +11566,7 @@ function validateDeclaredSelectors(manifest, findings, manifestPath) {
11380
11566
  }
11381
11567
  for (const entry of item.compose ?? []) {
11382
11568
  validateSelector(entry.include, `provides[${provideIndex}].items.${itemName}.compose.include`, findings, manifestPath, {
11383
- aliases: manifest.schemaVersion === 2 ? Object.keys(manifest.requires ?? {}) : [],
11569
+ aliases: manifest.schemaVersion !== 1 ? Object.keys(manifest.requires ?? {}) : [],
11384
11570
  fragmentsOnly: true
11385
11571
  });
11386
11572
  }
@@ -11572,7 +11758,7 @@ async function validateOwnershipHandoff(request, transport) {
11572
11758
  }
11573
11759
  const entry = matches[0];
11574
11760
  const fromOwner = workspaceOwnerForRoot(request.fromWorkspaceRoot);
11575
- const toOwner = workspaceOwnerForRoot(request.toWorkspaceRoot);
11761
+ const toOwner = workspaceOwnerForRoot(request.toWorkspaceRoot, request.toFleetId);
11576
11762
  if (fromOwner === toOwner) throw new Error("Ownership handoff requires different workspace roots.");
11577
11763
  if (entry.workspaceOwner !== fromOwner) {
11578
11764
  throw new Error(`Old owner precondition failed for ${entry.path}: expected ${fromOwner}, found ${entry.workspaceOwner}`);
@@ -11582,10 +11768,7 @@ async function validateOwnershipHandoff(request, transport) {
11582
11768
  }
11583
11769
  const destPath = containedArtifactPath(request.targetRoot, entry.path);
11584
11770
  if (!await transport.pathExists(destPath)) throw new Error(`Managed artifact is missing: ${entry.path}`);
11585
- const currentHash = await transport.hashPath(destPath);
11586
- if (currentHash !== entry.hash) {
11587
- throw new Error(`Managed artifact is drifted at ${entry.path}: manifest ${entry.hash}, current ${currentHash}`);
11588
- }
11771
+ const currentHash = await verifiedEntryHash(entry, destPath, transport);
11589
11772
  if (request.expectedHash && currentHash !== request.expectedHash) {
11590
11773
  throw new Error(`Current hash precondition failed for ${entry.path}: expected ${request.expectedHash}, found ${currentHash}`);
11591
11774
  }
@@ -11602,6 +11785,29 @@ async function validateOwnershipHandoff(request, transport) {
11602
11785
  toOwner
11603
11786
  };
11604
11787
  }
11788
+ async function verifiedEntryHash(entry, destPath, transport) {
11789
+ if (entry.semanticPlugin) throw new Error(`Ownership handoff cannot verify semantic plugin state at ${destPath}`);
11790
+ if (entry.mode === "managed-block") {
11791
+ const selector = managedInstructionSelector(entry.logicalSelector, entry.artifactType, entry.artifactName);
11792
+ const state = await readManagedInstructionBlockState(destPath, selector, transport);
11793
+ if (!state.hasBlock || state.drifted || state.hash !== entry.hash) {
11794
+ throw new Error(`Managed artifact is drifted at ${destPath}: managed block is missing or changed`);
11795
+ }
11796
+ return entry.hash;
11797
+ }
11798
+ if (entry.mergeStrategy) {
11799
+ if (!hasMergeRemovalContent(entry.mergeRemoval)) {
11800
+ throw new Error(`Ownership handoff cannot verify incomplete merge ownership at ${destPath}`);
11801
+ }
11802
+ assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, await transport.readFile(destPath));
11803
+ return entry.hash;
11804
+ }
11805
+ const currentHash = await transport.hashPath(destPath);
11806
+ if (currentHash !== entry.hash) {
11807
+ throw new Error(`Managed artifact is drifted at ${destPath}: manifest ${entry.hash}, current ${currentHash}`);
11808
+ }
11809
+ return currentHash;
11810
+ }
11605
11811
  function containedArtifactPath(targetRoot, relativePath) {
11606
11812
  if (!relativePath || relativePath.startsWith("/") || relativePath.includes("\0")) {
11607
11813
  throw new Error(`Unsafe managed artifact path: ${relativePath}`);
@@ -13610,7 +13816,7 @@ async function inspectInstalledState(source, destination, packageNames) {
13610
13816
  throw new Error(`Partial ownership at ${sourceEntry.renderedPath} includes packages outside the normalization selection.`);
13611
13817
  }
13612
13818
  const destinationRenderedPath = renderedEntryPathForRoot(destinationState.installRoot, sourceEntry.entry.path);
13613
- await assertEquivalentRuntimeBytes(sourceEntry.renderedPath, destinationRenderedPath, sourceEntry.entry.hash);
13819
+ await assertEquivalentRuntimeState(sourceEntry.entry, sourceEntry.renderedPath, destinationRenderedPath);
13614
13820
  plannedDestinationPaths.add(destinationRenderedPath);
13615
13821
  sourceRenderedPaths.push(sourceEntry.renderedPath);
13616
13822
  destinationRenderedPaths.push(destinationRenderedPath);
@@ -13774,7 +13980,7 @@ async function inspectLegacySelfInstalledState(fleet, packageNames, profileName,
13774
13980
  if (!coveredByCurrentGraph && !matchesCurrentGraph && !orphanedOwners.has(entry.entry.workspaceOwner)) {
13775
13981
  throw new Error(`Legacy manifest entry is not covered by its graph lock: ${entry.renderedPath}`);
13776
13982
  }
13777
- await assertEquivalentRuntimeBytes(entry.renderedPath, entry.renderedPath, entry.entry.hash);
13983
+ await assertEquivalentRuntimeState(entry.entry, entry.renderedPath, entry.renderedPath);
13778
13984
  renderedPaths.add(entry.renderedPath);
13779
13985
  sourceRenderedPaths.push(entry.renderedPath);
13780
13986
  if (coveredByCurrentGraph || matchesCurrentGraph) {
@@ -14184,6 +14390,33 @@ async function assertEquivalentRuntimeBytes(sourcePath, destinationPath, expecte
14184
14390
  throw new Error(`Runtime content drift at ${destinationPath}; source and destination bytes are not equivalent.`);
14185
14391
  }
14186
14392
  }
14393
+ async function assertEquivalentRuntimeState(entry, sourcePath, destinationPath) {
14394
+ if (entry.mode === "managed-block") {
14395
+ const selector = managedInstructionSelector(entry.logicalSelector, entry.artifactType, entry.artifactName);
14396
+ for (const path of /* @__PURE__ */ new Set([sourcePath, destinationPath])) {
14397
+ const state = await readManagedInstructionBlockState(path, selector, localTransport);
14398
+ if (!state.exists || !state.hasBlock || state.drifted || state.hash !== entry.hash) {
14399
+ throw new Error(`Runtime managed-block drift at ${path}; the installed contribution is missing or changed.`);
14400
+ }
14401
+ }
14402
+ return;
14403
+ }
14404
+ if (entry.mergeStrategy) {
14405
+ for (const path of /* @__PURE__ */ new Set([sourcePath, destinationPath])) {
14406
+ if (!await pathExists(path)) throw new Error(`Runtime merge destination is missing: ${path}`);
14407
+ const content = await readFile37(path, "utf8");
14408
+ if (hasMergeRemovalContent(entry.mergeRemoval)) {
14409
+ assertExactMergeContribution(entry.mergeRemoval, entry.mergeStrategy, content);
14410
+ } else if (resolve25(sourcePath) === resolve25(destinationPath) && entry.mergeStrategy !== "codex-toml-mcp") {
14411
+ assertExactMergeContribution({}, entry.mergeStrategy, content);
14412
+ } else {
14413
+ throw new Error(`Installed-state normalization cannot prove incomplete merge ownership at ${path}.`);
14414
+ }
14415
+ }
14416
+ return;
14417
+ }
14418
+ await assertEquivalentRuntimeBytes(sourcePath, destinationPath, entry.hash);
14419
+ }
14187
14420
  function renderedEntryPathForRoot(root, entryPath) {
14188
14421
  const normalizedRoot = resolve25(root);
14189
14422
  const candidate = resolve25(normalizedRoot, entryPath);
@@ -14291,8 +14524,8 @@ function workspaceOwners(scope) {
14291
14524
  ]);
14292
14525
  }
14293
14526
  function assertSimpleVerifiableEntry(entry, path) {
14294
- if (entry.semanticPlugin || entry.mergeStrategy || entry.mode) {
14295
- throw new Error(`Installed-state normalization cannot byte-verify semantic, merge, or managed-block entry ${path}.`);
14527
+ if (entry.semanticPlugin) {
14528
+ throw new Error(`Installed-state normalization cannot byte-verify semantic plugin entry ${path}.`);
14296
14529
  }
14297
14530
  if (entry.kind !== "file" && entry.kind !== "dir") throw new Error(`Unsupported installed entry kind at ${path}.`);
14298
14531
  }
@@ -14324,7 +14557,7 @@ function graphArtifactIdentity(artifact) {
14324
14557
  dependencyRole: artifact.dependencyRole,
14325
14558
  owners: artifact.owners,
14326
14559
  kind: artifact.kind,
14327
- sourceHash: artifact.hash,
14560
+ ...artifact.composedFrom?.length ? { composedFrom: artifact.composedFrom } : { sourceHash: artifact.hash },
14328
14561
  channel: artifact.channel
14329
14562
  });
14330
14563
  }
@@ -14338,7 +14571,7 @@ function graphEntryIdentity(entry) {
14338
14571
  dependencyRole: entry.dependencyRole,
14339
14572
  owners: entry.owners,
14340
14573
  kind: entry.kind,
14341
- sourceHash: entry.sourceHash,
14574
+ ...entry.composedFrom?.length ? { composedFrom: entry.composedFrom } : { sourceHash: entry.sourceHash },
14342
14575
  channel: entry.channel
14343
14576
  });
14344
14577
  }
@@ -14977,7 +15210,7 @@ program.command("remember").description("append text to the local instructions o
14977
15210
  console.log(nextInstallNudge());
14978
15211
  });
14979
15212
  var ownershipCommand = program.command("ownership").description("inspect and transfer manifest ownership without rewriting runtime artifacts");
14980
- 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("--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) => {
15213
+ 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) => {
14981
15214
  if (!options.dryRun && (!options.expectedHash || !options.expectedRevision)) {
14982
15215
  throw new Error("Applying an ownership handoff requires --expected-hash and --expected-revision from a reviewed --dry-run.");
14983
15216
  }
@@ -14995,6 +15228,13 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
14995
15228
  const adapter = await resolveAdapterForTarget(target, adapterOptions);
14996
15229
  const installationType = normalizedOptions.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
14997
15230
  const state = installStateForTarget(target, adapter, adapterOptions, installationType);
15231
+ const toWorkspaceRoot = normalizeCliPath(options.toWorkspaceRoot);
15232
+ if (options.toFleet) {
15233
+ const fleet = await showRegisteredFleet(options.toFleet);
15234
+ if (resolve26(fleet.root) !== resolve26(toWorkspaceRoot)) {
15235
+ throw new Error(`Destination fleet '${options.toFleet}' is registered at ${fleet.root}, not ${toWorkspaceRoot}.`);
15236
+ }
15237
+ }
14998
15238
  const request = {
14999
15239
  ...state,
15000
15240
  targetRoot: state.installRoot,
@@ -15002,7 +15242,8 @@ ownershipCommand.command("handoff").description("transfer one managed artifact b
15002
15242
  artifactType,
15003
15243
  artifactName,
15004
15244
  fromWorkspaceRoot: normalizeCliPath(options.fromWorkspaceRoot),
15005
- toWorkspaceRoot: normalizeCliPath(options.toWorkspaceRoot),
15245
+ toWorkspaceRoot,
15246
+ toFleetId: options.toFleet,
15006
15247
  expectedHash: options.expectedHash,
15007
15248
  expectedRevision: options.expectedRevision,
15008
15249
  transport: transportForTarget(target)
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.18.5",
4
+ "version": "0.19.1",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.18.5",
3
+ "version": "0.19.1",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.18.5"
8
+ version: "0.19.1"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -5,7 +5,7 @@ allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.18.5"
8
+ version: "0.19.1"
9
9
  ---
10
10
 
11
11
  # Agentwheel Discovery