@arinova-ai/agent-sdk 0.0.15 → 0.0.16-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/dist/client.js CHANGED
@@ -9,8 +9,10 @@ export class ArinovaAgent {
9
9
  pingInterval;
10
10
  ws = null;
11
11
  pingTimer = null;
12
+ commandHeartbeatTimer = null;
12
13
  reconnectTimer = null;
13
14
  stopped = false;
15
+ agentId = null;
14
16
  taskHandler = null;
15
17
  taskAbortControllers = new Map();
16
18
  listeners = {
@@ -69,7 +71,7 @@ export class ArinovaAgent {
69
71
  const httpUrl = this.serverUrl
70
72
  .replace(/^ws:/, "http:")
71
73
  .replace(/^wss:/, "https:");
72
- const res = await fetch(`${httpUrl}/api/agent/send`, {
74
+ const res = await fetch(`${httpUrl}/api/v1/messages/send`, {
73
75
  method: "POST",
74
76
  headers: {
75
77
  "Authorization": `Bearer ${this.botToken}`,
@@ -82,6 +84,20 @@ export class ArinovaAgent {
82
84
  throw new Error(`sendMessage failed (${res.status}): ${body}`);
83
85
  }
84
86
  }
87
+ /**
88
+ * Send a telemetry event to the server.
89
+ * Silently no-ops if WebSocket is not connected.
90
+ */
91
+ sendTelemetry(event, data) {
92
+ this.send({ type: "agent_telemetry", event, data });
93
+ }
94
+ /**
95
+ * Send HUD data to the server for display in the office HUD bar.
96
+ * The server forwards this to the agent owner's frontend.
97
+ */
98
+ sendHud(data) {
99
+ this.send({ type: "hud_update", data });
100
+ }
85
101
  emit(event, ...args) {
86
102
  for (const listener of this.listeners[event] ?? []) {
87
103
  listener(...args);
@@ -97,6 +113,10 @@ export class ArinovaAgent {
97
113
  clearInterval(this.pingTimer);
98
114
  this.pingTimer = null;
99
115
  }
116
+ if (this.commandHeartbeatTimer) {
117
+ clearInterval(this.commandHeartbeatTimer);
118
+ this.commandHeartbeatTimer = null;
119
+ }
100
120
  if (this.reconnectTimer) {
101
121
  clearTimeout(this.reconnectTimer);
102
122
  this.reconnectTimer = null;
@@ -145,7 +165,27 @@ export class ArinovaAgent {
145
165
  try {
146
166
  const data = JSON.parse(String(event.data));
147
167
  if (data.type === "auth_ok") {
168
+ this.agentId = data.agentId ?? null;
148
169
  this.emit("connected");
170
+ // Register SDK runtime commands from skills
171
+ if (this.skills.length > 0 && this.agentId) {
172
+ this.send({
173
+ type: "register_commands",
174
+ agentId: this.agentId,
175
+ commands: this.skills.map((s) => ({
176
+ name: s.id ?? s.name,
177
+ description: s.description ?? "",
178
+ })),
179
+ });
180
+ }
181
+ // Start heartbeat to extend Redis TTL every 60s
182
+ if (this.commandHeartbeatTimer)
183
+ clearInterval(this.commandHeartbeatTimer);
184
+ if (this.skills.length > 0 && this.agentId) {
185
+ this.commandHeartbeatTimer = setInterval(() => {
186
+ this.send({ type: "heartbeat_commands", agentId: this.agentId });
187
+ }, 60_000);
188
+ }
149
189
  // Resolve the connect() promise on first successful auth
150
190
  if (this.connectResolve) {
151
191
  this.connectResolve();
@@ -215,7 +255,7 @@ export class ArinovaAgent {
215
255
  formData.append("conversationId", conversationId);
216
256
  const blob = new Blob([new Uint8Array(file)], { type: mime });
217
257
  formData.append("file", blob, fileName);
218
- const res = await fetch(`${httpUrl}/api/agent/upload`, {
258
+ const res = await fetch(`${httpUrl}/api/v1/files/upload`, {
219
259
  method: "POST",
220
260
  headers: {
221
261
  Authorization: `Bearer ${this.botToken}`,
@@ -247,7 +287,7 @@ export class ArinovaAgent {
247
287
  if (options?.limit != null)
248
288
  params.set("limit", String(options.limit));
249
289
  const qs = params.toString();
250
- const url = `${httpUrl}/api/agent/messages/${conversationId}${qs ? `?${qs}` : ""}`;
290
+ const url = `${httpUrl}/api/v1/messages/${conversationId}${qs ? `?${qs}` : ""}`;
251
291
  const res = await fetch(url, {
252
292
  method: "GET",
253
293
  headers: {
@@ -274,8 +314,12 @@ export class ArinovaAgent {
274
314
  params.set("before", options.before);
275
315
  if (options?.limit != null)
276
316
  params.set("limit", String(options.limit));
317
+ if (options?.tags?.length)
318
+ params.set("tags", options.tags.join(","));
319
+ if (options?.archived)
320
+ params.set("archived", "true");
277
321
  const qs = params.toString();
278
- const url = `${httpUrl}/api/agent/conversations/${conversationId}/notes${qs ? `?${qs}` : ""}`;
322
+ const url = `${httpUrl}/api/v1/notes${qs ? `?${qs}` : ""}`;
279
323
  const res = await fetch(url, {
280
324
  method: "GET",
281
325
  headers: { Authorization: `Bearer ${this.botToken}` },
@@ -295,7 +339,7 @@ export class ArinovaAgent {
295
339
  const httpUrl = this.serverUrl
296
340
  .replace(/^ws:/, "http:")
297
341
  .replace(/^wss:/, "https:");
298
- const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes`, {
342
+ const res = await fetch(`${httpUrl}/api/v1/notes`, {
299
343
  method: "POST",
300
344
  headers: {
301
345
  Authorization: `Bearer ${this.botToken}`,
@@ -319,7 +363,7 @@ export class ArinovaAgent {
319
363
  const httpUrl = this.serverUrl
320
364
  .replace(/^ws:/, "http:")
321
365
  .replace(/^wss:/, "https:");
322
- const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes/${noteId}`, {
366
+ const res = await fetch(`${httpUrl}/api/v1/notes/${noteId}`, {
323
367
  method: "PATCH",
324
368
  headers: {
325
369
  Authorization: `Bearer ${this.botToken}`,
@@ -342,7 +386,7 @@ export class ArinovaAgent {
342
386
  const httpUrl = this.serverUrl
343
387
  .replace(/^ws:/, "http:")
344
388
  .replace(/^wss:/, "https:");
345
- const res = await fetch(`${httpUrl}/api/agent/conversations/${conversationId}/notes/${noteId}`, {
389
+ const res = await fetch(`${httpUrl}/api/v1/notes/${noteId}`, {
346
390
  method: "DELETE",
347
391
  headers: { Authorization: `Bearer ${this.botToken}` },
348
392
  });
@@ -351,6 +395,591 @@ export class ArinovaAgent {
351
395
  throw new Error(`deleteNote failed (${res.status}): ${text}`);
352
396
  }
353
397
  }
398
+ // ── Kanban API ────────────────────────────────────────────────
399
+ /**
400
+ * List the owner's kanban boards.
401
+ * Returns an array of boards with id, name, and createdAt.
402
+ */
403
+ async listBoards() {
404
+ const httpUrl = this.serverUrl
405
+ .replace(/^ws:/, "http:")
406
+ .replace(/^wss:/, "https:");
407
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards`, {
408
+ method: "GET",
409
+ headers: { Authorization: `Bearer ${this.botToken}` },
410
+ });
411
+ if (!res.ok) {
412
+ const body = await res.text();
413
+ throw new Error(`listBoards failed (${res.status}): ${body}`);
414
+ }
415
+ return res.json();
416
+ }
417
+ /**
418
+ * Create a kanban card on the owner's board.
419
+ * The card is automatically assigned to the calling agent.
420
+ * @param body - Card title and optional description, priority, column.
421
+ */
422
+ async createCard(body) {
423
+ const httpUrl = this.serverUrl
424
+ .replace(/^ws:/, "http:")
425
+ .replace(/^wss:/, "https:");
426
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards`, {
427
+ method: "POST",
428
+ headers: {
429
+ Authorization: `Bearer ${this.botToken}`,
430
+ "Content-Type": "application/json",
431
+ },
432
+ body: JSON.stringify(body),
433
+ });
434
+ if (!res.ok) {
435
+ const text = await res.text();
436
+ throw new Error(`createCard failed (${res.status}): ${text}`);
437
+ }
438
+ return res.json();
439
+ }
440
+ /**
441
+ * Update a kanban card.
442
+ * @param cardId - The card ID to update.
443
+ * @param body - Fields to update (title, description, priority, columnId, sortOrder).
444
+ */
445
+ async updateCard(cardId, body) {
446
+ const httpUrl = this.serverUrl
447
+ .replace(/^ws:/, "http:")
448
+ .replace(/^wss:/, "https:");
449
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}`, {
450
+ method: "PATCH",
451
+ headers: {
452
+ Authorization: `Bearer ${this.botToken}`,
453
+ "Content-Type": "application/json",
454
+ },
455
+ body: JSON.stringify(body),
456
+ });
457
+ if (!res.ok) {
458
+ const text = await res.text();
459
+ throw new Error(`updateCard failed (${res.status}): ${text}`);
460
+ }
461
+ return res.json();
462
+ }
463
+ /**
464
+ * Create a new kanban board.
465
+ * @param body - Board name and optional initial columns.
466
+ */
467
+ async createBoard(body) {
468
+ const httpUrl = this.serverUrl
469
+ .replace(/^ws:/, "http:")
470
+ .replace(/^wss:/, "https:");
471
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards`, {
472
+ method: "POST",
473
+ headers: {
474
+ Authorization: `Bearer ${this.botToken}`,
475
+ "Content-Type": "application/json",
476
+ },
477
+ body: JSON.stringify(body),
478
+ });
479
+ if (!res.ok) {
480
+ const text = await res.text();
481
+ throw new Error(`createBoard failed (${res.status}): ${text}`);
482
+ }
483
+ return res.json();
484
+ }
485
+ /**
486
+ * Update a kanban board.
487
+ * @param boardId - The board ID to update.
488
+ * @param body - Fields to update.
489
+ */
490
+ async updateBoard(boardId, body) {
491
+ const httpUrl = this.serverUrl
492
+ .replace(/^ws:/, "http:")
493
+ .replace(/^wss:/, "https:");
494
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}`, {
495
+ method: "PATCH",
496
+ headers: {
497
+ Authorization: `Bearer ${this.botToken}`,
498
+ "Content-Type": "application/json",
499
+ },
500
+ body: JSON.stringify(body),
501
+ });
502
+ if (!res.ok) {
503
+ const text = await res.text();
504
+ throw new Error(`updateBoard failed (${res.status}): ${text}`);
505
+ }
506
+ return res.json();
507
+ }
508
+ /**
509
+ * Archive a kanban board.
510
+ * @param boardId - The board ID to archive.
511
+ */
512
+ async archiveBoard(boardId) {
513
+ const httpUrl = this.serverUrl
514
+ .replace(/^ws:/, "http:")
515
+ .replace(/^wss:/, "https:");
516
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/archive`, {
517
+ method: "POST",
518
+ headers: { Authorization: `Bearer ${this.botToken}` },
519
+ });
520
+ if (!res.ok) {
521
+ const text = await res.text();
522
+ throw new Error(`archiveBoard failed (${res.status}): ${text}`);
523
+ }
524
+ }
525
+ /**
526
+ * List columns for a board.
527
+ * @param boardId - The board ID.
528
+ */
529
+ async listColumns(boardId) {
530
+ const httpUrl = this.serverUrl
531
+ .replace(/^ws:/, "http:")
532
+ .replace(/^wss:/, "https:");
533
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/columns`, {
534
+ method: "GET",
535
+ headers: { Authorization: `Bearer ${this.botToken}` },
536
+ });
537
+ if (!res.ok) {
538
+ const text = await res.text();
539
+ throw new Error(`listColumns failed (${res.status}): ${text}`);
540
+ }
541
+ return res.json();
542
+ }
543
+ /**
544
+ * Create a column in a board.
545
+ * @param boardId - The board ID.
546
+ * @param body - Column name and optional sort order.
547
+ */
548
+ async createColumn(boardId, body) {
549
+ const httpUrl = this.serverUrl
550
+ .replace(/^ws:/, "http:")
551
+ .replace(/^wss:/, "https:");
552
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/columns`, {
553
+ method: "POST",
554
+ headers: {
555
+ Authorization: `Bearer ${this.botToken}`,
556
+ "Content-Type": "application/json",
557
+ },
558
+ body: JSON.stringify(body),
559
+ });
560
+ if (!res.ok) {
561
+ const text = await res.text();
562
+ throw new Error(`createColumn failed (${res.status}): ${text}`);
563
+ }
564
+ return res.json();
565
+ }
566
+ /**
567
+ * Update a column.
568
+ * @param columnId - The column ID to update.
569
+ * @param body - Fields to update (name, sortOrder).
570
+ */
571
+ async updateColumn(columnId, body) {
572
+ const httpUrl = this.serverUrl
573
+ .replace(/^ws:/, "http:")
574
+ .replace(/^wss:/, "https:");
575
+ const res = await fetch(`${httpUrl}/api/v1/kanban/columns/${columnId}`, {
576
+ method: "PATCH",
577
+ headers: {
578
+ Authorization: `Bearer ${this.botToken}`,
579
+ "Content-Type": "application/json",
580
+ },
581
+ body: JSON.stringify(body),
582
+ });
583
+ if (!res.ok) {
584
+ const text = await res.text();
585
+ throw new Error(`updateColumn failed (${res.status}): ${text}`);
586
+ }
587
+ return res.json();
588
+ }
589
+ /**
590
+ * Delete a column.
591
+ * @param columnId - The column ID to delete.
592
+ */
593
+ async deleteColumn(columnId) {
594
+ const httpUrl = this.serverUrl
595
+ .replace(/^ws:/, "http:")
596
+ .replace(/^wss:/, "https:");
597
+ const res = await fetch(`${httpUrl}/api/v1/kanban/columns/${columnId}`, {
598
+ method: "DELETE",
599
+ headers: { Authorization: `Bearer ${this.botToken}` },
600
+ });
601
+ if (!res.ok) {
602
+ const text = await res.text();
603
+ throw new Error(`deleteColumn failed (${res.status}): ${text}`);
604
+ }
605
+ }
606
+ /**
607
+ * Reorder columns in a board.
608
+ * @param boardId - The board ID.
609
+ * @param columnIds - Ordered array of column IDs.
610
+ */
611
+ async reorderColumns(boardId, columnIds) {
612
+ const httpUrl = this.serverUrl
613
+ .replace(/^ws:/, "http:")
614
+ .replace(/^wss:/, "https:");
615
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/columns/reorder`, {
616
+ method: "POST",
617
+ headers: {
618
+ Authorization: `Bearer ${this.botToken}`,
619
+ "Content-Type": "application/json",
620
+ },
621
+ body: JSON.stringify({ columnIds }),
622
+ });
623
+ if (!res.ok) {
624
+ const text = await res.text();
625
+ throw new Error(`reorderColumns failed (${res.status}): ${text}`);
626
+ }
627
+ }
628
+ /**
629
+ * List all kanban cards for the agent's owner.
630
+ */
631
+ async listCards() {
632
+ const httpUrl = this.serverUrl
633
+ .replace(/^ws:/, "http:")
634
+ .replace(/^wss:/, "https:");
635
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards`, {
636
+ method: "GET",
637
+ headers: { Authorization: `Bearer ${this.botToken}` },
638
+ });
639
+ if (!res.ok) {
640
+ const text = await res.text();
641
+ throw new Error(`listCards failed (${res.status}): ${text}`);
642
+ }
643
+ return res.json();
644
+ }
645
+ /**
646
+ * Mark a card as complete (moves it to the Done column).
647
+ * @param cardId - The card ID to complete.
648
+ */
649
+ async completeCard(cardId) {
650
+ const httpUrl = this.serverUrl
651
+ .replace(/^ws:/, "http:")
652
+ .replace(/^wss:/, "https:");
653
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/complete`, {
654
+ method: "POST",
655
+ headers: { Authorization: `Bearer ${this.botToken}` },
656
+ });
657
+ if (!res.ok) {
658
+ const text = await res.text();
659
+ throw new Error(`completeCard failed (${res.status}): ${text}`);
660
+ }
661
+ return res.json();
662
+ }
663
+ /**
664
+ * List archived cards for a board.
665
+ * @param boardId - The board ID.
666
+ * @param options - Pagination options (page, limit).
667
+ */
668
+ async listArchivedCards(boardId, options) {
669
+ const httpUrl = this.serverUrl
670
+ .replace(/^ws:/, "http:")
671
+ .replace(/^wss:/, "https:");
672
+ const params = new URLSearchParams();
673
+ if (options?.page != null)
674
+ params.set("page", String(options.page));
675
+ if (options?.limit != null)
676
+ params.set("limit", String(options.limit));
677
+ const qs = params.toString();
678
+ const url = `${httpUrl}/api/v1/kanban/boards/${boardId}/archived-cards${qs ? `?${qs}` : ""}`;
679
+ const res = await fetch(url, {
680
+ method: "GET",
681
+ headers: { Authorization: `Bearer ${this.botToken}` },
682
+ });
683
+ if (!res.ok) {
684
+ const text = await res.text();
685
+ throw new Error(`listArchivedCards failed (${res.status}): ${text}`);
686
+ }
687
+ return res.json();
688
+ }
689
+ /**
690
+ * Add a commit link to a card.
691
+ * @param cardId - The card ID.
692
+ * @param body - Commit hash and optional message.
693
+ */
694
+ async addCardCommit(cardId, body) {
695
+ const httpUrl = this.serverUrl
696
+ .replace(/^ws:/, "http:")
697
+ .replace(/^wss:/, "https:");
698
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/commits`, {
699
+ method: "POST",
700
+ headers: {
701
+ Authorization: `Bearer ${this.botToken}`,
702
+ "Content-Type": "application/json",
703
+ },
704
+ body: JSON.stringify(body),
705
+ });
706
+ if (!res.ok) {
707
+ const text = await res.text();
708
+ throw new Error(`addCardCommit failed (${res.status}): ${text}`);
709
+ }
710
+ return res.json();
711
+ }
712
+ /**
713
+ * List commits linked to a card.
714
+ * @param cardId - The card ID.
715
+ */
716
+ async listCardCommits(cardId) {
717
+ const httpUrl = this.serverUrl
718
+ .replace(/^ws:/, "http:")
719
+ .replace(/^wss:/, "https:");
720
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/commits`, {
721
+ method: "GET",
722
+ headers: { Authorization: `Bearer ${this.botToken}` },
723
+ });
724
+ if (!res.ok) {
725
+ const text = await res.text();
726
+ throw new Error(`listCardCommits failed (${res.status}): ${text}`);
727
+ }
728
+ return res.json();
729
+ }
730
+ /**
731
+ * Link a note to a card.
732
+ * @param cardId - The card ID.
733
+ * @param noteId - The note ID to link.
734
+ */
735
+ async linkCardNote(cardId, noteId) {
736
+ const httpUrl = this.serverUrl
737
+ .replace(/^ws:/, "http:")
738
+ .replace(/^wss:/, "https:");
739
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/notes`, {
740
+ method: "POST",
741
+ headers: {
742
+ Authorization: `Bearer ${this.botToken}`,
743
+ "Content-Type": "application/json",
744
+ },
745
+ body: JSON.stringify({ noteId }),
746
+ });
747
+ if (!res.ok) {
748
+ const text = await res.text();
749
+ throw new Error(`linkCardNote failed (${res.status}): ${text}`);
750
+ }
751
+ }
752
+ /**
753
+ * Unlink a note from a card.
754
+ * @param cardId - The card ID.
755
+ * @param noteId - The note ID to unlink.
756
+ */
757
+ async unlinkCardNote(cardId, noteId) {
758
+ const httpUrl = this.serverUrl
759
+ .replace(/^ws:/, "http:")
760
+ .replace(/^wss:/, "https:");
761
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/notes/${noteId}`, {
762
+ method: "DELETE",
763
+ headers: { Authorization: `Bearer ${this.botToken}` },
764
+ });
765
+ if (!res.ok) {
766
+ const text = await res.text();
767
+ throw new Error(`unlinkCardNote failed (${res.status}): ${text}`);
768
+ }
769
+ }
770
+ /**
771
+ * List notes linked to a card.
772
+ * @param cardId - The card ID.
773
+ */
774
+ async listCardNotes(cardId) {
775
+ const httpUrl = this.serverUrl
776
+ .replace(/^ws:/, "http:")
777
+ .replace(/^wss:/, "https:");
778
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/notes`, {
779
+ method: "GET",
780
+ headers: { Authorization: `Bearer ${this.botToken}` },
781
+ });
782
+ if (!res.ok) {
783
+ const text = await res.text();
784
+ throw new Error(`listCardNotes failed (${res.status}): ${text}`);
785
+ }
786
+ return res.json();
787
+ }
788
+ // ── Label API ────────────────────────────────────────────────
789
+ /**
790
+ * List labels for a board.
791
+ * @param boardId - The board ID.
792
+ */
793
+ async listLabels(boardId) {
794
+ const httpUrl = this.serverUrl
795
+ .replace(/^ws:/, "http:")
796
+ .replace(/^wss:/, "https:");
797
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/labels`, {
798
+ method: "GET",
799
+ headers: { Authorization: `Bearer ${this.botToken}` },
800
+ });
801
+ if (!res.ok) {
802
+ const text = await res.text();
803
+ throw new Error(`listLabels failed (${res.status}): ${text}`);
804
+ }
805
+ return res.json();
806
+ }
807
+ /**
808
+ * Create a label on a board.
809
+ * @param boardId - The board ID.
810
+ * @param body - Label name and optional color.
811
+ */
812
+ async createLabel(boardId, body) {
813
+ const httpUrl = this.serverUrl
814
+ .replace(/^ws:/, "http:")
815
+ .replace(/^wss:/, "https:");
816
+ const res = await fetch(`${httpUrl}/api/v1/kanban/boards/${boardId}/labels`, {
817
+ method: "POST",
818
+ headers: {
819
+ Authorization: `Bearer ${this.botToken}`,
820
+ "Content-Type": "application/json",
821
+ },
822
+ body: JSON.stringify(body),
823
+ });
824
+ if (!res.ok) {
825
+ const text = await res.text();
826
+ throw new Error(`createLabel failed (${res.status}): ${text}`);
827
+ }
828
+ return res.json();
829
+ }
830
+ /**
831
+ * Update a label.
832
+ * @param labelId - The label ID to update.
833
+ * @param body - Fields to update (name, color).
834
+ */
835
+ async updateLabel(labelId, body) {
836
+ const httpUrl = this.serverUrl
837
+ .replace(/^ws:/, "http:")
838
+ .replace(/^wss:/, "https:");
839
+ const res = await fetch(`${httpUrl}/api/v1/kanban/labels/${labelId}`, {
840
+ method: "PATCH",
841
+ headers: {
842
+ Authorization: `Bearer ${this.botToken}`,
843
+ "Content-Type": "application/json",
844
+ },
845
+ body: JSON.stringify(body),
846
+ });
847
+ if (!res.ok) {
848
+ const text = await res.text();
849
+ throw new Error(`updateLabel failed (${res.status}): ${text}`);
850
+ }
851
+ return res.json();
852
+ }
853
+ /**
854
+ * Delete a label.
855
+ * @param labelId - The label ID to delete.
856
+ */
857
+ async deleteLabel(labelId) {
858
+ const httpUrl = this.serverUrl
859
+ .replace(/^ws:/, "http:")
860
+ .replace(/^wss:/, "https:");
861
+ const res = await fetch(`${httpUrl}/api/v1/kanban/labels/${labelId}`, {
862
+ method: "DELETE",
863
+ headers: { Authorization: `Bearer ${this.botToken}` },
864
+ });
865
+ if (!res.ok) {
866
+ const text = await res.text();
867
+ throw new Error(`deleteLabel failed (${res.status}): ${text}`);
868
+ }
869
+ }
870
+ /**
871
+ * Add a label to a card.
872
+ * @param cardId - The card ID.
873
+ * @param labelId - The label ID to add.
874
+ */
875
+ async addCardLabel(cardId, labelId) {
876
+ const httpUrl = this.serverUrl
877
+ .replace(/^ws:/, "http:")
878
+ .replace(/^wss:/, "https:");
879
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/labels`, {
880
+ method: "POST",
881
+ headers: {
882
+ Authorization: `Bearer ${this.botToken}`,
883
+ "Content-Type": "application/json",
884
+ },
885
+ body: JSON.stringify({ labelId }),
886
+ });
887
+ if (!res.ok) {
888
+ const text = await res.text();
889
+ throw new Error(`addCardLabel failed (${res.status}): ${text}`);
890
+ }
891
+ }
892
+ /**
893
+ * Remove a label from a card.
894
+ * @param cardId - The card ID.
895
+ * @param labelId - The label ID to remove.
896
+ */
897
+ async removeCardLabel(cardId, labelId) {
898
+ const httpUrl = this.serverUrl
899
+ .replace(/^ws:/, "http:")
900
+ .replace(/^wss:/, "https:");
901
+ const res = await fetch(`${httpUrl}/api/v1/kanban/cards/${cardId}/labels/${labelId}`, {
902
+ method: "DELETE",
903
+ headers: { Authorization: `Bearer ${this.botToken}` },
904
+ });
905
+ if (!res.ok) {
906
+ const text = await res.text();
907
+ throw new Error(`removeCardLabel failed (${res.status}): ${text}`);
908
+ }
909
+ }
910
+ // ── Memory API ───────────────────────────────────────────────
911
+ /**
912
+ * Search memories across all memory capsules granted to this agent.
913
+ * Uses hybrid search (embedding + text) to find relevant memories.
914
+ * @param options - Query string and optional limit.
915
+ */
916
+ async queryMemory(options) {
917
+ const httpUrl = this.serverUrl
918
+ .replace(/^ws:/, "http:")
919
+ .replace(/^wss:/, "https:");
920
+ const params = new URLSearchParams();
921
+ params.set("query", options.query);
922
+ if (options.limit != null)
923
+ params.set("limit", String(options.limit));
924
+ const res = await fetch(`${httpUrl}/api/v1/capsules?${params}`, {
925
+ method: "GET",
926
+ headers: { Authorization: `Bearer ${this.botToken}` },
927
+ });
928
+ if (!res.ok) {
929
+ const body = await res.text();
930
+ throw new Error(`queryMemory failed (${res.status}): ${body}`);
931
+ }
932
+ // Server returns snake_case, map to camelCase
933
+ const raw = (await res.json());
934
+ return raw.map((r) => ({
935
+ content: r.content,
936
+ capsuleName: r.capsule_name,
937
+ capsuleId: r.capsule_id,
938
+ score: r.score,
939
+ importance: r.importance,
940
+ }));
941
+ }
942
+ // ── Skill Prompt API ─────────────────────────────────────────
943
+ /**
944
+ * Fetch the full prompt content for an installed skill by slug.
945
+ * Use this when the agent decides to trigger a skill from availableSkills.
946
+ * @param skillSlug - The skill slug (e.g. "draw", "proactive-agent").
947
+ */
948
+ async fetchSkillPrompt(skillSlug) {
949
+ const httpUrl = this.serverUrl
950
+ .replace(/^ws:/, "http:")
951
+ .replace(/^wss:/, "https:");
952
+ const res = await fetch(`${httpUrl}/api/v1/skills/${encodeURIComponent(skillSlug)}/prompt`, {
953
+ method: "GET",
954
+ headers: { Authorization: `Bearer ${this.botToken}` },
955
+ });
956
+ if (!res.ok) {
957
+ const body = await res.text();
958
+ throw new Error(`fetchSkillPrompt failed (${res.status}): ${body}`);
959
+ }
960
+ return (await res.json());
961
+ }
962
+ // ── Note Share API ───────────────────────────────────────────
963
+ /**
964
+ * Share a note as a message in a conversation.
965
+ * Creates a rich preview card visible to all conversation members.
966
+ * @param conversationId - The conversation to share into.
967
+ * @param noteId - The note ID to share.
968
+ */
969
+ async shareNote(conversationId, noteId) {
970
+ const httpUrl = this.serverUrl
971
+ .replace(/^ws:/, "http:")
972
+ .replace(/^wss:/, "https:");
973
+ const res = await fetch(`${httpUrl}/api/v1/notes/${noteId}/share`, {
974
+ method: "POST",
975
+ headers: { Authorization: `Bearer ${this.botToken}` },
976
+ });
977
+ if (!res.ok) {
978
+ const text = await res.text();
979
+ throw new Error(`shareNote failed (${res.status}): ${text}`);
980
+ }
981
+ return res.json();
982
+ }
354
983
  handleTask(data) {
355
984
  if (!this.taskHandler)
356
985
  return;