@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.
- package/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/pi-tools.js +32 -2
- package/dist/agents/skills/security.js +217 -0
- package/dist/auto-reply/reply/get-reply.js +6 -0
- package/dist/auto-reply/reply/message-preprocess-hooks.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/banner.js +20 -1
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/cli/security-cli.js +211 -2
- package/dist/cli/tagline.js +7 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/config/types.cli.js +1 -0
- package/dist/config/types.security.js +33 -0
- package/dist/config/zod-schema.js +15 -0
- package/dist/config/zod-schema.providers-core.js +1 -0
- package/dist/config/zod-schema.security.js +113 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/discord/monitor/message-handler.preflight.js +11 -2
- package/dist/gateway/http-common.js +6 -1
- package/dist/hooks/fire-and-forget.js +6 -0
- package/dist/hooks/internal-hooks.js +64 -19
- package/dist/hooks/message-hook-mappers.js +179 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/capability-guards.js +89 -0
- package/dist/security/capability-manager.js +76 -0
- package/dist/security/capability.js +147 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/index.js +7 -0
- package/dist/security/middleware.js +105 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/slack/monitor/context.js +1 -0
- package/dist/slack/monitor/message-handler/dispatch.js +14 -1
- package/dist/slack/monitor/provider.js +2 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- 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
|
+
}
|
|
@@ -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
|
|
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 (
|
|
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;
|
|
@@ -5,8 +5,18 @@
|
|
|
5
5
|
* like command processing, session lifecycle, etc.
|
|
6
6
|
*/
|
|
7
7
|
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
8
|
-
/**
|
|
9
|
-
|
|
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
|
|
142
|
+
if (!isHookEventTypeAndAction(event, "agent", "bootstrap")) {
|
|
117
143
|
return false;
|
|
118
144
|
}
|
|
119
|
-
const context = event
|
|
120
|
-
if (!context
|
|
145
|
+
const context = getHookContext(event);
|
|
146
|
+
if (!context) {
|
|
121
147
|
return false;
|
|
122
148
|
}
|
|
123
|
-
if (
|
|
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
|
|
155
|
+
if (!isHookEventTypeAndAction(event, "gateway", "startup")) {
|
|
130
156
|
return false;
|
|
131
157
|
}
|
|
132
|
-
|
|
133
|
-
return Boolean(context && typeof context === "object");
|
|
158
|
+
return Boolean(getHookContext(event));
|
|
134
159
|
}
|
|
135
160
|
export function isMessageReceivedEvent(event) {
|
|
136
|
-
if (event
|
|
161
|
+
if (!isHookEventTypeAndAction(event, "message", "received")) {
|
|
137
162
|
return false;
|
|
138
163
|
}
|
|
139
|
-
const context = event
|
|
140
|
-
if (!context
|
|
164
|
+
const context = getHookContext(event);
|
|
165
|
+
if (!context) {
|
|
141
166
|
return false;
|
|
142
167
|
}
|
|
143
|
-
return
|
|
168
|
+
return hasStringContextField(context, "from") && hasStringContextField(context, "channelId");
|
|
144
169
|
}
|
|
145
170
|
export function isMessageSentEvent(event) {
|
|
146
|
-
if (event
|
|
171
|
+
if (!isHookEventTypeAndAction(event, "message", "sent")) {
|
|
147
172
|
return false;
|
|
148
173
|
}
|
|
149
|
-
const context = event
|
|
150
|
-
if (!context
|
|
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 (
|
|
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
|
+
}
|