botinabox 0.1.0 → 0.2.0

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 ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to `botinabox` are documented here.
4
+
5
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: [SemVer](https://semver.org/).
6
+
7
+ ---
8
+
9
+ ## [0.2.0] — 2026-04-03
10
+
11
+ ### Added
12
+
13
+ - **Users primitive** — `users` and `user_identities` core tables. Users are protected objects (never auto-rendered into other entities' context). `UserRegistry` class: `register()`, `getById()`, `getByEmail()`, `resolveByIdentity()`, `resolveOrCreate()`, `addIdentity()`.
14
+ - **Secrets primitive** — `secrets` core table for encrypted credential storage. Protected by default. `SecretStore` class: `set()`, `get()`, `getMeta()`, `list()`, `rotate()`, `delete()`.
15
+ - **Message pipeline user resolution** — `MessagePipeline` accepts optional `UserRegistry`. When provided, resolves `InboundMessage.from` to a user ID via `resolveOrCreate()` before task creation. `InboundMessage.userId` field added.
16
+ - **`user_id` on messages table** — Tracks resolved user alongside raw `peer_id`.
17
+ - **Protected/encrypted passthrough** — `EntityContextDef` now supports `protected` and `encrypted` fields, passed through to Lattice's entity context system.
18
+
19
+ ### Changed
20
+
21
+ - Core table count: 15 → 18 (added `users`, `user_identities`, `secrets`).
22
+ - `messages` table gains `user_id` column.
23
+
24
+ ## [0.1.1] — 2026-03-28
25
+
26
+ ### Fixed
27
+
28
+ - Initial release bug fixes and stability improvements.
29
+
30
+ ## [0.1.0] — 2026-03-25
31
+
32
+ ### Added
33
+
34
+ - Initial release: DataStore, HookBus, AgentRegistry, TaskQueue, RunManager, WakeupQueue, BudgetController, WorkflowEngine, SessionManager, ChannelRegistry, MessagePipeline.
35
+ - 15 core tables, LLM provider routing, channel adapters (Slack, Discord, Webhook).
package/README.md CHANGED
@@ -11,6 +11,7 @@ A modular TypeScript framework for building multi-agent bots with LLM orchestrat
11
11
  - **SQLite data layer** — Schema-driven tables, migrations, entity context rendering, and query builder. WAL mode for concurrent reads.
12
12
  - **Event-driven hooks** — Priority-ordered, filter-based event bus for decoupled inter-layer communication.
13
13
  - **Budget controls** — Per-agent and global cost tracking with warning thresholds and hard stops.
14
+ - **Protected primitives** — Users and secrets are first-class, privacy-isolated objects. User identity resolution across channels. Secrets with optional AES-256-GCM at-rest encryption.
14
15
  - **Security** — Input sanitization, field length enforcement, audit logging, and HMAC webhook verification.
15
16
  - **Self-updating** — Built-in update checker with configurable policies and maintenance windows.
16
17
 
package/bin/botinabox.mjs CHANGED
File without changes
@@ -21,6 +21,7 @@ interface InboundMessage {
21
21
  channel: string;
22
22
  account?: string;
23
23
  from: string;
24
+ userId?: string;
24
25
  body: string;
25
26
  threadId?: string;
26
27
  replyToId?: string;
@@ -1,4 +1,4 @@
1
- import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-m9f7MFD7.js';
1
+ import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-06G0vbIn.js';
2
2
 
3
3
  /**
4
4
  * DiscordAdapter — ChannelAdapter implementation for Discord.
@@ -1,4 +1,4 @@
1
- import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-m9f7MFD7.js';
1
+ import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-06G0vbIn.js';
2
2
 
3
3
  /**
4
4
  * SlackAdapter — ChannelAdapter implementation for Slack.
@@ -1,4 +1,4 @@
1
- import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-m9f7MFD7.js';
1
+ import { C as ChannelAdapter, c as ChannelMeta, a as ChannelCapabilities, I as InboundMessage, b as ChannelConfig, H as HealthStatus, O as OutboundPayload, S as SendResult } from '../../channel-06G0vbIn.js';
2
2
 
3
3
  /**
4
4
  * WebhookAdapter — ChannelAdapter implementation for webhook-based channels.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './channel-m9f7MFD7.js';
2
- export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-m9f7MFD7.js';
1
+ import { C as ChannelAdapter, H as HealthStatus, I as InboundMessage } from './channel-06G0vbIn.js';
2
+ export { A as Attachment, a as ChannelCapabilities, b as ChannelConfig, c as ChannelMeta, d as ChatType, F as FormattingMode, O as OutboundPayload, S as SendResult } from './channel-06G0vbIn.js';
3
3
  import { T as TokenUsage, L as LLMProvider, M as ModelInfo, R as ResolvedModel, C as ChatMessage } from './provider-qqJYv9nv.js';
4
4
  export { a as ChatParams, b as ChatResult, c as ContentBlock, d as ToolDefinition, e as ToolUse } from './provider-qqJYv9nv.js';
5
5
  import * as better_sqlite3 from 'better-sqlite3';
@@ -518,6 +518,12 @@ interface EntityContextDef {
518
518
  files: Record<string, EntityFileSpec>;
519
519
  indexFile?: string;
520
520
  protectedFiles?: string[];
521
+ /** When true, this entity's data is never rendered into other entities' context files. */
522
+ protected?: boolean;
523
+ /** Enable at-rest encryption. Requires encryptionKey in Lattice options. */
524
+ encrypted?: boolean | {
525
+ columns: string[];
526
+ };
521
527
  }
522
528
  interface EntityFileSpec {
523
529
  source: EntitySource;
@@ -741,6 +747,52 @@ declare class TaskQueue {
741
747
  private poll;
742
748
  }
743
749
 
750
+ interface UserInput {
751
+ id?: string;
752
+ org_id?: string;
753
+ name: string;
754
+ email?: string;
755
+ role?: string;
756
+ title?: string;
757
+ external_id?: string;
758
+ channel?: string;
759
+ timezone?: string;
760
+ preferences?: string;
761
+ notes?: string;
762
+ }
763
+ interface User {
764
+ id: string;
765
+ org_id: string | null;
766
+ name: string;
767
+ email: string | null;
768
+ role: string | null;
769
+ title: string | null;
770
+ external_id: string | null;
771
+ channel: string | null;
772
+ timezone: string | null;
773
+ preferences: string;
774
+ notes: string | null;
775
+ created_at: string;
776
+ updated_at: string;
777
+ deleted_at: string | null;
778
+ }
779
+ declare class UserRegistry {
780
+ private readonly db;
781
+ private readonly hooks;
782
+ constructor(db: DataStore, hooks: HookBus);
783
+ register(input: UserInput): Promise<User>;
784
+ getById(id: string): Promise<User | null>;
785
+ getByEmail(email: string): Promise<User | null>;
786
+ resolveByIdentity(channel: string, externalId: string): Promise<User | null>;
787
+ resolveOrCreate(externalId: string, channel: string, defaults?: Partial<UserInput>): Promise<User>;
788
+ list(filter?: {
789
+ role?: string;
790
+ org_id?: string;
791
+ }): Promise<User[]>;
792
+ update(id: string, changes: Partial<UserInput>): Promise<void>;
793
+ addIdentity(userId: string, channel: string, externalId: string, displayName?: string): Promise<void>;
794
+ }
795
+
744
796
  /**
745
797
  * MessagePipeline — routes inbound messages to the task queue.
746
798
  * Story 4.2
@@ -752,7 +804,8 @@ declare class MessagePipeline {
752
804
  private readonly taskQueue;
753
805
  private readonly config;
754
806
  private readonly agentBindings;
755
- constructor(hooks: HookBus, agentRegistry: AgentRegistry, taskQueue: TaskQueue, config: BotConfig);
807
+ private readonly userRegistry?;
808
+ constructor(hooks: HookBus, agentRegistry: AgentRegistry, taskQueue: TaskQueue, config: BotConfig, userRegistry?: UserRegistry);
756
809
  /**
757
810
  * Process an inbound message end-to-end.
758
811
  * 1. Emit 'message.inbound'
@@ -906,7 +959,7 @@ declare function chunkText(text: string, maxLen: number): string[];
906
959
  declare function formatText(text: string, mode: "mrkdwn" | "html" | "plain"): string;
907
960
 
908
961
  /**
909
- * Define all 15 core tables on a DataStore instance.
962
+ * Define all 18 core tables on a DataStore instance.
910
963
  * Call before db.init().
911
964
  */
912
965
  declare function defineCoreTables(db: DataStore): void;
@@ -1265,4 +1318,43 @@ declare class CliExecutionAdapter {
1265
1318
  }>;
1266
1319
  }
1267
1320
 
1268
- export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatSessionManager, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type Filter, HealthStatus, HeartbeatScheduler, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, LLMProvider, MAX_CHAIN_DEPTH, MessagePipeline, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type PkLookup, ProviderRegistry, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type SchemaError, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SqliteAdapter, type StepRef, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, areDependenciesMet, buildAgentBindings, buildChainOrigin, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, defineCoreTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, loadConfig, parseVersion, runPackageMigrations, sanitize, topologicalSort, validateConfig };
1321
+ interface SecretInput {
1322
+ name: string;
1323
+ type?: string;
1324
+ environment?: string;
1325
+ value?: string;
1326
+ location?: string;
1327
+ description?: string;
1328
+ rotation_schedule?: string;
1329
+ expires_at?: string;
1330
+ notes?: string;
1331
+ org_id?: string;
1332
+ }
1333
+ interface SecretMeta {
1334
+ id: string;
1335
+ org_id: string | null;
1336
+ name: string;
1337
+ type: string;
1338
+ environment: string;
1339
+ location: string | null;
1340
+ description: string | null;
1341
+ rotation_schedule: string | null;
1342
+ expires_at: string | null;
1343
+ notes: string | null;
1344
+ created_at: string;
1345
+ updated_at: string;
1346
+ }
1347
+ declare class SecretStore {
1348
+ private readonly db;
1349
+ private readonly hooks;
1350
+ constructor(db: DataStore, hooks: HookBus);
1351
+ set(input: SecretInput): Promise<SecretMeta>;
1352
+ get(name: string, environment?: string): Promise<string | null>;
1353
+ getMeta(name: string, environment?: string): Promise<SecretMeta | null>;
1354
+ list(): Promise<SecretMeta[]>;
1355
+ rotate(name: string, newValue: string, environment?: string): Promise<void>;
1356
+ delete(name: string, environment?: string): Promise<void>;
1357
+ private _toMeta;
1358
+ }
1359
+
1360
+ export { AGENT_STATUSES, type AgentConfig, type AgentDefinition, type AgentFilter, type AgentRecord, AgentRegistry, type AgentStatus, ApiExecutionAdapter, AuditEmitter, type AuditEvent, BackupManager, type BotConfig, type BudgetCheck, type BudgetConfig, BudgetController, CORE_MIGRATIONS, ChannelAdapter, ChannelRegistry, ChannelRegistryError, ChatMessage, ChatSessionManager, CliExecutionAdapter, type ColumnValidator, ColumnValidatorImpl, type ConfigLoadError, type ConfigLoadResult, DEFAULTS, DEFAULT_CONFIG, type DataConfig, DataStore, DataStoreError, EVENTS, type EntityColumnDef, type EntityConfig, type EntityContextDef, type EntityFileSpec, type EntitySource, type ExecutionAdapter, type Filter, HealthStatus, HeartbeatScheduler, HookBus, type HookHandler, type HookOptions, type HookRegistration, InboundMessage, LLMProvider, MAX_CHAIN_DEPTH, MessagePipeline, type ModelConfig, ModelInfo, ModelRouter, NdjsonLogger, NotificationQueue, type PackageMigration, type PackageUpdate, type PkLookup, ProviderRegistry, type QueryOptions, RUN_STATUSES, type RelationDef, type RenderConfig, ResolvedModel, type RetryPolicy, type Row, type RunContext, RunManager, type RunResult, type RunStatus, type SanitizerOptions, type SchemaError, type SecretInput, type SecretMeta, SecretStore, type SecurityConfig, type SeedItem, SessionKey, SessionManager, type SqliteAdapter, type StepRef, TASK_STATUSES, type TableDefinition, type TableInfoRow, type TaskDefinition, TaskQueue, type TaskRecord, type TaskStatus, TokenUsage, type Unsubscribe, UpdateChecker, type UpdateConfig, UpdateManager, type UpdateManifest, type User, type UserInput, UserRegistry, WakeupQueue, type WorkflowConfigEntry, type WorkflowDefinition$1 as WorkflowDefinition, WorkflowEngine, type WorkflowRunRecord, type WorkflowRunStatus, type WorkflowStep$1 as WorkflowStep, type WorkflowStepConfig, type WorkflowTrigger, _resetConfig, areDependenciesMet, buildAgentBindings, buildChainOrigin, checkAllowlist, checkChainDepth, checkMentionGate, chunkText, classifyUpdate, compareVersions, createConfigRevision, defineCoreTables, detectCycle, discoverChannels, discoverProviders, formatText, getConfig, initConfig, interpolate, interpolateEnv, loadConfig, parseVersion, runPackageMigrations, sanitize, topologicalSort, validateConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botinabox",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Bot in a Box — framework for building multi-agent bots",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,9 +44,10 @@
44
44
  "typecheck": "tsc --noEmit"
45
45
  },
46
46
  "dependencies": {
47
- "latticesql": "^0.17.0",
48
- "yaml": "^2.7.0",
49
- "ajv": "^8.17.1"
47
+ "@types/uuid": "^10.0.0",
48
+ "ajv": "^8.17.1",
49
+ "latticesql": "^0.18.0",
50
+ "yaml": "^2.7.0"
50
51
  },
51
52
  "peerDependencies": {
52
53
  "@anthropic-ai/sdk": "^0.52.0",
@@ -61,12 +62,12 @@
61
62
  }
62
63
  },
