@slock-ai/daemon 0.38.1 → 0.39.1-alpha.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.
@@ -228,6 +228,21 @@ function bridgeFetch(url, init = {}) {
228
228
  var RECENT_DELIVERY_CACHE_LIMIT = 5e3;
229
229
  var deliveredMessageKeys = /* @__PURE__ */ new Set();
230
230
  var deliveredMessageOrder = [];
231
+ var lastChatContextTarget = null;
232
+ function rememberChatContextTarget(target) {
233
+ if (typeof target === "string" && target.length > 0) {
234
+ lastChatContextTarget = target;
235
+ }
236
+ }
237
+ function captureChatContextFromMessages(messages) {
238
+ for (let i = messages.length - 1; i >= 0; i--) {
239
+ const target = formatTarget(messages[i]);
240
+ if (target) {
241
+ lastChatContextTarget = target;
242
+ return;
243
+ }
244
+ }
245
+ }
231
246
  function messageDeliveryKey(message) {
232
247
  if (message.seq) return `seq:${message.seq}`;
233
248
  if (message.message_id) return `msg:${message.message_id}`;
@@ -402,6 +417,7 @@ server.tool(
402
417
  ]
403
418
  };
404
419
  }
420
+ rememberChatContextTarget(target);
405
421
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
406
422
  const replyHint = shortId ? ` (to reply in this message's thread, use target "${target.includes(":") ? target : target + ":" + shortId}")` : "";
407
423
  let unreadSection = "";
