@slock-ai/daemon 0.7.0 → 0.9.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.
@@ -113,7 +113,7 @@ server.tool(
113
113
  );
114
114
  server.tool(
115
115
  "list_server",
116
- "List all channels you are in, all agents, and all humans in this server. Use this to discover who and where you can message.",
116
+ "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.",
117
117
  {},
118
118
  async () => {
119
119
  try {
@@ -123,12 +123,13 @@ server.tool(
123
123
  );
124
124
  const data = await res.json();
125
125
  let text = "## Server\n\n";
126
- text += "### Your Channels\n";
127
- text += "Use `#channel-name` with send_message to post in a channel.\n";
126
+ text += "### Channels\n";
127
+ text += "Use `#channel-name` with send_message to post in a channel. `joined` means you currently belong to that channel.\n";
128
128
  if (data.channels?.length > 0) {
129
129
  for (const t of data.channels) {
130
- text += t.description ? ` - #${t.name} \u2014 ${t.description}
131
- ` : ` - #${t.name}
130
+ const status = t.joined ? "joined" : "not joined";
131
+ text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
132
+ ` : ` - #${t.name} [${status}]
132
133
  `;
133
134
  }
134
135
  } else {
@@ -244,5 +245,214 @@ ${formatted}${footer}`
244
245
  }
245
246
  }
246
247
  );
248
+ server.tool(
249
+ "list_tasks",
250
+ "List tasks on a channel's task board. Returns tasks with their number (#t1, #t2...), title, status, and assignee.",
251
+ {
252
+ channel: z.string().describe("The channel whose task board to view \u2014 e.g. '#engineering', '#proj-slock'"),
253
+ status: z.enum(["all", "open", "claimed", "completed"]).default("all").describe("Filter by status (default: all)")
254
+ },
255
+ async ({ channel, status }) => {
256
+ try {
257
+ const params = new URLSearchParams();
258
+ params.set("channel", channel);
259
+ if (status !== "all") params.set("status", status);
260
+ const res = await fetch(
261
+ `${serverUrl}/internal/agent/${agentId}/tasks?${params}`,
262
+ { method: "GET", headers: commonHeaders }
263
+ );
264
+ const data = await res.json();
265
+ if (!res.ok) {
266
+ return {
267
+ content: [{ type: "text", text: `Error: ${data.error}` }]
268
+ };
269
+ }
270
+ if (!data.tasks || data.tasks.length === 0) {
271
+ return {
272
+ content: [{ type: "text", text: `No${status !== "all" ? ` ${status}` : ""} tasks in ${channel}.` }]
273
+ };
274
+ }
275
+ const formatted = data.tasks.map((t) => {
276
+ const assignee = t.claimedByName ? ` \u2192 @${t.claimedByName}` : "";
277
+ const creator = t.createdByName ? ` (by @${t.createdByName})` : "";
278
+ return `#t${t.taskNumber} [${t.status}] "${t.title}"${assignee}${creator}`;
279
+ }).join("\n");
280
+ return {
281
+ content: [
282
+ {
283
+ type: "text",
284
+ text: `## Task Board for ${channel} (${data.tasks.length} tasks)
285
+
286
+ ${formatted}`
287
+ }
288
+ ]
289
+ };
290
+ } catch (err) {
291
+ return {
292
+ content: [{ type: "text", text: `Error: ${err.message}` }]
293
+ };
294
+ }
295
+ }
296
+ );
297
+ server.tool(
298
+ "create_tasks",
299
+ "Create one or more tasks on a channel's task board. Returns the created task numbers.",
300
+ {
301
+ channel: z.string().describe("The channel to create tasks in \u2014 e.g. '#engineering'"),
302
+ tasks: z.array(
303
+ z.object({
304
+ title: z.string().describe("Task title")
305
+ })
306
+ ).describe("Array of tasks to create")
307
+ },
308
+ async ({ channel, tasks }) => {
309
+ try {
310
+ const res = await fetch(`${serverUrl}/internal/agent/${agentId}/tasks`, {
311
+ method: "POST",
312
+ headers: commonHeaders,
313
+ body: JSON.stringify({ channel, tasks })
314
+ });
315
+ const data = await res.json();
316
+ if (!res.ok) {
317
+ return {
318
+ content: [{ type: "text", text: `Error: ${data.error}` }]
319
+ };
320
+ }
321
+ const created = data.tasks.map((t) => `#t${t.taskNumber} "${t.title}"`).join("\n");
322
+ return {
323
+ content: [
324
+ {
325
+ type: "text",
326
+ text: `Created ${data.tasks.length} task(s) in ${channel}:
327
+ ${created}`
328
+ }
329
+ ]
330
+ };
331
+ } catch (err) {
332
+ return {
333
+ content: [{ type: "text", text: `Error: ${err.message}` }]
334
+ };
335
+ }
336
+ }
337
+ );
338
+ server.tool(
339
+ "claim_tasks",
340
+ "Claim one or more tasks by their number. Returns which claims succeeded and which failed (e.g. already claimed by someone else).",
341
+ {
342
+ channel: z.string().describe("The channel whose tasks to claim \u2014 e.g. '#engineering'"),
343
+ task_numbers: z.array(z.number()).describe("Task numbers to claim (e.g. [1, 3, 5])")
344
+ },
345
+ async ({ channel, task_numbers }) => {
346
+ try {
347
+ const res = await fetch(
348
+ `${serverUrl}/internal/agent/${agentId}/tasks/claim`,
349
+ {
350
+ method: "POST",
351
+ headers: commonHeaders,
352
+ body: JSON.stringify({ channel, task_numbers })
353
+ }
354
+ );
355
+ const data = await res.json();
356
+ if (!res.ok) {
357
+ return {
358
+ content: [{ type: "text", text: `Error: ${data.error}` }]
359
+ };
360
+ }
361
+ const lines = data.results.map((r) => {
362
+ if (r.success) {
363
+ return `#t${r.taskNumber}: claimed`;
364
+ }
365
+ return `#t${r.taskNumber}: FAILED \u2014 ${r.reason || "already claimed"}`;
366
+ });
367
+ const succeeded = data.results.filter((r) => r.success).length;
368
+ const failed = data.results.length - succeeded;
369
+ let summary = `${succeeded} claimed`;
370
+ if (failed > 0) summary += `, ${failed} failed`;
371
+ return {
372
+ content: [
373
+ {
374
+ type: "text",
375
+ text: `Claim results (${summary}):
376
+ ${lines.join("\n")}`
377
+ }
378
+ ]
379
+ };
380
+ } catch (err) {
381
+ return {
382
+ content: [{ type: "text", text: `Error: ${err.message}` }]
383
+ };
384
+ }
385
+ }
386
+ );
387
+ server.tool(
388
+ "unclaim_task",
389
+ "Release your claim on a task, setting it back to open.",
390
+ {
391
+ channel: z.string().describe("The channel \u2014 e.g. '#engineering'"),
392
+ task_number: z.number().describe("The task number to unclaim (e.g. 3)")
393
+ },
394
+ async ({ channel, task_number }) => {
395
+ try {
396
+ const res = await fetch(
397
+ `${serverUrl}/internal/agent/${agentId}/tasks/unclaim`,
398
+ {
399
+ method: "POST",
400
+ headers: commonHeaders,
401
+ body: JSON.stringify({ channel, task_number })
402
+ }
403
+ );
404
+ const data = await res.json();
405
+ if (!res.ok) {
406
+ return {
407
+ content: [{ type: "text", text: `Error: ${data.error}` }]
408
+ };
409
+ }
410
+ return {
411
+ content: [
412
+ { type: "text", text: `#t${task_number} unclaimed \u2014 now open.` }
413
+ ]
414
+ };
415
+ } catch (err) {
416
+ return {
417
+ content: [{ type: "text", text: `Error: ${err.message}` }]
418
+ };
419
+ }
420
+ }
421
+ );
422
+ server.tool(
423
+ "complete_task",
424
+ "Mark a task as completed. You must be the current claimer.",
425
+ {
426
+ channel: z.string().describe("The channel \u2014 e.g. '#engineering'"),
427
+ task_number: z.number().describe("The task number to complete (e.g. 3)")
428
+ },
429
+ async ({ channel, task_number }) => {
430
+ try {
431
+ const res = await fetch(
432
+ `${serverUrl}/internal/agent/${agentId}/tasks/complete`,
433
+ {
434
+ method: "POST",
435
+ headers: commonHeaders,
436
+ body: JSON.stringify({ channel, task_number })
437
+ }
438
+ );
439
+ const data = await res.json();
440
+ if (!res.ok) {
441
+ return {
442
+ content: [{ type: "text", text: `Error: ${data.error}` }]
443
+ };
444
+ }
445
+ return {
446
+ content: [
447
+ { type: "text", text: `#t${task_number} marked as completed.` }
448
+ ]
449
+ };
450
+ } catch (err) {
451
+ return {
452
+ content: [{ type: "text", text: `Error: ${err.message}` }]
453
+ };
454
+ }
455
+ }
456
+ );
247
457
  var transport = new StdioServerTransport();
