@slock-ai/daemon 0.39.1-alpha.0 → 0.39.1-alpha.2

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.
@@ -475,6 +475,7 @@ var DISPLAY_PLAN_CONFIG = {
475
475
  };
476
476
 
477
477
  // src/agentProcessManager.ts
478
+ import { writeFileSync as writeFileSync6, renameSync, rmSync } from "fs";
478
479
  import { mkdir, writeFile, access, readdir as readdir2, stat as stat2, readFile, rm as rm2 } from "fs/promises";
479
480
  import path10 from "path";
480
481
  import os3 from "os";
@@ -542,6 +543,11 @@ Use the \`slock\` CLI for chat / task / attachment operations. The daemon inject
542
543
  10. **\`slock task update\`** \u2014 Change a task's status (e.g. to in_review or done).
543
544
  11. **\`slock attachment upload\`** \u2014 Upload a file to attach to a message. Returns an attachment ID to pass to \`slock message send\`.
544
545
  12. **\`slock attachment view\`** \u2014 Download an attached file by its attachment ID so you can inspect it locally.
546
+ 13. **\`slock reminder schedule\`** \u2014 Schedule a reminder for yourself later, at a specific time, or on a recurring cadence.
547
+ 14. **\`slock reminder list\`** \u2014 List your reminders.
548
+ 15. **\`slock reminder cancel\`** \u2014 Cancel one of your reminders by ID.
549
+
550
+ When a user asks you to remind them later, at a specific time, or on a recurring schedule, prefer the reminder commands instead of relying on MEMORY or manual follow-up.
545
551
 
546
552
  The CLI prints human-readable canonical text on success (matching the format you see in received messages and history). On failure it prints JSON to stderr:
547
553
  - failure \u2192 stderr \`{"ok":false,"code":"...","message":"..."}\` with non-zero exit
@@ -937,6 +943,7 @@ function prepareCliTransport(ctx, extraEnv = {}, platform = process.platform) {
937
943
  mkdirSync(slockDir, { recursive: true });
938
944
  const tokenFile = path.join(slockDir, "agent-token");
939
945
  writeFileSync(tokenFile, ctx.config.authToken || ctx.daemonApiKey, { mode: 384 });
946
+ const chatContextFile = path.join(ctx.workingDirectory, "chat-context.json");
940
947
  const posixWrapper = path.join(slockDir, "slock");
941
948
  const posixBody = `#!/usr/bin/env bash
942
949
  exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)} "$@"
@@ -958,11 +965,13 @@ exec ${shellSingleQuote(process.execPath)} ${shellSingleQuote(ctx.slockCliPath)}
958
965
  SLOCK_AGENT_ID: ctx.agentId,
959
966
  SLOCK_SERVER_URL: ctx.config.serverUrl,
960
967
  SLOCK_AGENT_TOKEN_FILE: tokenFile,
968
+ SLOCK_CHAT_CONTEXT_FILE: chatContextFile,
961
969
  PATH: `${slockDir}${path.delimiter}${process.env.PATH ?? ""}`
962
970
  };
963
971
  delete spawnEnv.SLOCK_AGENT_TOKEN;
