@clawroom/sdk 0.3.0 → 0.5.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 +1 -1
- package/src/client.ts +3 -0
- package/src/index.ts +2 -0
- package/src/machine-client.ts +127 -0
- package/src/protocol.ts +1 -0
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type ClawroomClientOptions = {
|
|
|
21
21
|
deviceId: string;
|
|
22
22
|
/** Agent skills */
|
|
23
23
|
skills: string[];
|
|
24
|
+
/** Agent kind (e.g. "openclaw", "claude-code", "codex", "custom"). Defaults to "openclaw" */
|
|
25
|
+
kind?: string;
|
|
24
26
|
/** Optional logger */
|
|
25
27
|
log?: {
|
|
26
28
|
info?: (message: string, ...args: unknown[]) => void;
|
|
@@ -117,6 +119,7 @@ export class ClawroomClient {
|
|
|
117
119
|
await this.httpRequest("POST", "/heartbeat", {
|
|
118
120
|
deviceId: this.options.deviceId,
|
|
119
121
|
skills: this.options.skills,
|
|
122
|
+
kind: this.options.kind ?? "openclaw",
|
|
120
123
|
});
|
|
121
124
|
this.onPollSuccess(undefined);
|
|
122
125
|
} catch (err) {
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { ClawroomClient } from "./client.js";
|
|
2
2
|
export type { ClawroomClientOptions } from "./client.js";
|
|
3
|
+
export { ClawroomMachineClient } from "./machine-client.js";
|
|
4
|
+
export type { ClawroomMachineClientOptions } from "./machine-client.js";
|
|
3
5
|
|
|
4
6
|
export type {
|
|
5
7
|
AgentMessage,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawRoom Machine Client — machine-level auth with multi-agent support.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as os from "node:os";
|
|
6
|
+
|
|
7
|
+
export type ClawroomMachineClientOptions = {
|
|
8
|
+
endpoint?: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
hostname?: string;
|
|
11
|
+
capabilities?: string[];
|
|
12
|
+
log?: {
|
|
13
|
+
info?: (message: string, ...args: unknown[]) => void;
|
|
14
|
+
warn?: (message: string, ...args: unknown[]) => void;
|
|
15
|
+
error?: (message: string, ...args: unknown[]) => void;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type AgentWork = {
|
|
20
|
+
agentId: string;
|
|
21
|
+
agentName: string;
|
|
22
|
+
task: { type: "server.task"; taskId: string; title: string; description: string; input: string; skillTags: string[] } | null;
|
|
23
|
+
chat: Array<{ messageId: string; channelId: string; content: string; isMention: boolean; context: unknown[] }> | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export class ClawroomMachineClient {
|
|
27
|
+
private options: ClawroomMachineClientOptions;
|
|
28
|
+
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
29
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
30
|
+
private taskHandler: ((agentId: string, task: AgentWork["task"]) => void) | null = null;
|
|
31
|
+
private chatHandler: ((agentId: string, messages: NonNullable<AgentWork["chat"]>) => void) | null = null;
|
|
32
|
+
private _connected = false;
|
|
33
|
+
private _stopped = false;
|
|
34
|
+
|
|
35
|
+
constructor(options: ClawroomMachineClientOptions) {
|
|
36
|
+
this.options = options;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private get baseUrl(): string {
|
|
40
|
+
return this.options.endpoint ?? "http://localhost:3000/api/machines";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private async httpRequest(method: string, path: string, body?: unknown): Promise<unknown> {
|
|
44
|
+
const url = `${this.baseUrl}${path}`;
|
|
45
|
+
const res = await fetch(url, {
|
|
46
|
+
method,
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Authorization: `Bearer ${this.options.apiKey}`,
|
|
50
|
+
},
|
|
51
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
|
54
|
+
return res.json();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
onAgentTask(handler: (agentId: string, task: AgentWork["task"]) => void) {
|
|
58
|
+
this.taskHandler = handler;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
onAgentChat(handler: (agentId: string, messages: NonNullable<AgentWork["chat"]>) => void) {
|
|
62
|
+
this.chatHandler = handler;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async sendAgentComplete(agentId: string, taskId: string, output: string) {
|
|
66
|
+
// Use agent-level API via machine auth proxy (or direct)
|
|
67
|
+
await this.httpRequest("POST", "/complete", { agentId, taskId, output });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async sendAgentFail(agentId: string, taskId: string, reason: string) {
|
|
71
|
+
await this.httpRequest("POST", "/fail", { agentId, taskId, reason });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async sendAgentChatReply(agentId: string, channelId: string, content: string) {
|
|
75
|
+
await this.httpRequest("POST", "/chat-reply", { agentId, channelId, content });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async sendAgentTyping(agentId: string, channelId: string) {
|
|
79
|
+
await this.httpRequest("POST", "/typing", { agentId, channelId });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get connected() { return this._connected; }
|
|
83
|
+
get stopped() { return this._stopped; }
|
|
84
|
+
|
|
85
|
+
connect() {
|
|
86
|
+
this._stopped = false;
|
|
87
|
+
const hostname = this.options.hostname ?? os.hostname();
|
|
88
|
+
const hbBody = { hostname, capabilities: this.options.capabilities };
|
|
89
|
+
|
|
90
|
+
// Heartbeat every 30s
|
|
91
|
+
this.heartbeatTimer = setInterval(async () => {
|
|
92
|
+
try {
|
|
93
|
+
await this.httpRequest("POST", "/heartbeat", hbBody);
|
|
94
|
+
if (!this._connected) { this._connected = true; this.options.log?.info?.("[machine] connected"); }
|
|
95
|
+
} catch (err) {
|
|
96
|
+
if (this._connected) { this._connected = false; this.options.log?.warn?.("[machine] disconnected"); }
|
|
97
|
+
this.options.log?.warn?.(`[machine] heartbeat error: ${err}`);
|
|
98
|
+
}
|
|
99
|
+
}, 30_000);
|
|
100
|
+
|
|
101
|
+
// Initial heartbeat
|
|
102
|
+
this.httpRequest("POST", "/heartbeat", hbBody)
|
|
103
|
+
.then(() => { this._connected = true; this.options.log?.info?.("[machine] connected"); })
|
|
104
|
+
.catch((err) => this.options.log?.warn?.(`[machine] initial heartbeat failed: ${err}`));
|
|
105
|
+
|
|
106
|
+
// Poll every 10s
|
|
107
|
+
this.pollTimer = setInterval(async () => {
|
|
108
|
+
if (!this._connected) return;
|
|
109
|
+
try {
|
|
110
|
+
const result = (await this.httpRequest("POST", "/poll", {})) as { machineId: string; agents: AgentWork[] };
|
|
111
|
+
for (const agent of result.agents) {
|
|
112
|
+
if (agent.task && this.taskHandler) this.taskHandler(agent.agentId, agent.task);
|
|
113
|
+
if (agent.chat && agent.chat.length > 0 && this.chatHandler) this.chatHandler(agent.agentId, agent.chat);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
this.options.log?.warn?.(`[machine] poll error: ${err}`);
|
|
117
|
+
}
|
|
118
|
+
}, 10_000);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
disconnect() {
|
|
122
|
+
this._stopped = true;
|
|
123
|
+
this._connected = false;
|
|
124
|
+
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
125
|
+
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
126
|
+
}
|
|
127
|
+
}
|