acp-devtools 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ The `acp-devtools` binary is one CLI with subcommands plus a bundled UI:
33
33
 
34
34
  Works with Zed, WebStorm, IntelliJ, PyCharm, Neovim, and Visual Studio via
35
35
  ReSharper — any editor that supports custom ACP servers — and with Claude
36
- Code, Codex, Goose, OpenCode, and 30+ other agents.
36
+ Code, Codex, Goose, OpenCode, and 40+ other agents.
37
37
 
38
38
  ## Install
39
39
 
package/dist/index.js CHANGED
@@ -7278,8 +7278,12 @@ function createAcpValidator(schema3) {
7278
7278
  const ajv = new Ajv2020({ strict: false, allErrors: true, validateFormats: false });
7279
7279
  ajv.addSchema(schema3, SCHEMA_ID);
7280
7280
  const methodIndex = /* @__PURE__ */ new Map();
7281
+ const softRequired = /* @__PURE__ */ new Map();
7281
7282
  const defs = schema3.$defs ?? {};
7282
7283
  for (const [name, def] of Object.entries(defs)) {
7284
+ const soon = Object.entries(def.properties ?? {}).filter(([, prop]) => SOON_REQUIRED_RE.test(prop.description ?? "")).map(([field]) => field);
7285
+ if (soon.length > 0)
7286
+ softRequired.set(name, soon);
7283
7287
  const method = def["x-method"];
7284
7288
  if (typeof method !== "string")
7285
7289
  continue;
@@ -7332,22 +7336,41 @@ function createAcpValidator(schema3) {
7332
7336
  }
7333
7337
  if (!defName)
7334
7338
  return { valid: true, errors: [], skipped: "no-schema-for-kind" };
7339
+ const advisories = collectAdvisories(defName, target);
7335
7340
  const validator2 = getValidator(defName);
7336
7341
  const ok = validator2(target);
7337
- if (ok)
7338
- return { valid: true, errors: [], schemaName: defName };
7339
- return {
7342
+ const base = ok ? { valid: true, errors: [], schemaName: defName } : {
7340
7343
  valid: false,
7341
7344
  errors: (validator2.errors ?? []).map(toError),
7342
7345
  schemaName: defName
7343
7346
  };
7347
+ if (advisories.length > 0)
7348
+ base.advisories = advisories;
7349
+ return base;
7350
+ }
7351
+ function collectAdvisories(defName, target) {
7352
+ const fields = softRequired.get(defName);
7353
+ if (!fields || typeof target !== "object" || target === null)
7354
+ return [];
7355
+ const obj = target;
7356
+ const out = [];
7357
+ for (const field of fields) {
7358
+ if (obj[field] === void 0 || obj[field] === null) {
7359
+ out.push({
7360
+ path: field,
7361
+ message: `missing ${field} \u2014 will be required in a future ACP version`,
7362
+ kind: "soon-required"
7363
+ });
7364
+ }
7365
+ }
7366
+ return out;
7344
7367
  }
7345
7368
  function knownAcpMethods2() {
7346
7369
  return [...methodIndex.keys()].sort();
7347
7370
  }
7348
7371
  return { validateAcpMessage: validateAcpMessage2, knownAcpMethods: knownAcpMethods2 };
7349
7372
  }
7350
- var import__, ajvModule, Ajv2020, SCHEMA_ID;
7373
+ var import__, ajvModule, Ajv2020, SCHEMA_ID, SOON_REQUIRED_RE;
7351
7374
  var init_validate_factory = __esm({
7352
7375
  "../core/dist/acp/validate-factory.js"() {
7353
7376
  "use strict";
@@ -7355,6 +7378,7 @@ var init_validate_factory = __esm({
7355
7378
  ajvModule = import__.default;
7356
7379
  Ajv2020 = ajvModule.default ?? ajvModule;
7357
7380
  SCHEMA_ID = "acp";
7381
+ SOON_REQUIRED_RE = /will be required/i;
7358
7382
  }
7359
7383
  });
7360
7384
 
@@ -8430,6 +8454,7 @@ function detectAgentShortcut(agentCommand) {
8430
8454
  const trimmed = agentCommand.trim();
8431
8455
  if (trimmed.length === 0)
8432
8456
  return null;
8457
+ const candidates = [trimmed, basenameFirstToken(trimmed)];
8433
8458
  for (const def of Object.values(AGENT_REGISTRY)) {
8434
8459
  const variants = [
8435
8460
  [def.command, ...def.args],
@@ -8437,14 +8462,23 @@ function detectAgentShortcut(agentCommand) {
8437
8462
  ];
8438
8463
  for (const variant of variants) {
8439
8464
  const exact = variant.join(" ");
8440
- if (trimmed === exact)
8441
- return def;
8442
- if (trimmed.startsWith(exact + " "))
8443
- return def;
8465
+ for (const candidate of candidates) {
8466
+ if (candidate === exact)
8467
+ return def;
8468
+ if (candidate.startsWith(exact + " "))
8469
+ return def;
8470
+ }
8444
8471
  }
8445
8472
  }
8446
8473
  return null;
8447
8474
  }
8475
+ function basenameFirstToken(command) {
8476
+ const spaceAt = command.indexOf(" ");
8477
+ const head = spaceAt === -1 ? command : command.slice(0, spaceAt);
8478
+ const rest = spaceAt === -1 ? "" : command.slice(spaceAt);
8479
+ const base = head.split(/[/\\]/).pop() ?? head;
8480
+ return base + rest;
8481
+ }
8448
8482
  var AGENT_REGISTRY, DEFAULT_AGENT;
8449
8483
  var init_registry = __esm({
8450
8484
  "../core/dist/agents/registry.js"() {
@@ -10853,7 +10887,7 @@ var CLI_VERSION;
10853
10887
  var init_version = __esm({
10854
10888
  "src/version.ts"() {
10855
10889
  "use strict";
10856
- CLI_VERSION = "0.3.0";
10890
+ CLI_VERSION = "0.5.0";
10857
10891
  }
10858
10892
  });
10859
10893
 
@@ -11668,7 +11702,7 @@ function buildMcpServer(opts) {
11668
11702
  server.registerTool(
11669
11703
  "find_spec_violations",
11670
11704
  {
11671
- description: "Validate every captured frame in a session against the ACP JSON schema (Draft 2020-12, ajv-compiled). Returns every offender with its seq, method, and per-error path + keyword + message. Schema-skipped frames (unknown methods, parse errors, generic envelopes) are not counted. Use the returned seq with get_message to inspect the offending raw payload. Start here when an agent is misbehaving but you don't yet know how.",
11705
+ description: "Validate every captured frame in a session against the ACP JSON schema (Draft 2020-12, ajv-compiled). Returns every offender with its seq, method, and per-error path + keyword + message. Schema-skipped frames (unknown methods, parse errors, generic envelopes) are not counted. Also returns `advisories`: frames valid under today's schema that omit a field the spec says will become mandatory (e.g. `agentInfo` on an initialize response, `clientInfo` on the request) \u2014 these never count as violations. Use the returned seq with get_message to inspect the offending raw payload. Start here when an agent is misbehaving but you don't yet know how.",
11672
11706
  inputSchema: {
11673
11707
  session_id: z.number().int().positive().describe("session id from list_sessions")
11674
11708
  },
@@ -11690,6 +11724,14 @@ function buildMcpServer(opts) {
11690
11724
  message: z.string().optional()
11691
11725
  }))
11692
11726
  }).loose()
11727
+ ),
11728
+ advisories: z.array(
11729
+ z.object({
11730
+ seq: z.number(),
11731
+ method: z.string().nullable(),
11732
+ path: z.string(),
11733
+ message: z.string()
11734
+ })
11693
11735
  )
11694
11736
  },
11695
11737
  annotations: annotations({ title: "Validate session against ACP schema" })
@@ -11700,6 +11742,7 @@ function buildMcpServer(opts) {
11700
11742
  const seqToMethod = /* @__PURE__ */ new Map();
11701
11743
  for (const m of messages) if (m.method) seqToMethod.set(m.seq, m.method);
11702
11744
  const violations = [];
11745
+ const advisories = [];
11703
11746
  let checked = 0;
11704
11747
  for (const m of messages) {
11705
11748
  const pair = pairs.get(m.seq);
@@ -11709,15 +11752,15 @@ function buildMcpServer(opts) {
11709
11752
  const result = validateAcpMessage(m, validationOpts);
11710
11753
  if (result.skipped) continue;
11711
11754
  checked += 1;
11755
+ const method = m.method ?? pairedMethod ?? null;
11712
11756
  if (!result.valid) {
11713
- violations.push({
11714
- seq: m.seq,
11715
- method: m.method ?? pairedMethod ?? null,
11716
- errors: result.errors
11717
- });
11757
+ violations.push({ seq: m.seq, method, errors: result.errors });
11758
+ }
11759
+ for (const a of result.advisories ?? []) {
11760
+ advisories.push({ seq: m.seq, method, path: a.path, message: a.message });
11718
11761
  }
11719
11762
  }
11720
- return structuredResult({ session_id, checked, violations });
11763
+ return structuredResult({ session_id, checked, violations, advisories });
11721
11764
  }
11722
11765
  );
11723
11766
  server.registerTool(
@@ -13597,6 +13640,7 @@ function registerValidateCommand(program) {
13597
13640
  }
13598
13641
  const methodNeedle = opts.method ? opts.method.toLowerCase() : null;
13599
13642
  const violations = [];
13643
+ const advisories = [];
13600
13644
  let checked = 0;
13601
13645
  let skipped = 0;
13602
13646
  for (const m of messages) {
@@ -13610,11 +13654,20 @@ function registerValidateCommand(program) {
13610
13654
  continue;
13611
13655
  }
13612
13656
  checked += 1;
13613
- if (result.valid) continue;
13614
13657
  const method = m.method ?? pairedMethod ?? null;
13615
- if (methodNeedle && (!method || !method.toLowerCase().includes(methodNeedle))) {
13616
- continue;
13658
+ const methodMatch = !methodNeedle || !!method && method.toLowerCase().includes(methodNeedle);
13659
+ if (methodMatch && result.advisories) {
13660
+ for (const a of result.advisories) {
13661
+ advisories.push({
13662
+ seq: m.seq,
13663
+ method,
13664
+ schemaName: result.schemaName ?? "?",
13665
+ path: a.path,
13666
+ message: a.message
13667
+ });
13668
+ }
13617
13669
  }
13670
+ if (result.valid || !methodMatch) continue;
13618
13671
  for (const e of result.errors) {
13619
13672
  if (violations.length >= limit) break;
13620
13673
  const v = {
@@ -13633,7 +13686,7 @@ function registerValidateCommand(program) {
13633
13686
  if (opts.json) {
13634
13687
  process.stdout.write(
13635
13688
  JSON.stringify(
13636
- { sessionId: id, checked, skipped, violations },
13689
+ { sessionId: id, checked, skipped, violations, advisories },
13637
13690
  null,
13638
13691
  2
13639
13692
  ) + "\n"
@@ -13644,18 +13697,25 @@ function registerValidateCommand(program) {
13644
13697
  const errStyler = createStyler(colorEnabled(process.stderr));
13645
13698
  const affected = new Set(violations.map((v) => v.method ?? "?"));
13646
13699
  const vCount = violations.length === 0 ? errStyler.green("0 violations") : errStyler.red(`${violations.length} violations`);
13700
+ const aCount = advisories.length === 0 ? "" : ` \xB7 ${errStyler.yellow(`${advisories.length} advisor${advisories.length === 1 ? "y" : "ies"}`)}`;
13647
13701
  process.stderr.write(
13648
- `${errStyler.bold(errStyler.cyan(`session #${id}`))} \xB7 ${checked} checked \xB7 ${skipped} skipped (no schema) \xB7 ${vCount} in ${affected.size} method${affected.size === 1 ? "" : "s"}
13702
+ `${errStyler.bold(errStyler.cyan(`session #${id}`))} \xB7 ${checked} checked \xB7 ${skipped} skipped (no schema) \xB7 ${vCount} in ${affected.size} method${affected.size === 1 ? "" : "s"}${aCount}
13649
13703
 
13650
13704
  `
13651
13705
  );
13652
- if (violations.length === 0) {
13706
+ const s = createStyler(colorEnabled(process.stdout));
13707
+ if (violations.length > 0) {
13708
+ process.stdout.write(renderViolations(s, violations));
13709
+ }
13710
+ if (advisories.length > 0) {
13711
+ if (violations.length > 0) process.stdout.write("\n");
13712
+ process.stdout.write(renderAdvisories(s, advisories));
13713
+ }
13714
+ if (violations.length === 0 && advisories.length === 0) {
13653
13715
  process.stdout.write(errStyler.green("no violations") + "\n");
13654
13716
  return;
13655
13717
  }
13656
- const s = createStyler(colorEnabled(process.stdout));
13657
- process.stdout.write(renderViolations(s, violations));
13658
- process.exit(1);
13718
+ if (violations.length > 0) process.exit(1);
13659
13719
  });
13660
13720
  }
13661
13721
  function renderViolations(s, violations) {
@@ -13668,11 +13728,22 @@ function renderViolations(s, violations) {
13668
13728
  ]);
13669
13729
  return renderTable(s, COLUMNS3, body);
13670
13730
  }
13731
+ function renderAdvisories(s, advisories) {
13732
+ const heading5 = s.yellow(`${advisories.length} advisor${advisories.length === 1 ? "y" : "ies"}`) + s.dim(" \u2014 valid today, but a future ACP version will require these fields") + "\n";
13733
+ const body = advisories.map((a) => [
13734
+ s.dim(`#${a.seq}`),
13735
+ truncate3(a.method ?? "\u2014", METHOD_MAX4),
13736
+ s.dim(truncate3(a.schemaName, SCHEMA_MAX)),
13737
+ s.yellow(truncate3(a.path, PATH_MAX)),
13738
+ s.yellow(a.message)
13739
+ ]);
13740
+ return heading5 + renderTable(s, ADVISORY_COLUMNS, body);
13741
+ }
13671
13742
  function truncate3(s, max) {
13672
13743
  if (s.length <= max) return s;
13673
13744
  return s.slice(0, Math.max(1, max - 1)) + "\u2026";
13674
13745
  }
13675
- var METHOD_MAX4, SCHEMA_MAX, PATH_MAX, COLUMNS3;
13746
+ var METHOD_MAX4, SCHEMA_MAX, PATH_MAX, COLUMNS3, ADVISORY_COLUMNS;
13676
13747
  var init_validate2 = __esm({
13677
13748
  "src/commands/validate.ts"() {
13678
13749
  "use strict";
@@ -13690,6 +13761,13 @@ var init_validate2 = __esm({
13690
13761
  { title: "PATH", align: "left" },
13691
13762
  { title: "ERROR", align: "left" }
13692
13763
  ];
13764
+ ADVISORY_COLUMNS = [
13765
+ { title: "SEQ", align: "right" },
13766
+ { title: "METHOD", align: "left" },
13767
+ { title: "SCHEMA", align: "left" },
13768
+ { title: "FIELD", align: "left" },
13769
+ { title: "ADVISORY", align: "left" }
13770
+ ];
13693
13771
  }
13694
13772
  });
13695
13773