@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.
- package/dist/{chunk-SDJ4NOR7.js → chunk-D6DQHMCD.js} +38 -0
- package/dist/cli/index.js +116 -25
- package/dist/core.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
553
|
-
const res = await client.request("GET",
|
|
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
|
|
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
|
-
|
|
1033
|
-
|
|
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(
|
|
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
package/dist/index.js
CHANGED