63
64
  "devDependencies": {
64
- "typescript": "^5.7.2",
65
- "vitest": "^3.0.0",
66
- "tsup": "^8.3.5",
67
- "@types/node": "^22.10.0",
68
- "@types/better-sqlite3": "^7.6.12",
69
65
  "@anthropic-ai/sdk": "^0.52.0",
70
- "openai": "^4.104.0"
66
+ "@types/better-sqlite3": "^7.6.12",
67
+ "@types/node": "^22.10.0",
68
+ "openai": "^4.104.0",
69
+ "tsup": "^8.3.5",
70
+ "typescript": "^5.7.2",
71
+ "vitest": "^3.0.0"
71
72
  }
72
73
  }
@@ -1,98 +0,0 @@
1
- import {
2
- parseDiscordEvent
3
- } from "../../chunk-DLJKZD3Q.js";
4
-
5
- // src/channels/discord/outbound.ts
6
- var DISCORD_MAX_LENGTH = 2e3;
7
- function chunkForDiscord(text) {
8
- if (text.length <= DISCORD_MAX_LENGTH) return [text];
9
- const chunks = [];
10
- let remaining = text;
11
- while (remaining.length > DISCORD_MAX_LENGTH) {
12
- const slice = remaining.slice(0, DISCORD_MAX_LENGTH);
13
- const lastSpace = slice.lastIndexOf(" ");
14
- if (lastSpace > 0) {
15
- chunks.push(remaining.slice(0, lastSpace));
16
- remaining = remaining.slice(lastSpace + 1);
17
- } else {
18
- chunks.push(remaining.slice(0, DISCORD_MAX_LENGTH));
19
- remaining = remaining.slice(DISCORD_MAX_LENGTH);
20
- }
21
- }
22
- if (remaining.length > 0) chunks.push(remaining);
23
- return chunks;
24
- }
25
- function formatForDiscord(text) {
26
- return text;
27
- }
28
-
29
- // src/channels/discord/adapter.ts
30
- var DiscordAdapter = class {
31
- id = "discord";
32
- meta = {
33
- displayName: "Discord",
34
- icon: "https://discord.com/favicon.ico",
35
- homepage: "https://discord.com"
36
- };
37
- capabilities = {
38
- chatTypes: ["direct", "group", "channel"],
39
- threads: true,
40
- reactions: true,
41
- editing: true,
42
- media: true,
43
- polls: false,
44
- maxTextLength: 2e3,
45
- formattingMode: "markdown"
46
- };
47
- onMessage;
48
- connected = false;
49
- config = null;
50
- client;
51
- constructor(client) {
52
- this.client = client ?? null;
53
- }
54
- async connect(config) {
55
- this.config = config;
56
- this.connected = true;
57
- }
58
- async disconnect() {
59
- this.connected = false;
60
- this.config = null;
61
- }
62
- async healthCheck() {
63
- return { ok: this.connected };
64
- }
65
- async send(target, payload) {
66
- if (!this.connected) {
67
- return { success: false, error: "Not connected" };
68
- }
69
- const text = formatForDiscord(payload.text);
70
- if (this.client) {
71
- try {
72
- const result = await this.client.sendMessage(target.peerId, text);
73
- return { success: true, messageId: result.id };
74
- } catch (err) {
75
- return { success: false, error: String(err) };
76
- }
77
- }
78
- return { success: true };
79
- }
80
- /** Simulate receiving an inbound message (for testing/webhooks). */
81
- async receive(event) {
82
- if (this.onMessage) {
83
- const { parseDiscordEvent: parseDiscordEvent2 } = await import("../../inbound-SNEMBLGA.js");
84
- const msg = parseDiscordEvent2(event);
85
- await this.onMessage(msg);
86
- }
87
- }
88
- };
89
- function createDiscordAdapter(client) {
90
- return new DiscordAdapter(client);
91
- }
92
- export {
93
- DiscordAdapter,
94
- chunkForDiscord,
95
- createDiscordAdapter as default,
96
- formatForDiscord,
97
- parseDiscordEvent
98
- };
@@ -1,80 +0,0 @@
1
- import {
2
- parseSlackEvent
3
- } from "../../chunk-QLA6YOFN.js";
4
-
5
- // src/channels/slack/outbound.ts
6
- function formatForSlack(text) {
7
- let result = text.replace(/\*\*(.+?)\*\*/gs, "*$1*");
8
- result = result.replace(/__(.+?)__/gs, "*$1*");
9
- return result;
10
- }
11
-
12
- // src/channels/slack/adapter.ts
13
- var SlackAdapter = class {
14
- id = "slack";
15
- meta = {
16
- displayName: "Slack",
17
- icon: "https://slack.com/favicon.ico",
18
- homepage: "https://slack.com"
19
- };
20
- capabilities = {
21
- chatTypes: ["direct", "group", "channel"],
22
- threads: true,
23
- reactions: true,
24
- editing: true,
25
- media: true,
26
- polls: false,
27
- maxTextLength: 4e4,
28
- formattingMode: "mrkdwn"
29
- };
30
- onMessage;
31
- connected = false;
32
- config = null;
33
- client;
34
- constructor(client) {
35
- this.client = client ?? null;
36
- }
37
- async connect(config) {
38
- this.config = config;
39
- this.connected = true;
40
- }
41
- async disconnect() {
42
- this.connected = false;
43
- this.config = null;
44
- }
45
- async healthCheck() {
46
- return { ok: this.connected };
47
- }
48
- async send(target, payload) {
49
- if (!this.connected) {
50
- return { success: false, error: "Not connected" };
51
- }
52
- const text = formatForSlack(payload.text);
53
- if (this.client) {
54
- try {
55
- const result = await this.client.postMessage(target.peerId, text, target.threadId);
56
- return { success: result.ok, messageId: result.ts };
57
- } catch (err) {
58
- return { success: false, error: String(err) };
59
- }
60
- }
61
- return { success: true };
62
- }
63
- /** Simulate receiving an inbound message (for testing/webhooks). */
64
- async receive(event) {
65
- if (this.onMessage) {
66
- const { parseSlackEvent: parseSlackEvent2 } = await import("../../inbound-AFOHYNUY.js");
67
- const msg = parseSlackEvent2(event);
68
- await this.onMessage(msg);
69
- }
70
- }
71
- };
72
- function createSlackAdapter(client) {
73
- return new SlackAdapter(client);
74
- }
75
- export {
76
- SlackAdapter,
77
- createSlackAdapter as default,
78
- formatForSlack,
79
- parseSlackEvent
80
- };
@@ -1,178 +0,0 @@
1
- // src/channels/webhook/server.ts
2
- import { createServer } from "http";
3
-
4
- // src/channels/webhook/hmac.ts
5
- import { createHmac, timingSafeEqual } from "crypto";
6
- function verifyHmac(body, secret, signature) {
7
- const expected = createHmac("sha256", secret).update(body, "utf8").digest("hex");
8
- const provided = signature.startsWith("sha256=") ? signature.slice(7) : signature;
9
- if (expected.length !== provided.length) return false;
10
- try {
11
- return timingSafeEqual(Buffer.from(expected, "hex"), Buffer.from(provided, "hex"));
12
- } catch {
13
- return false;
14
- }
15
- }
16
-
17
- // src/channels/webhook/server.ts
18
- var WebhookServer = class {
19
- server = null;
20
- port;
21
- secret;
22
- onMessage;
23
- constructor(opts) {
24
- this.port = opts.port ?? 3200;
25
- this.secret = opts.secret;
26
- this.onMessage = opts.onMessage;
27
- }
28
- start() {
29
- return new Promise((resolve) => {
30
- this.server = createServer((req, res) => {
31
- void this.handleRequest(req, res);
32
- });
33
- this.server.listen(this.port, () => resolve());
34
- });
35
- }
36
- stop() {
37
- return new Promise((resolve, reject) => {
38
- if (!this.server) {
39
- resolve();
40
- return;
41
- }
42
- this.server.close((err) => {
43
- if (err) reject(err);
44
- else resolve();
45
- });
46
- });
47
- }
48
- async handleRequest(req, res) {
49
- const url = req.url ?? "/";
50
- const method = req.method ?? "GET";
51
- if (method !== "POST" || url !== "/webhook/inbound") {
52
- res.writeHead(404, { "Content-Type": "application/json" });
53
- res.end(JSON.stringify({ error: "Not found" }));
54
- return;
55
- }
56
- let body = "";
57
- for await (const chunk of req) {
58
- body += chunk;
59
- }
60
- if (this.secret) {
61
- const sig = req.headers["x-webhook-signature"];
62
- if (!sig || !verifyHmac(body, this.secret, sig)) {
63
- res.writeHead(401, { "Content-Type": "application/json" });
64
- res.end(JSON.stringify({ error: "Invalid signature" }));
65
- return;
66
- }
67
- }
68
- let parsed;
69
- try {
70
- parsed = JSON.parse(body);
71
- } catch {
72
- res.writeHead(400, { "Content-Type": "application/json" });
73
- res.end(JSON.stringify({ error: "Invalid JSON" }));
74
- return;
75
- }
76
- const msg = {
77
- id: parsed["id"] ?? `webhook-${Date.now()}`,
78
- channel: "webhook",
79
- from: parsed["from"] ?? "unknown",
80
- body: parsed["text"] ?? "",
81
- threadId: parsed["threadId"],
82
- receivedAt: (/* @__PURE__ */ new Date()).toISOString(),
83
- raw: parsed
84
- };
85
- try {
86
- await this.onMessage(msg);
87
- res.writeHead(200, { "Content-Type": "application/json" });
88
- res.end(JSON.stringify({ ok: true }));
89
- } catch (err) {
90
- res.writeHead(500, { "Content-Type": "application/json" });
91
- res.end(JSON.stringify({ error: String(err) }));
92
- }
93
- }
94
- };
95
-
96
- // src/channels/webhook/adapter.ts
97
- var WebhookAdapter = class {
98
- id = "webhook";
99
- meta = {
100
- displayName: "Webhook",
101
- homepage: "https://example.com"
102
- };
103
- capabilities = {
104
- chatTypes: ["direct"],
105
- threads: false,
106
- reactions: false,
107
- editing: false,
108
- media: false,
109
- polls: false,
110
- maxTextLength: 65535,
111
- formattingMode: "plain"
112
- };
113
- onMessage;
114
- connected = false;
115
- config = null;
116
- webhookServer = null;
117
- async connect(config) {
118
- this.config = config;
119
- this.connected = true;
120
- if (this.config.port) {
121
- this.webhookServer = new WebhookServer({
122
- port: this.config.port,
123
- secret: this.config.secret,
124
- onMessage: async (msg) => {
125
- if (this.onMessage) await this.onMessage(msg);
126
- }
127
- });
128
- await this.webhookServer.start();
129
- }
130
- }
131
- async disconnect() {
132
- if (this.webhookServer) {
133
- await this.webhookServer.stop();
134
- this.webhookServer = null;
135
- }
136
- this.connected = false;
137
- this.config = null;
138
- }
139
- async healthCheck() {
140
- return { ok: this.connected };
141
- }
142
- async send(target, payload) {
143
- if (!this.connected) {
144
- return { success: false, error: "Not connected" };
145
- }
146
- const callbackUrl = this.config?.callbackUrl;
147
- if (!callbackUrl) {
148
- return { success: true };
149
- }
150
- try {
151
- const body = JSON.stringify({
152
- to: target.peerId,
153
- threadId: target.threadId,
154
- text: payload.text
155
- });
156
- const response = await fetch(callbackUrl, {
157
- method: "POST",
158
- headers: { "Content-Type": "application/json" },
159
- body
160
- });
161
- if (response.ok) {
162
- return { success: true };
163
- }
164
- return { success: false, error: `HTTP ${response.status}` };
165
- } catch (err) {
166
- return { success: false, error: String(err) };
167
- }
168
- }
169
- };
170
- function createWebhookAdapter() {
171
- return new WebhookAdapter();
172
- }
173
- export {
174
- WebhookAdapter,
175
- WebhookServer,
176
- createWebhookAdapter as default,
177
- verifyHmac
178
- };
@@ -1,22 +0,0 @@
1
- // src/channels/discord/inbound.ts
2
- function parseDiscordEvent(event) {
3
- const id = event.id ?? `discord-${Date.now()}`;
4
- const channel = event.channel_id ?? "unknown";
5
- const from = event.author?.id ?? "unknown";
6
- const body = event.content ?? "";
7
- const replyToId = event.message_reference?.message_id;
8
- const receivedAt = event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
9
- return {
10
- id,
11
- channel,
12
- from,
13
- body,
14
- replyToId,
15
- receivedAt,
16
- raw: event
17
- };
18
- }
19
-
20
- export {
21
- parseDiscordEvent
22
- };
@@ -1,22 +0,0 @@
1
- // src/channels/slack/inbound.ts
2
- function parseSlackEvent(event) {
3
- const id = event.client_msg_id ?? event.ts ?? event.event_ts ?? `slack-${Date.now()}`;
4
- const channel = event.channel ?? "unknown";
5
- const from = event.user ?? "unknown";
6
- const body = event.text ?? "";
7
- const threadId = event.thread_ts !== void 0 ? event.thread_ts : void 0;
8
- const receivedAt = event.ts ? new Date(parseFloat(event.ts) * 1e3).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
9
- return {
10
- id,
11
- channel,
12
- from,
13
- body,
14
- threadId,
15
- receivedAt,
16
- raw: event
17
- };
18
- }
19
-
20
- export {
21
- parseSlackEvent
22
- };
@@ -1,6 +0,0 @@
1
- import {
2
- parseSlackEvent
3
- } from "./chunk-QLA6YOFN.js";
4
- export {
5
- parseSlackEvent
6
- };
@@ -1,6 +0,0 @@
1
- import {
2
- parseDiscordEvent
3
- } from "./chunk-DLJKZD3Q.js";
4
- export {
5
- parseDiscordEvent
6
- };