Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/engine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

## [Unreleased - Patch]

### Added

- `observerAllowsEvent` and the `ObserverToken` type are exported from the package entry, so a realtime adapter hosted outside the engine can apply the same per-socket observer filter the Node adapter uses.

### Fixed

- The realtime `message.created` event includes the message `metadata` that `GET /v1/channels/:name/messages` already returns.
Expand Down
23 changes: 23 additions & 0 deletions packages/engine/src/__tests__/observerEventExport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from 'vitest';
import { observerAllowsEvent, type ObserverToken } from '../index.js';

// The Cloudflare workspace stream fans events out outside this package and
// imports this filter from the public entry; pin both the export and the
// behaviour it relies on for a channel-scoped token.
const scoped = {
scopes: ['stream:read', 'messages:read', 'dms:read', 'channels:read'],
filters: { channel_names: ['wf-run'], include_dms: false },
} as Pick<ObserverToken, 'scopes' | 'filters'>;

describe('observerAllowsEvent (public export)', () => {
it('passes the scoped channel and withholds DMs and other channels', () => {
expect(observerAllowsEvent(scoped, { type: 'message.created', channel: 'wf-run', message: { agent_id: 'a' } })).toBe(true);
expect(observerAllowsEvent(scoped, { type: 'message.created', channel: 'general', message: { agent_id: 'a' } })).toBe(false);
expect(observerAllowsEvent(scoped, { type: 'dm.received', conversation_id: 'dm_1' })).toBe(false);
expect(observerAllowsEvent(scoped, { type: 'group_dm.received' })).toBe(false);
});

it('lets an unscoped principal (workspace key) see everything', () => {
expect(observerAllowsEvent(undefined, { type: 'dm.received', conversation_id: 'dm_1' })).toBe(true);
});
});
5 changes: 5 additions & 0 deletions packages/engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export type {
TelemetryEvent,
} from './ports/index.js';

// The one filter that decides what a scoped observer token may see on the
// workspace stream. A realtime adapter that fans events out to observer sockets
// must apply it per socket — the Cloudflare stream lives outside this package.
export { observerAllowsEvent, type ObserverToken } from './engine/observerToken.js';

// OSS default providers (self-host). Cloud injects its own.
export { SqliteApiKeyAuthProvider, hashToken } from './auth/index.js';
export { StaticEntitlementsProvider, PLAN_LIMITS } from './providers/static-entitlements.js';
Expand Down
Loading