bloby-bot 0.53.9 → 0.54.10

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.
@@ -12,9 +12,30 @@ import path from 'path';
12
12
  import { log } from '../../shared/logger.js';
13
13
  import { conditions, type ConditionResult } from './prompt-conditions.js';
14
14
 
15
- const PROMPT_FILE = path.join(import.meta.dirname, 'bloby-system-prompt.txt');
16
15
  const FRAGMENTS_FILE = path.join(import.meta.dirname, 'prompt-fragments.json');
17
16
 
17
+ /**
18
+ * One base system prompt per harness. The dynamic-fragment machinery
19
+ * (markers + prompt-fragments.json + prompt-conditions.ts) is shared across
20
+ * all three — each file carries the same `<!-- dynamic:* -->` markers, so a
21
+ * fragment applies regardless of which harness is active. Copy the markers
22
+ * into every file when adding a new dynamic section (see DYNAMIC-PROMPTS.md).
23
+ */
24
+ export type Harness = 'claude' | 'codex' | 'pi';
25
+
26
+ const DEFAULT_PROMPT_FILENAME = 'bloby-system-prompt.txt';
27
+ const PROMPT_FILENAMES: Record<Harness, string> = {
28
+ claude: 'bloby-system-prompt.txt',
29
+ codex: 'bloby-system-prompt-codex.txt',
30
+ pi: 'bloby-system-prompt-pi.txt',
31
+ };
32
+
33
+ /** Absolute path to the base prompt file for a given harness. */
34
+ function promptFilePath(harness: Harness): string {
35
+ const filename = PROMPT_FILENAMES[harness] ?? DEFAULT_PROMPT_FILENAME;
36
+ return path.join(import.meta.dirname, filename);
37
+ }
38
+
18
39
  // ── Types ────────────────────────────────────────────────────────────────────
19
40
 
20
41
  export interface PromptFragment {
@@ -43,19 +64,32 @@ function loadFragments(): PromptFragment[] {
43
64
  }
44
65
  }
45
66
 
