@slock-ai/daemon 0.11.0 → 0.13.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.
@@ -27,28 +27,38 @@ var commonHeaders = { "Content-Type": "application/json" };
27
27
  if (authToken) {
28
28
  commonHeaders["Authorization"] = `Bearer ${authToken}`;
29
29
  }
30
+ function formatTarget(m) {
31
+ if (m.channel_type === "thread" && m.parent_channel_name) {
32
+ const shortId = m.channel_name.startsWith("thread-") ? m.channel_name.slice(7) : m.channel_name;
33
+ if (m.parent_channel_type === "dm") {
34
+ return `dm:@${m.parent_channel_name}:${shortId}`;
35
+ }
36
+ return `#${m.parent_channel_name}:${shortId}`;
37
+ }
38
+ if (m.channel_type === "dm") {
39
+ return `dm:@${m.channel_name}`;
40
+ }
41
+ return `#${m.channel_name}`;
42
+ }
30
43
  var server = new McpServer({
31
44
  name: "chat",
32
45
  version: "1.0.0"
33
46
  });
34
47
  server.tool(
35
48
  "send_message",
36
- "Send a message to a channel or DM. To reply, reuse the channel value from the received message (e.g. channel='#all' or channel='DM:@richard'). To start a NEW DM, use dm_to with the person's name.",
49
+ "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'.",
37
50
  {
38
- channel: z.string().optional().describe(
39
- "Where to send. Reuse the identifier from received messages: '#channel-name' for channels, 'DM:@peer-name' for DMs. Examples: '#all', '#general', 'DM:@richard'."
40
- ),
41
- dm_to: z.string().optional().describe(
42
- "Person's name to start a NEW DM with (e.g. 'richard'). Only for starting a new DM \u2014 to reply in an existing DM, use channel instead."
51
+ target: z.string().describe(
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'."
43
53
  ),
44
54
  content: z.string().describe("The message content")
45
55
  },
46
- async ({ channel, dm_to, content }) => {
56
+ async ({ target, content }) => {
47
57
  try {
48
58
  const res = await fetch(`${serverUrl}/internal/agent/${agentId}/send`, {
49
59
  method: "POST",
50
60
  headers: commonHeaders,
51
- body: JSON.stringify({ channel, dm_to, content })
61
+ body: JSON.stringify({ target, content })
52
62
  });
53
63
  const data = await res.json();
54
64
  if (!res.ok) {
@@ -62,7 +72,7 @@ server.tool(
62
72
  content: [
63
73
  {
64
74
  type: "text",
65
- text: `Message sent to ${channel || `new DM with ${dm_to}`}`
75
+ text: `Message sent to ${target}`
66
76
  }
67
77
  ]
68
78
  };
@@ -75,7 +85,7 @@ server.tool(
75
85
  );
76
86
  server.tool(
77
87
  "receive_message",
78
- "Receive new messages. Use block=true to wait for new messages. Returns messages formatted as [#channel-name] or [DM:@peer-name] followed by the sender and content.",
88
+ "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.",
79
89
  {
80
90
  block: z.boolean().default(true).describe("Whether to block (wait) for new messages"),
81
91
  timeout_ms: z.number().default(59e3).describe("How long to wait in ms when blocking (default 59s, just under MCP tool call timeout)")
@@ -96,10 +106,10 @@ server.tool(
96
106
  };
97
107
  }
98
108
  const formatted = data.messages.map((m) => {
99
- const channel = m.channel_type === "dm" ? `DM:@${m.channel_name}` : `#${m.channel_name}`;
109
+ const target = formatTarget(m);
100
110
  const senderPrefix = m.sender_type === "agent" ? "(agent) " : "";
101
111
  const time = m.timestamp ? ` (${toLocalTime(m.timestamp)})` : "";
102
- return `[${channel}]${time} ${senderPrefix}@${m.sender_name}: ${m.content}`;
112
+ return `[${target}]${time} ${senderPrefix}@${m.sender_name}: ${m.content}`;
103
113
  }).join("\n");
104
114
  return {
105
115
  content: [{ type: "text", text: formatted }]
@@ -146,7 +156,7 @@ server.tool(
146
156
  text += " (none)\n";
147
157
  }
148
158
  text += "\n### Humans\n";
149
- text += 'To start a new DM: send_message(dm_to="<name>"). To reply in an existing DM: reuse channel from the received message.\n';
159
+ text += 'To start a new DM: send_message(target="dm:@name"). To reply in an existing DM: reuse the target from received messages.\n';
150
160
  if (data.humans?.length > 0) {
151
161
  for (const u of data.humans) {
152
162
  text += ` - @${u.name}
@@ -167,9 +177,9 @@ server.tool(
167
177
  );
168
178
  server.tool(
169
179
  "read_history",
170
- "Read message history for a channel or DM. Use #channel-name for channels or DM:@name for DMs. Supports pagination: use 'before' to load older messages, 'after' to load messages after a seq number (e.g. to catch up on unread).",
180
+ "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: use 'before' to load older messages, 'after' to load messages after a seq number (e.g. to catch up on unread).",
171
181
  {
172
- channel: z.string().describe("The channel to read history from \u2014 e.g. '#all', '#general', 'DM:@richard'"),
182
+ channel: z.string().describe("The target to read history from \u2014 e.g. '#general', 'dm:@richard', '#general:abcd1234', 'dm:@richard:abcd1234'"),
173
183
  limit: z.number().default(50).describe("Max number of messages to return (default 50, max 100)"),
174
184
  before: z.number().optional().describe("Return messages before this seq number (for backward pagination). Omit for latest messages."),
175
185
  after: z.number().optional().describe("Return messages after this seq number (for catching up on unread). Returns oldest-first.")
package/dist/index.js CHANGED
@@ -144,19 +144,31 @@ ${opts.postStartupNotes.join("\n")}`;
144
144
  ## Messaging
145
145
 
146
146
  Messages you receive look like:
147
- - **Channel message from a human**: \`[#all] @richard: hello everyone\`
148
- - **Channel message from an agent**: \`[#all] (agent) @Alice: hi there\`
149
- - **DM from a human**: \`[DM:@richard] @richard: hey, can you help?\`
147
+ - **Channel message**: \`[#general] @richard: hello everyone\`
148
+ - **Channel message from an agent**: \`[#general] (agent) @Alice: hi there\`
149
+ - **DM**: \`[dm:@richard] @richard: hey, can you help?\`
150
+ - **Thread in a channel**: \`[#general:a1b2c3] @richard: replying in thread\`
151
+ - **Thread in a DM**: \`[dm:@richard:x9y8z7] @richard: thread reply\`
150
152
 
151
- The \`[...]\` prefix identifies where the message came from. Reuse it as the \`channel\` parameter when replying.
153
+ The \`[...]\` prefix is the **target** \u2014 it identifies where the message came from. Reuse it exactly as the \`target\` parameter when replying.
152
154
 
153
155
  ### Sending messages
154
156
 
155
- - **Reply to a channel**: \`send_message(channel="#channel-name", content="...")\`
156
- - **Reply to a DM**: \`send_message(channel="DM:@peer-name", content="...")\` \u2014 reuse the channel value from the received message
157
- - **Start a NEW DM**: \`send_message(dm_to="peer-name", content="...")\` \u2014 use the human's name from list_server (no @ prefix)
157
+ - **Reply to a channel**: \`send_message(target="#channel-name", content="...")\`
158
+ - **Reply to a DM**: \`send_message(target="dm:@peer-name", content="...")\`
159
+ - **Reply in a thread**: \`send_message(target="#channel:shortid", content="...")\` or \`send_message(target="dm:@peer:shortid", content="...")\`
160
+ - **Start a NEW DM**: \`send_message(target="dm:@person-name", content="...")\`
158
161
 
159
- **IMPORTANT**: To reply to any message (channel or DM), always use \`channel\` with the exact identifier from the received message. Only use \`dm_to\` when you want to start a brand new DM that doesn't exist yet.
162
+ **IMPORTANT**: To reply to any message, always reuse the exact \`target\` from the received message. This ensures your reply goes to the right place \u2014 whether it's a channel, DM, or thread.
163
+
164
+ ### Threads
165
+
166
+ Threads are sub-conversations attached to a specific message. They let you discuss a topic without cluttering the main channel.
167
+
168
+ - **Thread targets** have a colon and short ID suffix: \`#general:a1b2c3\` (thread in #general) or \`dm:@richard:x9y8z7\` (thread in a DM).
169
+ - When you receive a message from a thread (the target has a \`:shortid\` suffix), **always reply using that same target** to keep the conversation in the thread.
170
+ - You can read thread history: \`read_history(channel="#general:a1b2c3")\`
171
+ - Threads cannot be nested \u2014 you cannot start a thread inside a thread.
160
172
 
161
173
  ### Discovering people and channels
162
174
 
@@ -165,13 +177,13 @@ Call \`list_server\` to see all channels in this server, which ones you have joi
165
177
  ### Channel awareness
166
178
 
167
179
  Each channel has a **name** and optionally a **description** that define its purpose (visible via \`list_server\`). Respect them:
168
- - **Reply in context** \u2014 always respond in the channel the message came from.
180
+ - **Reply in context** \u2014 always respond in the channel/thread the message came from.
169
181
  - **Stay on topic** \u2014 when proactively sharing results or updates, post in the channel most relevant to the work. Don't scatter messages across unrelated channels.
170
182
  - If unsure where something belongs, call \`list_server\` to review channel descriptions.
171
183
 
172
184
  ### Reading history
173
185
 
174
- \`read_history(channel="#channel-name")\` or \`read_history(channel="DM:@peer-name")\`
186
+ \`read_history(channel="#channel-name")\` or \`read_history(channel="dm:@peer-name")\` or \`read_history(channel="#channel:shortid")\`
175
187
 
176
188
  ### Task boards
177
189
 
@@ -369,7 +381,8 @@ var ClaudeDriver = class {
369
381
  const proc = spawn("claude", args2, {
370
382
  cwd: ctx.workingDirectory,
371
383
  stdio: ["pipe", "pipe", "pipe"],
372
- env: spawnEnv
384
+ env: spawnEnv,
385
+ shell: process.platform === "win32"
373
386
  });
374
387
  const stdinMsg = JSON.stringify({
375
388
  type: "user",
@@ -471,9 +484,9 @@ var ClaudeDriver = class {
471
484
  if (name === "WebFetch" || name === "web_fetch") return input.url || "";
472
485
  if (name === "WebSearch" || name === "web_search") return input.query || "";
473
486
  if (name === "mcp__chat__send_message") {
474
- return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
487
+ return input.target || input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
475
488
  }
476
- if (name === "mcp__chat__read_history") return input.channel || "";
489
+ if (name === "mcp__chat__read_history") return input.target || input.channel || "";
477
490
  if (name === "mcp__chat__list_tasks") return input.channel || "";
478
491
  if (name === "mcp__chat__create_tasks") return input.channel || "";
479
492
  if (name === "mcp__chat__claim_tasks") {
@@ -544,7 +557,8 @@ var CodexDriver = class {
544
557
  const proc = spawn2("codex", args2, {
545
558
  cwd: ctx.workingDirectory,
546
559
  stdio: ["pipe", "pipe", "pipe"],
547
- env: spawnEnv
560
+ env: spawnEnv,
561
+ shell: process.platform === "win32"
548
562
  });
549
563
  return { process: proc };
550
564
  }
@@ -681,9 +695,9 @@ var CodexDriver = class {
681
695
  if (name === "file_write") return input.path || input.file_path || "";
682
696
  if (name === "web_search") return input.query || "";
683
697
  if (name === `${this.mcpToolPrefix}send_message`) {
684
- return input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
698
+ return input.target || input.channel || (input.dm_to ? `DM:@${input.dm_to}` : "");
685
699
  }
686
- if (name === `${this.mcpToolPrefix}read_history`) return input.channel || "";
700
+ if (name === `${this.mcpToolPrefix}read_history`) return input.target || input.channel || "";
687
701
  if (name === `${this.mcpToolPrefix}list_tasks`) return input.channel || "";
688
702
  if (name === `${this.mcpToolPrefix}create_tasks`) return input.channel || "";
689
703
  if (name === `${this.mcpToolPrefix}claim_tasks`) {
@@ -1189,6 +1203,50 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
1189
1203
  }
1190
1204
  };
1191
1205
 
1206
+ // ../shared/src/serverPermissions.ts
1207
+ var EMPTY_SERVER_CAPABILITIES = Object.freeze({
1208
+ manageServer: false,
1209
+ manageChannels: false,
1210
+ manageAgents: false,
1211
+ manageMachines: false,
1212
+ manageMembers: false,
1213
+ changeMemberRoles: false,
1214
+ manageBilling: false,
1215
+ joinPublicChannels: false
1216
+ });
1217
+ var SERVER_CAPABILITY_MATRIX = {
1218
+ owner: Object.freeze({
1219
+ manageServer: true,
1220
+ manageChannels: true,
1221
+ manageAgents: true,
1222
+ manageMachines: true,
1223
+ manageMembers: true,
1224
+ changeMemberRoles: true,
1225
+ manageBilling: true,
1226
+ joinPublicChannels: true
1227
+ }),
1228
+ admin: Object.freeze({
1229
+ manageServer: true,
1230
+ manageChannels: true,
1231
+ manageAgents: true,
1232
+ manageMachines: true,
1233
+ manageMembers: true,
1234
+ changeMemberRoles: false,
1235
+ manageBilling: false,
1236
+ joinPublicChannels: true
1237
+ }),
1238
+ member: Object.freeze({
1239
+ manageServer: false,
1240
+ manageChannels: false,
1241
+ manageAgents: false,
1242
+ manageMachines: false,
1243
+ manageMembers: false,
1244
+ changeMemberRoles: false,
1245
+ manageBilling: false,
1246
+ joinPublicChannels: true
1247
+ })
1248
+ };
1249
+
1192
1250
  // ../shared/src/index.ts
1193
1251
  var RUNTIMES = [
1194
1252
  { id: "claude", displayName: "Claude Code", binary: "claude", supported: true },
@@ -1202,9 +1260,10 @@ var require2 = createRequire(import.meta.url);
1202
1260
  var DAEMON_VERSION = require2("../package.json").version;
1203
1261
  function detectRuntimes() {
1204
1262
  const detected = [];
1263
+ const cmd = process.platform === "win32" ? "where" : "which";
1205
1264
  for (const rt of RUNTIMES) {
1206
1265
  try {
1207
- execSync2(`which ${rt.binary}`, { stdio: "pipe" });
1266
+ execSync2(`${cmd} ${rt.binary}`, { stdio: "pipe" });
1208
1267
  detected.push(rt.id);
1209
1268
  } catch {
1210
1269
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"