964
972
  return {
965
973
  slockDir,
974
+ chatContextFile,
966
975
  tokenFile,
967
976
  wrapperPath,
968
977
  spawnEnv
@@ -2415,6 +2424,29 @@ function formatMessageTarget(message) {
2415
2424
  function getMessageShortId(messageId) {
2416
2425
  return messageId.startsWith("thread-") ? messageId.slice(7) : messageId.slice(0, 8);
2417
2426
  }
2427
+ function writeAgentChatContextFile(agentDataDir, message) {
2428
+ const target = formatMessageTarget(message);
2429
+ const msgId = message.message_id ?? null;
2430
+ const filePath = path10.join(agentDataDir, "chat-context.json");
2431
+ const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
2432
+ const json = JSON.stringify({
2433
+ target,
2434
+ msgId,
2435
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2436
+ });
2437
+ try {
2438
+ writeFileSync6(tmpPath, json, { encoding: "utf-8", mode: 384 });
2439
+ renameSync(tmpPath, filePath);
2440
+ } catch (error) {
2441
+ try {
2442
+ rmSync(tmpPath, { force: true });
2443
+ } catch {
2444
+ }
2445
+ logger.warn(
2446
+ `[AgentChatContext] Failed to persist current chat context for agent dir ${agentDataDir}: ${error instanceof Error ? error.message : String(error)}`
2447
+ );
2448
+ }
2449
+ }
2418
2450
  function formatSenderHandle(message) {
2419
2451
  return message.sender_description ? `@${message.sender_name} \u2014 ${message.sender_description}` : `@${message.sender_name}`;
2420
2452
  }
@@ -2650,6 +2682,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
2650
2682
  prompt = driver.supportsNativeStandingPrompt ? NATIVE_STANDING_PROMPT_STARTUP_INPUT : standingPrompt;
2651
2683
  }
2652
2684
  const effectiveConfig = await this.buildSpawnConfig(agentId, config);
2685
+ if (wakeMessage) {
2686
+ writeAgentChatContextFile(agentDataDir, wakeMessage);
2687
+ }
2653
2688
  const { process: proc } = driver.spawn({
2654
2689
  agentId,
2655
2690
  config: effectiveConfig,
@@ -3315,6 +3350,9 @@ Use read_history to catch up on the channels listed above, then stop. Read each
3315
3350
  /** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
3316
3351
  deliverMessagesViaStdin(agentId, ap, messages, mode) {
3317
3352
  if (messages.length === 0) return true;
3353
+ const latestMessage = messages[messages.length - 1];
3354
+ const agentDataDir = path10.join(this.dataDir, agentId);
3355
+ writeAgentChatContextFile(agentDataDir, latestMessage);
3318
3356
  const prompt = messages.length === 1 ? `New message received:
3319
3357
 
3320
3358
  ${formatIncomingMessage(messages[0])}
package/dist/cli/index.js CHANGED
@@ -295,6 +295,59 @@ function registerServerInfoCommand(parent) {
295
295
  });
296
296
  }
297
297
 
298
+ // src/chatContext.ts
299
+ import fs2 from "fs";
300
+ import os from "os";
301
+ import path from "path";
302
+ var EMPTY = { target: null, msgId: null };
303
+ function resolveChatContextPath(agentId, env = process.env) {
304
+ const override = env.SLOCK_CHAT_CONTEXT_FILE;
305
+ if (override && override.length > 0) return override;
306
+ return path.join(os.homedir(), ".slock", "agents", agentId, "chat-context.json");
307
+ }
308
+ function readChatContext(agentId, env = process.env) {
309
+ const filePath = resolveChatContextPath(agentId, env);
310
+ let raw;
311
+ try {
312
+ raw = fs2.readFileSync(filePath, "utf-8");
313
+ } catch {
314
+ return EMPTY;
315
+ }
316
+ try {
317
+ const parsed = JSON.parse(raw);
318
+ if (!parsed || typeof parsed !== "object") return EMPTY;
319
+ const target = typeof parsed.target === "string" && parsed.target.length > 0 ? parsed.target : null;
320
+ const msgId = typeof parsed.msgId === "string" && parsed.msgId.length > 0 ? parsed.msgId : null;
321
+ return { target, msgId };
322
+ } catch {
323
+ return EMPTY;
324
+ }
325
+ }
326
+ function writeChatContext(agentId, ctx, env = process.env) {
327
+ const filePath = resolveChatContextPath(agentId, env);
328
+ const current = readChatContext(agentId, env);
329
+ const next = {
330
+ target: ctx.target !== void 0 ? ctx.target : current.target,
331
+ msgId: ctx.msgId !== void 0 ? ctx.msgId : current.msgId
332
+ };
333
+ const dir = path.dirname(filePath);
334
+ try {
335
+ fs2.mkdirSync(dir, { recursive: true });
336
+ } catch {
337
+ }
338
+ const tmp = `${filePath}.tmp.${process.pid}.${Date.now()}`;
339
+ const json = JSON.stringify({ ...next, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
340
+ try {
341
+ fs2.writeFileSync(tmp, json, { encoding: "utf-8", mode: 384 });
342
+ fs2.renameSync(tmp, filePath);
343
+ } catch {
344
+ try {
345
+ fs2.unlinkSync(tmp);
346
+ } catch {
347
+ }
348
+ }
349
+ }
350
+
298
351
  // src/commands/message/_format.ts
299
352
  function toLocalTime(iso) {
300
353
  const d = new Date(iso);
@@ -527,6 +580,10 @@ function registerSendCommand(parent) {
527
580
  fail(code, res.error ?? `HTTP ${res.status}`);
528
581
  }
529
582
  const data = res.data;
583
+ writeChatContext(ctx.agentId, {
584
+ target: opts.target,
585
+ msgId: typeof data.messageId === "string" ? data.messageId : null
586
+ });
530
587
  const shortId = data.messageId ? data.messageId.slice(0, 8) : null;
531
588
  const replyHint = shortId ? ` (to reply in this message's thread, use target "${opts.target.includes(":") ? opts.target : opts.target + ":" + shortId}")` : "";
532
589
  let unreadSection = "";
@@ -549,8 +606,8 @@ async function drainInbox(ctx, opts) {
549
606
  const query = [];
550
607
  if (opts.block) query.push("block=true");
551
608
  if (opts.block && opts.timeoutMs !== void 0) query.push(`timeout=${opts.timeoutMs}`);
552
- const path = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
553
- const res = await client.request("GET", path);
609
+ const path2 = query.length > 0 ? `${agentPath}/receive?${query.join("&")}` : `${agentPath}/receive`;
610
+ const res = await client.request("GET", path2);
554
611
  if (!res.ok) {
555
612
  const code = res.status >= 500 ? "SERVER_5XX" : failCode;
556
613
  fail(code, res.error ?? `HTTP ${res.status}`);
@@ -576,6 +633,13 @@ function registerCheckCommand(parent) {
576
633
  throw err;
577
634
  }
578
635
  const result = await drainInbox(ctx, { block: false });
636
+ if (result.messages.length > 0) {
637
+ const latest = result.messages[result.messages.length - 1];
638
+ const target = formatTarget(latest);
639
+ const rawMsgId = latest.message_id;
640
+ const msgId = typeof rawMsgId === "string" ? rawMsgId : null;
641
+ writeChatContext(ctx.agentId, { target, msgId });
642
+ }
579
643
  process.stdout.write(formatMessages(result.messages) + "\n");
580
644
  });
581
645
  }
@@ -1008,6 +1072,48 @@ function formatReminderCanceled(r) {
1008
1072
  }
1009
1073
 
1010
1074
  // src/commands/reminder/schedule.ts
1075
+ function buildScheduleBody(opts, cached, now = () => Intl.DateTimeFormat().resolvedOptions().timeZone) {
1076
+ if (!opts.delaySeconds && !opts.fireAt && !opts.repeat) {
1077
+ return {
1078
+ body: {},
1079
+ error: { code: "INVALID_ARG", message: "Provide --delay-seconds, --fire-at, or --repeat" }
1080
+ };
1081
+ }
1082
+ if (opts.delaySeconds && opts.fireAt) {
1083
+ return {
1084
+ body: {},
1085
+ error: {
1086
+ code: "INVALID_ARG",
1087
+ message: "Pass either --delay-seconds or --fire-at, not both"
1088
+ }
1089
+ };
1090
+ }
1091
+ const body = { title: opts.title, msgId: opts.msgId ?? null };
1092
+ if (opts.delaySeconds !== void 0) {
1093
+ const n = Number(opts.delaySeconds);
1094
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
1095
+ return {
1096
+ body: {},
1097
+ error: {
1098
+ code: "INVALID_ARG",
1099
+ message: `--delay-seconds must be a positive integer; got ${opts.delaySeconds}`
1100
+ }
1101
+ };
1102
+ }
1103
+ body.delaySeconds = n;
1104
+ }
1105
+ if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
1106
+ if (opts.repeat !== void 0) {
1107
+ body.repeat = opts.repeat;
1108
+ body.tz = now();
1109
+ }
1110
+ if (opts.channel !== void 0) body.channel = opts.channel;
1111
+ if (opts.channel === void 0 && opts.msgId === void 0) {
1112
+ if (cached.target) body.channel = cached.target;
1113
+ if (cached.msgId) body.msgId = cached.msgId;
1114
+ }
1115
+ return { body };
1116
+ }
1011
1117
  function registerReminderScheduleCommand(parent) {
1012
1118
  parent.command("schedule").description("Schedule a reminder that fires at a future time").requiredOption("--title <t>", "Short description of what the reminder is about").option(
1013
1119
  "--delay-seconds <n>",
@@ -1020,7 +1126,7 @@ function registerReminderScheduleCommand(parent) {
1020
1126
  "Recurrence rule: every:15m | every:2h | every:1d | daily@09:00 | weekly:mon,fri@09:00"
1021
1127
  ).option(
1022
1128
  "--channel <ref>",
1023
- "Optional channel to post a receipt message in (e.g. #general, dm:@alice). Without this, receipt is only posted when --msg-id is given."
1129
+ "Optional channel to post a receipt message in (e.g. #general, dm:@alice). Without --channel or --msg-id, receipt inherits the agent's current chat context (last message received/sent)."
1024
1130
  ).option("--msg-id <id>", "Optional message id this reminder is anchored to").action(async (opts) => {
1025
1131
  let ctx;
1026
1132
  try {
@@ -1029,37 +1135,22 @@ function registerReminderScheduleCommand(parent) {
1029
1135
  if (err instanceof AgentBootstrapError) fail(err.code, err.message);
1030
1136
  throw err;
1031
1137
  }
1032
- if (!opts.delaySeconds && !opts.fireAt && !opts.repeat) {
1033
- fail("INVALID_ARG", "Provide --delay-seconds, --fire-at, or --repeat");
1034
- }
1035
- if (opts.delaySeconds && opts.fireAt) {
1036
- fail("INVALID_ARG", "Pass either --delay-seconds or --fire-at, not both");
1037
- }
1038
- const body = { title: opts.title, msgId: opts.msgId ?? null };
1039
- if (opts.delaySeconds !== void 0) {
1040
- const n = Number(opts.delaySeconds);
1041
- if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
1042
- fail("INVALID_ARG", `--delay-seconds must be a positive integer; got ${opts.delaySeconds}`);
1043
- }
1044
- body.delaySeconds = n;
1045
- }
1046
- if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
1047
- if (opts.repeat !== void 0) {
1048
- body.repeat = opts.repeat;
1049
- body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1050
- }
1051
- if (opts.channel !== void 0) body.channel = opts.channel;
1138
+ const cached = readChatContext(ctx.agentId);
1139
+ const built = buildScheduleBody(opts, cached);
1140
+ if (built.error) fail(built.error.code, built.error.message);
1052
1141
  const client = new ApiClient(ctx);
1053
1142
  const res = await client.request(
1054
1143
  "POST",
1055
1144
  `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders`,
1056
- body
1145
+ built.body
1057
1146
  );
1058
1147
  if (!res.ok || !res.data?.reminder) {
1059
1148
  const code = res.status >= 500 ? "SERVER_5XX" : "SCHEDULE_FAILED";
1060
1149
  fail(code, res.error ?? `HTTP ${res.status}`);
1061
1150
  }
1062
- process.stdout.write(formatReminderScheduled(res.data.reminder, res.data.warning ?? null) + "\n");
1151
+ process.stdout.write(
1152
+ formatReminderScheduled(res.data.reminder, res.data.warning ?? null) + "\n"
1153
+ );
1063
1154
  });
1064
1155
  }
1065
1156
 
package/dist/core.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-SDJ4NOR7.js";
12
+ } from "./chunk-D6DQHMCD.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
15
  } from "./chunk-E6OOH3IC.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-SDJ4NOR7.js";
6
+ } from "./chunk-D6DQHMCD.js";
7
7
  import "./chunk-E6OOH3IC.js";
8
8
 
9
9
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.39.1-alpha.0",
3
+ "version": "0.39.1-alpha.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"