@c4t4/heyamigo 0.1.18 → 0.5.0
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/config/memory-instructions.md +162 -21
- package/dist/gateway/commands.js +4 -0
- package/dist/gateway/outgoing.js +39 -0
- package/dist/memory/digest-flag.js +67 -5
- package/dist/memory/journal-cadence.js +120 -0
- package/dist/memory/journal-nudger.js +282 -0
- package/dist/memory/journal-observer.js +225 -0
- package/dist/memory/journals.js +306 -0
- package/dist/memory/preamble.js +33 -1
- package/dist/memory/scheduler.js +36 -1
- package/dist/queue/async-tasks.js +215 -0
- package/dist/queue/worker.js +60 -5
- package/dist/wa/whitelist.js +8 -3
- package/package.json +1 -1
package/dist/queue/worker.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { askClaude } from '../ai/claude.js';
|
|
2
2
|
import { clearSession, setSession, setUsage } from '../ai/sessions.js';
|
|
3
3
|
import { logger } from '../logger.js';
|
|
4
|
-
import {
|
|
4
|
+
import { extractFlags } from '../memory/digest-flag.js';
|
|
5
|
+
import { appendEntry, createJournal, getJournal, isValidSlug, } from '../memory/journals.js';
|
|
5
6
|
import { scheduleDigest } from '../memory/scheduler.js';
|
|
7
|
+
import { enqueueAsyncTask } from './async-tasks.js';
|
|
6
8
|
function isStaleSessionError(err) {
|
|
7
9
|
return (err instanceof Error &&
|
|
8
10
|
err.message.includes('No conversation found'));
|
|
@@ -25,17 +27,70 @@ async function callClaude(job) {
|
|
|
25
27
|
totalContextTokens,
|
|
26
28
|
updatedAt: Math.floor(Date.now() / 1000),
|
|
27
29
|
});
|
|
28
|
-
const { clean,
|
|
29
|
-
if (
|
|
30
|
-
logger.info({ jid: job.jid, number: job.senderNumber, reason:
|
|
30
|
+
const { clean, digest, journals, journalCreates, asyncTasks } = extractFlags(reply);
|
|
31
|
+
if (digest) {
|
|
32
|
+
logger.info({ jid: job.jid, number: job.senderNumber, reason: digest }, 'DIGEST flag raised, scheduling');
|
|
31
33
|
scheduleDigest({
|
|
32
34
|
jid: job.jid,
|
|
33
35
|
number: job.senderNumber,
|
|
34
|
-
reason:
|
|
36
|
+
reason: digest,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Creates run BEFORE entry appends so that a reply creating a new journal
|
|
40
|
+
// AND flagging its first entry in the same turn works correctly.
|
|
41
|
+
for (const op of journalCreates) {
|
|
42
|
+
if (!isValidSlug(op.slug)) {
|
|
43
|
+
logger.warn({ op, jid: job.jid }, 'JOURNAL-NEW: invalid slug, dropped');
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
if (getJournal(op.slug)) {
|
|
48
|
+
logger.info({ slug: op.slug }, 'JOURNAL-NEW for existing slug, ignored');
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
createJournal({
|
|
52
|
+
slug: op.slug,
|
|
53
|
+
name: titleCase(op.slug),
|
|
54
|
+
purpose: op.purpose,
|
|
55
|
+
});
|
|
56
|
+
logger.info({ slug: op.slug, jid: job.jid }, 'journal created via bot marker');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger.error({ err, op, jid: job.jid }, 'JOURNAL-NEW failed');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const j of journals) {
|
|
63
|
+
const ok = appendEntry(j.slug, {
|
|
64
|
+
source: 'reactive',
|
|
65
|
+
jid: job.jid,
|
|
66
|
+
senderNumber: job.senderNumber,
|
|
67
|
+
note: j.note,
|
|
68
|
+
});
|
|
69
|
+
if (!ok) {
|
|
70
|
+
logger.warn({ slug: j.slug, jid: job.jid }, 'JOURNAL flag pointed at unknown slug, dropped');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Async tasks: Claude delegated long work (browser scrapes, multi-step
|
|
74
|
+
// research, etc.) to the background lane. The clean reply above is the
|
|
75
|
+
// user-facing ack and will be sent normally. The async tasks run stateless
|
|
76
|
+
// in their own queue and report back via initiate() when done.
|
|
77
|
+
for (const t of asyncTasks) {
|
|
78
|
+
enqueueAsyncTask({
|
|
79
|
+
jid: job.jid,
|
|
80
|
+
senderNumber: job.senderNumber,
|
|
81
|
+
description: t.description,
|
|
82
|
+
originatingMessage: job.text,
|
|
83
|
+
allowedTools: job.allowedTools ?? 'all',
|
|
35
84
|
});
|
|
36
85
|
}
|
|
37
86
|
return { reply: clean };
|
|
38
87
|
}
|
|
88
|
+
function titleCase(slug) {
|
|
89
|
+
return slug
|
|
90
|
+
.split('-')
|
|
91
|
+
.map((p) => (p ? p[0].toUpperCase() + p.slice(1) : p))
|
|
92
|
+
.join(' ');
|
|
93
|
+
}
|
|
39
94
|
export async function processJob(job) {
|
|
40
95
|
try {
|
|
41
96
|
return await callClaude(job);
|
package/dist/wa/whitelist.js
CHANGED
|
@@ -96,9 +96,11 @@ function save(next) {
|
|
|
96
96
|
export function getAccess() {
|
|
97
97
|
return current;
|
|
98
98
|
}
|
|
99
|
-
// Guardrail for proactive (unsolicited) messaging.
|
|
100
|
-
//
|
|
101
|
-
//
|
|
99
|
+
// Guardrail for proactive (unsolicited) messaging. Default deny.
|
|
100
|
+
//
|
|
101
|
+
// Exception: the owner's own self-DM is always allowed — the owner implicitly
|
|
102
|
+
// consents to the bot nudging them in their own DM. Other DMs and groups
|
|
103
|
+
// require an explicit `proactive: true` entry in access.json.
|
|
102
104
|
export function canSendProactive(jid) {
|
|
103
105
|
const isGroup = jid.endsWith('@g.us');
|
|
104
106
|
if (isGroup) {
|
|
@@ -108,6 +110,9 @@ export function canSendProactive(jid) {
|
|
|
108
110
|
const number = jidDecode(jid)?.user;
|
|
109
111
|
if (!number)
|
|
110
112
|
return false;
|
|
113
|
+
// Owner's self-DM is always allowed.
|
|
114
|
+
if (config.owner.number && number === config.owner.number)
|
|
115
|
+
return true;
|
|
111
116
|
const entry = current.dms.allowed.find((d) => d.number === number);
|
|
112
117
|
return entry?.proactive === true;
|
|
113
118
|
}
|