@geminixiang/mama 0.2.0-beta.5 → 0.2.0-beta.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 +81 -18
- package/dist/adapter.d.ts +3 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +84 -17
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +12 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +219 -15
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/context.d.ts.map +1 -1
- package/dist/adapters/slack/context.js +5 -0
- package/dist/adapters/slack/context.js.map +1 -1
- package/dist/adapters/slack/tools/attach.d.ts +1 -1
- package/dist/adapters/slack/tools/attach.d.ts.map +1 -1
- package/dist/adapters/slack/tools/attach.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +32 -35
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +42 -26
- package/dist/agent.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +10 -1
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/model.d.ts +14 -0
- package/dist/commands/model.d.ts.map +1 -0
- package/dist/commands/model.js +112 -0
- package/dist/commands/model.js.map +1 -0
- package/dist/commands/new.d.ts +9 -0
- package/dist/commands/new.d.ts.map +1 -0
- package/dist/commands/new.js +28 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/sandbox.d.ts +10 -0
- package/dist/commands/sandbox.d.ts.map +1 -0
- package/dist/commands/sandbox.js +65 -0
- package/dist/commands/sandbox.js.map +1 -0
- package/dist/commands/session-view.d.ts.map +1 -1
- package/dist/commands/session-view.js +29 -9
- package/dist/commands/session-view.js.map +1 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/utils.d.ts +3 -0
- package/dist/commands/utils.d.ts.map +1 -1
- package/dist/commands/utils.js +5 -0
- package/dist/commands/utils.js.map +1 -1
- package/dist/config.d.ts +13 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +177 -31
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +50 -35
- package/dist/context.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +53 -4
- package/dist/main.js.map +1 -1
- package/dist/provisioner.d.ts +12 -0
- package/dist/provisioner.d.ts.map +1 -1
- package/dist/provisioner.js +41 -10
- package/dist/provisioner.js.map +1 -1
- package/dist/runtime/session-runtime.d.ts +1 -0
- package/dist/runtime/session-runtime.d.ts.map +1 -1
- package/dist/runtime/session-runtime.js +18 -0
- package/dist/runtime/session-runtime.js.map +1 -1
- package/dist/session-store.d.ts +1 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +1 -1
- package/dist/session-store.js.map +1 -1
- package/dist/session-view/service.d.ts.map +1 -1
- package/dist/session-view/service.js +1 -1
- package/dist/session-view/service.js.map +1 -1
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.d.ts.map +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/edit.d.ts +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/event.d.ts +1 -1
- package/dist/tools/event.d.ts.map +1 -1
- package/dist/tools/event.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/read.d.ts +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/write.d.ts +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js.map +1 -1
- package/package.json +4 -4
|
@@ -15,6 +15,38 @@ function slackIsRateLimited(err) {
|
|
|
15
15
|
return data?.error === "rate_limited" || data?.response?.status === 429;
|
|
16
16
|
}
|
|
17
17
|
const slackRetry = (fn) => withRetry(fn, { isRateLimited: slackIsRateLimited });
|
|
18
|
+
function collectSlackText(value, parts) {
|
|
19
|
+
if (value === null || value === undefined)
|
|
20
|
+
return;
|
|
21
|
+
if (typeof value === "string") {
|
|
22
|
+
const trimmed = value.trim();
|
|
23
|
+
if (trimmed)
|
|
24
|
+
parts.push(trimmed);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
for (const item of value)
|
|
29
|
+
collectSlackText(item, parts);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value !== "object")
|
|
33
|
+
return;
|
|
34
|
+
const obj = value;
|
|
35
|
+
for (const key of ["text", "fallback", "title", "value"]) {
|
|
36
|
+
collectSlackText(obj[key], parts);
|
|
37
|
+
}
|
|
38
|
+
collectSlackText(obj.fields, parts);
|
|
39
|
+
collectSlackText(obj.elements, parts);
|
|
40
|
+
collectSlackText(obj.blocks, parts);
|
|
41
|
+
}
|
|
42
|
+
function buildSlackAppMessageText(event) {
|
|
43
|
+
const parts = [];
|
|
44
|
+
collectSlackText(event.text, parts);
|
|
45
|
+
collectSlackText(event.blocks, parts);
|
|
46
|
+
collectSlackText(event.attachments, parts);
|
|
47
|
+
const deduped = parts.filter((part, index) => parts.indexOf(part) === index);
|
|
48
|
+
return deduped.join("\n");
|
|
49
|
+
}
|
|
18
50
|
import { createSlackAdapters } from "./context.js";
|
|
19
51
|
import { hasMaterializedSlackBranchSession } from "./branch-manager.js";
|
|
20
52
|
import { resolveSlackSessionKey } from "./session.js";
|
|
@@ -24,6 +56,8 @@ import { resolveSlackSessionKey } from "./session.js";
|
|
|
24
56
|
export class SlackBot {
|
|
25
57
|
constructor(handler, config) {
|
|
26
58
|
this.botUserId = null;
|
|
59
|
+
this.botId = null;
|
|
60
|
+
this.ownMentionRegex = null;
|
|
27
61
|
this.startupTs = null; // Messages older than this are just logged, not processed
|
|
28
62
|
this.users = new Map();
|
|
29
63
|
this.channels = new Map();
|
|
@@ -44,6 +78,7 @@ export class SlackBot {
|
|
|
44
78
|
async start() {
|
|
45
79
|
const auth = await this.webClient.auth.test();
|
|
46
80
|
this.botUserId = auth.user_id;
|
|
81
|
+
this.botId = typeof auth.bot_id === "string" ? auth.bot_id : null;
|
|
47
82
|
await Promise.all([this.fetchUsers(), this.fetchChannels()]);
|
|
48
83
|
log.logInfo(`Loaded ${this.channels.size} channels, ${this.users.size} users`);
|
|
49
84
|
await this.backfillAllChannels();
|
|
@@ -65,6 +100,15 @@ export class SlackBot {
|
|
|
65
100
|
getAllChannels() {
|
|
66
101
|
return Array.from(this.channels.values());
|
|
67
102
|
}
|
|
103
|
+
stripOwnMention(text) {
|
|
104
|
+
const source = text ?? "";
|
|
105
|
+
if (!this.botUserId)
|
|
106
|
+
return source.trim();
|
|
107
|
+
if (!this.ownMentionRegex || !this.ownMentionRegex.source.includes(this.botUserId)) {
|
|
108
|
+
this.ownMentionRegex = new RegExp(`<@${this.botUserId}>`, "gi");
|
|
109
|
+
}
|
|
110
|
+
return source.replace(this.ownMentionRegex, "").trim();
|
|
111
|
+
}
|
|
68
112
|
async postMessage(channel, text) {
|
|
69
113
|
return slackRetry(async () => {
|
|
70
114
|
const result = await this.webClient.chat.postMessage({ channel, text });
|
|
@@ -76,9 +120,37 @@ export class SlackBot {
|
|
|
76
120
|
await this.webClient.chat.postEphemeral({ channel, user, text });
|
|
77
121
|
});
|
|
78
122
|
}
|
|
123
|
+
async postEphemeralBlocks(channel, user, text, blocks) {
|
|
124
|
+
return slackRetry(async () => {
|
|
125
|
+
await this.webClient.chat.postEphemeral({ channel, user, text, blocks: blocks });
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async postMessageBlocks(channel, text, blocks) {
|
|
129
|
+
return slackRetry(async () => {
|
|
130
|
+
const result = await this.webClient.chat.postMessage({
|
|
131
|
+
channel,
|
|
132
|
+
text,
|
|
133
|
+
blocks: blocks,
|
|
134
|
+
});
|
|
135
|
+
return result.ts;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
79
138
|
async postPrivate(conversationId, userId, text) {
|
|
80
139
|
await this.postEphemeral(conversationId, userId, text);
|
|
81
140
|
}
|
|
141
|
+
async postPrivateDiagnostic(conversationId, userId, text, options) {
|
|
142
|
+
if (options?.style !== "muted") {
|
|
143
|
+
await this.postPrivate(conversationId, userId, options?.style === "error" ? `_${text}_` : text);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const CONTEXT_TEXT_LIMIT = 3000;
|
|
147
|
+
const blockText = text.length > CONTEXT_TEXT_LIMIT
|
|
148
|
+
? text.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
|
|
149
|
+
: text;
|
|
150
|
+
await this.postEphemeralBlocks(conversationId, userId, text, [
|
|
151
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: blockText }] },
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
82
154
|
async openDirectMessage(userId) {
|
|
83
155
|
return slackRetry(async () => {
|
|
84
156
|
const result = await this.webClient.conversations.open({ users: userId });
|
|
@@ -387,6 +459,10 @@ export class SlackBot {
|
|
|
387
459
|
return null;
|
|
388
460
|
return resolveOnlyScopedStopTarget(this.handler, channelId);
|
|
389
461
|
}
|
|
462
|
+
isStopText(text) {
|
|
463
|
+
const normalized = text.trim().toLowerCase();
|
|
464
|
+
return normalized === "stop" || normalized === "/stop";
|
|
465
|
+
}
|
|
390
466
|
createCommandAdapters(conversationId, userId, userName, text, ts, options = {}) {
|
|
391
467
|
const message = {
|
|
392
468
|
id: ts,
|
|
@@ -405,10 +481,29 @@ export class SlackBot {
|
|
|
405
481
|
const messageTs = await this.postMessage(conversationId, responseText);
|
|
406
482
|
this.logBotResponse(conversationId, responseText, messageTs);
|
|
407
483
|
};
|
|
484
|
+
const respondMuted = async (responseText) => {
|
|
485
|
+
const CONTEXT_TEXT_LIMIT = 3000;
|
|
486
|
+
const blockText = responseText.length > CONTEXT_TEXT_LIMIT
|
|
487
|
+
? responseText.substring(0, CONTEXT_TEXT_LIMIT - 20) + "\n_(truncated)_"
|
|
488
|
+
: responseText;
|
|
489
|
+
const blocks = [{ type: "context", elements: [{ type: "mrkdwn", text: blockText }] }];
|
|
490
|
+
if (options.ephemeralChannelId) {
|
|
491
|
+
await this.postEphemeralBlocks(options.ephemeralChannelId, userId, responseText, blocks);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const messageTs = await this.postMessageBlocks(conversationId, responseText, blocks);
|
|
495
|
+
this.logBotResponse(conversationId, responseText, messageTs);
|
|
496
|
+
};
|
|
408
497
|
const responseCtx = {
|
|
409
498
|
respond,
|
|
410
499
|
replaceResponse: respond,
|
|
411
|
-
respondDiagnostic:
|
|
500
|
+
respondDiagnostic: async (responseText, options) => {
|
|
501
|
+
if (options?.style === "muted") {
|
|
502
|
+
await respondMuted(responseText);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
await respond(options?.style === "error" ? `_${responseText}_` : responseText);
|
|
506
|
+
},
|
|
412
507
|
respondToolResult: async (result) => {
|
|
413
508
|
const duration = (result.durationMs / 1000).toFixed(1);
|
|
414
509
|
await respond(`${result.isError ? "Error" : "Done"} ${result.toolName} (${duration}s)\n${result.result}`);
|
|
@@ -500,6 +595,40 @@ export class SlackBot {
|
|
|
500
595
|
const commandBot = this.createSlashCommandBot(conversationId);
|
|
501
596
|
await this.handler.handleNew(conversationId, conversationId, commandBot);
|
|
502
597
|
}
|
|
598
|
+
async routeSlashModelCommand(payload) {
|
|
599
|
+
const conversationId = payload.channel_id;
|
|
600
|
+
const isDirectMessage = conversationId.startsWith("D");
|
|
601
|
+
const createdAt = new Date();
|
|
602
|
+
const eventTs = (createdAt.getTime() / 1000).toFixed(6);
|
|
603
|
+
const userName = payload.user_name ?? this.getUser(payload.user_id)?.userName;
|
|
604
|
+
const commandSuffix = payload.text?.trim();
|
|
605
|
+
const commandText = commandSuffix ? `${payload.command} ${commandSuffix}` : payload.command;
|
|
606
|
+
this.logToFile(conversationId, {
|
|
607
|
+
date: createdAt.toISOString(),
|
|
608
|
+
ts: eventTs,
|
|
609
|
+
user: payload.user_id,
|
|
610
|
+
userName,
|
|
611
|
+
text: commandText,
|
|
612
|
+
attachments: [],
|
|
613
|
+
isBot: false,
|
|
614
|
+
});
|
|
615
|
+
const sessionKey = conversationId;
|
|
616
|
+
const event = {
|
|
617
|
+
type: "dm",
|
|
618
|
+
conversationId,
|
|
619
|
+
conversationKind: isDirectMessage ? "direct" : "shared",
|
|
620
|
+
ts: eventTs,
|
|
621
|
+
user: payload.user_id,
|
|
622
|
+
text: commandText,
|
|
623
|
+
attachments: [],
|
|
624
|
+
sessionKey,
|
|
625
|
+
};
|
|
626
|
+
const adapters = this.createCommandAdapters(conversationId, payload.user_id, userName, commandText, eventTs, isDirectMessage ? {} : { ephemeralChannelId: conversationId });
|
|
627
|
+
await this.handler.handleEvent(event, this, adapters, false);
|
|
628
|
+
}
|
|
629
|
+
async routeSlashSandboxCommand(payload) {
|
|
630
|
+
await this.routeSlashModelCommand(payload);
|
|
631
|
+
}
|
|
503
632
|
async routeSlashSessionCommand(payload) {
|
|
504
633
|
const conversationId = payload.channel_id;
|
|
505
634
|
const isDirectMessage = conversationId.startsWith("D");
|
|
@@ -550,7 +679,7 @@ export class SlackBot {
|
|
|
550
679
|
ts: e.ts,
|
|
551
680
|
thread_ts: e.thread_ts,
|
|
552
681
|
user: e.user,
|
|
553
|
-
text: e.text
|
|
682
|
+
text: this.stripOwnMention(e.text),
|
|
554
683
|
files: e.files,
|
|
555
684
|
sessionKey,
|
|
556
685
|
};
|
|
@@ -565,7 +694,7 @@ export class SlackBot {
|
|
|
565
694
|
return;
|
|
566
695
|
}
|
|
567
696
|
// Check for stop command - execute immediately, don't queue!
|
|
568
|
-
if (slackEvent.text
|
|
697
|
+
if (this.isStopText(slackEvent.text)) {
|
|
569
698
|
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
570
699
|
if (stopTarget) {
|
|
571
700
|
this.handler.handleStop(stopTarget, e.channel, this);
|
|
@@ -589,8 +718,30 @@ export class SlackBot {
|
|
|
589
718
|
// All messages (for logging) + DMs (for triggering)
|
|
590
719
|
this.socketClient.on("message", ({ event, ack }) => {
|
|
591
720
|
const e = event;
|
|
592
|
-
|
|
593
|
-
|
|
721
|
+
const hasFiles = !!e.files && e.files.length > 0;
|
|
722
|
+
const hasSlackContent = !!e.text || hasFiles || !!e.blocks?.length || !!e.attachments?.length;
|
|
723
|
+
const isOwnBotMessage = (!!e.user && e.user === this.botUserId) || (!!this.botId && e.bot_id === this.botId);
|
|
724
|
+
if (isOwnBotMessage) {
|
|
725
|
+
ack();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const isExternalBotMessage = !!e.bot_id || e.subtype === "bot_message";
|
|
729
|
+
if (isExternalBotMessage) {
|
|
730
|
+
if (e.subtype !== undefined && e.subtype !== "bot_message" && e.subtype !== "file_share") {
|
|
731
|
+
ack();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (!hasSlackContent) {
|
|
735
|
+
ack();
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
void this.logExternalBotMessage(e).catch((err) => {
|
|
739
|
+
log.logWarning("Failed to log Slack bot message", String(err));
|
|
740
|
+
});
|
|
741
|
+
ack();
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (!e.user) {
|
|
594
745
|
ack();
|
|
595
746
|
return;
|
|
596
747
|
}
|
|
@@ -598,7 +749,7 @@ export class SlackBot {
|
|
|
598
749
|
ack();
|
|
599
750
|
return;
|
|
600
751
|
}
|
|
601
|
-
if (!
|
|
752
|
+
if (!hasSlackContent) {
|
|
602
753
|
ack();
|
|
603
754
|
return;
|
|
604
755
|
}
|
|
@@ -620,7 +771,7 @@ export class SlackBot {
|
|
|
620
771
|
ts: e.ts,
|
|
621
772
|
thread_ts: e.thread_ts,
|
|
622
773
|
user: e.user,
|
|
623
|
-
text: (e.text
|
|
774
|
+
text: this.stripOwnMention(e.text),
|
|
624
775
|
files: e.files,
|
|
625
776
|
sessionKey,
|
|
626
777
|
};
|
|
@@ -636,7 +787,7 @@ export class SlackBot {
|
|
|
636
787
|
}
|
|
637
788
|
// Check for stop command in channel threads (without @mention)
|
|
638
789
|
// app_mention handles "@mama stop", but bare "stop" in a thread comes here
|
|
639
|
-
if (!isDM && e.thread_ts && slackEvent.text
|
|
790
|
+
if (!isDM && e.thread_ts && this.isStopText(slackEvent.text)) {
|
|
640
791
|
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
641
792
|
if (stopTarget) {
|
|
642
793
|
this.handler.handleStop(stopTarget, e.channel, this);
|
|
@@ -654,7 +805,7 @@ export class SlackBot {
|
|
|
654
805
|
if (isDM || isSharedThreadReply) {
|
|
655
806
|
const activeSessionKey = slackEvent.sessionKey ?? resolveSlackSessionKey(e.channel, e.thread_ts);
|
|
656
807
|
// Check for stop command - execute immediately, don't queue!
|
|
657
|
-
if (slackEvent.text
|
|
808
|
+
if (this.isStopText(slackEvent.text)) {
|
|
658
809
|
const stopTarget = this.resolveStopTarget(e.channel, e.thread_ts);
|
|
659
810
|
if (stopTarget) {
|
|
660
811
|
this.handler.handleStop(stopTarget, e.channel, this); // Don't await, don't queue
|
|
@@ -709,7 +860,23 @@ export class SlackBot {
|
|
|
709
860
|
user_id: payload.user_id,
|
|
710
861
|
user_name: payload.user_name,
|
|
711
862
|
})
|
|
712
|
-
:
|
|
863
|
+
: payload.command === "/pi-model"
|
|
864
|
+
? this.routeSlashModelCommand({
|
|
865
|
+
command: payload.command,
|
|
866
|
+
text: payload.text,
|
|
867
|
+
channel_id: payload.channel_id,
|
|
868
|
+
user_id: payload.user_id,
|
|
869
|
+
user_name: payload.user_name,
|
|
870
|
+
})
|
|
871
|
+
: payload.command === "/pi-sandbox"
|
|
872
|
+
? this.routeSlashSandboxCommand({
|
|
873
|
+
command: payload.command,
|
|
874
|
+
text: payload.text,
|
|
875
|
+
channel_id: payload.channel_id,
|
|
876
|
+
user_id: payload.user_id,
|
|
877
|
+
user_name: payload.user_name,
|
|
878
|
+
})
|
|
879
|
+
: null;
|
|
713
880
|
if (!handlerPromise) {
|
|
714
881
|
return;
|
|
715
882
|
}
|
|
@@ -782,6 +949,27 @@ export class SlackBot {
|
|
|
782
949
|
});
|
|
783
950
|
return attachments;
|
|
784
951
|
}
|
|
952
|
+
async logExternalBotMessage(event) {
|
|
953
|
+
const attachments = event.files
|
|
954
|
+
? await this.store.processAttachments(event.channel, event.files, event.ts)
|
|
955
|
+
: [];
|
|
956
|
+
const botName = event.username ?? event.bot_profile?.name ?? event.bot_profile?.real_name ?? event.bot_id;
|
|
957
|
+
this.logToFile(event.channel, {
|
|
958
|
+
date: new Date(parseFloat(event.ts) * 1000).toISOString(),
|
|
959
|
+
ts: event.ts,
|
|
960
|
+
threadTs: event.thread_ts,
|
|
961
|
+
user: event.bot_id ? `bot:${event.bot_id}` : "external-bot",
|
|
962
|
+
userName: botName,
|
|
963
|
+
displayName: botName,
|
|
964
|
+
text: buildSlackAppMessageText(event),
|
|
965
|
+
attachments,
|
|
966
|
+
isBot: true,
|
|
967
|
+
botId: event.bot_id,
|
|
968
|
+
appId: event.app_id ?? event.bot_profile?.app_id,
|
|
969
|
+
subtype: event.subtype,
|
|
970
|
+
});
|
|
971
|
+
return attachments;
|
|
972
|
+
}
|
|
785
973
|
// ==========================================================================
|
|
786
974
|
// Private - Backfill
|
|
787
975
|
// ==========================================================================
|
|
@@ -830,14 +1018,26 @@ export class SlackBot {
|
|
|
830
1018
|
cursor = result.response_metadata?.next_cursor;
|
|
831
1019
|
pageCount++;
|
|
832
1020
|
} while (cursor && pageCount < maxPages);
|
|
833
|
-
// Filter: include mama's messages,
|
|
1021
|
+
// Filter: include mama's messages, external app/bot messages, and user messages.
|
|
834
1022
|
const relevantMessages = allMessages.filter((msg) => {
|
|
835
1023
|
if (!msg.ts || existingTs.has(msg.ts))
|
|
836
1024
|
return false; // Skip duplicates
|
|
837
1025
|
if (msg.user === this.botUserId)
|
|
838
1026
|
return true;
|
|
839
|
-
|
|
840
|
-
|
|
1027
|
+
const isExternalBotMessage = !!msg.bot_id || msg.subtype === "bot_message";
|
|
1028
|
+
if (isExternalBotMessage) {
|
|
1029
|
+
if (this.botId && msg.bot_id === this.botId)
|
|
1030
|
+
return false;
|
|
1031
|
+
if (msg.subtype !== undefined &&
|
|
1032
|
+
msg.subtype !== "bot_message" &&
|
|
1033
|
+
msg.subtype !== "file_share") {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
return (!!msg.text ||
|
|
1037
|
+
!!(msg.files && msg.files.length > 0) ||
|
|
1038
|
+
!!msg.blocks?.length ||
|
|
1039
|
+
!!msg.attachments?.length);
|
|
1040
|
+
}
|
|
841
1041
|
if (msg.subtype !== undefined && msg.subtype !== "file_share")
|
|
842
1042
|
return false;
|
|
843
1043
|
if (!msg.user)
|
|
@@ -851,9 +1051,13 @@ export class SlackBot {
|
|
|
851
1051
|
// Log each message to log.jsonl
|
|
852
1052
|
for (const msg of relevantMessages) {
|
|
853
1053
|
const isMamaMessage = msg.user === this.botUserId;
|
|
1054
|
+
const isExternalBotMessage = !isMamaMessage && (!!msg.bot_id || msg.subtype === "bot_message");
|
|
1055
|
+
if (isExternalBotMessage) {
|
|
1056
|
+
await this.logExternalBotMessage({ ...msg, channel: channelId, ts: msg.ts });
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
854
1059
|
const user = this.users.get(msg.user);
|
|
855
|
-
|
|
856
|
-
const text = (msg.text || "").replace(/<@[A-Z0-9]+>/gi, "").trim();
|
|
1060
|
+
const text = this.stripOwnMention(msg.text);
|
|
857
1061
|
const attachments = msg.files
|
|
858
1062
|
? await this.store.processAttachments(channelId, msg.files, msg.ts)
|
|
859
1063
|
: [];
|