248
458
  await server.connect(transport);
package/dist/index.js CHANGED
@@ -120,8 +120,13 @@ You have MCP tools from the "chat" server. Use ONLY these for communication:
120
120
 
121
121
  1. **${t("receive_message")}** \u2014 Call with block=true to wait for messages. This is your main loop.
122
122
  2. **${t("send_message")}** \u2014 Send a message to a channel or DM.
123
- 3. **${t("list_server")}** \u2014 List all channels, agents, and humans in this server.
123
+ 3. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
124
124
  4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
125
+ 5. **${t("list_tasks")}** \u2014 View a channel's task board.
126
+ 6. **${t("create_tasks")}** \u2014 Create tasks on a channel's task board (supports batch).
127
+ 7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
128
+ 8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
129
+ 9. **${t("complete_task")}** \u2014 Mark a task as done.
125
130
 
126
131
  CRITICAL RULES:
127
132
  ${criticalRules.join("\n")}
@@ -155,7 +160,7 @@ The \`[...]\` prefix identifies where the message came from. Reuse it as the \`c
155
160
 
156
161
  ### Discovering people and channels
157
162
 
158
- Call \`list_server\` to see all your channels, other agents, and humans in this server.
163
+ Call \`list_server\` to see all channels in this server, which ones you have joined, other agents, and humans.
159
164
 
160
165
  ### Channel awareness
161
166
 
@@ -168,6 +173,27 @@ Each channel has a **name** and optionally a **description** that define its pur
168
173
 
169
174
  \`read_history(channel="#channel-name")\` or \`read_history(channel="DM:@peer-name")\`
170
175
 
176
+ ### Task boards
177
+
178
+ Each channel has a task board where humans and agents can create, claim, and complete tasks. Tasks are numbered per channel (#t1, #t2, ...) for easy reference.
179
+
180
+ - **View tasks**: \`list_tasks(channel="#channel-name")\` \u2014 see all tasks with status and assignee.
181
+ - **Create tasks**: \`create_tasks(channel="#channel-name", tasks=[{title: "..."}, ...])\` \u2014 create one or more tasks. Useful for breaking down a large task into subtasks.
182
+ - **Claim tasks**: \`claim_tasks(channel="#channel-name", task_numbers=[1, 3])\` \u2014 claim tasks you want to work on. If another agent already claimed a task, your claim for that task fails (but others in the batch may succeed). Check the result to see which succeeded.
183
+ - **Unclaim**: \`unclaim_task(channel="#channel-name", task_number=3)\` \u2014 release a task back to open.
184
+ - **Complete**: \`complete_task(channel="#channel-name", task_number=3)\` \u2014 mark a task as done.
185
+
186
+ **CRITICAL: You MUST claim a task before starting work on it.** Never begin working on a task without claiming it first. The claim mechanism prevents multiple agents from doing the same work. If your claim fails (someone else claimed it), move on to another task.
187
+
188
+ ### Splitting tasks for parallel execution
189
+
190
+ When you need to break down a large task into subtasks, structure them so agents can work **in parallel**:
191
+ - **Group by phase** if tasks have dependencies. Label them clearly (e.g. "Phase 1: ...", "Phase 2: ...") so agents know what can run concurrently and what must wait.
192
+ - **Prefer independent subtasks** that don't block each other. Each subtask should be completable without waiting for another.
193
+ - **Avoid creating sequential chains** where each task depends on the previous one \u2014 this forces agents to work one at a time, wasting capacity.
194
+
195
+ When you receive a notification about new tasks, check the task board and claim tasks relevant to your skills.
196
+
171
197
  ## @Mentions
172
198
 
173
199
  In channel group chats, you can @mention people by their unique name (e.g. "@alice" or "@bob").
@@ -182,6 +208,20 @@ Keep the user informed. They cannot see your internal reasoning, so:
182
208
  - When done, summarize the result.
183
209
  - Keep updates concise \u2014 one or two sentences. Don't flood the chat.
184
210
 
211
+ ### Conversation etiquette
212
+
213
+ - **Don't interrupt ongoing conversations.** If a human is having a back-and-forth with another person (human or agent) on a topic, their follow-up messages are directed at that person \u2014 not at you. Do NOT jump in unless you are explicitly @mentioned or clearly addressed.
214
+ - **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work \u2014 let them respond to questions about it.
215
+ - **Claim before you start.** When picking up a task, announce it in the channel first to avoid duplicate work by others.
216
+
217
+ ### Formatting \u2014 URLs in non-English text
218
+
219
+ When writing a URL next to non-ASCII punctuation (Chinese, Japanese, etc.), always wrap the URL in angle brackets or use markdown link syntax. Otherwise the punctuation may be rendered as part of the URL.
220
+
221
+ - **Wrong**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1Ahttp://localhost:3000\uFF0C\u8BF7\u67E5\u770B\` (the \`\uFF0C\` gets swallowed into the link)
222
+ - **Correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A<http://localhost:3000>\uFF0C\u8BF7\u67E5\u770B\`
223
+ - **Also correct**: \`\u6D4B\u8BD5\u73AF\u5883\uFF1A[http://localhost:3000](http://localhost:3000)\uFF0C\u8BF7\u67E5\u770B\`
224
+
185
225
  ## Workspace & Memory
186
226
 
187
227
  Your working directory (cwd) is your **persistent workspace**. Everything you write here survives across sessions.
@@ -382,6 +422,11 @@ var ClaudeDriver = class {
382
422
  });
383
423
  }
384
424
  toolDisplayName(name) {
425
+ if (name === "mcp__chat__list_tasks") return "Viewing task board\u2026";
426
+ if (name === "mcp__chat__create_tasks") return "Creating tasks\u2026";
427
+ if (name === "mcp__chat__claim_tasks") return "Claiming tasks\u2026";
428
+ if (name === "mcp__chat__unclaim_task") return "Unclaiming task\u2026";
429
+ if (name === "mcp__chat__complete_task") return "Completing task\u2026";
385
430
  if (name.startsWith("mcp__chat__")) return "";
386
431
  if (name === "Read" || name === "read_file") return "Reading file\u2026";
387
432
  if (name === "Write" || name === "write_file") return "Writing file\u2026";
@@ -412,6 +457,15 @@ var ClaudeDriver = class {
412
457
  return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
413
458
  }
414
459
  if (name === "mcp__chat__read_history") return input.channel || "";
460
+ if (name === "mcp__chat__list_tasks") return input.channel || "";
461
+ if (name === "mcp__chat__create_tasks") return input.channel || "";
462
+ if (name === "mcp__chat__claim_tasks") {
463
+ const nums = input.task_numbers;
464
+ return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
465
+ }
466
+ if (name === "mcp__chat__unclaim_task" || name === "mcp__chat__complete_task") {
467
+ return input.channel ? `${input.channel} #t${input.task_number}` : "";
468
+ }
415
469
  return "";
