Skip to content

Releases

v1.2.0 — emit-event

Choose a tag to compare

@crockalet crockalet released this 23 Sep 10:59
6af3b34

Adds emit-event, an agent tool that makes a realtime event arrive in the running app with no backend, no publish and no network round trip. An automated test can use it to drive the app through a realtime flow.

npx rozenite agent avasapp/ably call --tool emit-event --session <id> \
  --args '{"channel":"bid-orders","name":"ride_assignment","data":{"rideId":"r_42"}}'

It never publishes

It hands a fabricated Ably.Message straight to the listeners the app itself registered with subscribe(). A real publish would fan the message out to every other client attached to the channel, such as another developer's app or a real device, and it would need the network. Local delivery is safe on a shared channel, works offline, and completes deterministically inside a test.

That also limits what it can do: the app must already have subscribed. delivered: 0 says so, and a note explains whether the channel had no listener at all or every listener filtered the message out. It injects messages only, not presence.

What a call reports

  • delivered, skipped, failed and errors. A listener that throws is isolated, as ably-js isolates it, but it is reported rather than swallowed. The other listeners still receive the message.
  • Async listeners are awaited for up to 2s. A rejected promise is reported like a synchronous throw, and the app has finished reacting before you assert. A listener still running at 2s is counted in pending.
  • Filters apply as in Ably. Event-name and MessageFilter subscriptions are honoured, and a listener only receives what Ably would have given it.
  • It is not idempotent. Two calls deliver twice.

In the event stream

Injected events are recorded like any other, marked injected: true and with their summary prefixed. A listing never passes a synthetic event off as one Ably delivered. They are left out of get-stats and the channel's inbound counter, which describe only what Ably delivered. While capture is paused, delivery still happens but nothing is recorded, and the result says so.

Keep data JSON-safe. The listener receives the value you passed, but the recorded event stores a JSON copy. undefined fields vanish from the record and NaN/Infinity become null, with no warning. The bundled ably-devtools skill covers this and the rest of the tool's semantics.

The name is a contract

emit-event is this plugin's implementation of a general capability: deliver an inbound message the app never asked for. Rozenite's AgentToolTraits has no capability field, so a wrapper that treats plugins as interchangeable providers discovers them by probing each domain for a tool named exactly emit-event. The name is a discovery key and will not be renamed. channel, name and data are the generic arguments. An app's own event envelope travels inside data, and the tool never inspects its shape.

Node scripts on @rozenite/agent-sdk get a typed descriptor at ablyTools.emitEvent from @avasapp/rozenite-plugin-ably/sdk.

Upgrading

Nothing to change. Peer ranges are unchanged, and the plugin still requires Rozenite 2.2 or later.

Repo

Development dependencies moved to React 19.2.8, react-native 0.87.1 and lucide-react 1.x (the icon library the panel uses), and CI moved to actions/checkout and actions/setup-node v7.

Full changelog: v1.1.0...v1.2.0

v1.1.0 — Rozenite bridge as a peer dependency

Choose a tag to compare

@crockalet crockalet released this 26 Aug 11:10

Upgrading Rozenite no longer forces an upgrade of this plugin. The bridge packages moved from dependencies to peerDependencies, so the plugin shares whatever bridge your app already has instead of resolving its own.

Cause

@rozenite/plugin-bridge, @rozenite/agent-bridge and @rozenite/agent-shared stay external in the build — they are bare imports in dist/react-native/index.js, resolved from the consuming app. Declared as dependencies with a caret range, any version outside that range installed a second copy nested under the plugin:

your-app/node_modules/@rozenite/plugin-bridge          <- the host's bridge
your-app/node_modules/@avasapp/rozenite-plugin-ably/
  node_modules/@rozenite/plugin-bridge                 <- the plugin's own

Two bridge instances do not talk to each other. The panel connects to one and the SDK sends to the other, so the Ably tab stays empty with no error anywhere. The only fix was a release here that widened a range — which is why every Rozenite major dragged this plugin along with it.

