@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clawroom/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",
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
- "marketplace",
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 TaskListCallback = (tasks: ServerTask[]) => void;
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
- * Claw Room SDK client using HTTP polling.
33
+ * ClawRoom SDK client using HTTP polling.
37
34
  *
38
- * Agents register with /heartbeat and fetch work from /poll.
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
- * // handle incoming task
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.onClaimAck((ack) => {
56
- * if (ack.ok) {
57
- * client.send({ type: "agent.complete", taskId: ack.taskId, output: "Done!" });
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
- private stopped = false;
68
- private readonly httpBase: string;
65
+ protected stopped = false;
66
+ protected readonly httpBase: string;
67
+ protected readonly options: ClawroomClientOptions;
69
68
 
70
- private taskCallbacks: TaskCallback[] = [];
71
- private taskListCallbacks: TaskListCallback[] = [];
72
- private claimAckCallbacks: ClaimAckCallback[] = [];
73
- private errorCallbacks: ErrorCallback[] = [];
69
+ protected taskCallbacks: TaskCallback[] = [];
70
+ protected chatCallbacks: ChatCallback[] = [];
74
71
 
75
- constructor(private readonly options: ClawroomClientOptions) {
76
- this.httpBase = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/stream\/?$/, "").replace(/\/+$/, "");
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
- onTaskList(cb: TaskListCallback): void { this.taskListCallbacks.push(cb); }
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.httpPost("/heartbeat", {}).catch(() => {});
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
- private async register(): Promise<void> {
115
+ protected async register(): Promise<void> {
120
116
  try {
121
- await this.httpPost("/heartbeat", {
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
- private async pollTick(): Promise<void> {
128
+ protected async pollTick(): Promise<void> {
131
129
  if (this.stopped) return;
132
130
  try {
133
- const res = await this.httpPost("/poll", {});
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
- for (const cb of this.claimAckCallbacks) cb({ type: "server.claim_ack", taskId: res.task.taskId, ok: true });
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.httpPost("/complete", { taskId: message.taskId, output: message.output, attachments: message.attachments }); break;
148
- case "agent.fail": await this.httpPost("/fail", { taskId: message.taskId, reason: message.reason }); break;
149
- case "agent.progress": await this.httpPost("/progress", { taskId: message.taskId, message: message.message, percent: message.percent }); break;
150
- case "agent.heartbeat": await this.httpPost("/heartbeat", {}); break;
151
- case "agent.claim": await this.httpPost("/claim", { taskId: message.taskId }); break;
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
- private async httpPost(path: string, body: unknown): Promise<any> {
167
+ protected async httpRequest(method: string, path: string, body: unknown): Promise<any> {
156
168
  const res = await fetch(`${this.httpBase}${path}`, {
157
- method: "POST",
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) throw new Error(`HTTP ${res.status}`);
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
- // ── Message handling ──────────────────────────────────────────────
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
- AgentRelease,
11
+ AgentChatReply,
12
+ AgentTyping,
14
13
  ServerMessage,
15
- ServerWelcome,
16
- ServerPong,
17
14
  ServerTask,
18
- ServerTaskList,
19
- ServerClaimAck,
20
- ServerError,
15
+ ServerChatMessage,
21
16
  } from "./protocol.js";
package/src/protocol.ts CHANGED
@@ -1,24 +1,12 @@
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
  }
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 AgentRelease {
49
- type: "agent.release";
50
- taskId: string;
51
- reason: string;
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
- | AgentRelease;
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 ServerTaskList {
84
- type: "server.task_list";
85
- tasks: ServerTask[];
86
- }
87
-
88
- export interface ServerClaimAck {
89
- type: "server.claim_ack";
90
- taskId: string;
91
- ok: boolean;
92
- reason?: string;
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
- | ServerTaskList
105
- | ServerClaimAck
106
- | ServerError;
82
+ | ServerChatMessage;