From cc44aad537456b691a1d8a20cc2b146b177b184e Mon Sep 17 00:00:00 2001 From: ScriptedAlchemy Date: Thu, 3 Sep 2026 04:18:59 +0000 Subject: [PATCH] feat(adapters): advertise notice delivery routes per host (#99 stage 4) --- .changeset/notice-delivery-advertisement.md | 5 +++ .../adapters/capabilities/claude-2.1.250.json | 22 +++++++++++++ .../adapters/capabilities/codex-0.147.0.json | 22 +++++++++++++ .../capabilities/cursor-2026-08-28.json | 22 +++++++++++++ .../adapters/capabilities/portable-1.0.0.json | 24 ++++++++++++++ .../tests/adapter-capability-states.test.ts | 33 +++++++++++++++++++ .../tests/adapter-metadata.test.ts | 4 +-- 7 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 .changeset/notice-delivery-advertisement.md diff --git a/.changeset/notice-delivery-advertisement.md b/.changeset/notice-delivery-advertisement.md new file mode 100644 index 000000000..5e208083e --- /dev/null +++ b/.changeset/notice-delivery-advertisement.md @@ -0,0 +1,5 @@ +--- +"agent-bundle": patch +--- + +Advertise #99 notice-delivery routes per pinned host in a new `noticeDelivery` capability-table section consumed by the runtime's delivery-route selector: `mcp-inbox` and `mcp-resource-updated` are supported on every target, `next-event` and `current-response` on the three hook-bearing hosts (honestly unavailable on hookless portable), and `directed-push`/`host-toast` carry dated unavailable rows because no pinned host documents a plugin-initiated directed message or toast API (2026-09-02 survey on #99). A pin test enforces the route set, dated reasons, and the per-host truths. diff --git a/packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.json b/packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.json index 0a50ce851..bf4dc7d20 100644 --- a/packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.json +++ b/packages/agent-bundle/src/adapters/capabilities/claude-2.1.250.json @@ -120,6 +120,28 @@ "stdio": true, "streamableHttp": true }, + "noticeDelivery": { + "current-response": { + "state": "supported" + }, + "directed-push": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-initiated directed cross-actor message API at this pin; the Claude Notification hook observes host-emitted notifications and does not send them.", + "state": "unavailable" + }, + "host-toast": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-facing toast/notification send API at this pin.", + "state": "unavailable" + }, + "mcp-inbox": { + "state": "supported" + }, + "mcp-resource-updated": { + "state": "supported" + }, + "next-event": { + "state": "supported" + } + }, "observedCliVersion": "2.1.250", "plugin": { "agents": { diff --git a/packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json b/packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json index 5d1c90e30..261fb83c7 100644 --- a/packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json +++ b/packages/agent-bundle/src/adapters/capabilities/codex-0.147.0.json @@ -87,6 +87,28 @@ "stdio": true, "streamableHttp": true }, + "noticeDelivery": { + "current-response": { + "state": "supported" + }, + "directed-push": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-initiated directed cross-actor message API at this pin; the Claude Notification hook observes host-emitted notifications and does not send them.", + "state": "unavailable" + }, + "host-toast": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-facing toast/notification send API at this pin.", + "state": "unavailable" + }, + "mcp-inbox": { + "state": "supported" + }, + "mcp-resource-updated": { + "state": "supported" + }, + "next-event": { + "state": "supported" + } + }, "observedCliVersion": "0.147.0", "plugin": { "apps": { diff --git a/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json b/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json index 12fbf7727..f63fb40cc 100644 --- a/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json +++ b/packages/agent-bundle/src/adapters/capabilities/cursor-2026-08-28.json @@ -157,6 +157,28 @@ "stdio": true, "streamableHttp": true }, + "noticeDelivery": { + "current-response": { + "state": "supported" + }, + "directed-push": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-initiated directed cross-actor message API at this pin; the Claude Notification hook observes host-emitted notifications and does not send them.", + "state": "unavailable" + }, + "host-toast": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-facing toast/notification send API at this pin.", + "state": "unavailable" + }, + "mcp-inbox": { + "state": "supported" + }, + "mcp-resource-updated": { + "state": "supported" + }, + "next-event": { + "state": "supported" + } + }, "observedCliVersion": "2026-08-28", "plugin": { "commands": true, diff --git a/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json b/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json index ecc29fea9..7a819741b 100644 --- a/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json +++ b/packages/agent-bundle/src/adapters/capabilities/portable-1.0.0.json @@ -94,6 +94,30 @@ "stdio": true, "streamableHttp": true }, + "noticeDelivery": { + "current-response": { + "reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks, so there is no synchronous hook response surface (https://agent-plugins.org/).", + "state": "unavailable" + }, + "directed-push": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-initiated directed cross-actor message API at this pin; the Claude Notification hook observes host-emitted notifications and does not send them.", + "state": "unavailable" + }, + "host-toast": { + "reason": "retrieved 2026-09-02 (#99 survey): no documented plugin-facing toast/notification send API at this pin.", + "state": "unavailable" + }, + "mcp-inbox": { + "state": "supported" + }, + "mcp-resource-updated": { + "state": "supported" + }, + "next-event": { + "reason": "2026-09-02: Agent Plugins 1.0.0 defines no hooks, so no event admission surface exists (https://agent-plugins.org/).", + "state": "unavailable" + } + }, "observedSpecificationVersion": "1.0.0", "plugin": { "manifest": "plugin.json", diff --git a/packages/agent-bundle/tests/adapter-capability-states.test.ts b/packages/agent-bundle/tests/adapter-capability-states.test.ts index bba20514c..7dfc07421 100644 --- a/packages/agent-bundle/tests/adapter-capability-states.test.ts +++ b/packages/agent-bundle/tests/adapter-capability-states.test.ts @@ -1052,3 +1052,36 @@ it('pins dated deferral rows for every explicitly deferred native callback from } } }); + +it('advertises notice delivery routes per host with dated unavailability (#99 stage 4)', async () => { + const { readFile } = await import('node:fs/promises'); + const routes = ['current-response', 'directed-push', 'host-toast', 'mcp-inbox', 'mcp-resource-updated', 'next-event']; + const files = { + claude: 'claude-2.1.250.json', + codex: 'codex-0.147.0.json', + cursor: 'cursor-2026-08-28.json', + portable: 'portable-1.0.0.json', + }; + for (const [host, file] of Object.entries(files)) { + const table = JSON.parse(await readFile(new URL(`../src/adapters/capabilities/${file}`, import.meta.url), 'utf8')) as Record; + const advertisement = table.noticeDelivery as Record; + expect(Object.keys(advertisement).sort()).toEqual(routes); + for (const route of routes) { + const entry = advertisement[route]!; + expect(['supported', 'unavailable']).toContain(entry.state); + if (entry.state === 'unavailable') expect(entry.reason).toMatch(/2026-09-02/u); + } + // No pinned host has a directed cross-actor push or toast surface (#99 survey). + expect(advertisement['directed-push']!.state).toBe('unavailable'); + expect(advertisement['host-toast']!.state).toBe('unavailable'); + // The generated MCP inbox surface exists on every target. + expect(advertisement['mcp-inbox']!.state).toBe('supported'); + // Hookless portable honestly loses the hook-borne routes. + if (host === 'portable') { + expect(advertisement['next-event']!.state).toBe('unavailable'); + expect(advertisement['current-response']!.state).toBe('unavailable'); + } else { + expect(advertisement['next-event']!.state).toBe('supported'); + } + } +}); diff --git a/packages/agent-bundle/tests/adapter-metadata.test.ts b/packages/agent-bundle/tests/adapter-metadata.test.ts index c435973b0..46f0fcf93 100644 --- a/packages/agent-bundle/tests/adapter-metadata.test.ts +++ b/packages/agent-bundle/tests/adapter-metadata.test.ts @@ -210,10 +210,10 @@ it('records observed capability versions and rehashes schema snapshots against p } if (target === 'claude') { - expect(sha256Hex(capability)).toBe('98938d9685411227acee422f90d2e5bb51f33da96ba499aa2ad7b5d3422b37f1'); + expect(sha256Hex(capability)).toBe('ad8913270725c3f4abbfbf12bac888092e271cecc9c64adaa114093c9e2e70de'); } if (target === 'cursor') { - expect(sha256Hex(capability)).toBe('e36e84f829f1c69562647a4bc26ecfbaea2dbd0559f194ed7f52bbf133688fe5'); + expect(sha256Hex(capability)).toBe('d056a5575fb98408e12e9236af356227911aec657f226eb720ff9b6a120c5819'); const pluginSchema = JSON.parse(await readFile( new URL('../src/adapters/schemas/cursor/plugin.schema.json', import.meta.url), 'utf8',