@@ -409,6 +425,7 @@ server.tool(
409
425
  await acknowledgeReceivedMessages(data.recentUnread);
410
426
  const unreadToShow = rememberDeliveredMessages(data.recentUnread);
411
427
  if (unreadToShow.length > 0) {
428
+ captureChatContextFromMessages(unreadToShow);
412
429
  unreadSection = `
413
430
 
414
431
  --- New messages you may have missed ---
@@ -600,6 +617,7 @@ server.tool(
600
617
  await acknowledgeReceivedMessages(messages);
601
618
  const messagesToShow = rememberDeliveredMessages(messages);
602
619
  if (messagesToShow.length > 0) {
620
+ captureChatContextFromMessages(messagesToShow);
603
621
  return { content: [{ type: "text", text: formatMessages(messagesToShow) }] };
604
622
  }
605
623
  }
@@ -973,7 +991,7 @@ server.tool(
973
991
  1. By task number: claim existing tasks shown in list_tasks. Use task_numbers=[1, 3].
974
992
  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
993
 
976
- Thread messages cannot be claimed or converted into tasks. 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.`,
994
+ 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
995
  {
978
996
  channel: z.string().describe("The channel \u2014 e.g. '#engineering'"),
979
997
  task_numbers: z.array(z.number()).optional().describe("Task numbers to claim (from list_tasks output, e.g. [1, 3])"),
@@ -1125,5 +1143,151 @@ server.tool(
1125
1143
  }
1126
1144
  }
1127
1145
  );
1146
+ function formatReminder(r) {
1147
+ const fireLocal = toLocalTime(r.fireAt);
1148
+ const ref = r.msgRef ? ` ref=${r.msgRef}` : "";
1149
+ const repeat = r.recurrence ? ` repeat=${r.recurrence.description}` : "";
1150
+ return `#${r.reminderId.slice(0, 8)} [${r.status}] fires=${fireLocal} "${r.title}"${ref}${repeat}`;
1151
+ }
1152
+ server.tool(
1153
+ "schedule_reminder",
1154
+ "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.",
1155
+ {
1156
+ title: z.string().describe("Short description of what the reminder is about. This is what you'll see when it fires."),
1157
+ delay_seconds: z.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."),
1158
+ fire_at: z.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."),
1159
+ repeat: z.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."),
1160
+ channel: z.string().optional().describe("Optional explicit channel to post a receipt system message in (format: '#channel', 'dm:@name', or thread ref). Normally you don't need to pass this \u2014 when you're actively in a conversation, the receipt is auto-posted in the most recent chat context. Use this only to redirect the receipt elsewhere."),
1161
+ msg_id: z.string().optional().describe("Optional message id this reminder is anchored to (from received messages), for context linking. If set, the receipt is posted in that message's channel automatically.")
1162
+ },
1163
+ async ({ title, delay_seconds, fire_at, repeat, channel, msg_id }) => {
1164
+ try {
1165
+ const body = { title, msgId: msg_id ?? null };
1166
+ if (delay_seconds !== void 0) body.delaySeconds = delay_seconds;
1167
+ if (fire_at !== void 0) body.fireAt = fire_at;
1168
+ if (repeat !== void 0) {
1169
+ body.repeat = repeat;
1170
+ body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1171
+ }
1172
+ const effectiveChannel = channel ?? (msg_id ? void 0 : lastChatContextTarget ?? void 0);
1173
+ if (effectiveChannel !== void 0) body.channel = effectiveChannel;
1174
+ const { response: res, data } = await executeJsonRequest(
1175
+ `${serverUrl}/internal/agent/${agentId}/reminders`,
1176
+ {
1177
+ method: "POST",
1178
+ headers: commonHeaders,
1179
+ body: JSON.stringify(body)
1180
+ },
1181
+ {
1182
+ toolName: "schedule_reminder",
1183
+ fetchImpl: bridgeFetch
1184
+ }
1185
+ );
1186
+ if (!res.ok || !data.reminder) {
1187
+ return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
1188
+ }
1189
+ const lines = [`Reminder scheduled: ${formatReminder(data.reminder)}`];
1190
+ if (data.warning) lines.push(`Warning: ${data.warning}`);
1191
+ return { content: [{ type: "text", text: lines.join("\n") }] };
1192
+ } catch (err) {
1193
+ return {
1194
+ isError: true,
1195
+ content: [{ type: "text", text: `Error: ${err.message}` }]
1196
+ };
1197
+ }
1198
+ }
1199
+ );
1200
+ server.tool(
1201
+ "list_reminders",
1202
+ "List your own reminders. Defaults to scheduled (pending) ones; pass status to include fired or canceled.",
1203
+ {
1204
+ status: z.string().optional().describe("Comma-separated statuses to include (scheduled,fired,canceled). Defaults to 'scheduled'.")
1205
+ },
1206
+ async ({ status }) => {
1207
+ try {
1208
+ const qs = new URLSearchParams();
1209
+ qs.set("status", status && status.trim().length > 0 ? status.trim() : "scheduled");
1210
+ const { response: res, data } = await executeJsonRequest(
1211
+ `${serverUrl}/internal/agent/${agentId}/reminders?${qs.toString()}`,
1212
+ { method: "GET", headers: commonHeaders },
1213
+ {
1214
+ toolName: "list_reminders",
1215
+ fetchImpl: bridgeFetch
1216
+ }
1217
+ );
1218
+ if (!res.ok) {
1219
+ return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
1220
+ }
1221
+ const list = data.reminders ?? [];
1222
+ if (list.length === 0) {
1223
+ return { content: [{ type: "text", text: "No reminders." }] };
1224
+ }
1225
+ return {
1226
+ content: [
1227
+ { type: "text", text: list.map(formatReminder).join("\n") }
1228
+ ]
1229
+ };
1230
+ } catch (err) {
1231
+ return {
1232
+ isError: true,
1233
+ content: [{ type: "text", text: `Error: ${err.message}` }]
1234
+ };
1235
+ }
1236
+ }
1237
+ );
1238
+ server.tool(
1239
+ "cancel_reminder",
1240
+ "Cancel one of your own scheduled reminders by id. Only reminders in 'scheduled' status can be canceled.",
1241
+ {
1242
+ reminder_id: z.string().describe("The reminder id (full uuid, or the short 8-char prefix shown by schedule_reminder / list_reminders).")
1243
+ },
1244
+ async ({ reminder_id }) => {
1245
+ try {
1246
+ let fullId = reminder_id;
1247
+ if (reminder_id.length < 32) {
1248
+ const { response: listRes, data: listData } = await executeJsonRequest(
1249
+ `${serverUrl}/internal/agent/${agentId}/reminders?status=scheduled`,
1250
+ { method: "GET", headers: commonHeaders },
1251
+ {
1252
+ toolName: "cancel_reminder.resolve",
1253
+ fetchImpl: bridgeFetch
1254
+ }
1255
+ );
1256
+ if (!listRes.ok) {
1257
+ return { isError: true, content: [{ type: "text", text: `Error: ${listData.error || listRes.statusText}` }] };
1258
+ }
1259
+ const matches = (listData.reminders ?? []).filter((r) => r.reminderId.startsWith(reminder_id));
1260
+ if (matches.length === 0) {
1261
+ return { isError: true, content: [{ type: "text", text: `No scheduled reminder matches id prefix '${reminder_id}'.` }] };
1262
+ }
1263
+ if (matches.length > 1) {
1264
+ return { isError: true, content: [{ type: "text", text: `Ambiguous id prefix '${reminder_id}' matches ${matches.length} reminders; pass a longer id.` }] };
1265
+ }
1266
+ fullId = matches[0].reminderId;
1267
+ }
1268
+ const { response: res, data } = await executeJsonRequest(
1269
+ `${serverUrl}/internal/agent/${agentId}/reminders/${fullId}`,
1270
+ { method: "DELETE", headers: commonHeaders },
1271
+ {
1272
+ toolName: "cancel_reminder",
1273
+ fetchImpl: bridgeFetch
1274
+ }
1275
+ );
1276
+ if (!res.ok || !data.reminder) {
1277
+ return { isError: true, content: [{ type: "text", text: `Error: ${data.error || res.statusText}` }] };
1278
+ }
1279
+ return {
1280
+ content: [
1281
+ { type: "text", text: `Reminder canceled: ${formatReminder(data.reminder)}` }
1282
+ ]
1283
+ };
1284
+ } catch (err) {
1285
+ return {
1286
+ isError: true,
1287
+ content: [{ type: "text", text: `Error: ${err.message}` }]
1288
+ };
1289
+ }
1290
+ }
1291
+ );
1128
1292
  var transport = new StdioServerTransport();
1129
1293
  await server.connect(transport);