@slock-ai/daemon 0.43.0 → 0.46.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.
@@ -110,9 +110,109 @@ function buildFetchDispatcher(targetUrl, env) {
110
110
  return dispatcher;
111
111
  }
112
112
 
113
+ // src/chatBridgeRequest.ts
114
+ var DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS = Number.parseInt(
115
+ process.env.SLOCK_CHAT_BRIDGE_TOOL_TIMEOUT_MS || "",
116
+ 10
117
+ ) || 6e4;
118
+ var ChatBridgeToolTimeoutError = class extends Error {
119
+ toolName;
120
+ target;
121
+ timeoutMs;
122
+ durationMs;
123
+ constructor(toolName, target, timeoutMs, durationMs) {
124
+ super(`${toolName} timed out after ${timeoutMs}ms${target ? ` (target: ${target})` : ""}`);
125
+ this.name = "ChatBridgeToolTimeoutError";
126
+ this.toolName = toolName;
127
+ this.target = target;
128
+ this.timeoutMs = timeoutMs;
129
+ this.durationMs = durationMs;
130
+ }
131
+ };
132
+ function describeError(err) {
133
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
134
+ return String(err);
135
+ }
136
+ async function executeJsonRequest(url, init, {
137
+ toolName,
138
+ target = null,
139
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
140
+ fetchImpl,
141
+ now = () => Date.now(),
142
+ warn = (message) => logger.warn(message)
143
+ }) {
144
+ const startedAt = now();
145
+ const timeoutController = new AbortController();
146
+ const signals = [timeoutController.signal];
147
+ if (init.signal) signals.push(init.signal);
148
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
149
+ const timeout = setTimeout(() => {
150
+ timeoutController.abort();
151
+ }, timeoutMs);
152
+ timeout.unref?.();
153
+ try {
154
+ const response = await fetchImpl(url, { ...init, signal });
155
+ const data = await response.json();
156
+ return { response, data, durationMs: now() - startedAt };
157
+ } catch (err) {
158
+ const durationMs = now() - startedAt;
159
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
160
+ warn(
161
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
162
+ );
163
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
164
+ }
165
+ warn(
166
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
167
+ );
168
+ throw err;
169
+ } finally {
170
+ clearTimeout(timeout);
171
+ }
172
+ }
173
+ async function executeResponseRequest(url, init, {
174
+ toolName,
175
+ target = null,
176
+ timeoutMs = DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
177
+ fetchImpl,
178
+ now = () => Date.now(),
179
+ warn = (message) => logger.warn(message)
180
+ }) {
181
+ const startedAt = now();
182
+ const timeoutController = new AbortController();
183
+ const signals = [timeoutController.signal];
184
+ if (init.signal) signals.push(init.signal);
185
+ const signal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
186
+ const timeout = setTimeout(() => {
187
+ timeoutController.abort();
188
+ }, timeoutMs);
189
+ timeout.unref?.();
190
+ try {
191
+ const response = await fetchImpl(url, { ...init, signal });
192
+ return { response, durationMs: now() - startedAt };
193
+ } catch (err) {
194
+ const durationMs = now() - startedAt;
195
+ if (timeoutController.signal.aborted && !init.signal?.aborted) {
196
+ warn(
197
+ `[ChatBridgeTimeout] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} timeout_ms=${timeoutMs} outcome=timeout`
198
+ );
199
+ throw new ChatBridgeToolTimeoutError(toolName, target, timeoutMs, durationMs);
200
+ }
201
+ warn(
202
+ `[ChatBridgeError] tool=${toolName} target=${target ?? "-"} duration_ms=${durationMs} outcome=error error=${describeError(err)}`
203
+ );
204
+ throw err;
205
+ } finally {
206
+ clearTimeout(timeout);
207
+ }
208
+ }
209
+
113
210
  export {
114
211
  subscribeDaemonLogs,
115
212
  logger,
116
213
  buildWebSocketOptions,
117
- buildFetchDispatcher
214
+ buildFetchDispatcher,
215
+ DEFAULT_CHAT_BRIDGE_TOOL_TIMEOUT_MS,
216
+ executeJsonRequest,
217
+ executeResponseRequest
118
218
  };