As peers, there is exactly one instance, and a genuine mismatch surfaces as a loud install-time warning rather than a silent empty panel.

What this means for you

Requires Rozenite 2.2 or later. The plugin now ships no runtime dependencies at all.

@rozenite/plugin-bridge and @rozenite/agent-shared already arrive transitively via @rozenite/metro and rozenite, so nothing to do there. @rozenite/agent-bridge is not in a typical app's tree, but npm 7+, bun, and pnpm 8+ all auto-install missing peers — verified against fresh npm and bun installs. If your package manager does not, add it:

npm install --save-dev @rozenite/agent-bridge

Peer ranges track the major (^2.2.0) rather than being open-ended, because Rozenite pins its own packages to each other exactly — matching the major is what keeps the plugin on the same bridge the host runtime uses.

Panel

@rozenite/ui renamed the Button and Badge prop enums in 2.2, so the panel moves with it: size="compact" is now "sm", and the variant scale is surfaceTone's — default → solid, secondary → soft, ghost unchanged. No visual change intended.

Repo

Dependabot now opens a weekly grouped PR for the Rozenite line, gated by CI: typecheck, tests, build, and two checks specific to this setup.

check-peer-ranges catches the case Dependabot cannot handle on its own — it updates devDependencies but not peerDependencies, so a Rozenite major would otherwise land as a PR that builds green while still declaring the old peer range.

check-peer-resolution packs the real tarball into a fresh consumer install and asserts a single shared bridge instance. The example app cannot catch a duplicate bridge, because its metro.config.js puts the plugin's own node_modules on the resolver path.

Full changelog: v1.0.2...v1.1.0

v1.0.2 — skill discovery fix

Choose a tag to compare

@crockalet crockalet released this 18 Aug 05:16

Makes the bundled agent skill discoverable. npx skills add avas-app/rozenite-plugin-ably reported "No skills found" on 1.0.0 and 1.0.1.

Cause

The skill's description was an unquoted YAML plain scalar containing ": ":

description: ... Use for any realtime debugging question: a channel that is not attached, ...

YAML reads ": " inside a plain scalar as a nested mapping, so description never parsed as a string — and every validator that requires string name and description skipped the skill. Quoting it, and rewording away the colon, fixes discovery.

The skill directory also moves to skills/ably-devtools/ to match its frontmatter name. That was not the cause — the Vercel CLI keys off name, not the path — but a directory that disagrees with the skill inside it is a trap worth removing.

Why it went unnoticed

The skill ships in skills/ for consumers rather than .claude/skills/, so no agent had ever loaded it from this repo. Broken frontmatter is invisible until something tries to parse it, which is exactly what skills add did.

Install it with:

npx skills add avas-app/rozenite-plugin-ably

No code changes. Anyone on 1.0.1 who is not using the skill can stay there.

Full changelog: v1.0.1...v1.0.2

v1.0.1 — read-event payload cap

Choose a tag to compare

@crockalet crockalet released this 18 Aug 04:48

First release published to npm. Use this rather than v1.0.0, which was tagged but never published and returns oversized payloads from read-event.

Fix

Reading a single 322KB message returned a 477KB response — roughly 119k tokens, enough to swallow most of an agent's context in one call. Found by driving the example app against the live agent domain.

The serializer's 128KB cap clips raw but passes a parsed value through whole. That is right for the panel, which renders it as a lazily-expanded tree, but an agent has no such affordance — the whole structure lands in context at once. And because raw and value both carried the payload, the response came back larger than the message itself.

read-event now clips to maxBytes, defaulting to 8KB with a 128KB ceiling, and drops the redundant structured copy. byteLength still reports the payload's true size and a note says what was withheld:

{"kind":"json","truncated":true,"byteLength":329822,
 "note":"clipped to 8192 of 329822 bytes; raise maxBytes (max 131072) for more"}

The same message now returns 9.5KB. Serialization is untouched, so the panel still receives the full tree.

Also in this release

The example app moves to Expo 57 — React Native 0.86.2, React 19.2.3, TypeScript 6 — and @rozenite/metro 2.1.0, so it exercises the agent server the plugin registers against.

