agentwheel 0.14.7 → 0.14.9

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.
Files changed (2) hide show
  1. package/dist/index.js +169 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -143,6 +143,20 @@ function supportedInstallationTypes(adapter, artifactType) {
143
143
  }
144
144
  return [...types].sort((a, b) => a.localeCompare(b));
145
145
  }
146
+ function adapterTargetSupport(adapter, artifactType, installationType) {
147
+ if (artifactType === "fragments") return { ok: true };
148
+ const registry = adapter.targets[artifactType];
149
+ const supported = supportedInstallationTypes(adapter, artifactType);
150
+ if (!registry) {
151
+ return { ok: false, reason: "adapter-target-unsupported", supportedInstallationTypes: supported };
152
+ }
153
+ const target = registry[installationType];
154
+ if (target?.enabled) return { ok: true };
155
+ if (target && !target.enabled) {
156
+ return { ok: false, reason: "adapter-target-disabled", supportedInstallationTypes: supported };
157
+ }
158
+ return { ok: false, reason: "adapter-target-unsupported", supportedInstallationTypes: supported };
159
+ }
146
160
  function resolveInstallationTypeForArtifacts(adapter, artifactTypes, requested) {
147
161
  const installableTypes = [...new Set(artifactTypes.filter((type) => type !== "fragments"))];
148
162
  if (installableTypes.length === 0) {
@@ -1256,6 +1270,23 @@ async function removeApplyJournal(targetRoot, adapter, transport = localTranspor
1256
1270
  await transport.rm(applyJournalPath(targetRoot, adapter, scope));
1257
1271
  await transport.rm(applyBackupDir(targetRoot, adapter, scope));
1258
1272
  }
1273
+ async function abortApplyJournal(targetRoot, adapter, transport = localTransport, scope = {}) {
1274
+ const lock = await acquireApplyLock(targetRoot, adapter, transport, {}, scope);
1275
+ try {
1276
+ const journalPath = applyJournalPath(targetRoot, adapter, scope);
1277
+ if (!await transport.pathExists(journalPath)) return void 0;
1278
+ const stateKey = stateKeyFor(adapter, scope);
1279
+ const archivePath = join3(metadataDir(targetRoot), "archive", `${stateKey}.apply-journal.failed-${journalTimestamp(/* @__PURE__ */ new Date())}.json`);
1280
+ const content = await transport.readFile(journalPath);
1281
+ await transport.writeFileAtomic(archivePath, content.endsWith("\n") ? content : `${content}
1282
+ `);
1283
+ await transport.rm(journalPath);
1284
+ await transport.rm(applyBackupDir(targetRoot, adapter, scope));
1285
+ return { journalPath, archivePath };
1286
+ } finally {
1287
+ await lock.release();
1288
+ }
1289
+ }
1259
1290
  async function recordBackup(operation, index, targetRoot, adapter, transport = localTransport, scope = {}) {
1260
1291
  const hadExisting = await transport.pathExists(operation.destPath);
1261
1292
  if (!hadExisting || transport.kind !== "local" || operation.action !== "update" && operation.action !== "remove" && operation.action !== "create") {
@@ -1312,6 +1343,9 @@ async function readLockOwner(ownerPath, transport) {
1312
1343
  function isAlreadyExists(error) {
1313
1344
  return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
1314
1345
  }
1346
+ function journalTimestamp(date) {
1347
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
1348
+ }
1315
1349
  async function localPathExists(path) {
1316
1350
  try {
1317
1351
  await stat2(path);
@@ -2024,7 +2058,12 @@ async function executePluginInstall(operation, transport) {
2024
2058
  async function executePluginUninstall(operation, transport) {
2025
2059
  const commands = operation.semanticPlugin?.uninstallCommands ?? [];
2026
2060
  if (commands.length === 0) throw new Error(`Invalid semantic plugin operation missing uninstall command: ${operation.relativeDestPath}`);
2027
- await executeSemanticCommands(operation, commands, transport);
2061
+ try {
2062
+ await executeSemanticCommands(operation, commands, transport);
2063
+ } catch (error) {
2064
+ if (!isPluginAlreadyAbsentError(operation, error)) throw error;
2065
+ console.warn(`WARNING plugin-already-absent ${operation.relativeDestPath}: ${firstErrorLine(error)}`);
2066
+ }
2028
2067
  if (operation.semanticPlugin?.stateRoot) {
2029
2068
  await transport.rm(operation.semanticPlugin.stateRoot);
2030
2069
  }
@@ -2171,6 +2210,35 @@ async function executeSemanticCommands(operation, commands, transport, options =
2171
2210
  await transport.rm(stagingRoot);
2172
2211
  }
2173
2212
  }
2213
+ function isPluginAlreadyAbsentError(operation, error) {
2214
+ const output = commandErrorOutput(error).toLowerCase();
2215
+ if (!output || output.includes("command not found") || output.includes("module not found")) return false;
2216
+ const pluginName = (operation.semanticPlugin?.pluginName ?? operation.artifactName).toLowerCase();
2217
+ const escapedName = escapeRegExp(pluginName);
2218
+ const namedAbsent = [
2219
+ new RegExp(`${escapedName}.{0,120}\\b(not installed|not found|does not exist|absent)\\b`, "s"),
2220
+ new RegExp(`\\b(not installed|not found|does not exist|absent)\\b.{0,120}${escapedName}`, "s"),
2221
+ new RegExp(`\\bunknown plugin\\b.{0,120}${escapedName}`, "s"),
2222
+ new RegExp(`${escapedName}.{0,120}\\bunknown plugin\\b`, "s")
2223
+ ].some((pattern) => pattern.test(output));
2224
+ const genericAbsent = /\b(no such|unknown)\s+plugins?\b/.test(output) || /\bplugins?\b.{0,80}\bnot installed\b/s.test(output);
2225
+ return namedAbsent || genericAbsent;
2226
+ }
2227
+ function commandErrorOutput(error) {
2228
+ if (typeof error === "object" && error !== null) {
2229
+ const stderr = "stderr" in error ? String(error.stderr ?? "") : "";
2230
+ if (stderr.trim()) return stderr;
2231
+ const stdout = "stdout" in error ? String(error.stdout ?? "") : "";
2232
+ if (stdout.trim()) return stdout;
2233
+ }
2234
+ return error instanceof Error ? error.message : String(error);
2235
+ }
2236
+ function firstErrorLine(error) {
2237
+ return commandErrorOutput(error).split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "plugin is already absent";
2238
+ }
2239
+ function escapeRegExp(value) {
2240
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2241
+ }
2174
2242
  function semanticInstallCommands(operation) {
2175
2243
  if (operation.semanticPlugin) return operation.semanticPlugin.installCommands;
2176
2244
  return operation.semanticCommand ? [operation.semanticCommand] : [];
@@ -2416,7 +2484,7 @@ function codexAgentName(artifact) {
2416
2484
  }
2417
2485
  function validateCodexAgentToml(content, path) {
2418
2486
  for (const field of requiredCodexAgentFields) {
2419
- const pattern = new RegExp(`(^|\\n)\\s*${escapeRegExp(field)}\\s*=`, "m");
2487
+ const pattern = new RegExp(`(^|\\n)\\s*${escapeRegExp2(field)}\\s*=`, "m");
2420
2488
  if (!pattern.test(content)) {
2421
2489
  throw new Error(`Codex custom agent TOML ${path} is missing required field '${field}'.`);
2422
2490
  }
@@ -2462,7 +2530,7 @@ function tomlMultilineString(value) {
2462
2530
  ${escaped.endsWith("\n") ? escaped : `${escaped}
2463
2531
  `}"""`;
2464
2532
  }
2465
- function escapeRegExp(value) {
2533
+ function escapeRegExp2(value) {
2466
2534
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2467
2535
  }
2468
2536
 
@@ -2867,11 +2935,6 @@ async function semanticPluginSpecForArtifact(request) {
2867
2935
  return void 0;
2868
2936
  }
2869
2937
 
2870
- // src/validation/artifacts.ts
2871
- import { readFile as readFile15 } from "fs/promises";
2872
- import { basename as basename7, join as join15 } from "path";
2873
- import { parseDocument as parseDocument2 } from "yaml";
2874
-
2875
2938
  // src/model/selection.ts
2876
2939
  function artifactSelectorKey(artifact) {
2877
2940
  return `${artifact.type}/${artifact.name}`;
@@ -2922,7 +2985,51 @@ function subagentBaseName(name) {
2922
2985
  return name.replace(/\.agent\.md$/i, "").replace(/\.toml$/i, "").replace(/\.md$/i, "");
2923
2986
  }
2924
2987
 
2988
+ // src/validation/adapter-targets.ts
2989
+ function filterArtifactsByAdapterTargets(artifacts, adapter, installationType, options = {}) {
2990
+ const skipped = [];
2991
+ const installable = artifacts.filter((artifact) => {
2992
+ if (artifact.type === "fragments") return true;
2993
+ const support = adapterTargetSupport(adapter, artifact.type, installationType);
2994
+ if (support.ok) return true;
2995
+ const selector = artifactSelectorKey(artifact);
2996
+ skipped.push({ selector, artifactType: artifact.type, support });
2997
+ options.warn?.(skipWarning(selector, adapter, installationType, artifact.type, support));
2998
+ return false;
2999
+ });
3000
+ const before = artifacts.filter((artifact) => artifact.type !== "fragments");
3001
+ const after = installable.filter((artifact) => artifact.type !== "fragments");
3002
+ if (before.length > 0 && after.length === 0) {
3003
+ throw new Error(
3004
+ `${unsupportedSummary(adapter, installationType, skipped)} No installable artifacts remain for adapter ${adapter.name}/${installationType} after skipping unsupported targets: ${skipped.map((item) => item.selector).join(", ")}`
3005
+ );
3006
+ }
3007
+ return installable;
3008
+ }
3009
+ function unsupportedSummary(adapter, installationType, skipped) {
3010
+ const types = [...new Set(skipped.map((item) => item.artifactType))].sort((a, b) => a.localeCompare(b));
3011
+ if (types.length !== 1) {
3012
+ return `Adapter ${adapter.name} does not support selected artifact targets for installation type '${installationType}'.`;
3013
+ }
3014
+ const type = types[0];
3015
+ const supported = [...new Set(skipped.flatMap((item) => item.support.supportedInstallationTypes))].sort((a, b) => a.localeCompare(b));
3016
+ if (supported.length > 0) {
3017
+ return `Adapter ${adapter.name} does not support ${type} artifacts for installation type '${installationType}'. Supported: ${supported.join(", ")}.`;
3018
+ }
3019
+ return `Adapter ${adapter.name} does not support ${type} artifacts for any installation type.`;
3020
+ }
3021
+ function skipWarning(selector, adapter, installationType, artifactType, support) {
3022
+ if (support.reason === "adapter-target-disabled") {
3023
+ return `skip ${selector} (selected but adapter-target-disabled: ${adapter.name}/${installationType} disables ${artifactType})`;
3024
+ }
3025
+ const suffix = support.supportedInstallationTypes.length > 0 ? `; supported installation types: ${support.supportedInstallationTypes.join(", ")}` : "";
3026
+ return `skip ${selector} (selected but adapter-target-unsupported: ${adapter.name}/${installationType} has no enabled target for ${artifactType}${suffix})`;
3027
+ }
3028
+
2925
3029
  // src/validation/artifacts.ts
3030
+ import { readFile as readFile15 } from "fs/promises";
3031
+ import { basename as basename7, join as join15 } from "path";
3032
+ import { parseDocument as parseDocument2 } from "yaml";
2926
3033
  var behavioralRuleFormats = ["markdown-rule", "claude-markdown-rule", "copilot-instruction-rule"];
2927
3034
  var pluginFormats = ["claude-plugin", "codex-plugin", "hermes-plugin", "copilot-plugin", "openclaw-plugin", "openclaw-clawhub-plugin"];
2928
3035
  async function filterArtifactsByInstallFormat(artifacts, adapter, installationType, options = {}) {
@@ -3380,7 +3487,10 @@ function errorMessage(error) {
3380
3487
  // src/install/plan.ts
3381
3488
  async function createCombinedInstallPlan(desiredArtifacts, adapter, targetRoot, manifest, transport = localTransport, options = {}) {
3382
3489
  const requestedInstallationType = options.installationType ?? defaultInstallationType;
3383
- const installableArtifacts = await filterArtifactsByInstallFormat(desiredArtifacts, adapter, requestedInstallationType, { warn: options.warn });
3490
+ const formatCompatibleArtifacts = await filterArtifactsByInstallFormat(desiredArtifacts, adapter, requestedInstallationType, { warn: options.warn });
3491
+ const installableArtifacts = filterArtifactsByAdapterTargets(formatCompatibleArtifacts, adapter, requestedInstallationType, {
3492
+ warn: options.suppressAdapterTargetWarnings ? void 0 : options.warn
3493
+ });
3384
3494
  const installationType = resolveInstallationTypeForArtifacts(adapter, installableArtifacts.map((artifact) => artifact.type), requestedInstallationType);
3385
3495
  const installRoot = installRootForArtifacts(adapter, targetRoot, installationType, installableArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
3386
3496
  await validateArtifactsForInstall(installableArtifacts, adapter, installationType);
@@ -6299,6 +6409,8 @@ var workspaceTrustSchema = z6.object({
6299
6409
  }).default({});
6300
6410
  var workspaceAgentSchema = z6.object({
6301
6411
  adapter: z6.string().min(1),
6412
+ adapterConfig: z6.string().min(1).optional(),
6413
+ adapterModule: z6.string().min(1).optional(),
6302
6414
  root: z6.string().min(1),
6303
6415
  installationType: installationTypeSchema.optional(),
6304
6416
  transport: z6.enum(["local", "ssh"]).default("local"),
@@ -8354,7 +8466,12 @@ async function createGraphSourcePlan(options) {
8354
8466
  targetFingerprint,
8355
8467
  warn
8356
8468
  });
8357
- const desiredArtifacts = desiredArtifactsFromGraphBundle(bundle);
8469
+ const desiredArtifacts = filterArtifactsByAdapterTargets(
8470
+ desiredArtifactsFromGraphBundle(bundle),
8471
+ options.adapter,
8472
+ installationType,
8473
+ { warn }
8474
+ );
8358
8475
  const resolvedInstallationType = resolveInstallationTypeForArtifacts(options.adapter, desiredArtifacts.map((artifact) => artifact.type), installationType);
8359
8476
  const resolvedInstallRoot = installRootForArtifacts(options.adapter, options.targetRoot, resolvedInstallationType, desiredArtifacts.map((artifact) => artifact.type), transport.kind === "ssh");
8360
8477
  const graphLockDigest = digestGraphLock(bundle.graphLock);
@@ -8622,6 +8739,8 @@ function targetFromAgent(name, config, workspaceRoot, installationType) {
8622
8739
  agentName: name,
8623
8740
  targetKey: name,
8624
8741
  adapter: agent.adapter,
8742
+ adapterConfig: agent.adapterConfig,
8743
+ adapterModule: agent.adapterModule,
8625
8744
  installationType: installationType ?? agent.installationType,
8626
8745
  targetRoot: agent.transport === "ssh" ? agent.root : resolveConfigPath(agent.root, workspaceRoot),
8627
8746
  workspaceRoot,
@@ -9449,6 +9568,38 @@ program.command("status").description("show configured packages and runtime inst
9449
9568
  await printStatus(target, normalizedOptions);
9450
9569
  }
9451
9570
  });
9571
+ var journalCommand = program.command("journal").description("inspect or abort pending apply journals");
9572
+ journalCommand.command("list").description("show pending apply journals for resolved runtime targets").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
9573
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
9574
+ const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
9575
+ let pending = 0;
9576
+ for (const target of targets) {
9577
+ const state = await journalStateForTarget(target, normalizedOptions);
9578
+ const journal = await readApplyJournal(state.installRoot, state.adapter.name, state.transport, state.state);
9579
+ if (!journal) continue;
9580
+ pending += 1;
9581
+ console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
9582
+ console.log(` journal: ${join41(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
9583
+ console.log(` stateKey: ${state.state.stateKey}`);
9584
+ console.log(` createdAt: ${journal.createdAt}`);
9585
+ console.log(` updatedAt: ${journal.updatedAt}`);
9586
+ console.log(` operations: ${journal.operations.length}, completed: ${journal.completed.length}`);
9587
+ }
9588
+ if (pending === 0) console.log("No pending apply journals.");
9589
+ });
9590
+ journalCommand.command("abort").description("archive pending apply journals without touching runtime files").option("--adapter <adapter>", "built-in adapter or comma-separated adapters").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").option("--all", "run for every configured agent", false).option("--profile <name>", "workspace runtime profile").action(async (options) => {
9591
+ const normalizedOptions = normalizeRuntimeScopeOptions(options);
9592
+ const targets = await resolveCliTargets(normalizedOptions, { preferAllProfile: true });
9593
+ let aborted = 0;
9594
+ for (const target of targets) {
9595
+ const state = await journalStateForTarget(target, normalizedOptions);
9596
+ const result = await abortApplyJournal(state.installRoot, state.adapter.name, state.transport, state.state);
9597
+ if (!result) continue;
9598
+ aborted += 1;
9599
+ console.log(`Archived ${state.adapter.name}/${state.installationType} pending journal: ${result.archivePath}`);
9600
+ }
9601
+ if (aborted === 0) console.log("No pending apply journals.");
9602
+ });
9452
9603
  program.command("doctor").description("check agentwheel runtime setup and companion skill guidance").option("--adapter <adapter>", "built-in adapter").option("-i, --installation-type <type>", "installation type (for example local or user)").option("--user", "shortcut for --installation-type user and home-scoped state", false).option("--local", "shortcut for --installation-type local", false).option("-t, --target-root <path>", "runtime/project root").option("--agent <name>", "named agent from merged config").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("--skill <name>", "check a specific skill by name (repeatable or comma-separated)", collectSkillOption, []).option("--source <source>", "source to use in suggested install commands").option("--json", "print machine-readable doctor report", false).action(async (options) => {
9453
9604
  const normalizedOptions = normalizeRuntimeScopeOptions(options);
9454
9605
  const target = await resolveRuntimeTarget({
@@ -10141,6 +10292,14 @@ async function printStatus(target, options) {
10141
10292
  }
10142
10293
  await printPendingInstallWork(target, options);
10143
10294
  }
10295
+ async function journalStateForTarget(target, options) {
10296
+ const adapterOptions = adapterOptionsForTarget(target, options);
10297
+ const adapter = await resolveAdapterForTarget(target, adapterOptions);
10298
+ const transport = transportForTarget(target);
10299
+ const installationType = options.installationType ?? target.installationType ?? resolveInstallationTypeForAdapter(adapter);
10300
+ const state = installStateForTarget(target, adapter, adapterOptions, installationType);
10301
+ return { adapter, transport, installationType, installRoot: state.installRoot, state };
10302
+ }
10144
10303
  async function printPendingInstallWork(target, options) {
10145
10304
  let results = [];
10146
10305
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.14.7",
3
+ "version": "0.14.9",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",