@clawroom/sdk 0.2.2 → 0.3.0
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 +59 -55
- package/src/index.ts +3 -8
- package/src/protocol.ts +25 -49
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawroom/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "ClawRoom SDK — polling client and protocol types for connecting any agent to ClawRoom",
|
|
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
|
-
"
|
|
18
|
-
"websocket"
|
|
17
|
+
"workspace"
|
|
19
18
|
],
|
|
20
19
|
"files": [
|
|
21
20
|
"src"
|
package/src/client.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentMessage,
|
|
3
|
-
ServerClaimAck,
|
|
4
|
-
ServerMessage,
|
|
5
3
|
ServerTask,
|
|
4
|
+
ServerChatMessage,
|
|
6
5
|
} from "./protocol.js";
|
|
7
6
|
|
|
8
7
|
const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
|
|
@@ -10,10 +9,8 @@ const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
|
|
|
10
9
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
11
10
|
const POLL_INTERVAL_MS = 10_000;
|
|
12
11
|
|
|
13
|
-
type TaskCallback = (task: ServerTask) => void;
|
|
14
|
-
type
|
|
15
|
-
type ClaimAckCallback = (ack: ServerClaimAck) => void;
|
|
16
|
-
type ErrorCallback = (error: ServerMessage & { type: "server.error" }) => void;
|
|
12
|
+
export type TaskCallback = (task: ServerTask) => void;
|
|
13
|
+
export type ChatCallback = (messages: ServerChatMessage[]) => void;
|
|
17
14
|
|
|
18
15
|
export type ClawroomClientOptions = {
|
|
19
16
|
/** HTTP base URL. Defaults to https://clawroom.site9.ai/api/agents */
|
|
@@ -26,17 +23,17 @@ export type ClawroomClientOptions = {
|
|
|
26
23
|
skills: string[];
|
|
27
24
|
/** Optional logger */
|
|
28
25
|
log?: {
|
|
29
|
-
info?: (...args: unknown[]) => void;
|
|
30
|
-
warn?: (...args: unknown[]) => void;
|
|
31
|
-
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;
|
|
32
29
|
};
|
|
33
30
|
};
|
|
34
31
|
|
|
35
32
|
/**
|
|
36
|
-
*
|
|
33
|
+
* ClawRoom SDK client using HTTP polling.
|
|
37
34
|
*
|
|
38
|
-
* Agents register with /heartbeat and
|
|
39
|
-
* All agent actions (complete, fail, progress) use HTTP POST.
|
|
35
|
+
* Agents register with /heartbeat and receive assigned tasks + chat via /poll.
|
|
36
|
+
* All agent actions (complete, fail, progress, chat reply) use HTTP POST.
|
|
40
37
|
*
|
|
41
38
|
* Usage:
|
|
42
39
|
* ```ts
|
|
@@ -49,12 +46,13 @@ export type ClawroomClientOptions = {
|
|
|
49
46
|
* });
|
|
50
47
|
*
|
|
51
48
|
* client.onTask((task) => {
|
|
52
|
-
* //
|
|
49
|
+
* // task was assigned to this agent — execute it
|
|
50
|
+
* client.send({ type: "agent.complete", taskId: task.taskId, output: "Done!" });
|
|
53
51
|
* });
|
|
54
52
|
*
|
|
55
|
-
* client.
|
|
56
|
-
*
|
|
57
|
-
* client.send({ type: "agent.
|
|
53
|
+
* client.onChatMessage((messages) => {
|
|
54
|
+
* for (const msg of messages) {
|
|
55
|
+
* client.send({ type: "agent.chat.reply", channelId: msg.channelId, content: "Hello!" });
|
|
58
56
|
* }
|
|
59
57
|
* });
|
|
60
58
|
*
|
|
@@ -64,16 +62,16 @@ export type ClawroomClientOptions = {
|
|
|
64
62
|
export class ClawroomClient {
|
|
65
63
|
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
66
64
|
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
protected stopped = false;
|
|
66
|
+
protected readonly httpBase: string;
|
|
67
|
+
protected readonly options: ClawroomClientOptions;
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
private claimAckCallbacks: ClaimAckCallback[] = [];
|
|
73
|
-
private errorCallbacks: ErrorCallback[] = [];
|
|
69
|
+
protected taskCallbacks: TaskCallback[] = [];
|
|
70
|
+
protected chatCallbacks: ChatCallback[] = [];
|
|
74
71
|
|
|
75
|
-
constructor(
|
|
76
|
-
this.
|
|
72
|
+
constructor(options: ClawroomClientOptions) {
|
|
73
|
+
this.options = options;
|
|
74
|
+
this.httpBase = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, "");
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
connect(): void {
|
|
@@ -95,16 +93,14 @@ export class ClawroomClient {
|
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
onTask(cb: TaskCallback): void { this.taskCallbacks.push(cb); }
|
|
98
|
-
|
|
99
|
-
onClaimAck(cb: ClaimAckCallback): void { this.claimAckCallbacks.push(cb); }
|
|
100
|
-
onError(cb: ErrorCallback): void { this.errorCallbacks.push(cb); }
|
|
96
|
+
onChatMessage(cb: ChatCallback): void { this.chatCallbacks.push(cb); }
|
|
101
97
|
|
|
102
98
|
// ── Heartbeat ───────────────────────────────────────────────────
|
|
103
99
|
|
|
104
100
|
private startHeartbeat(): void {
|
|
105
101
|
this.stopHeartbeat();
|
|
106
102
|
this.heartbeatTimer = setInterval(() => {
|
|
107
|
-
if (!this.stopped) this.
|
|
103
|
+
if (!this.stopped) void this.register();
|
|
108
104
|
}, HEARTBEAT_INTERVAL_MS);
|
|
109
105
|
}
|
|
110
106
|
|
|
@@ -116,64 +112,72 @@ export class ClawroomClient {
|
|
|
116
112
|
if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
|
|
117
113
|
}
|
|
118
114
|
|
|
119
|
-
|
|
115
|
+
protected async register(): Promise<void> {
|
|
120
116
|
try {
|
|
121
|
-
await this.
|
|
117
|
+
await this.httpRequest("POST", "/heartbeat", {
|
|
122
118
|
deviceId: this.options.deviceId,
|
|
123
119
|
skills: this.options.skills,
|
|
124
120
|
});
|
|
121
|
+
this.onPollSuccess(undefined);
|
|
125
122
|
} catch (err) {
|
|
126
123
|
this.options.log?.warn?.(`[clawroom] heartbeat error: ${err}`);
|
|
124
|
+
this.onPollError(err);
|
|
127
125
|
}
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
|
|
128
|
+
protected async pollTick(): Promise<void> {
|
|
131
129
|
if (this.stopped) return;
|
|
132
130
|
try {
|
|
133
|
-
const res = await this.
|
|
131
|
+
const res = await this.httpRequest("POST", "/poll", {});
|
|
132
|
+
this.onPollSuccess(res?.agentId);
|
|
133
|
+
|
|
134
134
|
if (res.task) {
|
|
135
|
+
this.options.log?.info?.(`[clawroom] received task ${res.task.taskId}: ${res.task.title}`);
|
|
135
136
|
for (const cb of this.taskCallbacks) cb(res.task);
|
|
136
|
-
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (res.chat && Array.isArray(res.chat) && res.chat.length > 0) {
|
|
140
|
+
this.options.log?.info?.(`[clawroom] received ${res.chat.length} chat mention(s)`);
|
|
141
|
+
for (const cb of this.chatCallbacks) cb(res.chat);
|
|
137
142
|
}
|
|
138
143
|
} catch (err) {
|
|
139
144
|
this.options.log?.warn?.(`[clawroom] poll error: ${err}`);
|
|
145
|
+
this.onPollError(err);
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
|
|
149
|
+
/** Override in subclass for lifecycle tracking */
|
|
150
|
+
protected onPollSuccess(_agentId: string | undefined): void {}
|
|
151
|
+
/** Override in subclass for lifecycle tracking */
|
|
152
|
+
protected onPollError(_err: unknown): void {}
|
|
153
|
+
|
|
143
154
|
// ── HTTP ────────────────────────────────────────────────────────
|
|
144
155
|
|
|
145
156
|
private async sendViaHttp(message: AgentMessage): Promise<void> {
|
|
146
157
|
switch (message.type) {
|
|
147
|
-
case "agent.complete": await this.
|
|
148
|
-
case "agent.fail": await this.
|
|
149
|
-
case "agent.progress": await this.
|
|
150
|
-
case "agent.heartbeat": await this.
|
|
151
|
-
case "agent.
|
|
158
|
+
case "agent.complete": await this.httpRequest("POST", "/complete", { taskId: message.taskId, output: message.output, attachments: message.attachments }); break;
|
|
159
|
+
case "agent.fail": await this.httpRequest("POST", "/fail", { taskId: message.taskId, reason: message.reason }); break;
|
|
160
|
+
case "agent.progress": await this.httpRequest("POST", "/progress", { taskId: message.taskId, message: message.message, percent: message.percent }); break;
|
|
161
|
+
case "agent.heartbeat": await this.httpRequest("POST", "/heartbeat", {}); break;
|
|
162
|
+
case "agent.chat.reply": await this.httpRequest("POST", "/chat/reply", { channelId: message.channelId, content: message.content, replyTo: message.replyTo }); break;
|
|
163
|
+
case "agent.typing": await this.httpRequest("POST", "/typing", { channelId: message.channelId }); break;
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
|
|
155
|
-
|
|
167
|
+
protected async httpRequest(method: string, path: string, body: unknown): Promise<any> {
|
|
156
168
|
const res = await fetch(`${this.httpBase}${path}`, {
|
|
157
|
-
method
|
|
169
|
+
method,
|
|
158
170
|
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${this.options.token}` },
|
|
159
171
|
body: JSON.stringify(body),
|
|
160
172
|
});
|
|
161
|
-
if (!res.ok)
|
|
173
|
+
if (!res.ok) {
|
|
174
|
+
const text = await res.text().catch(() => "");
|
|
175
|
+
this.onHttpError(res.status, text);
|
|
176
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
177
|
+
}
|
|
162
178
|
return res.json();
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
private handleMessage(raw: string): void {
|
|
168
|
-
let msg: ServerMessage;
|
|
169
|
-
try { msg = JSON.parse(raw) as ServerMessage; } catch { return; }
|
|
170
|
-
switch (msg.type) {
|
|
171
|
-
case "server.welcome": this.options.log?.info?.(`[clawroom] welcome, agentId=${msg.agentId}`); break;
|
|
172
|
-
case "server.pong": break;
|
|
173
|
-
case "server.task": for (const cb of this.taskCallbacks) cb(msg); break;
|
|
174
|
-
case "server.task_list": for (const cb of this.taskListCallbacks) cb(msg.tasks); break;
|
|
175
|
-
case "server.claim_ack": for (const cb of this.claimAckCallbacks) cb(msg); break;
|
|
176
|
-
case "server.error": for (const cb of this.errorCallbacks) cb(msg); break;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
181
|
+
/** Override in subclass for error handling (e.g. 401 auto-stop) */
|
|
182
|
+
protected onHttpError(_status: number, _text: string): void {}
|
|
179
183
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,19 +3,14 @@ export type { ClawroomClientOptions } from "./client.js";
|
|
|
3
3
|
|
|
4
4
|
export type {
|
|
5
5
|
AgentMessage,
|
|
6
|
-
AgentHello,
|
|
7
6
|
AgentHeartbeat,
|
|
8
|
-
AgentClaim,
|
|
9
7
|
AgentComplete,
|
|
10
8
|
AgentProgress,
|
|
11
9
|
AgentResultFile,
|
|
12
10
|
AgentFail,
|
|
13
|
-
|
|
11
|
+
AgentChatReply,
|
|
12
|
+
AgentTyping,
|
|
14
13
|
ServerMessage,
|
|
15
|
-
ServerWelcome,
|
|
16
|
-
ServerPong,
|
|
17
14
|
ServerTask,
|
|
18
|
-
|
|
19
|
-
ServerClaimAck,
|
|
20
|
-
ServerError,
|
|
15
|
+
ServerChatMessage,
|
|
21
16
|
} from "./protocol.js";
|
package/src/protocol.ts
CHANGED
|
@@ -1,24 +1,12 @@
|
|
|
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
|
}
|
|
16
9
|
|
|
17
|
-
export interface AgentClaim {
|
|
18
|
-
type: "agent.claim";
|
|
19
|
-
taskId: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
10
|
export interface AgentResultFile {
|
|
23
11
|
filename: string;
|
|
24
12
|
mimeType: string;
|
|
@@ -45,32 +33,28 @@ export interface AgentFail {
|
|
|
45
33
|
reason: string;
|
|
46
34
|
}
|
|
47
35
|
|
|
48
|
-
export interface
|
|
49
|
-
type: "agent.
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
export interface AgentChatReply {
|
|
37
|
+
type: "agent.chat.reply";
|
|
38
|
+
channelId: string;
|
|
39
|
+
content: string;
|
|
40
|
+
replyTo?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AgentTyping {
|
|
44
|
+
type: "agent.typing";
|
|
45
|
+
channelId: string;
|
|
52
46
|
}
|
|
53
47
|
|
|
54
48
|
export type AgentMessage =
|
|
55
|
-
| AgentHello
|
|
56
49
|
| AgentHeartbeat
|
|
57
|
-
| AgentClaim
|
|
58
50
|
| AgentComplete
|
|
59
51
|
| AgentProgress
|
|
60
52
|
| AgentFail
|
|
61
|
-
|
|
|
53
|
+
| AgentChatReply
|
|
54
|
+
| AgentTyping;
|
|
62
55
|
|
|
63
56
|
// ---- Server -> Agent ----
|
|
64
57
|
|
|
65
|
-
export interface ServerWelcome {
|
|
66
|
-
type: "server.welcome";
|
|
67
|
-
agentId: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export interface ServerPong {
|
|
71
|
-
type: "server.pong";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
58
|
export interface ServerTask {
|
|
75
59
|
type: "server.task";
|
|
76
60
|
taskId: string;
|
|
@@ -80,27 +64,19 @@ export interface ServerTask {
|
|
|
80
64
|
skillTags: string[];
|
|
81
65
|
}
|
|
82
66
|
|
|
83
|
-
export interface
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface ServerError {
|
|
96
|
-
type: "server.error";
|
|
97
|
-
message: string;
|
|
67
|
+
export interface ServerChatMessage {
|
|
68
|
+
messageId: string;
|
|
69
|
+
channelId: string;
|
|
70
|
+
content: string;
|
|
71
|
+
context: Array<{
|
|
72
|
+
id: string;
|
|
73
|
+
senderType: string;
|
|
74
|
+
senderName: string;
|
|
75
|
+
content: string;
|
|
76
|
+
createdAt: number;
|
|
77
|
+
}>;
|
|
98
78
|
}
|
|
99
79
|
|
|
100
80
|
export type ServerMessage =
|
|
101
|
-
| ServerWelcome
|
|
102
|
-
| ServerPong
|
|
103
81
|
| ServerTask
|
|
104
|
-
|
|
|
105
|
-
| ServerClaimAck
|
|
106
|
-
| ServerError;
|
|
82
|
+
| ServerChatMessage;
|