@clawroom/sdk 0.2.3 → 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.3",
4
- "description": "ClawRoom SDK — polling client and protocol types for connecting any agent to the ClawRoom 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,7 +14,7 @@
14
14
  "clawroom",
15
15
  "sdk",
16
16
  "agent",
17
- "marketplace"
17
+ "workspace"
18
18
  ],
19
19
  "files": [
20
20
  "src"
package/src/client.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  AgentMessage,
3
- ServerClaimAck,
4
3
  ServerTask,
4
+ ServerChatMessage,
5
5
  } from "./protocol.js";
6
6
 
7
7
  const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
@@ -9,8 +9,8 @@ const DEFAULT_ENDPOINT = "https://clawroom.site9.ai/api/agents";
9
9
  const HEARTBEAT_INTERVAL_MS = 30_000;
10
10
  const POLL_INTERVAL_MS = 10_000;
11
11
 
12
- type TaskCallback = (task: ServerTask) => void;
13
- type ClaimAckCallback = (ack: ServerClaimAck) => void;
12
+ export type TaskCallback = (task: ServerTask) => void;
13
+ export type ChatCallback = (messages: ServerChatMessage[]) => void;
14
14
 
15
15
  export type ClawroomClientOptions = {
16
16
  /** HTTP base URL. Defaults to https://clawroom.site9.ai/api/agents */
@@ -32,8 +32,8 @@ export type ClawroomClientOptions = {
32
32
  /**
33
33
  * ClawRoom SDK client using HTTP polling.
34
34
  *
35
- * Agents register with /heartbeat and fetch work from /poll.
36
- * 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.
37
37
  *
38
38
  * Usage:
39
39
  * ```ts
@@ -46,12 +46,13 @@ export type ClawroomClientOptions = {
46
46
  * });
47
47
  *
48
48
  * client.onTask((task) => {
49
- * // handle incoming task
49
+ * // task was assigned to this agent — execute it
50
+ * client.send({ type: "agent.complete", taskId: task.taskId, output: "Done!" });
50
51
  * });
51
52
  *
52
- * client.onClaimAck((ack) => {
53
- * if (ack.ok) {
54
- * 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!" });
55
56
  * }
56
57
  * });
57
58
  *
@@ -61,13 +62,15 @@ export type ClawroomClientOptions = {
61
62
  export class ClawroomClient {
62
63
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
63
64
  private pollTimer: ReturnType<typeof setInterval> | null = null;
64
- private stopped = false;
65
- private readonly httpBase: string;
65
+ protected stopped = false;
66
+ protected readonly httpBase: string;
67
+ protected readonly options: ClawroomClientOptions;
66
68
 
67
- private taskCallbacks: TaskCallback[] = [];
68
- private claimAckCallbacks: ClaimAckCallback[] = [];
69
+ protected taskCallbacks: TaskCallback[] = [];
70
+ protected chatCallbacks: ChatCallback[] = [];
69
71
 
70
- constructor(private readonly options: ClawroomClientOptions) {
72
+ constructor(options: ClawroomClientOptions) {
73
+ this.options = options;
71
74
  this.httpBase = (options.endpoint || DEFAULT_ENDPOINT).replace(/\/+$/, "");
72
75
  }
73
76
 
@@ -90,14 +93,14 @@ export class ClawroomClient {
90
93
  }
91
94
 
92
95
  onTask(cb: TaskCallback): void { this.taskCallbacks.push(cb); }
93
- onClaimAck(cb: ClaimAckCallback): void { this.claimAckCallbacks.push(cb); }
96
+ onChatMessage(cb: ChatCallback): void { this.chatCallbacks.push(cb); }
94
97
 
95
98
  // ── Heartbeat ───────────────────────────────────────────────────
96
99
 
97
100
  private startHeartbeat(): void {
98
101
  this.stopHeartbeat();
99
102
  this.heartbeatTimer = setInterval(() => {
100
- if (!this.stopped) this.httpPost("/heartbeat", {}).catch(() => {});
103
+ if (!this.stopped) void this.register();
101
104
  }, HEARTBEAT_INTERVAL_MS);
102
105
  }
103
106
 
@@ -109,49 +112,72 @@ export class ClawroomClient {
109
112
  if (this.pollTimer) { clearInterval(this.pollTimer); this.pollTimer = null; }
110
113
  }
111
114
 
112
- private async register(): Promise<void> {
115
+ protected async register(): Promise<void> {
113
116
  try {
114
- await this.httpPost("/heartbeat", {
117
+ await this.httpRequest("POST", "/heartbeat", {
115
118
  deviceId: this.options.deviceId,
116
119
  skills: this.options.skills,
117
120
  });
121
+ this.onPollSuccess(undefined);
118
122
  } catch (err) {
119
123
  this.options.log?.warn?.(`[clawroom] heartbeat error: ${err}`);
124
+ this.onPollError(err);
120
125
  }
121
126
  }
122
127
 
123
- private async pollTick(): Promise<void> {
128
+ protected async pollTick(): Promise<void> {
124
129
  if (this.stopped) return;
125
130
  try {
126
- const res = await this.httpPost("/poll", {});
131
+ const res = await this.httpRequest("POST", "/poll", {});
132
+ this.onPollSuccess(res?.agentId);
133
+
127
134
  if (res.task) {
135
+ this.options.log?.info?.(`[clawroom] received task ${res.task.taskId}: ${res.task.title}`);
128
136
  for (const cb of this.taskCallbacks) cb(res.task);
129
- 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);
130
142
  }
131
143
  } catch (err) {
132
144
  this.options.log?.warn?.(`[clawroom] poll error: ${err}`);
145
+ this.onPollError(err);
133
146
  }
134
147
  }
135
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
+
136
154
  // ── HTTP ────────────────────────────────────────────────────────
137
155
 
138
156
  private async sendViaHttp(message: AgentMessage): Promise<void> {
139
157
  switch (message.type) {
140
- case "agent.complete": await this.httpPost("/complete", { taskId: message.taskId, output: message.output, attachments: message.attachments }); break;
141
- case "agent.fail": await this.httpPost("/fail", { taskId: message.taskId, reason: message.reason }); break;
142
- case "agent.progress": await this.httpPost("/progress", { taskId: message.taskId, message: message.message, percent: message.percent }); break;
143
- case "agent.heartbeat": await this.httpPost("/heartbeat", {}); break;
144
- 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;
145
164
  }
146
165
  }
147
166
 
148
- private async httpPost(path: string, body: unknown): Promise<any> {
167
+ protected async httpRequest(method: string, path: string, body: unknown): Promise<any> {
149
168
  const res = await fetch(`${this.httpBase}${path}`, {
150
- method: "POST",
169
+ method,
151
170
  headers: { "Content-Type": "application/json", "Authorization": `Bearer ${this.options.token}` },
152
171
  body: JSON.stringify(body),
153
172
  });
154
- 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
+ }
155
178
  return res.json();
156
179
  }
180
+
181
+ /** Override in subclass for error handling (e.g. 401 auto-stop) */
182
+ protected onHttpError(_status: number, _text: string): void {}
157
183
  }
package/src/index.ts CHANGED
@@ -4,12 +4,13 @@ export type { ClawroomClientOptions } from "./client.js";
4
4
  export type {
5
5
  AgentMessage,
6
6
  AgentHeartbeat,
7
- AgentClaim,
8
7
  AgentComplete,
9
8
  AgentProgress,
10
9
  AgentResultFile,
11
10
  AgentFail,
11
+ AgentChatReply,
12
+ AgentTyping,
12
13
  ServerMessage,
13
14
  ServerTask,
14
- ServerClaimAck,
15
+ ServerChatMessage,
15
16
  } from "./protocol.js";
package/src/protocol.ts CHANGED
@@ -7,11 +7,6 @@ export interface AgentHeartbeat {
7
7
  type: "agent.heartbeat";
8
8
  }
9
9
 
10
- export interface AgentClaim {
11
- type: "agent.claim";
12
- taskId: string;
13
- }
14
-
15
10
  export interface AgentResultFile {
16
11
  filename: string;
17
12
  mimeType: string;
@@ -38,12 +33,25 @@ export interface AgentFail {
38
33
  reason: string;
39
34
  }
40
35
 
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;
46
+ }
47
+
41
48
  export type AgentMessage =
42
49
  | AgentHeartbeat
43
- | AgentClaim
44
50
  | AgentComplete
45
51
  | AgentProgress
46
- | AgentFail;
52
+ | AgentFail
53
+ | AgentChatReply
54
+ | AgentTyping;
47
55
 
48
56
  // ---- Server -> Agent ----
49
57
 
@@ -56,13 +64,19 @@ export interface ServerTask {
56
64
  skillTags: string[];
57
65
  }
58
66
 
59
- export interface ServerClaimAck {
60
- type: "server.claim_ack";
61
- taskId: string;
62
- ok: boolean;
63
- reason?: 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
+ }>;
64
78
  }
65
79
 
66
80
  export type ServerMessage =
67
81
  | ServerTask
68
- | ServerClaimAck;
82
+ | ServerChatMessage;