416
470
  } catch {
417
471
  return "";
@@ -584,6 +638,11 @@ var CodexDriver = class {
584
638
  });
585
639
  }
586
640
  toolDisplayName(name) {
641
+ if (name === `${this.mcpToolPrefix}list_tasks`) return "Viewing task board\u2026";
642
+ if (name === `${this.mcpToolPrefix}create_tasks`) return "Creating tasks\u2026";
643
+ if (name === `${this.mcpToolPrefix}claim_tasks`) return "Claiming tasks\u2026";
644
+ if (name === `${this.mcpToolPrefix}unclaim_task`) return "Unclaiming task\u2026";
645
+ if (name === `${this.mcpToolPrefix}complete_task`) return "Completing task\u2026";
587
646
  if (name.startsWith(this.mcpToolPrefix)) return "";
588
647
  if (name === "shell" || name === "command_execution") return "Running command\u2026";
589
648
  if (name === "file_change") return "Editing file\u2026";
@@ -608,6 +667,15 @@ var CodexDriver = class {
608
667
  return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
609
668
  }
610
669
  if (name === `${this.mcpToolPrefix}read_history`) return input.channel || "";
670
+ if (name === `${this.mcpToolPrefix}list_tasks`) return input.channel || "";
671
+ if (name === `${this.mcpToolPrefix}create_tasks`) return input.channel || "";
672
+ if (name === `${this.mcpToolPrefix}claim_tasks`) {
673
+ const nums = input.task_numbers;
674
+ return input.channel ? `${input.channel} #t${Array.isArray(nums) ? nums.join(",#t") : nums}` : "";
675
+ }
676
+ if (name === `${this.mcpToolPrefix}unclaim_task` || name === `${this.mcpToolPrefix}complete_task`) {
677
+ return input.channel ? `${input.channel} #t${input.task_number}` : "";
678
+ }
611
679
  return "";
612
680
  } catch {
613
681
  return "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"