@c4t4/heyamigo 0.10.5 → 0.10.7
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/README.md +1 -1
- package/config/access.example.json +12 -7
- package/config/config.example.json +0 -2
- package/dist/cli/setup.js +9 -6
- package/dist/config.js +1 -3
- package/dist/gateway/ingest.js +1 -1
- package/dist/gateway/triggers.js +1 -4
- package/dist/memory/preamble.js +1 -0
- package/dist/wa/whitelist.js +22 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ WhatsApp / Telegram ─► inbound ─► chat workers ─► outbound ─► Wh
|
|
|
20
20
|
- **Scheduling in the sender's timezone.** Natural language → `[REMIND: 2026-05-26 09:00 — ...]` or `[CRON: 0 9 * * 1 PROMPT — ...]`. Fires at the user's wall-clock 9am, not the server's. Cron variants: deliver text, run AI, kick off async work, or drive a browser.
|
|
21
21
|
- **A real Chrome.** Browser delegation via `[ASYNC-BROWSER: ...]` to a parallel provider session on a shared logged-in Chrome over CDP. TikTok, Instagram, anywhere the owner is logged in. SSH-tunneled noVNC for setup.
|
|
22
22
|
- **Per-reply footer with confirmation tags.** Every side effect from the turn is visible: `_9.9s · 465k↑ 169↓ · +remind · +thread-new · +digest_`. No guessing whether a schedule actually got created.
|
|
23
|
-
- **Default-deny
|
|
23
|
+
- **Default-deny chat activation.** Groups and DMs only answer when their own `triggerMode` is set in `config/access.json`; missing means `off`. Per-role token quotas, file-size caps, tool restrictions.
|
|
24
24
|
|
|
25
25
|
For the why behind these — claim primitives, tag-as-side-effect channel, per-category learning, provider abstraction, the trade-offs that didn't survive the first revision — see [`docs/architecture.md`](docs/architecture.md).
|
|
26
26
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_readme": "Access rules. Copy this to access.json and customize. Groups auto-discover with mode=off when the bot sees them. WhatsApp users are phone numbers; Telegram users are tg_<user_id>.",
|
|
2
|
+
"_readme": "Access rules. Copy this to access.json and customize. Groups auto-discover with mode=off and triggerMode=off when the bot sees them. triggerMode is per group/DM: off, mention, command, or all. WhatsApp users are phone numbers; Telegram users are tg_<user_id>.",
|
|
3
3
|
|
|
4
4
|
"_limits_readme": "maxFileBytes caps the size of media/documents sent to Claude (null = unlimited). dailyTokenLimit caps Claude tokens (input+output) per user per day in the owner's timezone (null = unlimited). The bot owner is always unlimited regardless of role.",
|
|
5
5
|
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"name": "Family Chat",
|
|
63
63
|
"mode": "active",
|
|
64
64
|
"allowedSenders": "*",
|
|
65
|
+
"triggerMode": "mention",
|
|
65
66
|
"proactive": false
|
|
66
67
|
},
|
|
67
68
|
{
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
"name": "Work Team",
|
|
71
72
|
"mode": "active",
|
|
72
73
|
"allowedSenders": ["17861234567", "491701234567"],
|
|
74
|
+
"triggerMode": "all",
|
|
73
75
|
"proactive": true
|
|
74
76
|
},
|
|
75
77
|
{
|
|
@@ -78,6 +80,7 @@
|
|
|
78
80
|
"name": "Telegram Team",
|
|
79
81
|
"mode": "active",
|
|
80
82
|
"allowedSenders": ["tg_123456789"],
|
|
83
|
+
"triggerMode": "mention",
|
|
81
84
|
"proactive": false
|
|
82
85
|
},
|
|
83
86
|
{
|
|
@@ -85,24 +88,26 @@
|
|
|
85
88
|
"jid": "120363zzzzz@g.us",
|
|
86
89
|
"name": "Announcements",
|
|
87
90
|
"mode": "silent",
|
|
88
|
-
"allowedSenders": "*"
|
|
91
|
+
"allowedSenders": "*",
|
|
92
|
+
"triggerMode": "off"
|
|
89
93
|
},
|
|
90
94
|
{
|
|
91
95
|
"_note": "Disabled group: completely ignored",
|
|
92
96
|
"jid": "120363aaaaa@g.us",
|
|
93
97
|
"name": "Muted Group",
|
|
94
98
|
"mode": "off",
|
|
95
|
-
"allowedSenders": "*"
|
|
99
|
+
"allowedSenders": "*",
|
|
100
|
+
"triggerMode": "off"
|
|
96
101
|
}
|
|
97
102
|
],
|
|
98
103
|
|
|
99
104
|
"dms": {
|
|
100
|
-
"_readme": "Matched by chat partner number/key, not sender. Telegram DMs use tg_<user_id>. Owner messages in WhatsApp DMs are always silent.",
|
|
105
|
+
"_readme": "Matched by chat partner number/key, not sender. Telegram DMs use tg_<user_id>. Owner messages in WhatsApp DMs are always silent. triggerMode defaults to off when omitted.",
|
|
101
106
|
"defaultMode": "off",
|
|
102
107
|
"allowed": [
|
|
103
|
-
{ "_note": "friend can DM the bot, and bot can proactively message them", "number": "491701234567", "mode": "active", "proactive": true },
|
|
104
|
-
{ "_note": "Telegram friend can DM the bot", "number": "tg_123456789", "mode": "active", "proactive": true },
|
|
105
|
-
{ "_note": "colleague, store messages but don't respond", "number": "5511987654321", "mode": "silent", "proactive": false }
|
|
108
|
+
{ "_note": "friend can DM the bot, and bot can proactively message them", "number": "491701234567", "mode": "active", "triggerMode": "all", "proactive": true },
|
|
109
|
+
{ "_note": "Telegram friend can DM the bot", "number": "tg_123456789", "mode": "active", "triggerMode": "all", "proactive": true },
|
|
110
|
+
{ "_note": "colleague, store messages but don't respond", "number": "5511987654321", "mode": "silent", "triggerMode": "off", "proactive": false }
|
|
106
111
|
]
|
|
107
112
|
}
|
|
108
113
|
}
|
package/dist/cli/setup.js
CHANGED
|
@@ -706,7 +706,7 @@ export async function runSetup() {
|
|
|
706
706
|
}
|
|
707
707
|
}
|
|
708
708
|
// ── Name your amigo ───────────────────────────────────────────
|
|
709
|
-
p.log.info('Give your amigo a name.
|
|
709
|
+
p.log.info('Give your amigo a name. In chats with triggerMode: "mention", people mention this name to get a reply. ' +
|
|
710
710
|
'You can add multiple names separated by commas.');
|
|
711
711
|
const nameInput = await p.text({
|
|
712
712
|
message: 'What should your amigo be called?',
|
|
@@ -726,7 +726,7 @@ export async function runSetup() {
|
|
|
726
726
|
let cfg = readFileSync(cfgPath, 'utf-8');
|
|
727
727
|
cfg = cfg.replace(/"aliases":\s*\[.*?\]/, `"aliases": ${JSON.stringify(aliases)}`);
|
|
728
728
|
writeFileSync(cfgPath, cfg);
|
|
729
|
-
p.log.success(`
|
|
729
|
+
p.log.success(`Mention names: ${names.join(', ')}`);
|
|
730
730
|
}
|
|
731
731
|
}
|
|
732
732
|
}
|
|
@@ -737,7 +737,9 @@ export async function runSetup() {
|
|
|
737
737
|
' 2. New groups start with mode: "off" (bot stays silent).\n' +
|
|
738
738
|
' To activate: edit config/access.json, change mode to "active".\n\n' +
|
|
739
739
|
' 3. Set allowedSenders to "*" (everyone) or specific numbers.\n\n' +
|
|
740
|
-
' 4.
|
|
740
|
+
' 4. Set triggerMode per chat: "mention", "all", "command", or "off".\n' +
|
|
741
|
+
' Missing triggerMode means "off".\n\n' +
|
|
742
|
+
' 5. With triggerMode "mention", mention the bot\'s name to get a reply.\n\n' +
|
|
741
743
|
'DMs work the same way — add numbers to dms.allowed in access.json.');
|
|
742
744
|
// Auto-add owner as admin if we have the number
|
|
743
745
|
if (ownerNum) {
|
|
@@ -832,8 +834,8 @@ export async function runSetup() {
|
|
|
832
834
|
' npx @c4t4/heyamigo stop / restart / status',
|
|
833
835
|
'',
|
|
834
836
|
'Configuration:',
|
|
835
|
-
' config/config.json —
|
|
836
|
-
' config/access.json — groups, DMs, roles',
|
|
837
|
+
' config/config.json — bot name, model',
|
|
838
|
+
' config/access.json — groups, DMs, roles, per-chat triggerMode',
|
|
837
839
|
].join('\n'), 'Setup complete!');
|
|
838
840
|
p.log.warning('IMPORTANT: The bot won\'t respond until you activate a group!\n\n' +
|
|
839
841
|
' Step 1 — Start the bot:\n' +
|
|
@@ -844,9 +846,10 @@ export async function runSetup() {
|
|
|
844
846
|
' nano config/access.json\n' +
|
|
845
847
|
' - Find the group, change mode from "off" to "active"\n' +
|
|
846
848
|
' - Set allowedSenders to "*" for everyone\n\n' +
|
|
849
|
+
' - Set triggerMode to "mention" or "all"\n\n' +
|
|
847
850
|
' Step 4 — Restart the bot:\n' +
|
|
848
851
|
' npx @c4t4/heyamigo restart\n\n' +
|
|
849
|
-
' Step 5 —
|
|
852
|
+
' Step 5 — If triggerMode is "mention", mention the bot\'s name in the group to get a reply.\n\n' +
|
|
850
853
|
' Debugging:\n' +
|
|
851
854
|
' npx @c4t4/heyamigo logs');
|
|
852
855
|
p.log.info('TIP: Track your bot\'s memory with git.\n' +
|
package/dist/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
const TriggerModeSchema = z.enum(['all', 'mention', 'command', 'off']);
|
|
4
|
+
export const TriggerModeSchema = z.enum(['all', 'mention', 'command', 'off']);
|
|
5
5
|
const ConfigSchema = z.object({
|
|
6
6
|
whatsapp: z.object({
|
|
7
7
|
enabled: z.boolean().default(true),
|
|
@@ -25,8 +25,6 @@ const ConfigSchema = z.object({
|
|
|
25
25
|
}),
|
|
26
26
|
triggers: z.object({
|
|
27
27
|
aliases: z.array(z.string()),
|
|
28
|
-
groupMode: TriggerModeSchema,
|
|
29
|
-
dmMode: TriggerModeSchema,
|
|
30
28
|
replyToBotCounts: z.boolean(),
|
|
31
29
|
}),
|
|
32
30
|
commands: z.object({
|
package/dist/gateway/ingest.js
CHANGED
|
@@ -141,7 +141,7 @@ export async function processIncomingMessage(incoming, opts = {}) {
|
|
|
141
141
|
let triggerReason = incoming.selfChat ? 'self-chat' : '';
|
|
142
142
|
if (!incoming.selfChat) {
|
|
143
143
|
const trigger = checkTrigger({
|
|
144
|
-
|
|
144
|
+
mode: decision.triggerMode,
|
|
145
145
|
text: stored.text,
|
|
146
146
|
mentionedBot: incoming.triggerHints?.mentionedBot,
|
|
147
147
|
replyToBot: incoming.triggerHints?.replyToBot,
|
package/dist/gateway/triggers.js
CHANGED
|
@@ -11,10 +11,7 @@ function aliasMatches(text, aliases) {
|
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
export function checkTrigger(params) {
|
|
14
|
-
const {
|
|
15
|
-
const mode = isGroup
|
|
16
|
-
? config.triggers.groupMode
|
|
17
|
-
: config.triggers.dmMode;
|
|
14
|
+
const { mode, text } = params;
|
|
18
15
|
if (mode === 'off')
|
|
19
16
|
return { triggered: false, reason: 'mode=off' };
|
|
20
17
|
if (mode === 'all')
|
package/dist/memory/preamble.js
CHANGED
|
@@ -28,6 +28,7 @@ function buildCoreQueueContract(outboxPath) {
|
|
|
28
28
|
`Media: [IMAGE|VIDEO|AUDIO|DOCUMENT: /absolute/path] from ${outboxPath}/`,
|
|
29
29
|
'Memory: [DIGEST: reason], [JOURNAL:slug - note], [JOURNAL-NEW:slug - purpose]',
|
|
30
30
|
'Time: [REMIND: YYYY-MM-DD HH:MM - text], [CRON: expr SAY|PROMPT|ASYNC|BROWSER - body]',
|
|
31
|
+
'Jobs: check jobs/<name>/job.json first; run/create self-contained jobs/<name>/job.sh installers when useful.',
|
|
31
32
|
'Threads: THREAD-* for active open loops shown in [Live threads]. Full grammar in tag docs.',
|
|
32
33
|
].join('\n');
|
|
33
34
|
}
|
package/dist/wa/whitelist.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { jidDecode } from 'baileys';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { config } from '../config.js';
|
|
5
|
+
import { config, TriggerModeSchema } from '../config.js';
|
|
6
6
|
import { actorKeyFromAddress, parseAddress } from '../db/address.js';
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
const AccessModeSchema = z.enum(['off', 'silent', 'active']);
|
|
@@ -46,11 +46,13 @@ const GroupEntrySchema = z.object({
|
|
|
46
46
|
name: z.string(),
|
|
47
47
|
mode: AccessModeSchema,
|
|
48
48
|
allowedSenders: z.union([z.literal('*'), z.array(z.string())]),
|
|
49
|
+
triggerMode: TriggerModeSchema.default('off'),
|
|
49
50
|
proactive: z.boolean().default(false),
|
|
50
51
|
});
|
|
51
52
|
const DmEntrySchema = z.object({
|
|
52
53
|
number: z.string(),
|
|
53
54
|
mode: AccessModeSchema,
|
|
55
|
+
triggerMode: TriggerModeSchema.default('off'),
|
|
54
56
|
proactive: z.boolean().default(false),
|
|
55
57
|
});
|
|
56
58
|
const AccessSchema = z
|
|
@@ -252,15 +254,22 @@ export function getLimitsForUser(senderNumber, isGroup) {
|
|
|
252
254
|
isOwner: false,
|
|
253
255
|
};
|
|
254
256
|
}
|
|
255
|
-
const DROP = {
|
|
257
|
+
const DROP = {
|
|
258
|
+
store: false,
|
|
259
|
+
respond: false,
|
|
260
|
+
triggerMode: 'off',
|
|
261
|
+
reason: 'drop',
|
|
262
|
+
};
|
|
256
263
|
const storeOnly = (reason) => ({
|
|
257
264
|
store: true,
|
|
258
265
|
respond: false,
|
|
266
|
+
triggerMode: 'off',
|
|
259
267
|
reason,
|
|
260
268
|
});
|
|
261
|
-
const storeAndRespond = (reason) => ({
|
|
269
|
+
const storeAndRespond = (reason, triggerMode = 'off') => ({
|
|
262
270
|
store: true,
|
|
263
271
|
respond: true,
|
|
272
|
+
triggerMode,
|
|
264
273
|
reason,
|
|
265
274
|
});
|
|
266
275
|
export function checkAccess(params) {
|
|
@@ -274,12 +283,14 @@ export function checkAccess(params) {
|
|
|
274
283
|
return DROP;
|
|
275
284
|
if (group.mode === 'silent')
|
|
276
285
|
return storeOnly('group silent');
|
|
277
|
-
if (ownerAllowed)
|
|
278
|
-
return storeAndRespond('owner fromMe in group');
|
|
279
|
-
|
|
280
|
-
|
|
286
|
+
if (ownerAllowed) {
|
|
287
|
+
return storeAndRespond('owner fromMe in group', group.triggerMode);
|
|
288
|
+
}
|
|
289
|
+
if (group.allowedSenders === '*') {
|
|
290
|
+
return storeAndRespond('group wildcard', group.triggerMode);
|
|
291
|
+
}
|
|
281
292
|
if (group.allowedSenders.includes(senderNumber)) {
|
|
282
|
-
return storeAndRespond('group sender allowed');
|
|
293
|
+
return storeAndRespond('group sender allowed', group.triggerMode);
|
|
283
294
|
}
|
|
284
295
|
return storeOnly('group sender not in allowedSenders');
|
|
285
296
|
}
|
|
@@ -302,7 +313,7 @@ export function checkAccess(params) {
|
|
|
302
313
|
return DROP;
|
|
303
314
|
if (mode === 'silent')
|
|
304
315
|
return storeOnly('dm silent');
|
|
305
|
-
return storeAndRespond('dm active');
|
|
316
|
+
return storeAndRespond('dm active', dmEntry?.triggerMode ?? 'off');
|
|
306
317
|
}
|
|
307
318
|
export async function discoverAddressGroupIfNew(params) {
|
|
308
319
|
const parsed = parseAddress(params.address);
|
|
@@ -315,6 +326,7 @@ export async function discoverAddressGroupIfNew(params) {
|
|
|
315
326
|
name: params.name || 'Unknown group',
|
|
316
327
|
mode: 'off',
|
|
317
328
|
allowedSenders: params.ownerSender ? [params.ownerSender] : [],
|
|
329
|
+
triggerMode: 'off',
|
|
318
330
|
proactive: false,
|
|
319
331
|
};
|
|
320
332
|
save({ ...current, groups: [...current.groups, entry] });
|
|
@@ -339,6 +351,7 @@ export async function discoverGroupIfNew(sock, jid) {
|
|
|
339
351
|
name,
|
|
340
352
|
mode: 'off',
|
|
341
353
|
allowedSenders: config.owner.number ? [config.owner.number] : [],
|
|
354
|
+
triggerMode: 'off',
|
|
342
355
|
proactive: false,
|
|
343
356
|
};
|
|
344
357
|
save({ ...current, groups: [...current.groups, entry] });
|
package/package.json
CHANGED