@slock-ai/daemon 0.14.0 → 0.17.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 +150 -5
- package/dist/index.js +23 -6
- package/package.json +1 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -51,14 +51,15 @@ server.tool(
|
|
|
51
51
|
target: z.string().describe(
|
|
52
52
|
"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'."
|
|
53
53
|
),
|
|
54
|
-
content: z.string().describe("The message content")
|
|
54
|
+
content: z.string().describe("The message content"),
|
|
55
|
+
attachment_ids: z.array(z.string()).optional().describe("Optional attachment IDs from upload_file to include with the message")
|
|
55
56
|
},
|
|
56
|
-
async ({ target, content }) => {
|
|
57
|
+
async ({ target, content, attachment_ids }) => {
|
|
57
58
|
try {
|
|
58
59
|
const res = await fetch(`${serverUrl}/internal/agent/${agentId}/send`, {
|
|
59
60
|
method: "POST",
|
|
60
61
|
headers: commonHeaders,
|
|
61
|
-
body: JSON.stringify({ target, content })
|
|
62
|
+
body: JSON.stringify({ target, content, attachmentIds: attachment_ids })
|
|
62
63
|
});
|
|
63
64
|
const data = await res.json();
|
|
64
65
|
if (!res.ok) {
|
|
@@ -85,6 +86,148 @@ server.tool(
|
|
|
85
86
|
}
|
|
86
87
|
}
|
|
87
88
|
);
|
|
89
|
+
server.tool(
|
|
90
|
+
"upload_file",
|
|
91
|
+
"Upload an image file to attach to a message. Returns an attachment ID that you can pass to send_message's attachment_ids parameter. Supported formats: JPEG, PNG, GIF, WebP. Max size: 5MB.",
|
|
92
|
+
{
|
|
93
|
+
file_path: z.string().describe("Absolute path to the image file on your local filesystem"),
|
|
94
|
+
channel: z.string().describe("The channel target where this file will be used (e.g. '#general', 'dm:@richard')")
|
|
95
|
+
},
|
|
96
|
+
async ({ file_path, channel }) => {
|
|
97
|
+
try {
|
|
98
|
+
const fs = await import("fs");
|
|
99
|
+
const path = await import("path");
|
|
100
|
+
if (!fs.existsSync(file_path)) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: "text", text: `Error: File not found: ${file_path}` }]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const stat = fs.statSync(file_path);
|
|
106
|
+
if (stat.size > 5 * 1024 * 1024) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: "text", text: `Error: File too large (${(stat.size / 1024 / 1024).toFixed(1)}MB). Max 5MB.` }]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const listRes = await fetch(`${serverUrl}/internal/agent/${agentId}/resolve-channel`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: commonHeaders,
|
|
114
|
+
body: JSON.stringify({ target: channel })
|
|
115
|
+
});
|
|
116
|
+
let channelId;
|
|
117
|
+
if (listRes.ok) {
|
|
118
|
+
const listData = await listRes.json();
|
|
119
|
+
channelId = listData.channelId;
|
|
120
|
+
} else {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: `Error: Could not resolve channel: ${channel}` }]
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const fileBuffer = fs.readFileSync(file_path);
|
|
126
|
+
const filename = path.basename(file_path);
|
|
127
|
+
const ext = path.extname(file_path).toLowerCase();
|
|
128
|
+
const mimeMap = {
|
|
129
|
+
".jpg": "image/jpeg",
|
|
130
|
+
".jpeg": "image/jpeg",
|
|
131
|
+
".png": "image/png",
|
|
132
|
+
".gif": "image/gif",
|
|
133
|
+
".webp": "image/webp"
|
|
134
|
+
};
|
|
135
|
+
const mimeType = mimeMap[ext] || "application/octet-stream";
|
|
136
|
+
const blob = new Blob([fileBuffer], { type: mimeType });
|
|
137
|
+
const formData = new FormData();
|
|
138
|
+
formData.append("file", blob, filename);
|
|
139
|
+
formData.append("channelId", channelId);
|
|
140
|
+
const uploadHeaders = {};
|
|
141
|
+
if (authToken) {
|
|
142
|
+
uploadHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
143
|
+
}
|
|
144
|
+
const res = await fetch(`${serverUrl}/internal/agent/${agentId}/upload`, {
|
|
145
|
+
method: "POST",
|
|
146
|
+
headers: uploadHeaders,
|
|
147
|
+
body: formData
|
|
148
|
+
});
|
|
149
|
+
const data = await res.json();
|
|
150
|
+
if (!res.ok) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: `Error: ${data.error}` }]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
content: [
|
|
157
|
+
{
|
|
158
|
+
type: "text",
|
|
159
|
+
text: `File uploaded: ${data.filename} (${(data.sizeBytes / 1024).toFixed(1)}KB)
|
|
160
|
+
Attachment ID: ${data.id}
|
|
161
|
+
|
|
162
|
+
Use this ID in send_message's attachment_ids parameter to include it in a message.`
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
};
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
);
|
|
173
|
+
server.tool(
|
|
174
|
+
"view_file",
|
|
175
|
+
"Download an attached image by its attachment ID and save it locally so you can view it. Returns the local file path. Use this when you see '[use view_file to see]' in a message with images.",
|
|
176
|
+
{
|
|
177
|
+
attachment_id: z.string().describe("The attachment UUID (from the 'id:...' shown in the message)")
|
|
178
|
+
},
|
|
179
|
+
async ({ attachment_id }) => {
|
|
180
|
+
try {
|
|
181
|
+
const fs = await import("fs");
|
|
182
|
+
const path = await import("path");
|
|
183
|
+
const os = await import("os");
|
|
184
|
+
const cacheDir = path.join(os.homedir(), ".slock", "attachments");
|
|
185
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
186
|
+
const existing = fs.readdirSync(cacheDir).find((f) => f.startsWith(attachment_id));
|
|
187
|
+
if (existing) {
|
|
188
|
+
const cachedPath = path.join(cacheDir, existing);
|
|
189
|
+
return {
|
|
190
|
+
content: [{ type: "text", text: `File already cached at: ${cachedPath}
|
|
191
|
+
|
|
192
|
+
Use your Read tool to view this image.` }]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const downloadHeaders = {};
|
|
196
|
+
if (authToken) {
|
|
197
|
+
downloadHeaders["Authorization"] = `Bearer ${authToken}`;
|
|
198
|
+
}
|
|
199
|
+
const res = await fetch(`${serverUrl}/api/attachments/${attachment_id}`, {
|
|
200
|
+
headers: downloadHeaders,
|
|
201
|
+
redirect: "follow"
|
|
202
|
+
});
|
|
203
|
+
if (!res.ok) {
|
|
204
|
+
return {
|
|
205
|
+
content: [{ type: "text", text: `Error: Failed to download attachment (${res.status})` }]
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const contentType = res.headers.get("content-type") || "application/octet-stream";
|
|
209
|
+
const extMap = {
|
|
210
|
+
"image/jpeg": ".jpg",
|
|
211
|
+
"image/png": ".png",
|
|
212
|
+
"image/gif": ".gif",
|
|
213
|
+
"image/webp": ".webp"
|
|
214
|
+
};
|
|
215
|
+
const ext = extMap[contentType] || ".bin";
|
|
216
|
+
const filePath = path.join(cacheDir, `${attachment_id}${ext}`);
|
|
217
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
218
|
+
fs.writeFileSync(filePath, buffer);
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: "text", text: `Downloaded to: ${filePath}
|
|
221
|
+
|
|
222
|
+
Use your Read tool to view this image.` }]
|
|
223
|
+
};
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return {
|
|
226
|
+
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
);
|
|
88
231
|
server.tool(
|
|
89
232
|
"receive_message",
|
|
90
233
|
"Receive new messages. Use block=true to wait for new messages. Returns messages formatted as [#channel], [dm:@peer], or [thread:#channel:id] followed by the sender and content.",
|
|
@@ -112,7 +255,8 @@ server.tool(
|
|
|
112
255
|
const msgId = m.message_id ? m.message_id.slice(0, 8) : "-";
|
|
113
256
|
const time = m.timestamp ? toLocalTime(m.timestamp) : "-";
|
|
114
257
|
const senderType = m.sender_type === "agent" ? " type=agent" : "";
|
|
115
|
-
|
|
258
|
+
const attachSuffix = m.attachments?.length ? ` [${m.attachments.length} image${m.attachments.length > 1 ? "s" : ""}: ${m.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
|
|
259
|
+
return `[target=${target} msg=${msgId} time=${time}${senderType}] @${m.sender_name}: ${m.content}${attachSuffix}`;
|
|
116
260
|
}).join("\n");
|
|
117
261
|
return {
|
|
118
262
|
content: [{ type: "text", text: formatted }]
|
|
@@ -217,7 +361,8 @@ server.tool(
|
|
|
217
361
|
const senderType = m.senderType === "agent" ? " type=agent" : "";
|
|
218
362
|
const time = m.createdAt ? toLocalTime(m.createdAt) : "-";
|
|
219
363
|
const msgId = m.id ? m.id.slice(0, 8) : "-";
|
|
220
|
-
|
|
364
|
+
const attachSuffix = m.attachments?.length ? ` [${m.attachments.length} image${m.attachments.length > 1 ? "s" : ""}: ${m.attachments.map((a) => `${a.filename} (id:${a.id})`).join(", ")} \u2014 use view_file to see]` : "";
|
|
365
|
+
return `[seq=${m.seq} msg=${msgId} time=${time}${senderType}] @${m.senderName}: ${m.content}${attachSuffix}`;
|
|
221
366
|
}).join("\n");
|
|
222
367
|
let footer = "";
|
|
223
368
|
if (data.historyLimited) {
|
package/dist/index.js
CHANGED
|
@@ -129,6 +129,7 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
|
129
129
|
7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
|
|
130
130
|
8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
|
|
131
131
|
9. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
|
|
132
|
+
10. **${t("upload_file")}** \u2014 Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
|
|
132
133
|
|
|
133
134
|
CRITICAL RULES:
|
|
134
135
|
${criticalRules.join("\n")}
|
|
@@ -465,11 +466,15 @@ var ClaudeDriver = class {
|
|
|
465
466
|
});
|
|
466
467
|
}
|
|
467
468
|
toolDisplayName(name) {
|
|
468
|
-
if (name === "
|
|
469
|
+
if (name === "mcp__chat__upload_file") return "Uploading file\u2026";
|
|
470
|
+
if (name === "mcp__chat__view_file") return "Viewing file\u2026";
|
|
471
|
+
if (name === "mcp__chat__list_tasks") return "Listing tasks\u2026";
|
|
469
472
|
if (name === "mcp__chat__create_tasks") return "Creating tasks\u2026";
|
|
470
473
|
if (name === "mcp__chat__claim_tasks") return "Claiming tasks\u2026";
|
|
471
474
|
if (name === "mcp__chat__unclaim_task") return "Unclaiming task\u2026";
|
|
472
|
-
if (name === "
|
|
475
|
+
if (name === "mcp__chat__update_task_status") return "Updating task status\u2026";
|
|
476
|
+
if (name === "mcp__chat__list_server") return "Listing server\u2026";
|
|
477
|
+
if (name === "mcp__chat__read_history") return "Reading history\u2026";
|
|
473
478
|
if (name.startsWith("mcp__chat__")) return "";
|
|
474
479
|
if (name === "Read" || name === "read_file") return "Reading file\u2026";
|
|
475
480
|
if (name === "Write" || name === "write_file") return "Writing file\u2026";
|
|
@@ -506,9 +511,13 @@ var ClaudeDriver = class {
|
|
|
506
511
|
const nums = input.task_numbers;
|
|
507
512
|
return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
|
|
508
513
|
}
|
|
509
|
-
if (name === "mcp__chat__unclaim_task"
|
|
514
|
+
if (name === "mcp__chat__unclaim_task") {
|
|
510
515
|
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
511
516
|
}
|
|
517
|
+
if (name === "mcp__chat__update_task_status") {
|
|
518
|
+
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
519
|
+
}
|
|
520
|
+
if (name === "mcp__chat__upload_file") return input.file_path || "";
|
|
512
521
|
return "";
|
|
513
522
|
} catch {
|
|
514
523
|
return "";
|
|
@@ -682,11 +691,15 @@ var CodexDriver = class {
|
|
|
682
691
|
});
|
|
683
692
|
}
|
|
684
693
|
toolDisplayName(name) {
|
|
685
|
-
if (name === `${this.mcpToolPrefix}
|
|
694
|
+
if (name === `${this.mcpToolPrefix}upload_file`) return "Uploading file\u2026";
|
|
695
|
+
if (name === `${this.mcpToolPrefix}view_file`) return "Viewing file\u2026";
|
|
696
|
+
if (name === `${this.mcpToolPrefix}list_tasks`) return "Listing tasks\u2026";
|
|
686
697
|
if (name === `${this.mcpToolPrefix}create_tasks`) return "Creating tasks\u2026";
|
|
687
698
|
if (name === `${this.mcpToolPrefix}claim_tasks`) return "Claiming tasks\u2026";
|
|
688
699
|
if (name === `${this.mcpToolPrefix}unclaim_task`) return "Unclaiming task\u2026";
|
|
689
|
-
if (name === `${this.mcpToolPrefix}
|
|
700
|
+
if (name === `${this.mcpToolPrefix}update_task_status`) return "Updating task status\u2026";
|
|
701
|
+
if (name === `${this.mcpToolPrefix}list_server`) return "Listing server\u2026";
|
|
702
|
+
if (name === `${this.mcpToolPrefix}read_history`) return "Reading history\u2026";
|
|
690
703
|
if (name.startsWith(this.mcpToolPrefix)) return "";
|
|
691
704
|
if (name === "shell" || name === "command_execution") return "Running command\u2026";
|
|
692
705
|
if (name === "file_change") return "Editing file\u2026";
|
|
@@ -717,9 +730,13 @@ var CodexDriver = class {
|
|
|
717
730
|
const nums = input.task_numbers;
|
|
718
731
|
return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
|
|
719
732
|
}
|
|
720
|
-
if (name === `${this.mcpToolPrefix}unclaim_task`
|
|
733
|
+
if (name === `${this.mcpToolPrefix}unclaim_task`) {
|
|
734
|
+
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
735
|
+
}
|
|
736
|
+
if (name === `${this.mcpToolPrefix}update_task_status`) {
|
|
721
737
|
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
722
738
|
}
|
|
739
|
+
if (name === `${this.mcpToolPrefix}upload_file`) return input.file_path || "";
|
|
723
740
|
return "";
|
|
724
741
|
} catch {
|
|
725
742
|
return "";
|