@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.
- package/package.json +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.
|
|
4
|
-
"description": "Claw Room SDK —
|
|
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
|
|
36
|
+
* Claw Room SDK client using HTTP polling.
|
|
44
37
|
*
|
|
45
|
-
*
|
|
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
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
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
|
-
|
|
138
|
+
} catch (err) {
|
|
139
|
+
this.options.log?.warn?.(`[clawroom] poll error: ${err}`);
|
|
140
|
+
}
|
|
204
141
|
}
|
|
205
142
|
|
|
206
143
|
// ── HTTP ────────────────────────────────────────────────────────
|