@botcord/daemon 0.2.67 → 0.2.69

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 CHANGED
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync, rmSync } from "node:fs";
4
4
  import { homedir, hostname } from "node:os";
5
5
  import path from "node:path";
6
+ import { augmentProcessPath } from "./path-env.js";
6
7
  import { loadConfig, saveConfig, initDefaultConfig, resolveConfiguredAgentIds, PID_PATH, SNAPSHOT_PATH, CONFIG_FILE_PATH, CONFIG_MISSING, } from "./config.js";
7
8
  import { resolveBootAgents } from "./agent-discovery.js";
8
9
  import { defaultTranscriptRoot, resolveTranscriptEnabled, transcriptAgentRoot, transcriptFilePath, } from "./gateway/index.js";
@@ -18,6 +19,7 @@ import { clearWorkingMemory, readWorkingMemory, resolveMemoryDir, updateWorkingM
18
19
  import { createDiagnosticBundle } from "./diagnostics.js";
19
20
  import { resolveStartAuthAction } from "./start-auth.js";
20
21
  import { discoverLocalOpenclawGateways, mergeOpenclawGateways, openclawDiscoveryConfigEnabled, } from "./openclaw-discovery.js";
22
+ augmentProcessPath();
21
23
  const ADAPTER_LIST = listAdapterIds().join("|");
22
24
  const DEFAULT_HUB = "https://api.botcord.chat";
23
25
  /**
package/dist/loop-risk.js CHANGED
@@ -62,12 +62,26 @@ export function stripBotCordPromptScaffolding(text) {
62
62
  return false;
63
63
  if (line.startsWith("[Room Rule]"))
64
64
  return false;
65
- if (line.startsWith("[In group chats, do NOT reply"))
65
+ if (line.startsWith("[In group chats,"))
66
+ return false;
67
+ if (line.startsWith("This group-reply restriction"))
68
+ return false;
69
+ if (line.startsWith("including analyzing the message"))
70
+ return false;
71
+ if (line.startsWith("forwarding a summary"))
72
+ return false;
73
+ if (line.startsWith("When a message matches an active monitoring rule"))
74
+ return false;
75
+ if (line.startsWith("keyword, sender rule"))
76
+ return false;
77
+ if (line.startsWith("you do not reply to the group"))
66
78
  return false;
67
79
  if (line.startsWith("[If the conversation has naturally concluded"))
68
80
  return false;
69
81
  if (line.startsWith("[You received a contact request"))
70
82
  return false;
83
+ if (line.includes("no background action is needed"))
84
+ return false;
71
85
  if (line.includes('reply with exactly "NO_REPLY"'))
72
86
  return false;
73
87
  if (line.startsWith("<agent-message"))
@@ -0,0 +1,8 @@
1
+ export declare function commonDaemonPathEntries(home?: string | undefined): string[];
2
+ export declare function mergePathEntries(basePath: string | undefined, extras: string[]): string;
3
+ /**
4
+ * GUI-launched macOS apps inherit a sparse launchd PATH and do not read the
5
+ * user's shell profile. Add common per-user CLI install locations so runtime
6
+ * adapters can find tools installed by uv/pipx, cargo, bun, npm, etc.
7
+ */
8
+ export declare function augmentProcessPath(): void;
@@ -0,0 +1,43 @@
1
+ import path from "node:path";
2
+ const COMMON_USER_BIN_RELATIVE_PATHS = [
3
+ ".botcord/bin",
4
+ ".local/bin",
5
+ ".cargo/bin",
6
+ ".bun/bin",
7
+ ".deno/bin",
8
+ ".npm-global/bin",
9
+ ".yarn/bin",
10
+ ".pnpm",
11
+ ".pyenv/shims",
12
+ ".rye/shims",
13
+ ".pixi/bin",
14
+ ];
15
+ const COMMON_SYSTEM_BIN_PATHS = process.platform === "darwin"
16
+ ? ["/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", "/usr/local/sbin"]
17
+ : ["/usr/local/bin", "/usr/local/sbin"];
18
+ export function commonDaemonPathEntries(home = process.env.HOME) {
19
+ const userEntries = home
20
+ ? COMMON_USER_BIN_RELATIVE_PATHS.map((entry) => path.join(home, entry))
21
+ : [];
22
+ return [...COMMON_SYSTEM_BIN_PATHS, ...userEntries];
23
+ }
24
+ export function mergePathEntries(basePath, extras) {
25
+ const seen = new Set();
26
+ const out = [];
27
+ for (const raw of [...(basePath ?? "").split(path.delimiter), ...extras]) {
28
+ const entry = raw.trim();
29
+ if (!entry || seen.has(entry))
30
+ continue;
31
+ seen.add(entry);
32
+ out.push(entry);
33
+ }
34
+ return out.join(path.delimiter);
35
+ }
36
+ /**
37
+ * GUI-launched macOS apps inherit a sparse launchd PATH and do not read the
38
+ * user's shell profile. Add common per-user CLI install locations so runtime
39
+ * adapters can find tools installed by uv/pipx, cargo, bun, npm, etc.
40
+ */
41
+ export function augmentProcessPath() {
42
+ process.env.PATH = mergePathEntries(process.env.PATH, commonDaemonPathEntries(process.env.HOME));
43
+ }
@@ -15,9 +15,11 @@
15
15
  * hello
16
16
  * </agent-message>
17
17
  *
18
- * [In group chats, do NOT reply unless you are explicitly mentioned or
19
- * addressed. If no response is needed, reply with exactly "NO_REPLY"
20
- * and nothing else.]
18
+ * [In group chats, do not send a message back to the current group room
19
+ * unless you are explicitly mentioned, addressed, or the room policy says
20
+ * you should participate. This group-reply restriction only controls
21
+ * whether you post back into the current group. It does not prevent
22
+ * owner-approved or policy-approved background actions...]
21
23
  *
22
24
  * Owner-chat messages bypass the wrapper entirely — they are trusted and
23
25
  * the owner-chat scene prompt in `system-context.ts` already gives the
package/dist/turn-text.js CHANGED
@@ -1,7 +1,15 @@
1
1
  import { sanitizeSenderName, sanitizeUntrustedContent } from "./gateway/index.js";
2
2
  import { classifyActivitySender } from "./sender-classify.js";
3
- const GROUP_HINT = '[In group chats, do NOT reply unless you are explicitly mentioned or addressed. ' +
4
- 'If no response is needed, reply with exactly "NO_REPLY" and nothing else.]';
3
+ const GROUP_HINT = "[In group chats, do not send a message back to the current group room " +
4
+ "unless you are explicitly mentioned, addressed, or the room policy says you should participate.\n\n" +
5
+ "This group-reply restriction only controls whether you post back into the current group. " +
6
+ "It does not prevent you from performing owner-approved or policy-approved background actions, " +
7
+ "including analyzing the message, updating memory, calling tools, starting a task, " +
8
+ "forwarding a summary, or notifying the owner.\n\n" +
9
+ "When a message matches an active monitoring rule, automation goal, working-memory task, " +
10
+ "keyword, sender rule, or owner-approved workflow, perform the required action even if " +
11
+ "you do not reply to the group.\n\n" +
12
+ 'If no group reply and no background action is needed, reply exactly "NO_REPLY".]';
5
13
  const DIRECT_HINT = '[If the conversation has naturally concluded or no response is needed, ' +
6
14
  'reply with exactly "NO_REPLY" and nothing else.]';
7
15
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botcord/daemon",
3
- "version": "0.2.67",
3
+ "version": "0.2.69",
4
4
  "description": "BotCord local daemon — bridges Hub inbox push to local Claude Code / Codex / Gemini CLIs",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,9 @@
32
32
  "@larksuiteoapi/node-sdk": "^1.63.1",
33
33
  "ws": "^8.18.0"
34
34
  },
