@slock-ai/daemon 0.35.0 → 0.36.1-alpha.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/chat-bridge.js +397 -93
- package/dist/{chunk-OSHFDFBV.js → chunk-7RQ2H2AM.js} +234 -114
- package/dist/{chunk-GX2DVINN.js → chunk-E6OOH3IC.js} +46 -1
- package/dist/core.js +4 -3
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -1,12 +1,205 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
buildFetchDispatcher
|
|
4
|
-
|
|
3
|
+
buildFetchDispatcher,
|
|
4
|
+
logger
|
|
5
|
+
} from "./chunk-E6OOH3IC.js";
|
|
5
6
|
|
|
6
7
|
// src/chat-bridge.ts
|
|
7
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
10
|
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
// src/historyFormatting.ts
|
|
13
|
+
function toLocalHistoryTime(iso) {
|
|
14
|
+
const d = new Date(iso);
|
|
15
|
+
if (Number.isNaN(d.getTime())) return iso;
|
|
16
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
17
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
18
|
+
}
|
|
19
|
+
function formatHistorySenderHandle(message) {
|
|
20
|
+
return message.senderDescription ? `@${message.senderName} \u2014 ${message.senderDescription}` : `@${message.senderName}`;
|
|
21
|
+
}
|
|
22
|
+
function formatHistoryMessageLine(message) {
|
|
23
|
+
const headerParts = [
|
|
24
|
+
`seq=${message.seq}`,
|
|
25
|
+
`msg=${message.id || "-"}`,
|
|
26
|
+
`time=${message.createdAt ? toLocalHistoryTime(message.createdAt) : "-"}`
|
|
27
|
+
];
|
|
28
|
+
if (message.senderType) {
|
|
29
|
+
headerParts.push(`type=${message.senderType}`);
|
|
30
|
+
}
|
|
31
|
+
if (message.threadId) {
|
|
32
|
+
headerParts.push(`threadId=${message.threadId}`);
|
|
33
|
+
}
|
|
34
|
+
if ((message.replyCount ?? 0) > 0) {
|
|
35
|
+
headerParts.push(`replyCount=${message.replyCount}`);
|
|
36
|
+
}
|
|
37
|
+
const attachSuffix = message.attachments?.length ? ` [${message.attachments.length} attachment${message.attachments.length > 1 ? "s" : ""}: ${message.attachments.map((attachment) => `${attachment.filename} (id:${attachment.id})`).join(", ")} \u2014 use view_file to download]` : "";
|
|
38
|
+
const taskSuffix = message.taskStatus ? ` [task #${message.taskNumber} status=${message.taskStatus}${message.taskAssigneeId ? ` assignee=${message.taskAssigneeType}:${message.taskAssigneeId}` : ""}]` : "";
|
|
39
|
+
return `[${headerParts.join(" ")}] ${formatHistorySenderHandle(message)}: ${message.content}${attachSuffix}${taskSuffix}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/chatBridgeRequest.ts
|
|
43
|
+
var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
|
|
44
|
+
process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
|
|
45
|
+
10
|
|
46
|
+
) || 6e4;
|
|
47
|
+
var ChatBridgeToolTimeoutError = class extends Error {
|
|
48
|
+
toolName;
|
|
49
|
+
target;
|
|
50
|
+
timeoutMs;
|
|
51
|
+
durationMs;
|
|
52
|
+
constructor(toolName, target, timeoutMs, durationMs) {
|
|
53
|
+
super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
|
|
54
|
+
this.name = "ChatBridgeToolTimeoutError";
|
|
55
|
+
this.toolName = toolName;
|
|
56
|
+
this.target = target;
|
|
57
|
+
this.timeoutMs = timeoutMs;
|
|
58
|
+
this.durationMs = durationMs;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
function describeError(err) {
|
|
62
|
+
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
63
|
+
return String(err);
|
|
64
|
+
}
|
|
65
|
+
async function executeJsonRequest(url, init, {
|
|
66
|
+
toolName,
|
|
67
|
+
target = null,
|
|
68
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
69
|
+
fetchImpl,
|
|
70
|
+
now = () => Date.now(),
|
|
71
|
+
warn = (message) => logger.warn(message)
|
|
72
|
+
}) {
|
|
73
|
+
const startedAt = now();
|
|
74
|
+
const timeoutController = new AbortController();
|
|
75
|
+
const signals = [timeoutController.signal];
|
|
76
|
+
if (init.signal) signals.push(init.signal);
|
|
77
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
78
|
+
const timeout = setTimeout(() => {
|
|
79
|
+
timeoutController.abort();
|
|
80
|
+
}, timeoutMs);
|
|
81
|
+
timeout.unref?.();
|
|
82
|
+
try {
|
|
83
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
84
|
+
const data = await response.json();
|
|
85
|
+
return { response, data, durationMs: now() - startedAt };
|
|
86
|
+
} catch (err) {
|
|
87
|
+
const durationMs = now() - startedAt;
|
|
88
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
89
|
+
warn(
|
|
90
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
91
|
+
);
|
|
92
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
93
|
+
}
|
|
94
|
+
warn(
|
|
95
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
96
|
+
);
|
|
97
|
+
throw err;
|
|
98
|
+
} finally {
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function executeResponseRequest(url, init, {
|
|
103
|
+
toolName,
|
|
104
|
+
target = null,
|
|
105
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
106
|
+
fetchImpl,
|
|
107
|
+
now = () => Date.now(),
|
|
108
|
+
warn = (message) => logger.warn(message)
|
|
109
|
+
}) {
|
|
110
|
+
const startedAt = now();
|
|
111
|
+
const timeoutController = new AbortController();
|
|
112
|
+
const signals = [timeoutController.signal];
|
|
113
|
+
if (init.signal) signals.push(init.signal);
|
|
114
|
+
const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
115
|
+
const timeout = setTimeout(() => {
|
|
116
|
+
timeoutController.abort();
|
|
117
|
+
}, timeoutMs);
|
|
118
|
+
timeout.unref?.();
|
|
119
|
+
try {
|
|
120
|
+
const response = await fetchImpl(url, { ...init, signal });
|
|
121
|
+
return { response, durationMs: now() - startedAt };
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const durationMs = now() - startedAt;
|
|
124
|
+
if (timeoutController.signal.aborted && !init.signal?.aborted) {
|
|
125
|
+
warn(
|
|
126
|
+
`[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
|
|
127
|
+
);
|
|
128
|
+
throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
|
|
129
|
+
}
|
|
130
|
+
warn(
|
|
131
|
+
`[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
|
|
132
|
+
);
|
|
133
|
+
throw err;
|
|
134
|
+
} finally {
|
|
135
|
+
clearTimeout(timeout);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/chatBridgeSendRequest.ts
|
|
140
|
+
import { randomUUID } from "crypto";
|
|
141
|
+
async function executeRetrySafeSendRequest(url, buildInit, {
|
|
142
|
+
fetchImpl,
|
|
143
|
+
target,
|
|
144
|
+
timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
|
|
145
|
+
now = () => Date.now(),
|
|
146
|
+
warn = (message) => logger.warn(message),
|
|
147
|
+
idempotencyKey = randomUUID()
|
|
148
|
+
}) {
|
|
149
|
+
let lastError;
|
|
150
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
151
|
+
try {
|
|
152
|
+
const result = await executeJsonRequest(
|
|
153
|
+
url,
|
|
154
|
+
buildInit(idempotencyKey),
|
|
155
|
+
{
|
|
156
|
+
toolName: "send_message",
|
|
157
|
+
target,
|
|
158
|
+
timeoutMs,
|
|
159
|
+
fetchImpl,
|
|
160
|
+
now,
|
|
161
|
+
warn
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
return {
|
|
165
|
+
...result,
|
|
166
|
+
idempotencyKey,
|
|
167
|
+
attempts: attempt
|
|
168
|
+
};
|
|
169
|
+
} catch (error) {
|
|
170
|
+
lastError = error;
|
|
171
|
+
if (attempt === 2) break;
|
|
172
|
+
warn(
|
|
173
|
+
`[ChatBridgeRetry] tool=send_message target=${target} attempt=${attempt + 1} reason=${describeRetryReason(error)}`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw lastError;
|
|
178
|
+
}
|
|
179
|
+
function describeRetryReason(error) {
|
|
180
|
+
if (error && typeof error === "object" && "name" in error && error.name === "ChatBridgeToolTimeoutError") {
|
|
181
|
+
return error.message;
|
|
182
|
+
}
|
|
183
|
+
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/perfAttribution.ts
|
|
187
|
+
var PERF_CALLER_CONTEXT_HEADER = "X-Perf-Caller-Context";
|
|
188
|
+
var AGENT_ORIGINATED_CALLER_CONTEXT = "agent_originated";
|
|
189
|
+
function buildChatBridgeCommonHeaders(authToken2, { includeContentType = true } = {}) {
|
|
190
|
+
const headers = {
|
|
191
|
+
[PERF_CALLER_CONTEXT_HEADER]: AGENT_ORIGINATED_CALLER_CONTEXT
|
|
192
|
+
};
|
|
193
|
+
if (includeContentType) {
|
|
194
|
+
headers["Content-Type"] = "application/json";
|
|
195
|
+
}
|
|
196
|
+
if (authToken2) {
|
|
197
|
+
headers.Authorization = `Bearer ${authToken2}`;
|
|
198
|
+
}
|
|
199
|
+
return headers;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/chat-bridge.ts
|
|
10
203
|
function toLocalTime(iso) {
|
|
11
204
|
const d = new Date(iso);
|
|
12
205
|
if (isNaN(d.getTime())) return iso;
|
|
@@ -26,15 +219,61 @@ if (!agentId) {
|
|
|
26
219
|
console.error("Missing --agent-id");
|
|
27
220
|
process.exit(1);
|
|
28
221
|
}
|
|
29
|
-
var commonHeaders =
|
|
30
|
-
if (authToken) {
|
|
31
|
-
commonHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
32
|
-
}
|
|
222
|
+
var commonHeaders = buildChatBridgeCommonHeaders(authToken);
|
|
33
223
|
function bridgeFetch(url, init = {}) {
|
|
34
224
|
const dispatcher = buildFetchDispatcher(url, process.env);
|
|
35
225
|
const requestInit = dispatcher ? { ...init, dispatcher } : init;
|
|
36
226
|
return fetch(url, requestInit);
|
|
37
227
|
}
|
|
228
|
+
var RECENT_DELIVERY_CACHE_LIMIT = 5e3;
|
|
229
|
+
var deliveredMessageKeys = /* @__PURE__ */ new Set();
|
|
230
|
+
var deliveredMessageOrder = [];
|
|
231
|
+
function messageDeliveryKey(message) {
|
|
232
|
+
if (message.seq) return `seq:${message.seq}`;
|
|
233
|
+
if (message.message_id) return `msg:${message.message_id}`;
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function rememberDeliveredMessages(messages) {
|
|
237
|
+
const unseen = [];
|
|
238
|
+
for (const message of messages) {
|
|
239
|
+
const key = messageDeliveryKey(message);
|
|
240
|
+
if (!key) {
|
|
241
|
+
unseen.push(message);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (deliveredMessageKeys.has(key)) {
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
deliveredMessageKeys.add(key);
|
|
248
|
+
deliveredMessageOrder.push(key);
|
|
249
|
+
unseen.push(message);
|
|
250
|
+
}
|
|
251
|
+
while (deliveredMessageOrder.length > RECENT_DELIVERY_CACHE_LIMIT) {
|
|
252
|
+
const evicted = deliveredMessageOrder.shift();
|
|
253
|
+
if (evicted) deliveredMessageKeys.delete(evicted);
|
|
254
|
+
}
|
|
255
|
+
return unseen;
|
|
256
|
+
}
|
|
257
|
+
async function acknowledgeReceivedMessages(messages) {
|
|
258
|
+
const seqs = [...new Set(
|
|
259
|
+
messages.map((message) => message.seq).filter((seq) => typeof seq === "number" && Number.isInteger(seq) && seq > 0)
|
|
260
|
+
)];
|
|
261
|
+
if (seqs.length === 0) return;
|
|
262
|
+
try {
|
|
263
|
+
const res = await bridgeFetch(`${serverUrl}/internal/agent/${agentId}/receive-ack`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: commonHeaders,
|
|
266
|
+
body: JSON.stringify({ seqs })
|
|
267
|
+
});
|
|
268
|
+
if (!res.ok) {
|
|
269
|
+
console.warn(`[chat-bridge] receive-ack failed (${res.status}) for agent ${agentId}; delivery will replay on the next poll`);
|
|
270
|
+
}
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.warn(
|
|
273
|
+
`[chat-bridge] receive-ack errored for agent ${agentId}; delivery will replay on the next poll: ${err instanceof Error ? err.message : String(err)}`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
38
277
|
function formatAttachmentSuffix(attachments) {
|
|
39
278
|
if (!attachments?.length) return "";
|
|
40
279
|
return ` [${attachments.length} attachment${attachments.length > 1 ? "s" : ""}: ${attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to download]`;
|
|
@@ -144,12 +383,18 @@ server.tool(
|
|
|
144
383
|
},
|
|
145
384
|
async ({ target, content, attachment_ids }) => {
|
|
146
385
|
try {
|
|
147
|
-
const res = await
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
386
|
+
const { response: res, data } = await executeRetrySafeSendRequest(
|
|
387
|
+
`${serverUrl}/internal/agent/${agentId}/send`,
|
|
388
|
+
(idempotencyKey) => ({
|
|
389
|
+
method: "POST",
|
|
390
|
+
headers: commonHeaders,
|
|
391
|
+
body: JSON.stringify({ target, content, attachmentIds: attachment_ids, idempotencyKey })
|
|
392
|
+
}),
|
|
393
|
+
{
|
|
394
|
+
target,
|
|
395
|
+
fetchImpl: bridgeFetch
|
|
396
|
+
}
|
|
397
|
+
);
|
|
153
398
|
if (!res.ok) {
|
|
154
399
|
return {
|
|
155
400
|
content: [
|
|
@@ -161,10 +406,14 @@ server.tool(
|
|
|
161
406
|
const replyHint = shortId ? ` (to reply in this message's thread, use target "${target.includes(":") ? target : target + ":" + shortId}")` : "";
|
|
162
407
|
let unreadSection = "";
|
|
163
408
|
if (data.recentUnread && data.recentUnread.length > 0) {
|
|
164
|
-
|
|
409
|
+
await acknowledgeReceivedMessages(data.recentUnread);
|
|
410
|
+
const unreadToShow = rememberDeliveredMessages(data.recentUnread);
|
|
411
|
+
if (unreadToShow.length > 0) {
|
|
412
|
+
unreadSection = `
|
|
165
413
|
|
|
166
414
|
--- New messages you may have missed ---
|
|
167
|
-
${formatMessages(
|
|
415
|
+
${formatMessages(unreadToShow)}`;
|
|
416
|
+
}
|
|
168
417
|
}
|
|
169
418
|
return {
|
|
170
419
|
content: [
|
|
@@ -184,7 +433,7 @@ ${formatMessages(data.recentUnread)}`;
|
|
|
184
433
|
);
|
|
185
434
|
server.tool(
|
|
186
435
|
"upload_file",
|
|
187
|
-
"Upload a file to attach to a message. Returns an attachment ID that you can pass to send_message's attachment_ids parameter. Images keep preview behavior; other files are sent as downloadable attachments. Max size:
|
|
436
|
+
"Upload a file to attach to a message. Returns an attachment ID that you can pass to send_message's attachment_ids parameter. Images keep preview behavior; other files are sent as downloadable attachments. Max size: 10MB.",
|
|
188
437
|
{
|
|
189
438
|
file_path: z.string().describe("Absolute path to the file on your local filesystem"),
|
|
190
439
|
channel: z.string().describe("The channel target where this file will be used (e.g. '#general', 'dm:@richard')")
|
|
@@ -200,27 +449,32 @@ server.tool(
|
|
|
200
449
|
};
|
|
201
450
|
}
|
|
202
451
|
const stat = fs.statSync(file_path);
|
|
203
|
-
if (stat.size >
|
|
452
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
204
453
|
return {
|
|
205
454
|
isError: true,
|
|
206
|
-
content: [{ type: "text", text: `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max
|
|
455
|
+
content: [{ type: "text", text: `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 10MB per file.` }]
|
|
207
456
|
};
|
|
208
457
|
}
|
|
209
|
-
const listRes = await
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
458
|
+
const { response: listRes, data: listData } = await executeJsonRequest(
|
|
459
|
+
`${serverUrl}/internal/agent/${agentId}/resolve-channel`,
|
|
460
|
+
{
|
|
461
|
+
method: "POST",
|
|
462
|
+
headers: commonHeaders,
|
|
463
|
+
body: JSON.stringify({ target: channel })
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
toolName: "upload_file.resolve_channel",
|
|
467
|
+
target: channel,
|
|
468
|
+
fetchImpl: bridgeFetch
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
if (!listRes.ok || !listData.channelId) {
|
|
219
472
|
return {
|
|
220
473
|
isError: true,
|
|
221
|
-
content: [{ type: "text", text: `Error: Could not resolve channel: ${channel}` }]
|
|
474
|
+
content: [{ type: "text", text: `Error: ${listData.error || `Could not resolve channel: ${channel}`}` }]
|
|
222
475
|
};
|
|
223
476
|
}
|
|
477
|
+
const channelId = listData.channelId;
|
|
224
478
|
const fileBuffer = fs.readFileSync(file_path);
|
|
225
479
|
const filename = path.basename(file_path);
|
|
226
480
|
const mimeType = guessMimeTypeFromFilename(filename);
|
|
@@ -228,16 +482,20 @@ server.tool(
|
|
|
228
482
|
const formData = new FormData();
|
|
229
483
|
formData.append("file", blob, filename);
|
|
230
484
|
formData.append("channelId", channelId);
|
|
231
|
-
const uploadHeaders = {};
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
485
|
+
const uploadHeaders = buildChatBridgeCommonHeaders(authToken, { includeContentType: false });
|
|
486
|
+
const { response: res, data } = await executeJsonRequest(
|
|
487
|
+
`${serverUrl}/internal/agent/${agentId}/upload`,
|
|
488
|
+
{
|
|
489
|
+
method: "POST",
|
|
490
|
+
headers: uploadHeaders,
|
|
491
|
+
body: formData
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
toolName: "upload_file",
|
|
495
|
+
target: channel,
|
|
496
|
+
fetchImpl: bridgeFetch
|
|
497
|
+
}
|
|
498
|
+
);
|
|
241
499
|
if (!res.ok) {
|
|
242
500
|
return {
|
|
243
501
|
isError: true,
|
|
@@ -283,14 +541,19 @@ server.tool(
|
|
|
283
541
|
content: [{ type: "text", text: `File already cached at: ${cachedPath}` }]
|
|
284
542
|
};
|
|
285
543
|
}
|
|
286
|
-
const downloadHeaders = {};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
544
|
+
const downloadHeaders = buildChatBridgeCommonHeaders(authToken, { includeContentType: false });
|
|
545
|
+
const { response: res } = await executeResponseRequest(
|
|
546
|
+
`${serverUrl}/api/attachments/${attachment_id}`,
|
|
547
|
+
{
|
|
548
|
+
headers: downloadHeaders,
|
|
549
|
+
redirect: "follow"
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
toolName: "view_file",
|
|
553
|
+
target: attachment_id,
|
|
554
|
+
fetchImpl: bridgeFetch
|
|
555
|
+
}
|
|
556
|
+
);
|
|
294
557
|
if (!res.ok) {
|
|
295
558
|
return {
|
|
296
559
|
isError: true,
|
|
@@ -320,17 +583,25 @@ server.tool(
|
|
|
320
583
|
{},
|
|
321
584
|
async () => {
|
|
322
585
|
try {
|
|
323
|
-
const res = await
|
|
586
|
+
const { response: res, data } = await executeJsonRequest(
|
|
324
587
|
`${serverUrl}/internal/agent/${agentId}/receive`,
|
|
325
|
-
{ method: "GET", headers: commonHeaders }
|
|
588
|
+
{ method: "GET", headers: commonHeaders },
|
|
589
|
+
{
|
|
590
|
+
toolName: "check_messages",
|
|
591
|
+
timeoutMs: 1e4,
|
|
592
|
+
fetchImpl: bridgeFetch
|
|
593
|
+
}
|
|
326
594
|
);
|
|
327
595
|
if (!res.ok) {
|
|
328
|
-
|
|
329
|
-
return { isError: true, content: [{ type: "text", text: `Error: ${errData.error || res.statusText}` }] };
|
|
596
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
|
|
330
597
|
}
|
|
331
|
-
const
|
|
332
|
-
if (
|
|
333
|
-
|
|
598
|
+
const messages = data.messages ?? [];
|
|
599
|
+
if (messages.length > 0) {
|
|
600
|
+
await acknowledgeReceivedMessages(messages);
|
|
601
|
+
const messagesToShow = rememberDeliveredMessages(messages);
|
|
602
|
+
if (messagesToShow.length > 0) {
|
|
603
|
+
return { content: [{ type: "text", text: formatMessages(messagesToShow) }] };
|
|
604
|
+
}
|
|
334
605
|
}
|
|
335
606
|
return {
|
|
336
607
|
content: [{ type: "text", text: "No new messages." }]
|
|
@@ -361,16 +632,22 @@ server.tool(
|
|
|
361
632
|
{},
|
|
362
633
|
async () => {
|
|
363
634
|
try {
|
|
364
|
-
const res = await
|
|
635
|
+
const { response: res, data } = await executeJsonRequest(
|
|
365
636
|
`${serverUrl}/internal/agent/${agentId}/server`,
|
|
366
|
-
{ method: "GET", headers: commonHeaders }
|
|
637
|
+
{ method: "GET", headers: commonHeaders },
|
|
638
|
+
{
|
|
639
|
+
toolName: "list_server",
|
|
640
|
+
fetchImpl: bridgeFetch
|
|
641
|
+
}
|
|
367
642
|
);
|
|
368
|
-
const data = await res.json();
|
|
369
643
|
let text = "## Server\n\n";
|
|
644
|
+
const channels = data.channels ?? [];
|
|
645
|
+
const agents = data.agents ?? [];
|
|
646
|
+
const humans = data.humans ?? [];
|
|
370
647
|
text += "### Channels\n";
|
|
371
648
|
text += "Use `#channel-name` with send_message to post in a channel. `joined` means you currently belong to that channel.\n";
|
|
372
|
-
if (
|
|
373
|
-
for (const t of
|
|
649
|
+
if (channels.length > 0) {
|
|
650
|
+
for (const t of channels) {
|
|
374
651
|
const status = t.joined ? "joined" : "not joined";
|
|
375
652
|
text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
|
|
376
653
|
` : ` - #${t.name} [${status}]
|
|
@@ -381,8 +658,8 @@ server.tool(
|
|
|
381
658
|
}
|
|
382
659
|
text += "\n### Agents\n";
|
|
383
660
|
text += "Other AI agents in this server.\n";
|
|
384
|
-
if (
|
|
385
|
-
for (const a of
|
|
661
|
+
if (agents.length > 0) {
|
|
662
|
+
for (const a of agents) {
|
|
386
663
|
text += a.description ? ` - @${a.name} (${a.status}) \u2014 ${a.description}
|
|
387
664
|
` : ` - @${a.name} (${a.status})
|
|
388
665
|
`;
|
|
@@ -392,8 +669,8 @@ server.tool(
|
|
|
392
669
|
}
|
|
393
670
|
text += "\n### Humans\n";
|
|
394
671
|
text += 'To start a new DM: send_message(target="dm:@name"). To reply in an existing DM: reuse the target from received messages.\n';
|
|
395
|
-
if (
|
|
396
|
-
for (const u of
|
|
672
|
+
if (humans.length > 0) {
|
|
673
|
+
for (const u of humans) {
|
|
397
674
|
text += u.description ? ` - @${u.name} \u2014 ${u.description}
|
|
398
675
|
` : ` - @${u.name}
|
|
399
676
|
`;
|
|
@@ -438,11 +715,15 @@ server.tool(
|
|
|
438
715
|
if (sender_id) params.set("senderId", sender_id);
|
|
439
716
|
if (after) params.set("after", after);
|
|
440
717
|
if (before) params.set("before", before);
|
|
441
|
-
const res = await
|
|
718
|
+
const { response: res, data } = await executeJsonRequest(
|
|
442
719
|
`${serverUrl}/internal/agent/${agentId}/search?${params}`,
|
|
443
|
-
{ method: "GET", headers: commonHeaders }
|
|
720
|
+
{ method: "GET", headers: commonHeaders },
|
|
721
|
+
{
|
|
722
|
+
toolName: "search_messages",
|
|
723
|
+
target: channel ?? null,
|
|
724
|
+
fetchImpl: bridgeFetch
|
|
725
|
+
}
|
|
444
726
|
);
|
|
445
|
-
const data = await res.json();
|
|
446
727
|
if (!res.ok) {
|
|
447
728
|
return {
|
|
448
729
|
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
@@ -500,11 +781,15 @@ server.tool(
|
|
|
500
781
|
if (around !== void 0) params.set("around", String(around));
|
|
501
782
|
if (before) params.set("before", String(before));
|
|
502
783
|
if (after) params.set("after", String(after));
|
|
503
|
-
const res = await
|
|
784
|
+
const { response: res, data } = await executeJsonRequest(
|
|
504
785
|
`${serverUrl}/internal/agent/${agentId}/history?${params}`,
|
|
505
|
-
{ method: "GET", headers: commonHeaders }
|
|
786
|
+
{ method: "GET", headers: commonHeaders },
|
|
787
|
+
{
|
|
788
|
+
toolName: "read_history",
|
|
789
|
+
target: channel,
|
|
790
|
+
fetchImpl: bridgeFetch
|
|
791
|
+
}
|
|
506
792
|
);
|
|
507
|
-
const data = await res.json();
|
|
508
793
|
if (!res.ok) {
|
|
509
794
|
return {
|
|
510
795
|
content: [
|
|
@@ -519,15 +804,11 @@ server.tool(
|
|
|
519
804
|
]
|
|
520
805
|
};
|
|
521
806
|
}
|
|
522
|
-
const formatted = data.messages.map((m) => {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
const attachSuffix = formatAttachmentSuffix(m.attachments);
|
|
528
|
-
const taskSuffix = m.taskStatus ? ` [task #${m.taskNumber} status=${m.taskStatus}${m.taskAssigneeId ? ` assignee=${m.taskAssigneeType}:${m.taskAssigneeId}` : ""}]` : "";
|
|
529
|
-
return `[seq=${m.seq} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(m)}: ${renderedContent}${attachSuffix}${taskSuffix}`;
|
|
530
|
-
}).join("\n");
|
|
807
|
+
const formatted = data.messages.map((m) => formatHistoryMessageLine({
|
|
808
|
+
...m,
|
|
809
|
+
senderName: m.senderName ?? m.sender_name ?? "unknown",
|
|
810
|
+
senderDescription: m.senderDescription ?? m.sender_description ?? null
|
|
811
|
+
})).join("\n");
|
|
531
812
|
let footer = "";
|
|
532
813
|
if (data.historyLimited) {
|
|
533
814
|
footer = `
|
|
@@ -553,7 +834,7 @@ server.tool(
|
|
|
553
834
|
}
|
|
554
835
|
}
|
|
555
836
|
let header = `## Message History for ${channel}${around ? ` around ${around}` : ""} (${data.messages.length} messages)`;
|
|
556
|
-
if (data.last_read_seq > 0 && !after && !before && !around) {
|
|
837
|
+
if ((data.last_read_seq ?? 0) > 0 && !after && !before && !around) {
|
|
557
838
|
header += `
|
|
558
839
|
Your last read position: seq ${data.last_read_seq}. Use read_history(channel="${channel}", after=${data.last_read_seq}) to see only unread messages.`;
|
|
559
840
|
}
|
|
@@ -587,11 +868,15 @@ server.tool(
|
|
|
587
868
|
const params = new URLSearchParams();
|
|
588
869
|
params.set("channel", channel);
|
|
589
870
|
if (status !== "all") params.set("status", status);
|
|
590
|
-
const res = await
|
|
871
|
+
const { response: res, data } = await executeJsonRequest(
|
|
591
872
|
`${serverUrl}/internal/agent/${agentId}/tasks?${params}`,
|
|
592
|
-
{ method: "GET", headers: commonHeaders }
|
|
873
|
+
{ method: "GET", headers: commonHeaders },
|
|
874
|
+
{
|
|
875
|
+
toolName: "list_tasks",
|
|
876
|
+
target: channel,
|
|
877
|
+
fetchImpl: bridgeFetch
|
|
878
|
+
}
|
|
593
879
|
);
|
|
594
|
-
const data = await res.json();
|
|
595
880
|
if (!res.ok) {
|
|
596
881
|
return {
|
|
597
882
|
isError: true,
|
|
@@ -641,12 +926,19 @@ server.tool(
|
|
|
641
926
|
},
|
|
642
927
|
async ({ channel, tasks }) => {
|
|
643
928
|
try {
|
|
644
|
-
const res = await
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
929
|
+
const { response: res, data } = await executeJsonRequest(
|
|
930
|
+
`${serverUrl}/internal/agent/${agentId}/tasks`,
|
|
931
|
+
{
|
|
932
|
+
method: "POST",
|
|
933
|
+
headers: commonHeaders,
|
|
934
|
+
body: JSON.stringify({ channel, tasks })
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
toolName: "create_tasks",
|
|
938
|
+
target: channel,
|
|
939
|
+
fetchImpl: bridgeFetch
|
|
940
|
+
}
|
|
941
|
+
);
|
|
650
942
|
if (!res.ok) {
|
|
651
943
|
return {
|
|
652
944
|
isError: true,
|
|
@@ -697,15 +989,19 @@ Thread messages cannot be claimed or converted into tasks. If a task is in "todo
|
|
|
697
989
|
const body = { channel };
|
|
698
990
|
if (task_numbers && task_numbers.length > 0) body.task_numbers = task_numbers;
|
|
699
991
|
if (message_ids && message_ids.length > 0) body.message_ids = message_ids;
|
|
700
|
-
const res = await
|
|
992
|
+
const { response: res, data } = await executeJsonRequest(
|
|
701
993
|
`${serverUrl}/internal/agent/${agentId}/tasks/claim`,
|
|
702
994
|
{
|
|
703
995
|
method: "POST",
|
|
704
996
|
headers: commonHeaders,
|
|
705
997
|
body: JSON.stringify(body)
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
toolName: "claim_tasks",
|
|
1001
|
+
target: channel,
|
|
1002
|
+
fetchImpl: bridgeFetch
|
|
706
1003
|
}
|
|
707
1004
|
);
|
|
708
|
-
const data = await res.json();
|
|
709
1005
|
if (!res.ok) {
|
|
710
1006
|
return {
|
|
711
1007
|
isError: true,
|
|
@@ -755,15 +1051,19 @@ server.tool(
|
|
|
755
1051
|
},
|
|
756
1052
|
async ({ channel, task_number }) => {
|
|
757
1053
|
try {
|
|
758
|
-
const res = await
|
|
1054
|
+
const { response: res, data } = await executeJsonRequest(
|
|
759
1055
|
`${serverUrl}/internal/agent/${agentId}/tasks/unclaim`,
|
|
760
1056
|
{
|
|
761
1057
|
method: "POST",
|
|
762
1058
|
headers: commonHeaders,
|
|
763
1059
|
body: JSON.stringify({ channel, task_number })
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
toolName: "unclaim_task",
|
|
1063
|
+
target: channel,
|
|
1064
|
+
fetchImpl: bridgeFetch
|
|
764
1065
|
}
|
|
765
1066
|
);
|
|
766
|
-
const data = await res.json();
|
|
767
1067
|
if (!res.ok) {
|
|
768
1068
|
return {
|
|
769
1069
|
isError: true,
|
|
@@ -793,15 +1093,19 @@ server.tool(
|
|
|
793
1093
|
},
|
|
794
1094
|
async ({ channel, task_number, status }) => {
|
|
795
1095
|
try {
|
|
796
|
-
const res = await
|
|
1096
|
+
const { response: res, data } = await executeJsonRequest(
|
|
797
1097
|
`${serverUrl}/internal/agent/${agentId}/tasks/update-status`,
|
|
798
1098
|
{
|
|
799
1099
|
method: "POST",
|
|
800
1100
|
headers: commonHeaders,
|
|
801
1101
|
body: JSON.stringify({ channel, task_number, status })
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
toolName: "update_task_status",
|
|
1105
|
+
target: channel,
|
|
1106
|
+
fetchImpl: bridgeFetch
|
|
802
1107
|
}
|
|
803
1108
|
);
|
|
804
|
-
const data = await res.json();
|
|
805
1109
|
if (!res.ok) {
|
|
806
1110
|
return {
|
|
807
1111
|
isError: true,
|