agent-relay 2.3.4 → 2.3.5
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 +1 -1
- package/dist/src/cli/index.js +124 -7
- package/dist/src/cli/index.js.map +1 -1
- package/package.json +20 -26
- package/packages/acp-bridge/package.json +2 -2
- package/packages/bridge/package.json +7 -7
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +2 -2
- package/packages/daemon/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/package.json +5 -5
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/dist/index.d.ts +1 -29
- package/packages/sdk/dist/index.d.ts.map +1 -1
- package/packages/sdk/dist/index.js +1 -38
- package/packages/sdk/dist/index.js.map +1 -1
- package/packages/sdk/package.json +4 -25
- package/packages/sdk/src/index.ts +1 -69
- package/packages/sdk-py/README.md +56 -0
- package/packages/sdk-py/pyproject.toml +23 -0
- package/packages/sdk-py/src/agent_relay/__init__.py +27 -0
- package/packages/sdk-py/src/agent_relay/builder.py +367 -0
- package/packages/sdk-py/src/agent_relay/types.py +92 -0
- package/packages/sdk-py/tests/__init__.py +0 -0
- package/packages/sdk-py/tests/test_builder.py +101 -0
- package/packages/sdk-ts/dist/index.d.ts +1 -0
- package/packages/sdk-ts/dist/index.d.ts.map +1 -1
- package/packages/sdk-ts/dist/index.js +1 -0
- package/packages/sdk-ts/dist/index.js.map +1 -1
- package/packages/sdk-ts/dist/workflows/barrier.d.ts +72 -0
- package/packages/sdk-ts/dist/workflows/barrier.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/barrier.js +162 -0
- package/packages/sdk-ts/dist/workflows/barrier.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/builder.d.ts +101 -0
- package/packages/sdk-ts/dist/workflows/builder.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/builder.js +179 -0
- package/packages/sdk-ts/dist/workflows/builder.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/cli.d.ts +10 -0
- package/packages/sdk-ts/dist/workflows/cli.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/cli.js +82 -0
- package/packages/sdk-ts/dist/workflows/cli.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/coordinator.d.ts +68 -0
- package/packages/sdk-ts/dist/workflows/coordinator.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/coordinator.js +353 -0
- package/packages/sdk-ts/dist/workflows/coordinator.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/index.d.ts +10 -0
- package/packages/sdk-ts/dist/workflows/index.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/index.js +10 -0
- package/packages/sdk-ts/dist/workflows/index.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/memory-db.d.ts +17 -0
- package/packages/sdk-ts/dist/workflows/memory-db.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/memory-db.js +33 -0
- package/packages/sdk-ts/dist/workflows/memory-db.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/run.d.ts +31 -0
- package/packages/sdk-ts/dist/workflows/run.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/run.js +24 -0
- package/packages/sdk-ts/dist/workflows/run.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/runner.d.ts +119 -0
- package/packages/sdk-ts/dist/workflows/runner.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/runner.js +650 -0
- package/packages/sdk-ts/dist/workflows/runner.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/state.d.ts +77 -0
- package/packages/sdk-ts/dist/workflows/state.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/state.js +140 -0
- package/packages/sdk-ts/dist/workflows/state.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/templates.d.ts +47 -0
- package/packages/sdk-ts/dist/workflows/templates.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/templates.js +395 -0
- package/packages/sdk-ts/dist/workflows/templates.js.map +1 -0
- package/packages/sdk-ts/dist/workflows/types.d.ts +126 -0
- package/packages/sdk-ts/dist/workflows/types.d.ts.map +1 -0
- package/packages/sdk-ts/dist/workflows/types.js +8 -0
- package/packages/sdk-ts/dist/workflows/types.js.map +1 -0
- package/packages/sdk-ts/package.json +8 -2
- package/packages/sdk-ts/src/__tests__/error-scenarios.test.ts +682 -0
- package/packages/sdk-ts/src/__tests__/swarm-coordinator.test.ts +416 -0
- package/packages/sdk-ts/src/__tests__/workflow-runner.test.ts +333 -0
- package/packages/sdk-ts/src/index.ts +1 -0
- package/packages/sdk-ts/src/workflows/README.md +450 -0
- package/packages/sdk-ts/src/workflows/barrier.ts +254 -0
- package/packages/sdk-ts/src/workflows/builder.ts +241 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/bug-fix.yaml +75 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/code-review.yaml +82 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/documentation.yaml +70 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/feature-dev.yaml +76 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/refactor.yaml +82 -0
- package/packages/sdk-ts/src/workflows/builtin-templates/security-audit.yaml +84 -0
- package/packages/sdk-ts/src/workflows/cli.ts +93 -0
- package/packages/sdk-ts/src/workflows/coordinator.ts +520 -0
- package/packages/sdk-ts/src/workflows/index.ts +9 -0
- package/packages/sdk-ts/src/workflows/memory-db.ts +39 -0
- package/packages/sdk-ts/src/workflows/run.ts +47 -0
- package/packages/sdk-ts/src/workflows/runner.ts +873 -0
- package/packages/sdk-ts/src/workflows/schema.json +321 -0
- package/packages/sdk-ts/src/workflows/state.ts +279 -0
- package/packages/sdk-ts/src/workflows/templates.ts +544 -0
- package/packages/sdk-ts/src/workflows/types.ts +178 -0
- package/packages/sdk-ts/tsconfig.json +6 -1
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +3 -3
- package/packages/wrapper/package.json +5 -6
- package/packages/api-types/.trajectories/active/traj_xbsvuzogscey.json +0 -15
- package/packages/api-types/.trajectories/index.json +0 -12
- package/packages/api-types/dist/index.d.ts +0 -21
- package/packages/api-types/dist/index.d.ts.map +0 -1
- package/packages/api-types/dist/index.js +0 -22
- package/packages/api-types/dist/index.js.map +0 -1
- package/packages/api-types/dist/schemas/agent.d.ts +0 -259
- package/packages/api-types/dist/schemas/agent.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/agent.js +0 -102
- package/packages/api-types/dist/schemas/agent.js.map +0 -1
- package/packages/api-types/dist/schemas/api.d.ts +0 -290
- package/packages/api-types/dist/schemas/api.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/api.js +0 -162
- package/packages/api-types/dist/schemas/api.js.map +0 -1
- package/packages/api-types/dist/schemas/decision.d.ts +0 -230
- package/packages/api-types/dist/schemas/decision.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/decision.js +0 -104
- package/packages/api-types/dist/schemas/decision.js.map +0 -1
- package/packages/api-types/dist/schemas/fleet.d.ts +0 -615
- package/packages/api-types/dist/schemas/fleet.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/fleet.js +0 -71
- package/packages/api-types/dist/schemas/fleet.js.map +0 -1
- package/packages/api-types/dist/schemas/history.d.ts +0 -180
- package/packages/api-types/dist/schemas/history.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/history.js +0 -72
- package/packages/api-types/dist/schemas/history.js.map +0 -1
- package/packages/api-types/dist/schemas/index.d.ts +0 -14
- package/packages/api-types/dist/schemas/index.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/index.js +0 -22
- package/packages/api-types/dist/schemas/index.js.map +0 -1
- package/packages/api-types/dist/schemas/message.d.ts +0 -456
- package/packages/api-types/dist/schemas/message.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/message.js +0 -88
- package/packages/api-types/dist/schemas/message.js.map +0 -1
- package/packages/api-types/dist/schemas/session.d.ts +0 -60
- package/packages/api-types/dist/schemas/session.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/session.js +0 -36
- package/packages/api-types/dist/schemas/session.js.map +0 -1
- package/packages/api-types/dist/schemas/task.d.ts +0 -111
- package/packages/api-types/dist/schemas/task.d.ts.map +0 -1
- package/packages/api-types/dist/schemas/task.js +0 -64
- package/packages/api-types/dist/schemas/task.js.map +0 -1
- package/packages/api-types/package.json +0 -61
- package/packages/api-types/scripts/generate-openapi.ts +0 -106
- package/packages/api-types/src/index.ts +0 -22
- package/packages/api-types/src/schemas/agent.test.ts +0 -164
- package/packages/api-types/src/schemas/agent.ts +0 -110
- package/packages/api-types/src/schemas/api.test.ts +0 -372
- package/packages/api-types/src/schemas/api.ts +0 -194
- package/packages/api-types/src/schemas/decision.test.ts +0 -324
- package/packages/api-types/src/schemas/decision.ts +0 -136
- package/packages/api-types/src/schemas/fleet.test.ts +0 -212
- package/packages/api-types/src/schemas/fleet.ts +0 -83
- package/packages/api-types/src/schemas/history.test.ts +0 -242
- package/packages/api-types/src/schemas/history.ts +0 -84
- package/packages/api-types/src/schemas/index.ts +0 -148
- package/packages/api-types/src/schemas/message.test.ts +0 -192
- package/packages/api-types/src/schemas/message.ts +0 -98
- package/packages/api-types/src/schemas/session.test.ts +0 -104
- package/packages/api-types/src/schemas/session.ts +0 -40
- package/packages/api-types/src/schemas/task.test.ts +0 -192
- package/packages/api-types/src/schemas/task.ts +0 -78
- package/packages/api-types/tsconfig.json +0 -19
- package/packages/api-types/vitest.config.ts +0 -9
- package/packages/benchmark/README.md +0 -200
- package/packages/benchmark/datasets/coding-tasks.yaml +0 -127
- package/packages/benchmark/datasets/coordination-tasks.yaml +0 -122
- package/packages/benchmark/datasets/quick-test.yaml +0 -20
- package/packages/benchmark/dist/benchmark.d.ts +0 -47
- package/packages/benchmark/dist/benchmark.d.ts.map +0 -1
- package/packages/benchmark/dist/benchmark.js +0 -224
- package/packages/benchmark/dist/benchmark.js.map +0 -1
- package/packages/benchmark/dist/cli.d.ts +0 -8
- package/packages/benchmark/dist/cli.d.ts.map +0 -1
- package/packages/benchmark/dist/cli.js +0 -185
- package/packages/benchmark/dist/cli.js.map +0 -1
- package/packages/benchmark/dist/harbor.d.ts +0 -53
- package/packages/benchmark/dist/harbor.d.ts.map +0 -1
- package/packages/benchmark/dist/harbor.js +0 -127
- package/packages/benchmark/dist/harbor.js.map +0 -1
- package/packages/benchmark/dist/index.d.ts +0 -48
- package/packages/benchmark/dist/index.d.ts.map +0 -1
- package/packages/benchmark/dist/index.js +0 -50
- package/packages/benchmark/dist/index.js.map +0 -1
- package/packages/benchmark/dist/runners/base.d.ts +0 -63
- package/packages/benchmark/dist/runners/base.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/base.js +0 -156
- package/packages/benchmark/dist/runners/base.js.map +0 -1
- package/packages/benchmark/dist/runners/index.d.ts +0 -10
- package/packages/benchmark/dist/runners/index.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/index.js +0 -10
- package/packages/benchmark/dist/runners/index.js.map +0 -1
- package/packages/benchmark/dist/runners/single.d.ts +0 -19
- package/packages/benchmark/dist/runners/single.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/single.js +0 -111
- package/packages/benchmark/dist/runners/single.js.map +0 -1
- package/packages/benchmark/dist/runners/subagent.d.ts +0 -32
- package/packages/benchmark/dist/runners/subagent.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/subagent.js +0 -212
- package/packages/benchmark/dist/runners/subagent.js.map +0 -1
- package/packages/benchmark/dist/runners/swarm.d.ts +0 -36
- package/packages/benchmark/dist/runners/swarm.d.ts.map +0 -1
- package/packages/benchmark/dist/runners/swarm.js +0 -273
- package/packages/benchmark/dist/runners/swarm.js.map +0 -1
- package/packages/benchmark/dist/types.d.ts +0 -178
- package/packages/benchmark/dist/types.d.ts.map +0 -1
- package/packages/benchmark/dist/types.js +0 -16
- package/packages/benchmark/dist/types.js.map +0 -1
- package/packages/benchmark/package.json +0 -80
- package/packages/benchmark/src/benchmark.ts +0 -298
- package/packages/benchmark/src/cli.ts +0 -240
- package/packages/benchmark/src/harbor.ts +0 -170
- package/packages/benchmark/src/index.ts +0 -73
- package/packages/benchmark/src/runners/base.ts +0 -205
- package/packages/benchmark/src/runners/index.ts +0 -10
- package/packages/benchmark/src/runners/single.ts +0 -121
- package/packages/benchmark/src/runners/subagent.ts +0 -240
- package/packages/benchmark/src/runners/swarm.ts +0 -326
- package/packages/benchmark/src/types.ts +0 -205
- package/packages/benchmark/tsconfig.json +0 -20
- package/packages/cli-tester/README.md +0 -277
- package/packages/cli-tester/dist/index.d.ts +0 -21
- package/packages/cli-tester/dist/index.d.ts.map +0 -1
- package/packages/cli-tester/dist/index.js +0 -21
- package/packages/cli-tester/dist/index.js.map +0 -1
- package/packages/cli-tester/dist/utils/credential-check.d.ts +0 -56
- package/packages/cli-tester/dist/utils/credential-check.d.ts.map +0 -1
- package/packages/cli-tester/dist/utils/credential-check.js +0 -230
- package/packages/cli-tester/dist/utils/credential-check.js.map +0 -1
- package/packages/cli-tester/dist/utils/socket-client.d.ts +0 -76
- package/packages/cli-tester/dist/utils/socket-client.d.ts.map +0 -1
- package/packages/cli-tester/dist/utils/socket-client.js +0 -153
- package/packages/cli-tester/dist/utils/socket-client.js.map +0 -1
- package/packages/cli-tester/docker/Dockerfile +0 -61
- package/packages/cli-tester/docker/docker-compose.yml +0 -71
- package/packages/cli-tester/docker/entrypoint.sh +0 -58
- package/packages/cli-tester/package.json +0 -32
- package/packages/cli-tester/scripts/clear-auth.sh +0 -101
- package/packages/cli-tester/scripts/inject-message.sh +0 -42
- package/packages/cli-tester/scripts/start.sh +0 -71
- package/packages/cli-tester/scripts/test-cli.sh +0 -56
- package/packages/cli-tester/scripts/test-full-spawn.sh +0 -238
- package/packages/cli-tester/scripts/test-registration.sh +0 -182
- package/packages/cli-tester/scripts/test-setup-flow.sh +0 -202
- package/packages/cli-tester/scripts/test-spawn.sh +0 -140
- package/packages/cli-tester/scripts/test-with-daemon.sh +0 -247
- package/packages/cli-tester/scripts/verify-auth.sh +0 -112
- package/packages/cli-tester/src/index.ts +0 -40
- package/packages/cli-tester/src/utils/credential-check.ts +0 -284
- package/packages/cli-tester/src/utils/socket-client.ts +0 -211
- package/packages/cli-tester/tests/credential-check.test.ts +0 -56
- package/packages/cli-tester/tsconfig.json +0 -11
- package/packages/sdk/dist/browser-client.d.ts +0 -212
- package/packages/sdk/dist/browser-client.d.ts.map +0 -1
- package/packages/sdk/dist/browser-client.js +0 -750
- package/packages/sdk/dist/browser-client.js.map +0 -1
- package/packages/sdk/dist/browser-framing.d.ts +0 -46
- package/packages/sdk/dist/browser-framing.d.ts.map +0 -1
- package/packages/sdk/dist/browser-framing.js +0 -122
- package/packages/sdk/dist/browser-framing.js.map +0 -1
- package/packages/sdk/dist/standalone.d.ts +0 -89
- package/packages/sdk/dist/standalone.d.ts.map +0 -1
- package/packages/sdk/dist/standalone.js +0 -131
- package/packages/sdk/dist/standalone.js.map +0 -1
- package/packages/sdk/dist/transports/index.d.ts +0 -92
- package/packages/sdk/dist/transports/index.d.ts.map +0 -1
- package/packages/sdk/dist/transports/index.js +0 -129
- package/packages/sdk/dist/transports/index.js.map +0 -1
- package/packages/sdk/dist/transports/socket-transport.d.ts +0 -30
- package/packages/sdk/dist/transports/socket-transport.d.ts.map +0 -1
- package/packages/sdk/dist/transports/socket-transport.js +0 -94
- package/packages/sdk/dist/transports/socket-transport.js.map +0 -1
- package/packages/sdk/dist/transports/types.d.ts +0 -69
- package/packages/sdk/dist/transports/types.d.ts.map +0 -1
- package/packages/sdk/dist/transports/types.js +0 -10
- package/packages/sdk/dist/transports/types.js.map +0 -1
- package/packages/sdk/dist/transports/websocket-transport.d.ts +0 -55
- package/packages/sdk/dist/transports/websocket-transport.d.ts.map +0 -1
- package/packages/sdk/dist/transports/websocket-transport.js +0 -180
- package/packages/sdk/dist/transports/websocket-transport.js.map +0 -1
- package/packages/sdk/src/browser-client.ts +0 -985
- package/packages/sdk/src/browser-framing.test.ts +0 -115
- package/packages/sdk/src/browser-framing.ts +0 -150
- package/packages/sdk/src/standalone.ts +0 -183
- package/packages/sdk/src/transports/index.ts +0 -197
- package/packages/sdk/src/transports/socket-transport.ts +0 -115
- package/packages/sdk/src/transports/types.ts +0 -77
- package/packages/sdk/src/transports/websocket-transport.ts +0 -245
|
@@ -1,985 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BrowserRelayClient - Browser-compatible Agent Relay SDK Client
|
|
3
|
-
* @agent-relay/sdk
|
|
4
|
-
*
|
|
5
|
-
* A client designed for browser environments using WebSocket transport.
|
|
6
|
-
* Can also be used in Node.js with the WebSocket transport.
|
|
7
|
-
*
|
|
8
|
-
* Key differences from RelayClient:
|
|
9
|
-
* - Uses transport abstraction instead of direct socket access
|
|
10
|
-
* - No Node.js-specific dependencies (node:net, node:crypto)
|
|
11
|
-
* - Uses browser-compatible APIs (crypto.randomUUID, etc.)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { Transport } from './transports/types.js';
|
|
15
|
-
import {
|
|
16
|
-
createAutoTransport,
|
|
17
|
-
type AutoTransportOptions,
|
|
18
|
-
} from './transports/index.js';
|
|
19
|
-
import {
|
|
20
|
-
type Envelope,
|
|
21
|
-
type HelloPayload,
|
|
22
|
-
type WelcomePayload,
|
|
23
|
-
type SendPayload,
|
|
24
|
-
type SendMeta,
|
|
25
|
-
type SendEnvelope,
|
|
26
|
-
type DeliverEnvelope,
|
|
27
|
-
type AckPayload,
|
|
28
|
-
type ErrorPayload,
|
|
29
|
-
type PayloadKind,
|
|
30
|
-
type LogPayload,
|
|
31
|
-
type SpeakOnTrigger,
|
|
32
|
-
type EntityType,
|
|
33
|
-
type ChannelMessagePayload,
|
|
34
|
-
type ChannelJoinEnvelope,
|
|
35
|
-
type ChannelLeaveEnvelope,
|
|
36
|
-
type ChannelMessageEnvelope,
|
|
37
|
-
type MessageAttachment,
|
|
38
|
-
PROTOCOL_VERSION,
|
|
39
|
-
} from '@agent-relay/protocol';
|
|
40
|
-
import { encodeFrameLegacyBrowser, BrowserFrameParser } from './browser-framing.js';
|
|
41
|
-
|
|
42
|
-
export type BrowserClientState = 'DISCONNECTED' | 'CONNECTING' | 'HANDSHAKING' | 'READY' | 'BACKOFF';
|
|
43
|
-
|
|
44
|
-
export interface BrowserClientConfig {
|
|
45
|
-
/** Agent name */
|
|
46
|
-
agentName: string;
|
|
47
|
-
/** Entity type: 'agent' (default) or 'user' */
|
|
48
|
-
entityType?: EntityType;
|
|
49
|
-
/** CLI identifier (claude, codex, gemini, etc.) */
|
|
50
|
-
cli?: string;
|
|
51
|
-
/** Display name for human users */
|
|
52
|
-
displayName?: string;
|
|
53
|
-
/** Avatar URL for human users */
|
|
54
|
-
avatarUrl?: string;
|
|
55
|
-
/** Suppress console logging */
|
|
56
|
-
quiet?: boolean;
|
|
57
|
-
/** Auto-reconnect on disconnect */
|
|
58
|
-
reconnect?: boolean;
|
|
59
|
-
/** Max reconnect attempts */
|
|
60
|
-
maxReconnectAttempts?: number;
|
|
61
|
-
/** Initial reconnect delay (ms) */
|
|
62
|
-
reconnectDelayMs?: number;
|
|
63
|
-
/** Max reconnect delay (ms) */
|
|
64
|
-
reconnectMaxDelayMs?: number;
|
|
65
|
-
/** Transport options (WebSocket URL, socket path, etc.) */
|
|
66
|
-
transport?: AutoTransportOptions;
|
|
67
|
-
/**
|
|
68
|
-
* Pre-configured transport instance (alternative to transport options).
|
|
69
|
-
* NOTE: Auto-reconnection is NOT supported when using transportInstance.
|
|
70
|
-
* If reconnection is needed, use `transport` options instead, or handle
|
|
71
|
-
* reconnection manually by listening for state changes.
|
|
72
|
-
*/
|
|
73
|
-
transportInstance?: Transport;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const DEFAULT_CONFIG: Required<Omit<BrowserClientConfig, 'entityType' | 'cli' | 'displayName' | 'avatarUrl' | 'transport' | 'transportInstance'>> = {
|
|
77
|
-
agentName: 'agent',
|
|
78
|
-
quiet: false,
|
|
79
|
-
reconnect: true,
|
|
80
|
-
maxReconnectAttempts: 10,
|
|
81
|
-
reconnectDelayMs: 1000,
|
|
82
|
-
reconnectMaxDelayMs: 30000,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
// Simple ID generator (browser-compatible)
|
|
86
|
-
let idCounter = 0;
|
|
87
|
-
function generateId(): string {
|
|
88
|
-
return `${Date.now().toString(36)}-${(++idCounter).toString(36)}`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Browser-compatible UUID generator
|
|
92
|
-
function generateUUID(): string {
|
|
93
|
-
// Use crypto.randomUUID if available (modern browsers)
|
|
94
|
-
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
|
|
95
|
-
return crypto.randomUUID();
|
|
96
|
-
}
|
|
97
|
-
// Fallback: simple UUID v4 implementation
|
|
98
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
99
|
-
const r = (Math.random() * 16) | 0;
|
|
100
|
-
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
101
|
-
return v.toString(16);
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Circular buffer for O(1) deduplication with bounded memory.
|
|
107
|
-
*/
|
|
108
|
-
class CircularDedupeCache {
|
|
109
|
-
private ids: Set<string> = new Set();
|
|
110
|
-
private ring: string[];
|
|
111
|
-
private head = 0;
|
|
112
|
-
private readonly capacity: number;
|
|
113
|
-
|
|
114
|
-
constructor(capacity = 2000) {
|
|
115
|
-
this.capacity = capacity;
|
|
116
|
-
this.ring = new Array(capacity);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
check(id: string): boolean {
|
|
120
|
-
if (this.ids.has(id)) return true;
|
|
121
|
-
|
|
122
|
-
if (this.ids.size >= this.capacity) {
|
|
123
|
-
const oldest = this.ring[this.head];
|
|
124
|
-
if (oldest) this.ids.delete(oldest);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
this.ring[this.head] = id;
|
|
128
|
-
this.ids.add(id);
|
|
129
|
-
this.head = (this.head + 1) % this.capacity;
|
|
130
|
-
|
|
131
|
-
return false;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
clear(): void {
|
|
135
|
-
this.ids.clear();
|
|
136
|
-
this.ring = new Array(this.capacity);
|
|
137
|
-
this.head = 0;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Browser-compatible request options.
|
|
143
|
-
*/
|
|
144
|
-
export interface BrowserRequestOptions {
|
|
145
|
-
/** Timeout in milliseconds (default: 30000) */
|
|
146
|
-
timeout?: number;
|
|
147
|
-
/** Optional structured data to include with the request */
|
|
148
|
-
data?: Record<string, unknown>;
|
|
149
|
-
/** Optional thread identifier */
|
|
150
|
-
thread?: string;
|
|
151
|
-
/** Message kind (default: 'message') */
|
|
152
|
-
kind?: PayloadKind;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Response from the request() method.
|
|
157
|
-
*/
|
|
158
|
-
export interface BrowserRequestResponse {
|
|
159
|
-
/** Sender of the response */
|
|
160
|
-
from: string;
|
|
161
|
-
/** Response body text */
|
|
162
|
-
body: string;
|
|
163
|
-
/** Optional structured data from the response */
|
|
164
|
-
data?: Record<string, unknown>;
|
|
165
|
-
/** The correlation ID used for this request/response */
|
|
166
|
-
correlationId: string;
|
|
167
|
-
/** Thread identifier if set */
|
|
168
|
-
thread?: string;
|
|
169
|
-
/** The full payload for advanced use cases */
|
|
170
|
-
payload: SendPayload;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* BrowserRelayClient - A browser-compatible relay client.
|
|
175
|
-
*
|
|
176
|
-
* Uses WebSocket transport by default, making it compatible with browsers.
|
|
177
|
-
* Can also be used in Node.js for WebSocket-based connections.
|
|
178
|
-
*
|
|
179
|
-
* @example Browser usage
|
|
180
|
-
* ```typescript
|
|
181
|
-
* import { BrowserRelayClient } from '@agent-relay/sdk/browser';
|
|
182
|
-
*
|
|
183
|
-
* const client = new BrowserRelayClient({
|
|
184
|
-
* agentName: 'MyAgent',
|
|
185
|
-
* transport: {
|
|
186
|
-
* wsUrl: 'wss://relay.example.com/ws',
|
|
187
|
-
* },
|
|
188
|
-
* });
|
|
189
|
-
*
|
|
190
|
-
* await client.connect();
|
|
191
|
-
*
|
|
192
|
-
* client.onMessage = (from, payload) => {
|
|
193
|
-
* console.log(`Message from ${from}: ${payload.body}`);
|
|
194
|
-
* };
|
|
195
|
-
*
|
|
196
|
-
* client.sendMessage('OtherAgent', 'Hello!');
|
|
197
|
-
* ```
|
|
198
|
-
*/
|
|
199
|
-
export class BrowserRelayClient {
|
|
200
|
-
private config: BrowserClientConfig;
|
|
201
|
-
private transport?: Transport;
|
|
202
|
-
private parser: BrowserFrameParser;
|
|
203
|
-
|
|
204
|
-
private _state: BrowserClientState = 'DISCONNECTED';
|
|
205
|
-
private sessionId?: string;
|
|
206
|
-
private resumeToken?: string;
|
|
207
|
-
private reconnectAttempts = 0;
|
|
208
|
-
private reconnectDelay: number;
|
|
209
|
-
private reconnectTimer?: ReturnType<typeof setTimeout>;
|
|
210
|
-
private _destroyed = false;
|
|
211
|
-
|
|
212
|
-
private dedupeCache = new CircularDedupeCache(2000);
|
|
213
|
-
private writeQueue: Uint8Array[] = [];
|
|
214
|
-
private writeScheduled = false;
|
|
215
|
-
|
|
216
|
-
private pendingSyncAcks: Map<string, {
|
|
217
|
-
resolve: (ack: AckPayload) => void;
|
|
218
|
-
reject: (err: Error) => void;
|
|
219
|
-
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
220
|
-
}> = new Map();
|
|
221
|
-
|
|
222
|
-
private pendingRequests: Map<string, {
|
|
223
|
-
resolve: (response: BrowserRequestResponse) => void;
|
|
224
|
-
reject: (err: Error) => void;
|
|
225
|
-
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
226
|
-
targetAgent: string;
|
|
227
|
-
}> = new Map();
|
|
228
|
-
|
|
229
|
-
// Event handlers
|
|
230
|
-
onMessage?: (from: string, payload: SendPayload, messageId: string, meta?: SendMeta, originalTo?: string) => void;
|
|
231
|
-
onChannelMessage?: (from: string, channel: string, body: string, envelope: Envelope<ChannelMessagePayload>) => void;
|
|
232
|
-
onStateChange?: (state: BrowserClientState) => void;
|
|
233
|
-
onError?: (error: Error) => void;
|
|
234
|
-
|
|
235
|
-
constructor(config: BrowserClientConfig) {
|
|
236
|
-
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
237
|
-
this.parser = new BrowserFrameParser(); // Always uses legacy mode (4-byte header, JSON only)
|
|
238
|
-
this.reconnectDelay = this.config.reconnectDelayMs ?? DEFAULT_CONFIG.reconnectDelayMs;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
get state(): BrowserClientState {
|
|
242
|
-
return this._state;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
get agentName(): string {
|
|
246
|
-
return this.config.agentName;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
get currentSessionId(): string | undefined {
|
|
250
|
-
return this.sessionId;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Connect to the relay daemon.
|
|
255
|
-
*/
|
|
256
|
-
async connect(): Promise<void> {
|
|
257
|
-
if (this._state !== 'DISCONNECTED' && this._state !== 'BACKOFF') {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
this.setState('CONNECTING');
|
|
262
|
-
|
|
263
|
-
// Create transport if not provided
|
|
264
|
-
if (!this.transport) {
|
|
265
|
-
if (this.config.transportInstance) {
|
|
266
|
-
this.transport = this.config.transportInstance;
|
|
267
|
-
} else if (this.config.transport) {
|
|
268
|
-
this.transport = createAutoTransport(this.config.transport);
|
|
269
|
-
} else {
|
|
270
|
-
throw new Error(
|
|
271
|
-
'Transport configuration required. Provide either transport options or transportInstance.'
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Set up transport events
|
|
277
|
-
this.transport.setEvents({
|
|
278
|
-
onConnect: () => {
|
|
279
|
-
this.setState('HANDSHAKING');
|
|
280
|
-
this.sendHello();
|
|
281
|
-
},
|
|
282
|
-
onData: (data) => this.handleData(data),
|
|
283
|
-
onClose: () => this.handleDisconnect(),
|
|
284
|
-
onError: (err) => this.handleError(err),
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
await this.transport.connect();
|
|
289
|
-
|
|
290
|
-
// Wait for READY state
|
|
291
|
-
await new Promise<void>((resolve, reject) => {
|
|
292
|
-
let checkReady: ReturnType<typeof setInterval>;
|
|
293
|
-
|
|
294
|
-
const cleanup = () => {
|
|
295
|
-
clearInterval(checkReady);
|
|
296
|
-
clearTimeout(timeout);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
const timeout = setTimeout(() => {
|
|
300
|
-
cleanup();
|
|
301
|
-
reject(new Error('Connection handshake timeout'));
|
|
302
|
-
}, 5000);
|
|
303
|
-
|
|
304
|
-
checkReady = setInterval(() => {
|
|
305
|
-
if (this._state === 'READY') {
|
|
306
|
-
cleanup();
|
|
307
|
-
resolve();
|
|
308
|
-
} else if (this._state === 'DISCONNECTED') {
|
|
309
|
-
cleanup();
|
|
310
|
-
reject(new Error('Connection failed'));
|
|
311
|
-
}
|
|
312
|
-
}, 10);
|
|
313
|
-
});
|
|
314
|
-
} catch (err) {
|
|
315
|
-
this.setState('DISCONNECTED');
|
|
316
|
-
throw err;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Disconnect from the relay daemon.
|
|
322
|
-
*/
|
|
323
|
-
disconnect(): void {
|
|
324
|
-
if (this.reconnectTimer) {
|
|
325
|
-
clearTimeout(this.reconnectTimer);
|
|
326
|
-
this.reconnectTimer = undefined;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (this.transport) {
|
|
330
|
-
// Send BYE message
|
|
331
|
-
this.send({
|
|
332
|
-
v: PROTOCOL_VERSION,
|
|
333
|
-
type: 'BYE',
|
|
334
|
-
id: generateId(),
|
|
335
|
-
ts: Date.now(),
|
|
336
|
-
payload: {},
|
|
337
|
-
});
|
|
338
|
-
this.transport.disconnect();
|
|
339
|
-
this.transport = undefined;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
this.setState('DISCONNECTED');
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Permanently destroy the client.
|
|
347
|
-
*/
|
|
348
|
-
destroy(): void {
|
|
349
|
-
this._destroyed = true;
|
|
350
|
-
this.disconnect();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Send a message to another agent.
|
|
355
|
-
*/
|
|
356
|
-
sendMessage(
|
|
357
|
-
to: string,
|
|
358
|
-
body: string,
|
|
359
|
-
kind: PayloadKind = 'message',
|
|
360
|
-
data?: Record<string, unknown>,
|
|
361
|
-
thread?: string,
|
|
362
|
-
meta?: SendMeta
|
|
363
|
-
): boolean {
|
|
364
|
-
if (this._state !== 'READY') {
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const envelope: SendEnvelope = {
|
|
369
|
-
v: PROTOCOL_VERSION,
|
|
370
|
-
type: 'SEND',
|
|
371
|
-
id: generateId(),
|
|
372
|
-
ts: Date.now(),
|
|
373
|
-
to,
|
|
374
|
-
payload: {
|
|
375
|
-
kind,
|
|
376
|
-
body,
|
|
377
|
-
data,
|
|
378
|
-
thread,
|
|
379
|
-
},
|
|
380
|
-
payload_meta: meta,
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
return this.send(envelope);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Send an ACK for a delivered message.
|
|
388
|
-
*/
|
|
389
|
-
sendAck(payload: AckPayload): boolean {
|
|
390
|
-
if (this._state !== 'READY') {
|
|
391
|
-
return false;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const envelope: Envelope<AckPayload> = {
|
|
395
|
-
v: PROTOCOL_VERSION,
|
|
396
|
-
type: 'ACK',
|
|
397
|
-
id: generateId(),
|
|
398
|
-
ts: Date.now(),
|
|
399
|
-
payload,
|
|
400
|
-
};
|
|
401
|
-
|
|
402
|
-
return this.send(envelope);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Send a request to another agent and wait for their response.
|
|
407
|
-
*/
|
|
408
|
-
async request(to: string, body: string, options: BrowserRequestOptions = {}): Promise<BrowserRequestResponse> {
|
|
409
|
-
if (this._state !== 'READY') {
|
|
410
|
-
throw new Error('Client not ready');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
const correlationId = generateUUID();
|
|
414
|
-
const timeoutMs = options.timeout ?? 30000;
|
|
415
|
-
const kind = options.kind ?? 'message';
|
|
416
|
-
|
|
417
|
-
return new Promise<BrowserRequestResponse>((resolve, reject) => {
|
|
418
|
-
const timeoutHandle = setTimeout(() => {
|
|
419
|
-
this.pendingRequests.delete(correlationId);
|
|
420
|
-
reject(new Error(`Request timeout after ${timeoutMs}ms waiting for response from ${to}`));
|
|
421
|
-
}, timeoutMs);
|
|
422
|
-
|
|
423
|
-
this.pendingRequests.set(correlationId, {
|
|
424
|
-
resolve,
|
|
425
|
-
reject,
|
|
426
|
-
timeoutHandle,
|
|
427
|
-
targetAgent: to,
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
const envelope: SendEnvelope = {
|
|
431
|
-
v: PROTOCOL_VERSION,
|
|
432
|
-
type: 'SEND',
|
|
433
|
-
id: generateId(),
|
|
434
|
-
ts: Date.now(),
|
|
435
|
-
to,
|
|
436
|
-
payload: {
|
|
437
|
-
kind,
|
|
438
|
-
body,
|
|
439
|
-
data: {
|
|
440
|
-
...options.data,
|
|
441
|
-
_correlationId: correlationId,
|
|
442
|
-
},
|
|
443
|
-
thread: options.thread,
|
|
444
|
-
},
|
|
445
|
-
payload_meta: {
|
|
446
|
-
replyTo: correlationId,
|
|
447
|
-
},
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
const sent = this.send(envelope);
|
|
451
|
-
if (!sent) {
|
|
452
|
-
clearTimeout(timeoutHandle);
|
|
453
|
-
this.pendingRequests.delete(correlationId);
|
|
454
|
-
reject(new Error('Failed to send request'));
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Respond to a request from another agent.
|
|
461
|
-
*/
|
|
462
|
-
respond(correlationId: string, to: string, body: string, data?: Record<string, unknown>): boolean {
|
|
463
|
-
if (this._state !== 'READY') {
|
|
464
|
-
return false;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
const envelope: SendEnvelope = {
|
|
468
|
-
v: PROTOCOL_VERSION,
|
|
469
|
-
type: 'SEND',
|
|
470
|
-
id: generateId(),
|
|
471
|
-
ts: Date.now(),
|
|
472
|
-
to,
|
|
473
|
-
payload: {
|
|
474
|
-
kind: 'message',
|
|
475
|
-
body,
|
|
476
|
-
data: {
|
|
477
|
-
...data,
|
|
478
|
-
_correlationId: correlationId,
|
|
479
|
-
_isResponse: true,
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
|
-
payload_meta: {
|
|
483
|
-
replyTo: correlationId,
|
|
484
|
-
},
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
return this.send(envelope);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Broadcast a message to all agents.
|
|
492
|
-
*/
|
|
493
|
-
broadcast(body: string, kind: PayloadKind = 'message', data?: Record<string, unknown>): boolean {
|
|
494
|
-
return this.sendMessage('*', body, kind, data);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Bind as a shadow to a primary agent.
|
|
499
|
-
*/
|
|
500
|
-
bindAsShadow(
|
|
501
|
-
primaryAgent: string,
|
|
502
|
-
options: {
|
|
503
|
-
speakOn?: SpeakOnTrigger[];
|
|
504
|
-
receiveIncoming?: boolean;
|
|
505
|
-
receiveOutgoing?: boolean;
|
|
506
|
-
} = {}
|
|
507
|
-
): boolean {
|
|
508
|
-
if (this._state !== 'READY') return false;
|
|
509
|
-
|
|
510
|
-
return this.send({
|
|
511
|
-
v: PROTOCOL_VERSION,
|
|
512
|
-
type: 'SHADOW_BIND',
|
|
513
|
-
id: generateId(),
|
|
514
|
-
ts: Date.now(),
|
|
515
|
-
payload: {
|
|
516
|
-
primaryAgent,
|
|
517
|
-
speakOn: options.speakOn,
|
|
518
|
-
receiveIncoming: options.receiveIncoming,
|
|
519
|
-
receiveOutgoing: options.receiveOutgoing,
|
|
520
|
-
},
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Unbind from a primary agent.
|
|
526
|
-
*/
|
|
527
|
-
unbindAsShadow(primaryAgent: string): boolean {
|
|
528
|
-
if (this._state !== 'READY') return false;
|
|
529
|
-
|
|
530
|
-
return this.send({
|
|
531
|
-
v: PROTOCOL_VERSION,
|
|
532
|
-
type: 'SHADOW_UNBIND',
|
|
533
|
-
id: generateId(),
|
|
534
|
-
ts: Date.now(),
|
|
535
|
-
payload: {
|
|
536
|
-
primaryAgent,
|
|
537
|
-
},
|
|
538
|
-
});
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
/**
|
|
542
|
-
* Send log output to the daemon.
|
|
543
|
-
*/
|
|
544
|
-
sendLog(data: string): boolean {
|
|
545
|
-
if (this._state !== 'READY') {
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const envelope: Envelope<LogPayload> = {
|
|
550
|
-
v: PROTOCOL_VERSION,
|
|
551
|
-
type: 'LOG',
|
|
552
|
-
id: generateId(),
|
|
553
|
-
ts: Date.now(),
|
|
554
|
-
payload: {
|
|
555
|
-
data,
|
|
556
|
-
timestamp: Date.now(),
|
|
557
|
-
},
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
return this.send(envelope);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// =============================================================================
|
|
564
|
-
// Channel Operations
|
|
565
|
-
// =============================================================================
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Join a channel.
|
|
569
|
-
*/
|
|
570
|
-
joinChannel(channel: string, displayName?: string): boolean {
|
|
571
|
-
if (this._state !== 'READY') {
|
|
572
|
-
return false;
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
const envelope: ChannelJoinEnvelope = {
|
|
576
|
-
v: PROTOCOL_VERSION,
|
|
577
|
-
type: 'CHANNEL_JOIN',
|
|
578
|
-
id: generateId(),
|
|
579
|
-
ts: Date.now(),
|
|
580
|
-
payload: {
|
|
581
|
-
channel,
|
|
582
|
-
displayName,
|
|
583
|
-
},
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
return this.send(envelope);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
/**
|
|
590
|
-
* Leave a channel.
|
|
591
|
-
*/
|
|
592
|
-
leaveChannel(channel: string, reason?: string): boolean {
|
|
593
|
-
if (this._state !== 'READY') return false;
|
|
594
|
-
|
|
595
|
-
const envelope: ChannelLeaveEnvelope = {
|
|
596
|
-
v: PROTOCOL_VERSION,
|
|
597
|
-
type: 'CHANNEL_LEAVE',
|
|
598
|
-
id: generateId(),
|
|
599
|
-
ts: Date.now(),
|
|
600
|
-
payload: {
|
|
601
|
-
channel,
|
|
602
|
-
reason,
|
|
603
|
-
},
|
|
604
|
-
};
|
|
605
|
-
|
|
606
|
-
return this.send(envelope);
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
/**
|
|
610
|
-
* Send a message to a channel.
|
|
611
|
-
*/
|
|
612
|
-
sendChannelMessage(
|
|
613
|
-
channel: string,
|
|
614
|
-
body: string,
|
|
615
|
-
options?: {
|
|
616
|
-
thread?: string;
|
|
617
|
-
mentions?: string[];
|
|
618
|
-
attachments?: MessageAttachment[];
|
|
619
|
-
data?: Record<string, unknown>;
|
|
620
|
-
}
|
|
621
|
-
): boolean {
|
|
622
|
-
if (this._state !== 'READY') {
|
|
623
|
-
return false;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
const envelope: ChannelMessageEnvelope = {
|
|
627
|
-
v: PROTOCOL_VERSION,
|
|
628
|
-
type: 'CHANNEL_MESSAGE',
|
|
629
|
-
id: generateId(),
|
|
630
|
-
ts: Date.now(),
|
|
631
|
-
payload: {
|
|
632
|
-
channel,
|
|
633
|
-
body,
|
|
634
|
-
thread: options?.thread,
|
|
635
|
-
mentions: options?.mentions,
|
|
636
|
-
attachments: options?.attachments,
|
|
637
|
-
data: options?.data,
|
|
638
|
-
},
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
return this.send(envelope);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// =============================================================================
|
|
645
|
-
// Private Methods
|
|
646
|
-
// =============================================================================
|
|
647
|
-
|
|
648
|
-
private setState(state: BrowserClientState): void {
|
|
649
|
-
this._state = state;
|
|
650
|
-
if (this.onStateChange) {
|
|
651
|
-
this.onStateChange(state);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
private sendHello(): void {
|
|
656
|
-
const hello: Envelope<HelloPayload> = {
|
|
657
|
-
v: PROTOCOL_VERSION,
|
|
658
|
-
type: 'HELLO',
|
|
659
|
-
id: generateId(),
|
|
660
|
-
ts: Date.now(),
|
|
661
|
-
payload: {
|
|
662
|
-
agent: this.config.agentName,
|
|
663
|
-
entityType: this.config.entityType,
|
|
664
|
-
cli: this.config.cli,
|
|
665
|
-
displayName: this.config.displayName,
|
|
666
|
-
avatarUrl: this.config.avatarUrl,
|
|
667
|
-
capabilities: {
|
|
668
|
-
ack: true,
|
|
669
|
-
resume: true,
|
|
670
|
-
max_inflight: 256,
|
|
671
|
-
supports_topics: true,
|
|
672
|
-
},
|
|
673
|
-
session: this.resumeToken ? { resume_token: this.resumeToken } : undefined,
|
|
674
|
-
},
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
this.send(hello);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
private send(envelope: Envelope): boolean {
|
|
681
|
-
if (!this.transport || this.transport.state !== 'connected') {
|
|
682
|
-
return false;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
try {
|
|
686
|
-
const frame = encodeFrameLegacyBrowser(envelope);
|
|
687
|
-
this.writeQueue.push(frame);
|
|
688
|
-
|
|
689
|
-
if (!this.writeScheduled) {
|
|
690
|
-
this.writeScheduled = true;
|
|
691
|
-
// Use setTimeout(0) for browser compatibility (instead of setImmediate)
|
|
692
|
-
setTimeout(() => this.flushWrites(), 0);
|
|
693
|
-
}
|
|
694
|
-
return true;
|
|
695
|
-
} catch (err) {
|
|
696
|
-
this.handleError(err as Error);
|
|
697
|
-
return false;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
private flushWrites(): void {
|
|
702
|
-
this.writeScheduled = false;
|
|
703
|
-
if (this.writeQueue.length === 0 || !this.transport) return;
|
|
704
|
-
|
|
705
|
-
// Concatenate all buffers
|
|
706
|
-
const totalLength = this.writeQueue.reduce((sum, buf) => sum + buf.length, 0);
|
|
707
|
-
const combined = new Uint8Array(totalLength);
|
|
708
|
-
let offset = 0;
|
|
709
|
-
for (const buf of this.writeQueue) {
|
|
710
|
-
combined.set(buf, offset);
|
|
711
|
-
offset += buf.length;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
this.transport.send(combined);
|
|
715
|
-
this.writeQueue = [];
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
private handleData(data: Uint8Array): void {
|
|
719
|
-
try {
|
|
720
|
-
const frames = this.parser.push(data);
|
|
721
|
-
for (const frame of frames) {
|
|
722
|
-
this.processFrame(frame);
|
|
723
|
-
}
|
|
724
|
-
} catch (err) {
|
|
725
|
-
this.handleError(err as Error);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
private processFrame(envelope: Envelope): void {
|
|
730
|
-
switch (envelope.type) {
|
|
731
|
-
case 'WELCOME':
|
|
732
|
-
this.handleWelcome(envelope as Envelope<WelcomePayload>);
|
|
733
|
-
break;
|
|
734
|
-
|
|
735
|
-
case 'DELIVER':
|
|
736
|
-
this.handleDeliver(envelope as DeliverEnvelope);
|
|
737
|
-
break;
|
|
738
|
-
|
|
739
|
-
case 'CHANNEL_MESSAGE':
|
|
740
|
-
this.handleChannelMessage(envelope as Envelope<ChannelMessagePayload> & { from?: string });
|
|
741
|
-
break;
|
|
742
|
-
|
|
743
|
-
case 'PING':
|
|
744
|
-
this.handlePing(envelope);
|
|
745
|
-
break;
|
|
746
|
-
|
|
747
|
-
case 'ACK':
|
|
748
|
-
this.handleAck(envelope as Envelope<AckPayload>);
|
|
749
|
-
break;
|
|
750
|
-
|
|
751
|
-
case 'ERROR':
|
|
752
|
-
this.handleErrorFrame(envelope as Envelope<ErrorPayload>);
|
|
753
|
-
break;
|
|
754
|
-
|
|
755
|
-
case 'BUSY':
|
|
756
|
-
if (!this.config.quiet) {
|
|
757
|
-
console.warn('[sdk] Server busy, backing off');
|
|
758
|
-
}
|
|
759
|
-
break;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
private handleWelcome(envelope: Envelope<WelcomePayload>): void {
|
|
764
|
-
this.sessionId = envelope.payload.session_id;
|
|
765
|
-
this.resumeToken = envelope.payload.resume_token;
|
|
766
|
-
this.reconnectAttempts = 0;
|
|
767
|
-
this.reconnectDelay = this.config.reconnectDelayMs ?? DEFAULT_CONFIG.reconnectDelayMs;
|
|
768
|
-
this.setState('READY');
|
|
769
|
-
if (!this.config.quiet) {
|
|
770
|
-
console.log(`[sdk] Connected as ${this.config.agentName} (session: ${this.sessionId})`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
private handleDeliver(envelope: DeliverEnvelope): void {
|
|
775
|
-
// Send ACK
|
|
776
|
-
this.send({
|
|
777
|
-
v: PROTOCOL_VERSION,
|
|
778
|
-
type: 'ACK',
|
|
779
|
-
id: generateId(),
|
|
780
|
-
ts: Date.now(),
|
|
781
|
-
payload: {
|
|
782
|
-
ack_id: envelope.id,
|
|
783
|
-
seq: envelope.delivery.seq,
|
|
784
|
-
},
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
const duplicate = this.dedupeCache.check(envelope.id);
|
|
788
|
-
if (duplicate) {
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
// Check if this is a response to a pending request
|
|
793
|
-
const correlationId = this.extractCorrelationId(envelope);
|
|
794
|
-
if (correlationId && envelope.from) {
|
|
795
|
-
const pending = this.pendingRequests.get(correlationId);
|
|
796
|
-
if (pending) {
|
|
797
|
-
clearTimeout(pending.timeoutHandle);
|
|
798
|
-
this.pendingRequests.delete(correlationId);
|
|
799
|
-
pending.resolve({
|
|
800
|
-
from: envelope.from,
|
|
801
|
-
body: envelope.payload.body,
|
|
802
|
-
data: envelope.payload.data,
|
|
803
|
-
correlationId,
|
|
804
|
-
thread: envelope.payload.thread,
|
|
805
|
-
payload: envelope.payload,
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
if (this.onMessage && envelope.from) {
|
|
811
|
-
this.onMessage(
|
|
812
|
-
envelope.from,
|
|
813
|
-
envelope.payload,
|
|
814
|
-
envelope.id,
|
|
815
|
-
envelope.payload_meta,
|
|
816
|
-
envelope.delivery.originalTo
|
|
817
|
-
);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
private extractCorrelationId(envelope: DeliverEnvelope): string | undefined {
|
|
822
|
-
if (envelope.payload_meta?.replyTo) {
|
|
823
|
-
return envelope.payload_meta.replyTo;
|
|
824
|
-
}
|
|
825
|
-
if (envelope.payload.data && typeof envelope.payload.data._correlationId === 'string') {
|
|
826
|
-
return envelope.payload.data._correlationId;
|
|
827
|
-
}
|
|
828
|
-
return undefined;
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
private handleChannelMessage(envelope: Envelope<ChannelMessagePayload> & { from?: string }): void {
|
|
832
|
-
const duplicate = this.dedupeCache.check(envelope.id);
|
|
833
|
-
if (duplicate) {
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (this.onChannelMessage && envelope.from) {
|
|
838
|
-
this.onChannelMessage(
|
|
839
|
-
envelope.from,
|
|
840
|
-
envelope.payload.channel,
|
|
841
|
-
envelope.payload.body,
|
|
842
|
-
envelope as Envelope<ChannelMessagePayload>
|
|
843
|
-
);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// Also call onMessage for backwards compatibility
|
|
847
|
-
if (this.onMessage && envelope.from) {
|
|
848
|
-
const sendPayload: SendPayload = {
|
|
849
|
-
kind: 'message',
|
|
850
|
-
body: envelope.payload.body,
|
|
851
|
-
data: {
|
|
852
|
-
_isChannelMessage: true,
|
|
853
|
-
_channel: envelope.payload.channel,
|
|
854
|
-
_mentions: envelope.payload.mentions,
|
|
855
|
-
},
|
|
856
|
-
thread: envelope.payload.thread,
|
|
857
|
-
};
|
|
858
|
-
this.onMessage(envelope.from, sendPayload, envelope.id, undefined, envelope.payload.channel);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
private handleAck(envelope: Envelope<AckPayload>): void {
|
|
863
|
-
const correlationId = envelope.payload.correlationId;
|
|
864
|
-
if (!correlationId) return;
|
|
865
|
-
|
|
866
|
-
const pending = this.pendingSyncAcks.get(correlationId);
|
|
867
|
-
if (!pending) return;
|
|
868
|
-
|
|
869
|
-
clearTimeout(pending.timeoutHandle);
|
|
870
|
-
this.pendingSyncAcks.delete(correlationId);
|
|
871
|
-
pending.resolve(envelope.payload);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
private handlePing(envelope: Envelope): void {
|
|
875
|
-
this.send({
|
|
876
|
-
v: PROTOCOL_VERSION,
|
|
877
|
-
type: 'PONG',
|
|
878
|
-
id: generateId(),
|
|
879
|
-
ts: Date.now(),
|
|
880
|
-
payload: (envelope.payload as { nonce?: string }) ?? {},
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
private handleErrorFrame(envelope: Envelope<ErrorPayload>): void {
|
|
885
|
-
if (!this.config.quiet) {
|
|
886
|
-
console.error('[sdk] Server error:', envelope.payload);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
if (envelope.payload.code === 'RESUME_TOO_OLD') {
|
|
890
|
-
this.resumeToken = undefined;
|
|
891
|
-
this.sessionId = undefined;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
if (envelope.payload.fatal) {
|
|
895
|
-
if (!this.config.quiet) {
|
|
896
|
-
console.error('[sdk] Fatal error received, will not reconnect:', envelope.payload.message);
|
|
897
|
-
}
|
|
898
|
-
this._destroyed = true;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
private handleDisconnect(): void {
|
|
903
|
-
this.parser.reset();
|
|
904
|
-
this.transport = undefined;
|
|
905
|
-
this.rejectPendingSyncAcks(new Error('Disconnected while awaiting ACK'));
|
|
906
|
-
this.rejectPendingRequests(new Error('Disconnected while awaiting request response'));
|
|
907
|
-
|
|
908
|
-
if (this._destroyed) {
|
|
909
|
-
this.setState('DISCONNECTED');
|
|
910
|
-
return;
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
if (this.config.reconnect && this.reconnectAttempts < (this.config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts)) {
|
|
914
|
-
this.scheduleReconnect();
|
|
915
|
-
} else {
|
|
916
|
-
this.setState('DISCONNECTED');
|
|
917
|
-
if (this.reconnectAttempts >= (this.config.maxReconnectAttempts ?? DEFAULT_CONFIG.maxReconnectAttempts) && !this.config.quiet) {
|
|
918
|
-
console.error(
|
|
919
|
-
`[sdk] Max reconnect attempts reached (${this.config.maxReconnectAttempts}), giving up`
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
private handleError(error: Error): void {
|
|
926
|
-
if (!this.config.quiet) {
|
|
927
|
-
console.error('[sdk] Error:', error.message);
|
|
928
|
-
}
|
|
929
|
-
if (this.onError) {
|
|
930
|
-
this.onError(error);
|
|
931
|
-
}
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
private rejectPendingSyncAcks(error: Error): void {
|
|
935
|
-
for (const [correlationId, pending] of this.pendingSyncAcks.entries()) {
|
|
936
|
-
clearTimeout(pending.timeoutHandle);
|
|
937
|
-
pending.reject(error);
|
|
938
|
-
this.pendingSyncAcks.delete(correlationId);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
private rejectPendingRequests(error: Error): void {
|
|
943
|
-
for (const [correlationId, pending] of this.pendingRequests.entries()) {
|
|
944
|
-
clearTimeout(pending.timeoutHandle);
|
|
945
|
-
pending.reject(error);
|
|
946
|
-
this.pendingRequests.delete(correlationId);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
private scheduleReconnect(): void {
|
|
951
|
-
// Cannot reconnect when using transportInstance - we can't recreate an
|
|
952
|
-
// externally-provided transport. Users must handle reconnection themselves
|
|
953
|
-
// or use transport options instead.
|
|
954
|
-
if (!this.config.transport && this.config.transportInstance) {
|
|
955
|
-
if (!this.config.quiet) {
|
|
956
|
-
console.warn(
|
|
957
|
-
'[sdk] Cannot auto-reconnect with transportInstance. ' +
|
|
958
|
-
'Use transport options instead, or handle reconnection manually.'
|
|
959
|
-
);
|
|
960
|
-
}
|
|
961
|
-
this.setState('DISCONNECTED');
|
|
962
|
-
return;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
this.setState('BACKOFF');
|
|
966
|
-
this.reconnectAttempts++;
|
|
967
|
-
|
|
968
|
-
const jitter = Math.random() * 0.3 + 0.85;
|
|
969
|
-
const maxDelay = this.config.reconnectMaxDelayMs ?? DEFAULT_CONFIG.reconnectMaxDelayMs;
|
|
970
|
-
const delay = Math.min(this.reconnectDelay * jitter, maxDelay);
|
|
971
|
-
this.reconnectDelay *= 2;
|
|
972
|
-
|
|
973
|
-
if (!this.config.quiet) {
|
|
974
|
-
console.log(`[sdk] Reconnecting in ${Math.round(delay)}ms (attempt ${this.reconnectAttempts})`);
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
this.reconnectTimer = setTimeout(() => {
|
|
978
|
-
// Re-create transport for reconnection
|
|
979
|
-
if (this.config.transport) {
|
|
980
|
-
this.transport = createAutoTransport(this.config.transport);
|
|
981
|
-
}
|
|
982
|
-
this.connect().catch(() => {});
|
|
983
|
-
}, delay);
|
|
984
|
-
}
|
|
985
|
-
}
|