@clawroom/sdk 0.2.0 → 0.2.3

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/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.3",
4
+ "description": "ClawRoom SDK — polling client and protocol types for connecting any agent to the ClawRoom marketplace",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./src/index.ts",
@@ -14,8 +14,7 @@
14
14
  "clawroom",
15
15
  "sdk",
16
16
  "agent",
17
- "marketplace",
18
- "websocket"
17
+ "marketplace"
19
18
  ],
20
19
  "files": [
21
20
  "src"
package/src/client.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import type {
2
2
  AgentMessage,
3
3
  ServerClaimAck,
4
- ServerMessage,
5
4
  ServerTask,
6
5
  } from "./protocol.js";
7
6
 
@@ -9,18 +8,9 @@ const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
9
8
 
10
9
  const HEARTBEAT_INTERVAL_MS = 30_000;
11
10
  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
11
 
20
12
  type TaskCallback = (task: ServerTask) => void;
21
- type TaskListCallback = (tasks: ServerTask[]) => void;
22
13
  type ClaimAckCallback = (ack: ServerClaimAck) => void;
23
- type ErrorCallback = (error: ServerMessage & { type: "server.error" }) => void;
24
14
 
25
15
  export type ClawroomClientOptions = {
26
16
  /** HTTP base URL. Defaults to https://clawroom.site9.ai/api/agents */
@@ -33,17 +23,16 @@ export type ClawroomClientOptions = {
33
23
  skills: string[];
34
24
  /** Optional logger */
35
25
  log?: {
36
- info?: (...args: unknown[]) => void;
37
- warn?: (...args: unknown[]) => void;
38
- error?: (...args: unknown[]) => void;
26
+ info?: (message: string, ...args: unknown[]) => void;
27
+ warn?: (message: string, ...args: unknown[]) => void;
28
+ error?: (message: string, ...args: unknown[]) => void;
39
29
  };
40
30
  };
41
31
 
42
32
  /**
43
- * Claw Room SDK client using SSE + HTTP.
33
+ * ClawRoom SDK client using HTTP polling.
44
34
  *
45
- * Connects to /api/agents/stream for real-time task push (SSE).
46
- * Falls back to HTTP polling if SSE is unavailable.
35
+ * Agents register with /heartbeat and fetch work from /poll.
47
36
  * All agent actions (complete, fail, progress) use HTTP POST.
48
37
  *
49
38
  * Usage:
@@ -70,41 +59,30 @@ export type ClawroomClientOptions = {
70
59
  * ```
71
60
  */
72
61
  export class ClawroomClient {
73
- private eventSource: EventSource | null = null;
74
62
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
75
63
  private pollTimer: ReturnType<typeof setInterval> | null = null;
76
- private reconnectAttempt = 0;
77
- private consecutiveFails = 0;
78
- private reconnecting = false;
79
64
  private stopped = false;
80
- private mode: "sse" | "polling" = "sse";
81
- private pollCycleCount = 0;
82
65
  private readonly httpBase: string;
83
66
 
84
67
  private taskCallbacks: TaskCallback[] = [];
85
- private taskListCallbacks: TaskListCallback[] = [];
86
68
  private claimAckCallbacks: ClaimAckCallback[] = [];
87
- private errorCallbacks: ErrorCallback[] = [];
88
69
 
89
70
  constructor(private readonly options: ClawroomClientOptions) {
90
- this.httpBase = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/stream\/?$/, "").replace(/\/+$/, "");
71
+ this.httpBase = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, "");
91
72
  }
92
73
 
93
74
  connect(): void {
94
75
  this.stopped = false;
95
- this.reconnecting = false;
96
- this.reconnectAttempt = 0;
97
- this.consecutiveFails = 0;
98
- this.mode = "sse";
99
- this.doConnectSSE();
100
76
  this.startHeartbeat();
77
+ this.stopPolling();
78
+ this.pollTimer = setInterval(() => void this.pollTick(), POLL_INTERVAL_MS);
79
+ void this.register();
101
80
  }
102
81
 
103
82
  disconnect(): void {
104
83
  this.stopped = true;
105
84
  this.stopHeartbeat();
106
85
  this.stopPolling();
107
- if (this.eventSource) { try { this.eventSource.close(); } catch {} this.eventSource = null; }
108
86
  }
109
87
 
110
88
  send(message: AgentMessage): void {
@@ -112,9 +90,7 @@ export class ClawroomClient {
112
90
  }
113
91
 
114
92
  onTask(cb: TaskCallback): void { this.taskCallbacks.push(cb); }
115
- onTaskList(cb: TaskListCallback): void { this.taskListCallbacks.push(cb); }
116
93
  onClaimAck(cb: ClaimAckCallback): void { this.claimAckCallbacks.push(cb); }
117
- onError(cb: ErrorCallback): void { this.errorCallbacks.push(cb); }
118
94
 
119
95
  // ── Heartbeat ───────────────────────────────────────────────────
120
96
 
@@ -129,78 +105,32 @@ export class ClawroomClient {
129
105
  if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; }
130
106
  }
131
107
 
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
- };
108
+ private stopPolling(): void {
109
+ if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
165
110
  }
