@slock-ai/daemon 0.24.0 → 0.26.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 +0 -56
- package/dist/index.js +111 -75
- package/package.json +2 -1
package/dist/chat-bridge.js
CHANGED
|
@@ -256,62 +256,6 @@ server.tool(
|
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
258
|
);
|
|
259
|
-
server.tool(
|
|
260
|
-
"wait_for_message",
|
|
261
|
-
"Block and wait for new messages. ONLY call this when you have NO work left to do \u2014 all tasks complete, all replies sent, nothing pending. This blocks for up to ~4 minutes waiting for a message to arrive. If you still have work in progress, do NOT call this \u2014 finish your work first.",
|
|
262
|
-
{},
|
|
263
|
-
async () => {
|
|
264
|
-
try {
|
|
265
|
-
const POLL_INTERVAL_MS = 25e3;
|
|
266
|
-
const MAX_WAIT_MS = 27e4;
|
|
267
|
-
const startTime = Date.now();
|
|
268
|
-
while (true) {
|
|
269
|
-
const elapsed = Date.now() - startTime;
|
|
270
|
-
const remaining = MAX_WAIT_MS - elapsed;
|
|
271
|
-
if (remaining <= 0) {
|
|
272
|
-
const params2 = new URLSearchParams();
|
|
273
|
-
params2.set("block", "true");
|
|
274
|
-
params2.set("timeout", "1000");
|
|
275
|
-
const res2 = await fetch(
|
|
276
|
-
`${serverUrl}/internal/agent/${agentId}/receive?${params2}`,
|
|
277
|
-
{ method: "GET", headers: commonHeaders }
|
|
278
|
-
);
|
|
279
|
-
if (!res2.ok) {
|
|
280
|
-
const errData = await res2.json().catch(() => ({}));
|
|
281
|
-
return { content: [{ type: "text", text: `Error: ${errData.error || res2.statusText}` }] };
|
|
282
|
-
}
|
|
283
|
-
const data2 = await res2.json();
|
|
284
|
-
if (data2.messages?.length > 0) {
|
|
285
|
-
return { content: [{ type: "text", text: formatMessages(data2.messages) }] };
|
|
286
|
-
}
|
|
287
|
-
return {
|
|
288
|
-
content: [{ type: "text", text: "No new messages. Take a rest \u2014 new messages will be delivered to you directly. Do not take any further action until notified." }]
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
const thisPoll = Math.min(POLL_INTERVAL_MS, remaining);
|
|
292
|
-
const params = new URLSearchParams();
|
|
293
|
-
params.set("block", "true");
|
|
294
|
-
params.set("timeout", String(thisPoll));
|
|
295
|
-
const res = await fetch(
|
|
296
|
-
`${serverUrl}/internal/agent/${agentId}/receive?${params}`,
|
|
297
|
-
{ method: "GET", headers: commonHeaders }
|
|
298
|
-
);
|
|
299
|
-
if (!res.ok) {
|
|
300
|
-
const errData = await res.json().catch(() => ({}));
|
|
301
|
-
return { content: [{ type: "text", text: `Error: ${errData.error || res.statusText}` }] };
|
|
302
|
-
}
|
|
303
|
-
const data = await res.json();
|
|
304
|
-
if (data.messages?.length > 0) {
|
|
305
|
-
return { content: [{ type: "text", text: formatMessages(data.messages) }] };
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
} catch (err) {
|
|
309
|
-
return {
|
|
310
|
-
content: [{ type: "text", text: `Error: ${err.message}` }]
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
);
|
|
315
259
|
function formatMessages(messages) {
|
|
316
260
|
return messages.map((m) => {
|
|
317
261
|
const target = formatTarget(m);
|
package/dist/index.js
CHANGED
|
@@ -98,17 +98,19 @@ function toolRef(prefix, name) {
|
|
|
98
98
|
}
|
|
99
99
|
function buildBaseSystemPrompt(config, opts) {
|
|
100
100
|
const t = (name) => toolRef(opts.toolPrefix, name);
|
|
101
|
+
const messageDeliveryText = opts.includeStdinNotificationSection ? "New messages will be delivered to you automatically via stdin." : "The daemon will automatically restart you when new messages arrive.";
|
|
101
102
|
const criticalRules = [
|
|
102
103
|
`- Do NOT output text directly. ALL communication goes through ${t("send_message")}.`,
|
|
103
104
|
...opts.extraCriticalRules,
|
|
104
|
-
`- Do NOT explore the filesystem looking for messaging scripts. The MCP tools are already available
|
|
105
|
+
`- Do NOT explore the filesystem looking for messaging scripts. The MCP tools are already available.`,
|
|
106
|
+
`- NEVER start working on a task without claiming it first via ${t("claim_tasks")}. If the claim fails, do NOT work on it.`
|
|
105
107
|
];
|
|
106
108
|
const startupSteps = [
|
|
107
|
-
`1.
|
|
108
|
-
`2.
|
|
109
|
-
`3.
|
|
109
|
+
`1. If this turn already includes a concrete incoming message, first decide whether that message needs a visible acknowledgment, blocker question, or ownership signal. If it does, send it early with ${t("send_message")} before deep context gathering.`,
|
|
110
|
+
`2. Read MEMORY.md (in your cwd) and then only the additional memory/files you need to handle the current turn well.`,
|
|
111
|
+
`3. If there is no concrete incoming message to handle, stop and wait. ${messageDeliveryText}`,
|
|
110
112
|
`4. When you receive a message, process it and reply with ${t("send_message")}.`,
|
|
111
|
-
`5. **
|
|
113
|
+
`5. **Complete ALL your work before stopping.** If a task requires multi-step work (research, code changes, testing), finish everything, report results, then stop. New messages arrive automatically \u2014 you do not need to poll or wait for them.`
|
|
112
114
|
];
|
|
113
115
|
let prompt = `You are "${config.displayName || config.name}", an AI agent in Slock \u2014 a collaborative platform for human-AI collaboration.
|
|
114
116
|
|
|
@@ -120,18 +122,17 @@ You are a **long-running, persistent agent**. You are NOT a one-shot assistant \
|
|
|
120
122
|
|
|
121
123
|
You have MCP tools from the "chat" server. Use ONLY these for communication:
|
|
122
124
|
|
|
123
|
-
1. **${t("
|
|
124
|
-
2. **${t("
|
|
125
|
-
3. **${t("
|
|
126
|
-
4. **${t("
|
|
127
|
-
5. **${t("
|
|
128
|
-
6. **${t("
|
|
129
|
-
7. **${t("
|
|
130
|
-
8. **${t("
|
|
131
|
-
9. **${t("
|
|
132
|
-
10. **${t("
|
|
133
|
-
11. **${t("
|
|
134
|
-
12. **${t("view_file")}** \u2014 Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
|
|
125
|
+
1. **${t("check_messages")}** \u2014 Non-blocking check for new messages. Use freely during work \u2014 at natural breakpoints or after notifications.
|
|
126
|
+
2. **${t("send_message")}** \u2014 Send a message to a channel or DM.
|
|
127
|
+
3. **${t("list_server")}** \u2014 List all channels in this server, which ones you have joined, plus all agents and humans.
|
|
128
|
+
4. **${t("read_history")}** \u2014 Read past messages from a channel or DM.
|
|
129
|
+
5. **${t("list_tasks")}** \u2014 View a channel's task board.
|
|
130
|
+
6. **${t("create_tasks")}** \u2014 Create tasks on a channel's task board (supports batch).
|
|
131
|
+
7. **${t("claim_tasks")}** \u2014 Claim tasks by number (supports batch, handles conflicts).
|
|
132
|
+
8. **${t("unclaim_task")}** \u2014 Release your claim on a task.
|
|
133
|
+
9. **${t("update_task_status")}** \u2014 Change a task's status (e.g. to in_review or done).
|
|
134
|
+
10. **${t("upload_file")}** \u2014 Upload an image file to attach to a message. Returns an attachment ID to pass to send_message.
|
|
135
|
+
11. **${t("view_file")}** \u2014 Download an attached image by its attachment ID so you can view it. Use when messages contain image attachments.
|
|
135
136
|
|
|
136
137
|
CRITICAL RULES:
|
|
137
138
|
${criticalRules.join("\n")}
|
|
@@ -218,7 +219,7 @@ Each channel has a task board with two independent dimensions: **status** (progr
|
|
|
218
219
|
- **Unclaim**: \`unclaim_task(channel="#channel-name", task_number=3)\` \u2014 remove your assignment. Does not change progress status.
|
|
219
220
|
- **Update status**: \`update_task_status(channel="#channel-name", task_number=3, status="in_review")\` \u2014 move a task to a new status. Valid transitions: todo\u2192in_progress, in_progress\u2192in_review, in_progress\u2192done, in_review\u2192done, in_review\u2192in_progress.
|
|
220
221
|
|
|
221
|
-
**CRITICAL: You MUST claim a task before starting work on it.**
|
|
222
|
+
**CRITICAL: You MUST claim a task before starting ANY work on it.** Call \`${t("claim_tasks")}\` first. If the claim fails (someone else already claimed it), you MUST NOT work on that task \u2014 move on to another one. This is the only way to prevent duplicate work across agents. No exceptions.
|
|
222
223
|
|
|
223
224
|
**IMPORTANT: When you finish a task, use \`update_task_status(..., status="in_review")\`.** This gives humans a chance to validate your work before it's marked as done. Only set status to \`done\` directly for trivial tasks that don't need review.
|
|
224
225
|
|
|
@@ -251,7 +252,7 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
251
252
|
|
|
252
253
|
- **Don't interrupt ongoing conversations.** If a human is having a back-and-forth with another person (human or agent) on a topic, their follow-up messages are directed at that person \u2014 not at you. Do NOT jump in unless you are explicitly @mentioned or clearly addressed.
|
|
253
254
|
- **Only the person doing the work should report on it.** If someone else completed a task or submitted a PR, don't echo or summarize their work \u2014 let them respond to questions about it.
|
|
254
|
-
- **Claim before you start.**
|
|
255
|
+
- **Claim before you start.** Always call \`${t("claim_tasks")}\` before doing any work on a task. If the claim fails, stop immediately and pick a different task.
|
|
255
256
|
|
|
256
257
|
### Formatting \u2014 No HTML
|
|
257
258
|
|
|
@@ -340,7 +341,7 @@ While you are busy (executing tools, thinking, etc.), new messages may arrive. W
|
|
|
340
341
|
How to handle these:
|
|
341
342
|
- Call \`${t("check_messages")}()\` to check for new messages. You are encouraged to do this frequently \u2014 at natural breakpoints in your work, or whenever you see a notification.
|
|
342
343
|
- If the new message is higher priority, you may pivot to it. If not, continue your current work.
|
|
343
|
-
-
|
|
344
|
+
- \`check_messages\` returns instantly with any pending messages (or "no new messages"). It is always safe to call.`;
|
|
344
345
|
}
|
|
345
346
|
if (config.description) {
|
|
346
347
|
prompt += `
|
|
@@ -718,11 +719,10 @@ var CodexDriver = class {
|
|
|
718
719
|
return buildBaseSystemPrompt(config, {
|
|
719
720
|
toolPrefix: "",
|
|
720
721
|
extraCriticalRules: [
|
|
721
|
-
"- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
|
|
722
|
-
"- ALWAYS call wait_for_message after completing any task \u2014 this keeps you listening for new messages."
|
|
722
|
+
"- Do NOT use shell commands to send or receive messages. The MCP tools handle everything."
|
|
723
723
|
],
|
|
724
724
|
postStartupNotes: [
|
|
725
|
-
"**IMPORTANT**: Your process exits after each turn completes. You will be automatically restarted when new messages arrive.
|
|
725
|
+
"**IMPORTANT**: Your process exits after each turn completes. You will be automatically restarted when new messages arrive. Complete all your work, then stop \u2014 new messages will wake you up."
|
|
726
726
|
],
|
|
727
727
|
includeStdinNotificationSection: false
|
|
728
728
|
});
|
|
@@ -803,8 +803,23 @@ function toLocalTime(iso) {
|
|
|
803
803
|
const pad = (n) => String(n).padStart(2, "0");
|
|
804
804
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
805
805
|
}
|
|
806
|
+
function formatChannelLabel(message) {
|
|
807
|
+
return message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
|
|
808
|
+
}
|
|
809
|
+
function buildUnreadSummary(messages, excludeChannel) {
|
|
810
|
+
const summary = /* @__PURE__ */ new Map();
|
|
811
|
+
for (const message of messages) {
|
|
812
|
+
const label = formatChannelLabel(message);
|
|
813
|
+
if (excludeChannel && label === excludeChannel) continue;
|
|
814
|
+
summary.set(label, (summary.get(label) || 0) + 1);
|
|
815
|
+
}
|
|
816
|
+
return summary.size > 0 ? Object.fromEntries(summary) : void 0;
|
|
817
|
+
}
|
|
806
818
|
var MAX_TRAJECTORY_TEXT = 2e3;
|
|
807
819
|
var ACTIVITY_HEARTBEAT_MS = 6e4;
|
|
820
|
+
function getMessageDeliveryText(supportsStdinNotification) {
|
|
821
|
+
return supportsStdinNotification ? "New messages will be delivered to you automatically via stdin." : "The daemon will automatically restart you when new messages arrive.";
|
|
822
|
+
}
|
|
808
823
|
var AgentProcessManager = class {
|
|
809
824
|
agents = /* @__PURE__ */ new Map();
|
|
810
825
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
@@ -814,17 +829,21 @@ var AgentProcessManager = class {
|
|
|
814
829
|
chatBridgePath;
|
|
815
830
|
sendToServer;
|
|
816
831
|
daemonApiKey;
|
|
817
|
-
|
|
832
|
+
dataDir;
|
|
833
|
+
driverResolver;
|
|
834
|
+
constructor(chatBridgePath2, sendToServer, daemonApiKey, opts = {}) {
|
|
818
835
|
this.chatBridgePath = chatBridgePath2;
|
|
819
836
|
this.sendToServer = sendToServer;
|
|
820
837
|
this.daemonApiKey = daemonApiKey;
|
|
838
|
+
this.dataDir = opts.dataDir || DATA_DIR;
|
|
839
|
+
this.driverResolver = opts.driverResolver || getDriver;
|
|
821
840
|
}
|
|
822
841
|
async startAgent(agentId, config, wakeMessage, unreadSummary) {
|
|
823
842
|
if (this.agents.has(agentId) || this.agentsStarting.has(agentId)) return;
|
|
824
843
|
this.agentsStarting.add(agentId);
|
|
825
844
|
try {
|
|
826
|
-
const driver =
|
|
827
|
-
const agentDataDir = path3.join(
|
|
845
|
+
const driver = this.driverResolver(config.runtime || "claude");
|
|
846
|
+
const agentDataDir = path3.join(this.dataDir, agentId);
|
|
828
847
|
await mkdir(agentDataDir, { recursive: true });
|
|
829
848
|
const memoryMdPath = path3.join(agentDataDir, "MEMORY.md");
|
|
830
849
|
try {
|
|
@@ -872,9 +891,9 @@ Use read_history to catch up, or respond to the message above first.`;
|
|
|
872
891
|
}
|
|
873
892
|
prompt += `
|
|
874
893
|
|
|
875
|
-
Respond as appropriate \u2014 reply using send_message, or take action as needed.
|
|
894
|
+
Respond as appropriate \u2014 reply using send_message, or take action as needed. Complete ALL your work before stopping.
|
|
876
895
|
|
|
877
|
-
IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before
|
|
896
|
+
IMPORTANT: If the message requires multi-step work (e.g. research, code changes, testing), complete ALL steps before stopping. Sending a progress update does NOT mean your task is done \u2014 only stop when you have NO more work to do. ${getMessageDeliveryText(driver.supportsStdinNotification)}`;
|
|
878
897
|
if (driver.supportsStdinNotification) {
|
|
879
898
|
prompt += `
|
|
880
899
|
|
|
@@ -888,14 +907,14 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
888
907
|
}
|
|
889
908
|
prompt += `
|
|
890
909
|
|
|
891
|
-
Use read_history to catch up on
|
|
910
|
+
Use read_history to catch up on the channels listed above, then stop. Read each listed channel at most once unless a read fails. Do NOT call check_messages in this mode. If the history reveals a direct request, assignment, @mention, review request, or task clearly addressed to you, switch into active handling instead of stopping: reply with send_message and claim the relevant task before starting work. Otherwise, do NOT send any message in this mode. ${getMessageDeliveryText(driver.supportsStdinNotification)}`;
|
|
892
911
|
if (driver.supportsStdinNotification) {
|
|
893
912
|
prompt += `
|
|
894
913
|
|
|
895
914
|
Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.`;
|
|
896
915
|
}
|
|
897
916
|
} else if (isResume) {
|
|
898
|
-
prompt = `No new messages while you were away.
|
|
917
|
+
prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver.supportsStdinNotification)}`;
|
|
899
918
|
if (driver.supportsStdinNotification) {
|
|
900
919
|
prompt += `
|
|
901
920
|
|
|
@@ -916,10 +935,9 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
916
935
|
process: proc,
|
|
917
936
|
driver,
|
|
918
937
|
inbox: [],
|
|
919
|
-
pendingReceive: null,
|
|
920
938
|
config,
|
|
921
939
|
sessionId: config.sessionId || null,
|
|
922
|
-
|
|
940
|
+
isIdle: false,
|
|
923
941
|
notificationTimer: null,
|
|
924
942
|
pendingNotificationCount: 0,
|
|
925
943
|
activityHeartbeat: null,
|
|
@@ -952,10 +970,6 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
952
970
|
if (this.agents.has(agentId)) {
|
|
953
971
|
const ap = this.agents.get(agentId);
|
|
954
972
|
if (ap.process !== proc) return;
|
|
955
|
-
if (ap.pendingReceive) {
|
|
956
|
-
clearTimeout(ap.pendingReceive.timer);
|
|
957
|
-
ap.pendingReceive.resolve([]);
|
|
958
|
-
}
|
|
959
973
|
if (ap.notificationTimer) {
|
|
960
974
|
clearTimeout(ap.notificationTimer);
|
|
961
975
|
}
|
|
@@ -964,6 +978,26 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
964
978
|
}
|
|
965
979
|
this.agents.delete(agentId);
|
|
966
980
|
if (code === 0) {
|
|
981
|
+
const queuedWakeMessage = !ap.driver.supportsStdinNotification ? ap.inbox.shift() : void 0;
|
|
982
|
+
const unreadSummary2 = queuedWakeMessage ? buildUnreadSummary(ap.inbox, formatChannelLabel(queuedWakeMessage)) : void 0;
|
|
983
|
+
if (queuedWakeMessage) {
|
|
984
|
+
const nextConfig = { ...ap.config, sessionId: ap.sessionId };
|
|
985
|
+
this.idleAgentConfigs.set(agentId, {
|
|
986
|
+
config: nextConfig,
|
|
987
|
+
sessionId: ap.sessionId
|
|
988
|
+
});
|
|
989
|
+
this.broadcastActivity(agentId, "working", "Message received");
|
|
990
|
+
this.idleAgentConfigs.delete(agentId);
|
|
991
|
+
this.startAgent(agentId, nextConfig, queuedWakeMessage, unreadSummary2).catch((err) => {
|
|
992
|
+
console.error(`[Agent ${agentId}] Failed to continue with queued message:`, err);
|
|
993
|
+
this.idleAgentConfigs.set(agentId, {
|
|
994
|
+
config: nextConfig,
|
|
995
|
+
sessionId: ap.sessionId
|
|
996
|
+
});
|
|
997
|
+
this.broadcastActivity(agentId, "online", "Process idle");
|
|
998
|
+
});
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
967
1001
|
this.idleAgentConfigs.set(agentId, {
|
|
968
1002
|
config: { ...ap.config, sessionId: ap.sessionId },
|
|
969
1003
|
sessionId: ap.sessionId
|
|
@@ -989,10 +1023,6 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
989
1023
|
this.idleAgentConfigs.delete(agentId);
|
|
990
1024
|
const ap = this.agents.get(agentId);
|
|
991
1025
|
if (!ap) return;
|
|
992
|
-
if (ap.pendingReceive) {
|
|
993
|
-
clearTimeout(ap.pendingReceive.timer);
|
|
994
|
-
ap.pendingReceive.resolve([]);
|
|
995
|
-
}
|
|
996
1026
|
if (ap.notificationTimer) {
|
|
997
1027
|
clearTimeout(ap.notificationTimer);
|
|
998
1028
|
}
|
|
@@ -1038,19 +1068,14 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1038
1068
|
}
|
|
1039
1069
|
return;
|
|
1040
1070
|
}
|
|
1041
|
-
if (ap.
|
|
1042
|
-
ap.
|
|
1071
|
+
if (ap.isIdle && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
1072
|
+
ap.isIdle = false;
|
|
1043
1073
|
this.broadcastActivity(agentId, "working", "Message received");
|
|
1074
|
+
this.deliverMessageViaStdin(agentId, ap, message);
|
|
1075
|
+
return;
|
|
1044
1076
|
}
|
|
1045
|
-
|
|
1046
|
-
clearTimeout(ap.pendingReceive.timer);
|
|
1047
|
-
ap.pendingReceive.resolve([message]);
|
|
1048
|
-
ap.pendingReceive = null;
|
|
1049
|
-
} else {
|
|
1050
|
-
ap.inbox.push(message);
|
|
1051
|
-
}
|
|
1077
|
+
ap.inbox.push(message);
|
|
1052
1078
|
if (!ap.driver.supportsStdinNotification) return;
|
|
1053
|
-
if (ap.isInReceiveMessage) return;
|
|
1054
1079
|
if (!ap.sessionId) return;
|
|
1055
1080
|
ap.pendingNotificationCount++;
|
|
1056
1081
|
if (!ap.notificationTimer) {
|
|
@@ -1060,7 +1085,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1060
1085
|
}
|
|
1061
1086
|
}
|
|
1062
1087
|
async resetWorkspace(agentId) {
|
|
1063
|
-
const agentDataDir = path3.join(
|
|
1088
|
+
const agentDataDir = path3.join(this.dataDir, agentId);
|
|
1064
1089
|
try {
|
|
1065
1090
|
await rm(agentDataDir, { recursive: true, force: true });
|
|
1066
1091
|
console.log(`[Agent ${agentId}] Workspace deleted: ${agentDataDir}`);
|
|
@@ -1081,7 +1106,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1081
1106
|
const results = [];
|
|
1082
1107
|
let entries;
|
|
1083
1108
|
try {
|
|
1084
|
-
entries = await readdir(
|
|
1109
|
+
entries = await readdir(this.dataDir, { withFileTypes: true });
|
|
1085
1110
|
} catch {
|
|
1086
1111
|
return [];
|
|
1087
1112
|
}
|
|
@@ -1124,7 +1149,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1124
1149
|
if (directoryName.includes("/") || directoryName.includes("..") || directoryName.includes("\\")) {
|
|
1125
1150
|
return false;
|
|
1126
1151
|
}
|
|
1127
|
-
const targetDir = path3.join(
|
|
1152
|
+
const targetDir = path3.join(this.dataDir, directoryName);
|
|
1128
1153
|
try {
|
|
1129
1154
|
await rm(targetDir, { recursive: true, force: true });
|
|
1130
1155
|
console.log(`[Workspace] Deleted directory: ${targetDir}`);
|
|
@@ -1136,7 +1161,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1136
1161
|
}
|
|
1137
1162
|
// Workspace file browsing
|
|
1138
1163
|
async getFileTree(agentId, dirPath) {
|
|
1139
|
-
const agentDir = path3.join(
|
|
1164
|
+
const agentDir = path3.join(this.dataDir, agentId);
|
|
1140
1165
|
try {
|
|
1141
1166
|
await stat(agentDir);
|
|
1142
1167
|
} catch {
|
|
@@ -1153,7 +1178,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1153
1178
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
1154
1179
|
}
|
|
1155
1180
|
async readFile(agentId, filePath) {
|
|
1156
|
-
const agentDir = path3.join(
|
|
1181
|
+
const agentDir = path3.join(this.dataDir, agentId);
|
|
1157
1182
|
const resolved = path3.resolve(agentDir, filePath);
|
|
1158
1183
|
if (!resolved.startsWith(agentDir + path3.sep) && resolved !== agentDir) {
|
|
1159
1184
|
throw new Error("Access denied");
|
|
@@ -1229,40 +1254,34 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1229
1254
|
const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
|
|
1230
1255
|
const extra = text ? [{ kind: "thinking", text }] : [];
|
|
1231
1256
|
this.broadcastActivity(agentId, "thinking", "", extra);
|
|
1232
|
-
if (ap) ap.
|
|
1257
|
+
if (ap) ap.isIdle = false;
|
|
1233
1258
|
break;
|
|
1234
1259
|
}
|
|
1235
1260
|
case "text": {
|
|
1236
1261
|
const text = event.text.length > MAX_TRAJECTORY_TEXT ? event.text.slice(0, MAX_TRAJECTORY_TEXT) + "\u2026" : event.text;
|
|
1237
1262
|
this.broadcastActivity(agentId, "thinking", "", [{ kind: "text", text }]);
|
|
1238
|
-
if (ap) ap.
|
|
1263
|
+
if (ap) ap.isIdle = false;
|
|
1239
1264
|
break;
|
|
1240
1265
|
}
|
|
1241
1266
|
case "tool_call": {
|
|
1242
1267
|
const toolName = event.name;
|
|
1243
1268
|
const inputSummary = driver.summarizeToolInput(toolName, event.input);
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
ap.isInReceiveMessage = true;
|
|
1248
|
-
ap.pendingNotificationCount = 0;
|
|
1249
|
-
if (ap.notificationTimer) {
|
|
1250
|
-
clearTimeout(ap.notificationTimer);
|
|
1251
|
-
ap.notificationTimer = null;
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
} else {
|
|
1255
|
-
const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
|
|
1256
|
-
this.broadcastActivity(agentId, "working", detail, [{ kind: "tool_start", toolName, toolInput: inputSummary }]);
|
|
1257
|
-
if (ap) ap.isInReceiveMessage = false;
|
|
1258
|
-
}
|
|
1269
|
+
const detail = toolName === `${driver.mcpToolPrefix}check_messages` ? "Checking messages\u2026" : toolName === `${driver.mcpToolPrefix}send_message` ? "Sending message\u2026" : driver.toolDisplayName(toolName);
|
|
1270
|
+
this.broadcastActivity(agentId, "working", detail, [{ kind: "tool_start", toolName, toolInput: inputSummary }]);
|
|
1271
|
+
if (ap) ap.isIdle = false;
|
|
1259
1272
|
break;
|
|
1260
1273
|
}
|
|
1261
1274
|
case "turn_end":
|
|
1262
|
-
this.broadcastActivity(agentId, "online", "Turn complete");
|
|
1263
1275
|
if (ap) {
|
|
1264
|
-
ap.isInReceiveMessage = false;
|
|
1265
1276
|
if (event.sessionId) ap.sessionId = event.sessionId;
|
|
1277
|
+
if (ap.inbox.length > 0 && ap.driver.supportsStdinNotification && ap.sessionId) {
|
|
1278
|
+
const nextMessage = ap.inbox.shift();
|
|
1279
|
+
this.broadcastActivity(agentId, "working", "Message received");
|
|
1280
|
+
this.deliverMessageViaStdin(agentId, ap, nextMessage);
|
|
1281
|
+
} else {
|
|
1282
|
+
ap.isIdle = true;
|
|
1283
|
+
this.broadcastActivity(agentId, "online", "Idle");
|
|
1284
|
+
}
|
|
1266
1285
|
}
|
|
1267
1286
|
if (event.sessionId) {
|
|
1268
1287
|
this.sendToServer({ type: "agent:session", agentId, sessionId: event.sessionId });
|
|
@@ -1284,7 +1303,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1284
1303
|
ap.pendingNotificationCount = 0;
|
|
1285
1304
|
ap.notificationTimer = null;
|
|
1286
1305
|
if (count === 0) return;
|
|
1287
|
-
if (ap.
|
|
1306
|
+
if (ap.isIdle) return;
|
|
1288
1307
|
if (!ap.sessionId) return;
|
|
1289
1308
|
const notification = `[System notification: You have ${count} new message${count > 1 ? "s" : ""} waiting. Call check_messages to read ${count > 1 ? "them" : "it"} when you're ready.]`;
|
|
1290
1309
|
console.log(`[Agent ${agentId}] Sending stdin notification: ${count} message(s)`);
|
|
@@ -1293,6 +1312,23 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1293
1312
|
ap.process.stdin?.write(encoded + "\n");
|
|
1294
1313
|
}
|
|
1295
1314
|
}
|
|
1315
|
+
/** Deliver a message to an agent via stdin, formatting it the same way as the MCP bridge */
|
|
1316
|
+
deliverMessageViaStdin(agentId, ap, message) {
|
|
1317
|
+
const channelLabel = message.channel_type === "dm" ? `DM:@${message.channel_name}` : `#${message.channel_name}`;
|
|
1318
|
+
const senderPrefix = message.sender_type === "agent" ? "(agent) " : "";
|
|
1319
|
+
const time = message.timestamp ? ` (${toLocalTime(message.timestamp)})` : "";
|
|
1320
|
+
const formatted = `[${channelLabel}]${time} ${senderPrefix}@${message.sender_name}: ${message.content}`;
|
|
1321
|
+
const prompt = `New message received:
|
|
1322
|
+
|
|
1323
|
+
${formatted}
|
|
1324
|
+
|
|
1325
|
+
Respond as appropriate. Complete all your work before stopping.`;
|
|
1326
|
+
const encoded = ap.driver.encodeStdinMessage(prompt, ap.sessionId);
|
|
1327
|
+
if (encoded) {
|
|
1328
|
+
console.log(`[Agent ${agentId}] Delivering message via stdin from @${message.sender_name}`);
|
|
1329
|
+
ap.process.stdin?.write(encoded + "\n");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1296
1332
|
/** List ONE level of a directory — directories returned without children (lazy-loaded on demand) */
|
|
1297
1333
|
async listDirectoryChildren(dir, rootDir) {
|
|
1298
1334
|
let entries;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slock-ai/daemon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"slock-daemon": "dist/index.js"
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dev": "tsx watch src/index.ts",
|
|
33
33
|
"start": "tsx src/index.ts",
|
|
34
34
|
"build": "tsup",
|
|
35
|
+
"test": "node --import tsx --test src/**/*.test.ts",
|
|
35
36
|
"typecheck": "tsc --noEmit",
|
|
36
37
|
"release:patch": "npm version patch --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|
|
37
38
|
"release:minor": "npm version minor --no-git-tag-version && cd ../.. && pnpm install --lockfile-only && git add packages/daemon/package.json pnpm-lock.yaml && git commit -m \"chore: bump @slock-ai/daemon to v$(node -p \"require('./packages/daemon/package.json').version\")\" && git tag daemon-v$(node -p \"require('./packages/daemon/package.json').version\") && git push && git push --tags",
|