@arinova-ai/agent-sdk 0.0.13 → 0.0.15-staging.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/README.md +276 -0
- package/dist/client.d.ts +202 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +755 -7
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +240 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -82,6 +82,20 @@ export class ArinovaAgent {
|
|
|
82
82
|
throw new Error(`sendMessage failed (${res.status}): ${body}`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Send a telemetry event to the server.
|
|
87
|
+
* Silently no-ops if WebSocket is not connected.
|
|
88
|
+
*/
|
|
89
|
+
sendTelemetry(event, data) {
|
|
90
|
+
this.send({ type: "agent_telemetry", event, data });
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Send HUD data to the server for display in the office HUD bar.
|
|
94
|
+
* The server forwards this to the agent owner's frontend.
|
|
95
|
+
*/
|
|
96
|
+
sendHud(data) {
|
|
97
|
+
this.send({ type: "hud_update", data });
|
|
98
|
+
}
|
|
85
99
|
emit(event, ...args) {
|
|
86
100
|
for (const listener of this.listeners[event] ?? []) {
|
|
87
101
|
listener(...args);
|
|
@@ -228,6 +242,718 @@ export class ArinovaAgent {
|
|
|
228
242
|
}
|
|
229
243
|
return res.json();
|
|
230
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Fetch conversation history via the agent messages endpoint.
|
|
247
|
+
* @param conversationId - The conversation to fetch messages from.
|
|
248
|
+
* @param options - Pagination options (before, after, around, limit).
|
|
249
|
+
*/
|
|
250
|
+
async fetchHistory(conversationId, options) {
|
|
251
|
+
const httpUrl = this.serverUrl
|
|
252
|
+
.replace(/^ws:/, "http:")
|
|
253
|
+
.replace(/^wss:/, "https:");
|
|
254
|
+
const params = new URLSearchParams();
|
|
255
|
+
if (options?.before)
|
|
256
|
+
params.set("before", options.before);
|
|
257
|
+
if (options?.after)
|
|
258
|
+
params.set("after", options.after);
|
|
259
|
+
if (options?.around)
|
|
260
|
+
params.set("around", options.around);
|
|
261
|
+
if (options?.limit != null)
|
|
262
|
+
params.set("limit", String(options.limit));
|
|
263
|
+
const qs = params.toString();
|
|
264
|
+
const url = `${httpUrl}/api/agent/messages/${conversationId}${qs ? `?${qs}` : ""}`;
|
|
265
|
+
const res = await fetch(url, {
|
|
266
|
+
method: "GET",
|
|
267
|
+
headers: {
|
|
268
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
if (!res.ok) {
|
|
272
|
+
const body = await res.text();
|
|
273
|
+
throw new Error(`fetchHistory failed (${res.status}): ${body}`);
|
|
274
|
+
}
|
|
275
|
+
return res.json();
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* List notes in a conversation.
|
|
279
|
+
* @param conversationId - The conversation to list notes from.
|
|
280
|
+
* @param options - Pagination options (before, limit).
|
|
281
|
+
*/
|
|
282
|
+
async listNotes(conversationId, options) {
|
|
283
|
+
const httpUrl = this.serverUrl
|
|
284
|
+
.replace(/^ws:/, "http:")
|
|
285
|
+
.replace(/^wss:/, "https:");
|
|
286
|
+
const params = new URLSearchParams();
|
|
287
|
+
if (options?.before)
|
|
288
|
+
params.set("before", options.before);
|
|
289
|
+
if (options?.limit != null)
|
|
290
|
+
params.set("limit", String(options.limit));
|
|
291
|
+
if (options?.tags?.length)
|
|
292
|
+
params.set("tags", options.tags.join(","));
|
|
293
|
+
if (options?.archived)
|
|
294
|
+
params.set("archived", "true");
|
|
295
|
+
const qs = params.toString();
|
|
296
|
+
const url = `${httpUrl}/api/agent/conversations/${conversationId}/notes${qs ? `?${qs}` : ""}`;
|
|
297
|
+
const res = await fetch(url, {
|
|
298
|
+
method: "GET",
|
|
299
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
300
|
+
});
|
|
301
|
+
if (!res.ok) {
|
|
302
|
+
const body = await res.text();
|
|
303
|
+
throw new Error(`listNotes failed (${res.status}): ${body}`);
|
|
304
|
+
}
|
|
305
|
+
return res.json();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Create a note in a conversation.
|
|
309
|
+
* @param conversationId - The conversation to create the note in.
|
|
310
|
+
* @param body - Note title and optional content.
|
|
311
|
+
*/
|
|
312
|
+
async createNote(conversationId, body) {
|
|
313
|
+
const httpUrl = this.serverUrl
|
|
314
|
+
.replace(/^ws:/, "http:")
|
|
315
|
+
.replace(/^wss:/, "https:");
|
|
316
|
+
const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
headers: {
|
|
319
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
320
|
+
"Content-Type": "application/json",
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(body),
|
|
323
|
+
});
|
|
324
|
+
if (!res.ok) {
|
|
325
|
+
const text = await res.text();
|
|
326
|
+
throw new Error(`createNote failed (${res.status}): ${text}`);
|
|
327
|
+
}
|
|
328
|
+
return res.json();
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Update a note in a conversation.
|
|
332
|
+
* @param conversationId - The conversation the note belongs to.
|
|
333
|
+
* @param noteId - The note ID to update.
|
|
334
|
+
* @param body - Fields to update (title and/or content).
|
|
335
|
+
*/
|
|
336
|
+
async updateNote(conversationId, noteId, body) {
|
|
337
|
+
const httpUrl = this.serverUrl
|
|
338
|
+
.replace(/^ws:/, "http:")
|
|
339
|
+
.replace(/^wss:/, "https:");
|
|
340
|
+
const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes/${noteId}`, {
|
|
341
|
+
method: "PATCH",
|
|
342
|
+
headers: {
|
|
343
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
344
|
+
"Content-Type": "application/json",
|
|
345
|
+
},
|
|
346
|
+
body: JSON.stringify(body),
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) {
|
|
349
|
+
const text = await res.text();
|
|
350
|
+
throw new Error(`updateNote failed (${res.status}): ${text}`);
|
|
351
|
+
}
|
|
352
|
+
return res.json();
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Delete a note from a conversation.
|
|
356
|
+
* @param conversationId - The conversation the note belongs to.
|
|
357
|
+
* @param noteId - The note ID to delete.
|
|
358
|
+
*/
|
|
359
|
+
async deleteNote(conversationId, noteId) {
|
|
360
|
+
const httpUrl = this.serverUrl
|
|
361
|
+
.replace(/^ws:/, "http:")
|
|
362
|
+
.replace(/^wss:/, "https:");
|
|
363
|
+
const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes/${noteId}`, {
|
|
364
|
+
method: "DELETE",
|
|
365
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
366
|
+
});
|
|
367
|
+
if (!res.ok) {
|
|
368
|
+
const text = await res.text();
|
|
369
|
+
throw new Error(`deleteNote failed (${res.status}): ${text}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// ── Kanban API ────────────────────────────────────────────────
|
|
373
|
+
/**
|
|
374
|
+
* List the owner's kanban boards.
|
|
375
|
+
* Returns an array of boards with id, name, and createdAt.
|
|
376
|
+
*/
|
|
377
|
+
async listBoards() {
|
|
378
|
+
const httpUrl = this.serverUrl
|
|
379
|
+
.replace(/^ws:/, "http:")
|
|
380
|
+
.replace(/^wss:/, "https:");
|
|
381
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards`, {
|
|
382
|
+
method: "GET",
|
|
383
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
384
|
+
});
|
|
385
|
+
if (!res.ok) {
|
|
386
|
+
const body = await res.text();
|
|
387
|
+
throw new Error(`listBoards failed (${res.status}): ${body}`);
|
|
388
|
+
}
|
|
389
|
+
return res.json();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Create a kanban card on the owner's board.
|
|
393
|
+
* The card is automatically assigned to the calling agent.
|
|
394
|
+
* @param body - Card title and optional description, priority, column.
|
|
395
|
+
*/
|
|
396
|
+
async createCard(body) {
|
|
397
|
+
const httpUrl = this.serverUrl
|
|
398
|
+
.replace(/^ws:/, "http:")
|
|
399
|
+
.replace(/^wss:/, "https:");
|
|
400
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards`, {
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: {
|
|
403
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
404
|
+
"Content-Type": "application/json",
|
|
405
|
+
},
|
|
406
|
+
body: JSON.stringify(body),
|
|
407
|
+
});
|
|
408
|
+
if (!res.ok) {
|
|
409
|
+
const text = await res.text();
|
|
410
|
+
throw new Error(`createCard failed (${res.status}): ${text}`);
|
|
411
|
+
}
|
|
412
|
+
return res.json();
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Update a kanban card.
|
|
416
|
+
* @param cardId - The card ID to update.
|
|
417
|
+
* @param body - Fields to update (title, description, priority, columnId, sortOrder).
|
|
418
|
+
*/
|
|
419
|
+
async updateCard(cardId, body) {
|
|
420
|
+
const httpUrl = this.serverUrl
|
|
421
|
+
.replace(/^ws:/, "http:")
|
|
422
|
+
.replace(/^wss:/, "https:");
|
|
423
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}`, {
|
|
424
|
+
method: "PATCH",
|
|
425
|
+
headers: {
|
|
426
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
427
|
+
"Content-Type": "application/json",
|
|
428
|
+
},
|
|
429
|
+
body: JSON.stringify(body),
|
|
430
|
+
});
|
|
431
|
+
if (!res.ok) {
|
|
432
|
+
const text = await res.text();
|
|
433
|
+
throw new Error(`updateCard failed (${res.status}): ${text}`);
|
|
434
|
+
}
|
|
435
|
+
return res.json();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Create a new kanban board.
|
|
439
|
+
* @param body - Board name and optional initial columns.
|
|
440
|
+
*/
|
|
441
|
+
async createBoard(body) {
|
|
442
|
+
const httpUrl = this.serverUrl
|
|
443
|
+
.replace(/^ws:/, "http:")
|
|
444
|
+
.replace(/^wss:/, "https:");
|
|
445
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards`, {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: {
|
|
448
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
449
|
+
"Content-Type": "application/json",
|
|
450
|
+
},
|
|
451
|
+
body: JSON.stringify(body),
|
|
452
|
+
});
|
|
453
|
+
if (!res.ok) {
|
|
454
|
+
const text = await res.text();
|
|
455
|
+
throw new Error(`createBoard failed (${res.status}): ${text}`);
|
|
456
|
+
}
|
|
457
|
+
return res.json();
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Update a kanban board.
|
|
461
|
+
* @param boardId - The board ID to update.
|
|
462
|
+
* @param body - Fields to update.
|
|
463
|
+
*/
|
|
464
|
+
async updateBoard(boardId, body) {
|
|
465
|
+
const httpUrl = this.serverUrl
|
|
466
|
+
.replace(/^ws:/, "http:")
|
|
467
|
+
.replace(/^wss:/, "https:");
|
|
468
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}`, {
|
|
469
|
+
method: "PATCH",
|
|
470
|
+
headers: {
|
|
471
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
472
|
+
"Content-Type": "application/json",
|
|
473
|
+
},
|
|
474
|
+
body: JSON.stringify(body),
|
|
475
|
+
});
|
|
476
|
+
if (!res.ok) {
|
|
477
|
+
const text = await res.text();
|
|
478
|
+
throw new Error(`updateBoard failed (${res.status}): ${text}`);
|
|
479
|
+
}
|
|
480
|
+
return res.json();
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Archive a kanban board.
|
|
484
|
+
* @param boardId - The board ID to archive.
|
|
485
|
+
*/
|
|
486
|
+
async archiveBoard(boardId) {
|
|
487
|
+
const httpUrl = this.serverUrl
|
|
488
|
+
.replace(/^ws:/, "http:")
|
|
489
|
+
.replace(/^wss:/, "https:");
|
|
490
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/archive`, {
|
|
491
|
+
method: "POST",
|
|
492
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
493
|
+
});
|
|
494
|
+
if (!res.ok) {
|
|
495
|
+
const text = await res.text();
|
|
496
|
+
throw new Error(`archiveBoard failed (${res.status}): ${text}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* List columns for a board.
|
|
501
|
+
* @param boardId - The board ID.
|
|
502
|
+
*/
|
|
503
|
+
async listColumns(boardId) {
|
|
504
|
+
const httpUrl = this.serverUrl
|
|
505
|
+
.replace(/^ws:/, "http:")
|
|
506
|
+
.replace(/^wss:/, "https:");
|
|
507
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/columns`, {
|
|
508
|
+
method: "GET",
|
|
509
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
510
|
+
});
|
|
511
|
+
if (!res.ok) {
|
|
512
|
+
const text = await res.text();
|
|
513
|
+
throw new Error(`listColumns failed (${res.status}): ${text}`);
|
|
514
|
+
}
|
|
515
|
+
return res.json();
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Create a column in a board.
|
|
519
|
+
* @param boardId - The board ID.
|
|
520
|
+
* @param body - Column name and optional sort order.
|
|
521
|
+
*/
|
|
522
|
+
async createColumn(boardId, body) {
|
|
523
|
+
const httpUrl = this.serverUrl
|
|
524
|
+
.replace(/^ws:/, "http:")
|
|
525
|
+
.replace(/^wss:/, "https:");
|
|
526
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/columns`, {
|
|
527
|
+
method: "POST",
|
|
528
|
+
headers: {
|
|
529
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
530
|
+
"Content-Type": "application/json",
|
|
531
|
+
},
|
|
532
|
+
body: JSON.stringify(body),
|
|
533
|
+
});
|
|
534
|
+
if (!res.ok) {
|
|
535
|
+
const text = await res.text();
|
|
536
|
+
throw new Error(`createColumn failed (${res.status}): ${text}`);
|
|
537
|
+
}
|
|
538
|
+
return res.json();
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Update a column.
|
|
542
|
+
* @param columnId - The column ID to update.
|
|
543
|
+
* @param body - Fields to update (name, sortOrder).
|
|
544
|
+
*/
|
|
545
|
+
async updateColumn(columnId, body) {
|
|
546
|
+
const httpUrl = this.serverUrl
|
|
547
|
+
.replace(/^ws:/, "http:")
|
|
548
|
+
.replace(/^wss:/, "https:");
|
|
549
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/columns/${columnId}`, {
|
|
550
|
+
method: "PATCH",
|
|
551
|
+
headers: {
|
|
552
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
553
|
+
"Content-Type": "application/json",
|
|
554
|
+
},
|
|
555
|
+
body: JSON.stringify(body),
|
|
556
|
+
});
|
|
557
|
+
if (!res.ok) {
|
|
558
|
+
const text = await res.text();
|
|
559
|
+
throw new Error(`updateColumn failed (${res.status}): ${text}`);
|
|
560
|
+
}
|
|
561
|
+
return res.json();
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Delete a column.
|
|
565
|
+
* @param columnId - The column ID to delete.
|
|
566
|
+
*/
|
|
567
|
+
async deleteColumn(columnId) {
|
|
568
|
+
const httpUrl = this.serverUrl
|
|
569
|
+
.replace(/^ws:/, "http:")
|
|
570
|
+
.replace(/^wss:/, "https:");
|
|
571
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/columns/${columnId}`, {
|
|
572
|
+
method: "DELETE",
|
|
573
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
574
|
+
});
|
|
575
|
+
if (!res.ok) {
|
|
576
|
+
const text = await res.text();
|
|
577
|
+
throw new Error(`deleteColumn failed (${res.status}): ${text}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Reorder columns in a board.
|
|
582
|
+
* @param boardId - The board ID.
|
|
583
|
+
* @param columnIds - Ordered array of column IDs.
|
|
584
|
+
*/
|
|
585
|
+
async reorderColumns(boardId, columnIds) {
|
|
586
|
+
const httpUrl = this.serverUrl
|
|
587
|
+
.replace(/^ws:/, "http:")
|
|
588
|
+
.replace(/^wss:/, "https:");
|
|
589
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/columns/reorder`, {
|
|
590
|
+
method: "POST",
|
|
591
|
+
headers: {
|
|
592
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
593
|
+
"Content-Type": "application/json",
|
|
594
|
+
},
|
|
595
|
+
body: JSON.stringify({ columnIds }),
|
|
596
|
+
});
|
|
597
|
+
if (!res.ok) {
|
|
598
|
+
const text = await res.text();
|
|
599
|
+
throw new Error(`reorderColumns failed (${res.status}): ${text}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* List all kanban cards for the agent's owner.
|
|
604
|
+
*/
|
|
605
|
+
async listCards() {
|
|
606
|
+
const httpUrl = this.serverUrl
|
|
607
|
+
.replace(/^ws:/, "http:")
|
|
608
|
+
.replace(/^wss:/, "https:");
|
|
609
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards`, {
|
|
610
|
+
method: "GET",
|
|
611
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
612
|
+
});
|
|
613
|
+
if (!res.ok) {
|
|
614
|
+
const text = await res.text();
|
|
615
|
+
throw new Error(`listCards failed (${res.status}): ${text}`);
|
|
616
|
+
}
|
|
617
|
+
return res.json();
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Mark a card as complete (moves it to the Done column).
|
|
621
|
+
* @param cardId - The card ID to complete.
|
|
622
|
+
*/
|
|
623
|
+
async completeCard(cardId) {
|
|
624
|
+
const httpUrl = this.serverUrl
|
|
625
|
+
.replace(/^ws:/, "http:")
|
|
626
|
+
.replace(/^wss:/, "https:");
|
|
627
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/complete`, {
|
|
628
|
+
method: "POST",
|
|
629
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
630
|
+
});
|
|
631
|
+
if (!res.ok) {
|
|
632
|
+
const text = await res.text();
|
|
633
|
+
throw new Error(`completeCard failed (${res.status}): ${text}`);
|
|
634
|
+
}
|
|
635
|
+
return res.json();
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* List archived cards for a board.
|
|
639
|
+
* @param boardId - The board ID.
|
|
640
|
+
* @param options - Pagination options (page, limit).
|
|
641
|
+
*/
|
|
642
|
+
async listArchivedCards(boardId, options) {
|
|
643
|
+
const httpUrl = this.serverUrl
|
|
644
|
+
.replace(/^ws:/, "http:")
|
|
645
|
+
.replace(/^wss:/, "https:");
|
|
646
|
+
const params = new URLSearchParams();
|
|
647
|
+
if (options?.page != null)
|
|
648
|
+
params.set("page", String(options.page));
|
|
649
|
+
if (options?.limit != null)
|
|
650
|
+
params.set("limit", String(options.limit));
|
|
651
|
+
const qs = params.toString();
|
|
652
|
+
const url = `${httpUrl}/api/agent/kanban/boards/${boardId}/archived-cards${qs ? `?${qs}` : ""}`;
|
|
653
|
+
const res = await fetch(url, {
|
|
654
|
+
method: "GET",
|
|
655
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
656
|
+
});
|
|
657
|
+
if (!res.ok) {
|
|
658
|
+
const text = await res.text();
|
|
659
|
+
throw new Error(`listArchivedCards failed (${res.status}): ${text}`);
|
|
660
|
+
}
|
|
661
|
+
return res.json();
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Add a commit link to a card.
|
|
665
|
+
* @param cardId - The card ID.
|
|
666
|
+
* @param body - Commit hash and optional message.
|
|
667
|
+
*/
|
|
668
|
+
async addCardCommit(cardId, body) {
|
|
669
|
+
const httpUrl = this.serverUrl
|
|
670
|
+
.replace(/^ws:/, "http:")
|
|
671
|
+
.replace(/^wss:/, "https:");
|
|
672
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/commits`, {
|
|
673
|
+
method: "POST",
|
|
674
|
+
headers: {
|
|
675
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
676
|
+
"Content-Type": "application/json",
|
|
677
|
+
},
|
|
678
|
+
body: JSON.stringify(body),
|
|
679
|
+
});
|
|
680
|
+
if (!res.ok) {
|
|
681
|
+
const text = await res.text();
|
|
682
|
+
throw new Error(`addCardCommit failed (${res.status}): ${text}`);
|
|
683
|
+
}
|
|
684
|
+
return res.json();
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* List commits linked to a card.
|
|
688
|
+
* @param cardId - The card ID.
|
|
689
|
+
*/
|
|
690
|
+
async listCardCommits(cardId) {
|
|
691
|
+
const httpUrl = this.serverUrl
|
|
692
|
+
.replace(/^ws:/, "http:")
|
|
693
|
+
.replace(/^wss:/, "https:");
|
|
694
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/commits`, {
|
|
695
|
+
method: "GET",
|
|
696
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
697
|
+
});
|
|
698
|
+
if (!res.ok) {
|
|
699
|
+
const text = await res.text();
|
|
700
|
+
throw new Error(`listCardCommits failed (${res.status}): ${text}`);
|
|
701
|
+
}
|
|
702
|
+
return res.json();
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Link a note to a card.
|
|
706
|
+
* @param cardId - The card ID.
|
|
707
|
+
* @param noteId - The note ID to link.
|
|
708
|
+
*/
|
|
709
|
+
async linkCardNote(cardId, noteId) {
|
|
710
|
+
const httpUrl = this.serverUrl
|
|
711
|
+
.replace(/^ws:/, "http:")
|
|
712
|
+
.replace(/^wss:/, "https:");
|
|
713
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/notes`, {
|
|
714
|
+
method: "POST",
|
|
715
|
+
headers: {
|
|
716
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
717
|
+
"Content-Type": "application/json",
|
|
718
|
+
},
|
|
719
|
+
body: JSON.stringify({ noteId }),
|
|
720
|
+
});
|
|
721
|
+
if (!res.ok) {
|
|
722
|
+
const text = await res.text();
|
|
723
|
+
throw new Error(`linkCardNote failed (${res.status}): ${text}`);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Unlink a note from a card.
|
|
728
|
+
* @param cardId - The card ID.
|
|
729
|
+
* @param noteId - The note ID to unlink.
|
|
730
|
+
*/
|
|
731
|
+
async unlinkCardNote(cardId, noteId) {
|
|
732
|
+
const httpUrl = this.serverUrl
|
|
733
|
+
.replace(/^ws:/, "http:")
|
|
734
|
+
.replace(/^wss:/, "https:");
|
|
735
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/notes/${noteId}`, {
|
|
736
|
+
method: "DELETE",
|
|
737
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
738
|
+
});
|
|
739
|
+
if (!res.ok) {
|
|
740
|
+
const text = await res.text();
|
|
741
|
+
throw new Error(`unlinkCardNote failed (${res.status}): ${text}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* List notes linked to a card.
|
|
746
|
+
* @param cardId - The card ID.
|
|
747
|
+
*/
|
|
748
|
+
async listCardNotes(cardId) {
|
|
749
|
+
const httpUrl = this.serverUrl
|
|
750
|
+
.replace(/^ws:/, "http:")
|
|
751
|
+
.replace(/^wss:/, "https:");
|
|
752
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/notes`, {
|
|
753
|
+
method: "GET",
|
|
754
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
755
|
+
});
|
|
756
|
+
if (!res.ok) {
|
|
757
|
+
const text = await res.text();
|
|
758
|
+
throw new Error(`listCardNotes failed (${res.status}): ${text}`);
|
|
759
|
+
}
|
|
760
|
+
return res.json();
|
|
761
|
+
}
|
|
762
|
+
// ── Label API ────────────────────────────────────────────────
|
|
763
|
+
/**
|
|
764
|
+
* List labels for a board.
|
|
765
|
+
* @param boardId - The board ID.
|
|
766
|
+
*/
|
|
767
|
+
async listLabels(boardId) {
|
|
768
|
+
const httpUrl = this.serverUrl
|
|
769
|
+
.replace(/^ws:/, "http:")
|
|
770
|
+
.replace(/^wss:/, "https:");
|
|
771
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/labels`, {
|
|
772
|
+
method: "GET",
|
|
773
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
774
|
+
});
|
|
775
|
+
if (!res.ok) {
|
|
776
|
+
const text = await res.text();
|
|
777
|
+
throw new Error(`listLabels failed (${res.status}): ${text}`);
|
|
778
|
+
}
|
|
779
|
+
return res.json();
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Create a label on a board.
|
|
783
|
+
* @param boardId - The board ID.
|
|
784
|
+
* @param body - Label name and optional color.
|
|
785
|
+
*/
|
|
786
|
+
async createLabel(boardId, body) {
|
|
787
|
+
const httpUrl = this.serverUrl
|
|
788
|
+
.replace(/^ws:/, "http:")
|
|
789
|
+
.replace(/^wss:/, "https:");
|
|
790
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/boards/${boardId}/labels`, {
|
|
791
|
+
method: "POST",
|
|
792
|
+
headers: {
|
|
793
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
794
|
+
"Content-Type": "application/json",
|
|
795
|
+
},
|
|
796
|
+
body: JSON.stringify(body),
|
|
797
|
+
});
|
|
798
|
+
if (!res.ok) {
|
|
799
|
+
const text = await res.text();
|
|
800
|
+
throw new Error(`createLabel failed (${res.status}): ${text}`);
|
|
801
|
+
}
|
|
802
|
+
return res.json();
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Update a label.
|
|
806
|
+
* @param labelId - The label ID to update.
|
|
807
|
+
* @param body - Fields to update (name, color).
|
|
808
|
+
*/
|
|
809
|
+
async updateLabel(labelId, body) {
|
|
810
|
+
const httpUrl = this.serverUrl
|
|
811
|
+
.replace(/^ws:/, "http:")
|
|
812
|
+
.replace(/^wss:/, "https:");
|
|
813
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/labels/${labelId}`, {
|
|
814
|
+
method: "PATCH",
|
|
815
|
+
headers: {
|
|
816
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
817
|
+
"Content-Type": "application/json",
|
|
818
|
+
},
|
|
819
|
+
body: JSON.stringify(body),
|
|
820
|
+
});
|
|
821
|
+
if (!res.ok) {
|
|
822
|
+
const text = await res.text();
|
|
823
|
+
throw new Error(`updateLabel failed (${res.status}): ${text}`);
|
|
824
|
+
}
|
|
825
|
+
return res.json();
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Delete a label.
|
|
829
|
+
* @param labelId - The label ID to delete.
|
|
830
|
+
*/
|
|
831
|
+
async deleteLabel(labelId) {
|
|
832
|
+
const httpUrl = this.serverUrl
|
|
833
|
+
.replace(/^ws:/, "http:")
|
|
834
|
+
.replace(/^wss:/, "https:");
|
|
835
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/labels/${labelId}`, {
|
|
836
|
+
method: "DELETE",
|
|
837
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
838
|
+
});
|
|
839
|
+
if (!res.ok) {
|
|
840
|
+
const text = await res.text();
|
|
841
|
+
throw new Error(`deleteLabel failed (${res.status}): ${text}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Add a label to a card.
|
|
846
|
+
* @param cardId - The card ID.
|
|
847
|
+
* @param labelId - The label ID to add.
|
|
848
|
+
*/
|
|
849
|
+
async addCardLabel(cardId, labelId) {
|
|
850
|
+
const httpUrl = this.serverUrl
|
|
851
|
+
.replace(/^ws:/, "http:")
|
|
852
|
+
.replace(/^wss:/, "https:");
|
|
853
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/labels`, {
|
|
854
|
+
method: "POST",
|
|
855
|
+
headers: {
|
|
856
|
+
Authorization: `Bearer ${this.botToken}`,
|
|
857
|
+
"Content-Type": "application/json",
|
|
858
|
+
},
|
|
859
|
+
body: JSON.stringify({ labelId }),
|
|
860
|
+
});
|
|
861
|
+
if (!res.ok) {
|
|
862
|
+
const text = await res.text();
|
|
863
|
+
throw new Error(`addCardLabel failed (${res.status}): ${text}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Remove a label from a card.
|
|
868
|
+
* @param cardId - The card ID.
|
|
869
|
+
* @param labelId - The label ID to remove.
|
|
870
|
+
*/
|
|
871
|
+
async removeCardLabel(cardId, labelId) {
|
|
872
|
+
const httpUrl = this.serverUrl
|
|
873
|
+
.replace(/^ws:/, "http:")
|
|
874
|
+
.replace(/^wss:/, "https:");
|
|
875
|
+
const res = await fetch(`${httpUrl}/api/agent/kanban/cards/${cardId}/labels/${labelId}`, {
|
|
876
|
+
method: "DELETE",
|
|
877
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
878
|
+
});
|
|
879
|
+
if (!res.ok) {
|
|
880
|
+
const text = await res.text();
|
|
881
|
+
throw new Error(`removeCardLabel failed (${res.status}): ${text}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
// ── Memory API ───────────────────────────────────────────────
|
|
885
|
+
/**
|
|
886
|
+
* Search memories across all memory capsules granted to this agent.
|
|
887
|
+
* Uses hybrid search (embedding + text) to find relevant memories.
|
|
888
|
+
* @param options - Query string and optional limit.
|
|
889
|
+
*/
|
|
890
|
+
async queryMemory(options) {
|
|
891
|
+
const httpUrl = this.serverUrl
|
|
892
|
+
.replace(/^ws:/, "http:")
|
|
893
|
+
.replace(/^wss:/, "https:");
|
|
894
|
+
const params = new URLSearchParams();
|
|
895
|
+
params.set("query", options.query);
|
|
896
|
+
if (options.limit != null)
|
|
897
|
+
params.set("limit", String(options.limit));
|
|
898
|
+
const res = await fetch(`${httpUrl}/api/agent/capsules?${params}`, {
|
|
899
|
+
method: "GET",
|
|
900
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
901
|
+
});
|
|
902
|
+
if (!res.ok) {
|
|
903
|
+
const body = await res.text();
|
|
904
|
+
throw new Error(`queryMemory failed (${res.status}): ${body}`);
|
|
905
|
+
}
|
|
906
|
+
// Server returns snake_case, map to camelCase
|
|
907
|
+
const raw = (await res.json());
|
|
908
|
+
return raw.map((r) => ({
|
|
909
|
+
content: r.content,
|
|
910
|
+
capsuleName: r.capsule_name,
|
|
911
|
+
capsuleId: r.capsule_id,
|
|
912
|
+
score: r.score,
|
|
913
|
+
importance: r.importance,
|
|
914
|
+
}));
|
|
915
|
+
}
|
|
916
|
+
// ── Skill Prompt API ─────────────────────────────────────────
|
|
917
|
+
/**
|
|
918
|
+
* Fetch the full prompt content for an installed skill by slug.
|
|
919
|
+
* Use this when the agent decides to trigger a skill from availableSkills.
|
|
920
|
+
* @param skillSlug - The skill slug (e.g. "draw", "proactive-agent").
|
|
921
|
+
*/
|
|
922
|
+
async fetchSkillPrompt(skillSlug) {
|
|
923
|
+
const httpUrl = this.serverUrl
|
|
924
|
+
.replace(/^ws:/, "http:")
|
|
925
|
+
.replace(/^wss:/, "https:");
|
|
926
|
+
const res = await fetch(`${httpUrl}/api/agent/skills/${encodeURIComponent(skillSlug)}/prompt`, {
|
|
927
|
+
method: "GET",
|
|
928
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
929
|
+
});
|
|
930
|
+
if (!res.ok) {
|
|
931
|
+
const body = await res.text();
|
|
932
|
+
throw new Error(`fetchSkillPrompt failed (${res.status}): ${body}`);
|
|
933
|
+
}
|
|
934
|
+
return (await res.json());
|
|
935
|
+
}
|
|
936
|
+
// ── Note Share API ───────────────────────────────────────────
|
|
937
|
+
/**
|
|
938
|
+
* Share a note as a message in a conversation.
|
|
939
|
+
* Creates a rich preview card visible to all conversation members.
|
|
940
|
+
* @param conversationId - The conversation to share into.
|
|
941
|
+
* @param noteId - The note ID to share.
|
|
942
|
+
*/
|
|
943
|
+
async shareNote(conversationId, noteId) {
|
|
944
|
+
const httpUrl = this.serverUrl
|
|
945
|
+
.replace(/^ws:/, "http:")
|
|
946
|
+
.replace(/^wss:/, "https:");
|
|
947
|
+
const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes/${noteId}/share`, {
|
|
948
|
+
method: "POST",
|
|
949
|
+
headers: { Authorization: `Bearer ${this.botToken}` },
|
|
950
|
+
});
|
|
951
|
+
if (!res.ok) {
|
|
952
|
+
const text = await res.text();
|
|
953
|
+
throw new Error(`shareNote failed (${res.status}): ${text}`);
|
|
954
|
+
}
|
|
955
|
+
return res.json();
|
|
956
|
+
}
|
|
231
957
|
handleTask(data) {
|
|
232
958
|
if (!this.taskHandler)
|
|
233
959
|
return;
|
|
@@ -239,6 +965,18 @@ export class ArinovaAgent {
|
|
|
239
965
|
this.send({ type: "agent_heartbeat", taskId });
|
|
240
966
|
}, TASK_HEARTBEAT_INTERVAL);
|
|
241
967
|
const stopHeartbeat = () => clearInterval(heartbeatTimer);
|
|
968
|
+
// Guard: ensure sendComplete/sendError only fires once per task.
|
|
969
|
+
// After cancel_task, the background handler may still call sendComplete
|
|
970
|
+
// when the LLM finishes — the guard prevents duplicate events.
|
|
971
|
+
let taskFinished = false;
|
|
972
|
+
const markFinished = () => {
|
|
973
|
+
if (taskFinished)
|
|
974
|
+
return false;
|
|
975
|
+
taskFinished = true;
|
|
976
|
+
stopHeartbeat();
|
|
977
|
+
this.taskAbortControllers.delete(taskId);
|
|
978
|
+
return true;
|
|
979
|
+
};
|
|
242
980
|
const ctx = {
|
|
243
981
|
taskId,
|
|
244
982
|
conversationId: data.conversationId,
|
|
@@ -250,10 +988,14 @@ export class ArinovaAgent {
|
|
|
250
988
|
replyTo: data.replyTo,
|
|
251
989
|
history: data.history,
|
|
252
990
|
attachments: data.attachments,
|
|
253
|
-
sendChunk: (delta) =>
|
|
991
|
+
sendChunk: (delta) => {
|
|
992
|
+
if (taskFinished)
|
|
993
|
+
return;
|
|
994
|
+
this.send({ type: "agent_chunk", taskId, chunk: delta });
|
|
995
|
+
},
|
|
254
996
|
sendComplete: (fullContent, options) => {
|
|
255
|
-
|
|
256
|
-
|
|
997
|
+
if (!markFinished())
|
|
998
|
+
return;
|
|
257
999
|
this.send({
|
|
258
1000
|
type: "agent_complete",
|
|
259
1001
|
taskId,
|
|
@@ -262,15 +1004,21 @@ export class ArinovaAgent {
|
|
|
262
1004
|
});
|
|
263
1005
|
},
|
|
264
1006
|
sendError: (error) => {
|
|
265
|
-
|
|
266
|
-
|
|
1007
|
+
if (!markFinished())
|
|
1008
|
+
return;
|
|
267
1009
|
this.send({ type: "agent_error", taskId, error });
|
|
268
1010
|
},
|
|
269
1011
|
signal: abortController.signal,
|
|
270
1012
|
uploadFile: (file, fileName, fileType) => this.uploadFile(data.conversationId, file, fileName, fileType),
|
|
1013
|
+
fetchHistory: (options) => this.fetchHistory(data.conversationId, options),
|
|
271
1014
|
};
|
|
272
|
-
//
|
|
273
|
-
|
|
1015
|
+
// When task is aborted (user cancelled), immediately send cancellation
|
|
1016
|
+
// error so the server knows this agent is free for new tasks.
|
|
1017
|
+
abortController.signal.addEventListener("abort", () => {
|
|
1018
|
+
if (!markFinished())
|
|
1019
|
+
return;
|
|
1020
|
+
this.send({ type: "agent_error", taskId, error: "cancelled" });
|
|
1021
|
+
}, { once: true });
|
|
274
1022
|
Promise.resolve(this.taskHandler(ctx)).catch((err) => {
|
|
275
1023
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
276
1024
|
ctx.sendError(errorMsg);
|