package/dist/cli/index.js CHANGED
@@ -258,10 +258,11 @@ function formatServerInfo(data) {
258
258
  const humans = data.humans ?? [];
259
259
  text += formatRuntimeContext(data.runtimeContext);
260
260
  text += "### Channels\n";
261
- text += 'Visible public channels may appear even when `joined=false`. Use `slock message read --channel "#name"` to inspect them. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a channel you have joined, use `slock channel leave --target "#name"`. To stop following a thread, use `slock thread unfollow --target "#name:shortid"`.\n';
261
+ text += 'Visible public channels may appear even when `joined=false`. Private channels are shown only when you are a member; do not disclose private-channel names, membership, or content outside that channel. Use `slock message read --channel "#name"` to inspect visible channels. When a channel is not joined, you cannot send messages there or receive ordinary channel delivery until a human adds you to the channel. To leave a channel you have joined, use `slock channel leave --target "#name"`. To stop following a thread, use `slock thread unfollow --target "#name:shortid"`.\n';
262
262
  if (channels.length > 0) {
263
263
  for (const t of channels) {
264
- const status = t.joined ? "joined" : "not joined";
264
+ const visibility = t.type === "private" ? "private" : "public";
265
+ const status = `${visibility}, ${t.joined ? "joined" : "not joined"}`;
265
266
  text += t.description ? ` - #${t.name} [${status}] \u2014 ${t.description}
266
267
  ` : ` - #${t.name} [${status}]
267
268
  `;
@@ -314,10 +315,12 @@ function formatChannelMembers(data) {
314
315
  text += " (none)\n";
315
316
  }
316
317
  text += "\n### Humans\n";
318
+ text += "Role labels show server-level owner/admin authority; no role label means ordinary member.\n";
317
319
  if (humans.length > 0) {
318
320
  for (const u of humans) {
319
- text += u.description ? ` - @${u.name} \u2014 ${u.description}
320
- ` : ` - @${u.name}
321
+ const role = u.role && u.role !== "member" ? ` (${u.role})` : "";
322
+ text += u.description ? ` - @${u.name}${role} \u2014 ${u.description}
323
+ ` : ` - @${u.name}${role}
321
324
  `;
322
325
  }
323
326
  } else {
@@ -815,8 +818,23 @@ function registerReadCommand(parent) {
815
818
  }
816
819
 
817
820
  // src/commands/message/search.ts
821
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
822
+ function normalizeMemberHandleRef(raw) {
823
+ const trimmed = raw.trim();
824
+ if (!trimmed) {
825
+ fail("INVALID_ARG", "--sender must not be empty");
826
+ }
827
+ if (UUID_RE2.test(trimmed)) {
828
+ fail("INVALID_ARG", "--sender expects a member handle like @alice, not a UUID");
829
+ }
830
+ const handle = trimmed.replace(/^@/, "").trim();
831
+ if (!handle) {
832
+ fail("INVALID_ARG", "--sender handle must not be empty");
833
+ }
834
+ return handle;
835
+ }
818
836
  function registerSearchCommand(parent) {
819
- parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <id>", "Restrict to messages by this sender id").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
837
+ parent.command("search").description("Search messages across channels the agent can see").requiredOption("--query <q>", "Search query string").option("--channel <target>", "Restrict to a single channel/DM/thread").option("--sender <handle>", "Restrict to messages by sender handle, e.g. @alice").option("--sort <mode>", "Sort results by relevance or recent (default: relevance)").option("--before <iso>", "Only messages before this ISO datetime").option("--after <iso>", "Only messages after this ISO datetime").option("--limit <n>", "Max results (server default applies if omitted)").action(async (opts) => {
820
838
  let ctx;
821
839
  try {
822
840
  ctx = loadAgentContext();
@@ -832,10 +850,14 @@ function registerSearchCommand(parent) {
832
850
  }
833
851
  limit = n;
834
852
  }
853
+ if (opts.sort !== void 0 && opts.sort !== "relevance" && opts.sort !== "recent") {
854
+ fail("INVALID_ARG", `--sort must be "relevance" or "recent"; got ${opts.sort}`);
855
+ }
835
856
  const params = new URLSearchParams();
836
857
  params.set("q", opts.query);
837
858
  if (opts.channel) params.set("channel", opts.channel);
838
- if (opts.sender) params.set("senderId", opts.sender);
859
+ if (opts.sender) params.set("sender", normalizeMemberHandleRef(opts.sender));
860
+ if (opts.sort) params.set("sort", opts.sort);
839
861
  if (opts.before) params.set("before", opts.before);
840
862
  if (opts.after) params.set("after", opts.after);
841
863
  if (limit !== void 0) params.set("limit", String(limit));
@@ -845,7 +867,7 @@ function registerSearchCommand(parent) {
845
867
  `/internal/agent/${encodeURIComponent(ctx.agentId)}/search?${params.toString()}`
846
868
  );
847
869
  if (!res.ok) {
848
- const code = res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED";
870
+ const code = res.errorCode ?? (res.status >= 500 ? "SERVER_5XX" : "SEARCH_FAILED");
849
871
  fail(code, res.error ?? `HTTP ${res.status}`);
850
872
  }
851
873
  process.stdout.write(formatSearchResults(opts.query, res.data) + "\n");
@@ -855,7 +877,8 @@ function registerSearchCommand(parent) {
855
877
  // src/commands/attachment/upload.ts
856
878
  import { existsSync, statSync, readFileSync } from "fs";
857
879
  import { basename } from "path";
858
- var MAX_BYTES = 10 * 1024 * 1024;
880
+ var MAX_ATTACHMENT_UPLOAD_BYTES = 50 * 1024 * 1024;
881
+ var MAX_ATTACHMENT_UPLOAD_LABEL = "50MB";
859
882
  var FILENAME_MIME_MAP = {
860
883
  ".jpg": "image/jpeg",
861
884
  ".jpeg": "image/jpeg",
@@ -912,8 +935,33 @@ function inferUploadMimeType(filename, buffer, explicitMimeType) {
912
935
  const explicit = normalizeExplicitMimeType(explicitMimeType);
913
936
  return explicit || inferMimeTypeFromBuffer(buffer) || inferMimeTypeFromFilename(filename) || "application/octet-stream";
914
937
  }
938
+ function validateUploadFileSize(size) {
939
+ if (size > MAX_ATTACHMENT_UPLOAD_BYTES) {
940
+ throw new AttachmentUploadArgError(
941
+ "INVALID_ARG",
942
+ `--path is ${formatBytes(size)}; max upload size is ${MAX_ATTACHMENT_UPLOAD_LABEL}`
943
+ );
944
+ }
945
+ if (size === 0) {
946
+ throw new AttachmentUploadArgError(
947
+ "INVALID_ARG",
948
+ "--path is empty; refusing to upload a 0-byte attachment"
949
+ );
950
+ }
951
+ }
952
+ function formatBytes(bytes) {
953
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0B";
954
+ const units = ["B", "KB", "MB", "GB"];
955
+ let value = bytes;
956
+ let unitIndex = 0;
957
+ while (value >= 1024 && unitIndex < units.length - 1) {
958
+ value /= 1024;
959
+ unitIndex += 1;
960
+ }
961
+ return `${unitIndex === 0 ? value.toFixed(0) : value.toFixed(1)}${units[unitIndex]}`;
962
+ }
915
963
  function registerAttachmentUploadCommand(parent) {
916
- parent.command("upload").description("Upload a local file as an attachment (max 10MB)").requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
964
+ parent.command("upload").description(`Upload a local file as an attachment (max ${MAX_ATTACHMENT_UPLOAD_LABEL})`).requiredOption("--path <filepath>", "Absolute path to the local file to upload").option(
917
965
  "--channel <target>",
918
966
  "Target where the attachment will be used: '#channel', 'dm:@peer', or thread variants. Required by the v0 server until channel-less uploads land."
919
967
  ).option("--mime-type <type>", "Explicit MIME type override, e.g. image/png").action(async (opts) => {
@@ -931,11 +979,11 @@ function registerAttachmentUploadCommand(parent) {
931
979
  if (!stat.isFile()) {
932
980
  fail("INVALID_ARG", `--path is not a regular file: ${opts.path}`);
933
981
  }
934
- if (stat.size > MAX_BYTES) {
935
- fail(
936
- "INVALID_ARG",
937
- `--path is ${stat.size} bytes; max upload size is ${MAX_BYTES} bytes (10MB)`
938
- );
982
+ try {
983
+ validateUploadFileSize(stat.size);
984
+ } catch (err) {
985
+ if (err instanceof AttachmentUploadArgError) fail(err.code, err.message);
986
+ throw err;
939
987
  }
940
988
  if (!opts.channel) {
941
989
  fail(
@@ -1049,7 +1097,7 @@ function formatClaimResults(channel, data) {
1049
1097
  const msgShort = r.messageId ? r.messageId.slice(0, 8) : "";
1050
1098
  return `${label} (msg:${msgShort}): claimed`;
1051
1099
  }
1052
- return `${label}: FAILED \u2014 ${r.reason || "already claimed"}`;
1100
+ return `${label}: FAILED \u2014 ${r.reason || "already claimed"}. Do not reply.`;
1053
1101
  });
1054
1102
  const succeeded = data.results.filter((r) => r.success).length;
1055
1103
  const failed = data.results.length - succeeded;
@@ -1325,6 +1373,9 @@ function randomHex(length) {
1325
1373
  return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
1326
1374
  }
1327
1375
 
1376
+ // ../shared/src/attachmentPreview.ts
1377
+ var CSV_PREVIEW_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
1378
+
1328
1379
  // ../shared/src/testing/failpoints.ts
1329
1380
  var NoopFailpointRegistry = class {
1330
1381
  get enabled() {
@@ -1681,20 +1732,18 @@ function registerProfileUpdateCommand(parent) {
1681
1732
  }
1682
1733
 
1683
1734
  // src/commands/reminder/_format.ts
1684
- function toLocalTime2(iso) {
1685
- const d = new Date(iso);
1686
- if (isNaN(d.getTime())) return iso;
1687
- const pad = (n) => String(n).padStart(2, "0");
1688
- return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
1689
- }
1735
+ var MODIFY_HINT = "(to modify: snooze/update/cancel; slock reminder --help)";
1690
1736
  function formatReminder(r) {
1691
- const fireLocal = toLocalTime2(r.fireAt);
1692
1737
  const ref = r.msgRef ? ` ref=${r.msgRef}` : "";
1693
- const repeat = r.recurrence ? ` repeat=${r.recurrence.description}` : "";
1694
- return `#${r.reminderId.slice(0, 8)} [${r.status}] fires=${fireLocal} "${r.title}"${ref}${repeat}`;
1738
+ const timeField = r.status === "fired" ? `fired_at=${r.firedAt ?? r.fireAt}` : `next=${r.fireAt}`;
1739
+ return `#${r.reminderId.slice(0, 8)} [${r.status}] ${formatReminderType(r)} ${timeField} "${r.title}"${ref}`;
1695
1740
  }
1696
1741
  function formatReminderScheduled(r, warning) {
1697
- const lines = [`Reminder scheduled: ${formatReminder(r)}`];
1742
+ const lines = [
1743
+ `Reminder scheduled: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
1744
+ `Next: ${r.fireAt}`,
1745
+ MODIFY_HINT
1746
+ ];
1698
1747
  if (warning) lines.push(`Warning: ${warning}`);
1699
1748
  return lines.join("\n");
1700
1749
  }
@@ -1703,7 +1752,34 @@ function formatReminderList(reminders) {
1703
1752
  return reminders.map(formatReminder).join("\n");
1704
1753
  }
1705
1754
  function formatReminderCanceled(r) {
1706
- return `Reminder canceled: ${formatReminder(r)}`;
1755
+ return `Reminder canceled: #${r.reminderId.slice(0, 8)} [${r.status}] "${r.title}"`;
1756
+ }
1757
+ function formatReminderSnoozed(r) {
1758
+ return [
1759
+ `Reminder snoozed: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
1760
+ `Next: ${r.fireAt}`,
1761
+ MODIFY_HINT
1762
+ ].join("\n");
1763
+ }
1764
+ function formatReminderUpdated(r, warning) {
1765
+ const lines = [
1766
+ `Reminder updated: #${r.reminderId.slice(0, 8)} ${formatReminderType(r)} "${r.title}"`,
1767
+ `Next: ${r.fireAt}`,
1768
+ MODIFY_HINT
1769
+ ];
1770
+ if (warning) lines.push(`Warning: ${warning}`);
1771
+ return lines.join("\n");
1772
+ }
1773
+ function formatReminderLog(events) {
1774
+ if (events.length === 0) return "No reminder events.";
1775
+ return events.map((event) => {
1776
+ const next = event.nextFireAt ? ` next=${event.nextFireAt}` : " next=none";
1777
+ return `${event.occurredAt} ${event.eventType.toUpperCase()} by ${event.actorType}${event.actorId ? `:${event.actorId}` : ""}${next}`;
1778
+ }).join("\n");
1779
+ }
1780
+ function formatReminderType(r) {
1781
+ if (!r.recurrence) return "(one-time)";
1782
+ return `(recurring \xB7 ${r.recurrence.description})`;
1707
1783
  }
1708
1784
 
1709
1785
  // src/commands/reminder/schedule.ts
@@ -1799,9 +1875,9 @@ function registerReminderScheduleCommand(parent) {
1799
1875
  // src/commands/reminder/list.ts
1800
1876
  var VALID_STATUSES2 = /* @__PURE__ */ new Set(["scheduled", "fired", "canceled"]);
1801
1877
  function registerReminderListCommand(parent) {
1802
- parent.command("list").description("List your own reminders (defaults to scheduled)").option(
1878
+ parent.command("list").description("List your own reminders (defaults to scheduled and fired)").option("--all", "Include canceled reminders").option(
1803
1879
  "--status <s>",
1804
- "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled"
1880
+ "Comma-separated statuses (scheduled,fired,canceled). Default: scheduled,fired"
1805
1881
  ).action(async (opts) => {
1806
1882
  let ctx;
1807
1883
  try {
@@ -1810,14 +1886,18 @@ function registerReminderListCommand(parent) {
1810
1886
  if (err instanceof AgentBootstrapError) fail(err.code, err.message);
1811
1887
  throw err;
1812
1888
  }
1813
- const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled";
1889
+ const statusRaw = opts.status && opts.status.trim().length > 0 ? opts.status.trim() : "scheduled,fired";
1814
1890
  for (const s of statusRaw.split(",").map((x) => x.trim()).filter(Boolean)) {
1815
1891
  if (!VALID_STATUSES2.has(s)) {
1816
1892
  fail("INVALID_ARG", `--status entries must be one of ${Array.from(VALID_STATUSES2).join("|")}; got ${s}`);
1817
1893
  }
1818
1894
  }
1819
1895
  const params = new URLSearchParams();
1820
- params.set("status", statusRaw);
1896
+ if (opts.all && !opts.status) {
1897
+ params.set("all", "true");
1898
+ } else {
1899
+ params.set("status", statusRaw);
1900
+ }
1821
1901
  const client = new ApiClient(ctx);
1822
1902
  const res = await client.request(
1823
1903
  "GET",
@@ -1831,6 +1911,37 @@ function registerReminderListCommand(parent) {
1831
1911
  });
1832
1912
  }
1833
1913
 
1914
+ // src/commands/reminder/_resolve.ts
1915
+ async function resolveReminderId(client, agentId, id, opts) {
1916
+ const trimmed = id.trim();
1917
+ if (!trimmed) fail("INVALID_ARG", "--id is required");
1918
+ if (trimmed.length >= 32) return trimmed;
1919
+ const params = new URLSearchParams();
1920
+ if (opts.all) {
1921
+ params.set("all", "true");
1922
+ } else if (opts.statuses && opts.statuses.length > 0) {
1923
+ params.set("status", opts.statuses.join(","));
1924
+ }
1925
+ const query = params.toString();
1926
+ const res = await client.request(
1927
+ "GET",
1928
+ `/internal/agent/${encodeURIComponent(agentId)}/reminders${query ? `?${query}` : ""}`
1929
+ );
1930
+ if (!res.ok) {
1931
+ const code = res.status >= 500 ? "SERVER_5XX" : opts.failureCode;
1932
+ fail(code, res.error ?? `HTTP ${res.status}`);
1933
+ }
1934
+ const matches = (res.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(trimmed));
1935
+ if (matches.length === 0) {
1936
+ const scope = opts.all ? "reminder" : `${opts.statuses?.join("/") ?? "active"} reminder`;
1937
+ fail("NOT_FOUND", `No ${scope} matches id prefix '${trimmed}'.`);
1938
+ }
1939
+ if (matches.length > 1) {
1940
+ fail("AMBIGUOUS", `Ambiguous id prefix '${trimmed}' matches ${matches.length} reminders; pass a longer id.`);
1941
+ }
1942
+ return matches[0].reminderId;
1943
+ }
1944
+
1834
1945
  // src/commands/reminder/cancel.ts
1835
1946
  function registerReminderCancelCommand(parent) {
1836
1947
  parent.command("cancel").description("Cancel a scheduled reminder by id (full uuid or 8-char prefix)").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
@@ -1845,25 +1956,10 @@ function registerReminderCancelCommand(parent) {
1845
1956
  fail("INVALID_ARG", "--id is required");
1846
1957
  }
1847
1958
  const client = new ApiClient(ctx);
1848
- let fullId = opts.id;
1849
- if (opts.id.length < 32) {
1850
- const listRes = await client.request(
1851
- "GET",
1852
- `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders?status=scheduled`
1853
- );
1854
- if (!listRes.ok) {
1855
- const code = listRes.status >= 500 ? "SERVER_5XX" : "CANCEL_FAILED";
1856
- fail(code, listRes.error ?? `HTTP ${listRes.status}`);
1857
- }
1858
- const matches = (listRes.data?.reminders ?? []).filter((r) => r.reminderId.startsWith(opts.id));
1859
- if (matches.length === 0) {
1860
- fail("NOT_FOUND", `No scheduled reminder matches id prefix '${opts.id}'.`);
1861
- }
1862
- if (matches.length > 1) {
1863
- fail("AMBIGUOUS", `Ambiguous id prefix '${opts.id}' matches ${matches.length} reminders; pass a longer id.`);
1864
- }
1865
- fullId = matches[0].reminderId;
1866
- }
1959
+ const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
1960
+ statuses: ["scheduled", "fired"],
1961
+ failureCode: "CANCEL_FAILED"
1962
+ });
1867
1963
  const res = await client.request(
1868
1964
  "DELETE",
1869
1965
  `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`
@@ -1876,6 +1972,124 @@ function registerReminderCancelCommand(parent) {
1876
1972
  });
1877
1973
  }
1878
1974
 
1975
+ // src/commands/reminder/_duration.ts
1976
+ function parseDurationSeconds(input) {
1977
+ const raw = input.trim();
1978
+ const match = /^(\d+)(s|m|h|d)?$/.exec(raw);
1979
+ if (!match) return null;
1980
+ const value = Number(match[1]);
1981
+ const unit = match[2] ?? "s";
1982
+ const multiplier = unit === "s" ? 1 : unit === "m" ? 60 : unit === "h" ? 3600 : 86400;
1983
+ const seconds = value * multiplier;
1984
+ if (!Number.isSafeInteger(seconds) || seconds <= 0) return null;
1985
+ return seconds;
1986
+ }
1987
+
1988
+ // src/commands/reminder/snooze.ts
1989
+ function registerReminderSnoozeCommand(parent) {
1990
+ parent.command("snooze").description("Snooze a scheduled or fired reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").requiredOption("--by <duration>", "Snooze duration, e.g. 30m, 2h, 1d").action(async (opts) => {
1991
+ let ctx;
1992
+ try {
1993
+ ctx = loadAgentContext();
1994
+ } catch (err) {
1995
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
1996
+ throw err;
1997
+ }
1998
+ const delaySeconds = parseDurationSeconds(opts.by);
1999
+ if (delaySeconds == null) {
2000
+ fail("INVALID_ARG", "--by must be a positive duration like 30m, 2h, or 1d");
2001
+ }
2002
+ const client = new ApiClient(ctx);
2003
+ const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
2004
+ statuses: ["scheduled", "fired"],
2005
+ failureCode: "SNOOZE_FAILED"
2006
+ });
2007
+ const res = await client.request(
2008
+ "POST",
2009
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/snooze`,
2010
+ { delaySeconds }
2011
+ );
2012
+ if (!res.ok || !res.data?.reminder) {
2013
+ const code = res.status >= 500 ? "SERVER_5XX" : "SNOOZE_FAILED";
2014
+ fail(code, res.error ?? `HTTP ${res.status}`);
2015
+ }
2016
+ process.stdout.write(formatReminderSnoozed(res.data.reminder) + "\n");
2017
+ });
2018
+ }
2019
+
2020
+ // src/commands/reminder/update.ts
2021
+ function registerReminderUpdateCommand(parent) {
2022
+ parent.command("update").description("Update one field on a scheduled reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").option("--fire-at <iso>", "New absolute next fire time").option("--in <duration>", "New relative next fire time, e.g. 30m, 2h").option("--cadence <rule>", "New recurrence rule: every:15m | daily@09:00 | weekly:mon,fri@09:00").option("--title <text>", "New reminder title").action(async (opts) => {
2023
+ let ctx;
2024
+ try {
2025
+ ctx = loadAgentContext();
2026
+ } catch (err) {
2027
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
2028
+ throw err;
2029
+ }
2030
+ const mutationCount = [opts.fireAt, opts.in, opts.cadence, opts.title].filter((x) => x !== void 0 && x !== null).length;
2031
+ if (mutationCount !== 1) {
2032
+ fail("INVALID_ARG", "Pass exactly one of --fire-at, --in, --cadence, or --title");
2033
+ }
2034
+ const body = {};
2035
+ if (opts.fireAt !== void 0) body.fireAt = opts.fireAt;
2036
+ if (opts.in !== void 0) {
2037
+ const delaySeconds = parseDurationSeconds(opts.in);
2038
+ if (delaySeconds == null) {
2039
+ fail("INVALID_ARG", "--in must be a positive duration like 30m, 2h, or 1d");
2040
+ }
2041
+ body.delaySeconds = delaySeconds;
2042
+ }
2043
+ if (opts.cadence !== void 0) {
2044
+ body.repeat = opts.cadence;
2045
+ body.tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
2046
+ }
2047
+ if (opts.title !== void 0) body.title = opts.title;
2048
+ const client = new ApiClient(ctx);
2049
+ const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
2050
+ all: true,
2051
+ failureCode: "UPDATE_FAILED"
2052
+ });
2053
+ const res = await client.request(
2054
+ "PATCH",
2055
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}`,
2056
+ body
2057
+ );
2058
+ if (!res.ok || !res.data?.reminder) {
2059
+ const code = res.status >= 500 ? "SERVER_5XX" : "UPDATE_FAILED";
2060
+ fail(code, res.error ?? `HTTP ${res.status}`);
2061
+ }
2062
+ process.stdout.write(formatReminderUpdated(res.data.reminder, res.data.warning ?? null) + "\n");
2063
+ });
2064
+ }
2065
+
2066
+ // src/commands/reminder/log.ts
2067
+ function registerReminderLogCommand(parent) {
2068
+ parent.command("log").description("Show lifecycle events for one reminder").requiredOption("--id <id>", "Reminder id (full uuid or short prefix)").action(async (opts) => {
2069
+ let ctx;
2070
+ try {
2071
+ ctx = loadAgentContext();
2072
+ } catch (err) {
2073
+ if (err instanceof AgentBootstrapError) fail(err.code, err.message);
2074
+ throw err;
2075
+ }
2076
+ const client = new ApiClient(ctx);
2077
+ const fullId = await resolveReminderId(client, ctx.agentId, opts.id, {
2078
+ all: true,
2079
+ failureCode: "LOG_FAILED"
2080
+ });
2081
+ const res = await client.request(
2082
+ "GET",
2083
+ `/internal/agent/${encodeURIComponent(ctx.agentId)}/reminders/${encodeURIComponent(fullId)}/log`
2084
+ );
2085
+ if (!res.ok || !res.data?.events) {
2086
+ const code = res.status >= 500 ? "SERVER_5XX" : "LOG_FAILED";
2087
+ fail(code, res.error ?? `HTTP ${res.status}`);
2088
+ }
2089
+ process.stdout.write(formatReminderLog(res.data.events) + "\n");
2090
+ });
2091
+ }
2092
+
1879
2093
  // src/index.ts
1880
2094
  var program = new Command();
1881
2095
  program.name("slock").description(
@@ -1911,6 +2125,9 @@ var reminderCmd = program.command("reminder").description("Reminder operations")
1911
2125
  registerReminderScheduleCommand(reminderCmd);
1912
2126
  registerReminderListCommand(reminderCmd);
1913
2127
  registerReminderCancelCommand(reminderCmd);
2128
+ registerReminderSnoozeCommand(reminderCmd);
2129
+ registerReminderUpdateCommand(reminderCmd);
2130
+ registerReminderLogCommand(reminderCmd);
1914
2131
  program.parseAsync().catch((err) => {
1915
2132
  if (err instanceof CliExit) {
1916
2133
  process.exitCode = err.exitCode;
package/dist/core.js CHANGED
@@ -9,10 +9,10 @@ import {
9
9
  resolveSlockCliPath,
10
10
  resolveWorkspaceDirectoryPath,
11
11
  scanWorkspaceDirectories
12
- } from "./chunk-37O7EHYE.js";
12
+ } from "./chunk-Q4XUZB34.js";
13
13
  import {
14
14
  subscribeDaemonLogs
15
- } from "./chunk-JG7ONJZ6.js";
15
+ } from "./chunk-Z3PCMYZO.js";
16
16
  export {
17
17
  DAEMON_CLI_USAGE,
18
18
  DaemonCore,
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  DAEMON_CLI_USAGE,
4
4
  DaemonCore,
5
5
  parseDaemonCliArgs
6
- } from "./chunk-37O7EHYE.js";
7
- import "./chunk-JG7ONJZ6.js";
6
+ } from "./chunk-Q4XUZB34.js";
7
+ import "./chunk-Z3PCMYZO.js";
8
8
 
9
9
  // src/index.ts
10
10
  var parsedArgs = parseDaemonCliArgs(process.argv.slice(2));
@@ -12,7 +12,7 @@ if (!parsedArgs) {
12
12
  console.error(DAEMON_CLI_USAGE);
13
13
  process.exit(1);
14
14
  }
15
- var daemon = new DaemonCore(parsedArgs);
15
+ var daemon = new DaemonCore({ ...parsedArgs, localTrace: true });
16
16
  try {
17
17
  daemon.start();
18
18
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slock-ai/daemon",
3
- "version": "0.43.0",
3
+ "version": "0.46.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "slock-daemon": "dist/index.js"