@slock-ai/daemon 0.25.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/index.js +58 -17
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -98,15 +98,17 @@ 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
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
|
];
|
|
@@ -217,7 +219,7 @@ Each channel has a task board with two independent dimensions: **status** (progr
|
|
|
217
219
|
- **Unclaim**: \`unclaim_task(channel="#channel-name", task_number=3)\` \u2014 remove your assignment. Does not change progress status.
|
|
218
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.
|
|
219
221
|
|
|
220
|
-
**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.
|
|
221
223
|
|
|
222
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.
|
|
223
225
|
|
|
@@ -250,7 +252,7 @@ Keep the user informed. They cannot see your internal reasoning, so:
|
|
|
250
252
|
|
|
251
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.
|
|
252
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.
|
|
253
|
-
- **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.
|
|
254
256
|
|
|
255
257
|
### Formatting \u2014 No HTML
|
|
256
258
|
|
|
@@ -801,8 +803,23 @@ function toLocalTime(iso) {
|
|
|
801
803
|
const pad = (n) => String(n).padStart(2, "0");
|
|
802
804
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
803
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
|
+
}
|
|
804
818
|
var MAX_TRAJECTORY_TEXT = 2e3;
|
|
805
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
|
+
}
|
|
806
823
|
var AgentProcessManager = class {
|
|
807
824
|
agents = /* @__PURE__ */ new Map();
|
|
808
825
|
agentsStarting = /* @__PURE__ */ new Set();
|
|
@@ -812,17 +829,21 @@ var AgentProcessManager = class {
|
|
|
812
829
|
chatBridgePath;
|
|
813
830
|
sendToServer;
|
|
814
831
|
daemonApiKey;
|
|
815
|
-
|
|
832
|
+
dataDir;
|
|
833
|
+
driverResolver;
|
|
834
|
+
constructor(chatBridgePath2, sendToServer, daemonApiKey, opts = {}) {
|
|
816
835
|
this.chatBridgePath = chatBridgePath2;
|
|
817
836
|
this.sendToServer = sendToServer;
|
|
818
837
|
this.daemonApiKey = daemonApiKey;
|
|
838
|
+
this.dataDir = opts.dataDir || DATA_DIR;
|
|
839
|
+
this.driverResolver = opts.driverResolver || getDriver;
|
|
819
840
|
}
|
|
820
841
|
async startAgent(agentId, config, wakeMessage, unreadSummary) {
|
|
821
842
|
if (this.agents.has(agentId) || this.agentsStarting.has(agentId)) return;
|
|
822
843
|
this.agentsStarting.add(agentId);
|
|
823
844
|
try {
|
|
824
|
-
const driver =
|
|
825
|
-
const agentDataDir = path3.join(
|
|
845
|
+
const driver = this.driverResolver(config.runtime || "claude");
|
|
846
|
+
const agentDataDir = path3.join(this.dataDir, agentId);
|
|
826
847
|
await mkdir(agentDataDir, { recursive: true });
|
|
827
848
|
const memoryMdPath = path3.join(agentDataDir, "MEMORY.md");
|
|
828
849
|
try {
|
|
@@ -872,7 +893,7 @@ Use read_history to catch up, or respond to the message above first.`;
|
|
|
872
893
|
|
|
873
894
|
Respond as appropriate \u2014 reply using send_message, or take action as needed. Complete ALL your work before stopping.
|
|
874
895
|
|
|
875
|
-
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.
|
|
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)}`;
|
|
876
897
|
if (driver.supportsStdinNotification) {
|
|
877
898
|
prompt += `
|
|
878
899
|
|
|
@@ -886,14 +907,14 @@ Note: While you are busy, you may receive [System notification: ...] messages. F
|
|
|
886
907
|
}
|
|
887
908
|
prompt += `
|
|
888
909
|
|
|
889
|
-
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)}`;
|
|
890
911
|
if (driver.supportsStdinNotification) {
|
|
891
912
|
prompt += `
|
|
892
913
|
|
|
893
914
|
Note: While you are busy, you may receive [System notification: ...] messages. Finish your current step, then call check_messages to check for messages.`;
|
|
894
915
|
}
|
|
895
916
|
} else if (isResume) {
|
|
896
|
-
prompt = `No new messages while you were away. Nothing to do \u2014 just stop.
|
|
917
|
+
prompt = `No new messages while you were away. Nothing to do \u2014 just stop. ${getMessageDeliveryText(driver.supportsStdinNotification)}`;
|
|
897
918
|
if (driver.supportsStdinNotification) {
|
|
898
919
|
prompt += `
|
|
899
920
|
|
|
@@ -957,6 +978,26 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
957
978
|
}
|
|
958
979
|
this.agents.delete(agentId);
|
|
959
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
|
+
}
|
|
960
1001
|
this.idleAgentConfigs.set(agentId, {
|
|
961
1002
|
config: { ...ap.config, sessionId: ap.sessionId },
|
|
962
1003
|
sessionId: ap.sessionId
|
|
@@ -1044,7 +1085,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1044
1085
|
}
|
|
1045
1086
|
}
|
|
1046
1087
|
async resetWorkspace(agentId) {
|
|
1047
|
-
const agentDataDir = path3.join(
|
|
1088
|
+
const agentDataDir = path3.join(this.dataDir, agentId);
|
|
1048
1089
|
try {
|
|
1049
1090
|
await rm(agentDataDir, { recursive: true, force: true });
|
|
1050
1091
|
console.log(`[Agent ${agentId}] Workspace deleted: ${agentDataDir}`);
|
|
@@ -1065,7 +1106,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1065
1106
|
const results = [];
|
|
1066
1107
|
let entries;
|
|
1067
1108
|
try {
|
|
1068
|
-
entries = await readdir(
|
|
1109
|
+
entries = await readdir(this.dataDir, { withFileTypes: true });
|
|
1069
1110
|
} catch {
|
|
1070
1111
|
return [];
|
|
1071
1112
|
}
|
|
@@ -1108,7 +1149,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1108
1149
|
if (directoryName.includes("/") || directoryName.includes("..") || directoryName.includes("\\")) {
|
|
1109
1150
|
return false;
|
|
1110
1151
|
}
|
|
1111
|
-
const targetDir = path3.join(
|
|
1152
|
+
const targetDir = path3.join(this.dataDir, directoryName);
|
|
1112
1153
|
try {
|
|
1113
1154
|
await rm(targetDir, { recursive: true, force: true });
|
|
1114
1155
|
console.log(`[Workspace] Deleted directory: ${targetDir}`);
|
|
@@ -1120,7 +1161,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1120
1161
|
}
|
|
1121
1162
|
// Workspace file browsing
|
|
1122
1163
|
async getFileTree(agentId, dirPath) {
|
|
1123
|
-
const agentDir = path3.join(
|
|
1164
|
+
const agentDir = path3.join(this.dataDir, agentId);
|
|
1124
1165
|
try {
|
|
1125
1166
|
await stat(agentDir);
|
|
1126
1167
|
} catch {
|
|
@@ -1137,7 +1178,7 @@ Note: While you are busy, you may receive [System notification: ...] messages ab
|
|
|
1137
1178
|
return this.listDirectoryChildren(targetDir, agentDir);
|
|
1138
1179
|
}
|
|
1139
1180
|
async readFile(agentId, filePath) {
|
|
1140
|
-
const agentDir = path3.join(
|
|
1181
|
+
const agentDir = path3.join(this.dataDir, agentId);
|
|
1141
1182
|
const resolved = path3.resolve(agentDir, filePath);
|
|
1142
1183
|
if (!resolved.startsWith(agentDir + path3.sep) && resolved !== agentDir) {
|
|
1143
1184
|
throw new Error("Access denied");
|
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",
|