@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.
@@ -106,7 +106,7 @@ class WorkspaceClient {
106
106
  }
107
107
 
108
108
  /**
109
- * Post a task result via POST /v1/events.
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: { content, message_type: messageType },
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/sessions/{channelName}.
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 params = new URLSearchParams({ network: workspaceId });
188
- const data = await this._get(`/v1/sessions/${channelName}?${params}`, this._wsHeaders(token));
189
- return (data.data || data) || {};
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 PUT /v1/sessions/{channelName}.
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 = { network: workspaceId };
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._post(`/v1/sessions/${channelName}`, body, this._wsHeaders(token));
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 targets = (e.metadata || {}).target_agents || [];
223
- return !targets.length || targets.includes(agentName);
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: 15000,
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
- _post(urlPath, body, headers = {}) {
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: 30000,
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 };