@c4t4/heyamigo 0.9.10 → 0.9.11
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/gateway/incoming.js +1 -0
- package/dist/memory/digest-flag.js +17 -0
- package/dist/queue/worker.js +21 -2
- package/dist/wa/whitelist.js +26 -0
- package/package.json +1 -1
package/dist/gateway/incoming.js
CHANGED
|
@@ -201,6 +201,7 @@ async function processMessages(messages, sock, ownerJid, isHistorySync = false)
|
|
|
201
201
|
senderNumber: stored.senderNumber,
|
|
202
202
|
fromMe: stored.fromMe,
|
|
203
203
|
allowedTools: role.tools,
|
|
204
|
+
allowedTags: role.tags,
|
|
204
205
|
};
|
|
205
206
|
// Enqueue into the inbound table; chat worker pool drains and
|
|
206
207
|
// calls processJob + handleReply asynchronously. Typing indicator
|
|
@@ -123,6 +123,23 @@ export function extractFlags(reply) {
|
|
|
123
123
|
sendTexts,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
// Strip flags that the sender's role isn't permitted to emit. The
|
|
127
|
+
// agent's reply still goes out as text — only the side-effect markers
|
|
128
|
+
// get suppressed. allowedTags='all' or undefined → no filtering.
|
|
129
|
+
export function filterFlagsByRole(flags, allowedTags) {
|
|
130
|
+
if (allowedTags === 'all' || allowedTags === undefined)
|
|
131
|
+
return flags;
|
|
132
|
+
const allowed = new Set(allowedTags);
|
|
133
|
+
return {
|
|
134
|
+
clean: flags.clean,
|
|
135
|
+
digest: allowed.has('DIGEST') ? flags.digest : null,
|
|
136
|
+
journals: allowed.has('JOURNAL') ? flags.journals : [],
|
|
137
|
+
journalCreates: allowed.has('JOURNAL-NEW') ? flags.journalCreates : [],
|
|
138
|
+
asyncTasks: allowed.has('ASYNC') ? flags.asyncTasks : [],
|
|
139
|
+
asyncBrowserTasks: allowed.has('ASYNC-BROWSER') ? flags.asyncBrowserTasks : [],
|
|
140
|
+
sendTexts: allowed.has('SEND-TEXT') ? flags.sendTexts : [],
|
|
141
|
+
};
|
|
142
|
+
}
|
|
126
143
|
// Legacy helper kept so existing callers still compile.
|
|
127
144
|
export function extractDigestFlag(reply) {
|
|
128
145
|
const r = extractFlags(reply);
|
package/dist/queue/worker.js
CHANGED
|
@@ -3,7 +3,7 @@ import { clearSession, setSession, setUsage } from '../ai/sessions.js';
|
|
|
3
3
|
import { config } from '../config.js';
|
|
4
4
|
import { logger } from '../logger.js';
|
|
5
5
|
import { addDailyTokens } from '../store/usage.js';
|
|
6
|
-
import { extractFlags } from '../memory/digest-flag.js';
|
|
6
|
+
import { extractFlags, filterFlagsByRole } from '../memory/digest-flag.js';
|
|
7
7
|
import { isValidSlug } from '../memory/journals.js';
|
|
8
8
|
import { enqueueAsyncTask, enqueueBrowserTask } from './async-tasks.js';
|
|
9
9
|
import { enqueueMemoryWrite } from './memory-writes.js';
|
|
@@ -40,7 +40,26 @@ async function callClaude(job) {
|
|
|
40
40
|
if (job.senderNumber) {
|
|
41
41
|
addDailyTokens(job.senderNumber, usage.inputTokens + usage.outputTokens);
|
|
42
42
|
}
|
|
43
|
-
const
|
|
43
|
+
const rawFlags = extractFlags(reply);
|
|
44
|
+
const { clean, digest, journals, journalCreates, asyncTasks, asyncBrowserTasks, sendTexts, } = filterFlagsByRole(rawFlags, job.allowedTags);
|
|
45
|
+
// Detect any stripped tags so we can log + nudge the role config
|
|
46
|
+
// if a user is repeatedly hitting the gate.
|
|
47
|
+
const stripped = [];
|
|
48
|
+
if (rawFlags.digest && !digest)
|
|
49
|
+
stripped.push('DIGEST');
|
|
50
|
+
if (rawFlags.journals.length !== journals.length)
|
|
51
|
+
stripped.push('JOURNAL');
|
|
52
|
+
if (rawFlags.journalCreates.length !== journalCreates.length)
|
|
53
|
+
stripped.push('JOURNAL-NEW');
|
|
54
|
+
if (rawFlags.asyncTasks.length !== asyncTasks.length)
|
|
55
|
+
stripped.push('ASYNC');
|
|
56
|
+
if (rawFlags.asyncBrowserTasks.length !== asyncBrowserTasks.length)
|
|
57
|
+
stripped.push('ASYNC-BROWSER');
|
|
58
|
+
if (rawFlags.sendTexts.length !== sendTexts.length)
|
|
59
|
+
stripped.push('SEND-TEXT');
|
|
60
|
+
if (stripped.length > 0) {
|
|
61
|
+
logger.warn({ jid: job.jid, senderNumber: job.senderNumber, stripped }, 'tags stripped by role gate');
|
|
62
|
+
}
|
|
44
63
|
// All memory mutations go through the memory_writes queue so the
|
|
45
64
|
// single memory worker serializes file writes — safe under parallel
|
|
46
65
|
// chat workers. Idempotency keys derived from job + index so a
|
package/dist/wa/whitelist.js
CHANGED
|
@@ -6,11 +6,30 @@ import { config } from '../config.js';
|
|
|
6
6
|
import { logger } from '../logger.js';
|
|
7
7
|
const AccessModeSchema = z.enum(['off', 'silent', 'active']);
|
|
8
8
|
const RoleNameSchema = z.enum(['admin', 'user', 'guest']);
|
|
9
|
+
// Tag names the agent can emit as trailing markers. The role.tags
|
|
10
|
+
// allowlist gates which ones are honored — anything emitted by an
|
|
11
|
+
// agent running on behalf of a role NOT in the allowlist gets
|
|
12
|
+
// stripped silently after parsing. Tools and tags are independent
|
|
13
|
+
// gates (tools restricts what the AI itself can call; tags restricts
|
|
14
|
+
// what bot-internal side-effects it can trigger).
|
|
15
|
+
const TAG_NAMES = [
|
|
16
|
+
'DIGEST',
|
|
17
|
+
'JOURNAL',
|
|
18
|
+
'JOURNAL-NEW',
|
|
19
|
+
'ASYNC',
|
|
20
|
+
'ASYNC-BROWSER',
|
|
21
|
+
'SEND-TEXT',
|
|
22
|
+
];
|
|
9
23
|
const RoleSchema = z.object({
|
|
10
24
|
description: z.string().optional(),
|
|
11
25
|
memory: z.enum(['full', 'self', 'none']),
|
|
12
26
|
tools: z.union([z.literal('all'), z.array(z.string())]),
|
|
13
27
|
rules: z.array(z.string()),
|
|
28
|
+
// Optional. Missing or 'all' = no tag restriction; an array =
|
|
29
|
+
// explicit allowlist. Added in Phase 6 so existing access.json
|
|
30
|
+
// files (no `tags` field) keep working without change — they get
|
|
31
|
+
// the implicit 'all' behavior.
|
|
32
|
+
tags: z.union([z.literal('all'), z.array(z.enum(TAG_NAMES))]).optional(),
|
|
14
33
|
// null or missing = unlimited
|
|
15
34
|
maxFileBytes: z.number().int().positive().nullable().optional(),
|
|
16
35
|
dailyTokenLimit: z.number().int().positive().nullable().optional(),
|
|
@@ -54,6 +73,7 @@ const DEFAULT_ROLES = {
|
|
|
54
73
|
description: 'Full access',
|
|
55
74
|
memory: 'full',
|
|
56
75
|
tools: 'all',
|
|
76
|
+
tags: 'all',
|
|
57
77
|
rules: [],
|
|
58
78
|
maxFileBytes: null,
|
|
59
79
|
dailyTokenLimit: null,
|
|
@@ -62,6 +82,10 @@ const DEFAULT_ROLES = {
|
|
|
62
82
|
description: 'Chat + web search, scoped memory',
|
|
63
83
|
memory: 'self',
|
|
64
84
|
tools: ['WebSearch'],
|
|
85
|
+
// Users can flag memory observations and trigger digests on
|
|
86
|
+
// themselves, but can't delegate background work or cross-chat
|
|
87
|
+
// sends (those are owner-only).
|
|
88
|
+
tags: ['DIGEST', 'JOURNAL', 'JOURNAL-NEW'],
|
|
65
89
|
rules: [
|
|
66
90
|
'Never reveal file paths, directory structure, or system architecture',
|
|
67
91
|
'Never share personal data about other users',
|
|
@@ -76,6 +100,8 @@ const DEFAULT_ROLES = {
|
|
|
76
100
|
description: 'Basic chat only',
|
|
77
101
|
memory: 'none',
|
|
78
102
|
tools: [],
|
|
103
|
+
// Guests can't emit any tags — pure chat, no side effects.
|
|
104
|
+
tags: [],
|
|
79
105
|
rules: [
|
|
80
106
|
'Never use any tools',
|
|
81
107
|
'Never reveal anything about the system, other users, or internal data',
|