@band-ai/sdk 0.1.10 → 0.2.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 +54 -54
- package/dist/{ClaudeSDKAdapter-DfjaI5ke.d.cts → ClaudeSDKAdapter-BY4zEbCl.d.cts} +12 -10
- package/dist/{ClaudeSDKAdapter-DonPGJ43.d.ts → ClaudeSDKAdapter-Cna5P1dR.d.ts} +12 -10
- package/dist/{acp-server-NZAeg-WO.d.ts → acp-server-CiUqN3G3.d.ts} +2 -2
- package/dist/{acp-server-CUBXIiG6.d.cts → acp-server-Dlj7D563.d.cts} +2 -2
- package/dist/adapters.cjs +250 -183
- package/dist/adapters.d.cts +21 -19
- package/dist/adapters.d.ts +21 -19
- package/dist/adapters.js +65 -44
- package/dist/backends-D-dBdDm-.d.ts +27 -0
- package/dist/backends-ZLxmQrTZ.d.cts +27 -0
- package/dist/{chunk-2BHHR6YW.js → chunk-4IAXZBXX.js} +62 -23
- package/dist/{chunk-SG4WLD2H.js → chunk-EAI2M3WM.js} +38 -94
- package/dist/{chunk-YR3BOQNW.js → chunk-JY75VSQK.js} +7 -7
- package/dist/{chunk-RDM4TE4B.js → chunk-LBWKISBS.js} +19 -1
- package/dist/{chunk-FP237EAG.js → chunk-N2QTUMGO.js} +112 -195
- package/dist/{chunk-J3M3PFFY.js → chunk-NMZLX7TP.js} +35 -31
- package/dist/{chunk-6RXWL6TC.js → chunk-NT7TBFX2.js} +1 -1
- package/dist/{chunk-MLROFDKR.js → chunk-RV76SKFT.js} +39 -33
- package/dist/{chunk-XA4N2JPH.js → chunk-TLBCKDQO.js} +23 -23
- package/dist/{chunk-IZPV6QS6.js → chunk-WL2HNJZ6.js} +38 -11
- package/dist/{chunk-DNPVCR3S.js → chunk-WLCM2OXG.js} +31 -31
- package/dist/config.cjs +40 -13
- package/dist/config.d.cts +7 -1
- package/dist/config.d.ts +7 -1
- package/dist/config.js +2 -2
- package/dist/converters.cjs +61 -61
- package/dist/converters.d.cts +5 -5
- package/dist/converters.d.ts +5 -5
- package/dist/converters.js +4 -4
- package/dist/core.cjs +8 -8
- package/dist/core.d.cts +9 -9
- package/dist/core.d.ts +9 -9
- package/dist/core.js +3 -3
- package/dist/{dtos-Dd_fTfLn.d.cts → dtos-B8KU_q4d.d.cts} +2 -2
- package/dist/{dtos-Dd_fTfLn.d.ts → dtos-B8KU_q4d.d.ts} +2 -2
- package/dist/index.cjs +459 -504
- package/dist/index.d.cts +16 -15
- package/dist/index.d.ts +16 -15
- package/dist/index.js +15 -13
- package/dist/linear.cjs +74 -74
- package/dist/linear.d.cts +14 -14
- package/dist/linear.d.ts +14 -14
- package/dist/linear.js +72 -72
- package/dist/mcp-claude.cjs +37 -36
- package/dist/mcp-claude.d.cts +3 -3
- package/dist/mcp-claude.d.ts +3 -3
- package/dist/mcp-claude.js +7 -6
- package/dist/mcp.cjs +103 -62
- package/dist/mcp.d.cts +29 -16
- package/dist/mcp.d.ts +29 -16
- package/dist/mcp.js +14 -14
- package/dist/{opencode--iA5-KYk.d.cts → opencode-2YdscIc1.d.cts} +5 -5
- package/dist/{opencode-YNacv-eJ.d.ts → opencode-Bld62x5t.d.ts} +5 -5
- package/dist/{pagination-BBLsYLhT.d.ts → pagination-BgEx-HeL.d.ts} +2 -2
- package/dist/{pagination-CqidD-L_.d.cts → pagination-DqdkQArT.d.cts} +2 -2
- package/dist/{protocols-BF6jamIl.d.ts → protocols-Bwwcql0h.d.ts} +1 -1
- package/dist/{protocols-D2bTrQ_t.d.cts → protocols-Bz1rxcUg.d.cts} +1 -1
- package/dist/rest.cjs +41 -97
- package/dist/rest.d.cts +7 -7
- package/dist/rest.d.ts +7 -7
- package/dist/rest.js +2 -2
- package/dist/runtime.cjs +293 -464
- package/dist/runtime.d.cts +16 -28
- package/dist/runtime.d.ts +16 -28
- package/dist/runtime.js +12 -63
- package/dist/{schemas-CI0qgcp_.d.cts → schemas-BotL3ZLH.d.cts} +40 -54
- package/dist/{schemas-B1Fd3oUM.d.ts → schemas-DBsC4J1g.d.ts} +40 -54
- package/dist/{sdk-D2rTDU_o.d.cts → sdk-BTciSLRQ.d.cts} +5 -5
- package/dist/{sdk-CyqN1W5g.d.ts → sdk-qKJtMma_.d.ts} +5 -5
- package/dist/{simpleAdapter-qQt-tSz4.d.ts → simpleAdapter--wznuoOw.d.ts} +1 -1
- package/dist/{simpleAdapter-C_f-01Cu.d.cts → simpleAdapter-BpT4XbZC.d.cts} +1 -1
- package/dist/testing.d.cts +3 -3
- package/dist/testing.d.ts +3 -3
- package/dist/{types-DNySTI_8.d.ts → types-BTN_ZETM.d.ts} +2 -2
- package/dist/{types-D3AsUnd0.d.ts → types-BgYFd_Yw.d.cts} +5 -5
- package/dist/{types-DtMms5Fi.d.cts → types-BivGO7I9.d.ts} +5 -5
- package/dist/{types-CKqrhGB1.d.cts → types-D958TC71.d.cts} +2 -2
- package/package.json +6 -5
- package/dist/backends-DYtEhCZG.d.cts +0 -25
- package/dist/backends-oYAb7EU-.d.ts +0 -25
|
@@ -3,8 +3,10 @@ import {
|
|
|
3
3
|
SYNTHETIC_CONTACT_EVENTS_SENDER_ID,
|
|
4
4
|
SYNTHETIC_CONTACT_EVENTS_SENDER_NAME,
|
|
5
5
|
SYNTHETIC_SENDER_TYPE,
|
|
6
|
-
buildParticipantsMessage
|
|
7
|
-
|
|
6
|
+
buildParticipantsMessage,
|
|
7
|
+
toParticipantRecord,
|
|
8
|
+
toParticipantRecordFromRest
|
|
9
|
+
} from "./chunk-LBWKISBS.js";
|
|
8
10
|
import {
|
|
9
11
|
CHAT_EVENT_TYPES,
|
|
10
12
|
CHAT_TOOL_NAMES,
|
|
@@ -28,7 +30,7 @@ import {
|
|
|
28
30
|
isMemoryType,
|
|
29
31
|
isMemoryTypeForSystem,
|
|
30
32
|
memoryTypeForSystemError
|
|
31
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-RV76SKFT.js";
|
|
32
34
|
import {
|
|
33
35
|
WebSocketDisconnectError,
|
|
34
36
|
genericCloseReason,
|
|
@@ -39,7 +41,7 @@ import {
|
|
|
39
41
|
DEFAULT_REQUEST_OPTIONS,
|
|
40
42
|
FernRestAdapter,
|
|
41
43
|
fetchPaginated
|
|
42
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-EAI2M3WM.js";
|
|
43
45
|
import {
|
|
44
46
|
DEFAULT_AGENT_TOOLS_CAPABILITIES,
|
|
45
47
|
createToolExecutorError,
|
|
@@ -53,7 +55,7 @@ import {
|
|
|
53
55
|
TransportError,
|
|
54
56
|
UnsupportedFeatureError,
|
|
55
57
|
ValidationError
|
|
56
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-JY75VSQK.js";
|
|
57
59
|
|
|
58
60
|
// src/contracts/capabilities.ts
|
|
59
61
|
var CAPABILITY_LABELS = {
|
|
@@ -170,7 +172,7 @@ import { Socket } from "phoenix";
|
|
|
170
172
|
import { WebSocket as NodeWebSocket } from "ws";
|
|
171
173
|
function createNodeWebSocketFactory(headers) {
|
|
172
174
|
const Constructor = NodeWebSocket;
|
|
173
|
-
class
|
|
175
|
+
class BandNodeWebSocket {
|
|
174
176
|
constructor(address, protocols) {
|
|
175
177
|
const socket = new Constructor(address, protocols, headers ? { headers } : void 0);
|
|
176
178
|
socket.once("unexpected-response", (request, response) => {
|
|
@@ -179,7 +181,7 @@ function createNodeWebSocketFactory(headers) {
|
|
|
179
181
|
return socket;
|
|
180
182
|
}
|
|
181
183
|
}
|
|
182
|
-
return
|
|
184
|
+
return BandNodeWebSocket;
|
|
183
185
|
}
|
|
184
186
|
async function emitUpgradeError(websocket, request, response) {
|
|
185
187
|
const body = await readResponseBody(response);
|
|
@@ -567,9 +569,9 @@ function getSocketChannelCount(socket) {
|
|
|
567
569
|
return candidate.channels.length;
|
|
568
570
|
}
|
|
569
571
|
|
|
570
|
-
// src/platform/
|
|
571
|
-
import {
|
|
572
|
-
var DEFAULT_WS_URL = "wss://app.
|
|
572
|
+
// src/platform/BandLink.ts
|
|
573
|
+
import { BandClient } from "@band-ai/rest-client";
|
|
574
|
+
var DEFAULT_WS_URL = "wss://app.band.ai/api/v1/socket";
|
|
573
575
|
function deriveDefaultRestUrl(wsUrl) {
|
|
574
576
|
const parsed = new URL(wsUrl);
|
|
575
577
|
const protocol = parsed.protocol === "ws:" ? "http:" : "https:";
|
|
@@ -594,7 +596,7 @@ function toPlatformMessage(roomId, message) {
|
|
|
594
596
|
createdAt: new Date(message.inserted_at)
|
|
595
597
|
};
|
|
596
598
|
}
|
|
597
|
-
var
|
|
599
|
+
var BandLink = class {
|
|
598
600
|
agentId;
|
|
599
601
|
apiKey;
|
|
600
602
|
wsUrl;
|
|
@@ -620,7 +622,7 @@ var ThenvoiLink = class {
|
|
|
620
622
|
...options.capabilities
|
|
621
623
|
};
|
|
622
624
|
const restApi = options.restApi ?? new FernRestAdapter(
|
|
623
|
-
new
|
|
625
|
+
new BandClient({
|
|
624
626
|
apiKey: this.apiKey,
|
|
625
627
|
baseUrl: this.restUrl
|
|
626
628
|
})
|
|
@@ -1235,6 +1237,7 @@ var Execution = class {
|
|
|
1235
1237
|
};
|
|
1236
1238
|
|
|
1237
1239
|
// src/runtime/tools/AgentTools.ts
|
|
1240
|
+
import { ParticipantRoster } from "@band-ai/band-sdk-core";
|
|
1238
1241
|
var REQUIRED_ADAPTER_TOOL_METHODS = [
|
|
1239
1242
|
"sendMessage",
|
|
1240
1243
|
"sendEvent",
|
|
@@ -1273,13 +1276,13 @@ var AgentTools = class {
|
|
|
1273
1276
|
roomId;
|
|
1274
1277
|
capabilities;
|
|
1275
1278
|
rest;
|
|
1276
|
-
|
|
1279
|
+
roster;
|
|
1277
1280
|
adapterTools;
|
|
1278
1281
|
toolHandlers;
|
|
1279
1282
|
constructor(options) {
|
|
1280
1283
|
this.roomId = options.roomId;
|
|
1281
1284
|
this.rest = options.rest;
|
|
1282
|
-
this.
|
|
1285
|
+
this.roster = options.roster ?? new ParticipantRoster();
|
|
1283
1286
|
this.capabilities = {
|
|
1284
1287
|
...DEFAULT_AGENT_TOOLS_CAPABILITIES,
|
|
1285
1288
|
...options.capabilities
|
|
@@ -1291,17 +1294,20 @@ var AgentTools = class {
|
|
|
1291
1294
|
return this.adapterTools;
|
|
1292
1295
|
}
|
|
1293
1296
|
async sendMessage(content, mentions = []) {
|
|
1294
|
-
|
|
1295
|
-
|
|
1297
|
+
let participants;
|
|
1298
|
+
if (mentions.length > 0 && typeof mentions[0] === "string") {
|
|
1299
|
+
participants = this.roster.list();
|
|
1300
|
+
if (participants.length === 0) {
|
|
1301
|
+
participants = await this.syncParticipants();
|
|
1302
|
+
}
|
|
1296
1303
|
}
|
|
1297
|
-
const resolvedMentions = this.resolveMentions(mentions);
|
|
1304
|
+
const resolvedMentions = this.resolveMentions(mentions, participants);
|
|
1298
1305
|
return this.rest.createChatMessage(
|
|
1299
1306
|
this.roomId,
|
|
1300
1307
|
{
|
|
1301
1308
|
content,
|
|
1302
1309
|
mentions: resolvedMentions
|
|
1303
|
-
}
|
|
1304
|
-
DEFAULT_REQUEST_OPTIONS
|
|
1310
|
+
}
|
|
1305
1311
|
);
|
|
1306
1312
|
}
|
|
1307
1313
|
async sendEvent(content, messageType, metadata) {
|
|
@@ -1312,8 +1318,7 @@ var AgentTools = class {
|
|
|
1312
1318
|
content,
|
|
1313
1319
|
messageType,
|
|
1314
1320
|
metadata
|
|
1315
|
-
}
|
|
1316
|
-
DEFAULT_REQUEST_OPTIONS
|
|
1321
|
+
}
|
|
1317
1322
|
);
|
|
1318
1323
|
}
|
|
1319
1324
|
async createChatroom(taskId) {
|
|
@@ -1352,7 +1357,7 @@ var AgentTools = class {
|
|
|
1352
1357
|
type: String(peer.type ?? "Agent"),
|
|
1353
1358
|
handle: typeof peer.handle === "string" ? peer.handle : null
|
|
1354
1359
|
};
|
|
1355
|
-
this.
|
|
1360
|
+
this.roster.add(participantRecord);
|
|
1356
1361
|
return {
|
|
1357
1362
|
...participantRecord,
|
|
1358
1363
|
status: "added"
|
|
@@ -1367,7 +1372,7 @@ var AgentTools = class {
|
|
|
1367
1372
|
throw new ValidationError(`Participant '${name}' not found in room`);
|
|
1368
1373
|
}
|
|
1369
1374
|
await this.rest.removeChatParticipant(this.roomId, String(participant.id), DEFAULT_REQUEST_OPTIONS);
|
|
1370
|
-
this.
|
|
1375
|
+
this.roster.remove(participant.id);
|
|
1371
1376
|
return {
|
|
1372
1377
|
id: participant.id,
|
|
1373
1378
|
name,
|
|
@@ -1395,17 +1400,12 @@ var AgentTools = class {
|
|
|
1395
1400
|
}
|
|
1396
1401
|
async fetchParticipants() {
|
|
1397
1402
|
const participants = await this.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
|
|
1398
|
-
return participants.map(
|
|
1399
|
-
id: participant.id,
|
|
1400
|
-
name: participant.name,
|
|
1401
|
-
type: participant.type,
|
|
1402
|
-
handle: participant.handle ?? null
|
|
1403
|
-
}));
|
|
1403
|
+
return participants.map(toParticipantRecordFromRest);
|
|
1404
1404
|
}
|
|
1405
1405
|
async syncParticipants() {
|
|
1406
1406
|
const participants = await this.fetchParticipants();
|
|
1407
|
-
this.
|
|
1408
|
-
return
|
|
1407
|
+
this.roster.setAll(participants);
|
|
1408
|
+
return this.roster.list().map(toParticipantRecord);
|
|
1409
1409
|
}
|
|
1410
1410
|
async executeToolCall(toolName, arguments_) {
|
|
1411
1411
|
const validationError = validateToolArgs(toolName, arguments_);
|
|
@@ -1453,7 +1453,7 @@ var AgentTools = class {
|
|
|
1453
1453
|
if (!CHAT_TOOL_NAMES.has(name) && !this.capabilities.contacts) {
|
|
1454
1454
|
return false;
|
|
1455
1455
|
}
|
|
1456
|
-
if (name === "
|
|
1456
|
+
if (name === "band_lookup_peers" && !this.capabilities.peers) {
|
|
1457
1457
|
return false;
|
|
1458
1458
|
}
|
|
1459
1459
|
return true;
|
|
@@ -1648,7 +1648,7 @@ var AgentTools = class {
|
|
|
1648
1648
|
}
|
|
1649
1649
|
return this.rest.archiveMemory(normalizedMemoryId, DEFAULT_REQUEST_OPTIONS);
|
|
1650
1650
|
}
|
|
1651
|
-
resolveMentions(mentions) {
|
|
1651
|
+
resolveMentions(mentions, participants) {
|
|
1652
1652
|
if (mentions.length === 0) {
|
|
1653
1653
|
return [];
|
|
1654
1654
|
}
|
|
@@ -1661,7 +1661,7 @@ var AgentTools = class {
|
|
|
1661
1661
|
const participantsByHandle = /* @__PURE__ */ new Map();
|
|
1662
1662
|
const participantsById = /* @__PURE__ */ new Map();
|
|
1663
1663
|
const participantsByName = /* @__PURE__ */ new Map();
|
|
1664
|
-
for (const participant of
|
|
1664
|
+
for (const participant of participants ?? []) {
|
|
1665
1665
|
const ref = {
|
|
1666
1666
|
id: String(participant.id),
|
|
1667
1667
|
handle: typeof participant.handle === "string" ? participant.handle : void 0
|
|
@@ -1717,9 +1717,6 @@ var AgentTools = class {
|
|
|
1717
1717
|
normalizeMentionHandle(handle) {
|
|
1718
1718
|
return handle.trim().replace(/^@+/, "").toLowerCase();
|
|
1719
1719
|
}
|
|
1720
|
-
replaceParticipants(participants) {
|
|
1721
|
-
this.participants.splice(0, this.participants.length, ...participants);
|
|
1722
|
-
}
|
|
1723
1720
|
buildAdapterTools() {
|
|
1724
1721
|
const tools = {
|
|
1725
1722
|
capabilities: this.capabilities
|
|
@@ -1750,51 +1747,51 @@ var AgentTools = class {
|
|
|
1750
1747
|
}
|
|
1751
1748
|
buildMessagingToolHandlers() {
|
|
1752
1749
|
return {
|
|
1753
|
-
|
|
1750
|
+
band_send_message: async (arguments_) => this.sendMessage(
|
|
1754
1751
|
String(arguments_.content ?? ""),
|
|
1755
1752
|
this.normalizeMentionInput(arguments_.mentions)
|
|
1756
1753
|
),
|
|
1757
|
-
|
|
1754
|
+
band_send_event: async (arguments_) => this.sendEvent(
|
|
1758
1755
|
String(arguments_.content ?? ""),
|
|
1759
1756
|
String(arguments_.message_type ?? "task"),
|
|
1760
1757
|
this.normalizeOptionalMetadata(arguments_.metadata)
|
|
1761
1758
|
),
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1759
|
+
band_add_participant: async (arguments_) => this.addParticipant(String(arguments_.name ?? ""), String(arguments_.role ?? "member")),
|
|
1760
|
+
band_remove_participant: async (arguments_) => this.removeParticipant(String(arguments_.name ?? "")),
|
|
1761
|
+
band_lookup_peers: async (arguments_) => this.lookupPeers(
|
|
1765
1762
|
coercePositiveInt(arguments_.page, 1),
|
|
1766
1763
|
coercePositiveInt(arguments_.page_size, 50)
|
|
1767
1764
|
),
|
|
1768
|
-
|
|
1769
|
-
|
|
1765
|
+
band_get_participants: async () => this.getParticipants(),
|
|
1766
|
+
band_create_chatroom: async (arguments_) => this.createChatroom(this.normalizeOptionalString(arguments_.task_id))
|
|
1770
1767
|
};
|
|
1771
1768
|
}
|
|
1772
1769
|
buildContactToolHandlers() {
|
|
1773
1770
|
return {
|
|
1774
|
-
|
|
1771
|
+
band_list_contacts: async (arguments_) => this.listContacts({
|
|
1775
1772
|
page: coercePositiveInt(arguments_.page, 1),
|
|
1776
1773
|
pageSize: coercePositiveInt(arguments_.page_size, 50)
|
|
1777
1774
|
}),
|
|
1778
|
-
|
|
1775
|
+
band_add_contact: async (arguments_) => this.addContact({
|
|
1779
1776
|
handle: String(arguments_.handle ?? ""),
|
|
1780
1777
|
...typeof arguments_.message === "string" ? { message: arguments_.message } : {}
|
|
1781
1778
|
}),
|
|
1782
|
-
|
|
1783
|
-
|
|
1779
|
+
band_remove_contact: async (arguments_) => this.removeContact(this.toRemoveContactArgs(arguments_)),
|
|
1780
|
+
band_list_contact_requests: async (arguments_) => this.listContactRequests({
|
|
1784
1781
|
page: coercePositiveInt(arguments_.page, 1),
|
|
1785
1782
|
pageSize: coercePositiveInt(arguments_.page_size, 50),
|
|
1786
1783
|
sentStatus: String(arguments_.sent_status ?? "pending")
|
|
1787
1784
|
}),
|
|
1788
|
-
|
|
1785
|
+
band_respond_contact_request: async (arguments_) => this.respondContactRequest(this.toRespondContactRequestArgs(arguments_))
|
|
1789
1786
|
};
|
|
1790
1787
|
}
|
|
1791
1788
|
buildMemoryToolHandlers() {
|
|
1792
1789
|
return {
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1790
|
+
band_list_memories: async (arguments_) => this.listMemories(this.toListMemoriesArgs(arguments_)),
|
|
1791
|
+
band_store_memory: async (arguments_) => this.storeMemory(this.toStoreMemoryArgs(arguments_)),
|
|
1792
|
+
band_get_memory: async (arguments_) => this.getMemory(String(arguments_.memory_id ?? "")),
|
|
1793
|
+
band_supersede_memory: async (arguments_) => this.supersedeMemory(String(arguments_.memory_id ?? "")),
|
|
1794
|
+
band_archive_memory: async (arguments_) => this.archiveMemory(String(arguments_.memory_id ?? ""))
|
|
1798
1795
|
};
|
|
1799
1796
|
}
|
|
1800
1797
|
toRemoveContactArgs(arguments_) {
|
|
@@ -2038,13 +2035,13 @@ function validateToolArgs(toolName, args) {
|
|
|
2038
2035
|
errors.push(`${field}: Field required`);
|
|
2039
2036
|
}
|
|
2040
2037
|
}
|
|
2041
|
-
if (toolName === "
|
|
2038
|
+
if (toolName === "band_send_message") {
|
|
2042
2039
|
const mentions = args.mentions;
|
|
2043
2040
|
if (Array.isArray(mentions) && mentions.length === 0) {
|
|
2044
2041
|
errors.push("mentions: At least one mention is required");
|
|
2045
2042
|
}
|
|
2046
2043
|
}
|
|
2047
|
-
if (toolName === "
|
|
2044
|
+
if (toolName === "band_send_event") {
|
|
2048
2045
|
const messageType = args.message_type;
|
|
2049
2046
|
if (typeof messageType === "string" && !CHAT_EVENT_TYPES.includes(messageType)) {
|
|
2050
2047
|
errors.push(
|
|
@@ -2052,7 +2049,7 @@ function validateToolArgs(toolName, args) {
|
|
|
2052
2049
|
);
|
|
2053
2050
|
}
|
|
2054
2051
|
}
|
|
2055
|
-
if (toolName === "
|
|
2052
|
+
if (toolName === "band_respond_contact_request") {
|
|
2056
2053
|
const action = args.action;
|
|
2057
2054
|
const validActions = ["approve", "reject", "cancel"];
|
|
2058
2055
|
if (typeof action === "string" && !validActions.includes(action)) {
|
|
@@ -2061,7 +2058,7 @@ function validateToolArgs(toolName, args) {
|
|
|
2061
2058
|
);
|
|
2062
2059
|
}
|
|
2063
2060
|
}
|
|
2064
|
-
if (toolName === "
|
|
2061
|
+
if (toolName === "band_store_memory") {
|
|
2065
2062
|
if (typeof args.system === "string" && !isMemorySystem(args.system)) {
|
|
2066
2063
|
errors.push(`system: Invalid value '${args.system}'. Expected one of: ${expectedList(MEMORY_SYSTEMS)}`);
|
|
2067
2064
|
}
|
|
@@ -2090,58 +2087,8 @@ function validateToolArgs(toolName, args) {
|
|
|
2090
2087
|
return null;
|
|
2091
2088
|
}
|
|
2092
2089
|
|
|
2093
|
-
// src/runtime/retryTracker.ts
|
|
2094
|
-
var DEFAULT_MAX_TRACKED = 1e4;
|
|
2095
|
-
var MessageRetryTracker = class {
|
|
2096
|
-
maxRetriesValue;
|
|
2097
|
-
maxTracked;
|
|
2098
|
-
attempts = /* @__PURE__ */ new Map();
|
|
2099
|
-
failed = /* @__PURE__ */ new Set();
|
|
2100
|
-
constructor(maxRetries = 1, maxTracked = DEFAULT_MAX_TRACKED) {
|
|
2101
|
-
this.maxRetriesValue = maxRetries;
|
|
2102
|
-
this.maxTracked = maxTracked;
|
|
2103
|
-
}
|
|
2104
|
-
get maxRetries() {
|
|
2105
|
-
return this.maxRetriesValue;
|
|
2106
|
-
}
|
|
2107
|
-
isPermanentlyFailed(messageId) {
|
|
2108
|
-
return this.failed.has(messageId);
|
|
2109
|
-
}
|
|
2110
|
-
recordAttempt(messageId) {
|
|
2111
|
-
const attempts = (this.attempts.get(messageId) ?? 0) + 1;
|
|
2112
|
-
this.attempts.set(messageId, attempts);
|
|
2113
|
-
const exceeded = attempts > this.maxRetriesValue;
|
|
2114
|
-
if (exceeded) {
|
|
2115
|
-
this.evictSetIfNeeded(this.failed);
|
|
2116
|
-
this.failed.add(messageId);
|
|
2117
|
-
}
|
|
2118
|
-
this.evictMapIfNeeded(this.attempts);
|
|
2119
|
-
return [attempts, exceeded];
|
|
2120
|
-
}
|
|
2121
|
-
markSuccess(messageId) {
|
|
2122
|
-
this.attempts.delete(messageId);
|
|
2123
|
-
}
|
|
2124
|
-
markPermanentlyFailed(messageId) {
|
|
2125
|
-
this.evictSetIfNeeded(this.failed);
|
|
2126
|
-
this.failed.add(messageId);
|
|
2127
|
-
}
|
|
2128
|
-
evictMapIfNeeded(map) {
|
|
2129
|
-
while (map.size >= this.maxTracked) {
|
|
2130
|
-
const first = map.keys().next();
|
|
2131
|
-
if (first.done) break;
|
|
2132
|
-
map.delete(first.value);
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
evictSetIfNeeded(set) {
|
|
2136
|
-
while (set.size >= this.maxTracked) {
|
|
2137
|
-
const first = set.values().next();
|
|
2138
|
-
if (first.done) break;
|
|
2139
|
-
set.delete(first.value);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
};
|
|
2143
|
-
|
|
2144
2090
|
// src/runtime/ExecutionContext.ts
|
|
2091
|
+
import { ParticipantRoster as ParticipantRoster2, RetryTracker } from "@band-ai/band-sdk-core";
|
|
2145
2092
|
var DEDUP_CACHE_MAX = 500;
|
|
2146
2093
|
var ExecutionContext = class {
|
|
2147
2094
|
roomId;
|
|
@@ -2153,11 +2100,10 @@ var ExecutionContext = class {
|
|
|
2153
2100
|
history = [];
|
|
2154
2101
|
messageIds = /* @__PURE__ */ new Set();
|
|
2155
2102
|
dedupCache = /* @__PURE__ */ new Map();
|
|
2156
|
-
|
|
2103
|
+
roster = new ParticipantRoster2();
|
|
2157
2104
|
tools;
|
|
2158
2105
|
adapterTools;
|
|
2159
2106
|
participantsMessage = null;
|
|
2160
|
-
lastSentParticipantIds = null;
|
|
2161
2107
|
contactsMessage = null;
|
|
2162
2108
|
contextCache = null;
|
|
2163
2109
|
contextCacheExpiresAt = 0;
|
|
@@ -2172,11 +2118,11 @@ var ExecutionContext = class {
|
|
|
2172
2118
|
this.enableContextCache = options.enableContextCache ?? true;
|
|
2173
2119
|
this.contextCacheTtlMs = Math.max(0, (options.contextCacheTtlSeconds ?? 300) * 1e3);
|
|
2174
2120
|
this.enableContextHydration = options.enableContextHydration ?? true;
|
|
2175
|
-
this.retryTrackerInstance = new
|
|
2121
|
+
this.retryTrackerInstance = new RetryTracker(options.maxMessageRetries ?? 1);
|
|
2176
2122
|
this.tools = new AgentTools({
|
|
2177
2123
|
roomId: this.roomId,
|
|
2178
2124
|
rest: this.link.rest,
|
|
2179
|
-
|
|
2125
|
+
roster: this.roster,
|
|
2180
2126
|
capabilities: this.link.capabilities
|
|
2181
2127
|
});
|
|
2182
2128
|
this.adapterTools = this.tools.getAdapterTools();
|
|
@@ -2244,23 +2190,20 @@ var ExecutionContext = class {
|
|
|
2244
2190
|
}));
|
|
2245
2191
|
}
|
|
2246
2192
|
setParticipants(participants) {
|
|
2247
|
-
this.
|
|
2193
|
+
this.roster.setAll(participants);
|
|
2248
2194
|
this.updateCachedParticipants();
|
|
2249
2195
|
}
|
|
2250
2196
|
addParticipant(participant) {
|
|
2251
|
-
const
|
|
2252
|
-
if (
|
|
2253
|
-
|
|
2197
|
+
const isNew = this.roster.add(participant);
|
|
2198
|
+
if (isNew) {
|
|
2199
|
+
const name = String(participant.name ?? "unknown");
|
|
2200
|
+
this.participantsMessage = `${name} joined the room.`;
|
|
2254
2201
|
}
|
|
2255
|
-
this.participants.push(participant);
|
|
2256
|
-
const name = String(participant.name ?? "unknown");
|
|
2257
|
-
this.participantsMessage = `${name} joined the room.`;
|
|
2258
2202
|
this.updateCachedParticipants();
|
|
2259
2203
|
}
|
|
2260
2204
|
removeParticipant(participantId) {
|
|
2261
|
-
const removed = this.
|
|
2262
|
-
|
|
2263
|
-
this.replaceParticipants(next);
|
|
2205
|
+
const removed = this.roster.list().find((entry) => entry.id === participantId);
|
|
2206
|
+
this.roster.remove(participantId);
|
|
2264
2207
|
if (removed) {
|
|
2265
2208
|
this.participantsMessage = `${String(removed.name ?? participantId)} left the room.`;
|
|
2266
2209
|
}
|
|
@@ -2270,16 +2213,7 @@ var ExecutionContext = class {
|
|
|
2270
2213
|
this.contactsMessage = message;
|
|
2271
2214
|
}
|
|
2272
2215
|
consumeParticipantsMessage() {
|
|
2273
|
-
const
|
|
2274
|
-
let changed = !this.lastSentParticipantIds || currentIds.size !== this.lastSentParticipantIds.size;
|
|
2275
|
-
if (!changed) {
|
|
2276
|
-
for (const id of currentIds) {
|
|
2277
|
-
if (!this.lastSentParticipantIds.has(id)) {
|
|
2278
|
-
changed = true;
|
|
2279
|
-
break;
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2216
|
+
const changed = this.roster.changed();
|
|
2283
2217
|
if (!changed && !this.participantsMessage) {
|
|
2284
2218
|
return null;
|
|
2285
2219
|
}
|
|
@@ -2289,15 +2223,9 @@ var ExecutionContext = class {
|
|
|
2289
2223
|
this.participantsMessage = null;
|
|
2290
2224
|
}
|
|
2291
2225
|
if (changed) {
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
type: p.type,
|
|
2296
|
-
handle: p.handle ?? null
|
|
2297
|
-
}));
|
|
2298
|
-
parts.push(buildParticipantsMessage(asRecords));
|
|
2299
|
-
}
|
|
2300
|
-
this.lastSentParticipantIds = currentIds;
|
|
2226
|
+
parts.push(buildParticipantsMessage(this.roster.list().map((p) => ({ ...toParticipantRecord(p) }))));
|
|
2227
|
+
}
|
|
2228
|
+
this.roster.markSent();
|
|
2301
2229
|
return parts.length > 0 ? parts.join("\n") : null;
|
|
2302
2230
|
}
|
|
2303
2231
|
consumeContactsMessage() {
|
|
@@ -2348,27 +2276,19 @@ var ExecutionContext = class {
|
|
|
2348
2276
|
return fallback;
|
|
2349
2277
|
}
|
|
2350
2278
|
}
|
|
2351
|
-
replaceParticipants(participants) {
|
|
2352
|
-
this.participants.splice(0, this.participants.length, ...participants);
|
|
2353
|
-
}
|
|
2354
2279
|
buildLocalContext() {
|
|
2355
2280
|
return {
|
|
2356
2281
|
roomId: this.roomId,
|
|
2357
2282
|
messages: this.getRawHistory(),
|
|
2358
|
-
participants:
|
|
2283
|
+
participants: this.roster.list().map(toParticipantRecord),
|
|
2359
2284
|
hydratedAt: /* @__PURE__ */ new Date()
|
|
2360
2285
|
};
|
|
2361
2286
|
}
|
|
2362
2287
|
async loadParticipants() {
|
|
2363
2288
|
const participants = await this.link.rest.listChatParticipants(this.roomId, DEFAULT_REQUEST_OPTIONS);
|
|
2364
|
-
const normalized = participants.map(
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
type: participant.type,
|
|
2368
|
-
handle: participant.handle ?? null
|
|
2369
|
-
}));
|
|
2370
|
-
this.replaceParticipants(normalized);
|
|
2371
|
-
return [...this.participants];
|
|
2289
|
+
const normalized = participants.map(toParticipantRecordFromRest);
|
|
2290
|
+
this.roster.setAll(normalized);
|
|
2291
|
+
return this.roster.list().map(toParticipantRecord);
|
|
2372
2292
|
}
|
|
2373
2293
|
async loadHydratedMessages() {
|
|
2374
2294
|
const messages = [];
|
|
@@ -2410,7 +2330,7 @@ var ExecutionContext = class {
|
|
|
2410
2330
|
if (!this.contextCache) {
|
|
2411
2331
|
return;
|
|
2412
2332
|
}
|
|
2413
|
-
this.contextCache.participants =
|
|
2333
|
+
this.contextCache.participants = this.roster.list().map(toParticipantRecord);
|
|
2414
2334
|
this.contextCache.hydratedAt = /* @__PURE__ */ new Date();
|
|
2415
2335
|
this.contextCacheExpiresAt = this.nextCacheExpiry();
|
|
2416
2336
|
}
|
|
@@ -3048,14 +2968,6 @@ var ContactToolsImpl = class {
|
|
|
3048
2968
|
};
|
|
3049
2969
|
|
|
3050
2970
|
// src/runtime/tools/ContactCallbackTools.ts
|
|
3051
|
-
function toParticipantRecord(participant) {
|
|
3052
|
-
return {
|
|
3053
|
-
id: participant.id,
|
|
3054
|
-
name: participant.name,
|
|
3055
|
-
type: participant.type,
|
|
3056
|
-
handle: participant.handle ?? null
|
|
3057
|
-
};
|
|
3058
|
-
}
|
|
3059
2971
|
function normalizePage(value, fallback) {
|
|
3060
2972
|
return Number.isInteger(value) && value && value > 0 ? value : fallback;
|
|
3061
2973
|
}
|
|
@@ -3099,8 +3011,7 @@ var ContactCallbackTools = class {
|
|
|
3099
3011
|
{
|
|
3100
3012
|
content,
|
|
3101
3013
|
...normalizedMentions ? { mentions: normalizedMentions } : {}
|
|
3102
|
-
}
|
|
3103
|
-
DEFAULT_REQUEST_OPTIONS
|
|
3014
|
+
}
|
|
3104
3015
|
);
|
|
3105
3016
|
}
|
|
3106
3017
|
async sendEvent(content, messageType, metadata) {
|
|
@@ -3114,8 +3025,7 @@ var ContactCallbackTools = class {
|
|
|
3114
3025
|
content,
|
|
3115
3026
|
messageType,
|
|
3116
3027
|
...metadata ? { metadata } : {}
|
|
3117
|
-
}
|
|
3118
|
-
DEFAULT_REQUEST_OPTIONS
|
|
3028
|
+
}
|
|
3119
3029
|
);
|
|
3120
3030
|
}
|
|
3121
3031
|
async addParticipant() {
|
|
@@ -3129,7 +3039,7 @@ var ContactCallbackTools = class {
|
|
|
3129
3039
|
if (!this.rest.listChatParticipants) {
|
|
3130
3040
|
throw new UnsupportedFeatureError("Participant listing is not available in current REST adapter");
|
|
3131
3041
|
}
|
|
3132
|
-
return (await this.rest.listChatParticipants(roomId, DEFAULT_REQUEST_OPTIONS)).map(
|
|
3042
|
+
return (await this.rest.listChatParticipants(roomId, DEFAULT_REQUEST_OPTIONS)).map(toParticipantRecordFromRest);
|
|
3133
3043
|
}
|
|
3134
3044
|
async createChatroom(taskId) {
|
|
3135
3045
|
const chat = await this.rest.createChat(taskId, DEFAULT_REQUEST_OPTIONS);
|
|
@@ -3219,48 +3129,48 @@ var ContactCallbackTools = class {
|
|
|
3219
3129
|
}
|
|
3220
3130
|
async executeToolCall(toolName, toolArgs) {
|
|
3221
3131
|
switch (toolName) {
|
|
3222
|
-
case "
|
|
3132
|
+
case "band_send_message":
|
|
3223
3133
|
return this.sendMessage(
|
|
3224
3134
|
String(toolArgs.content ?? ""),
|
|
3225
3135
|
toolArgs.mentions
|
|
3226
3136
|
);
|
|
3227
|
-
case "
|
|
3137
|
+
case "band_send_event":
|
|
3228
3138
|
return this.sendEvent(
|
|
3229
3139
|
String(toolArgs.content ?? ""),
|
|
3230
3140
|
String(toolArgs.message_type ?? "task"),
|
|
3231
3141
|
toolArgs.metadata
|
|
3232
3142
|
);
|
|
3233
|
-
case "
|
|
3143
|
+
case "band_get_participants":
|
|
3234
3144
|
return this.getParticipants();
|
|
3235
|
-
case "
|
|
3145
|
+
case "band_create_chatroom":
|
|
3236
3146
|
return this.createChatroom(typeof toolArgs.task_id === "string" ? toolArgs.task_id : void 0);
|
|
3237
|
-
case "
|
|
3147
|
+
case "band_lookup_peers":
|
|
3238
3148
|
return this.lookupPeers(
|
|
3239
3149
|
typeof toolArgs.page === "number" ? toolArgs.page : void 0,
|
|
3240
3150
|
typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
|
|
3241
3151
|
);
|
|
3242
|
-
case "
|
|
3152
|
+
case "band_list_contacts":
|
|
3243
3153
|
return this.listContacts({
|
|
3244
3154
|
page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
|
|
3245
3155
|
pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0
|
|
3246
3156
|
});
|
|
3247
|
-
case "
|
|
3157
|
+
case "band_add_contact":
|
|
3248
3158
|
return this.addContact({
|
|
3249
3159
|
handle: String(toolArgs.handle ?? ""),
|
|
3250
3160
|
...typeof toolArgs.message === "string" ? { message: toolArgs.message } : {}
|
|
3251
3161
|
});
|
|
3252
|
-
case "
|
|
3162
|
+
case "band_remove_contact":
|
|
3253
3163
|
if (typeof toolArgs.contact_id === "string") {
|
|
3254
3164
|
return this.removeContact({ target: "contactId", contactId: toolArgs.contact_id });
|
|
3255
3165
|
}
|
|
3256
3166
|
return this.removeContact({ target: "handle", handle: String(toolArgs.handle ?? "") });
|
|
3257
|
-
case "
|
|
3167
|
+
case "band_list_contact_requests":
|
|
3258
3168
|
return this.listContactRequests({
|
|
3259
3169
|
page: typeof toolArgs.page === "number" ? toolArgs.page : void 0,
|
|
3260
3170
|
pageSize: typeof toolArgs.page_size === "number" ? toolArgs.page_size : void 0,
|
|
3261
3171
|
sentStatus: typeof toolArgs.sent_status === "string" ? toolArgs.sent_status : void 0
|
|
3262
3172
|
});
|
|
3263
|
-
case "
|
|
3173
|
+
case "band_respond_contact_request":
|
|
3264
3174
|
if (typeof toolArgs.request_id === "string") {
|
|
3265
3175
|
return this.respondContactRequest({
|
|
3266
3176
|
action: String(toolArgs.action ?? "approve"),
|
|
@@ -3273,15 +3183,15 @@ var ContactCallbackTools = class {
|
|
|
3273
3183
|
target: "handle",
|
|
3274
3184
|
handle: String(toolArgs.handle ?? "")
|
|
3275
3185
|
});
|
|
3276
|
-
case "
|
|
3186
|
+
case "band_list_memories":
|
|
3277
3187
|
return this.listMemories(toolArgs);
|
|
3278
|
-
case "
|
|
3188
|
+
case "band_store_memory":
|
|
3279
3189
|
return this.storeMemory(toolArgs);
|
|
3280
|
-
case "
|
|
3190
|
+
case "band_get_memory":
|
|
3281
3191
|
return this.getMemory(String(toolArgs.memory_id ?? ""));
|
|
3282
|
-
case "
|
|
3192
|
+
case "band_supersede_memory":
|
|
3283
3193
|
return this.supersedeMemory(String(toolArgs.memory_id ?? ""));
|
|
3284
|
-
case "
|
|
3194
|
+
case "band_archive_memory":
|
|
3285
3195
|
return this.archiveMemory(String(toolArgs.memory_id ?? ""));
|
|
3286
3196
|
default:
|
|
3287
3197
|
throw new UnsupportedFeatureError(`Unsupported tool call for contact callback: ${toolName}`);
|
|
@@ -3295,14 +3205,14 @@ var HUB_ROOM_SYSTEM_PROMPT = `## OVERRIDE: Contact Management Mode
|
|
|
3295
3205
|
|
|
3296
3206
|
This is your CONTACTS HUB - a dedicated room for managing contact requests.
|
|
3297
3207
|
|
|
3298
|
-
**IMPORTANT: Do NOT delegate or add participants here.** You handle contact events DIRECTLY using the contact tools below. Do NOT call
|
|
3208
|
+
**IMPORTANT: Do NOT delegate or add participants here.** You handle contact events DIRECTLY using the contact tools below. Do NOT call band_lookup_peers() or band_add_participant() in this room.
|
|
3299
3209
|
|
|
3300
3210
|
## Your Role
|
|
3301
3211
|
|
|
3302
3212
|
1. **Review incoming contact requests** - When you see a [Contact Request] message, evaluate it
|
|
3303
3213
|
2. **Take action** - Use the contact tools to respond:
|
|
3304
|
-
- \`
|
|
3305
|
-
- \`
|
|
3214
|
+
- \`band_respond_contact_request(action="approve", request_id="...")\` to accept
|
|
3215
|
+
- \`band_respond_contact_request(action="reject", request_id="...")\` to decline
|
|
3306
3216
|
3. **Report your decision** - Send a thought event explaining what you did
|
|
3307
3217
|
|
|
3308
3218
|
## Example
|
|
@@ -3311,14 +3221,14 @@ This is your CONTACTS HUB - a dedicated room for managing contact requests.
|
|
|
3311
3221
|
Request ID: abc-123
|
|
3312
3222
|
|
|
3313
3223
|
Your response:
|
|
3314
|
-
1.
|
|
3315
|
-
2.
|
|
3316
|
-
3.
|
|
3224
|
+
1. band_send_event("Received contact request from Alice. Approving.", message_type="thought")
|
|
3225
|
+
2. band_respond_contact_request(action="approve", request_id="abc-123")
|
|
3226
|
+
3. band_send_event("Approved contact request from Alice (@alice)", message_type="thought")
|
|
3317
3227
|
|
|
3318
3228
|
## Contact Tools (use these, NOT participant tools)
|
|
3319
|
-
- \`
|
|
3320
|
-
- \`
|
|
3321
|
-
- \`
|
|
3229
|
+
- \`band_respond_contact_request(action, request_id)\` - Approve/reject requests
|
|
3230
|
+
- \`band_list_contact_requests()\` - List pending requests
|
|
3231
|
+
- \`band_list_contacts()\` - List current contacts`;
|
|
3322
3232
|
var ContactEventHandlerError = class extends Error {
|
|
3323
3233
|
retryable;
|
|
3324
3234
|
stage;
|
|
@@ -3646,6 +3556,8 @@ function normalizeHandle(handle) {
|
|
|
3646
3556
|
}
|
|
3647
3557
|
|
|
3648
3558
|
// src/runtime/PlatformRuntime.ts
|
|
3559
|
+
var MAX_MESSAGE_RETRIES = 4294967295;
|
|
3560
|
+
var isValidRetryCount = (value) => Number.isInteger(value) && value >= 0 && value <= MAX_MESSAGE_RETRIES;
|
|
3649
3561
|
var PlatformRuntime = class {
|
|
3650
3562
|
_agentId;
|
|
3651
3563
|
_apiKey;
|
|
@@ -3682,6 +3594,12 @@ var PlatformRuntime = class {
|
|
|
3682
3594
|
"apiKey is required and must be a non-empty string. Use loadAgentConfig() to load credentials from agent_config.yaml."
|
|
3683
3595
|
);
|
|
3684
3596
|
}
|
|
3597
|
+
const maxMessageRetries = options.sessionConfig?.maxMessageRetries;
|
|
3598
|
+
if (maxMessageRetries !== void 0 && !isValidRetryCount(maxMessageRetries)) {
|
|
3599
|
+
throw new ValidationError(
|
|
3600
|
+
`sessionConfig.maxMessageRetries must be an integer between 0 and ${MAX_MESSAGE_RETRIES}, got ${maxMessageRetries}.`
|
|
3601
|
+
);
|
|
3602
|
+
}
|
|
3685
3603
|
this._agentId = options.agentId;
|
|
3686
3604
|
this._apiKey = options.apiKey;
|
|
3687
3605
|
this._wsUrl = options.wsUrl;
|
|
@@ -3734,7 +3652,7 @@ var PlatformRuntime = class {
|
|
|
3734
3652
|
}
|
|
3735
3653
|
async doInitialize() {
|
|
3736
3654
|
if (!this.linkInstance) {
|
|
3737
|
-
this.linkInstance = new
|
|
3655
|
+
this.linkInstance = new BandLink({
|
|
3738
3656
|
...this.linkOptions,
|
|
3739
3657
|
agentId: this._agentId,
|
|
3740
3658
|
apiKey: this._apiKey,
|
|
@@ -3958,10 +3876,9 @@ async function runWithGracefulShutdown(agent, options) {
|
|
|
3958
3876
|
|
|
3959
3877
|
export {
|
|
3960
3878
|
deriveDefaultRestUrl,
|
|
3961
|
-
|
|
3879
|
+
BandLink,
|
|
3962
3880
|
Execution,
|
|
3963
3881
|
AgentTools,
|
|
3964
|
-
MessageRetryTracker,
|
|
3965
3882
|
ExecutionContext,
|
|
3966
3883
|
trackRoomJoin,
|
|
3967
3884
|
trackRoomLeave,
|