@poolzin/pool-bot 2026.3.6 → 2026.3.9

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 (68) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/.buildstamp +1 -1
  3. package/dist/agents/error-classifier.js +302 -0
  4. package/dist/agents/pi-tools.js +32 -2
  5. package/dist/agents/skills/security.js +217 -0
  6. package/dist/auto-reply/reply/get-reply.js +6 -0
  7. package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
  8. package/dist/build-info.json +3 -3
  9. package/dist/cli/banner.js +20 -1
  10. package/dist/cli/lazy-commands.example.js +113 -0
  11. package/dist/cli/lazy-commands.js +329 -0
  12. package/dist/cli/program/command-registry.js +13 -0
  13. package/dist/cli/program/register.skills.js +4 -0
  14. package/dist/cli/security-cli.js +211 -2
  15. package/dist/cli/tagline.js +7 -0
  16. package/dist/config/config.js +1 -0
  17. package/dist/config/secrets-integration.js +88 -0
  18. package/dist/config/types.cli.js +1 -0
  19. package/dist/config/types.security.js +33 -0
  20. package/dist/config/zod-schema.js +15 -0
  21. package/dist/config/zod-schema.providers-core.js +1 -0
  22. package/dist/config/zod-schema.security.js +113 -0
  23. package/dist/context-engine/index.js +33 -0
  24. package/dist/context-engine/legacy.js +181 -0
  25. package/dist/context-engine/registry.js +86 -0
  26. package/dist/context-engine/summarizing.js +293 -0
  27. package/dist/context-engine/types.js +7 -0
  28. package/dist/discord/monitor/message-handler.preflight.js +11 -2
  29. package/dist/gateway/http-common.js +6 -1
  30. package/dist/hooks/fire-and-forget.js +6 -0
  31. package/dist/hooks/internal-hooks.js +64 -19
  32. package/dist/hooks/message-hook-mappers.js +179 -0
  33. package/dist/infra/abort-pattern.js +106 -0
  34. package/dist/infra/retry.js +94 -0
  35. package/dist/secrets/index.js +28 -0
  36. package/dist/secrets/resolver.js +185 -0
  37. package/dist/secrets/runtime.js +142 -0
  38. package/dist/secrets/types.js +11 -0
  39. package/dist/security/capability-guards.js +89 -0
  40. package/dist/security/capability-manager.js +76 -0
  41. package/dist/security/capability.js +147 -0
  42. package/dist/security/dangerous-tools.js +80 -0
  43. package/dist/security/index.js +7 -0
  44. package/dist/security/middleware.js +105 -0
  45. package/dist/security/types.js +12 -0
  46. package/dist/skills/commands.js +351 -0
  47. package/dist/skills/index.js +167 -0
  48. package/dist/skills/loader.js +282 -0
  49. package/dist/skills/parser.js +461 -0
  50. package/dist/skills/registry.js +397 -0
  51. package/dist/skills/security.js +318 -0
  52. package/dist/skills/types.js +21 -0
  53. package/dist/slack/monitor/context.js +1 -0
  54. package/dist/slack/monitor/message-handler/dispatch.js +14 -1
  55. package/dist/slack/monitor/provider.js +2 -0
  56. package/dist/test-utils/index.js +219 -0
  57. package/dist/tui/index.js +595 -0
  58. package/docs/INTEGRATION_PLAN.md +475 -0
  59. package/docs/INTEGRATION_SUMMARY.md +215 -0
  60. package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
  61. package/docs/integrations/INTEGRATION_PLAN.md +424 -0
  62. package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
  63. package/docs/integrations/XYOPS_PLAN.md +978 -0
  64. package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
  65. package/docs/skills/SKILL.md +524 -0
  66. package/docs/skills.md +405 -0
  67. package/package.json +1 -1
  68. package/skills/example-skill/SKILL.md +195 -0
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Summarizing Context Engine
3
+ *
4
+ * Advanced context management with intelligent summarization.
5
+ * Provides better token efficiency than the legacy engine.
6
+ *
7
+ * Features:
8
+ * - Sliding window with summarization
9
+ * - Token-aware message prioritization
10
+ * - Configurable summarization strategy
11
+ */
12
+ /**
13
+ * Token estimator using character-based approximation
14
+ * More accurate than simple division
15
+ */
16
+ function estimateTokens(text) {
17
+ // GPT-style tokenization approximation
18
+ // - Average English word: ~1.3 tokens
19
+ // - Whitespace and punctuation: ~0.5 tokens each
20
+ // - Code/non-English: ~2-3 tokens per word
21
+ const words = text.trim().split(/\s+/).length;
22
+ const chars = text.length;
23
+ const punctuation = (text.match(/[.,!?;:()[\]{}"']/g) ?? []).length;
24
+ // Heuristic: code detection (high special char ratio)
25
+ const specialChars = (text.match(/[^\w\s]/g) ?? []).length;
26
+ const isCode = specialChars / chars > 0.15;
27
+ if (isCode) {
28
+ return Math.ceil(chars / 2.5); // Code: ~2.5 chars/token
29
+ }
30
+ // Regular text estimation
31
+ const wordTokens = words * 1.3;
32
+ const punctTokens = punctuation * 0.5;
33
+ const overhead = Math.ceil(chars / 100); // Metadata overhead
34
+ return Math.ceil(wordTokens + punctTokens + overhead);
35
+ }
36
+ /**
37
+ * Summarizing Context Engine
38
+ *
39
+ * Implements sliding window with intelligent summarization:
40
+ * 1. Keep system messages (highest priority)
41
+ * 2. Keep recent conversation (high priority)
42
+ * 3. Summarize older messages into running summary
43
+ */
44
+ export class SummarizingContextEngine {
45
+ info = {
46
+ id: "summarizing",
47
+ name: "Summarizing Context Engine",
48
+ description: "Sliding window with intelligent message summarization",
49
+ supportsVectorSearch: false,
50
+ supportsFullTextSearch: false,
51
+ tokenLimit: undefined,
52
+ };
53
+ config;
54
+ sessions = new Map();
55
+ constructor(config) {
56
+ this.config = {
57
+ maxTokens: config?.maxTokens ?? 8000,
58
+ keepRecentMessages: config?.keepRecentMessages ?? 6,
59
+ summarizationThreshold: config?.summarizationThreshold ?? 4000,
60
+ };
61
+ }
62
+ async bootstrap(params) {
63
+ try {
64
+ this.sessions.set(params.sessionId, {
65
+ messages: [],
66
+ summary: undefined,
67
+ summaryTokens: 0,
68
+ lastAccessed: new Date(),
69
+ totalIngested: 0,
70
+ });
71
+ return { success: true };
72
+ }
73
+ catch (error) {
74
+ return {
75
+ success: false,
76
+ error: error instanceof Error ? error.message : String(error),
77
+ };
78
+ }
79
+ }
80
+ async ingest(params) {
81
+ try {
82
+ const session = this.sessions.get(params.sessionId);
83
+ if (!session) {
84
+ return {
85
+ success: false,
86
+ error: `Session ${params.sessionId} not found`,
87
+ };
88
+ }
89
+ const content = typeof params.message.content === "string"
90
+ ? params.message.content
91
+ : JSON.stringify(params.message.content);
92
+ const estimatedTokens = estimateTokens(content);
93
+ // Calculate priority
94
+ const priority = this.calculatePriority(params.message, session.messages.length);
95
+ const prioritized = {
96
+ ...params.message,
97
+ priority,
98
+ estimatedTokens,
99
+ internalTimestamp: Date.now(),
100
+ };
101
+ session.messages.push(prioritized);
102
+ session.totalIngested++;
103
+ session.lastAccessed = new Date();
104
+ // Check if we need to compact
105
+ const currentTokens = this.getTotalTokenCount(session);
106
+ if (currentTokens > this.config.summarizationThreshold) {
107
+ await this.compact({ sessionId: params.sessionId });
108
+ }
109
+ return {
110
+ success: true,
111
+ tokensConsumed: estimatedTokens,
112
+ };
113
+ }
114
+ catch (error) {
115
+ return {
116
+ success: false,
117
+ error: error instanceof Error ? error.message : String(error),
118
+ };
119
+ }
120
+ }
121
+ async assemble(params) {
122
+ const session = this.sessions.get(params.sessionId);
123
+ if (!session) {
124
+ return {
125
+ messages: params.messages ?? [],
126
+ totalTokens: 0,
127
+ wasTruncated: false,
128
+ };
129
+ }
130
+ // Build context: summary + recent messages
131
+ const resultMessages = [];
132
+ let totalTokens = 0;
133
+ // Add summary if exists
134
+ if (session.summary) {
135
+ const summaryMsg = {
136
+ role: "system",
137
+ content: `[Previous conversation summary: ${session.summary}]`,
138
+ metadata: { isSummary: true },
139
+ };
140
+ resultMessages.push(summaryMsg);
141
+ totalTokens += session.summaryTokens;
142
+ }
143
+ // Add messages within budget
144
+ const budget = params.tokenBudget ?? this.config.maxTokens;
145
+ const sortedMessages = [...session.messages].sort((a, b) => b.priority - a.priority || a.internalTimestamp - b.internalTimestamp);
146
+ const included = [];
147
+ const excluded = [];
148
+ for (const msg of sortedMessages) {
149
+ if (totalTokens + msg.estimatedTokens <= budget) {
150
+ // Strip priority metadata before returning
151
+ const { priority: _, estimatedTokens: __, internalTimestamp: ___, ...cleanMsg } = msg;
152
+ included.push(cleanMsg);
153
+ totalTokens += msg.estimatedTokens;
154
+ }
155
+ else {
156
+ const { priority: _, estimatedTokens: __, internalTimestamp: ___, ...cleanMsg } = msg;
157
+ excluded.push(cleanMsg);
158
+ }
159
+ }
160
+ // Sort by internalTimestamp for final output
161
+ included.sort((a, b) => {
162
+ const ta = session.messages.find(m => m.id === a.id)?.internalTimestamp ?? 0;
163
+ const tb = session.messages.find(m => m.id === b.id)?.internalTimestamp ?? 0;
164
+ return ta - tb;
165
+ });
166
+ resultMessages.push(...included);
167
+ return {
168
+ messages: resultMessages,
169
+ totalTokens,
170
+ wasTruncated: excluded.length > 0,
171
+ excludedMessages: excluded.length > 0 ? excluded : undefined,
172
+ };
173
+ }
174
+ async compact(params) {
175
+ try {
176
+ const session = this.sessions.get(params.sessionId);
177
+ if (!session || session.messages.length <= this.config.keepRecentMessages) {
178
+ return { success: true, tokensSaved: 0 };
179
+ }
180
+ const originalTokens = this.getTotalTokenCount(session);
181
+ // Messages to summarize (older ones)
182
+ const toSummarize = session.messages.slice(0, -this.config.keepRecentMessages);
183
+ const toKeep = session.messages.slice(-this.config.keepRecentMessages);
184
+ if (toSummarize.length === 0) {
185
+ return { success: true, tokensSaved: 0 };
186
+ }
187
+ // Generate summary (simplified - in production, use LLM)
188
+ const summary = this.generateSummary(toSummarize);
189
+ const _summaryTokens = estimateTokens(summary);
190
+ // Update session
191
+ session.summary = session.summary
192
+ ? `${session.summary} ${summary}`
193
+ : summary;
194
+ session.summaryTokens = estimateTokens(session.summary);
195
+ session.messages = toKeep;
196
+ const newTokens = this.getTotalTokenCount(session);
197
+ const tokensSaved = originalTokens - newTokens;
198
+ return {
199
+ success: true,
200
+ summary: session.summary,
201
+ tokensSaved: Math.max(0, tokensSaved),
202
+ };
203
+ }
204
+ catch (error) {
205
+ return {
206
+ success: false,
207
+ error: error instanceof Error ? error.message : String(error),
208
+ };
209
+ }
210
+ }
211
+ async cleanup(params) {
212
+ this.sessions.delete(params.sessionId);
213
+ }
214
+ async getStats(params) {
215
+ const session = this.sessions.get(params.sessionId);
216
+ if (!session) {
217
+ return { messageCount: 0 };
218
+ }
219
+ return {
220
+ messageCount: session.messages.length,
221
+ totalTokens: this.getTotalTokenCount(session),
222
+ lastAccessed: session.lastAccessed,
223
+ };
224
+ }
225
+ /**
226
+ * Calculate message priority (0-100)
227
+ */
228
+ calculatePriority(message, index) {
229
+ let priority = 50; // Base priority
230
+ // System messages are highest priority
231
+ if (message.role === "system") {
232
+ priority += 40;
233
+ }
234
+ // Recent messages get boost
235
+ if (index > -5) {
236
+ priority += 20;
237
+ }
238
+ // Messages with tool calls/results are important
239
+ if (message.metadata?.tool_calls ||
240
+ message.metadata?.tool_call_id) {
241
+ priority += 15;
242
+ }
243
+ return Math.min(100, priority);
244
+ }
245
+ /**
246
+ * Get total token count for session
247
+ */
248
+ getTotalTokenCount(session) {
249
+ const messageTokens = session.messages.reduce((sum, m) => sum + m.estimatedTokens, 0);
250
+ return messageTokens + session.summaryTokens;
251
+ }
252
+ /**
253
+ * Generate a simple summary of messages
254
+ * In production, this would use an LLM
255
+ */
256
+ generateSummary(messages) {
257
+ const userMsgs = messages.filter((m) => m.role === "user");
258
+ const assistantMsgs = messages.filter((m) => m.role === "assistant");
259
+ const topics = this.extractTopics(messages);
260
+ return (`Conversation covered ${userMsgs.length} user queries and ${assistantMsgs.length} responses. ` +
261
+ `Topics: ${topics.join(", ") || "general discussion"}.`);
262
+ }
263
+ /**
264
+ * Extract key topics from messages (simplified)
265
+ */
266
+ extractTopics(messages) {
267
+ const allContent = messages
268
+ .map((m) => typeof m.content === "string" ? m.content : JSON.stringify(m.content))
269
+ .join(" ")
270
+ .toLowerCase();
271
+ // Simple keyword extraction
272
+ const keywords = [
273
+ "code",
274
+ "function",
275
+ "error",
276
+ "bug",
277
+ "fix",
278
+ "implement",
279
+ "create",
280
+ "update",
281
+ "delete",
282
+ "configure",
283
+ "setup",
284
+ ];
285
+ return keywords.filter((kw) => allContent.includes(kw)).slice(0, 3);
286
+ }
287
+ }
288
+ /**
289
+ * Create a summarizing context engine
290
+ */
291
+ export function createSummarizingContextEngine(config) {
292
+ return new SummarizingContextEngine(config);
293
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Context Engine - Pluggable conversation context management
3
+ *
4
+ * Provides a modular architecture for managing conversation context
5
+ * with support for different storage and retrieval strategies.
6
+ */
7
+ export {};
@@ -66,7 +66,8 @@ export async function preflightDiscordMessage(params) {
66
66
  logVerbose(`discord: drop message ${message.id} (missing channel id)`);
67
67
  return null;
68
68
  }
69
- const allowBots = params.discordConfig?.allowBots ?? false;
69
+ const allowBotsSetting = params.discordConfig?.allowBots;
70
+ const allowBotsMode = allowBotsSetting === "mentions" ? "mentions" : allowBotsSetting === true ? "all" : "off";
70
71
  if (params.botUserId && author.id === params.botUserId) {
71
72
  // Always ignore own messages to prevent self-reply loops
72
73
  return null;
@@ -92,7 +93,7 @@ export async function preflightDiscordMessage(params) {
92
93
  pluralkitInfo,
93
94
  });
94
95
  if (author.bot) {
95
- if (!allowBots && !sender.isPluralKit) {
96
+ if (allowBotsMode === "off" && !sender.isPluralKit) {
96
97
  logVerbose("discord: drop bot message (allowBots=false)");
97
98
  return null;
98
99
  }
@@ -523,6 +524,14 @@ export async function preflightDiscordMessage(params) {
523
524
  });
524
525
  return null;
525
526
  }
527
+ if (author.bot && !sender.isPluralKit && allowBotsMode === "mentions") {
528
+ const botMentioned = isDirectMessage || wasMentioned || implicitMention;
529
+ if (!botMentioned) {
530
+ logDebug(`[discord-preflight] drop: bot message missing mention (allowBots=mentions)`);
531
+ logVerbose("discord: drop bot message (allowBots=mentions, missing mention)");
532
+ return null;
533
+ }
534
+ }
526
535
  if (!messageText) {
527
536
  logDebug(`[discord-preflight] drop: empty content`);
528
537
  logVerbose(`discord: drop message ${message.id} (empty content)`);
@@ -5,9 +5,14 @@ import { readJsonBody } from "./hooks.js";
5
5
  * Content-Security-Policy are intentionally omitted here because some handlers
6
6
  * (canvas host, A2UI) serve content that may be loaded inside frames.
7
7
  */
8
- export function setDefaultSecurityHeaders(res) {
8
+ export function setDefaultSecurityHeaders(res, opts) {
9
9
  res.setHeader("X-Content-Type-Options", "nosniff");
10
10
  res.setHeader("Referrer-Policy", "no-referrer");
11
+ res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
12
+ const strictTransportSecurity = opts?.strictTransportSecurity;
13
+ if (typeof strictTransportSecurity === "string" && strictTransportSecurity.length > 0) {
14
+ res.setHeader("Strict-Transport-Security", strictTransportSecurity);
15
+ }
11
16
  }
12
17
  export function sendJson(res, status, body) {
13
18
  res.statusCode = status;
@@ -0,0 +1,6 @@
1
+ import { logVerbose } from "../globals.js";
2
+ export function fireAndForgetHook(task, label, logger = logVerbose) {
3
+ void task.catch((err) => {
4
+ logger(`${label}: ${String(err)}`);
5
+ });
6
+ }
@@ -5,8 +5,18 @@
5
5
  * like command processing, session lifecycle, etc.
6
6
  */
7
7
  import { createSubsystemLogger } from "../logging/subsystem.js";
8
- /** Registry of hook handlers by event key */
9
- const handlers = new Map();
8
+ /**
9
+ * Registry of hook handlers by event key.
10
+ *
11
+ * Uses a globalThis singleton so that registerInternalHook and
12
+ * triggerInternalHook always share the same Map even when the bundler
13
+ * emits multiple copies of this module into separate chunks (bundle
14
+ * splitting). Without the singleton, handlers registered in one chunk
15
+ * are invisible to triggerInternalHook in another chunk, causing hooks
16
+ * to silently fire with zero handlers.
17
+ */
18
+ const _g = globalThis;
19
+ const handlers = (_g.__poolbot_internal_hook_handlers__ ??= new Map());
10
20
  const log = createSubsystemLogger("internal-hooks");
11
21
  /**
12
22
  * Register a hook handler for a specific event type or event:action combination
@@ -112,45 +122,80 @@ export function createInternalHookEvent(type, action, sessionKey, context = {})
112
122
  messages: [],
113
123
  };
114
124
  }
125
+ function isHookEventTypeAndAction(event, type, action) {
126
+ return event.type === type && event.action === action;
127
+ }
128
+ function getHookContext(event) {
129
+ const context = event.context;
130
+ if (!context || typeof context !== "object") {
131
+ return null;
132
+ }
133
+ return context;
134
+ }
135
+ function hasStringContextField(context, key) {
136
+ return typeof context[key] === "string";
137
+ }
138
+ function hasBooleanContextField(context, key) {
139
+ return typeof context[key] === "boolean";
140
+ }
115
141
  export function isAgentBootstrapEvent(event) {
116
- if (event.type !== "agent" || event.action !== "bootstrap") {
142
+ if (!isHookEventTypeAndAction(event, "agent", "bootstrap")) {
117
143
  return false;
118
144
  }
119
- const context = event.context;
120
- if (!context || typeof context !== "object") {
145
+ const context = getHookContext(event);
146
+ if (!context) {
121
147
  return false;
122
148
  }
123
- if (typeof context.workspaceDir !== "string") {
149
+ if (!hasStringContextField(context, "workspaceDir")) {
124
150
  return false;
125
151
  }
126
152
  return Array.isArray(context.bootstrapFiles);
127
153
  }
128
154
  export function isGatewayStartupEvent(event) {
129
- if (event.type !== "gateway" || event.action !== "startup") {
155
+ if (!isHookEventTypeAndAction(event, "gateway", "startup")) {
130
156
  return false;
131
157
  }
132
- const context = event.context;
133
- return Boolean(context && typeof context === "object");
158
+ return Boolean(getHookContext(event));
134
159
  }
135
160
  export function isMessageReceivedEvent(event) {
136
- if (event.type !== "message" || event.action !== "received") {
161
+ if (!isHookEventTypeAndAction(event, "message", "received")) {
137
162
  return false;
138
163
  }
139
- const context = event.context;
140
- if (!context || typeof context !== "object") {
164
+ const context = getHookContext(event);
165
+ if (!context) {
141
166
  return false;
142
167
  }
143
- return typeof context.from === "string" && typeof context.channelId === "string";
168
+ return hasStringContextField(context, "from") && hasStringContextField(context, "channelId");
144
169
  }
145
170
  export function isMessageSentEvent(event) {
146
- if (event.type !== "message" || event.action !== "sent") {
171
+ if (!isHookEventTypeAndAction(event, "message", "sent")) {
147
172
  return false;
148
173
  }
149
- const context = event.context;
150
- if (!context || typeof context !== "object") {
174
+ const context = getHookContext(event);
175
+ if (!context) {
176
+ return false;
177
+ }
178
+ return (hasStringContextField(context, "to") &&
179
+ hasStringContextField(context, "channelId") &&
180
+ hasBooleanContextField(context, "success"));
181
+ }
182
+ export function isMessageTranscribedEvent(event) {
183
+ if (!isHookEventTypeAndAction(event, "message", "transcribed")) {
184
+ return false;
185
+ }
186
+ const context = getHookContext(event);
187
+ if (!context) {
188
+ return false;
189
+ }
190
+ return (hasStringContextField(context, "transcript") && hasStringContextField(context, "channelId"));
191
+ }
192
+ export function isMessagePreprocessedEvent(event) {
193
+ if (!isHookEventTypeAndAction(event, "message", "preprocessed")) {
194
+ return false;
195
+ }
196
+ const context = getHookContext(event);
197
+ if (!context) {
151
198
  return false;
152
199
  }
153
- return (typeof context.to === "string" &&
154
- typeof context.channelId === "string" &&
155
- typeof context.success === "boolean");
200
+ return hasStringContextField(context, "channelId");
156
201
  }
@@ -0,0 +1,179 @@
1
+ export function deriveInboundMessageHookContext(ctx, overrides) {
2
+ const content = overrides?.content ??
3
+ (typeof ctx.BodyForCommands === "string"
4
+ ? ctx.BodyForCommands
5
+ : typeof ctx.RawBody === "string"
6
+ ? ctx.RawBody
7
+ : typeof ctx.Body === "string"
8
+ ? ctx.Body
9
+ : "");
10
+ const channelId = (ctx.OriginatingChannel ?? ctx.Surface ?? ctx.Provider ?? "").toLowerCase();
11
+ const conversationId = ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? undefined;
12
+ const isGroup = Boolean(ctx.GroupSubject || ctx.GroupChannel);
13
+ return {
14
+ from: ctx.From ?? "",
15
+ to: ctx.To,
16
+ content,
17
+ body: ctx.Body,
18
+ bodyForAgent: ctx.BodyForAgent,
19
+ transcript: ctx.Transcript,
20
+ timestamp: typeof ctx.Timestamp === "number" && Number.isFinite(ctx.Timestamp)
21
+ ? ctx.Timestamp
22
+ : undefined,
23
+ channelId,
24
+ accountId: ctx.AccountId,
25
+ conversationId,
26
+ messageId: overrides?.messageId ??
27
+ ctx.MessageSidFull ??
28
+ ctx.MessageSid ??
29
+ ctx.MessageSidFirst ??
30
+ ctx.MessageSidLast,
31
+ senderId: ctx.SenderId,
32
+ senderName: ctx.SenderName,
33
+ senderUsername: ctx.SenderUsername,
34
+ senderE164: ctx.SenderE164,
35
+ provider: ctx.Provider,
36
+ surface: ctx.Surface,
37
+ threadId: ctx.MessageThreadId,
38
+ mediaPath: ctx.MediaPath,
39
+ mediaType: ctx.MediaType,
40
+ originatingChannel: ctx.OriginatingChannel,
41
+ originatingTo: ctx.OriginatingTo,
42
+ guildId: ctx.GroupSpace,
43
+ channelName: ctx.GroupChannel,
44
+ isGroup,
45
+ groupId: isGroup ? conversationId : undefined,
46
+ };
47
+ }
48
+ export function buildCanonicalSentMessageHookContext(params) {
49
+ return {
50
+ to: params.to,
51
+ content: params.content,
52
+ success: params.success,
53
+ error: params.error,
54
+ channelId: params.channelId,
55
+ accountId: params.accountId,
56
+ conversationId: params.conversationId ?? params.to,
57
+ messageId: params.messageId,
58
+ isGroup: params.isGroup,
59
+ groupId: params.groupId,
60
+ };
61
+ }
62
+ export function toPluginMessageContext(canonical) {
63
+ return {
64
+ channelId: canonical.channelId,
65
+ accountId: canonical.accountId,
66
+ conversationId: canonical.conversationId,
67
+ };
68
+ }
69
+ export function toPluginMessageReceivedEvent(canonical) {
70
+ return {
71
+ from: canonical.from,
72
+ content: canonical.content,
73
+ timestamp: canonical.timestamp,
74
+ metadata: {
75
+ to: canonical.to,
76
+ provider: canonical.provider,
77
+ surface: canonical.surface,
78
+ threadId: canonical.threadId,
79
+ originatingChannel: canonical.originatingChannel,
80
+ originatingTo: canonical.originatingTo,
81
+ messageId: canonical.messageId,
82
+ senderId: canonical.senderId,
83
+ senderName: canonical.senderName,
84
+ senderUsername: canonical.senderUsername,
85
+ senderE164: canonical.senderE164,
86
+ guildId: canonical.guildId,
87
+ channelName: canonical.channelName,
88
+ },
89
+ };
90
+ }
91
+ export function toPluginMessageSentEvent(canonical) {
92
+ return {
93
+ to: canonical.to,
94
+ content: canonical.content,
95
+ success: canonical.success,
96
+ ...(canonical.error ? { error: canonical.error } : {}),
97
+ };
98
+ }
99
+ export function toInternalMessageReceivedContext(canonical) {
100
+ return {
101
+ from: canonical.from,
102
+ content: canonical.content,
103
+ timestamp: canonical.timestamp,
104
+ channelId: canonical.channelId,
105
+ accountId: canonical.accountId,
106
+ conversationId: canonical.conversationId,
107
+ messageId: canonical.messageId,
108
+ metadata: {
109
+ to: canonical.to,
110
+ provider: canonical.provider,
111
+ surface: canonical.surface,
112
+ threadId: canonical.threadId,
113
+ senderId: canonical.senderId,
114
+ senderName: canonical.senderName,
115
+ senderUsername: canonical.senderUsername,
116
+ senderE164: canonical.senderE164,
117
+ guildId: canonical.guildId,
118
+ channelName: canonical.channelName,
119
+ },
120
+ };
121
+ }
122
+ export function toInternalMessageTranscribedContext(canonical, cfg) {
123
+ return {
124
+ from: canonical.from,
125
+ to: canonical.to,
126
+ body: canonical.body,
127
+ bodyForAgent: canonical.bodyForAgent,
128
+ transcript: canonical.transcript ?? "",
129
+ timestamp: canonical.timestamp,
130
+ channelId: canonical.channelId,
131
+ conversationId: canonical.conversationId,
132
+ messageId: canonical.messageId,
133
+ senderId: canonical.senderId,
134
+ senderName: canonical.senderName,
135
+ senderUsername: canonical.senderUsername,
136
+ provider: canonical.provider,
137
+ surface: canonical.surface,
138
+ mediaPath: canonical.mediaPath,
139
+ mediaType: canonical.mediaType,
140
+ cfg,
141
+ };
142
+ }
143
+ export function toInternalMessagePreprocessedContext(canonical, cfg) {
144
+ return {
145
+ from: canonical.from,
146
+ to: canonical.to,
147
+ body: canonical.body,
148
+ bodyForAgent: canonical.bodyForAgent,
149
+ transcript: canonical.transcript,
150
+ timestamp: canonical.timestamp,
151
+ channelId: canonical.channelId,
152
+ conversationId: canonical.conversationId,
153
+ messageId: canonical.messageId,
154
+ senderId: canonical.senderId,
155
+ senderName: canonical.senderName,
156
+ senderUsername: canonical.senderUsername,
157
+ provider: canonical.provider,
158
+ surface: canonical.surface,
159
+ mediaPath: canonical.mediaPath,
160
+ mediaType: canonical.mediaType,
161
+ isGroup: canonical.isGroup,
162
+ groupId: canonical.groupId,
163
+ cfg,
164
+ };
165
+ }
166
+ export function toInternalMessageSentContext(canonical) {
167
+ return {
168
+ to: canonical.to,
169
+ content: canonical.content,
170
+ success: canonical.success,
171
+ ...(canonical.error ? { error: canonical.error } : {}),
172
+ channelId: canonical.channelId,
173
+ accountId: canonical.accountId,
174
+ conversationId: canonical.conversationId,
175
+ messageId: canonical.messageId,
176
+ ...(canonical.isGroup != null ? { isGroup: canonical.isGroup } : {}),
177
+ ...(canonical.groupId ? { groupId: canonical.groupId } : {}),
178
+ };
179
+ }