fluxy-bot 0.15.2 → 0.15.5
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/package.json +1 -1
- package/shared/config.ts +3 -1
- package/supervisor/channels/manager.ts +97 -34
- package/supervisor/channels/types.ts +3 -1
- package/supervisor/channels/whatsapp.ts +6 -3
- package/supervisor/fluxy-agent.ts +12 -19
- package/supervisor/index.ts +1 -0
- package/worker/prompts/fluxy-system-prompt.txt +32 -1
- package/workspace/skills/whatsapp-support/.claude-plugin/plugin.json +0 -5
- /package/workspace/skills/whatsapp-support/{SUPPORT.md → SCRIPT.md} +0 -0
package/package.json
CHANGED
package/shared/config.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { paths, DATA_DIR } from './paths.js';
|
|
|
3
3
|
|
|
4
4
|
export interface ChannelConfig {
|
|
5
5
|
enabled: boolean;
|
|
6
|
-
/** 'channel' = just talk to me (self-chat only), 'business' = admin/customer
|
|
6
|
+
/** 'channel' = just talk to me (self-chat only), 'business' = admin/customer mode */
|
|
7
7
|
mode: 'channel' | 'business';
|
|
8
8
|
/** Phone numbers with admin access (owner, secretary, etc.) — business mode only */
|
|
9
9
|
admins?: string[];
|
|
10
|
+
/** Active skill for customer-facing mode (folder name in workspace/skills/) */
|
|
11
|
+
skill?: string;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export interface BotConfig {
|
|
@@ -25,6 +25,7 @@ import { WhatsAppChannel } from './whatsapp.js';
|
|
|
25
25
|
import type { ChannelConfig, ChannelProvider, ChannelStatus, ChannelType, InboundMessage, SenderRole } from './types.js';
|
|
26
26
|
|
|
27
27
|
const MAX_CONCURRENT_AGENTS = 5;
|
|
28
|
+
const MAX_BUFFER_MESSAGES = 30;
|
|
28
29
|
|
|
29
30
|
interface ChannelManagerOpts {
|
|
30
31
|
broadcastFluxy: (type: string, data: any) => void;
|
|
@@ -38,12 +39,19 @@ interface ActiveAgentQuery {
|
|
|
38
39
|
channel: ChannelType;
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
interface BufferedMessage {
|
|
43
|
+
role: 'user' | 'assistant';
|
|
44
|
+
content: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
41
47
|
export class ChannelManager {
|
|
42
48
|
private providers = new Map<ChannelType, ChannelProvider>();
|
|
43
49
|
private opts: ChannelManagerOpts;
|
|
44
50
|
private activeAgents = new Map<string, ActiveAgentQuery>();
|
|
45
51
|
private messageQueue: InboundMessage[] = [];
|
|
46
52
|
private statusListeners: ((status: ChannelStatus) => void)[] = [];
|
|
53
|
+
/** In-memory conversation history per customer (keyed by "channel:phone") */
|
|
54
|
+
private customerBuffers = new Map<string, BufferedMessage[]>();
|
|
47
55
|
|
|
48
56
|
constructor(opts: ChannelManagerOpts) {
|
|
49
57
|
this.opts = opts;
|
|
@@ -61,7 +69,7 @@ export class ChannelManager {
|
|
|
61
69
|
|
|
62
70
|
log.info('[channels] Initializing WhatsApp channel...');
|
|
63
71
|
const whatsapp = new WhatsAppChannel(
|
|
64
|
-
(sender, senderName, text, fromMe) => this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe),
|
|
72
|
+
(sender, senderName, text, fromMe, isSelfChat) => this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe, isSelfChat),
|
|
65
73
|
(status) => this.handleStatusChange(status),
|
|
66
74
|
);
|
|
67
75
|
this.providers.set('whatsapp', whatsapp);
|
|
@@ -81,7 +89,7 @@ export class ChannelManager {
|
|
|
81
89
|
let provider = this.providers.get('whatsapp');
|
|
82
90
|
if (!provider) {
|
|
83
91
|
const whatsapp = new WhatsAppChannel(
|
|
84
|
-
(sender, senderName, text, fromMe) => this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe),
|
|
92
|
+
(sender, senderName, text, fromMe, isSelfChat) => this.handleInboundMessage('whatsapp', sender, senderName, text, fromMe, isSelfChat),
|
|
85
93
|
(status) => this.handleStatusChange(status),
|
|
86
94
|
);
|
|
87
95
|
this.providers.set('whatsapp', whatsapp);
|
|
@@ -165,16 +173,17 @@ export class ChannelManager {
|
|
|
165
173
|
senderName: string | undefined,
|
|
166
174
|
text: string,
|
|
167
175
|
fromMe: boolean,
|
|
176
|
+
isSelfChat: boolean,
|
|
168
177
|
) {
|
|
169
178
|
const channelConfig = this.getChannelConfig(channel);
|
|
170
179
|
if (!channelConfig) return;
|
|
171
180
|
|
|
172
181
|
const mode = channelConfig.mode || 'channel';
|
|
173
182
|
|
|
174
|
-
// ── Channel mode:
|
|
183
|
+
// ── Channel mode: ONLY respond to self-chat ──
|
|
175
184
|
if (mode === 'channel') {
|
|
176
|
-
if (!fromMe) {
|
|
177
|
-
// Ignore
|
|
185
|
+
if (!fromMe || !isSelfChat) {
|
|
186
|
+
// Ignore everything except self-chat messages
|
|
178
187
|
return;
|
|
179
188
|
}
|
|
180
189
|
|
|
@@ -192,8 +201,16 @@ export class ChannelManager {
|
|
|
192
201
|
return;
|
|
193
202
|
}
|
|
194
203
|
|
|
195
|
-
// ── Business mode:
|
|
196
|
-
|
|
204
|
+
// ── Business mode: only respond to INCOMING messages (fromMe=false) ──
|
|
205
|
+
// fromMe=true means either:
|
|
206
|
+
// - Fluxy's own sent replies (would cause loops)
|
|
207
|
+
// - User typing on Fluxy's WhatsApp Web (not intended for the bot)
|
|
208
|
+
// Both should be ignored.
|
|
209
|
+
if (fromMe) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const role = this.resolveBusinessRole(channelConfig, sender);
|
|
197
214
|
|
|
198
215
|
const message: InboundMessage = {
|
|
199
216
|
channel,
|
|
@@ -209,16 +226,12 @@ export class ChannelManager {
|
|
|
209
226
|
if (role === 'admin') {
|
|
210
227
|
await this.handleAdminMessage(message);
|
|
211
228
|
} else {
|
|
212
|
-
await this.handleCustomerMessage(message);
|
|
229
|
+
await this.handleCustomerMessage(message, channelConfig);
|
|
213
230
|
}
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
/** Resolve role in business mode — check admins array */
|
|
217
|
-
private resolveBusinessRole(config: ChannelConfig, sender: string
|
|
218
|
-
// fromMe is always admin (the number Fluxy is connected with)
|
|
219
|
-
if (fromMe) return 'admin';
|
|
220
|
-
|
|
221
|
-
// Check admins array
|
|
234
|
+
private resolveBusinessRole(config: ChannelConfig, sender: string): SenderRole {
|
|
222
235
|
if (config.admins?.length) {
|
|
223
236
|
const senderPhone = sender.replace(/@.*/, '').replace(/[^0-9]/g, '');
|
|
224
237
|
for (const admin of config.admins) {
|
|
@@ -329,8 +342,8 @@ export class ChannelManager {
|
|
|
329
342
|
);
|
|
330
343
|
}
|
|
331
344
|
|
|
332
|
-
/** Handle message from a customer — runs support agent in parallel */
|
|
333
|
-
private async handleCustomerMessage(msg: InboundMessage) {
|
|
345
|
+
/** Handle message from a customer — runs support agent in parallel with conversation context */
|
|
346
|
+
private async handleCustomerMessage(msg: InboundMessage, channelConfig: ChannelConfig) {
|
|
334
347
|
const agentKey = `${msg.channel}:${msg.sender}`;
|
|
335
348
|
|
|
336
349
|
// Check concurrent limit
|
|
@@ -343,8 +356,8 @@ export class ChannelManager {
|
|
|
343
356
|
const { workerApi, getModel } = this.opts;
|
|
344
357
|
const model = getModel();
|
|
345
358
|
|
|
346
|
-
// Load
|
|
347
|
-
const
|
|
359
|
+
// Load the active skill's SCRIPT.md as the customer-facing system prompt
|
|
360
|
+
const scriptPrompt = this.loadActiveScript(channelConfig);
|
|
348
361
|
|
|
349
362
|
// Fetch agent name
|
|
350
363
|
let botName = 'Fluxy', humanName = 'Human';
|
|
@@ -354,17 +367,60 @@ export class ChannelManager {
|
|
|
354
367
|
humanName = status.userName || 'Human';
|
|
355
368
|
} catch {}
|
|
356
369
|
|
|
370
|
+
// Get or create conversation buffer for this customer
|
|
371
|
+
let buffer = this.customerBuffers.get(agentKey);
|
|
372
|
+
if (!buffer) {
|
|
373
|
+
buffer = [];
|
|
374
|
+
this.customerBuffers.set(agentKey, buffer);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Add the new user message to the buffer
|
|
378
|
+
buffer.push({ role: 'user', content: msg.text });
|
|
379
|
+
|
|
380
|
+
// Trim buffer to max size
|
|
381
|
+
if (buffer.length > MAX_BUFFER_MESSAGES) {
|
|
382
|
+
buffer.splice(0, buffer.length - MAX_BUFFER_MESSAGES);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Build recent messages for context (everything except the last one, which is the current message)
|
|
386
|
+
const recentMessages: RecentMessage[] = buffer.length > 1
|
|
387
|
+
? buffer.slice(0, -1).map((m) => ({ role: m.role, content: m.content }))
|
|
388
|
+
: [];
|
|
389
|
+
|
|
390
|
+
// Also load long-term memory from whatsapp/{phone}.md if it exists
|
|
391
|
+
let customerMemory = '';
|
|
392
|
+
try {
|
|
393
|
+
const memoryPath = path.join(WORKSPACE_DIR, 'whatsapp', `${msg.sender}.md`);
|
|
394
|
+
if (fs.existsSync(memoryPath)) {
|
|
395
|
+
customerMemory = fs.readFileSync(memoryPath, 'utf-8').trim();
|
|
396
|
+
}
|
|
397
|
+
} catch {}
|
|
398
|
+
|
|
357
399
|
const channelContext = `[WhatsApp | ${msg.sender} | customer${msg.senderName ? ` | ${msg.senderName}` : ''}]\n`;
|
|
358
|
-
|
|
400
|
+
|
|
401
|
+
// Stable convId per customer (not per message)
|
|
402
|
+
const convId = `channel-${agentKey}`;
|
|
359
403
|
|
|
360
404
|
this.activeAgents.set(agentKey, { sender: msg.sender, channel: msg.channel });
|
|
361
405
|
|
|
406
|
+
// Build an enriched script prompt with customer memory if available
|
|
407
|
+
let enrichedScript = scriptPrompt;
|
|
408
|
+
if (customerMemory && enrichedScript) {
|
|
409
|
+
enrichedScript += `\n\n---\n# Customer History (${msg.sender})\n\n${customerMemory}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
362
412
|
startFluxyAgentQuery(
|
|
363
413
|
convId,
|
|
364
414
|
channelContext + msg.text,
|
|
365
415
|
model,
|
|
366
416
|
(type, eventData) => {
|
|
367
417
|
if (type === 'bot:response' && eventData.content) {
|
|
418
|
+
// Add assistant response to the buffer
|
|
419
|
+
buffer!.push({ role: 'assistant', content: eventData.content });
|
|
420
|
+
if (buffer!.length > MAX_BUFFER_MESSAGES) {
|
|
421
|
+
buffer!.splice(0, buffer!.length - MAX_BUFFER_MESSAGES);
|
|
422
|
+
}
|
|
423
|
+
|
|
368
424
|
this.sendMessage(msg.channel, msg.rawSender, eventData.content).catch((err) => {
|
|
369
425
|
log.warn(`[channels] Failed to send customer reply: ${err.message}`);
|
|
370
426
|
});
|
|
@@ -379,27 +435,32 @@ export class ChannelManager {
|
|
|
379
435
|
undefined,
|
|
380
436
|
undefined,
|
|
381
437
|
{ botName, humanName },
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
recentMessages,
|
|
439
|
+
enrichedScript,
|
|
384
440
|
);
|
|
385
441
|
}
|
|
386
442
|
|
|
387
|
-
/** Load
|
|
388
|
-
private
|
|
389
|
-
const
|
|
443
|
+
/** Load SCRIPT.md from the active skill configured for this channel */
|
|
444
|
+
private loadActiveScript(channelConfig: ChannelConfig): string | undefined {
|
|
445
|
+
const skillName = channelConfig.skill;
|
|
446
|
+
if (!skillName) {
|
|
447
|
+
log.warn('[channels] No active skill configured — customer will get no script');
|
|
448
|
+
return undefined;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const scriptPath = path.join(WORKSPACE_DIR, 'skills', skillName, 'SCRIPT.md');
|
|
390
452
|
try {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
if (content) {
|
|
397
|
-
log.info(`[channels] Loaded support prompt from skill: ${entry.name}`);
|
|
398
|
-
return content;
|
|
399
|
-
}
|
|
453
|
+
if (fs.existsSync(scriptPath)) {
|
|
454
|
+
const content = fs.readFileSync(scriptPath, 'utf-8').trim();
|
|
455
|
+
if (content) {
|
|
456
|
+
log.info(`[channels] Loaded SCRIPT.md from skill: ${skillName}`);
|
|
457
|
+
return content;
|
|
400
458
|
}
|
|
401
459
|
}
|
|
402
|
-
|
|
460
|
+
log.warn(`[channels] SCRIPT.md not found in skill: ${skillName}`);
|
|
461
|
+
} catch (err: any) {
|
|
462
|
+
log.warn(`[channels] Failed to load SCRIPT.md from ${skillName}: ${err.message}`);
|
|
463
|
+
}
|
|
403
464
|
return undefined;
|
|
404
465
|
}
|
|
405
466
|
|
|
@@ -407,8 +468,10 @@ export class ChannelManager {
|
|
|
407
468
|
private processQueue() {
|
|
408
469
|
while (this.messageQueue.length > 0 && this.activeAgents.size < MAX_CONCURRENT_AGENTS) {
|
|
409
470
|
const queued = this.messageQueue.shift()!;
|
|
471
|
+
const config = this.getChannelConfig(queued.channel);
|
|
472
|
+
if (!config) continue;
|
|
410
473
|
log.info(`[channels] Processing queued message from ${queued.sender}`);
|
|
411
|
-
this.handleCustomerMessage(queued);
|
|
474
|
+
this.handleCustomerMessage(queued, config);
|
|
412
475
|
}
|
|
413
476
|
}
|
|
414
477
|
}
|
|
@@ -7,10 +7,12 @@ export type SenderRole = 'admin' | 'customer';
|
|
|
7
7
|
|
|
8
8
|
export interface ChannelConfig {
|
|
9
9
|
enabled: boolean;
|
|
10
|
-
/** 'channel' = just talk to me (self-chat only), 'business' = admin/customer
|
|
10
|
+
/** 'channel' = just talk to me (self-chat only), 'business' = admin/customer mode */
|
|
11
11
|
mode: 'channel' | 'business';
|
|
12
12
|
/** Phone numbers with admin access (owner, secretary, etc.) — business mode only */
|
|
13
13
|
admins?: string[];
|
|
14
|
+
/** Active skill for customer-facing mode (folder name in workspace/skills/) */
|
|
15
|
+
skill?: string;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface InboundMessage {
|
|
@@ -23,7 +23,7 @@ import type { ChannelProvider, ChannelStatus, ChannelType } from './types.js';
|
|
|
23
23
|
const AUTH_DIR = path.join(DATA_DIR, 'channels', 'whatsapp', 'auth');
|
|
24
24
|
|
|
25
25
|
/** Callback when a new message arrives */
|
|
26
|
-
export type OnWhatsAppMessage = (sender: string, senderName: string | undefined, text: string, fromMe: boolean) => void;
|
|
26
|
+
export type OnWhatsAppMessage = (sender: string, senderName: string | undefined, text: string, fromMe: boolean, isSelfChat: boolean) => void;
|
|
27
27
|
|
|
28
28
|
export class WhatsAppChannel implements ChannelProvider {
|
|
29
29
|
readonly type: ChannelType = 'whatsapp';
|
|
@@ -255,9 +255,12 @@ export class WhatsAppChannel implements ChannelProvider {
|
|
|
255
255
|
const sender = this.translateJid(rawSender);
|
|
256
256
|
const pushName = msg.pushName || undefined;
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
// Detect self-chat: remoteJid matches our own phone number
|
|
259
|
+
const isSelfChat = this.ownPhoneJid !== null && sender === this.ownPhoneJid;
|
|
259
260
|
|
|
260
|
-
|
|
261
|
+
log.info(`[whatsapp] Message from ${sender} (raw=${rawSender}, fromMe=${fromMe}, selfChat=${isSelfChat}): ${text.slice(0, 80)}`);
|
|
262
|
+
|
|
263
|
+
this.onMessage(sender, pushName, text, fromMe, isSelfChat);
|
|
261
264
|
}
|
|
262
265
|
});
|
|
263
266
|
}
|
|
@@ -7,7 +7,7 @@ import { query, type SDKMessage, type SDKUserMessage } from '@anthropic-ai/claud
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { log } from '../shared/logger.js';
|
|
10
|
-
import {
|
|
10
|
+
import { WORKSPACE_DIR } from '../shared/paths.js';
|
|
11
11
|
import type { SavedFile } from './file-saver.js';
|
|
12
12
|
import { getClaudeAccessToken } from '../worker/claude-auth.js';
|
|
13
13
|
|
|
@@ -178,29 +178,23 @@ export async function startFluxyAgentQuery(
|
|
|
178
178
|
const sdkPrompt: string | AsyncIterable<SDKUserMessage> =
|
|
179
179
|
attachments?.length ? buildMultiPartPrompt(prompt, attachments, savedFiles) : plainPrompt;
|
|
180
180
|
|
|
181
|
+
// Auto-discover skills — inject SKILL.md contents into system prompt (no SDK plugin system needed)
|
|
181
182
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// skills/{name}/SKILL.md — we bridge the gap with symlinks created on discovery.
|
|
185
|
-
const skillsDir = path.join(PKG_DIR, 'workspace', 'skills');
|
|
186
|
-
const plugins: { type: 'local'; path: string }[] = [];
|
|
183
|
+
const skillsDir = path.join(WORKSPACE_DIR, 'skills');
|
|
184
|
+
const skillContents: string[] = [];
|
|
187
185
|
try {
|
|
188
186
|
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
189
|
-
if (entry.isDirectory()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const flatSkillMd = path.join(skillsDir, skillName, 'SKILL.md');
|
|
195
|
-
const sdkDir = path.join(skillsDir, skillName, 'skills', skillName);
|
|
196
|
-
const sdkSkillMd = path.join(sdkDir, 'SKILL.md');
|
|
197
|
-
if (fs.existsSync(flatSkillMd) && !fs.existsSync(sdkSkillMd)) {
|
|
198
|
-
fs.mkdirSync(sdkDir, { recursive: true });
|
|
199
|
-
fs.symlinkSync(flatSkillMd, sdkSkillMd);
|
|
200
|
-
}
|
|
187
|
+
if (!entry.isDirectory()) continue;
|
|
188
|
+
const skillMd = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
189
|
+
if (fs.existsSync(skillMd)) {
|
|
190
|
+
const content = fs.readFileSync(skillMd, 'utf-8').trim();
|
|
191
|
+
if (content) skillContents.push(`## Skill: ${entry.name}\n\n${content}`);
|
|
201
192
|
}
|
|
202
193
|
}
|
|
203
194
|
} catch {}
|
|
195
|
+
if (skillContents.length) {
|
|
196
|
+
enrichedPrompt += `\n\n---\n# Installed Skills\n\n${skillContents.join('\n\n---\n\n')}`;
|
|
197
|
+
}
|
|
204
198
|
|
|
205
199
|
// Load MCP server config from workspace/MCP.json if it exists
|
|
206
200
|
// Format: { "server-name": { command, args, env }, ... } (object, not array)
|
|
@@ -231,7 +225,6 @@ export async function startFluxyAgentQuery(
|
|
|
231
225
|
maxTurns: 50,
|
|
232
226
|
abortController,
|
|
233
227
|
systemPrompt: enrichedPrompt,
|
|
234
|
-
plugins: plugins.length ? plugins : undefined,
|
|
235
228
|
mcpServers,
|
|
236
229
|
stderr: (chunk: string) => { stderrBuf += chunk; },
|
|
237
230
|
env: {
|
package/supervisor/index.ts
CHANGED
|
@@ -492,6 +492,7 @@ ${!connected ? '<script>setTimeout(()=>location.reload(),4000)</script>' : ''}
|
|
|
492
492
|
if (!cfg.channels.whatsapp) cfg.channels.whatsapp = { enabled: true, mode: 'channel' };
|
|
493
493
|
if (data.mode) cfg.channels.whatsapp.mode = data.mode;
|
|
494
494
|
if (data.admins !== undefined) cfg.channels.whatsapp.admins = data.admins;
|
|
495
|
+
if (data.skill !== undefined) cfg.channels.whatsapp.skill = data.skill;
|
|
495
496
|
saveConfig(cfg);
|
|
496
497
|
res.writeHead(200);
|
|
497
498
|
res.end(JSON.stringify({ ok: true, config: cfg.channels.whatsapp }));
|
|
@@ -202,6 +202,37 @@ Complex cron tasks can have detailed instruction files in `tasks/{cron-id}.md`.
|
|
|
202
202
|
|
|
203
203
|
---
|
|
204
204
|
|
|
205
|
+
## Skills
|
|
206
|
+
|
|
207
|
+
Skills live in `skills/` — each skill is a folder with instructions and resources:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
skills/
|
|
211
|
+
whatsapp-clinic/
|
|
212
|
+
SKILL.md # Instructions for you (how to use this skill)
|
|
213
|
+
SCRIPT.md # Customer-facing prompt (loaded as system prompt in business mode)
|
|
214
|
+
files/ # RAG documents, FAQs, etc.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Only ONE skill can be active for customer-facing mode at a time. The active skill is set in the channel config (`channels.whatsapp.skill`). When your human asks to switch skills, update the config:
|
|
218
|
+
```bash
|
|
219
|
+
curl -s -X POST http://localhost:3000/api/channels/whatsapp/configure \
|
|
220
|
+
-H "Content-Type: application/json" -d '{"skill":"whatsapp-clinic"}'
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**IMPORTANT: When editing skill files, always use the full path inside the skill directory.**
|
|
224
|
+
- Correct: `skills/whatsapp-clinic/SCRIPT.md`
|
|
225
|
+
- Wrong: `SCRIPT.md` (this writes to workspace root!)
|
|
226
|
+
|
|
227
|
+
Your installed skills and their SKILL.md contents are injected below in your context. If your human asks you to update a skill's behavior or script, edit the files INSIDE `skills/{skill-name}/`.
|
|
228
|
+
|
|
229
|
+
**Separation of concerns:**
|
|
230
|
+
- `MYSELF.md`, `MYHUMAN.md`, `MEMORY.md` — about YOU and your human. Always yours.
|
|
231
|
+
- `skills/{name}/SCRIPT.md` — business logic for customer interactions. Belongs to the skill.
|
|
232
|
+
- `whatsapp/{phone}.md` — customer conversation logs. Your memory of each customer.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
205
236
|
## Channels (WhatsApp, Telegram, etc.)
|
|
206
237
|
|
|
207
238
|
You can communicate through messaging channels beyond the chat bubble. Currently supported: **WhatsApp**.
|
|
@@ -229,7 +260,7 @@ Hi, I'd like to schedule an appointment.
|
|
|
229
260
|
The format is: `[Channel | phone | role | name (optional)]`
|
|
230
261
|
|
|
231
262
|
- **role=admin**: This is your human or an authorized admin. Use your normal personality, full capabilities, main system prompt.
|
|
232
|
-
- **role=customer**: This is someone else messaging.
|
|
263
|
+
- **role=customer**: This is someone else messaging. Follow the instructions from the active skill's SCRIPT.md (loaded as your system prompt).
|
|
233
264
|
|
|
234
265
|
### WhatsApp Modes
|
|
235
266
|
|
|
File without changes
|