35
+ "overrides": {
36
+ "axios": "^1.15.2"
37
+ },
35
38
  "devDependencies": {
36
39
  "@types/node": "^20.0.0",
37
40
  "@types/ws": "^8.5.0",
@@ -22,7 +22,13 @@ describe("stripBotCordPromptScaffolding", () => {
22
22
  "hello world",
23
23
  "</agent-message>",
24
24
  "",
25
- '[In group chats, do NOT reply unless you are explicitly mentioned or addressed. If no response is needed, reply with exactly "NO_REPLY" and nothing else.]',
25
+ "[In group chats, do not send a message back to the current group room unless you are explicitly mentioned, addressed, or the room policy says you should participate.",
26
+ "",
27
+ "This group-reply restriction only controls whether you post back into the current group. It does not prevent you from performing owner-approved or policy-approved background actions, including analyzing the message, updating memory, calling tools, starting a task, forwarding a summary, or notifying the owner.",
28
+ "",
29
+ "When a message matches an active monitoring rule, automation goal, working-memory task, keyword, sender rule, or owner-approved workflow, perform the required action even if you do not reply to the group.",
30
+ "",
31
+ 'If no group reply and no background action is needed, reply exactly "NO_REPLY".]',
26
32
  ].join("\n");
27
33
  expect(stripBotCordPromptScaffolding(wrapped)).toBe("hello world");
28
34
  });
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import path from "node:path";
3
+ import { commonDaemonPathEntries, mergePathEntries } from "../path-env.js";
4
+
5
+ describe("path-env", () => {
6
+ it("adds common user CLI locations", () => {
7
+ expect(commonDaemonPathEntries("/Users/alice")).toEqual(
8
+ expect.arrayContaining([
9
+ "/Users/alice/.botcord/bin",
10
+ "/Users/alice/.local/bin",
11
+ "/Users/alice/.cargo/bin",
12
+ "/Users/alice/.bun/bin",
13
+ "/Users/alice/.pyenv/shims",
14
+ ]),
15
+ );
16
+ });
17
+
18
+ it("preserves existing PATH precedence and de-duplicates entries", () => {
19
+ const base = ["/usr/bin", "/bin", "/Users/alice/.local/bin"].join(path.delimiter);
20
+ const merged = mergePathEntries(base, [
21
+ "/Users/alice/.local/bin",
22
+ "/opt/homebrew/bin",
23
+ "/usr/bin",
24
+ ]);
25
+
26
+ expect(merged.split(path.delimiter)).toEqual([
27
+ "/usr/bin",
28
+ "/bin",
29
+ "/Users/alice/.local/bin",
30
+ "/opt/homebrew/bin",
31
+ ]);
32
+ });
33
+ });
@@ -34,7 +34,9 @@ describe("composeBotCordUserTurn", () => {
34
34
  expect(out).toContain('<agent-message sender="ag_alice" sender_kind="agent">');
35
35
  expect(out).toContain("hey everyone");
36
36
  expect(out).toContain("</agent-message>");
37
- expect(out).toContain('do NOT reply unless you are explicitly mentioned');
37
+ expect(out).toContain("do not send a message back to the current group room");
38
+ expect(out).toContain("owner-approved or policy-approved background actions");
39
+ expect(out).toContain("active monitoring rule");
38
40
  expect(out).toContain('"NO_REPLY"');
39
41
  });
