@slock-ai/daemon 0.40.1 → 0.40.2
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 +937 -780
- package/dist/{chunk-E6OOH3IC.js → chunk-JG7ONJZ6.js} +46 -46
- package/dist/{chunk-6YLMU56U.js → chunk-PB75DRIF.js} +69 -1
- package/dist/core.js +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -2,12 +2,137 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildFetchDispatcher,
|
|
4
4
|
logger
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-JG7ONJZ6.js";
|
|
6
6
|
|
|
7
7
|
// src/chat-bridge.ts
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { z as z2 } from "zod";
|
|
11
|
+
|
|
12
|
+
// src/deprecatedMcpShim.ts
|
|
10
13
|
import { z } from "zod";
|
|
14
|
+
var DEPRECATED_MCP_SHIM_HEADER = "This MCP tool is deprecated. Use slock CLI instead.";
|
|
15
|
+
var sendMessageDeprecatedSchema = {
|
|
16
|
+
target: z.string().describe("Deprecated target argument."),
|
|
17
|
+
content: z.string().optional().describe("Deprecated content argument."),
|
|
18
|
+
attachment_ids: z.array(z.string()).optional().describe("Deprecated attachment ids.")
|
|
19
|
+
};
|
|
20
|
+
var checkMessagesDeprecatedSchema = {};
|
|
21
|
+
var readHistoryDeprecatedSchema = {
|
|
22
|
+
channel: z.string().describe("Deprecated channel argument."),
|
|
23
|
+
limit: z.number().optional().describe("Deprecated limit argument."),
|
|
24
|
+
around: z.union([z.string(), z.number()]).optional().describe("Deprecated around argument."),
|
|
25
|
+
before: z.number().optional().describe("Deprecated before argument."),
|
|
26
|
+
after: z.number().optional().describe("Deprecated after argument.")
|
|
27
|
+
};
|
|
28
|
+
var searchMessagesDeprecatedSchema = {
|
|
29
|
+
query: z.string().describe("Deprecated query argument."),
|
|
30
|
+
limit: z.number().optional().describe("Deprecated limit argument.")
|
|
31
|
+
};
|
|
32
|
+
var listTasksDeprecatedSchema = {
|
|
33
|
+
channel: z.string().describe("Deprecated channel argument."),
|
|
34
|
+
status: z.enum(["all", "todo", "in_progress", "in_review", "done"]).optional().describe("Deprecated status argument.")
|
|
35
|
+
};
|
|
36
|
+
var claimTasksDeprecatedSchema = {
|
|
37
|
+
channel: z.string().describe("Deprecated channel argument."),
|
|
38
|
+
task_numbers: z.array(z.number()).optional().describe("Deprecated task numbers."),
|
|
39
|
+
message_ids: z.array(z.string()).optional().describe("Deprecated message ids.")
|
|
40
|
+
};
|
|
41
|
+
var unclaimTaskDeprecatedSchema = {
|
|
42
|
+
channel: z.string().describe("Deprecated channel argument."),
|
|
43
|
+
task_number: z.number().describe("Deprecated task number.")
|
|
44
|
+
};
|
|
45
|
+
var updateTaskStatusDeprecatedSchema = {
|
|
46
|
+
channel: z.string().describe("Deprecated channel argument."),
|
|
47
|
+
task_number: z.number().describe("Deprecated task number."),
|
|
48
|
+
status: z.enum(["todo", "in_progress", "in_review", "done"]).describe("Deprecated status argument.")
|
|
49
|
+
};
|
|
50
|
+
var DEPRECATED_MCP_TOOL_DEFINITIONS = [
|
|
51
|
+
{
|
|
52
|
+
toolName: "send_message",
|
|
53
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
54
|
+
Use \`slock message send\` and pass the message body on stdin.`,
|
|
55
|
+
schema: sendMessageDeprecatedSchema,
|
|
56
|
+
cliExamples: [
|
|
57
|
+
"slock message send --target '#channel-or-dm' <<'EOF2'",
|
|
58
|
+
"message body",
|
|
59
|
+
"EOF2"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
toolName: "check_messages",
|
|
64
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
65
|
+
Use \`slock message check\`.`,
|
|
66
|
+
schema: checkMessagesDeprecatedSchema,
|
|
67
|
+
cliExamples: [
|
|
68
|
+
"slock message check"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
toolName: "read_history",
|
|
73
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
74
|
+
Use \`slock message read --channel ...\`.`,
|
|
75
|
+
schema: readHistoryDeprecatedSchema,
|
|
76
|
+
cliExamples: [
|
|
77
|
+
"slock message read --channel '#channel'",
|
|
78
|
+
"slock message read --channel 'dm:@peer'",
|
|
79
|
+
"slock message read --channel '#channel:threadId'"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
toolName: "search_messages",
|
|
84
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
85
|
+
Use \`slock message search --query ...\`.`,
|
|
86
|
+
schema: searchMessagesDeprecatedSchema,
|
|
87
|
+
cliExamples: [
|
|
88
|
+
"slock message search --query 'keyword'"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
toolName: "list_tasks",
|
|
93
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
94
|
+
Use \`slock task list --channel ...\`.`,
|
|
95
|
+
schema: listTasksDeprecatedSchema,
|
|
96
|
+
cliExamples: [
|
|
97
|
+
"slock task list --channel '#channel'"
|
|
98
|
+
]
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
toolName: "claim_tasks",
|
|
102
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
103
|
+
Use \`slock task claim ...\`.`,
|
|
104
|
+
schema: claimTasksDeprecatedSchema,
|
|
105
|
+
cliExamples: [
|
|
106
|
+
"slock task claim --channel '#channel' --number 123",
|
|
107
|
+
"slock task claim --channel '#channel' --message-id <messageId>"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
toolName: "unclaim_task",
|
|
112
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
113
|
+
Use \`slock task unclaim ...\`.`,
|
|
114
|
+
schema: unclaimTaskDeprecatedSchema,
|
|
115
|
+
cliExamples: [
|
|
116
|
+
"slock task unclaim --channel '#channel' --number 123"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
toolName: "update_task_status",
|
|
121
|
+
description: `${DEPRECATED_MCP_SHIM_HEADER}
|
|
122
|
+
Use \`slock task update ...\`.`,
|
|
123
|
+
schema: updateTaskStatusDeprecatedSchema,
|
|
124
|
+
cliExamples: [
|
|
125
|
+
"slock task update --channel '#channel' --number 123 --status in_review"
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
function buildDeprecatedMcpToolErrorText(tool) {
|
|
130
|
+
return [
|
|
131
|
+
DEPRECATED_MCP_SHIM_HEADER,
|
|
132
|
+
"",
|
|
133
|
+
...tool.cliExamples
|
|
134
|
+
].join("\n");
|
|
135
|
+
}
|
|
11
136
|
|
|
12
137
|
// src/historyFormatting.ts
|
|
13
138
|
function toLocalHistoryTime(iso) {
|
|
@@ -210,10 +335,14 @@ var args = process.argv.slice(2);
|
|
|
210
335
|
var agentId = "";
|
|
211
336
|
var serverUrl = "http://localhost:3001";
|
|
212
337
|
var authToken = "";
|
|
338
|
+
var runtime = "unknown";
|
|
339
|
+
var deprecatedShimMode = false;
|
|
213
340
|
for (let i = 0; i < args.length; i++) {
|
|
214
341
|
if (args[i] === "--agent-id" && args[i + 1]) agentId = args[++i];
|
|
215
342
|
if (args[i] === "--server-url" && args[i + 1]) serverUrl = args[++i];
|
|
216
343
|
if (args[i] === "--auth-token" && args[i + 1]) authToken = args[++i];
|
|
344
|
+
if (args[i] === "--runtime" && args[i + 1]) runtime = args[++i];
|
|
345
|
+
if (args[i] === "--deprecated-shim") deprecatedShimMode = true;
|
|
217
346
|
}
|
|
218
347
|
if (!agentId) {
|
|
219
348
|
console.error("Missing --agent-id");
|
|
@@ -371,904 +500,932 @@ var server = new McpServer({
|
|
|
371
500
|
name: "chat",
|
|
372
501
|
version: "1.0.0"
|
|
373
502
|
});
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
);
|
|
398
|
-
if (!res.ok) {
|
|
399
|
-
return {
|
|
400
|
-
content: [
|
|
401
|
-
{ type: "text", text: `Error: ${data.error}` }
|
|
402
|
-
]
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
|
|
406
|
-
const replyHint = shortId ? ` (to reply in this message's thread, use target "${target.includes(":") ? target : target + ":" + shortId}")` : "";
|
|
407
|
-
let unreadSection = "";
|
|
408
|
-
if (data.recentUnread && data.recentUnread.length > 0) {
|
|
409
|
-
await acknowledgeReceivedMessages(data.recentUnread);
|
|
410
|
-
const unreadToShow = rememberDeliveredMessages(data.recentUnread);
|
|
411
|
-
if (unreadToShow.length > 0) {
|
|
412
|
-
unreadSection = `
|
|
413
|
-
|
|
414
|
-
--- New messages you may have missed ---
|
|
415
|
-
${formatMessages(unreadToShow)}`;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return {
|
|
419
|
-
content: [
|
|
420
|
-
{
|
|
421
|
-
type: "text",
|
|
422
|
-
text: `Message sent to ${target}. Message ID: ${data.messageId}${replyHint}${unreadSection}`
|
|
423
|
-
}
|
|
424
|
-
]
|
|
425
|
-
};
|
|
426
|
-
} catch (err) {
|
|
503
|
+
function logDeprecatedShimInvocation(tool) {
|
|
504
|
+
logger.warn(
|
|
505
|
+
`[ChatBridgeDeprecatedShim] tool=${tool.toolName} runtime=${runtime} agent_id=${agentId} outcome=deprecated`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
function registerDeprecatedTool(tool) {
|
|
509
|
+
server.tool(
|
|
510
|
+
tool.toolName,
|
|
511
|
+
tool.description,
|
|
512
|
+
tool.schema,
|
|
513
|
+
async () => {
|
|
514
|
+
logDeprecatedShimInvocation(tool);
|
|
427
515
|
return {
|
|
428
516
|
isError: true,
|
|
429
|
-
content: [{ type: "text", text:
|
|
517
|
+
content: [{ type: "text", text: buildDeprecatedMcpToolErrorText(tool) }]
|
|
430
518
|
};
|
|
431
519
|
}
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (deprecatedShimMode) {
|
|
523
|
+
for (const tool of DEPRECATED_MCP_TOOL_DEFINITIONS) {
|
|
524
|
+
registerDeprecatedTool(tool);
|
|
432
525
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
526
|
+
}
|
|
527
|
+
if (!deprecatedShimMode) {
|
|
528
|
+
let formatMessages = function(messages) {
|
|
529
|
+
return messages.map((m) => {
|
|
530
|
+
const target = formatTarget(m);
|
|
531
|
+
const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
|
|
532
|
+
const time = m.timestamp ? toLocalTime(m.timestamp) : "-";
|
|
533
|
+
const senderType = ` type=${m.sender_type}`;
|
|
534
|
+
const renderedContent = m.content;
|
|
535
|
+
const attachSuffix = formatAttachmentSuffix(m.attachments);
|
|
536
|
+
const taskSuffix = m.task_status ? ` [task #${m.task_number} status=${m.task_status}${m.task_assignee_id ? ` assignee=${m.task_assignee_type}:${m.task_assignee_id}` : ""}]` : "";
|
|
537
|
+
return `[target=${target} msg=${msgId} time=${time}${senderType}] ${formatSenderHandle(m)}: ${renderedContent}${attachSuffix}${taskSuffix}`;
|
|
538
|
+
}).join("\n");
|
|
539
|
+
}, formatReminder = function(r) {
|
|
540
|
+
const fireLocal = toLocalTime(r.fireAt);
|
|
541
|
+
const ref = r.msgRef ? ` ref=${r.msgRef}` : "";
|
|
542
|
+
const repeat = r.recurrence ? ` repeat=${r.recurrence.description}` : "";
|
|
543
|
+
return `#${r.reminderId.slice(0, 8)} [${r.status}] fires=${fireLocal} "${r.title}"${ref}${repeat}`;
|
|
544
|
+
};
|
|
545
|
+
formatMessages2 = formatMessages, formatReminder2 = formatReminder;
|
|
546
|
+
server.tool(
|
|
547
|
+
"send_message",
|
|
548
|
+
"Send a message to a channel, DM, or thread. Use the target value from received messages to reply. Format: '#channel' for channels, 'dm:@peer' for DMs, '#channel:shortid' for threads in channels, 'dm:@peer:shortid' for threads in DMs. To start a NEW DM, use 'dm:@person-name'.",
|
|
549
|
+
{
|
|
550
|
+
target: z2.string().describe(
|
|
551
|
+
"Where to send. Reuse the identifier from received messages. Format: '#channel' for channels, 'dm:@name' for DMs, '#channel:id' for channel threads, 'dm:@name:id' for DM threads. Examples: '#general', 'dm:@richard', '#general:abcd1234', 'dm:@richard:abcd1234'."
|
|
552
|
+
),
|
|
553
|
+
content: z2.string().describe("The message content"),
|
|
554
|
+
attachment_ids: z2.array(z2.string()).optional().describe("Optional attachment IDs from upload_file to include with the message")
|
|
555
|
+
},
|
|
556
|
+
async ({ target, content, attachment_ids }) => {
|
|
557
|
+
try {
|
|
558
|
+
const { response: res, data } = await executeRetrySafeSendRequest(
|
|
559
|
+
`${serverUrl}/internal/agent/${agentId}/send`,
|
|
560
|
+
(idempotencyKey) => ({
|
|
561
|
+
method: "POST",
|
|
562
|
+
headers: commonHeaders,
|
|
563
|
+
body: JSON.stringify({ target, content, attachmentIds: attachment_ids, idempotencyKey })
|
|
564
|
+
}),
|
|
565
|
+
{
|
|
566
|
+
target,
|
|
567
|
+
fetchImpl: bridgeFetch
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
if (!res.ok) {
|
|
571
|
+
return {
|
|
572
|
+
content: [
|
|
573
|
+
{ type: "text", text: `Error: ${data.error}` }
|
|
574
|
+
]
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
|
|
578
|
+
const replyHint = shortId ? ` (to reply in this message's thread, use target "${target.includes(":") ? target : target + ":" + shortId}")` : "";
|
|
579
|
+
let unreadSection = "";
|
|
580
|
+
if (data.recentUnread && data.recentUnread.length > 0) {
|
|
581
|
+
await acknowledgeReceivedMessages(data.recentUnread);
|
|
582
|
+
const unreadToShow = rememberDeliveredMessages(data.recentUnread);
|
|
583
|
+
if (unreadToShow.length > 0) {
|
|
584
|
+
unreadSection = `
|
|
585
|
+
|
|
586
|
+
--- New messages you may have missed ---
|
|
587
|
+
${formatMessages(unreadToShow)}`;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
446
590
|
return {
|
|
447
|
-
|
|
448
|
-
|
|
591
|
+
content: [
|
|
592
|
+
{
|
|
593
|
+
type: "text",
|
|
594
|
+
text: `Message sent to ${target}. Message ID: ${data.messageId}${replyHint}${unreadSection}`
|
|
595
|
+
}
|
|
596
|
+
]
|
|
449
597
|
};
|
|
450
|
-
}
|
|
451
|
-
const stat = fs.statSync(file_path);
|
|
452
|
-
if (stat.size > 10 * 1024 * 1024) {
|
|
598
|
+
} catch (err) {
|
|
453
599
|
return {
|
|
454
600
|
isError: true,
|
|
455
|
-
content: [{ type: "text", text: `Error:
|
|
601
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
456
602
|
};
|
|
457
603
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
server.tool(
|
|
607
|
+
"upload_file",
|
|
608
|
+
"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.",
|
|
609
|
+
{
|
|
610
|
+
file_path: z2.string().describe("Absolute path to the file on your local filesystem"),
|
|
611
|
+
channel: z2.string().describe("The channel target where this file will be used (e.g. '#general', 'dm:@richard')")
|
|
612
|
+
},
|
|
613
|
+
async ({ file_path, channel }) => {
|
|
614
|
+
try {
|
|
615
|
+
const fs = await import("fs");
|
|
616
|
+
const path = await import("path");
|
|
617
|
+
if (!fs.existsSync(file_path)) {
|
|
618
|
+
return {
|
|
619
|
+
isError: true,
|
|
620
|
+
content: [{ type: "text", text: `Error: File not found: ${file_path}` }]
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
const stat = fs.statSync(file_path);
|
|
624
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
625
|
+
return {
|
|
626
|
+
isError: true,
|
|
627
|
+
content: [{ type: "text", text: `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 10MB per file.` }]
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
const { response: listRes, data: listData } = await executeJsonRequest(
|
|
631
|
+
`${serverUrl}/internal/agent/${agentId}/resolve-channel`,
|
|
632
|
+
{
|
|
633
|
+
method: "POST",
|
|
634
|
+
headers: commonHeaders,
|
|
635
|
+
body: JSON.stringify({ target: channel })
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
toolName: "upload_file.resolve_channel",
|
|
639
|
+
target: channel,
|
|
640
|
+
fetchImpl: bridgeFetch
|
|
641
|
+
}
|
|
642
|
+
);
|
|
643
|
+
if (!listRes.ok || !listData.channelId) {
|
|
644
|
+
return {
|
|
645
|
+
isError: true,
|
|
646
|
+
content: [{ type: "text", text: `Error: ${listData.error || `Could not resolve channel: ${channel}`}` }]
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const channelId = listData.channelId;
|
|
650
|
+
const fileBuffer = fs.readFileSync(file_path);
|
|
651
|
+
const filename = path.basename(file_path);
|
|
652
|
+
const mimeType = guessMimeTypeFromFilename(filename);
|
|
653
|
+
const blob = new Blob([fileBuffer], { type: mimeType });
|
|
654
|
+
const formData = new FormData();
|
|
655
|
+
formData.append("file", blob, filename);
|
|
656
|
+
formData.append("channelId", channelId);
|
|
657
|
+
const uploadHeaders = buildChatBridgeCommonHeaders(authToken, { includeContentType: false });
|
|
658
|
+
const { response: res, data } = await executeJsonRequest(
|
|
659
|
+
`${serverUrl}/internal/agent/${agentId}/upload`,
|
|
660
|
+
{
|
|
661
|
+
method: "POST",
|
|
662
|
+
headers: uploadHeaders,
|
|
663
|
+
body: formData
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
toolName: "upload_file",
|
|
667
|
+
target: channel,
|
|
668
|
+
fetchImpl: bridgeFetch
|
|
669
|
+
}
|
|
670
|
+
);
|
|
671
|
+
if (!res.ok) {
|
|
672
|
+
return {
|
|
673
|
+
isError: true,
|
|
674
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
675
|
+
};
|
|
469
676
|
}
|
|
470
|
-
);
|
|
471
|
-
if (!listRes.ok || !listData.channelId) {
|
|
472
677
|
return {
|
|
473
|
-
|
|
474
|
-
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: "text",
|
|
681
|
+
text: `File uploaded: ${data.filename} (${(data.sizeBytes / 1024).toFixed(1)}KB)
|
|
682
|
+
Attachment ID: ${data.id}
|
|
683
|
+
|
|
684
|
+
Use this ID in send_message's attachment_ids parameter to include it in a message.`
|
|
685
|
+
}
|
|
686
|
+
]
|
|
475
687
|
};
|
|
476
|
-
}
|
|
477
|
-
const channelId = listData.channelId;
|
|
478
|
-
const fileBuffer = fs.readFileSync(file_path);
|
|
479
|
-
const filename = path.basename(file_path);
|
|
480
|
-
const mimeType = guessMimeTypeFromFilename(filename);
|
|
481
|
-
const blob = new Blob([fileBuffer], { type: mimeType });
|
|
482
|
-
const formData = new FormData();
|
|
483
|
-
formData.append("file", blob, filename);
|
|
484
|
-
formData.append("channelId", channelId);
|
|
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
|
-
);
|
|
499
|
-
if (!res.ok) {
|
|
688
|
+
} catch (err) {
|
|
500
689
|
return {
|
|
501
690
|
isError: true,
|
|
502
|
-
content: [{ type: "text", text: `Error: ${
|
|
691
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
503
692
|
};
|
|
504
693
|
}
|
|
505
|
-
|
|
506
|
-
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
server.tool(
|
|
697
|
+
"view_file",
|
|
698
|
+
"Download an attached file by its attachment ID and save it locally so you can inspect it. Returns the local file path.",
|
|
699
|
+
{
|
|
700
|
+
attachment_id: z2.string().describe("The attachment UUID (from the 'id:...' shown in the message)")
|
|
701
|
+
},
|
|
702
|
+
async ({ attachment_id }) => {
|
|
703
|
+
try {
|
|
704
|
+
const fs = await import("fs");
|
|
705
|
+
const path = await import("path");
|
|
706
|
+
const os = await import("os");
|
|
707
|
+
const cacheDir = path.join(os.homedir(), ".slock", "attachments");
|
|
708
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
709
|
+
const existing = fs.readdirSync(cacheDir).find((f) => f.startsWith(attachment_id));
|
|
710
|
+
if (existing) {
|
|
711
|
+
const cachedPath = path.join(cacheDir, existing);
|
|
712
|
+
return {
|
|
713
|
+
content: [{ type: "text", text: `File already cached at: ${cachedPath}` }]
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const downloadHeaders = buildChatBridgeCommonHeaders(authToken, { includeContentType: false });
|
|
717
|
+
const { response: res } = await executeResponseRequest(
|
|
718
|
+
`${serverUrl}/api/attachments/${attachment_id}`,
|
|
507
719
|
{
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
720
|
+
headers: downloadHeaders,
|
|
721
|
+
redirect: "follow"
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
toolName: "view_file",
|
|
725
|
+
target: attachment_id,
|
|
726
|
+
fetchImpl: bridgeFetch
|
|
513
727
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
);
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
{
|
|
528
|
-
attachment_id: z.string().describe("The attachment UUID (from the 'id:...' shown in the message)")
|
|
529
|
-
},
|
|
530
|
-
async ({ attachment_id }) => {
|
|
531
|
-
try {
|
|
532
|
-
const fs = await import("fs");
|
|
533
|
-
const path = await import("path");
|
|
534
|
-
const os = await import("os");
|
|
535
|
-
const cacheDir = path.join(os.homedir(), ".slock", "attachments");
|
|
536
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
537
|
-
const existing = fs.readdirSync(cacheDir).find((f) => f.startsWith(attachment_id));
|
|
538
|
-
if (existing) {
|
|
539
|
-
const cachedPath = path.join(cacheDir, existing);
|
|
728
|
+
);
|
|
729
|
+
if (!res.ok) {
|
|
730
|
+
return {
|
|
731
|
+
isError: true,
|
|
732
|
+
content: [{ type: "text", text: `Error: Failed to download attachment (${res.status})` }]
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
const contentType = res.headers.get("content-type") || "application/octet-stream";
|
|
736
|
+
const filename = parseFilenameFromContentDisposition(res.headers.get("content-disposition"));
|
|
737
|
+
const ext = filename ? path.extname(filename) || extensionForContentType(contentType) : extensionForContentType(contentType);
|
|
738
|
+
const filePath = path.join(cacheDir, `${attachment_id}${ext}`);
|
|
739
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
740
|
+
fs.writeFileSync(filePath, buffer);
|
|
540
741
|
return {
|
|
541
|
-
content: [{ type: "text", text: `
|
|
742
|
+
content: [{ type: "text", text: `Downloaded to: ${filePath}` }]
|
|
542
743
|
};
|
|
543
|
-
}
|
|
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
|
-
);
|
|
557
|
-
if (!res.ok) {
|
|
744
|
+
} catch (err) {
|
|
558
745
|
return {
|
|
559
746
|
isError: true,
|
|
560
|
-
content: [{ type: "text", text: `Error:
|
|
747
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
561
748
|
};
|
|
562
749
|
}
|
|
563
|
-
const contentType = res.headers.get("content-type") || "application/octet-stream";
|
|
564
|
-
const filename = parseFilenameFromContentDisposition(res.headers.get("content-disposition"));
|
|
565
|
-
const ext = filename ? path.extname(filename) || extensionForContentType(contentType) : extensionForContentType(contentType);
|
|
566
|
-
const filePath = path.join(cacheDir, `${attachment_id}${ext}`);
|
|
567
|
-
const buffer = Buffer.from(await res.arrayBuffer());
|
|
568
|
-
fs.writeFileSync(filePath, buffer);
|
|
569
|
-
return {
|
|
570
|
-
content: [{ type: "text", text: `Downloaded to: ${filePath}` }]
|
|
571
|
-
};
|
|
572
|
-
} catch (err) {
|
|
573
|
-
return {
|
|
574
|
-
isError: true,
|
|
575
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
576
|
-
};
|
|
577
750
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
751
|
+
);
|
|
752
|
+
server.tool(
|
|
753
|
+
"check_messages",
|
|
754
|
+
"Check for new messages without waiting. Returns immediately with any pending messages, or 'No new messages' if none. Use this freely during work \u2014 at natural breakpoints, after notifications, or whenever you want to see if anything new came in.",
|
|
755
|
+
{},
|
|
756
|
+
async () => {
|
|
757
|
+
try {
|
|
758
|
+
const { response: res, data } = await executeJsonRequest(
|
|
759
|
+
`${serverUrl}/internal/agent/${agentId}/receive`,
|
|
760
|
+
{ method: "GET", headers: commonHeaders },
|
|
761
|
+
{
|
|
762
|
+
toolName: "check_messages",
|
|
763
|
+
timeoutMs: 1e4,
|
|
764
|
+
fetchImpl: bridgeFetch
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
if (!res.ok) {
|
|
768
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
|
|
593
769
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
const messagesToShow = rememberDeliveredMessages(messages);
|
|
602
|
-
if (messagesToShow.length > 0) {
|
|
603
|
-
return { content: [{ type: "text", text: formatMessages(messagesToShow) }] };
|
|
770
|
+
const messages = data.messages ?? [];
|
|
771
|
+
if (messages.length > 0) {
|
|
772
|
+
await acknowledgeReceivedMessages(messages);
|
|
773
|
+
const messagesToShow = rememberDeliveredMessages(messages);
|
|
774
|
+
if (messagesToShow.length > 0) {
|
|
775
|
+
return { content: [{ type: "text", text: formatMessages(messagesToShow) }] };
|
|
776
|
+
}
|
|
604
777
|
}
|
|
778
|
+
return {
|
|
779
|
+
content: [{ type: "text", text: "No new messages." }]
|
|
780
|
+
};
|
|
781
|
+
} catch (err) {
|
|
782
|
+
return {
|
|
783
|
+
isError: true,
|
|
784
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
785
|
+
};
|
|
605
786
|
}
|
|
606
|
-
return {
|
|
607
|
-
content: [{ type: "text", text: "No new messages." }]
|
|
608
|
-
};
|
|
609
|
-
} catch (err) {
|
|
610
|
-
return {
|
|
611
|
-
isError: true,
|
|
612
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
613
|
-
};
|
|
614
787
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
fetchImpl: bridgeFetch
|
|
641
|
-
}
|
|
642
|
-
);
|
|
643
|
-
let text = "## Server\n\n";
|
|
644
|
-
const channels = data.channels ?? [];
|
|
645
|
-
const agents = data.agents ?? [];
|
|
646
|
-
const humans = data.humans ?? [];
|
|
647
|
-
text += "### Channels\n";
|
|
648
|
-
text += 'Visible public channels may appear even when `joined=false`. Use `read_history(channel="#name")` to inspect them. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel.\n';
|
|
649
|
-
if (channels.length > 0) {
|
|
650
|
-
for (const t of channels) {
|
|
651
|
-
const status = t.joined ? "joined" : "not joined";
|
|
652
|
-
text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
|
|
788
|
+
);
|
|
789
|
+
server.tool(
|
|
790
|
+
"list_server",
|
|
791
|
+
"List all channels in this server, including which ones you have joined, plus all agents and humans. Use this to discover who and where you can message.",
|
|
792
|
+
{},
|
|
793
|
+
async () => {
|
|
794
|
+
try {
|
|
795
|
+
const { response: res, data } = await executeJsonRequest(
|
|
796
|
+
`${serverUrl}/internal/agent/${agentId}/server`,
|
|
797
|
+
{ method: "GET", headers: commonHeaders },
|
|
798
|
+
{
|
|
799
|
+
toolName: "list_server",
|
|
800
|
+
fetchImpl: bridgeFetch
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
let text = "## Server\n\n";
|
|
804
|
+
const channels = data.channels ?? [];
|
|
805
|
+
const agents = data.agents ?? [];
|
|
806
|
+
const humans = data.humans ?? [];
|
|
807
|
+
text += "### Channels\n";
|
|
808
|
+
text += 'Visible public channels may appear even when `joined=false`. Use `read_history(channel="#name")` to inspect them. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel.\n';
|
|
809
|
+
if (channels.length > 0) {
|
|
810
|
+
for (const t of channels) {
|
|
811
|
+
const status = t.joined ? "joined" : "not joined";
|
|
812
|
+
text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
|
|
653
813
|
` : ` - #${t.name} [${status}]
|
|
654
814
|
`;
|
|
815
|
+
}
|
|
816
|
+
} else {
|
|
817
|
+
text += " (none)\n";
|
|
655
818
|
}
|
|
656
|
-
|
|
657
|
-
text += "
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
if (agents.length > 0) {
|
|
662
|
-
for (const a of agents) {
|
|
663
|
-
text += a.description ? ` - @${a.name} (${a.status}) \u2014 ${a.description}
|
|
819
|
+
text += "\n### Agents\n";
|
|
820
|
+
text += "Other AI agents in this server.\n";
|
|
821
|
+
if (agents.length > 0) {
|
|
822
|
+
for (const a of agents) {
|
|
823
|
+
text += a.description ? ` - @${a.name} (${a.status}) \u2014 ${a.description}
|
|
664
824
|
` : ` - @${a.name} (${a.status})
|
|
665
825
|
`;
|
|
826
|
+
}
|
|
827
|
+
} else {
|
|
828
|
+
text += " (none)\n";
|
|
666
829
|
}
|
|
667
|
-
|
|
668
|
-
text += "
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if (humans.length > 0) {
|
|
673
|
-
for (const u of humans) {
|
|
674
|
-
text += u.description ? ` - @${u.name} \u2014 ${u.description}
|
|
830
|
+
text += "\n### Humans\n";
|
|
831
|
+
text += 'To start a new DM: send_message(target="dm:@name"). To reply in an existing DM: reuse the target from received messages.\n';
|
|
832
|
+
if (humans.length > 0) {
|
|
833
|
+
for (const u of humans) {
|
|
834
|
+
text += u.description ? ` - @${u.name} \u2014 ${u.description}
|
|
675
835
|
` : ` - @${u.name}
|
|
676
836
|
`;
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
text += " (none)\n";
|
|
677
840
|
}
|
|
678
|
-
} else {
|
|
679
|
-
text += " (none)\n";
|
|
680
|
-
}
|
|
681
|
-
return {
|
|
682
|
-
content: [{ type: "text", text }]
|
|
683
|
-
};
|
|
684
|
-
} catch (err) {
|
|
685
|
-
return {
|
|
686
|
-
isError: true,
|
|
687
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
);
|
|
692
|
-
server.tool(
|
|
693
|
-
"search_messages",
|
|
694
|
-
"Search messages visible to the agent. Use this to find relevant conversations, then inspect a hit with read_history(channel=..., around=messageId).",
|
|
695
|
-
{
|
|
696
|
-
query: z.string().describe("Search query"),
|
|
697
|
-
channel: z.string().optional().describe("Optional target to scope the search, e.g. '#general', 'dm:@richard', '#general:abcd1234'"),
|
|
698
|
-
sender_id: z.string().optional().describe("Optional exact sender id filter."),
|
|
699
|
-
after: z.string().optional().describe("Optional inclusive ISO datetime lower bound for message created_at."),
|
|
700
|
-
before: z.string().optional().describe("Optional inclusive ISO datetime upper bound for message created_at."),
|
|
701
|
-
limit: z.number().default(10).describe("Max number of search results to return (default 10, max 20)")
|
|
702
|
-
},
|
|
703
|
-
async ({ query, channel, sender_id, after, before, limit }) => {
|
|
704
|
-
try {
|
|
705
|
-
const trimmed = query.trim();
|
|
706
|
-
if (!trimmed) {
|
|
707
|
-
return {
|
|
708
|
-
content: [{ type: "text", text: "Search query cannot be empty." }]
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
const params = new URLSearchParams();
|
|
712
|
-
params.set("q", trimmed);
|
|
713
|
-
params.set("limit", String(Math.min(limit, 20)));
|
|
714
|
-
if (channel) params.set("channel", channel);
|
|
715
|
-
if (sender_id) params.set("senderId", sender_id);
|
|
716
|
-
if (after) params.set("after", after);
|
|
717
|
-
if (before) params.set("before", before);
|
|
718
|
-
const { response: res, data } = await executeJsonRequest(
|
|
719
|
-
`${serverUrl}/internal/agent/${agentId}/search?${params}`,
|
|
720
|
-
{ method: "GET", headers: commonHeaders },
|
|
721
|
-
{
|
|
722
|
-
toolName: "search_messages",
|
|
723
|
-
target: channel ?? null,
|
|
724
|
-
fetchImpl: bridgeFetch
|
|
725
|
-
}
|
|
726
|
-
);
|
|
727
|
-
if (!res.ok) {
|
|
728
841
|
return {
|
|
729
|
-
content: [{ type: "text", text
|
|
842
|
+
content: [{ type: "text", text }]
|
|
730
843
|
};
|
|
731
|
-
}
|
|
732
|
-
if (!data.results || data.results.length === 0) {
|
|
844
|
+
} catch (err) {
|
|
733
845
|
return {
|
|
734
|
-
|
|
846
|
+
isError: true,
|
|
847
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
735
848
|
};
|
|
736
849
|
}
|
|
737
|
-
const formatted = data.results.map((result, index) => {
|
|
738
|
-
const target = formatSearchTarget(result);
|
|
739
|
-
const threadInfo = result.channelType === "thread" ? `
|
|
740
|
-
thread: ${result.parentChannelName} -> ${target}` : "";
|
|
741
|
-
return [
|
|
742
|
-
`[${index + 1}] msg=${result.id} seq=${result.seq} time=${toLocalTime(result.createdAt)}`,
|
|
743
|
-
`target: ${target}${threadInfo}`,
|
|
744
|
-
`sender: @${result.senderName} (${result.senderType})`,
|
|
745
|
-
`content: ${result.content}`,
|
|
746
|
-
`match: ${result.snippet}`,
|
|
747
|
-
`next: read_history(channel="${target}", around="${result.id}", limit=20)`
|
|
748
|
-
].join("\n");
|
|
749
|
-
}).join("\n\n");
|
|
750
|
-
return {
|
|
751
|
-
content: [{
|
|
752
|
-
type: "text",
|
|
753
|
-
text: `## Search Results for "${trimmed}" (${data.results.length} results)
|
|
754
|
-
|
|
755
|
-
${formatted}`
|
|
756
|
-
}]
|
|
757
|
-
};
|
|
758
|
-
} catch (err) {
|
|
759
|
-
return {
|
|
760
|
-
isError: true,
|
|
761
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
762
|
-
};
|
|
763
850
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
if (after) params.set("after", String(after));
|
|
784
|
-
const { response: res, data } = await executeJsonRequest(
|
|
785
|
-
`${serverUrl}/internal/agent/${agentId}/history?${params}`,
|
|
786
|
-
{ method: "GET", headers: commonHeaders },
|
|
787
|
-
{
|
|
788
|
-
toolName: "read_history",
|
|
789
|
-
target: channel,
|
|
790
|
-
fetchImpl: bridgeFetch
|
|
851
|
+
);
|
|
852
|
+
server.tool(
|
|
853
|
+
"search_messages",
|
|
854
|
+
"Search messages visible to the agent. Use this to find relevant conversations, then inspect a hit with read_history(channel=..., around=messageId).",
|
|
855
|
+
{
|
|
856
|
+
query: z2.string().describe("Search query"),
|
|
857
|
+
channel: z2.string().optional().describe("Optional target to scope the search, e.g. '#general', 'dm:@richard', '#general:abcd1234'"),
|
|
858
|
+
sender_id: z2.string().optional().describe("Optional exact sender id filter."),
|
|
859
|
+
after: z2.string().optional().describe("Optional inclusive ISO datetime lower bound for message created_at."),
|
|
860
|
+
before: z2.string().optional().describe("Optional inclusive ISO datetime upper bound for message created_at."),
|
|
861
|
+
limit: z2.number().default(10).describe("Max number of search results to return (default 10, max 20)")
|
|
862
|
+
},
|
|
863
|
+
async ({ query, channel, sender_id, after, before, limit }) => {
|
|
864
|
+
try {
|
|
865
|
+
const trimmed = query.trim();
|
|
866
|
+
if (!trimmed) {
|
|
867
|
+
return {
|
|
868
|
+
content: [{ type: "text", text: "Search query cannot be empty." }]
|
|
869
|
+
};
|
|
791
870
|
}
|
|
792
|
-
|
|
793
|
-
|
|
871
|
+
const params = new URLSearchParams();
|
|
872
|
+
params.set("q", trimmed);
|
|
873
|
+
params.set("limit", String(Math.min(limit, 20)));
|
|
874
|
+
if (channel) params.set("channel", channel);
|
|
875
|
+
if (sender_id) params.set("senderId", sender_id);
|
|
876
|
+
if (after) params.set("after", after);
|
|
877
|
+
if (before) params.set("before", before);
|
|
878
|
+
const { response: res, data } = await executeJsonRequest(
|
|
879
|
+
`${serverUrl}/internal/agent/${agentId}/search?${params}`,
|
|
880
|
+
{ method: "GET", headers: commonHeaders },
|
|
881
|
+
{
|
|
882
|
+
toolName: "search_messages",
|
|
883
|
+
target: channel ?? null,
|
|
884
|
+
fetchImpl: bridgeFetch
|
|
885
|
+
}
|
|
886
|
+
);
|
|
887
|
+
if (!res.ok) {
|
|
888
|
+
return {
|
|
889
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
if (!data.results || data.results.length === 0) {
|
|
893
|
+
return {
|
|
894
|
+
content: [{ type: "text", text: "No search results." }]
|
|
895
|
+
};
|
|
896
|
+
}
|
|
897
|
+
const formatted = data.results.map((result, index) => {
|
|
898
|
+
const target = formatSearchTarget(result);
|
|
899
|
+
const threadInfo = result.channelType === "thread" ? `
|
|
900
|
+
thread: ${result.parentChannelName} -> ${target}` : "";
|
|
901
|
+
return [
|
|
902
|
+
`[${index + 1}] msg=${result.id} seq=${result.seq} time=${toLocalTime(result.createdAt)}`,
|
|
903
|
+
`target: ${target}${threadInfo}`,
|
|
904
|
+
`sender: @${result.senderName} (${result.senderType})`,
|
|
905
|
+
`content: ${result.content}`,
|
|
906
|
+
`match: ${result.snippet}`,
|
|
907
|
+
`next: read_history(channel="${target}", around="${result.id}", limit=20)`
|
|
908
|
+
].join("\n");
|
|
909
|
+
}).join("\n\n");
|
|
794
910
|
return {
|
|
795
|
-
content: [
|
|
796
|
-
|
|
797
|
-
|
|
911
|
+
content: [{
|
|
912
|
+
type: "text",
|
|
913
|
+
text: `## Search Results for "${trimmed}" (${data.results.length} results)
|
|
914
|
+
|
|
915
|
+
${formatted}`
|
|
916
|
+
}]
|
|
798
917
|
};
|
|
799
|
-
}
|
|
800
|
-
if (!data.messages || data.messages.length === 0) {
|
|
918
|
+
} catch (err) {
|
|
801
919
|
return {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
]
|
|
920
|
+
isError: true,
|
|
921
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
805
922
|
};
|
|
806
923
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
server.tool(
|
|
927
|
+
"read_history",
|
|
928
|
+
"Read message history for a channel, DM, or thread. Use the same target format: '#channel', 'dm:@name', '#channel:id' for threads, 'dm:@name:id' for DM threads. Supports pagination via 'before' / 'after', and context jumps via 'around' (messageId or seq).",
|
|
929
|
+
{
|
|
930
|
+
channel: z2.string().describe("The target to read history from \u2014 e.g. '#general', 'dm:@richard', '#general:abcd1234', 'dm:@richard:abcd1234'"),
|
|
931
|
+
limit: z2.number().default(50).describe("Max number of messages to return (default 50, max 100)"),
|
|
932
|
+
around: z2.union([z2.string(), z2.number()]).optional().describe("Center the result window around a messageId or seq in this channel/thread."),
|
|
933
|
+
before: z2.number().optional().describe("Return messages before this seq number (for backward pagination). Omit for latest messages."),
|
|
934
|
+
after: z2.number().optional().describe("Return messages after this seq number (for catching up on unread). Returns oldest-first.")
|
|
935
|
+
},
|
|
936
|
+
async ({ channel, limit, around, before, after }) => {
|
|
937
|
+
try {
|
|
938
|
+
const params = new URLSearchParams();
|
|
939
|
+
params.set("channel", channel);
|
|
940
|
+
params.set("limit", String(Math.min(limit, 100)));
|
|
941
|
+
if (around !== void 0) params.set("around", String(around));
|
|
942
|
+
if (before) params.set("before", String(before));
|
|
943
|
+
if (after) params.set("after", String(after));
|
|
944
|
+
const { response: res, data } = await executeJsonRequest(
|
|
945
|
+
`${serverUrl}/internal/agent/${agentId}/history?${params}`,
|
|
946
|
+
{ method: "GET", headers: commonHeaders },
|
|
947
|
+
{
|
|
948
|
+
toolName: "read_history",
|
|
949
|
+
target: channel,
|
|
950
|
+
fetchImpl: bridgeFetch
|
|
951
|
+
}
|
|
952
|
+
);
|
|
953
|
+
if (!res.ok) {
|
|
954
|
+
return {
|
|
955
|
+
content: [
|
|
956
|
+
{ type: "text", text: `Error: ${data.error}` }
|
|
957
|
+
]
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
if (!data.messages || data.messages.length === 0) {
|
|
961
|
+
return {
|
|
962
|
+
content: [
|
|
963
|
+
{ type: "text", text: "No messages in this channel." }
|
|
964
|
+
]
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
const formatted = data.messages.map((m) => formatHistoryMessageLine({
|
|
968
|
+
...m,
|
|
969
|
+
senderName: m.senderName ?? m.sender_name ?? "unknown",
|
|
970
|
+
senderDescription: m.senderDescription ?? m.sender_description ?? null
|
|
971
|
+
})).join("\n");
|
|
972
|
+
let footer = "";
|
|
973
|
+
if (data.historyLimited) {
|
|
974
|
+
footer = `
|
|
815
975
|
|
|
816
976
|
--- ${data.historyLimitMessage || "Message history is limited on this plan."} ---`;
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const maxSeq = data.messages[data.messages.length - 1].seq;
|
|
820
|
-
footer = `
|
|
821
|
-
|
|
822
|
-
--- Context window shown. Use before=${minSeq} to load older messages or after=${maxSeq} to load newer messages. ---`;
|
|
823
|
-
} else if (data.has_more && data.messages.length > 0) {
|
|
824
|
-
if (after) {
|
|
977
|
+
} else if (around && data.messages.length > 0 && (data.has_older || data.has_newer)) {
|
|
978
|
+
const minSeq = data.messages[0].seq;
|
|
825
979
|
const maxSeq = data.messages[data.messages.length - 1].seq;
|
|
826
980
|
footer = `
|
|
827
981
|
|
|
982
|
+
--- Context window shown. Use before=${minSeq} to load older messages or after=${maxSeq} to load newer messages. ---`;
|
|
983
|
+
} else if (data.has_more && data.messages.length > 0) {
|
|
984
|
+
if (after) {
|
|
985
|
+
const maxSeq = data.messages[data.messages.length - 1].seq;
|
|
986
|
+
footer = `
|
|
987
|
+
|
|
828
988
|
--- ${data.messages.length} messages shown. Use after=${maxSeq} to load more recent messages. ---`;
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
989
|
+
} else {
|
|
990
|
+
const minSeq = data.messages[0].seq;
|
|
991
|
+
footer = `
|
|
832
992
|
|
|
833
993
|
--- ${data.messages.length} messages shown. Use before=${minSeq} to load older messages. ---`;
|
|
994
|
+
}
|
|
834
995
|
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
header += `
|
|
996
|
+
let header = `## Message History for ${channel}${around ? ` around ${around}` : ""} (${data.messages.length} messages)`;
|
|
997
|
+
if ((data.last_read_seq ?? 0) > 0 && !after && !before && !around) {
|
|
998
|
+
header += `
|
|
839
999
|
Your last read position: seq ${data.last_read_seq}. Use read_history(channel="${channel}", after=${data.last_read_seq}) to see only unread messages.`;
|
|
840
|
-
}
|
|
841
|
-
return {
|
|
842
|
-
content: [
|
|
843
|
-
{
|
|
844
|
-
type: "text",
|
|
845
|
-
text: `${header}
|
|
846
|
-
|
|
847
|
-
${formatted}${footer}`
|
|
848
|
-
}
|
|
849
|
-
]
|
|
850
|
-
};
|
|
851
|
-
} catch (err) {
|
|
852
|
-
return {
|
|
853
|
-
isError: true,
|
|
854
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
);
|
|
859
|
-
server.tool(
|
|
860
|
-
"list_tasks",
|
|
861
|
-
"List all tasks in a channel. Returns each task's number, title, status, assignee, and message ID. Use this to see what work exists before claiming. Tasks marked as legacy are from an older system and cannot be claimed or modified.",
|
|
862
|
-
{
|
|
863
|
-
channel: z.string().describe("The channel whose task board to view \u2014 e.g. '#engineering', '#proj-slock'"),
|
|
864
|
-
status: z.enum(["all", "todo", "in_progress", "in_review", "done"]).default("all").describe("Filter by status (default: all)")
|
|
865
|
-
},
|
|
866
|
-
async ({ channel, status }) => {
|
|
867
|
-
try {
|
|
868
|
-
const params = new URLSearchParams();
|
|
869
|
-
params.set("channel", channel);
|
|
870
|
-
if (status !== "all") params.set("status", status);
|
|
871
|
-
const { response: res, data } = await executeJsonRequest(
|
|
872
|
-
`${serverUrl}/internal/agent/${agentId}/tasks?${params}`,
|
|
873
|
-
{ method: "GET", headers: commonHeaders },
|
|
874
|
-
{
|
|
875
|
-
toolName: "list_tasks",
|
|
876
|
-
target: channel,
|
|
877
|
-
fetchImpl: bridgeFetch
|
|
878
1000
|
}
|
|
879
|
-
);
|
|
880
|
-
if (!res.ok) {
|
|
881
1001
|
return {
|
|
882
|
-
|
|
883
|
-
|
|
1002
|
+
content: [
|
|
1003
|
+
{
|
|
1004
|
+
type: "text",
|
|
1005
|
+
text: `${header}
|
|
1006
|
+
|
|
1007
|
+
${formatted}${footer}`
|
|
1008
|
+
}
|
|
1009
|
+
]
|
|
884
1010
|
};
|
|
885
|
-
}
|
|
886
|
-
if (!data.tasks || data.tasks.length === 0) {
|
|
1011
|
+
} catch (err) {
|
|
887
1012
|
return {
|
|
888
|
-
|
|
1013
|
+
isError: true,
|
|
1014
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
889
1015
|
};
|
|
890
1016
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
server.tool(
|
|
1020
|
+
"list_tasks",
|
|
1021
|
+
"List all tasks in a channel. Returns each task's number, title, status, assignee, and message ID. Use this to see what work exists before claiming. Tasks marked as legacy are from an older system and cannot be claimed or modified.",
|
|
1022
|
+
{
|
|
1023
|
+
channel: z2.string().describe("The channel whose task board to view \u2014 e.g. '#engineering', '#proj-slock'"),
|
|
1024
|
+
status: z2.enum(["all", "todo", "in_progress", "in_review", "done"]).default("all").describe("Filter by status (default: all)")
|
|
1025
|
+
},
|
|
1026
|
+
async ({ channel, status }) => {
|
|
1027
|
+
try {
|
|
1028
|
+
const params = new URLSearchParams();
|
|
1029
|
+
params.set("channel", channel);
|
|
1030
|
+
if (status !== "all") params.set("status", status);
|
|
1031
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1032
|
+
`${serverUrl}/internal/agent/${agentId}/tasks?${params}`,
|
|
1033
|
+
{ method: "GET", headers: commonHeaders },
|
|
900
1034
|
{
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
${formatted}`
|
|
1035
|
+
toolName: "list_tasks",
|
|
1036
|
+
target: channel,
|
|
1037
|
+
fetchImpl: bridgeFetch
|
|
905
1038
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
};
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
);
|
|
916
|
-
server.tool(
|
|
917
|
-
"create_tasks",
|
|
918
|
-
"Create one or more new task-messages in a top-level channel or DM. This is a convenience helper for creating a brand-new message and publishing it as a task-message in the chat flow. Thread messages cannot become tasks. It does not claim the task for you; if you want to own it, still call claim_tasks afterward. It is not a separate task board outside the chat flow. Typical uses are breaking down a larger task into parallel subtasks or batch-creating new work for others to claim. Do not use this to convert an existing message \u2014 use claim_tasks with message_ids instead. If the work already exists as a task, either claim that task or leave it alone; do not create a second task/message for the same work.",
|
|
919
|
-
{
|
|
920
|
-
channel: z.string().describe("The channel to create tasks in \u2014 e.g. '#engineering'"),
|
|
921
|
-
tasks: z.array(
|
|
922
|
-
z.object({
|
|
923
|
-
title: z.string().describe("Task title")
|
|
924
|
-
})
|
|
925
|
-
).describe("Array of tasks to create")
|
|
926
|
-
},
|
|
927
|
-
async ({ channel, tasks }) => {
|
|
928
|
-
try {
|
|
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
|
|
1039
|
+
);
|
|
1040
|
+
if (!res.ok) {
|
|
1041
|
+
return {
|
|
1042
|
+
isError: true,
|
|
1043
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
1044
|
+
};
|
|
940
1045
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1046
|
+
if (!data.tasks || data.tasks.length === 0) {
|
|
1047
|
+
return {
|
|
1048
|
+
content: [{ type: "text", text: `No${status !== "all" ? ` ${status}` : ""} tasks in ${channel}.` }]
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
const formatted = data.tasks.map((t) => {
|
|
1052
|
+
const assignee = t.claimedByName ? ` \u2192 @${t.claimedByName}` : "";
|
|
1053
|
+
const creator = t.createdByName ? ` (by @${t.createdByName})` : "";
|
|
1054
|
+
const msgId = t.messageId ? ` msg=${t.messageId.slice(0, 8)}` : "";
|
|
1055
|
+
const legacy = t.isLegacy ? " [LEGACY \u2014 read-only]" : "";
|
|
1056
|
+
return `#${t.taskNumber} [${t.status}] ${t.title}${assignee}${creator}${msgId}${legacy}`;
|
|
1057
|
+
}).join("\n");
|
|
1058
|
+
return {
|
|
1059
|
+
content: [
|
|
1060
|
+
{
|
|
1061
|
+
type: "text",
|
|
1062
|
+
text: `## Task Board for ${channel} (${data.tasks.length} tasks)
|
|
1063
|
+
|
|
1064
|
+
${formatted}`
|
|
1065
|
+
}
|
|
1066
|
+
]
|
|
1067
|
+
};
|
|
1068
|
+
} catch (err) {
|
|
943
1069
|
return {
|
|
944
1070
|
isError: true,
|
|
945
|
-
content: [{ type: "text", text: `Error: ${
|
|
1071
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
946
1072
|
};
|
|
947
1073
|
}
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1074
|
+
}
|
|
1075
|
+
);
|
|
1076
|
+
server.tool(
|
|
1077
|
+
"create_tasks",
|
|
1078
|
+
"Create one or more new task-messages in a top-level channel or DM. This is a convenience helper for creating a brand-new message and publishing it as a task-message in the chat flow. Thread messages cannot become tasks. It does not claim the task for you; if you want to own it, still call claim_tasks afterward. It is not a separate task board outside the chat flow. Typical uses are breaking down a larger task into parallel subtasks or batch-creating new work for others to claim. Do not use this to convert an existing message \u2014 use claim_tasks with message_ids instead. If the work already exists as a task, either claim that task or leave it alone; do not create a second task/message for the same work.",
|
|
1079
|
+
{
|
|
1080
|
+
channel: z2.string().describe("The channel to create tasks in \u2014 e.g. '#engineering'"),
|
|
1081
|
+
tasks: z2.array(
|
|
1082
|
+
z2.object({
|
|
1083
|
+
title: z2.string().describe("Task title")
|
|
1084
|
+
})
|
|
1085
|
+
).describe("Array of tasks to create")
|
|
1086
|
+
},
|
|
1087
|
+
async ({ channel, tasks }) => {
|
|
1088
|
+
try {
|
|
1089
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1090
|
+
`${serverUrl}/internal/agent/${agentId}/tasks`,
|
|
952
1091
|
{
|
|
953
|
-
|
|
954
|
-
|
|
1092
|
+
method: "POST",
|
|
1093
|
+
headers: commonHeaders,
|
|
1094
|
+
body: JSON.stringify({ channel, tasks })
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
toolName: "create_tasks",
|
|
1098
|
+
target: channel,
|
|
1099
|
+
fetchImpl: bridgeFetch
|
|
1100
|
+
}
|
|
1101
|
+
);
|
|
1102
|
+
if (!res.ok) {
|
|
1103
|
+
return {
|
|
1104
|
+
isError: true,
|
|
1105
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
const created = data.tasks.map((t) => `#${t.taskNumber} msg=${t.messageId.slice(0, 8)} "${t.title}"`).join("\n");
|
|
1109
|
+
const threadHints = data.tasks.map((t) => `#${t.taskNumber} \u2192 send_message to "${channel}:${t.messageId.slice(0, 8)}"`).join("\n");
|
|
1110
|
+
return {
|
|
1111
|
+
content: [
|
|
1112
|
+
{
|
|
1113
|
+
type: "text",
|
|
1114
|
+
text: `Created ${data.tasks.length} task(s) in ${channel}:
|
|
955
1115
|
${created}
|
|
956
1116
|
|
|
957
1117
|
To follow up in each task's thread:
|
|
958
1118
|
${threadHints}`
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1119
|
+
}
|
|
1120
|
+
]
|
|
1121
|
+
};
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
return {
|
|
1124
|
+
isError: true,
|
|
1125
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
967
1128
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
`Claim tasks so you are assigned to work on them. Two modes:
|
|
1129
|
+
);
|
|
1130
|
+
server.tool(
|
|
1131
|
+
"claim_tasks",
|
|
1132
|
+
`Claim tasks so you are assigned to work on them. Two modes:
|
|
973
1133
|
1. By task number: claim existing tasks shown in list_tasks. Use task_numbers=[1, 3].
|
|
974
1134
|
2. By message ID: convert a regular top-level message into a task and claim it. Use message_ids=["a1b2c3d4"]. The message ID is the 8-character msg= value from received messages or read_history.
|
|
975
1135
|
|
|
976
1136
|
Thread messages and system messages (e.g. task-claim / task-status announcements) cannot be claimed or converted into tasks \u2014 if a system message describes an action you should take, just do it; otherwise ignore it. If a task is in "todo" status, claiming auto-advances it to "in_progress". If another agent already claimed it, the claim fails \u2014 do not work on that task, move on. Always claim before starting any work to prevent duplicate effort.`,
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1137
|
+
{
|
|
1138
|
+
channel: z2.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
1139
|
+
task_numbers: z2.array(z2.number()).optional().describe("Task numbers to claim (from list_tasks output, e.g. [1, 3])"),
|
|
1140
|
+
message_ids: z2.array(z2.string()).optional().describe("Message IDs or short ID prefixes (the 8-char msg= value, e.g. ['a1b2c3d4']). Converts a regular top-level message to a task and claims it. Thread messages are not allowed.")
|
|
1141
|
+
},
|
|
1142
|
+
async ({ channel, task_numbers, message_ids }) => {
|
|
1143
|
+
try {
|
|
1144
|
+
if ((!task_numbers || task_numbers.length === 0) && (!message_ids || message_ids.length === 0)) {
|
|
1145
|
+
return {
|
|
1146
|
+
content: [{ type: "text", text: "Error: provide at least one of task_numbers or message_ids" }]
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
const body = { channel };
|
|
1150
|
+
if (task_numbers && task_numbers.length > 0) body.task_numbers = task_numbers;
|
|
1151
|
+
if (message_ids && message_ids.length > 0) body.message_ids = message_ids;
|
|
1152
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1153
|
+
`${serverUrl}/internal/agent/${agentId}/tasks/claim`,
|
|
1154
|
+
{
|
|
1155
|
+
method: "POST",
|
|
1156
|
+
headers: commonHeaders,
|
|
1157
|
+
body: JSON.stringify(body)
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
toolName: "claim_tasks",
|
|
1161
|
+
target: channel,
|
|
1162
|
+
fetchImpl: bridgeFetch
|
|
1163
|
+
}
|
|
1164
|
+
);
|
|
1165
|
+
if (!res.ok) {
|
|
1166
|
+
return {
|
|
1167
|
+
isError: true,
|
|
1168
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
const lines = data.results.map((r) => {
|
|
1172
|
+
const label = r.taskNumber ? `#${r.taskNumber}` : `msg:${r.messageId}`;
|
|
1173
|
+
if (r.success) {
|
|
1174
|
+
const msgShort = r.messageId ? r.messageId.slice(0, 8) : "";
|
|
1175
|
+
return `${label} (msg:${msgShort}): claimed`;
|
|
1176
|
+
}
|
|
1177
|
+
return `${label}: FAILED \u2014 ${r.reason || "already claimed"}`;
|
|
1178
|
+
});
|
|
1179
|
+
const succeeded = data.results.filter((r) => r.success).length;
|
|
1180
|
+
const failed = data.results.length - succeeded;
|
|
1181
|
+
let summary = `${succeeded} claimed`;
|
|
1182
|
+
if (failed > 0) summary += `, ${failed} failed`;
|
|
1183
|
+
const claimedMsgs = data.results.filter((r) => r.success && r.messageId).map((r) => `#${r.taskNumber} \u2192 send_message to "${channel}:${r.messageId.slice(0, 8)}"`).join("\n");
|
|
1184
|
+
const threadHint = claimedMsgs ? `
|
|
1185
|
+
|
|
1186
|
+
Follow up in each task's thread:
|
|
1187
|
+
${claimedMsgs}` : "";
|
|
985
1188
|
return {
|
|
986
|
-
content: [
|
|
1189
|
+
content: [
|
|
1190
|
+
{
|
|
1191
|
+
type: "text",
|
|
1192
|
+
text: `Claim results (${summary}):
|
|
1193
|
+
${lines.join("\n")}${threadHint}`
|
|
1194
|
+
}
|
|
1195
|
+
]
|
|
987
1196
|
};
|
|
988
|
-
}
|
|
989
|
-
const body = { channel };
|
|
990
|
-
if (task_numbers && task_numbers.length > 0) body.task_numbers = task_numbers;
|
|
991
|
-
if (message_ids && message_ids.length > 0) body.message_ids = message_ids;
|
|
992
|
-
const { response: res, data } = await executeJsonRequest(
|
|
993
|
-
`${serverUrl}/internal/agent/${agentId}/tasks/claim`,
|
|
994
|
-
{
|
|
995
|
-
method: "POST",
|
|
996
|
-
headers: commonHeaders,
|
|
997
|
-
body: JSON.stringify(body)
|
|
998
|
-
},
|
|
999
|
-
{
|
|
1000
|
-
toolName: "claim_tasks",
|
|
1001
|
-
target: channel,
|
|
1002
|
-
fetchImpl: bridgeFetch
|
|
1003
|
-
}
|
|
1004
|
-
);
|
|
1005
|
-
if (!res.ok) {
|
|
1197
|
+
} catch (err) {
|
|
1006
1198
|
return {
|
|
1007
1199
|
isError: true,
|
|
1008
|
-
content: [{ type: "text", text: `Error: ${
|
|
1200
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1009
1201
|
};
|
|
1010
1202
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const threadHint = claimedMsgs ? `
|
|
1025
|
-
|
|
1026
|
-
Follow up in each task's thread:
|
|
1027
|
-
${claimedMsgs}` : "";
|
|
1028
|
-
return {
|
|
1029
|
-
content: [
|
|
1203
|
+
}
|
|
1204
|
+
);
|
|
1205
|
+
server.tool(
|
|
1206
|
+
"unclaim_task",
|
|
1207
|
+
"Release your claim on a task so someone else can pick it up. Only use this if you can no longer work on the task \u2014 not as a way to mark it done. Use update_task_status to change status instead.",
|
|
1208
|
+
{
|
|
1209
|
+
channel: z2.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
1210
|
+
task_number: z2.number().describe("The task number to unclaim (e.g. 3)")
|
|
1211
|
+
},
|
|
1212
|
+
async ({ channel, task_number }) => {
|
|
1213
|
+
try {
|
|
1214
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1215
|
+
`${serverUrl}/internal/agent/${agentId}/tasks/unclaim`,
|
|
1030
1216
|
{
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1217
|
+
method: "POST",
|
|
1218
|
+
headers: commonHeaders,
|
|
1219
|
+
body: JSON.stringify({ channel, task_number })
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
toolName: "unclaim_task",
|
|
1223
|
+
target: channel,
|
|
1224
|
+
fetchImpl: bridgeFetch
|
|
1034
1225
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
);
|
|
1045
|
-
server.tool(
|
|
1046
|
-
"unclaim_task",
|
|
1047
|
-
"Release your claim on a task so someone else can pick it up. Only use this if you can no longer work on the task \u2014 not as a way to mark it done. Use update_task_status to change status instead.",
|
|
1048
|
-
{
|
|
1049
|
-
channel: z.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
1050
|
-
task_number: z.number().describe("The task number to unclaim (e.g. 3)")
|
|
1051
|
-
},
|
|
1052
|
-
async ({ channel, task_number }) => {
|
|
1053
|
-
try {
|
|
1054
|
-
const { response: res, data } = await executeJsonRequest(
|
|
1055
|
-
`${serverUrl}/internal/agent/${agentId}/tasks/unclaim`,
|
|
1056
|
-
{
|
|
1057
|
-
method: "POST",
|
|
1058
|
-
headers: commonHeaders,
|
|
1059
|
-
body: JSON.stringify({ channel, task_number })
|
|
1060
|
-
},
|
|
1061
|
-
{
|
|
1062
|
-
toolName: "unclaim_task",
|
|
1063
|
-
target: channel,
|
|
1064
|
-
fetchImpl: bridgeFetch
|
|
1226
|
+
);
|
|
1227
|
+
if (!res.ok) {
|
|
1228
|
+
return {
|
|
1229
|
+
isError: true,
|
|
1230
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
1231
|
+
};
|
|
1065
1232
|
}
|
|
1066
|
-
|
|
1067
|
-
|
|
1233
|
+
return {
|
|
1234
|
+
content: [
|
|
1235
|
+
{ type: "text", text: `#${task_number} unclaimed \u2014 now open.` }
|
|
1236
|
+
]
|
|
1237
|
+
};
|
|
1238
|
+
} catch (err) {
|
|
1068
1239
|
return {
|
|
1069
1240
|
isError: true,
|
|
1070
|
-
content: [{ type: "text", text: `Error: ${
|
|
1241
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1071
1242
|
};
|
|
1072
1243
|
}
|
|
1073
|
-
return {
|
|
1074
|
-
content: [
|
|
1075
|
-
{ type: "text", text: `#${task_number} unclaimed \u2014 now open.` }
|
|
1076
|
-
]
|
|
1077
|
-
};
|
|
1078
|
-
} catch (err) {
|
|
1079
|
-
return {
|
|
1080
|
-
isError: true,
|
|
1081
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1082
|
-
};
|
|
1083
1244
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1245
|
+
);
|
|
1246
|
+
server.tool(
|
|
1247
|
+
"update_task_status",
|
|
1248
|
+
"Update a task's progress status. You must be the task's assignee to update it. Use in_review when your work is ready for human validation. Only set done for trivial tasks or after explicit approval. Valid transitions: todo\u2192in_progress, in_progress\u2192in_review or done, in_review\u2192done or back to in_progress.",
|
|
1249
|
+
{
|
|
1250
|
+
channel: z2.string().describe("The channel \u2014 e.g. '#engineering'"),
|
|
1251
|
+
task_number: z2.number().describe("The task number to update (e.g. 3)"),
|
|
1252
|
+
status: z2.enum(["todo", "in_progress", "in_review", "done"]).describe("The new status")
|
|
1253
|
+
},
|
|
1254
|
+
async ({ channel, task_number, status }) => {
|
|
1255
|
+
try {
|
|
1256
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1257
|
+
`${serverUrl}/internal/agent/${agentId}/tasks/update-status`,
|
|
1258
|
+
{
|
|
1259
|
+
method: "POST",
|
|
1260
|
+
headers: commonHeaders,
|
|
1261
|
+
body: JSON.stringify({ channel, task_number, status })
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
toolName: "update_task_status",
|
|
1265
|
+
target: channel,
|
|
1266
|
+
fetchImpl: bridgeFetch
|
|
1267
|
+
}
|
|
1268
|
+
);
|
|
1269
|
+
if (!res.ok) {
|
|
1270
|
+
return {
|
|
1271
|
+
isError: true,
|
|
1272
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
1273
|
+
};
|
|
1107
1274
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1275
|
+
return {
|
|
1276
|
+
content: [
|
|
1277
|
+
{ type: "text", text: `#${task_number} moved to ${status}.` }
|
|
1278
|
+
]
|
|
1279
|
+
};
|
|
1280
|
+
} catch (err) {
|
|
1110
1281
|
return {
|
|
1111
1282
|
isError: true,
|
|
1112
|
-
content: [{ type: "text", text: `Error: ${
|
|
1283
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1113
1284
|
};
|
|
1114
1285
|
}
|
|
1115
|
-
return {
|
|
1116
|
-
content: [
|
|
1117
|
-
{ type: "text", text: `#${task_number} moved to ${status}.` }
|
|
1118
|
-
]
|
|
1119
|
-
};
|
|
1120
|
-
} catch (err) {
|
|
1121
|
-
return {
|
|
1122
|
-
isError: true,
|
|
1123
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1124
|
-
};
|
|
1125
1286
|
}
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
try {
|
|
1147
|
-
const body = { title, msgId: msg_id };
|
|
1148
|
-
if (delay_seconds !== void 0) body.delaySeconds = delay_seconds;
|
|
1149
|
-
if (fire_at !== void 0) body.fireAt = fire_at;
|
|
1150
|
-
if (repeat !== void 0) {
|
|
1151
|
-
body.repeat = repeat;
|
|
1152
|
-
body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1153
|
-
}
|
|
1154
|
-
if (channel !== void 0) body.channel = channel;
|
|
1155
|
-
const { response: res, data } = await executeJsonRequest(
|
|
1156
|
-
`${serverUrl}/internal/agent/${agentId}/reminders`,
|
|
1157
|
-
{
|
|
1158
|
-
method: "POST",
|
|
1159
|
-
headers: commonHeaders,
|
|
1160
|
-
body: JSON.stringify(body)
|
|
1161
|
-
},
|
|
1162
|
-
{
|
|
1163
|
-
toolName: "schedule_reminder",
|
|
1164
|
-
fetchImpl: bridgeFetch
|
|
1287
|
+
);
|
|
1288
|
+
server.tool(
|
|
1289
|
+
"schedule_reminder",
|
|
1290
|
+
"Schedule a reminder that will fire at a future time and wake you up with a DM. Use this when you need to follow up on something after a delay, at a specific time, or on a schedule. The reminder persists across daemon restarts. For one-shot reminders, provide delay_seconds (preferred) OR fire_at. For recurring reminders, provide repeat; you may also combine repeat with delay_seconds or fire_at to pin the first fire.",
|
|
1291
|
+
{
|
|
1292
|
+
title: z2.string().describe("Short description of what the reminder is about. This is what you'll see when it fires."),
|
|
1293
|
+
delay_seconds: z2.number().int().positive().optional().describe("Preferred for relative times. Fires this many seconds from now (server-computed, timezone-safe). Use this for any 'in N seconds/minutes/hours' request."),
|
|
1294
|
+
fire_at: z2.string().optional().describe("ISO-8601 UTC timestamp, e.g. '2026-04-21T09:00:00Z'. Use only for absolute calendar times ('tomorrow 9am UTC'). Your local clock is NOT trusted as UTC \u2014 if you mean a relative delay, use delay_seconds instead."),
|
|
1295
|
+
repeat: z2.string().optional().describe("Recurrence rule. Supported forms: 'every:15m' | 'every:2h' | 'every:1d' (fixed interval) | 'daily@09:00' (in your local tz, snapshotted at creation) | 'weekly:mon,fri@09:00' (specific weekdays). The reminder auto-reschedules after each fire until you cancel it."),
|
|
1296
|
+
channel: z2.string().optional().describe("Optional explicit channel to post a receipt system message in (format: '#channel', 'dm:@name', or thread ref). Use this only if you want the receipt somewhere other than the anchor message's channel."),
|
|
1297
|
+
msg_id: z2.string().describe("Required anchor message id (from a received message). Resolve it explicitly and pass it in; if you cannot resolve one, do not create the reminder.")
|
|
1298
|
+
},
|
|
1299
|
+
async ({ title, delay_seconds, fire_at, repeat, channel, msg_id }) => {
|
|
1300
|
+
try {
|
|
1301
|
+
const body = { title, msgId: msg_id };
|
|
1302
|
+
if (delay_seconds !== void 0) body.delaySeconds = delay_seconds;
|
|
1303
|
+
if (fire_at !== void 0) body.fireAt = fire_at;
|
|
1304
|
+
if (repeat !== void 0) {
|
|
1305
|
+
body.repeat = repeat;
|
|
1306
|
+
body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
1165
1307
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
server.tool(
|
|
1182
|
-
"list_reminders",
|
|
1183
|
-
"List your own reminders. Defaults to scheduled (pending) ones; pass status to include fired or canceled.",
|
|
1184
|
-
{
|
|
1185
|
-
status: z.string().optional().describe("Comma-separated statuses to include (scheduled,fired,canceled). Defaults to 'scheduled'.")
|
|
1186
|
-
},
|
|
1187
|
-
async ({ status }) => {
|
|
1188
|
-
try {
|
|
1189
|
-
const qs = new URLSearchParams();
|
|
1190
|
-
qs.set("status", status && status.trim().length > 0 ? status.trim() : "scheduled");
|
|
1191
|
-
const { response: res, data } = await executeJsonRequest(
|
|
1192
|
-
`${serverUrl}/internal/agent/${agentId}/reminders?${qs.toString()}`,
|
|
1193
|
-
{ method: "GET", headers: commonHeaders },
|
|
1194
|
-
{
|
|
1195
|
-
toolName: "list_reminders",
|
|
1196
|
-
fetchImpl: bridgeFetch
|
|
1308
|
+
if (channel !== void 0) body.channel = channel;
|
|
1309
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1310
|
+
`${serverUrl}/internal/agent/${agentId}/reminders`,
|
|
1311
|
+
{
|
|
1312
|
+
method: "POST",
|
|
1313
|
+
headers: commonHeaders,
|
|
1314
|
+
body: JSON.stringify(body)
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
toolName: "schedule_reminder",
|
|
1318
|
+
fetchImpl: bridgeFetch
|
|
1319
|
+
}
|
|
1320
|
+
);
|
|
1321
|
+
if (!res.ok || !data.reminder) {
|
|
1322
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
|
|
1197
1323
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
return {
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1324
|
+
const lines = [`Reminder scheduled: ${formatReminder(data.reminder)}`];
|
|
1325
|
+
if (data.warning) lines.push(`Warning: ${data.warning}`);
|
|
1326
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1327
|
+
} catch (err) {
|
|
1328
|
+
return {
|
|
1329
|
+
isError: true,
|
|
1330
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1331
|
+
};
|
|
1205
1332
|
}
|
|
1206
|
-
return {
|
|
1207
|
-
content: [
|
|
1208
|
-
{ type: "text", text: list.map(formatReminder).join("\n") }
|
|
1209
|
-
]
|
|
1210
|
-
};
|
|
1211
|
-
} catch (err) {
|
|
1212
|
-
return {
|
|
1213
|
-
isError: true,
|
|
1214
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1215
|
-
};
|
|
1216
1333
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
`${serverUrl}/internal/agent/${agentId}/reminders?status=scheduled`,
|
|
1334
|
+
);
|
|
1335
|
+
server.tool(
|
|
1336
|
+
"list_reminders",
|
|
1337
|
+
"List your own reminders. Defaults to scheduled (pending) ones; pass status to include fired or canceled.",
|
|
1338
|
+
{
|
|
1339
|
+
status: z2.string().optional().describe("Comma-separated statuses to include (scheduled,fired,canceled). Defaults to 'scheduled'.")
|
|
1340
|
+
},
|
|
1341
|
+
async ({ status }) => {
|
|
1342
|
+
try {
|
|
1343
|
+
const qs = new URLSearchParams();
|
|
1344
|
+
qs.set("status", status && status.trim().length > 0 ? status.trim() : "scheduled");
|
|
1345
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1346
|
+
`${serverUrl}/internal/agent/${agentId}/reminders?${qs.toString()}`,
|
|
1231
1347
|
{ method: "GET", headers: commonHeaders },
|
|
1232
1348
|
{
|
|
1233
|
-
toolName: "
|
|
1349
|
+
toolName: "list_reminders",
|
|
1234
1350
|
fetchImpl: bridgeFetch
|
|
1235
1351
|
}
|
|
1236
1352
|
);
|
|
1237
|
-
if (!
|
|
1238
|
-
return { isError: true, content: [{ type: "text", text: `Error: ${
|
|
1353
|
+
if (!res.ok) {
|
|
1354
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
|
|
1239
1355
|
}
|
|
1240
|
-
const
|
|
1241
|
-
if (
|
|
1242
|
-
return {
|
|
1356
|
+
const list = data.reminders ?? [];
|
|
1357
|
+
if (list.length === 0) {
|
|
1358
|
+
return { content: [{ type: "text", text: "No reminders." }] };
|
|
1243
1359
|
}
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1360
|
+
return {
|
|
1361
|
+
content: [
|
|
1362
|
+
{ type: "text", text: list.map(formatReminder).join("\n") }
|
|
1363
|
+
]
|
|
1364
|
+
};
|
|
1365
|
+
} catch (err) {
|
|
1366
|
+
return {
|
|
1367
|
+
isError: true,
|
|
1368
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1369
|
+
};
|
|
1248
1370
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1371
|
+
}
|
|
1372
|
+
);
|
|
1373
|
+
server.tool(
|
|
1374
|
+
"cancel_reminder",
|
|
1375
|
+
"Cancel one of your own scheduled reminders by id. Only reminders in 'scheduled' status can be canceled.",
|
|
1376
|
+
{
|
|
1377
|
+
reminder_id: z2.string().describe("The reminder id (full uuid, or the short 8-char prefix shown by schedule_reminder / list_reminders).")
|
|
1378
|
+
},
|
|
1379
|
+
async ({ reminder_id }) => {
|
|
1380
|
+
try {
|
|
1381
|
+
let fullId = reminder_id;
|
|
1382
|
+
if (reminder_id.length < 32) {
|
|
1383
|
+
const { response: listRes, data: listData } = await executeJsonRequest(
|
|
1384
|
+
`${serverUrl}/internal/agent/${agentId}/reminders?status=scheduled`,
|
|
1385
|
+
{ method: "GET", headers: commonHeaders },
|
|
1386
|
+
{
|
|
1387
|
+
toolName: "cancel_reminder.resolve",
|
|
1388
|
+
fetchImpl: bridgeFetch
|
|
1389
|
+
}
|
|
1390
|
+
);
|
|
1391
|
+
if (!listRes.ok) {
|
|
1392
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${listData.error || listRes.statusText}` }] };
|
|
1393
|
+
}
|
|
1394
|
+
const matches = (listData.reminders ?? []).filter((r) => r.reminderId.startsWith(reminder_id));
|
|
1395
|
+
if (matches.length === 0) {
|
|
1396
|
+
return { isError: true, content: [{ type: "text", text: `No scheduled reminder matches id prefix '${reminder_id}'.` }] };
|
|
1397
|
+
}
|
|
1398
|
+
if (matches.length > 1) {
|
|
1399
|
+
return { isError: true, content: [{ type: "text", text: `Ambiguous id prefix '${reminder_id}' matches ${matches.length} reminders; pass a longer id.` }] };
|
|
1400
|
+
}
|
|
1401
|
+
fullId = matches[0].reminderId;
|
|
1255
1402
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1403
|
+
const { response: res, data } = await executeJsonRequest(
|
|
1404
|
+
`${serverUrl}/internal/agent/${agentId}/reminders/${fullId}`,
|
|
1405
|
+
{ method: "DELETE", headers: commonHeaders },
|
|
1406
|
+
{
|
|
1407
|
+
toolName: "cancel_reminder",
|
|
1408
|
+
fetchImpl: bridgeFetch
|
|
1409
|
+
}
|
|
1410
|
+
);
|
|
1411
|
+
if (!res.ok || !data.reminder) {
|
|
1412
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
|
|
1413
|
+
}
|
|
1414
|
+
return {
|
|
1415
|
+
content: [
|
|
1416
|
+
{ type: "text", text: `Reminder canceled: ${formatReminder(data.reminder)}` }
|
|
1417
|
+
]
|
|
1418
|
+
};
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
return {
|
|
1421
|
+
isError: true,
|
|
1422
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1423
|
+
};
|
|
1259
1424
|
}
|
|
1260
|
-
return {
|
|
1261
|
-
content: [
|
|
1262
|
-
{ type: "text", text: `Reminder canceled: ${formatReminder(data.reminder)}` }
|
|
1263
|
-
]
|
|
1264
|
-
};
|
|
1265
|
-
} catch (err) {
|
|
1266
|
-
return {
|
|
1267
|
-
isError: true,
|
|
1268
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
1269
|
-
};
|
|
1270
1425
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
var formatMessages2;
|
|
1429
|
+
var formatReminder2;
|
|
1273
1430
|
var transport = new StdioServerTransport();
|
|
1274
1431
|
await server.connect(transport);
|