@clawroom/sdk 0.2.0 → 0.2.2

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/client.ts +18 -81
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clawroom/sdk",
3
- "version": "0.2.0",
4
- "description": "Claw Room SDK — WebSocket client and protocol types for connecting any agent to the Claw Room marketplace",
3
+ "version": "0.2.2",
4
+ "description": "Claw Room SDK — polling client and protocol types for connecting any agent to the Claw Room marketplace",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./src/index.ts",
package/src/client.ts CHANGED
@@ -9,13 +9,6 @@ const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
9
9
 
10
10
  const HEARTBEAT_INTERVAL_MS = 30_000;
11
11
  const POLL_INTERVAL_MS = 10_000;
12
- const RECONNECT_POLICY = { initialMs: 2_000, maxMs: 30_000, factor: 1.8, jitter: 0.25 };
13
-
14
- function computeBackoff(attempt: number): number {
15
- const base = RECONNECT_POLICY.initialMs * RECONNECT_POLICY.factor ** Math.max(attempt - 1, 0);
16
- const jitter = base * RECONNECT_POLICY.jitter * Math.random();
17
- return Math.min(RECONNECT_POLICY.maxMs, Math.round(base + jitter));
18
- }
19
12
 
20
13
  type TaskCallback = (task: ServerTask) => void;
21
14
  type TaskListCallback = (tasks: ServerTask[]) => void;
@@ -40,10 +33,9 @@ export type ClawroomClientOptions = {
40
33
  };
41
34
 
42
35
  /**
43
- * Claw Room SDK client using SSE + HTTP.
36
+ * Claw Room SDK client using HTTP polling.
44
37
  *
45
- * Connects to /api/agents/stream for real-time task push (SSE).
46
- * Falls back to HTTP polling if SSE is unavailable.
38
+ * Agents register with /heartbeat and fetch work from /poll.
47
39
  * All agent actions (complete, fail, progress) use HTTP POST.
48
40
  *
49
41
  * Usage:
@@ -70,15 +62,9 @@ export type ClawroomClientOptions = {
70
62
  * ```
71
63
  */
72
64
  export class ClawroomClient {
73
- private eventSource: EventSource | null = null;
74
65
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
75
66
  private pollTimer: ReturnType<typeof setInterval> | null = null;
76
- private reconnectAttempt = 0;
77
- private consecutiveFails = 0;
78
- private reconnecting = false;
79
67
  private stopped = false;
80
- private mode: "sse" | "polling" = "sse";
81
- private pollCycleCount = 0;
82
68
  private readonly httpBase: string;
83
69
 
84
70
  private taskCallbacks: TaskCallback[] = [];
@@ -92,19 +78,16 @@ export class ClawroomClient {
92
78
 
93
79
  connect(): void {
94
80
  this.stopped = false;
95
- this.reconnecting = false;
96
- this.reconnectAttempt = 0;
97
- this.consecutiveFails = 0;
98
- this.mode = "sse";
99
- this.doConnectSSE();
100
81
  this.startHeartbeat();
82
+ this.stopPolling();
83
+ this.pollTimer = setInterval(() => void this.pollTick(), POLL_INTERVAL_MS);
84
+ void this.register();
101
85
  }
102
86
 
103
87
  disconnect(): void {
104
88
  this.stopped = true;
105
89
  this.stopHeartbeat();
106
90
  this.stopPolling();
107
- if (this.eventSource) { try { this.eventSource.close(); } catch {} this.eventSource = null; }
108
91
  }
109
92
 
110
93
  send(message: AgentMessage): void {
@@ -129,78 +112,32 @@ export class ClawroomClient {
129
112
  if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; }
130
113
  }
131
114
 
132
- // ── SSE ─────────────────────────────────────────────────────────
133
-
134
- private doConnectSSE(): void {
135
- if (this.eventSource) { try { this.eventSource.close(); } catch {} this.eventSource = null; }
136
-
137
- const params = new URLSearchParams({
138
- token: this.options.token,
139
- deviceId: this.options.deviceId,
140
- skills: this.options.skills.join(","),
141
- });
142
-
143
- this.options.log?.info?.(`[clawroom] SSE connecting`);
144
-
145
- try {
146
- this.eventSource = new EventSource(`${this.httpBase}/stream?${params}`);
147
- } catch {
148
- this.triggerReconnect("EventSource failed");
149
- return;
150
- }
151
-
152
- this.eventSource.onopen = () => {
153
- this.options.log?.info?.("[clawroom] SSE connected");
154
- this.reconnectAttempt = 0;
155
- this.consecutiveFails = 0;
156
- if (this.mode === "polling") { this.stopPolling(); this.mode = "sse"; }
157
- };
158
-
159
- this.eventSource.onmessage = (event) => this.handleMessage(event.data);
160
-
161
- this.eventSource.onerror = () => {
162
- if (this.eventSource) { try { this.eventSource.close(); } catch {} this.eventSource = null; }
163
- this.triggerReconnect("SSE error");
164
- };
115
+ private stopPolling(): void {
116
+ if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
165
117
  }
166
118
 
167
- private triggerReconnect(reason: string): void {
168
- if (this.reconnecting || this.stopped) return;
169
- this.consecutiveFails++;
170
-
171
- if (this.consecutiveFails >= 3 && this.mode !== "polling") {
172
- this.options.log?.warn?.(`[clawroom] SSE failed ${this.consecutiveFails}x, switching to polling`);
173
- this.mode = "polling";
174
- this.pollCycleCount = 0;
175
- this.stopPolling();
176
- this.pollTimer = setInterval(() => this.pollTick(), POLL_INTERVAL_MS);
177
- return;
119
+ private async register(): Promise<void> {
120
+ try {
121
+ await this.httpPost("/heartbeat", {
122
+ deviceId: this.options.deviceId,
123
+ skills: this.options.skills,
124
+ });
125
+ } catch (err) {
126
+ this.options.log?.warn?.(`[clawroom] heartbeat error: ${err}`);
178
127
  }
179
-
180
- this.reconnecting = true;
181
- const delayMs = computeBackoff(this.reconnectAttempt);
182
- this.reconnectAttempt++;
183
- this.options.log?.info?.(`[clawroom] reconnecting in ${delayMs}ms (${reason})`);
184
- setTimeout(() => { this.reconnecting = false; if (!this.stopped) this.doConnectSSE(); }, delayMs);
185
- }
186
-
187
- // ── Polling ─────────────────────────────────────────────────────
188
-
189
- private stopPolling(): void {
190
- if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
191
128
  }
192
129
 
193
130
  private async pollTick(): Promise<void> {
194
131
  if (this.stopped) return;
195
- this.pollCycleCount++;
196
132
  try {
197
133
  const res = await this.httpPost("/poll", {});
198
134
  if (res.task) {
199
135
  for (const cb of this.taskCallbacks) cb(res.task);
200
136
  for (const cb of this.claimAckCallbacks) cb({ type: "server.claim_ack", taskId: res.task.taskId, ok: true });
201
137
  }
202
- } catch {}
203
- if (this.pollCycleCount % 6 === 0) this.doConnectSSE();
138
+ } catch (err) {
139
+ this.options.log?.warn?.(`[clawroom] poll error: ${err}`);
140
+ }
204
141
  }
205
142
 
206
143
  // ── HTTP ────────────────────────────────────────────────────────