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.
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/bin/botinabox.mjs +2 -0
- package/dist/channel-m9f7MFD7.d.ts +71 -0
- package/dist/channels/discord/index.d.ts +86 -0
- package/dist/channels/discord/index.js +98 -0
- package/dist/channels/slack/index.d.ts +81 -0
- package/dist/channels/slack/index.js +80 -0
- package/dist/channels/webhook/index.d.ts +72 -0
- package/dist/channels/webhook/index.js +178 -0
- package/dist/chunk-DLJKZD3Q.js +22 -0
- package/dist/chunk-QLA6YOFN.js +22 -0
- package/dist/inbound-AFOHYNUY.js +6 -0
- package/dist/inbound-SNEMBLGA.js +6 -0
- package/dist/index.d.ts +1268 -0
- package/dist/index.js +2965 -0
- package/dist/provider-qqJYv9nv.d.ts +75 -0
- package/dist/providers/anthropic/index.d.ts +22 -0
- package/dist/providers/anthropic/index.js +169 -0
- package/dist/providers/ollama/index.d.ts +24 -0
- package/dist/providers/ollama/index.js +179 -0
- package/dist/providers/openai/index.d.ts +22 -0
- package/dist/providers/openai/index.js +212 -0
- package/package.json +72 -0
package/CONTRIBUTING.md
ADDED
|
@@ -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,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 };
|