The whole tool surface was verified end-to-end against it: all nine tools on a real device, cursor pagination and field projection, channel-action changing actual channel state, and payload truncation.

Full changelog: v1.0.0...v1.0.1

v1.0.0 — Agent tools

Choose a tag to compare

@crockalet crockalet released this 18 Aug 04:12

Exposes the inspector to coding agents, and commits to the API.

Agent tools

The same session the panel reads is now a Rozenite agent domain, avasapp/ably, so an agent can inspect realtime traffic from the terminal with no DevTools window open:

npx rozenite agent session create
npx rozenite agent avasapp/ably call --tool list-channels \
  --args '{"onlyErrored":true}' --session <id>

Nine tools: get-connection, list-channels, read-channel, list-events, read-event, get-stats, set-options, clear, channel-action. Only the last three change anything, and only channel-action touches Ably itself rather than what is recorded.

Because the session already outlives the panel, an agent that attaches late still sees the history captured before it arrived — the same property that makes the panel survive a reload.

list-events deliberately omits payload bodies. A single message can carry 128KB, so a page of twenty would be unreadable; find the id in a listing, then read-event for the one that matters. Its search still matches decoded payload contents, so "which message carried this device id" works without dumping them.

Typed access from Node

Scripts built on @rozenite/agent-sdk can import descriptors from the new /sdk entry point instead of passing tool names as strings:

import { ablyTools } from '@avasapp/rozenite-plugin-ably/sdk'

const { items } = await session.callTool(ablyTools.listChannels, {
  onlyErrored: true,
})

Agent skill

Rozenite 2.1 serves its own agent docs from the CLI (npx rozenite skills list) and does not import skills from installed plugins, so this package ships one itself at skills/ably/SKILL.md.

It documents the semantics the tool output cannot convey — that inbound messages are only captured once the app has itself subscribed, so a channel showing counters but no messages is the design rather than a dropped message; that an outbound row means publish() was called, not that Ably accepted it; and that [Circular] and [Max depth reached] are the serializer's markers, not your data.

Requirements

Requires Rozenite 2.1 or later, up from 2.0 — the agent bridge landed in 2.1. Stay on 0.2.0 until you upgrade.

The plugin's own API is unchanged: useAblyDevTools(client, options) and every option behave exactly as before. Nothing needs to be rewritten to take this release.

On the version

1.0.0 rather than 0.3.0 because the surface is now complete enough to commit to — the panel, the hook, the agent domain, and the typed descriptors. 0.x was reserving a right to break these that is no longer wanted.

The version deliberately does not track Rozenite's. The first-party plugins match it because they ship from Rozenite's monorepo, not because the number carries meaning; compatibility here is expressed, and enforced, by the ^2.1.0 dependency ranges.

Full changelog: v0.2.0...v1.0.0

v0.2.0 — Rozenite 2.x

Choose a tag to compare

@crockalet crockalet released this 09 Aug 05:36

Moves the panel onto the Rozenite 2.x line.

Breaking

Requires Rozenite 2.x. @rozenite/plugin-bridge is now ^2.0.0, so a host app still on 1.x cannot use this release — stay on 0.1.1 until you upgrade Rozenite.

The plugin's own API is unchanged: useAblyDevTools(client, options) and every option behave exactly as before.

What changed

@rozenite/ui v2 was a redesign rather than a version bump — the components moved from a HeroUI-style API onto Base UI primitives, and the design tokens were replaced. The panel is rebuilt against both. Visually it stays the same three-pane inspector and still follows the DevTools light/dark theme; the status colours it needs beyond the shared palette (healthy / degraded / failed) are now defined locally.

Known regression

Search in the payload tree reveals a match by expanding to its depth, rather than by opening only the subtrees that contain it — so some non-matching siblings open along the way. JsonInspector dropped the per-node expansion predicate this relied on, and keeping the old behaviour would mean re-implementing the tree.

Full changelog: 4ac2529...v0.2.0