@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.
- package/dist/chat-bridge.js +22 -105
- package/dist/{chunk-37O7EHYE.js → chunk-Q4XUZB34.js} +2122 -294
- package/dist/{chunk-JG7ONJZ6.js → chunk-Z3PCMYZO.js} +101 -1
- package/dist/cli/index.js +266 -49
- package/dist/core.js +2 -2
- package/dist/index.js +3 -3
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
320
|
-
|
|
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 <
|
|
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("
|
|
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
|
|
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(
|
|
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
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
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
|
|
1694
|
-
return `#${r.reminderId.slice(0, 8)} [${r.status}]
|
|
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 = [
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
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-
|
|
12
|
+
} from "./chunk-Q4XUZB34.js";
|
|
13
13
|
import {
|
|
14
14
|
subscribeDaemonLogs
|
|
15
|
-
} from "./chunk-
|
|
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-
|
|
7
|
-
import "./chunk-
|
|
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) {
|