@mininglamp-oss/cc-channel-octo 1.0.1-dev.0ac574a

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +361 -0
  2. package/LICENSE +191 -0
  3. package/README.md +577 -0
  4. package/config.bot.example.json +15 -0
  5. package/config.example.json +33 -0
  6. package/dist/agent-bridge.d.ts +91 -0
  7. package/dist/agent-bridge.js +397 -0
  8. package/dist/agent-bridge.js.map +1 -0
  9. package/dist/cli.d.ts +109 -0
  10. package/dist/cli.js +467 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/commands.d.ts +57 -0
  13. package/dist/commands.js +121 -0
  14. package/dist/commands.js.map +1 -0
  15. package/dist/config.d.ts +294 -0
  16. package/dist/config.js +344 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/configure.d.ts +11 -0
  19. package/dist/configure.js +106 -0
  20. package/dist/configure.js.map +1 -0
  21. package/dist/cron-evaluator.d.ts +53 -0
  22. package/dist/cron-evaluator.js +191 -0
  23. package/dist/cron-evaluator.js.map +1 -0
  24. package/dist/cron-fire-marker.d.ts +24 -0
  25. package/dist/cron-fire-marker.js +25 -0
  26. package/dist/cron-fire-marker.js.map +1 -0
  27. package/dist/cron-scheduler.d.ts +46 -0
  28. package/dist/cron-scheduler.js +114 -0
  29. package/dist/cron-scheduler.js.map +1 -0
  30. package/dist/cron-store.d.ts +62 -0
  31. package/dist/cron-store.js +63 -0
  32. package/dist/cron-store.js.map +1 -0
  33. package/dist/cron-tool.d.ts +44 -0
  34. package/dist/cron-tool.js +151 -0
  35. package/dist/cron-tool.js.map +1 -0
  36. package/dist/cwd-resolver.d.ts +72 -0
  37. package/dist/cwd-resolver.js +166 -0
  38. package/dist/cwd-resolver.js.map +1 -0
  39. package/dist/db-adapter.d.ts +21 -0
  40. package/dist/db-adapter.js +64 -0
  41. package/dist/db-adapter.js.map +1 -0
  42. package/dist/file-inline-wrap.d.ts +94 -0
  43. package/dist/file-inline-wrap.js +243 -0
  44. package/dist/file-inline-wrap.js.map +1 -0
  45. package/dist/gateway.d.ts +105 -0
  46. package/dist/gateway.js +425 -0
  47. package/dist/gateway.js.map +1 -0
  48. package/dist/group-config.d.ts +41 -0
  49. package/dist/group-config.js +104 -0
  50. package/dist/group-config.js.map +1 -0
  51. package/dist/group-context.d.ts +81 -0
  52. package/dist/group-context.js +466 -0
  53. package/dist/group-context.js.map +1 -0
  54. package/dist/inbound.d.ts +136 -0
  55. package/dist/inbound.js +667 -0
  56. package/dist/inbound.js.map +1 -0
  57. package/dist/index.d.ts +65 -0
  58. package/dist/index.js +1026 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/media-inbound.d.ts +38 -0
  61. package/dist/media-inbound.js +131 -0
  62. package/dist/media-inbound.js.map +1 -0
  63. package/dist/mention-utils.d.ts +108 -0
  64. package/dist/mention-utils.js +199 -0
  65. package/dist/mention-utils.js.map +1 -0
  66. package/dist/octo/api.d.ts +148 -0
  67. package/dist/octo/api.js +320 -0
  68. package/dist/octo/api.js.map +1 -0
  69. package/dist/octo/socket.d.ts +102 -0
  70. package/dist/octo/socket.js +793 -0
  71. package/dist/octo/socket.js.map +1 -0
  72. package/dist/octo/types.d.ts +126 -0
  73. package/dist/octo/types.js +35 -0
  74. package/dist/octo/types.js.map +1 -0
  75. package/dist/prompt-safety.d.ts +78 -0
  76. package/dist/prompt-safety.js +148 -0
  77. package/dist/prompt-safety.js.map +1 -0
  78. package/dist/session-router.d.ts +144 -0
  79. package/dist/session-router.js +490 -0
  80. package/dist/session-router.js.map +1 -0
  81. package/dist/session-store.d.ts +89 -0
  82. package/dist/session-store.js +297 -0
  83. package/dist/session-store.js.map +1 -0
  84. package/dist/skill-linker.d.ts +31 -0
  85. package/dist/skill-linker.js +160 -0
  86. package/dist/skill-linker.js.map +1 -0
  87. package/dist/stream-relay.d.ts +42 -0
  88. package/dist/stream-relay.js +243 -0
  89. package/dist/stream-relay.js.map +1 -0
  90. package/dist/url-policy.d.ts +103 -0
  91. package/dist/url-policy.js +290 -0
  92. package/dist/url-policy.js.map +1 -0
  93. package/package.json +79 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Group Context — group chat message cache + maxContextChars budget + mention mapping.
