@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/README.md +276 -0
- package/dist/client.d.ts +173 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +636 -7
- package/dist/client.js.map +1 -1
- package/dist/client.test.d.ts +2 -0
- package/dist/client.test.d.ts.map +1 -0
- package/dist/client.test.js +90 -0
- package/dist/client.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +166 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -2
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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;
|