@slock-ai/daemon 0.14.0 → 0.16.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 +3 -0
- 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")}
|
|
@@ -509,6 +510,7 @@ var ClaudeDriver = class {
|
|
|
509
510
|
if (name === "mcp__chat__unclaim_task" || name === "mcp__chat__complete_task") {
|
|
510
511
|
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
511
512
|
}
|
|
513
|
+
if (name === "mcp__chat__upload_file") return input.file_path || "";
|
|
512
514
|
return "";
|
|
513
515
|
} catch {
|
|
514
516
|
return "";
|
|
@@ -720,6 +722,7 @@ var CodexDriver = class {
|
|
|
720
722
|
if (name === `${this.mcpToolPrefix}unclaim_task` || name === `${this.mcpToolPrefix}complete_task`) {
|
|
721
723
|
return input.channel ? `${input.channel} #t${input.task_number}` : "";
|
|
722
724
|
}
|
|
725
|
+
if (name === `${this.mcpToolPrefix}upload_file`) return input.file_path || "";
|
|
723
726
|
return "";
|
|
724
727
|
} catch {
|
|
725
728
|
return "";
|