3
+ */
4
+ import type { DbAdapter } from './db-adapter.js';
5
+ export declare class GroupContext {
6
+ private readonly messageCache;
7
+ private readonly memberMapByChannel;
8
+ private readonly nameToUidByChannel;
9
+ private readonly robotFlags;
10
+ private readonly lastRefresh;
11
+ private readonly adapter;
12
+ private readonly maxContextChars;
13
+ private readonly maxWindowSize;
14
+ private upsertMember;
15
+ private deleteMember;
16
+ private selectMembers;
17
+ private insertMessage;
18
+ private selectRecentMessages;
19
+ private deleteOldMessages;
20
+ private selectMessagesSince;
21
+ private selectMaxId;
22
+ private upsertCursor;
23
+ private selectCursor;
24
+ constructor(adapter: DbAdapter, maxContextChars: number);
25
+ private initStatements;
26
+ private getMemberMap;
27
+ private getNameToUid;
28
+ pushMessage(channelId: string, fromUid: string, fromName: string, content: string, timestamp: number): void;
29
+ learnMember(channelId: string, uid: string, name: string): void;
30
+ refreshMembers(channelId: string, apiUrl: string, botToken: string): Promise<void>;
31
+ fetchAndLearnUser(uid: string, channelId: string, apiUrl: string, botToken: string): Promise<string | undefined>;
32
+ buildContext(channelId: string): string;
33
+ /** Read the per-channel consumption cursor (highest already-injected id), or 0. */
34
+ getContextCursor(channelId: string): number;
35
+ /** Advance the per-channel cursor to `lastId` (monotonic — never moves backward). */
36
+ setContextCursor(channelId: string, lastId: number): void;
37
+ /** The highest group_messages.id for a channel (for cursor priming), or 0. */
38
+ getMaxMessageId(channelId: string): number;
39
+ /**
40
+ * Build a group-context block from messages NEWER than `sinceId` (the delta the
41
+ * model hasn't seen yet), within `maxContextChars`. Returns the block text (same
42
+ * `[Recent group messages]` format as buildContext) and the highest id that
43
+ * EXISTS in the channel above `sinceId` (so the caller advances the cursor past
44
+ * the whole delta, including any oldest lines the char budget dropped — those
45
+ * are the least-relevant and are intentionally not re-shown). Empty text + the
46
+ * unchanged cursor when there's nothing new.
47
+ *
48
+ * Rows are fetched newest-first (so a backlog larger than the budget keeps the
49
+ * most-recent messages); the selected slice is reversed back to chronological
50
+ * order for display. Unlike buildContext (in-memory rolling window), this reads
51
+ * from the DB so the cursor delta is exact even across restarts / window eviction.
52
+ */
53
+ buildContextSince(channelId: string, sinceId: number): {
54
+ text: string;
55
+ lastId: number;
56
+ };
57
+ resolveMentions(text: string, channelId: string): string[];
58
+ getName(uid: string, channelId: string): string | undefined;
59
+ /** G23: Check the server-authoritative robot flag for a group member. */
60
+ isRobot(channelId: string, uid: string): boolean | undefined;
61
+ /**
62
+ * A8 (#143): true iff `uid` is a current member of `channelId` per the cached
63
+ * member list. The list is kept authoritative by refreshMembers (it prunes
64
+ * departed members, not just upserts) — but it is only best-effort fresh:
65
+ * refresh is throttled to REFRESH_INTERVAL_MS and seeded from DB on restart,
66
+ * so a membership change inside that window may not be reflected yet. Used by
67
+ * the outbound mention guard as defense-in-depth (the server still enforces
68
+ * real permissions), NOT as a real-time authority.
69
+ */
70
+ isMember(channelId: string, uid: string): boolean;
71
+ /**
72
+ * A8 (#143): the channel's displayName→uid map, for v1 `@name` outbound
73
+ * resolution in StreamRelay.deliver. Returns the live map (empty if the
74
+ * channel has no cached members yet). Read-only use by callers.
75
+ */
76
+ getNameToUidMap(channelId: string): Map<string, string>;
77
+ loadMembersFromDb(channelId: string): void;
78
+ loadMessagesFromDb(channelId: string): void;
79
+ /** Load all persisted members and messages from DB (call once at startup). */
80
+ loadAllFromDb(): void;
81
+ }
@@ -0,0 +1,466 @@
1
+ /**
2
+ * Group Context — group chat message cache + maxContextChars budget + mention mapping.
3
+ */
4
+ import { getGroupMembers, fetchUserInfo } from './octo/api.js';
5
+ import { sanitizeDisplayName } from './prompt-safety.js';
6
+ const REFRESH_INTERVAL_MS = 60 * 60 * 1000;
7
+ export class GroupContext {
8
+ messageCache = new Map();
9
+ // Per-channel member maps to avoid cross-group name collisions
10
+ memberMapByChannel = new Map(); // channelId → uid → name
11
+ nameToUidByChannel = new Map(); // channelId → name → uid
12
+ // G23: per-channel robot flag map (channelId → uid → isRobot).
13
+ // Populated from refreshMembers; stored for future routing integrations
14
+ // (e.g. 免@ gate that should treat bot members differently).
15
+ robotFlags = new Map();
16
+ lastRefresh = new Map();
17
+ adapter;
18
+ maxContextChars;
19
+ maxWindowSize;
20
+ upsertMember;
21
+ deleteMember;
22
+ selectMembers;
23
+ insertMessage;
24
+ selectRecentMessages;
25
+ deleteOldMessages;
26
+ selectMessagesSince;
27
+ selectMaxId;
28
+ upsertCursor;
29
+ selectCursor;
30
+ constructor(adapter, maxContextChars) {
31
+ this.adapter = adapter;
32
+ this.maxContextChars = maxContextChars;
33
+ this.maxWindowSize = 100;
34
+ this.initStatements();
35
+ }
36
+ initStatements() {
37
+ this.adapter.exec(`
38
+ CREATE TABLE IF NOT EXISTS group_messages (
39
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
40
+ channel_id TEXT NOT NULL,
41
+ from_uid TEXT NOT NULL,
42
+ from_name TEXT NOT NULL,
43
+ content TEXT NOT NULL,
44
+ timestamp INTEGER NOT NULL
45
+ )
46
+ `);
47
+ this.adapter.exec(`
48
+ CREATE INDEX IF NOT EXISTS idx_group_messages_channel
49
+ ON group_messages (channel_id, id DESC)
50
+ `);
51
+ // Per-channel consumption cursor: the highest group_messages.id that has
52
+ // already been injected into a turn for this channel. Only group messages
53
+ // NEWER than this are injected next turn (the bot's standing context lives in
54
+ // the SDK session, so re-injecting the whole window every turn would be both
55
+ // redundant and a frozen-prompt violation). Mirrors the reset_barriers /
56
+ // sdk_sessions single-row-per-key pattern.
57
+ this.adapter.exec(`
58
+ CREATE TABLE IF NOT EXISTS group_context_cursors (
59
+ channel_id TEXT PRIMARY KEY,
60
+ last_id INTEGER NOT NULL,
61
+ updated_at INTEGER NOT NULL
62
+ )
63
+ `);
64
+ this.upsertMember = this.adapter.prepare('INSERT INTO group_members (group_id, uid, name, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(group_id, uid) DO UPDATE SET name = excluded.name, updated_at = excluded.updated_at');
65
+ this.deleteMember = this.adapter.prepare('DELETE FROM group_members WHERE group_id = ? AND uid = ?');
66
+ this.selectMembers = this.adapter.prepare('SELECT uid, name FROM group_members WHERE group_id = ?');
67
+ this.insertMessage = this.adapter.prepare('INSERT INTO group_messages (channel_id, from_uid, from_name, content, timestamp) VALUES (?, ?, ?, ?, ?)');
68
+ this.selectRecentMessages = this.adapter.prepare('SELECT from_uid, from_name, content, timestamp FROM group_messages WHERE channel_id = ? ORDER BY id DESC LIMIT ?');
69
+ this.deleteOldMessages = this.adapter.prepare('DELETE FROM group_messages WHERE channel_id = ? AND id NOT IN (SELECT id FROM group_messages WHERE channel_id = ? ORDER BY id DESC LIMIT ?)');
70
+ // Cursor delta: messages strictly newer than the cursor, NEWEST-first so a
71
+ // backlog larger than the fetch limit keeps the most-recent messages (the
72
+ // relevant ones) rather than ancient ones. LIMIT (maxWindowSize=100) is <=
73
+ // deleteOldMessages' retained 200, so every fetchable row survives trimming.
74
+ // buildContextSince re-sorts the budget-selected slice into chronological
75
+ // order for display. Mirrors the in-memory buildContext rolling-window.
76
+ this.selectMessagesSince = this.adapter.prepare('SELECT id, from_name, content FROM group_messages WHERE channel_id = ? AND id > ? ORDER BY id DESC LIMIT ?');
77
+ this.selectMaxId = this.adapter.prepare('SELECT MAX(id) AS maxId FROM group_messages WHERE channel_id = ?');
78
+ this.upsertCursor = this.adapter.prepare('INSERT INTO group_context_cursors (channel_id, last_id, updated_at) VALUES (?, ?, ?) ' +
79
+ 'ON CONFLICT(channel_id) DO UPDATE SET last_id = excluded.last_id, updated_at = excluded.updated_at ' +
80
+ 'WHERE excluded.last_id > group_context_cursors.last_id');
81
+ this.selectCursor = this.adapter.prepare('SELECT last_id FROM group_context_cursors WHERE channel_id = ?');
82
+ }
83
+ getMemberMap(channelId) {
84
+ let m = this.memberMapByChannel.get(channelId);
85
+ if (!m) {
86
+ m = new Map();
87
+ this.memberMapByChannel.set(channelId, m);
88
+ }
89
+ return m;
90
+ }
91
+ getNameToUid(channelId) {
92
+ let m = this.nameToUidByChannel.get(channelId);
93
+ if (!m) {
94
+ m = new Map();
95
+ this.nameToUidByChannel.set(channelId, m);
96
+ }
97
+ return m;
98
+ }
99
+ pushMessage(channelId, fromUid, fromName, content, timestamp) {
100
+ // SECURITY: fromName is the user-controlled IM display name and is rendered
101
+ // into the [Group context] block as `<name>:<content>`. Bound + strip it at
102
+ // the boundary (shared choke point) so it can't forge a label/line. fromUid
103
+ // is ALSO user-controlled, and sanitizeDisplayName returns its fallback
104
+ // verbatim — so sanitize the uid fallback too rather than passing it raw
105
+ // (PR #128 review: raw-uid-as-fallback re-introduces the injection).
106
+ const safeName = sanitizeDisplayName(fromName) || sanitizeDisplayName(fromUid) || 'unknown';
107
+ let window = this.messageCache.get(channelId);
108
+ if (!window) {
109
+ window = [];
110
+ this.messageCache.set(channelId, window);
111
+ }
112
+ window.push({ fromUid, fromName: safeName, content, timestamp });
113
+ while (window.length > this.maxWindowSize) {
114
+ window.shift();
115
+ }
116
+ this.learnMember(channelId, fromUid, safeName);
117
+ try {
118
+ this.insertMessage.run(channelId, fromUid, safeName, content, timestamp);
119
+ // Trim old messages to keep DB bounded (keep 2x window for safety)
120
+ this.deleteOldMessages.run(channelId, channelId, this.maxWindowSize * 2);
121
+ }
122
+ catch (err) {
123
+ console.error(`group-context: insert message failed: ${String(err)}`);
124
+ }
125
+ }
126
+ learnMember(channelId, uid, name) {
127
+ if (!uid || !name)
128
+ return;
129
+ const memberMap = this.getMemberMap(channelId);
130
+ const nameMap = this.getNameToUid(channelId);
131
+ const existing = memberMap.get(uid);
132
+ if (existing !== name) {
133
+ // Remove old reverse mapping only if it still points to THIS uid.
134
+ // If another user already claimed the same display name, don't clobber.
135
+ if (existing && nameMap.get(existing) === uid) {
136
+ nameMap.delete(existing);
137
+ }
138
+ memberMap.set(uid, name);
139
+ nameMap.set(name, uid);
140
+ try {
141
+ this.upsertMember.run(channelId, uid, name, Date.now());
142
+ }
143
+ catch (err) {
144
+ console.error(`group-context: upsert member failed: ${String(err)}`);
145
+ }
146
+ }
147
+ }
148
+ async refreshMembers(channelId, apiUrl, botToken) {
149
+ const now = Date.now();
150
+ const last = this.lastRefresh.get(channelId) ?? 0;
151
+ if (now - last < REFRESH_INTERVAL_MS)
152
+ return;
153
+ // Don't set lastRefresh here — only on success
154
+ try {
155
+ const members = await getGroupMembers({ apiUrl, botToken, groupNo: channelId });
156
+ this.lastRefresh.set(channelId, now); // Record only on success
157
+ const memberMap = this.getMemberMap(channelId);
158
+ const nameMap = this.getNameToUid(channelId);
159
+ // A8 (#143, take 2): the server response is AUTHORITATIVE — it is the full
160
+ // current roster, not a delta. Upserting returned members WITHOUT pruning
161
+ // departed ones (the original #144 bug, Jerry-Xin's 🔴) left a user who
162
+ // left the group cached forever, so isMember() kept accepting them and the
163
+ // outbound mention guard let a stale @uid through. Track who the server
164
+ // returned, then drop anyone cached/persisted who is no longer present.
165
+ //
166
+ // Best-effort caveat: this is only as fresh as the last successful refresh,
167
+ // which is throttled to REFRESH_INTERVAL_MS (1h) and seeded from DB on
168
+ // restart. A membership change inside that window is not reflected until
169
+ // the next refresh — the outbound guard is defense-in-depth, not a
170
+ // real-time authority. The server still enforces real permissions.
171
+ const present = new Set();
172
+ for (const m of members) {
173
+ if (!m.uid || !m.name)
174
+ continue;
175
+ present.add(m.uid);
176
+ const oldName = memberMap.get(m.uid);
177
+ if (oldName && oldName !== m.name && nameMap.get(oldName) === m.uid) {
178
+ nameMap.delete(oldName);
179
+ }
180
+ memberMap.set(m.uid, m.name);
181
+ nameMap.set(m.name, m.uid);
182
+ // G23: Track server-authoritative robot flag for future 免@ gate.
183
+ if (m.robot !== undefined) {
184
+ let rfMap = this.robotFlags.get(channelId);
185
+ if (!rfMap) {
186
+ rfMap = new Map();
187
+ this.robotFlags.set(channelId, rfMap);
188
+ }
189
+ rfMap.set(m.uid, m.robot === 1);
190
+ }
191
+ try {
192
+ this.upsertMember.run(channelId, m.uid, m.name, now);
193
+ }
194
+ catch (err) {
195
+ console.error(`group-context: upsert member failed: ${String(err)}`);
196
+ }
197
+ }
198
+ // Prune members no longer in the authoritative roster (memory + DB + robot
199
+ // flags). Iterate a snapshot of uids since we mutate the map in the loop.
200
+ //
201
+ // Guard: skip pruning when the roster came back EMPTY. getGroupMembers
202
+ // returns [] not only for a genuinely empty group but also for a
203
+ // malformed/unexpected-shape 200 response (data.members not an array →
204
+ // silently []). A group the bot is in always has ≥1 member, so an empty
205
+ // roster is far more likely a transient quirk than "everyone left".
206
+ // Mass-pruning on it would wipe the whole roster — and since lastRefresh
207
+ // was already set on this "success", it wouldn't re-fetch for an hour,
208
+ // downgrading every mention to plain text in that window. Keep the prior
209
+ // snapshot instead; a real emptying still prunes member-by-member as the
210
+ // roster shrinks across non-empty responses.
211
+ if (present.size > 0) {
212
+ const rfMap = this.robotFlags.get(channelId);
213
+ for (const uid of [...memberMap.keys()]) {
214
+ if (present.has(uid))
215
+ continue;
216
+ const staleName = memberMap.get(uid);
217
+ memberMap.delete(uid);
218
+ // Only remove the reverse entry if it still points at THIS uid (a rename
219
+ // may have already re-pointed the name to someone else).
220
+ if (staleName !== undefined && nameMap.get(staleName) === uid) {
221
+ nameMap.delete(staleName);
222
+ }
223
+ rfMap?.delete(uid);
224
+ try {
225
+ this.deleteMember.run(channelId, uid);
226
+ }
227
+ catch (err) {
228
+ console.error(`group-context: delete stale member failed: ${String(err)}`);
229
+ }
230
+ }
231
+ }
232
+ }
233
+ catch (err) {
234
+ console.error(`group-context: refreshMembers(${channelId}) failed: ${String(err)}`);
235
+ // Don't update lastRefresh on failure — allow retry
236
+ }
237
+ }
238
+ async fetchAndLearnUser(uid, channelId, apiUrl, botToken) {
239
+ const memberMap = this.getMemberMap(channelId);
240
+ const cached = memberMap.get(uid);
241
+ if (cached)
242
+ return cached;
243
+ try {
244
+ const info = await fetchUserInfo({ apiUrl, botToken, uid });
245
+ if (info?.name) {
246
+ this.learnMember(channelId, uid, info.name);
247
+ return info.name;
248
+ }
249
+ }
250
+ catch (err) {
251
+ console.error(`group-context: fetchUserInfo(${uid}) failed: ${String(err)}`);
252
+ }
253
+ return undefined;
254
+ }
255
+ buildContext(channelId) {
256
+ const window = this.messageCache.get(channelId);
257
+ if (!window || window.length === 0)
258
+ return '';
259
+ const header = '[Recent group messages]\n';
260
+ const trailer = '\n';
261
+ const budget = this.maxContextChars - header.length - trailer.length;
262
+ if (budget <= 0)
263
+ return '';
264
+ const selected = [];
265
+ let used = 0;
266
+ for (let i = window.length - 1; i >= 0; i--) {
267
+ const m = window[i];
268
+ const line = `${m.fromName}:${m.content}`;
269
+ const cost = line.length + (selected.length > 0 ? 1 : 0);
270
+ if (used + cost > budget)
271
+ break;
272
+ selected.push(line);
273
+ used += cost;
274
+ }
275
+ if (selected.length === 0)
276
+ return '';
277
+ selected.reverse();
278
+ return `${header}${selected.join('\n')}${trailer}`;
279
+ }
280
+ /** Read the per-channel consumption cursor (highest already-injected id), or 0. */
281
+ getContextCursor(channelId) {
282
+ try {
283
+ const row = this.selectCursor.get(channelId);
284
+ return row?.last_id ?? 0;
285
+ }
286
+ catch (err) {
287
+ console.error(`group-context: getContextCursor(${channelId}) failed: ${String(err)}`);
288
+ return 0;
289
+ }
290
+ }
291
+ /** Advance the per-channel cursor to `lastId` (monotonic — never moves backward). */
292
+ setContextCursor(channelId, lastId) {
293
+ try {
294
+ this.upsertCursor.run(channelId, lastId, Date.now());
295
+ }
296
+ catch (err) {
297
+ console.error(`group-context: setContextCursor(${channelId}) failed: ${String(err)}`);
298
+ }
299
+ }
300
+ /** The highest group_messages.id for a channel (for cursor priming), or 0. */
301
+ getMaxMessageId(channelId) {
302
+ try {
303
+ const row = this.selectMaxId.get(channelId);
304
+ return row?.maxId ?? 0;
305
+ }
306
+ catch (err) {
307
+ console.error(`group-context: getMaxMessageId(${channelId}) failed: ${String(err)}`);
308
+ return 0;
309
+ }
310
+ }
311
+ /**
312
+ * Build a group-context block from messages NEWER than `sinceId` (the delta the
313
+ * model hasn't seen yet), within `maxContextChars`. Returns the block text (same
314
+ * `[Recent group messages]` format as buildContext) and the highest id that
315
+ * EXISTS in the channel above `sinceId` (so the caller advances the cursor past
316
+ * the whole delta, including any oldest lines the char budget dropped — those
317
+ * are the least-relevant and are intentionally not re-shown). Empty text + the
318
+ * unchanged cursor when there's nothing new.
319
+ *
320
+ * Rows are fetched newest-first (so a backlog larger than the budget keeps the
321
+ * most-recent messages); the selected slice is reversed back to chronological
322
+ * order for display. Unlike buildContext (in-memory rolling window), this reads
323
+ * from the DB so the cursor delta is exact even across restarts / window eviction.
324
+ */
325
+ buildContextSince(channelId, sinceId) {
326
+ let rows;
327
+ try {
328
+ rows = this.selectMessagesSince.all(channelId, sinceId, this.maxWindowSize);
329
+ }
330
+ catch (err) {
331
+ console.error(`group-context: buildContextSince(${channelId}) failed: ${String(err)}`);
332
+ return { text: '', lastId: sinceId };
333
+ }
334
+ if (rows.length === 0)
335
+ return { text: '', lastId: sinceId };
336
+ // rows are newest-first; the highest id is the first row. Advance the cursor to
337
+ // it regardless of what the budget keeps, so we never re-show a line.
338
+ const lastId = rows[0].id;
339
+ const header = '[Recent group messages]\n';
340
+ const trailer = '\n';
341
+ const budget = this.maxContextChars - header.length - trailer.length;
342
+ if (budget <= 0)
343
+ return { text: '', lastId };
344
+ // Walk newest→oldest (rows[0] is newest) keeping lines within budget.
345
+ const selected = [];
346
+ let used = 0;
347
+ for (let i = 0; i < rows.length; i++) {
348
+ const line = `${rows[i].from_name}:${rows[i].content}`;
349
+ const cost = line.length + (selected.length > 0 ? 1 : 0);
350
+ if (used + cost > budget)
351
+ break;
352
+ selected.push(line);
353
+ used += cost;
354
+ }
355
+ if (selected.length === 0)
356
+ return { text: '', lastId };
357
+ selected.reverse(); // chronological order for display
358
+ return { text: `${header}${selected.join('\n')}${trailer}`, lastId };
359
+ }
360
+ resolveMentions(text, channelId) {
361
+ const uids = [];
362
+ const seen = new Set();
363
+ const nameMap = this.nameToUidByChannel.get(channelId);
364
+ if (!nameMap)
365
+ return uids;
366
+ // Match @name where name is a run of word-like characters (letters, digits, underscores,
367
+ // CJK ideographs, Hangul, Kana, etc.) — stops at punctuation and whitespace.
368
+ const regex = /@([\w\u4e00-\u9fff\u3400-\u4dbf\uac00-\ud7af\u3040-\u309f\u30a0-\u30ff]+)/g;
369
+ let match;
370
+ while ((match = regex.exec(text)) !== null) {
371
+ // Strip known trailing punctuation that might stick to names
372
+ let name = match[1];
373
+ name = name.replace(/[,.!?;:,。!?;:、)\]]+$/, '');
374
+ if (!name)
375
+ continue;
376
+ const uid = nameMap.get(name);
377
+ if (uid && !seen.has(uid)) {
378
+ seen.add(uid);
379
+ uids.push(uid);
380
+ }
381
+ }
382
+ return uids;
383
+ }
384
+ getName(uid, channelId) {
385
+ return this.getMemberMap(channelId).get(uid);
386
+ }
387
+ /** G23: Check the server-authoritative robot flag for a group member. */
388
+ isRobot(channelId, uid) {
389
+ return this.robotFlags.get(channelId)?.get(uid);
390
+ }
391
+ /**
392
+ * A8 (#143): true iff `uid` is a current member of `channelId` per the cached
393
+ * member list. The list is kept authoritative by refreshMembers (it prunes
394
+ * departed members, not just upserts) — but it is only best-effort fresh:
395
+ * refresh is throttled to REFRESH_INTERVAL_MS and seeded from DB on restart,
396
+ * so a membership change inside that window may not be reflected yet. Used by
397
+ * the outbound mention guard as defense-in-depth (the server still enforces
398
+ * real permissions), NOT as a real-time authority.
399
+ */
400
+ isMember(channelId, uid) {
401
+ return this.memberMapByChannel.get(channelId)?.has(uid) ?? false;
402
+ }
403
+ /**
404
+ * A8 (#143): the channel's displayName→uid map, for v1 `@name` outbound
405
+ * resolution in StreamRelay.deliver. Returns the live map (empty if the
406
+ * channel has no cached members yet). Read-only use by callers.
407
+ */
408
+ getNameToUidMap(channelId) {
409
+ return this.getNameToUid(channelId);
410
+ }
411
+ loadMembersFromDb(channelId) {
412
+ try {
413
+ const rows = this.selectMembers.all(channelId);
414
+ const memberMap = this.getMemberMap(channelId);
415
+ const nameMap = this.getNameToUid(channelId);
416
+ for (const r of rows) {
417
+ if (!r.uid || !r.name)
418
+ continue;
419
+ memberMap.set(r.uid, r.name);
420
+ nameMap.set(r.name, r.uid);
421
+ }
422
+ }
423
+ catch (err) {
424
+ console.error(`group-context: loadMembersFromDb(${channelId}) failed: ${String(err)}`);
425
+ }
426
+ }
427
+ loadMessagesFromDb(channelId) {
428
+ try {
429
+ const rows = this.selectRecentMessages.all(channelId, this.maxWindowSize);
430
+ if (rows.length === 0)
431
+ return;
432
+ // Rows come in DESC order, reverse to chronological
433
+ rows.reverse();
434
+ const existing = this.messageCache.get(channelId);
435
+ if (existing && existing.length > 0)
436
+ return; // Don't overwrite live data
437
+ // Map snake_case DB columns to camelCase GroupMessage
438
+ this.messageCache.set(channelId, rows.map(r => ({
439
+ fromUid: r.from_uid,
440
+ fromName: r.from_name,
441
+ content: r.content,
442
+ timestamp: r.timestamp,
443
+ })));
444
+ }
445
+ catch (err) {
446
+ console.error(`group-context: loadMessagesFromDb(${channelId}) failed: ${String(err)}`);
447
+ }
448
+ }
449
+ /** Load all persisted members and messages from DB (call once at startup). */
450
+ loadAllFromDb() {
451
+ try {
452
+ const rows = this.adapter.prepare('SELECT DISTINCT group_id FROM group_members').all();
453
+ for (const row of rows) {
454
+ this.loadMembersFromDb(row.group_id);
455
+ this.loadMessagesFromDb(row.group_id);
456
+ }
457
+ if (rows.length > 0) {
458
+ console.log(`[group-context] Loaded members + messages for ${rows.length} group(s) from DB`);
459
+ }
460
+ }
461
+ catch (err) {
462
+ console.error(`group-context: loadAllFromDb failed: ${String(err)}`);
463
+ }
464
+ }
465
+ }
466
+ //# sourceMappingURL=group-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-context.js","sourceRoot":"","sources":["../src/group-context.ts"],"names":[],"mappings":"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"}