@botcord/daemon 0.2.80 → 0.2.82
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/daemon-singleton.js
CHANGED
|
@@ -147,12 +147,30 @@ export function removePidFile(pidPath = PID_PATH) {
|
|
|
147
147
|
// ignore
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
function isBotCordDaemonStartCommand(command) {
|
|
150
|
+
export function isBotCordDaemonStartCommand(command) {
|
|
151
151
|
if (!/\bstart\b/.test(command))
|
|
152
152
|
return false;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
// Only treat a row as "the daemon" when argv[0] is the real entry point:
|
|
154
|
+
// either the node interpreter (running `dist/index.js`) or the resolved
|
|
155
|
+
// `botcord-daemon` bin shim. Reject shell wrappers (`sh`, `bash`, `npm`,
|
|
156
|
+
// `npx`, `timeout`, ...) — their argv mentions `botcord-daemon` only as a
|
|
157
|
+
// literal arg, not as the executable being run. Killing a wrapper takes
|
|
158
|
+
// out the actual daemon it started, which is exactly the bug we want
|
|
159
|
+
// to avoid in cloud sandboxes.
|
|
160
|
+
const exe = (command.trim().split(/\s+/, 1)[0] ?? "").split("/").pop() ?? "";
|
|
161
|
+
const isNode = /^node(\d.*)?$/.test(exe);
|
|
162
|
+
const isDaemonBin = exe === "botcord-daemon";
|
|
163
|
+
if (!isNode && !isDaemonBin)
|
|
164
|
+
return false;
|
|
165
|
+
return (
|
|
166
|
+
// node-resolved bin shim, e.g. .../node_modules/.bin/botcord-daemon
|
|
167
|
+
/\/\.?bin\/botcord-daemon(?:\s|$)/.test(command) ||
|
|
168
|
+
// direct bin invocation, e.g. /usr/local/bin/botcord-daemon or argv[0]=botcord-daemon
|
|
169
|
+
/(?:^|\s)\S*botcord-daemon(?:\s|$)/.test(command) ||
|
|
170
|
+
// node running the published daemon entry script
|
|
171
|
+
/\bbotcord\S*\/daemon\/dist\/index\.js(?:\s|$)/.test(command) ||
|
|
172
|
+
// node running the in-repo daemon entry script (dev / monorepo)
|
|
173
|
+
/\bpackages\/daemon\/dist\/index\.js(?:\s|$)/.test(command));
|
|
156
174
|
}
|
|
157
175
|
function delay(ms) {
|
|
158
176
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botcord/daemon",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.82",
|
|
4
4
|
"description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@botcord/cli": "^0.1.7",
|
|
31
|
-
"@botcord/protocol-core": "^0.2.
|
|
31
|
+
"@botcord/protocol-core": "^0.2.11",
|
|
32
32
|
"@larksuiteoapi/node-sdk": "^1.63.1",
|
|
33
33
|
"ws": "^8.20.1"
|
|
34
34
|
},
|
|
@@ -5,6 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
6
6
|
import {
|
|
7
7
|
ensureNoOtherDaemonFromPidFile,
|
|
8
|
+
isBotCordDaemonStartCommand,
|
|
8
9
|
parseDaemonProcesses,
|
|
9
10
|
readPid,
|
|
10
11
|
removePidFile,
|
|
@@ -90,6 +91,38 @@ describe("daemon singleton pid helpers", () => {
|
|
|
90
91
|
expect(out.map((p) => p.pid)).toEqual([111, 222]);
|
|
91
92
|
});
|
|
92
93
|
|
|
94
|
+
it("does not match shell wrappers whose argv mentions botcord-daemon as a literal", () => {
|
|
95
|
+
// These are the wrapper command lines we observed in cloud sandboxes;
|
|
96
|
+
// they must NOT be classified as the daemon, otherwise the singleton
|
|
97
|
+
// check kills the wrapper and takes the actual daemon down with it.
|
|
98
|
+
const wrappers = [
|
|
99
|
+
"npm exec --yes --package @botcord/daemon@latest -- botcord-daemon start --foreground",
|
|
100
|
+
"npx --yes --package @botcord/daemon@latest -- botcord-daemon start --foreground",
|
|
101
|
+
"sh -c botcord-daemon start --foreground",
|
|
102
|
+
"/bin/bash -l -c export npm_config_cache=/tmp/c; npm exec --yes --package @botcord/daemon@latest -- botcord-daemon start --foreground",
|
|
103
|
+
"timeout 30 npm exec --yes --package @botcord/daemon@latest -- botcord-daemon start --foreground",
|
|
104
|
+
];
|
|
105
|
+
for (const cmd of wrappers) {
|
|
106
|
+
expect(isBotCordDaemonStartCommand(cmd), `wrongly matched wrapper: ${cmd}`).toBe(false);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("matches the actual daemon entry processes", () => {
|
|
111
|
+
const matches = [
|
|
112
|
+
// node running the published daemon (npx / npm exec resolution under _npx)
|
|
113
|
+
"node /tmp/botcord-npm-cache/_npx/abc123/node_modules/@botcord/daemon/dist/index.js start --foreground",
|
|
114
|
+
// node running the resolved bin shim
|
|
115
|
+
"node /Users/me/.botcord/daemon/node_modules/.bin/botcord-daemon start --foreground",
|
|
116
|
+
// direct invocation of the bin
|
|
117
|
+
"/usr/local/bin/botcord-daemon start --foreground",
|
|
118
|
+
// monorepo dev: node running packages/daemon/dist/index.js
|
|
119
|
+
"node /home/dev/botcord/packages/daemon/dist/index.js start --foreground",
|
|
120
|
+
];
|
|
121
|
+
for (const cmd of matches) {
|
|
122
|
+
expect(isBotCordDaemonStartCommand(cmd), `failed to match daemon: ${cmd}`).toBe(true);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
93
126
|
it("terminates extra daemon processes discovered outside the pid file", async () => {
|
|
94
127
|
const child = spawn(process.execPath, ["-e", "setInterval(() => {}, 1000)"], {
|
|
95
128
|
stdio: "ignore",
|
package/src/daemon-singleton.ts
CHANGED
|
@@ -193,12 +193,28 @@ export function removePidFile(pidPath = PID_PATH): void {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
function isBotCordDaemonStartCommand(command: string): boolean {
|
|
196
|
+
export function isBotCordDaemonStartCommand(command: string): boolean {
|
|
197
197
|
if (!/\bstart\b/.test(command)) return false;
|
|
198
|
+
// Only treat a row as "the daemon" when argv[0] is the real entry point:
|
|
199
|
+
// either the node interpreter (running `dist/index.js`) or the resolved
|
|
200
|
+
// `botcord-daemon` bin shim. Reject shell wrappers (`sh`, `bash`, `npm`,
|
|
201
|
+
// `npx`, `timeout`, ...) — their argv mentions `botcord-daemon` only as a
|
|
202
|
+
// literal arg, not as the executable being run. Killing a wrapper takes
|
|
203
|
+
// out the actual daemon it started, which is exactly the bug we want
|
|
204
|
+
// to avoid in cloud sandboxes.
|
|
205
|
+
const exe = (command.trim().split(/\s+/, 1)[0] ?? "").split("/").pop() ?? "";
|
|
206
|
+
const isNode = /^node(\d.*)?$/.test(exe);
|
|
207
|
+
const isDaemonBin = exe === "botcord-daemon";
|
|
208
|
+
if (!isNode && !isDaemonBin) return false;
|
|
198
209
|
return (
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
/
|
|
210
|
+
// node-resolved bin shim, e.g. .../node_modules/.bin/botcord-daemon
|
|
211
|
+
/\/\.?bin\/botcord-daemon(?:\s|$)/.test(command) ||
|
|
212
|
+
// direct bin invocation, e.g. /usr/local/bin/botcord-daemon or argv[0]=botcord-daemon
|
|
213
|
+
/(?:^|\s)\S*botcord-daemon(?:\s|$)/.test(command) ||
|
|
214
|
+
// node running the published daemon entry script
|
|
215
|
+
/\bbotcord\S*\/daemon\/dist\/index\.js(?:\s|$)/.test(command) ||
|
|
216
|
+
// node running the in-repo daemon entry script (dev / monorepo)
|
|
217
|
+
/\bpackages\/daemon\/dist\/index\.js(?:\s|$)/.test(command)
|
|
202
218
|
);
|
|
203
219
|
}
|
|
204
220
|
|