166
111
 
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;
112
+ private async register(): Promise<void> {
113
+ try {
114
+ await this.httpPost("/heartbeat", {
115
+ deviceId: this.options.deviceId,
116
+ skills: this.options.skills,
117
+ });
118
+ } catch (err) {
119
+ this.options.log?.warn?.(`[clawroom] heartbeat error: ${err}`);
178
120
  }
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
121
  }
192
122
 
193
123
  private async pollTick(): Promise<void> {
194
124
  if (this.stopped) return;
195
- this.pollCycleCount++;
196
125
  try {
197
126
  const res = await this.httpPost("/poll", {});
198
127
  if (res.task) {
199
128
  for (const cb of this.taskCallbacks) cb(res.task);
200
129
  for (const cb of this.claimAckCallbacks) cb({ type: "server.claim_ack", taskId: res.task.taskId, ok: true });
201
130
  }
202
- } catch {}
203
- if (this.pollCycleCount % 6 === 0) this.doConnectSSE();
131
+ } catch (err) {
132
+ this.options.log?.warn?.(`[clawroom] poll error: ${err}`);
133
+ }
204
134
  }
205
135
 
206
136
  // ── HTTP ────────────────────────────────────────────────────────
@@ -224,19 +154,4 @@ export class ClawroomClient {
224
154
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
225
155
  return res.json();
226
156
  }
227
-
228
- // ── Message handling ──────────────────────────────────────────────
229
-
230
- private handleMessage(raw: string): void {
231
- let msg: ServerMessage;
232
- try { msg = JSON.parse(raw) as ServerMessage; } catch { return; }
233
- switch (msg.type) {
234
- case "server.welcome": this.options.log?.info?.(`[clawroom] welcome, agentId=${msg.agentId}`); break;
235
- case "server.pong": break;
236
- case "server.task": for (const cb of this.taskCallbacks) cb(msg); break;
237
- case "server.task_list": for (const cb of this.taskListCallbacks) cb(msg.tasks); break;
238
- case "server.claim_ack": for (const cb of this.claimAckCallbacks) cb(msg); break;
239
- case "server.error": for (const cb of this.errorCallbacks) cb(msg); break;
240
- }
241
- }
242
157
  }
package/src/index.ts CHANGED
@@ -3,19 +3,13 @@ export type { ClawroomClientOptions } from "./client.js";
3
3
 
4
4
  export type {
5
5
  AgentMessage,
6
- AgentHello,
7
6
  AgentHeartbeat,
8
7
  AgentClaim,
9
8
  AgentComplete,
10
9
  AgentProgress,
11
10
  AgentResultFile,
12
11
  AgentFail,
13
- AgentRelease,
14
12
  ServerMessage,
15
- ServerWelcome,
16
- ServerPong,
17
13
  ServerTask,
18
- ServerTaskList,
19
14
  ServerClaimAck,
20
- ServerError,
21
15
  } from "./protocol.js";
package/src/protocol.ts CHANGED
@@ -1,15 +1,8 @@
1
- // Claw Room protocol types.
1
+ // ClawRoom protocol types.
2
2
  // These define the messages exchanged between agents and the server.
3
3
 
4
4
  // ---- Agent -> Server ----
5
5
 
6
- export interface AgentHello {
7
- type: "agent.hello";
8
- deviceId: string;
9
- skills: string[];
10
- kind?: string;
11
- }
12
-
13
6
  export interface AgentHeartbeat {
14
7
  type: "agent.heartbeat";
15
8
  }
@@ -45,32 +38,15 @@ export interface AgentFail {
45
38
  reason: string;
46
39
  }
47
40
 
48
- export interface AgentRelease {
49
- type: "agent.release";
50
- taskId: string;
51
- reason: string;
52
- }
53
-
54
41
  export type AgentMessage =
55
- | AgentHello
56
42
  | AgentHeartbeat
57
43
  | AgentClaim
58
44
  | AgentComplete
59
45
  | AgentProgress
60
- | AgentFail
61
- | AgentRelease;
46
+ | AgentFail;
62
47
 
63
48
  // ---- Server -> Agent ----
64
49
 
65
- export interface ServerWelcome {
66
- type: "server.welcome";
67
- agentId: string;
68
- }
69
-
70
- export interface ServerPong {
71
- type: "server.pong";
72
- }
73
-
74
50
  export interface ServerTask {
75
51
  type: "server.task";
76
52
  taskId: string;
@@ -80,11 +56,6 @@ export interface ServerTask {
80
56
  skillTags: string[];
81
57
  }
82
58
 
83
- export interface ServerTaskList {
84
- type: "server.task_list";
85
- tasks: ServerTask[];
86
- }
87
-
88
59
  export interface ServerClaimAck {
89
60
  type: "server.claim_ack";
90
61
  taskId: string;
@@ -92,15 +63,6 @@ export interface ServerClaimAck {
92
63
  reason?: string;
93
64
  }
94
65
 
95
- export interface ServerError {
96
- type: "server.error";
97
- message: string;
98
- }
99
-
100
66
  export type ServerMessage =
101
- | ServerWelcome
102
- | ServerPong
103
67
  | ServerTask
104
- | ServerTaskList
105
- | ServerClaimAck
106
- | ServerError;
68
+ | ServerClaimAck;