46
- /** Read the base system prompt with $BOT / $HUMAN replacement */
47
- function readBasePrompt(botName = 'Bloby', humanName = 'Human'): string {
48
- try {
49
- const raw = fs.readFileSync(PROMPT_FILE, 'utf-8').trim();
50
- if (!raw) {
51
- log.warn('System prompt file is empty — using minimal fallback');
52
- return `You are ${botName}, a helpful AI agent. Your human is ${humanName}.`;
67
+ /**
68
+ * Read the base system prompt for `harness` with $BOT / $HUMAN replacement.
69
+ * If the harness-specific file is missing or empty, fall back to the Claude
70
+ * prompt (`bloby-system-prompt.txt`) so a not-yet-created codex/pi file never
71
+ * collapses the agent to the minimal stub.
72
+ */
73
+ function readBasePrompt(botName = 'Bloby', humanName = 'Human', harness: Harness = 'claude'): string {
74
+ const primary = promptFilePath(harness);
75
+ const fallback = path.join(import.meta.dirname, DEFAULT_PROMPT_FILENAME);
76
+ const candidates = primary === fallback ? [primary] : [primary, fallback];
77
+
78
+ for (const file of candidates) {
79
+ try {
80
+ const raw = fs.readFileSync(file, 'utf-8').trim();
81
+ if (!raw) continue;
82
+ if (file === fallback && primary !== fallback) {
83
+ log.warn(`[prompt] "${harness}" prompt (${path.basename(primary)}) missing/empty — falling back to ${DEFAULT_PROMPT_FILENAME}`);
84
+ }
85
+ return raw.replace(/\$BOT/g, botName).replace(/\$HUMAN/g, humanName);
86
+ } catch {
87
+ // try next candidate
53
88
  }
54
- return raw.replace(/\$BOT/g, botName).replace(/\$HUMAN/g, humanName);
55
- } catch {
56
- log.warn('System prompt file not found — using minimal fallback');
57
- return `You are ${botName}, a helpful AI agent. Your human is ${humanName}.`;
58
89
  }
90
+
91
+ log.warn('System prompt file not found — using minimal fallback');
92
+ return `You are ${botName}, a helpful AI agent. Your human is ${humanName}.`;
59
93
  }
60
94
 
61
95
  /** Interpolate {{variable}} placeholders in content using vars map */
@@ -84,8 +118,9 @@ function markerRegex(target: string): RegExp {
84
118
  export async function assembleSystemPrompt(
85
119
  botName = 'Bloby',
86
120
  humanName = 'Human',
121
+ harness: Harness = 'claude',
87
122
  ): Promise<string> {
88
- let prompt = readBasePrompt(botName, humanName);
123
+ let prompt = readBasePrompt(botName, humanName, harness);
89
124
  const fragments = loadFragments();
90
125
 
91
126
  if (!fragments.length) {
@@ -93,7 +128,7 @@ export async function assembleSystemPrompt(
93
128
  return prompt;
94
129
  }
95
130
 
96
- log.info(`[prompt] Evaluating ${fragments.length} fragment(s)...`);
131
+ log.info(`[prompt] Harness "${harness}" (${PROMPT_FILENAMES[harness] ?? DEFAULT_PROMPT_FILENAME}) — evaluating ${fragments.length} fragment(s)...`);
97
132
 
98
133
  // Sort by priority (lower = first)
99
134
  const sorted = [...fragments].sort((a, b) => a.priority - b.priority);
@@ -57,7 +57,7 @@ The mode determines the routing/security policy for inbound messages. The core (
57
57
 
58
58
  **Business Mode**: Bloby has its own dedicated WhatsApp number. Numbers in the `admins` array get admin access (main system prompt). Everyone else is a customer and gets the support prompt from the active skill's SCRIPT.md.
59
59
 
60
- **Assistant Mode**: Your personal assistant inside your own conversations. Self-chat works as a normal admin channel. When other people message you, their messages are silently stored for context. When YOU type `@botname:` followed by a command in someone's chat, the agent activates with full conversation context and responds in that chat. The trigger uses the bot's configured name (from `config.json` `username` field) and is case-insensitive. Nobody else can trigger the agent — only you (the account owner). Uses the active skill's SCRIPT.md for the system prompt and `customer_data/` for per-contact memory.
60
+ **Assistant Mode**: Your personal assistant inside your own conversations. Self-chat works as a normal admin channel. When other people message you, their messages are silently stored for context. When YOU type `@botname:` followed by a command in someone's chat, the agent activates with full conversation context and responds in that chat. The trigger uses the bot's configured name (from `config.json` `username` field) and is case-insensitive. By default nobody else can trigger the agent — only you (the account owner). (This can be opened up with the dangerous `allowOthersToTrigger` flag — see the disclaimer under Setup.) Uses the active skill's SCRIPT.md for the system prompt and `customer_data/` for per-contact memory.
61
61
 
62
62
  ### Group chats (`allowGroups`)
63
63
 
@@ -131,10 +131,33 @@ curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
131
131
  -d '{"mode":"assistant","skill":"SKILL_FOLDER_NAME"}'
132
132
  ```
133
133
 
134
- The trigger uses the bot's name from `config.json` `username` field (e.g. if username is "bloby", trigger is `@bloby:`). Only the account owner can trigger — other people's messages are context only.
134
+ The trigger uses the bot's name from `config.json` `username` field (e.g. if username is "bloby", trigger is `@bloby:`). By default **only the account owner can trigger** — other people's messages are context only.
135
135
 
136
136
  To also let assistant mode operate in group chats your human is in, add `"allowGroups":true` to the configure call (see "Group chats" above).
137
137
 
138
+ ### ⚠️ Shared control — `allowOthersToTrigger` (DANGEROUS)
139
+
140
+ By default, **only your human** (the account owner) can drive the agent with `@botname:`. Everyone else's messages are stored as context but cannot trigger anything.
141
+
142
+ Setting `allowOthersToTrigger: true` changes that: **anyone** who tags `@botname:` — in a 1:1 chat or in any group your human is in — can drive the agent.
143
+
144
+ ```bash
145
+ curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
146
+ -H "Content-Type: application/json" \
147
+ -d '{"mode":"assistant","allowOthersToTrigger":true}'
148
+ ```
149
+
150
+ **Before enabling this, you MUST warn your human, in your own words, that this is dangerous:** anyone who can tag the bot gains control of an agent that can run Bash, read and write files, send messages, spend money, and act with your human's full permissions. A stranger added to a group — or anyone who learns the bot's name — could issue commands. There is no per-command confirmation.
151
+
152
+ It exists for genuinely trusted shared use — e.g. sharing one assistant with a partner. Do not enable it on a whim. Confirm explicitly with your human first, and remind them it can be turned off at any time:
153
+
154
+ ```bash
155
+ curl -s -X POST http://localhost:7400/api/channels/whatsapp/configure \
156
+ -H "Content-Type: application/json" -d '{"allowOthersToTrigger":false}'
157
+ ```
158
+
159
+ Default is `false`. Leave it off unless your human clearly understands the risk and asks for it.
160
+
138
161
  ### 3. Verify
139
162
 
140
163
  ```bash