40
42
 
@@ -247,7 +249,8 @@ describe("composeBotCordUserTurn", () => {
247
249
  // Single-message header must NOT appear in batch mode.
248
250
  expect(out).not.toContain("[BotCord Message]");
249
251
  // Group hint still appears after the blocks.
250
- expect(out).toContain("do NOT reply unless");
252
+ expect(out).toContain("do not send a message back to the current group room");
253
+ expect(out).toContain("no background action is needed");
251
254
  });
252
255
 
253
256
  it("batched path tags dashboard_human_room senders as human-message", () => {
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { existsSync, readFileSync, writeFileSync, unlinkSync, readdirSync, statSync, rmSync } from "node:fs";
4
4
  import { homedir, hostname } from "node:os";
5
5
  import path from "node:path";
6
+ import { augmentProcessPath } from "./path-env.js";
6
7
  import {
7
8
  loadConfig,
8
9
  saveConfig,
@@ -65,6 +66,8 @@ import {
65
66
  openclawDiscoveryConfigEnabled,
66
67
  } from "./openclaw-discovery.js";
67
68
 
69
+ augmentProcessPath();
70
+
68
71
  const ADAPTER_LIST = listAdapterIds().join("|");
69
72
 
70
73
  const DEFAULT_HUB = "https://api.botcord.chat";
package/src/loop-risk.ts CHANGED
@@ -83,9 +83,16 @@ export function stripBotCordPromptScaffolding(text: string): string {
83
83
  if (line.startsWith("[BotCord Message]")) return false;
84
84
  if (line.startsWith("[BotCord Notification]")) return false;
85
85
  if (line.startsWith("[Room Rule]")) return false;
86
- if (line.startsWith("[In group chats, do NOT reply")) return false;
86
+ if (line.startsWith("[In group chats,")) return false;
87
+ if (line.startsWith("This group-reply restriction")) return false;
88
+ if (line.startsWith("including analyzing the message")) return false;
89
+ if (line.startsWith("forwarding a summary")) return false;
90
+ if (line.startsWith("When a message matches an active monitoring rule")) return false;
91
+ if (line.startsWith("keyword, sender rule")) return false;
92
+ if (line.startsWith("you do not reply to the group")) return false;
87
93
  if (line.startsWith("[If the conversation has naturally concluded")) return false;
88
94
  if (line.startsWith("[You received a contact request")) return false;
95
+ if (line.includes("no background action is needed")) return false;
89
96
  if (line.includes('reply with exactly "NO_REPLY"')) return false;
90
97
  if (line.startsWith("<agent-message")) return false;
91
98
  if (line === "</agent-message>") return false;
@@ -0,0 +1,53 @@
1
+ import path from "node:path";
2
+
3
+ const COMMON_USER_BIN_RELATIVE_PATHS = [
4
+ ".botcord/bin",
5
+ ".local/bin",
6
+ ".cargo/bin",
7
+ ".bun/bin",
8
+ ".deno/bin",
9
+ ".npm-global/bin",
10
+ ".yarn/bin",
11
+ ".pnpm",
12
+ ".pyenv/shims",
13
+ ".rye/shims",
14
+ ".pixi/bin",
15
+ ];
16
+
17
+ const COMMON_SYSTEM_BIN_PATHS =
18
+ process.platform === "darwin"
19
+ ? ["/opt/homebrew/bin", "/opt/homebrew/sbin", "/usr/local/bin", "/usr/local/sbin"]
20
+ : ["/usr/local/bin", "/usr/local/sbin"];
21
+
22
+ export function commonDaemonPathEntries(home = process.env.HOME): string[] {
23
+ const userEntries = home
24
+ ? COMMON_USER_BIN_RELATIVE_PATHS.map((entry) => path.join(home, entry))
25
+ : [];
26
+ return [...COMMON_SYSTEM_BIN_PATHS, ...userEntries];
27
+ }
28
+
29
+ export function mergePathEntries(basePath: string | undefined, extras: string[]): string {
30
+ const seen = new Set<string>();
31
+ const out: string[] = [];
32
+
33
+ for (const raw of [...(basePath ?? "").split(path.delimiter), ...extras]) {
34
+ const entry = raw.trim();
35
+ if (!entry || seen.has(entry)) continue;
36
+ seen.add(entry);
37
+ out.push(entry);
38
+ }
39
+
40
+ return out.join(path.delimiter);
41
+ }
42
+
43
+ /**
44
+ * GUI-launched macOS apps inherit a sparse launchd PATH and do not read the
45
+ * user's shell profile. Add common per-user CLI install locations so runtime
46
+ * adapters can find tools installed by uv/pipx, cargo, bun, npm, etc.
47
+ */
48
+ export function augmentProcessPath(): void {
49
+ process.env.PATH = mergePathEntries(
50
+ process.env.PATH,
51
+ commonDaemonPathEntries(process.env.HOME),
52
+ );
53
+ }
package/src/turn-text.ts CHANGED
@@ -15,9 +15,11 @@
15
15
  * hello
16
16
  * </agent-message>
17
17
  *
18
- * [In group chats, do NOT reply unless you are explicitly mentioned or
19
- * addressed. If no response is needed, reply with exactly "NO_REPLY"
20
- * and nothing else.]
18
+ * [In group chats, do not send a message back to the current group room
19
+ * unless you are explicitly mentioned, addressed, or the room policy says
20
+ * you should participate. This group-reply restriction only controls
21
+ * whether you post back into the current group. It does not prevent
22
+ * owner-approved or policy-approved background actions...]
21
23
  *
22
24
  * Owner-chat messages bypass the wrapper entirely — they are trusted and
23
25
  * the owner-chat scene prompt in `system-context.ts` already gives the
@@ -28,8 +30,16 @@ import { sanitizeSenderName, sanitizeUntrustedContent } from "./gateway/index.js
28
30
  import { classifyActivitySender } from "./sender-classify.js";
29
31
 
30
32
  const GROUP_HINT =
31
- '[In group chats, do NOT reply unless you are explicitly mentioned or addressed. ' +
32
- 'If no response is needed, reply with exactly "NO_REPLY" and nothing else.]';
33
+ "[In group chats, do not send a message back to the current group room " +
34
+ "unless you are explicitly mentioned, addressed, or the room policy says you should participate.\n\n" +
35
+ "This group-reply restriction only controls whether you post back into the current group. " +
36
+ "It does not prevent you from performing owner-approved or policy-approved background actions, " +
37
+ "including analyzing the message, updating memory, calling tools, starting a task, " +
38
+ "forwarding a summary, or notifying the owner.\n\n" +
39
+ "When a message matches an active monitoring rule, automation goal, working-memory task, " +
40
+ "keyword, sender rule, or owner-approved workflow, perform the required action even if " +
41
+ "you do not reply to the group.\n\n" +
42
+ 'If no group reply and no background action is needed, reply exactly "NO_REPLY".]';
33
43
  const DIRECT_HINT =
34
44
  '[If the conversation has naturally concluded or no response is needed, ' +
35
45
  'reply with exactly "NO_REPLY" and nothing else.]';