agentspd 1.0.0 → 1.1.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.
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Lightweight HTTP client for the OpenClaw Gateway API.
3
+ *
4
+ * OpenClaw exposes a single HTTP endpoint that multiplexes tool calls:
5
+ * - POST /tools/invoke — invoke any registered tool
6
+ *
7
+ * Available tools include:
8
+ * - sessions_list — list all active sessions
9
+ * - sessions_history — get transcript for a session
10
+ * - message (send) — send a message to a channel
11
+ *
12
+ * All endpoints share the same auth: Bearer token via `gateway.auth.token`.
13
+ */
14
+ function authHeaders(token) {
15
+ return {
16
+ Authorization: `Bearer ${token}`,
17
+ "Content-Type": "application/json",
18
+ };
19
+ }
20
+ /**
21
+ * Extract sessions from the OpenClaw Gateway tool-invoke response.
22
+ *
23
+ * The gateway wraps sessions_list output as:
24
+ * { ok, result: { details: { sessions: [...] } } }
25
+ *
26
+ * But older / simpler gateways may return a flat array at `result` directly.
27
+ */
28
+ function extractSessions(data) {
29
+ const result = data.result;
30
+ if (Array.isArray(result))
31
+ return result;
32
+ if (result && typeof result === "object") {
33
+ const payload = result;
34
+ if (Array.isArray(payload.details?.sessions)) {
35
+ return payload.details.sessions;
36
+ }
37
+ }
38
+ return [];
39
+ }
40
+ /**
41
+ * Test connectivity to an OpenClaw Gateway by invoking `sessions_list`.
42
+ */
43
+ export async function testConnection(gatewayUrl, token) {
44
+ const start = Date.now();
45
+ try {
46
+ const response = await fetch(`${gatewayUrl}/tools/invoke`, {
47
+ method: "POST",
48
+ headers: authHeaders(token),
49
+ body: JSON.stringify({ tool: "sessions_list", action: "json", args: {} }),
50
+ });
51
+ const latencyMs = Date.now() - start;
52
+ if (response.status === 401) {
53
+ return {
54
+ connected: false,
55
+ latencyMs,
56
+ error: "Authentication failed — check your Gateway token",
57
+ };
58
+ }
59
+ if (response.status === 404) {
60
+ // Tools Invoke API might not recognize sessions_list, but the endpoint responded
61
+ return {
62
+ connected: true,
63
+ latencyMs,
64
+ error: "Gateway reachable but sessions_list tool not available",
65
+ };
66
+ }
67
+ if (!response.ok) {
68
+ return {
69
+ connected: false,
70
+ latencyMs,
71
+ error: `Gateway returned HTTP ${response.status}`,
72
+ };
73
+ }
74
+ const data = (await response.json());
75
+ const sessions = extractSessions(data);
76
+ return {
77
+ connected: true,
78
+ latencyMs,
79
+ sessions: sessions.length > 0 ? sessions : undefined,
80
+ };
81
+ }
82
+ catch (err) {
83
+ const latencyMs = Date.now() - start;
84
+ const message = err instanceof Error ? err.message : String(err);
85
+ return {
86
+ connected: false,
87
+ latencyMs,
88
+ error: `Cannot reach Gateway: ${message}`,
89
+ };
90
+ }
91
+ }
92
+ /**
93
+ * List active sessions on the OpenClaw Gateway.
94
+ */
95
+ export async function listSessions(gatewayUrl, token) {
96
+ const response = await fetch(`${gatewayUrl}/tools/invoke`, {
97
+ method: "POST",
98
+ headers: authHeaders(token),
99
+ body: JSON.stringify({ tool: "sessions_list", action: "json", args: {} }),
100
+ });
101
+ if (!response.ok) {
102
+ throw new Error(`Failed to list sessions: HTTP ${response.status}`);
103
+ }
104
+ const data = (await response.json());
105
+ if (!data.ok) {
106
+ throw new Error(data.error?.message || "Failed to list sessions");
107
+ }
108
+ return extractSessions(data);
109
+ }
110
+ /**
111
+ * Fetch the transcript / history for a specific OpenClaw session.
112
+ */
113
+ export async function getSessionHistory(gatewayUrl, token, sessionKey) {
114
+ const response = await fetch(`${gatewayUrl}/tools/invoke`, {
115
+ method: "POST",
116
+ headers: authHeaders(token),
117
+ body: JSON.stringify({
118
+ tool: "sessions_history",
119
+ action: "json",
120
+ args: { sessionKey },
121
+ }),
122
+ });
123
+ if (!response.ok) {
124
+ throw new Error(`Failed to fetch session history: HTTP ${response.status}`);
125
+ }
126
+ const data = (await response.json());
127
+ if (!data.ok) {
128
+ throw new Error(data.error?.message || "Failed to fetch session history");
129
+ }
130
+ // History may come nested in result.details.messages or result directly.
131
+ const result = data.result;
132
+ if (Array.isArray(result))
133
+ return result;
134
+ if (result && typeof result === "object") {
135
+ const details = result.details;
136
+ if (details && Array.isArray(details.messages))
137
+ return details.messages;
138
+ if (details && Array.isArray(details.history))
139
+ return details.history;
140
+ }
141
+ return [];
142
+ }
143
+ /**
144
+ * Send a message through the OpenClaw Gateway using the `message` tool.
145
+ *
146
+ * The Gateway exposes tool invocation via `POST /tools/invoke`. The
147
+ * `message` tool with `action: "send"` delivers a message to the
148
+ * specified channel (slack, telegram, whatsapp, etc.).
149
+ *
150
+ * The `message` tool always requires a `to` target. When none is
151
+ * explicitly provided we auto-resolve it from the most recently active
152
+ * session that matches the requested channel.
153
+ */
154
+ export async function invokeHook(gatewayUrl, token, options) {
155
+ const wantedChannel = options.channel && options.channel !== "last"
156
+ ? options.channel
157
+ : undefined;
158
+ let to = options.to;
159
+ // Auto-resolve target from sessions if not provided.
160
+ if (!to) {
161
+ try {
162
+ const sessions = await listSessions(gatewayUrl, token);
163
+ // Sort by most recently active (updatedAt descending).
164
+ const sorted = [...sessions].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));
165
+ // Pick the first session that matches the wanted channel, or the
166
+ // most recently active session overall.
167
+ const match = wantedChannel
168
+ ? sorted.find((s) => s.channel === wantedChannel)
169
+ : sorted[0];
170
+ if (match) {
171
+ // deliveryContext holds the correct `to` for message_send.
172
+ const dc = match
173
+ .deliveryContext;
174
+ to = dc?.to;
175
+ // If the session doesn't carry a deliveryContext.to, fall back
176
+ // to deriving one from the session key.
177
+ if (!to && match.key) {
178
+ // Session keys look like "agent:main:slack:channel:C0AE86…"
179
+ const parts = match.key.split(":");
180
+ const channelIdx = parts.indexOf("channel");
181
+ const groupIdx = parts.indexOf("group");
182
+ const targetIdx = channelIdx !== -1
183
+ ? channelIdx
184
+ : groupIdx !== -1
185
+ ? groupIdx
186
+ : -1;
187
+ if (targetIdx !== -1 && parts[targetIdx + 1]) {
188
+ to = `channel:${parts[targetIdx + 1].toUpperCase()}`;
189
+ }
190
+ }
191
+ }
192
+ if (!to) {
193
+ return {
194
+ ok: false,
195
+ error: wantedChannel
196
+ ? `No active ${wantedChannel} session found on the Gateway to resolve a delivery target`
197
+ : "No active sessions found on the Gateway to resolve a delivery target",
198
+ };
199
+ }
200
+ }
201
+ catch (err) {
202
+ return {
203
+ ok: false,
204
+ error: `Failed to resolve delivery target: ${err instanceof Error ? err.message : String(err)}`,
205
+ };
206
+ }
207
+ }
208
+ const args = {
209
+ message: options.message,
210
+ to,
211
+ };
212
+ if (wantedChannel) {
213
+ args.channel = wantedChannel;
214
+ }
215
+ try {
216
+ const response = await fetch(`${gatewayUrl}/tools/invoke`, {
217
+ method: "POST",
218
+ headers: authHeaders(token),
219
+ body: JSON.stringify({
220
+ tool: "message",
221
+ action: "send",
222
+ args,
223
+ }),
224
+ });
225
+ if (response.status === 401) {
226
+ return {
227
+ ok: false,
228
+ error: "Authentication failed — check your Gateway token",
229
+ };
230
+ }
231
+ if (!response.ok) {
232
+ return {
233
+ ok: false,
234
+ error: `Gateway returned HTTP ${response.status}`,
235
+ };
236
+ }
237
+ const data = (await response.json());
238
+ if (!data.ok) {
239
+ return {
240
+ ok: false,
241
+ error: data.error?.message || "Message delivery failed",
242
+ };
243
+ }
244
+ return { ok: true };
245
+ }
246
+ catch (err) {
247
+ return {
248
+ ok: false,
249
+ error: err instanceof Error ? err.message : String(err),
250
+ };
251
+ }
252
+ }
253
+ /**
254
+ * Wake the OpenClaw main session by sending a system-level message
255
+ * through the Gateway's `message` tool.
256
+ */
257
+ export async function wakeGateway(gatewayUrl, token, text) {
258
+ return invokeHook(gatewayUrl, token, {
259
+ message: text,
260
+ channel: "last",
261
+ });
262
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentspd",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Emotos CLI - Security Infrastructure for AI Agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,20 +29,19 @@
29
29
  "author": "Emotos <hello@emotos.ai>",
30
30
  "repository": {
31
31
  "type": "git",
32
- "url": "git+https://github.com/emotos-ai/emotos.git",
33
- "directory": "cli"
32
+ "url": "git+https://github.com/EmotosAI/AgentsPD.git"
34
33
  },
35
34
  "homepage": "https://emotos.ai",
36
35
  "bugs": {
37
- "url": "https://github.com/emotos-ai/emotos/issues"
36
+ "url": "https://github.com/EmotosAI/AgentsPD/issues"
38
37
  },
39
38
  "dependencies": {
40
39
  "chalk": "^5.3.0",
41
40
  "commander": "^12.1.0",
42
- "conf": "^13.0.1",
41
+ "conf": "^13.1.0",
43
42
  "inquirer": "^9.2.15",
44
43
  "ora": "^8.0.1",
45
- "table": "^6.8.1",
44
+ "table": "^6.9.0",
46
45
  "ws": "^8.17.0",
47
46
  "yaml": "^2.3.4"
48
47
  },
@@ -50,6 +49,7 @@
50
49
  "@types/inquirer": "^9.0.7",
51
50
  "@types/node": "^22.10.5",
52
51
  "@types/ws": "^8.5.12",
52
+ "@types/yaml": "^1.9.6",
53
53
  "typescript": "^5.7.3"
54
54
  },
55
55
  "engines": {