feat(sdk): idempotent registration, typed errors, onError hook, listener narrowing#1074
Conversation
…ner narrowing
Four backward-compatible ergonomics fixes in @agent-relay/sdk:
- relay.workspace.register() is idempotent by default via the relaycast
registerOrRotate API: re-registering an existing name adopts the
identity and rotates its token instead of throwing name_conflict.
Pass { strict: true } to restore fail-on-conflict.
- Re-export RelayError and RelayErrorCode from the package root and use
typed errors in the facade where a code applies (in-batch duplicate
register -> name_conflict, missing workspace key -> transport_error).
- Add an onError hook (constructor option or relay.onError(cb)) that
receives listener and action handler errors with a context naming the
listener selector or action name. Without a hook, handler errors log
a console warning instead of being silently swallowed.
- Add a RelayEventMap so exact dotted selectors narrow the handler event
type in addListener, plus relay.once(selector, handler) which
auto-unsubscribes after the first matching event. Wildcards and the
predicate-builder API keep their existing types.
Adds unit tests for register idempotency, onError routing, and once()
semantics, plus a vitest --typecheck suite (npm run test:types) for the
selector narrowing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI. |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (16)
📝 WalkthroughWalkthroughThe SDK adds structured error handling for listener and action handlers, introduces typed selector-based listener registration with auto-unsubscribe, makes workspace agent registration idempotent with token rotation, and publishes ChangesRelay SDK runtime and API behavior changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a53872fcb2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| this.relaycast.registerOrRotate | ||
| ? await this.relaycast.registerOrRotate(input) | ||
| : await this.relaycast.agents.register(input) |
There was a problem hiding this comment.
Call registerOrRotate on the agents surface
For real RelayCast instances the rotate API is exposed as relay.agents.registerOrRotate (the existing CLI uses that shape in packages/cli/src/cli/agent-relay-mcp.ts:706), but this adapter checks a top-level this.relaycast.registerOrRotate. In production that check is false, so relay.workspace.register(...) falls back to plain agents.register and still throws name_conflict instead of performing the new idempotent rotate path.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 6ee24d5: RelaycastMessagingClient now checks relaycast.agents.registerOrRotate and falls back to agents.register only when that method is unavailable. I also updated the messaging test to model the real agents.registerOrRotate surface.
Four backward-compatible ergonomics fixes in
packages/sdk.1. Idempotent agent registration
relay.workspace.register(name)no longer throwsname_conflicton every app restart. The facade now routes through the relaycastregisterOrRotateAPI: when the name already exists, the existing identity is adopted and its token rotated.register(agents, { strict: true })restores the old fail-on-conflict behavior.RelayMessagingClient.agents.registerOrRotateis optional; backends without it (and injected mocks) fall back to plainregister.2. Typed errors
RelayErrorandRelayErrorCode(8 coded errors +retryable) are re-exported from the@agent-relay/sdkroot.Errorthrows replaced where a sensible code exists: in-batch duplicate names inregister()→name_conflict;createWorkspaceresponse missing a workspace key →transport_error(retryable: false).Error: pure usage errors with no matching code (register() is only available on the workspace client, unresolvable agent/message references).relaycast-errors.ts: the typedcodepath (upstream PR fix(dashboard): enable mobile touch scrolling in log viewers #137) has shipped in@relaycast/sdk2.5.x and is the primary signal; the structural fallback is kept (and documented as such) because it still catches errors that crossed HTTP/MCP serialization boundaries and lost theirRelayErrorshape.3. Error visibility for handlers
onErrorhook:new AgentRelay({ onError })orrelay.onError(cb)(returns an unsubscribe). Invoked with(error, context)where context identifies the listener selector ({ source: 'listener', selector }) or action name ({ source: 'action', action, operation }).catch(() => {}); with no hook they now log aconsole.warnnaming the listener.register,connect,load_invocation,complete_invocation) route through the hook; default staysconsole.error.4. Typed listener narrowing +
onceRelayEventMap: exact dotted selectors narrow the handler parameter (addListener('message.created', e => ...)givesRelayMessageEvent<'message.created'>); wildcards ('*','message.*') and unknown strings keep the fullRelayEventunion.relay.once(selector, handler)auto-unsubscribes after the first match (works with predicates too).(e: RelayEvent) => voidhandlers continue to compile unchanged.Tests
npm test(packages/sdk): 9 files, 91 tests passed — includes new tests for register idempotency/strict/fallback/duplicatename_conflict,onErrorinvocation (sync + async, constructor + method, hook isolation), default-warn behavior,once()semantics, action-wiringonErrorrouting, registerOrRotate delegation in the relaycast client, and the typedcreateWorkspaceerror.npm run test:types(new): vitest--typecheckoverlisteners.test-d.ts— 5 type tests, no errors; verified non-vacuous (a deliberately wrong assertion fails the run). Uses a dedicatedtsconfig.typetest.jsonbecause the package tsconfig excludes__tests__.npm run checkandnpm run build: clean.🤖 Generated with Claude Code