botinabox 0.1.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.
@@ -0,0 +1,92 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in contributing to Bot in a Box.
4
+
5
+ ## Development Setup
6
+
7
+ ```bash
8
+ git clone https://github.com/automated-industries/botinabox.git
9
+ cd botinabox
10
+ pnpm install
11
+ pnpm build
12
+ ```
13
+
14
+ ## Project Structure
15
+
16
+ This is a pnpm monorepo. Packages are in `packages/`:
17
+
18
+ - `shared/` — Types and constants (zero dependencies)
19
+ - `core/` — Core framework
20
+ - `cli/` — CLI scaffolding tool
21
+ - `providers/` — LLM provider adapters
22
+ - `channels/` — Messaging channel adapters
23
+
24
+ ## Running Tests
25
+
26
+ ```bash
27
+ # All packages
28
+ pnpm test:run
29
+
30
+ # Single package
31
+ cd packages/core && pnpm test
32
+ ```
33
+
34
+ Tests use [Vitest](https://vitest.dev/). Each package has its own `vitest.config.ts`.
35
+
36
+ ## Building
37
+
38
+ ```bash
39
+ # All packages
40
+ pnpm build
41
+
42
+ # Single package
43
+ cd packages/core && pnpm build
44
+ ```
45
+
46
+ Build uses [tsup](https://tsup.egoist.dev/) targeting ESM with declaration files.
47
+
48
+ ## Type Checking
49
+
50
+ ```bash
51
+ pnpm typecheck
52
+ ```
53
+
54
+ ## Code Style
55
+
56
+ - TypeScript with strict mode
57
+ - ESM modules (`"type": "module"`)
58
+ - ES2022 target
59
+ - No default exports except for provider/channel factory functions
60
+
61
+ ## Making Changes
62
+
63
+ 1. Create a branch from `main`
64
+ 2. Make your changes
65
+ 3. Add or update tests
66
+ 4. Run `pnpm test:run` and `pnpm typecheck`
67
+ 5. Open a pull request
68
+
69
+ ## Adding a Provider
70
+
71
+ 1. Create `packages/providers/your-provider/`
72
+ 2. Implement the `LLMProvider` interface from `@botinabox/shared`
73
+ 3. Export a default factory function
74
+ 4. Add `"botinabox": { "type": "provider" }` to `package.json`
75
+ 5. Add tests
76
+
77
+ ## Adding a Channel Adapter
78
+
79
+ 1. Create `packages/channels/your-channel/`
80
+ 2. Implement the `ChannelAdapter` interface from `@botinabox/shared`
81
+ 3. Export a default factory function
82
+ 4. Add `"botinabox": { "type": "channel" }` to `package.json`
83
+ 5. Add tests
84
+
85
+ ## Reporting Issues
86
+
87
+ Open an issue on GitHub with:
88
+
89
+ - Steps to reproduce
90
+ - Expected behavior
91
+ - Actual behavior
92
+ - Node.js and pnpm versions
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Automated Industries
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # Bot in a Box
2
+
3
+ A modular TypeScript framework for building multi-agent bots with LLM orchestration, multi-channel messaging, and task automation.
4
+
5
+ ## Features
6
+
7
+ - **Multi-agent orchestration** — Define agents with different models, roles, and execution adapters. Task queue with priority scheduling, retry policies, and followup chains.
8
+ - **LLM provider abstraction** — Swap between Anthropic, OpenAI, and Ollama with a unified interface. Model aliasing, purpose-based routing, and fallback chains.
9
+ - **Channel adapters** — Connect to Slack, Discord, and webhooks. Auto-discovery, session management, and notification queuing.
10
+ - **Workflow engine** — Define multi-step workflows with dependency resolution, parallel execution, and conditional branching.
11
+ - **SQLite data layer** — Schema-driven tables, migrations, entity context rendering, and query builder. WAL mode for concurrent reads.
12
+ - **Event-driven hooks** — Priority-ordered, filter-based event bus for decoupled inter-layer communication.
13
+ - **Budget controls** — Per-agent and global cost tracking with warning thresholds and hard stops.
14
+ - **Security** — Input sanitization, field length enforcement, audit logging, and HMAC webhook verification.
15
+ - **Self-updating** — Built-in update checker with configurable policies and maintenance windows.
16
+
17
+ ## Packages
18
+
19
+ | Package | Description |
20
+ |---------|-------------|
21
+ | [`@botinabox/core`](packages/core) | Core framework — config, data, hooks, LLM, orchestration, chat, security |
22
+ | [`@botinabox/shared`](packages/shared) | Shared types, interfaces, and constants (zero dependencies) |
23
+ | [`@botinabox/cli`](packages/cli) | CLI tool for scaffolding new projects |
24
+ | [`@botinabox/provider-anthropic`](packages/providers/anthropic) | Anthropic Claude provider |
25
+ | [`@botinabox/provider-openai`](packages/providers/openai) | OpenAI GPT provider |
26
+ | [`@botinabox/provider-ollama`](packages/providers/ollama) | Ollama local model provider |
27
+ | [`@botinabox/channel-slack`](packages/channels/slack) | Slack channel adapter |
28
+ | [`@botinabox/channel-discord`](packages/channels/discord) | Discord channel adapter |
29
+ | [`@botinabox/channel-webhook`](packages/channels/webhook) | Webhook channel adapter with HMAC verification |
30
+
31
+ ## Quick Start
32
+
33
+ ### Prerequisites
34
+
35
+ - Node.js 20+
36
+ - pnpm 9+
37
+
38
+ ### Install
39
+
40
+ ```bash
41
+ git clone https://github.com/automated-industries/botinabox.git
42
+ cd botinabox
43
+ pnpm install
44
+ pnpm build
45
+ ```
46
+
47
+ ### Create a Project
48
+
49
+ ```bash
50
+ npx botinabox init my-bot
51
+ cd my-bot
52
+ ```
53
+
54
+ This generates a project with a config file, environment template, and entry point.
55
+
56
+ ### Configure
57
+
58
+ Edit `botinabox.config.yml`:
59
+
60
+ ```yaml
61
+ data:
62
+ path: ./data/bot.db
63
+ walMode: true
64
+
65
+ channels:
66
+ slack:
67
+ enabled: true
68
+ botToken: ${SLACK_BOT_TOKEN}
69
+
70
+ providers:
71
+ anthropic:
72
+ enabled: true
73
+ apiKey: ${ANTHROPIC_API_KEY}
74
+
75
+ agents:
76
+ - slug: assistant
77
+ name: Assistant
78
+ adapter: api
79
+ model: smart
80
+
81
+ models:
82
+ default: claude-sonnet-4-6
83
+ aliases:
84
+ fast: claude-haiku-4-5
85
+ smart: claude-opus-4-6
86
+ balanced: claude-sonnet-4-6
87
+ routing:
88
+ conversation: fast
89
+ task_execution: smart
90
+ classification: fast
91
+ fallbackChain: []
92
+ ```
93
+
94
+ Set environment variables in `.env`:
95
+
96
+ ```bash
97
+ ANTHROPIC_API_KEY=your_key_here
98
+ SLACK_BOT_TOKEN=xoxb-your-token
99
+ ```
100
+
101
+ ### Run
102
+
103
+ ```typescript
104
+ import { HookBus } from '@botinabox/core';
105
+ import { loadConfig } from '@botinabox/core';
106
+ import { DataStore, defineCoreTables } from '@botinabox/core';
107
+ import { ProviderRegistry, ModelRouter } from '@botinabox/core';
108
+ import { AgentRegistry, TaskQueue, RunManager } from '@botinabox/core';
109
+ import { ChannelRegistry, MessagePipeline } from '@botinabox/core';
110
+ import createAnthropicProvider from '@botinabox/provider-anthropic';
111
+ import createSlackAdapter from '@botinabox/channel-slack';
112
+
113
+ // Load config
114
+ const { config } = loadConfig({ configPath: 'botinabox.config.yml' });
115
+
116
+ // Initialize systems
117
+ const hooks = new HookBus();
118
+ const db = new DataStore({ dbPath: config.data.path, wal: config.data.walMode, hooks });
119
+ defineCoreTables(db);
120
+ db.init();
121
+
122
+ // LLM providers
123
+ const providers = new ProviderRegistry();
124
+ providers.register(createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }));
125
+ const router = new ModelRouter(providers, config.models);
126
+
127
+ // Channels
128
+ const channels = new ChannelRegistry();
129
+ channels.register(createSlackAdapter(), config.channels.slack);
130
+
131
+ // Orchestration
132
+ const agents = new AgentRegistry(db, hooks);
133
+ const tasks = new TaskQueue(db, hooks);
134
+ const runs = new RunManager(db, hooks);
135
+ const pipeline = new MessagePipeline(hooks, agents, tasks, config);
136
+
137
+ // Start
138
+ tasks.startPolling();
139
+ await channels.start();
140
+ ```
141
+
142
+ ## Architecture
143
+
144
+ ```
145
+ ┌─────────────────────────────────────┐
146
+ │ Channel Adapters │
147
+ │ Slack · Discord · Webhook │
148
+ └──────────────┬──────────────────────┘
149
+ │ InboundMessage
150
+ ┌──────────────▼──────────────────────┐
151
+ │ Message Pipeline │
152
+ │ routing · policies · sessions │
153
+ └──────────────┬──────────────────────┘
154
+ │ Task
155
+ ┌──────────────▼──────────────────────┐
156
+ │ Task Queue │
157
+ │ priority · retry · followup chains │
158
+ └──────────────┬──────────────────────┘
159
+
160
+ ┌──────────────▼──────────────────────┐
161
+ │ Run Manager │
162
+ │ locking · retries · cost tracking │
163
+ └──────────────┬──────────────────────┘
164
+
165
+ ┌────────────────────┼────────────────────┐
166
+ ▼ ▼ ▼
167
+ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐
168
+ │ CLI Adapter │ │ API Adapter │ │ Custom │
169
+ │ (subprocess) │ │ (LLM + tools) │ │ Adapters │
170
+ └─────────────────┘ └────────┬─────────┘ └──────────────┘
171
+
172
+ ┌─────────────▼───────────────────────┐
173
+ │ LLM Layer │
174
+ │ ProviderRegistry · ModelRouter │
175
+ │ CostTracker · Tool Loop │
176
+ └─────────────┬───────────────────────┘
177
+
178
+ ┌───────────────────┼───────────────────┐
179
+ ▼ ▼ ▼
180
+ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
181
+ │ Anthropic │ │ OpenAI │ │ Ollama │
182
+ └──────────────┘ └──────────────┘ └──────────────┘
183
+ ```
184
+
185
+ Cross-cutting concerns — **HookBus** (events), **DataStore** (persistence), **Security** (sanitization + audit) — connect all layers.
186
+
187
+ ## Documentation
188
+
189
+ - [Getting Started](docs/getting-started.md) — Installation, project setup, first bot
190
+ - [Configuration](docs/configuration.md) — Full config reference
191
+ - [Architecture](docs/architecture.md) — System design and patterns
192
+ - [Providers](docs/providers.md) — LLM provider setup and custom providers
193
+ - [Channels](docs/channels.md) — Channel adapter setup and custom adapters
194
+ - [Orchestration](docs/orchestration.md) — Agents, tasks, workflows, and budget controls
195
+ - [API Reference](docs/api-reference.md) — Complete API documentation
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ # Install dependencies
201
+ pnpm install
202
+
203
+ # Build all packages
204
+ pnpm build
205
+
206
+ # Run all tests
207
+ pnpm test:run
208
+
209
+ # Type-check
210
+ pnpm typecheck
211
+ ```
212
+
213
+ ### Project Structure
214
+
215
+ ```
216
+ botinabox/
217
+ ├── packages/
218
+ │ ├── core/ # Core framework
219
+ │ │ └── src/
220
+ │ │ ├── config/ # YAML config loader + validation
221
+ │ │ ├── data/ # SQLite ORM, migrations, entity rendering
222
+ │ │ ├── hooks/ # Event bus
223
+ │ │ ├── llm/ # Provider registry, model router, cost tracking
224
+ │ │ ├── chat/ # Channel registry, message pipeline, sessions
225
+ │ │ ├── orchestrator/ # Agents, tasks, runs, workflows, adapters
226
+ │ │ ├── security/ # Sanitizer, audit, column validation
227
+ │ │ └── update/ # Self-update system
228
+ │ ├── shared/ # Types and constants (zero deps)
229
+ │ ├── cli/ # CLI scaffolding tool
230
+ │ ├── providers/
231
+ │ │ ├── anthropic/ # Claude models
232
+ │ │ ├── openai/ # GPT models
233
+ │ │ └── ollama/ # Local models
234
+ │ └── channels/
235
+ │ ├── slack/ # Slack adapter
236
+ │ ├── discord/ # Discord adapter
237
+ │ └── webhook/ # Webhook adapter + HMAC
238
+ ├── package.json
239
+ ├── pnpm-workspace.yaml
240
+ └── tsconfig.json
241
+ ```
242
+
243
+ ## License
244
+
245
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/cli.js').then(m => m.main(process.argv.slice(2)));
@@ -0,0 +1,71 @@
1
+ /** Channel adapter types — Story 1.5 / 4.1 */
2
+ type ChatType = "direct" | "group" | "channel";
3
+ type FormattingMode = "markdown" | "mrkdwn" | "html" | "plain";
4
+ interface ChannelCapabilities {
5
+ chatTypes: ChatType[];
6
+ threads: boolean;
7
+ reactions: boolean;
8
+ editing: boolean;
9
+ media: boolean;
10
+ polls: boolean;
11
+ maxTextLength: number;
12
+ formattingMode: FormattingMode;
13
+ }
14
+ interface ChannelMeta {
15
+ displayName: string;
16
+ icon?: string;
17
+ homepage?: string;
18
+ }
19
+ interface InboundMessage {
20
+ id: string;
21
+ channel: string;
22
+ account?: string;
23
+ from: string;
24
+ body: string;
25
+ threadId?: string;
26
+ replyToId?: string;
27
+ attachments?: Attachment[];
28
+ receivedAt: string;
29
+ raw?: unknown;
30
+ }
31
+ interface Attachment {
32
+ type: "image" | "file" | "audio" | "video";
33
+ url?: string;
34
+ mimeType?: string;
35
+ filename?: string;
36
+ size?: number;
37
+ }
38
+ interface OutboundPayload {
39
+ text: string;
40
+ threadId?: string;
41
+ replyToId?: string;
42
+ attachments?: Attachment[];
43
+ }
44
+ interface SendResult {
45
+ success: boolean;
46
+ messageId?: string;
47
+ error?: string;
48
+ }
49
+ interface HealthStatus {
50
+ ok: boolean;
51
+ latencyMs?: number;
52
+ error?: string;
53
+ }
54
+ type ChannelConfig = Record<string, unknown>;
55
+ interface ChannelAdapter {
56
+ /** Unique identifier for this adapter instance */
57
+ id: string;
58
+ meta: ChannelMeta;
59
+ capabilities: ChannelCapabilities;
60
+ connect(config: ChannelConfig): Promise<void>;
61
+ disconnect(): Promise<void>;
62
+ healthCheck(): Promise<HealthStatus>;
63
+ send(target: {
64
+ peerId: string;
65
+ threadId?: string;
66
+ }, payload: OutboundPayload): Promise<SendResult>;
67
+ /** Called when a message arrives — set by the framework */
68
+ onMessage?: (message: InboundMessage) => Promise<void>;
69
+ }
70
+
71
+ export type { Attachment as A, ChannelAdapter as C, FormattingMode as F, HealthStatus as H, InboundMessage as I, OutboundPayload as O, SendResult as S, ChannelCapabilities as a, ChannelConfig as b, ChannelMeta as c, ChatType as d };
@@ -0,0 +1,86 @@
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';
2
+
3
+ /**
4
+ * DiscordAdapter — ChannelAdapter implementation for Discord.
5
+ * Story 4.6
6
+ */
7
+
8
+ /** Minimal Discord client interface for mockability. */
9
+ interface DiscordClient {
10
+ sendMessage(channelId: string, content: string): Promise<{
11
+ id: string;
12
+ }>;
13
+ }
14
+ declare class DiscordAdapter implements ChannelAdapter {
15
+ readonly id = "discord";
16
+ readonly meta: ChannelMeta;
17
+ readonly capabilities: ChannelCapabilities;
18
+ onMessage?: (message: InboundMessage) => Promise<void>;
19
+ private connected;
20
+ private config;
21
+ private client;
22
+ constructor(client?: DiscordClient);
23
+ connect(config: ChannelConfig): Promise<void>;
24
+ disconnect(): Promise<void>;
25
+ healthCheck(): Promise<HealthStatus>;
26
+ send(target: {
27
+ peerId: string;
28
+ threadId?: string;
29
+ }, payload: OutboundPayload): Promise<SendResult>;
30
+ /** Simulate receiving an inbound message (for testing/webhooks). */
31
+ receive(event: Record<string, unknown>): Promise<void>;
32
+ }
33
+ /** Factory function — default export for auto-discovery. */
34
+ declare function createDiscordAdapter(client?: DiscordClient): DiscordAdapter;
35
+
36
+ /**
37
+ * Discord inbound message parsing.
38
+ * Story 4.6
39
+ */
40
+
41
+ interface DiscordEvent {
42
+ id?: string;
43
+ channel_id?: string;
44
+ guild_id?: string;
45
+ author?: {
46
+ id?: string;
47
+ username?: string;
48
+ };
49
+ content?: string;
50
+ message_reference?: {
51
+ message_id?: string;
52
+ channel_id?: string;
53
+ };
54
+ timestamp?: string;
55
+ [key: string]: unknown;
56
+ }
57
+ /**
58
+ * Parse a Discord message event into an InboundMessage.
59
+ */
60
+ declare function parseDiscordEvent(event: DiscordEvent): InboundMessage;
61
+
62
+ /**
63
+ * Discord outbound formatting.
64
+ * Discord uses native markdown — just enforce the 2000-char limit.
65
+ * Story 4.6
66
+ */
67
+ /**
68
+ * Split text into Discord-compatible chunks (2000 char max each).
69
+ */
70
+ declare function chunkForDiscord(text: string): string[];
71
+ /**
72
+ * Discord uses native markdown — no conversion needed.
73
+ * Returns the text unchanged (Discord renders it natively).
74
+ */
75
+ declare function formatForDiscord(text: string): string;
76
+
77
+ /**
78
+ * Discord channel configuration types.
79
+ * Story 4.6
80
+ */
81
+ interface DiscordConfig {
82
+ token: string;
83
+ guildId?: string;
84
+ }
85
+
86
+ export { DiscordAdapter, type DiscordClient, type DiscordConfig, type DiscordEvent, chunkForDiscord, createDiscordAdapter as default, formatForDiscord, parseDiscordEvent };
@@ -0,0 +1,98 @@
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
+ };
@@ -0,0 +1,81 @@
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';
2
+
3
+ /**
4
+ * SlackAdapter — ChannelAdapter implementation for Slack.
5
+ * Story 4.5
6
+ */
7
+
8
+ /** Minimal Bolt-compatible client interface for mockability. */
9
+ interface BoltClient {
10
+ postMessage(channel: string, text: string, threadTs?: string): Promise<{
11
+ ok: boolean;
12
+ ts?: string;
13
+ }>;
14
+ }
15
+ declare class SlackAdapter implements ChannelAdapter {
16
+ readonly id = "slack";
17
+ readonly meta: ChannelMeta;
18
+ readonly capabilities: ChannelCapabilities;
19
+ onMessage?: (message: InboundMessage) => Promise<void>;
20
+ private connected;
21
+ private config;
22
+ private client;
23
+ constructor(client?: BoltClient);
24
+ connect(config: ChannelConfig): Promise<void>;
25
+ disconnect(): Promise<void>;
26
+ healthCheck(): Promise<HealthStatus>;
27
+ send(target: {
28
+ peerId: string;
29
+ threadId?: string;
30
+ }, payload: OutboundPayload): Promise<SendResult>;
31
+ /** Simulate receiving an inbound message (for testing/webhooks). */
32
+ receive(event: Record<string, unknown>): Promise<void>;
33
+ }
34
+ /** Factory function — default export for auto-discovery. */
35
+ declare function createSlackAdapter(client?: BoltClient): SlackAdapter;
36
+
37
+ /**
38
+ * Slack inbound message parsing.
39
+ * Story 4.5
40
+ */
41
+
42
+ interface SlackEvent {
43
+ type: string;
44
+ client_msg_id?: string;
45
+ ts?: string;
46
+ event_ts?: string;
47
+ channel?: string;
48
+ user?: string;
49
+ text?: string;
50
+ thread_ts?: string;
51
+ [key: string]: unknown;
52
+ }
53
+ /**
54
+ * Parse a Slack event into an InboundMessage.
55
+ */
56
+ declare function parseSlackEvent(event: SlackEvent): InboundMessage;
57
+
58
+ /**
59
+ * Slack outbound formatting.
60
+ * Story 4.5
61
+ */
62
+ /**
63
+ * Convert standard markdown to Slack's mrkdwn format.
64
+ * - **bold** → *bold*
65
+ * - _italic_ preserved
66
+ * - `code` preserved
67
+ * - ```code block``` preserved
68
+ */
69
+ declare function formatForSlack(text: string): string;
70
+
71
+ /**
72
+ * Slack channel configuration types.
73
+ * Story 4.5
74
+ */
75
+ interface SlackConfig {
76
+ botToken: string;
77
+ appToken?: string;
78
+ signingSecret?: string;
79
+ }
80
+
81
+ export { type BoltClient, SlackAdapter, type SlackConfig, type SlackEvent, createSlackAdapter as default, formatForSlack, parseSlackEvent };