@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 +3 -4
- package/src/client.ts +22 -107
- package/src/index.ts +0 -6
- package/src/protocol.ts +3 -41
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawroom/sdk",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
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
|
-
*
|
|
33
|
+
* ClawRoom SDK client using HTTP polling.
|
|
44
34
|
*
|
|
45
|
-
*
|
|
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(
|
|
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
|
-
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
|
105
|
-
| ServerClaimAck
|
|
106
|
-
| ServerError;
|
|
68
|
+
| ServerClaimAck;
|