@openagents-org/agent-launcher 0.1.17 → 0.2.2
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/registry.json +50 -0
- package/src/adapters/claude.js +98 -9
- package/src/adapters/cursor.js +22 -0
- package/src/adapters/index.js +10 -1
- package/src/adapters/llm-direct.js +180 -0
- package/src/adapters/nanoclaw.js +22 -0
- package/src/adapters/openclaw.js +0 -9
- package/src/adapters/opencode.js +380 -0
- package/src/adapters/workspace-prompt.js +37 -0
- package/src/daemon.js +16 -2
- package/src/identity.js +113 -0
- package/src/index.js +5 -0
- package/src/tui.js +147 -8
- package/src/workspace-client.js +311 -21
package/src/workspace-client.js
CHANGED
|
@@ -106,7 +106,7 @@ class WorkspaceClient {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* Post a
|
|
109
|
+
* Post a raw event via POST /v1/events.
|
|
110
110
|
*/
|
|
111
111
|
async sendEvent(workspaceId, event, token) {
|
|
112
112
|
event.network = workspaceId;
|
|
@@ -118,20 +118,42 @@ class WorkspaceClient {
|
|
|
118
118
|
* Send a chat message to a workspace channel.
|
|
119
119
|
*/
|
|
120
120
|
async sendMessage(workspaceId, channelName, token, content, {
|
|
121
|
-
senderType = 'agent', senderName, messageType = 'chat', metadata,
|
|
121
|
+
senderType = 'agent', senderName, messageType = 'chat', metadata, attachments,
|
|
122
122
|
} = {}) {
|
|
123
123
|
const sourcePrefix = senderType === 'agent' ? 'openagents' : 'human';
|
|
124
124
|
const source = senderName ? `${sourcePrefix}:${senderName}` : `${sourcePrefix}:unknown`;
|
|
125
125
|
|
|
126
|
+
const payload = { content, message_type: messageType };
|
|
127
|
+
if (attachments && attachments.length) payload.attachments = attachments;
|
|
128
|
+
|
|
126
129
|
return this.sendEvent(workspaceId, {
|
|
127
130
|
type: 'workspace.message.posted',
|
|
128
131
|
source,
|
|
129
132
|
target: `channel/${channelName}`,
|
|
130
|
-
payload
|
|
133
|
+
payload,
|
|
131
134
|
metadata: metadata || {},
|
|
132
135
|
}, token);
|
|
133
136
|
}
|
|
134
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Poll messages in a channel via GET /v1/events.
|
|
140
|
+
* @returns {Array} message-compatible objects
|
|
141
|
+
*/
|
|
142
|
+
async pollMessages(workspaceId, channelName, token, { after, limit = 50 } = {}) {
|
|
143
|
+
const params = new URLSearchParams({
|
|
144
|
+
network: workspaceId,
|
|
145
|
+
channel: channelName,
|
|
146
|
+
type: 'workspace.message',
|
|
147
|
+
limit: String(limit),
|
|
148
|
+
});
|
|
149
|
+
if (after) params.set('after', after);
|
|
150
|
+
|
|
151
|
+
const data = await this._get(`/v1/events?${params}`, this._wsHeaders(token));
|
|
152
|
+
const result = data.data || data;
|
|
153
|
+
const events = (result && result.events) || [];
|
|
154
|
+
return events.map((e) => this._eventToMessage(e));
|
|
155
|
+
}
|
|
156
|
+
|
|
135
157
|
/**
|
|
136
158
|
* Poll for pending messages targeted at an agent via GET /v1/events.
|
|
137
159
|
* Returns { messages, cursor } where cursor is the last event ID.
|
|
@@ -180,27 +202,41 @@ class WorkspaceClient {
|
|
|
180
202
|
}
|
|
181
203
|
|
|
182
204
|
/**
|
|
183
|
-
* Get session/channel info via GET /v1/
|
|
205
|
+
* Get session/channel info via GET /v1/workspaces/{id}/channels/{name}.
|
|
184
206
|
*/
|
|
185
207
|
async getSession(workspaceId, channelName, token) {
|
|
186
208
|
try {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
209
|
+
const data = await this._get(
|
|
210
|
+
`/v1/workspaces/${workspaceId}/channels/${channelName}`,
|
|
211
|
+
this._wsHeaders(token),
|
|
212
|
+
);
|
|
213
|
+
const result = data.data || data;
|
|
214
|
+
return {
|
|
215
|
+
sessionId: result.name || channelName,
|
|
216
|
+
title: result.title || channelName,
|
|
217
|
+
titleManuallySet: result.titleManuallySet || false,
|
|
218
|
+
resumeFrom: result.resumeFrom || null,
|
|
219
|
+
status: result.status || 'active',
|
|
220
|
+
};
|
|
190
221
|
} catch {
|
|
191
|
-
return {};
|
|
222
|
+
return { sessionId: channelName, title: channelName, status: 'active' };
|
|
192
223
|
}
|
|
193
224
|
}
|
|
194
225
|
|
|
195
226
|
/**
|
|
196
|
-
* Update session/channel info via
|
|
227
|
+
* Update session/channel info via PATCH /v1/workspaces/{id}/channels/{name}.
|
|
197
228
|
*/
|
|
198
|
-
async updateSession(workspaceId, channelName, token, { title, autoTitle } = {}) {
|
|
199
|
-
const body = {
|
|
229
|
+
async updateSession(workspaceId, channelName, token, { title, status, autoTitle } = {}) {
|
|
230
|
+
const body = {};
|
|
200
231
|
if (title !== undefined) body.title = title;
|
|
232
|
+
if (status !== undefined) body.status = status;
|
|
201
233
|
if (autoTitle !== undefined) body.auto_title = autoTitle;
|
|
202
234
|
try {
|
|
203
|
-
await this.
|
|
235
|
+
await this._patch(
|
|
236
|
+
`/v1/workspaces/${workspaceId}/channels/${channelName}`,
|
|
237
|
+
body,
|
|
238
|
+
this._wsHeaders(token),
|
|
239
|
+
);
|
|
204
240
|
} catch {}
|
|
205
241
|
}
|
|
206
242
|
|
|
@@ -211,7 +247,7 @@ class WorkspaceClient {
|
|
|
211
247
|
try {
|
|
212
248
|
const params = new URLSearchParams({
|
|
213
249
|
network: workspaceId,
|
|
214
|
-
type: 'workspace.control',
|
|
250
|
+
type: 'workspace.agent.control',
|
|
215
251
|
limit: '10',
|
|
216
252
|
});
|
|
217
253
|
if (after) params.set('after', after);
|
|
@@ -219,14 +255,165 @@ class WorkspaceClient {
|
|
|
219
255
|
const result = data.data || data;
|
|
220
256
|
const events = (result && result.events) || [];
|
|
221
257
|
return events.filter((e) => {
|
|
222
|
-
const
|
|
223
|
-
return
|
|
258
|
+
const target = e.target || '';
|
|
259
|
+
return target === `openagents:${agentName}`;
|
|
224
260
|
});
|
|
225
261
|
} catch {
|
|
226
262
|
return [];
|
|
227
263
|
}
|
|
228
264
|
}
|
|
229
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Get workspace agents via GET /v1/discover.
|
|
268
|
+
* @returns {Array<{ agentName, role, status }>}
|
|
269
|
+
*/
|
|
270
|
+
async getAgents(workspaceId, token) {
|
|
271
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
272
|
+
const data = await this._get(`/v1/discover?${params}`, this._wsHeaders(token));
|
|
273
|
+
const result = data.data || data;
|
|
274
|
+
const agents = (result && result.agents) || [];
|
|
275
|
+
return agents.map((a) => ({
|
|
276
|
+
agentName: (a.address || '').replace('openagents:', ''),
|
|
277
|
+
role: a.role || 'member',
|
|
278
|
+
status: a.status || 'offline',
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ── File methods ──
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Upload a file via POST /v1/files/base64.
|
|
286
|
+
*/
|
|
287
|
+
async uploadFile(workspaceId, token, filename, contentBase64, {
|
|
288
|
+
contentType = 'application/octet-stream', source = 'human:user', channelName,
|
|
289
|
+
} = {}) {
|
|
290
|
+
const body = {
|
|
291
|
+
filename,
|
|
292
|
+
content_base64: contentBase64,
|
|
293
|
+
content_type: contentType,
|
|
294
|
+
network: workspaceId,
|
|
295
|
+
source,
|
|
296
|
+
};
|
|
297
|
+
if (channelName) body.channel_name = channelName;
|
|
298
|
+
|
|
299
|
+
const data = await this._post('/v1/files/base64', body, this._wsHeaders(token), 60000);
|
|
300
|
+
return data.data || data;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* List files via GET /v1/files.
|
|
305
|
+
*/
|
|
306
|
+
async listFiles(workspaceId, token, { limit = 50, offset = 0 } = {}) {
|
|
307
|
+
const params = new URLSearchParams({
|
|
308
|
+
network: workspaceId,
|
|
309
|
+
limit: String(limit),
|
|
310
|
+
offset: String(offset),
|
|
311
|
+
});
|
|
312
|
+
const data = await this._get(`/v1/files?${params}`, this._wsHeaders(token));
|
|
313
|
+
return data.data || data;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Get file metadata via GET /v1/files/{fileId}/info.
|
|
318
|
+
*/
|
|
319
|
+
async getFileInfo(token, fileId) {
|
|
320
|
+
try {
|
|
321
|
+
const data = await this._get(`/v1/files/${fileId}/info`, this._wsHeaders(token));
|
|
322
|
+
return data.data || data;
|
|
323
|
+
} catch {
|
|
324
|
+
return { id: fileId, filename: fileId, content_type: 'application/octet-stream' };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Download a file via GET /v1/files/{fileId}.
|
|
330
|
+
* @returns {Buffer}
|
|
331
|
+
*/
|
|
332
|
+
async readFile(workspaceId, token, fileId) {
|
|
333
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
334
|
+
return this._getRaw(`/v1/files/${fileId}?${params}`, this._wsHeaders(token), 60000);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Delete a file via DELETE /v1/files/{fileId}.
|
|
339
|
+
*/
|
|
340
|
+
async deleteFile(workspaceId, token, fileId) {
|
|
341
|
+
const data = await this._delete(`/v1/files/${fileId}`, this._wsHeaders(token), workspaceId);
|
|
342
|
+
return data.data || data;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Browser methods ──
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Open a new browser tab via POST /v1/browser/tabs.
|
|
349
|
+
*/
|
|
350
|
+
async browserOpenTab(workspaceId, token, { url = 'about:blank', source = 'human:user' } = {}) {
|
|
351
|
+
const data = await this._post('/v1/browser/tabs', {
|
|
352
|
+
url, network: workspaceId, source,
|
|
353
|
+
}, this._wsHeaders(token));
|
|
354
|
+
return data.data || data;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* List browser tabs via GET /v1/browser/tabs.
|
|
359
|
+
*/
|
|
360
|
+
async browserListTabs(workspaceId, token) {
|
|
361
|
+
const params = new URLSearchParams({ network: workspaceId });
|
|
362
|
+
const data = await this._get(`/v1/browser/tabs?${params}`, this._wsHeaders(token));
|
|
363
|
+
return data.data || data;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Navigate a browser tab via POST /v1/browser/tabs/{tabId}/navigate.
|
|
368
|
+
*/
|
|
369
|
+
async browserNavigate(workspaceId, token, tabId, url) {
|
|
370
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/navigate`, { url }, this._wsHeaders(token));
|
|
371
|
+
return data.data || data;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Click an element via POST /v1/browser/tabs/{tabId}/click.
|
|
376
|
+
*/
|
|
377
|
+
async browserClick(workspaceId, token, tabId, selector) {
|
|
378
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/click`, { selector }, this._wsHeaders(token));
|
|
379
|
+
return data.data || data;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Type text via POST /v1/browser/tabs/{tabId}/type.
|
|
384
|
+
*/
|
|
385
|
+
async browserType(workspaceId, token, tabId, selector, text) {
|
|
386
|
+
const data = await this._post(`/v1/browser/tabs/${tabId}/type`, { selector, text }, this._wsHeaders(token));
|
|
387
|
+
return data.data || data;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get screenshot via GET /v1/browser/tabs/{tabId}/screenshot.
|
|
392
|
+
* @returns {Buffer}
|
|
393
|
+
*/
|
|
394
|
+
async browserScreenshot(workspaceId, token, tabId) {
|
|
395
|
+
return this._getRaw(`/v1/browser/tabs/${tabId}/screenshot`, this._wsHeaders(token), 30000);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Get accessibility snapshot via GET /v1/browser/tabs/{tabId}/snapshot.
|
|
400
|
+
* @returns {string}
|
|
401
|
+
*/
|
|
402
|
+
async browserSnapshot(workspaceId, token, tabId) {
|
|
403
|
+
const buf = await this._getRaw(`/v1/browser/tabs/${tabId}/snapshot`, this._wsHeaders(token));
|
|
404
|
+
return buf.toString('utf-8');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Close a browser tab via DELETE /v1/browser/tabs/{tabId}.
|
|
409
|
+
*/
|
|
410
|
+
async browserCloseTab(workspaceId, token, tabId) {
|
|
411
|
+
const data = await this._delete(`/v1/browser/tabs/${tabId}`, this._wsHeaders(token));
|
|
412
|
+
return data.data || data;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ── Internal helpers ──
|
|
416
|
+
|
|
230
417
|
/**
|
|
231
418
|
* Convert an ONM event to a message-compatible object.
|
|
232
419
|
*/
|
|
@@ -244,17 +431,19 @@ class WorkspaceClient {
|
|
|
244
431
|
senderType: isHuman ? 'human' : 'agent',
|
|
245
432
|
senderName,
|
|
246
433
|
content: (payload.content || ''),
|
|
434
|
+
mentions: payload.mentions || [],
|
|
247
435
|
messageType: payload.message_type || 'chat',
|
|
248
436
|
metadata: event.metadata || {},
|
|
249
437
|
};
|
|
250
438
|
if (ts) {
|
|
251
439
|
msg.createdAt = new Date(ts).toISOString();
|
|
252
440
|
}
|
|
441
|
+
if (payload.attachments) {
|
|
442
|
+
msg.attachments = payload.attachments;
|
|
443
|
+
}
|
|
253
444
|
return msg;
|
|
254
445
|
}
|
|
255
446
|
|
|
256
|
-
// -- Internal --
|
|
257
|
-
|
|
258
447
|
_wsHeaders(token) {
|
|
259
448
|
return {
|
|
260
449
|
'Content-Type': 'application/json',
|
|
@@ -262,7 +451,7 @@ class WorkspaceClient {
|
|
|
262
451
|
};
|
|
263
452
|
}
|
|
264
453
|
|
|
265
|
-
_get(urlPath, headers = {}) {
|
|
454
|
+
_get(urlPath, headers = {}, timeout = 15000) {
|
|
266
455
|
const fullUrl = this.endpoint + urlPath;
|
|
267
456
|
|
|
268
457
|
return new Promise((resolve, reject) => {
|
|
@@ -272,7 +461,7 @@ class WorkspaceClient {
|
|
|
272
461
|
const req = transport.request(fullUrl, {
|
|
273
462
|
method: 'GET',
|
|
274
463
|
headers,
|
|
275
|
-
timeout
|
|
464
|
+
timeout,
|
|
276
465
|
}, (res) => {
|
|
277
466
|
let data = '';
|
|
278
467
|
res.on('data', (chunk) => { data += chunk; });
|
|
@@ -296,7 +485,37 @@ class WorkspaceClient {
|
|
|
296
485
|
});
|
|
297
486
|
}
|
|
298
487
|
|
|
299
|
-
|
|
488
|
+
_getRaw(urlPath, headers = {}, timeout = 15000) {
|
|
489
|
+
const fullUrl = this.endpoint + urlPath;
|
|
490
|
+
|
|
491
|
+
return new Promise((resolve, reject) => {
|
|
492
|
+
const parsedUrl = new URL(fullUrl);
|
|
493
|
+
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
|
494
|
+
|
|
495
|
+
const req = transport.request(fullUrl, {
|
|
496
|
+
method: 'GET',
|
|
497
|
+
headers,
|
|
498
|
+
timeout,
|
|
499
|
+
}, (res) => {
|
|
500
|
+
const chunks = [];
|
|
501
|
+
res.on('data', (chunk) => { chunks.push(chunk); });
|
|
502
|
+
res.on('end', () => {
|
|
503
|
+
const buf = Buffer.concat(chunks);
|
|
504
|
+
if (res.statusCode >= 400) {
|
|
505
|
+
reject(new Error(`HTTP ${res.statusCode}: ${buf.toString('utf-8').slice(0, 200)}`));
|
|
506
|
+
} else {
|
|
507
|
+
resolve(buf);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
req.on('error', reject);
|
|
513
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
|
|
514
|
+
req.end();
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_post(urlPath, body, headers = {}, timeout = 30000) {
|
|
300
519
|
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
|
|
301
520
|
const jsonBody = JSON.stringify(body);
|
|
302
521
|
const fullUrl = this.endpoint + urlPath;
|
|
@@ -308,7 +527,7 @@ class WorkspaceClient {
|
|
|
308
527
|
const req = transport.request(fullUrl, {
|
|
309
528
|
method: 'POST',
|
|
310
529
|
headers: { ...headers, 'Content-Length': Buffer.byteLength(jsonBody) },
|
|
311
|
-
timeout
|
|
530
|
+
timeout,
|
|
312
531
|
}, (res) => {
|
|
313
532
|
let data = '';
|
|
314
533
|
res.on('data', (chunk) => { data += chunk; });
|
|
@@ -333,6 +552,77 @@ class WorkspaceClient {
|
|
|
333
552
|
req.end();
|
|
334
553
|
});
|
|
335
554
|
}
|
|
555
|
+
|
|
556
|
+
_patch(urlPath, body, headers = {}, timeout = 15000) {
|
|
557
|
+
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json';
|
|
558
|
+
const jsonBody = JSON.stringify(body);
|
|
559
|
+
const fullUrl = this.endpoint + urlPath;
|
|
560
|
+
|
|
561
|
+
return new Promise((resolve, reject) => {
|
|
562
|
+
const parsedUrl = new URL(fullUrl);
|
|
563
|
+
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
|
564
|
+
|
|
565
|
+
const req = transport.request(fullUrl, {
|
|
566
|
+
method: 'PATCH',
|
|
567
|
+
headers: { ...headers, 'Content-Length': Buffer.byteLength(jsonBody) },
|
|
568
|
+
timeout,
|
|
569
|
+
}, (res) => {
|
|
570
|
+
let data = '';
|
|
571
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
572
|
+
res.on('end', () => {
|
|
573
|
+
try {
|
|
574
|
+
const parsed = JSON.parse(data);
|
|
575
|
+
if (res.statusCode >= 400) {
|
|
576
|
+
reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
|
|
577
|
+
} else {
|
|
578
|
+
resolve(parsed);
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
reject(new Error(`Invalid response: ${data.slice(0, 200)}`));
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
req.on('error', reject);
|
|
587
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
|
|
588
|
+
req.write(jsonBody);
|
|
589
|
+
req.end();
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
_delete(urlPath, headers = {}, network) {
|
|
594
|
+
const fullUrl = this.endpoint + urlPath + (network ? `?network=${network}` : '');
|
|
595
|
+
|
|
596
|
+
return new Promise((resolve, reject) => {
|
|
597
|
+
const parsedUrl = new URL(fullUrl);
|
|
598
|
+
const transport = parsedUrl.protocol === 'https:' ? https : http;
|
|
599
|
+
|
|
600
|
+
const req = transport.request(fullUrl, {
|
|
601
|
+
method: 'DELETE',
|
|
602
|
+
headers,
|
|
603
|
+
timeout: 15000,
|
|
604
|
+
}, (res) => {
|
|
605
|
+
let data = '';
|
|
606
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
607
|
+
res.on('end', () => {
|
|
608
|
+
try {
|
|
609
|
+
const parsed = JSON.parse(data);
|
|
610
|
+
if (res.statusCode >= 400) {
|
|
611
|
+
reject(new Error(parsed.message || `HTTP ${res.statusCode}`));
|
|
612
|
+
} else {
|
|
613
|
+
resolve(parsed);
|
|
614
|
+
}
|
|
615
|
+
} catch {
|
|
616
|
+
reject(new Error(`Invalid response: ${data.slice(0, 200)}`));
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
req.on('error', reject);
|
|
622
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Request timed out')); });
|
|
623
|
+
req.end();
|
|
624
|
+
});
|
|
625
|
+
}
|
|
336
626
|
}
|
|
337
627
|
|
|
338
628
|
module.exports = { WorkspaceClient };
|