@inceptionstack/roundhouse 0.2.2 → 0.3.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.
Files changed (49) hide show
  1. package/README.md +321 -9
  2. package/architecture.md +77 -8
  3. package/package.json +3 -1
  4. package/src/agents/pi.ts +433 -26
  5. package/src/agents/registry.ts +8 -0
  6. package/src/cli/cli.ts +384 -189
  7. package/src/cli/cron.ts +296 -0
  8. package/src/cli/doctor/checks/agent.ts +68 -0
  9. package/src/cli/doctor/checks/config.ts +88 -0
  10. package/src/cli/doctor/checks/credentials.ts +62 -0
  11. package/src/cli/doctor/checks/disk.ts +69 -0
  12. package/src/cli/doctor/checks/stt.ts +76 -0
  13. package/src/cli/doctor/checks/system.ts +86 -0
  14. package/src/cli/doctor/checks/systemd.ts +76 -0
  15. package/src/cli/doctor/output.ts +58 -0
  16. package/src/cli/doctor/runner.ts +142 -0
  17. package/src/cli/doctor/shell.ts +33 -0
  18. package/src/cli/doctor/types.ts +44 -0
  19. package/src/cli/doctor.ts +48 -0
  20. package/src/cli/setup-telegram.ts +148 -0
  21. package/src/cli/setup.ts +936 -0
  22. package/src/commands.ts +23 -0
  23. package/src/config.ts +188 -0
  24. package/src/cron/constants.ts +54 -0
  25. package/src/cron/durations.ts +33 -0
  26. package/src/cron/format.ts +139 -0
  27. package/src/cron/helpers.ts +30 -0
  28. package/src/cron/runner.ts +148 -0
  29. package/src/cron/schedule.ts +101 -0
  30. package/src/cron/scheduler.ts +295 -0
  31. package/src/cron/store.ts +125 -0
  32. package/src/cron/template.ts +89 -0
  33. package/src/cron/types.ts +76 -0
  34. package/src/gateway.ts +927 -18
  35. package/src/index.ts +1 -58
  36. package/src/memory/bootstrap.ts +98 -0
  37. package/src/memory/files.ts +100 -0
  38. package/src/memory/inject.ts +41 -0
  39. package/src/memory/lifecycle.ts +245 -0
  40. package/src/memory/policy.ts +122 -0
  41. package/src/memory/prompts.ts +42 -0
  42. package/src/memory/state.ts +43 -0
  43. package/src/memory/types.ts +90 -0
  44. package/src/notify/telegram.ts +48 -0
  45. package/src/types.ts +68 -1
  46. package/src/util.ts +28 -2
  47. package/src/voice/providers/whisper.ts +339 -0
  48. package/src/voice/stt-service.ts +284 -0
  49. package/src/voice/types.ts +63 -0
package/README.md CHANGED
@@ -5,6 +5,12 @@ A multi-platform chat gateway that routes messages through a single configured A
5
5
  One gateway instance = one agent target (pi, Kiro, etc.), configured at install time.
6
6
  Multiple chat inputs (Telegram, Slack, Discord via [Vercel Chat SDK](https://chat-sdk.dev)) all feed into that same agent.
7
7
 
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install -g @inceptionstack/roundhouse
12
+ ```
13
+
8
14
  ## Architecture
9
15
 
10
16
  ```
@@ -44,27 +50,80 @@ Multiple chat inputs (Telegram, Slack, Discord via [Vercel Chat SDK](https://cha
44
50
  └────────────────────┘
45
51
  ```
46
52
 
53
+ See [architecture.md](architecture.md) for full system diagrams, data flow, config model, and module dependency graph.
54
+
47
55
  ### Design decisions
48
56
 
49
57
  - **One gateway = one agent target.** The `agent` block in config picks the type and its settings. All chat inputs route to this single agent instance.
50
58
  - **Multiple chat inputs into the same agent.** Telegram and Slack messages go to the same agent, each on their own session thread (`telegram:<id>`, `slack:<id>`).
51
59
  - **AgentRouter is a seam.** Today it's `SingleAgentRouter` (hardcoded pass-through). The interface exists so we can later swap in per-thread routing, multi-agent, or load-balanced strategies without changing the gateway or adapters.
52
- - **AgentAdapter is the only abstraction we own.** The chat side is Vercel Chat SDK — we don't wrap it. The agent side is our `AgentAdapter` interface: `prompt(threadId, text) → AgentResponse`.
60
+ - **AgentAdapter is the only abstraction we own.** The chat side is Vercel Chat SDK — we don't wrap it. The agent side is our `AgentAdapter` interface: `prompt(threadId, message) → AgentResponse`.
53
61
  - **Config-driven.** `gateway.config.json` (or `--config` flag, or env vars) determines everything at startup. No runtime reconfiguration.
54
62
  - **Persistent sessions.** Each thread gets its own session file on disk. Gateway restarts resume from the same file. Pi CLI can join the same session.
55
63
 
56
64
  ## Quick start
57
65
 
66
+ ### 1. Create a Telegram bot
67
+
68
+ 1. Message [@BotFather](https://t.me/BotFather) on Telegram
69
+ 2. `/newbot` → pick a name and username
70
+ 3. Copy the **bot token**
71
+
72
+ ### 2. Run (dev mode)
73
+
58
74
  ```bash
75
+ git clone https://github.com/inceptionstack/roundhouse.git
76
+ cd roundhouse
59
77
  npm install
60
78
  export TELEGRAM_BOT_TOKEN="your-token"
61
79
  export ALLOWED_USERS="your_telegram_username"
62
80
  npm start
63
81
  ```
64
82
 
83
+ ### 3. Or install globally and run as a daemon
84
+
85
+ ```bash
86
+ npm install -g @inceptionstack/roundhouse
87
+ export TELEGRAM_BOT_TOKEN="your-token"
88
+ export ALLOWED_USERS="your_username"
89
+ roundhouse install # installs as systemd service, starts automatically
90
+ ```
91
+
92
+ ## CLI
93
+
94
+ ```
95
+ roundhouse <command>
96
+
97
+ Commands:
98
+ start Start the gateway (foreground)
99
+ tui [thread] Open agent TUI on a gateway session
100
+ install Install as a systemd daemon (requires sudo)
101
+ uninstall Remove the systemd daemon
102
+ update Update from npm + restart daemon
103
+ status Show daemon status (rich detail view)
104
+ logs Tail daemon logs
105
+ stop Stop the daemon
106
+ restart Restart the daemon
107
+ config Show config path and contents
108
+ ```
109
+
110
+ ### `roundhouse status`
111
+
112
+ Shows detailed daemon info including version, state, PID, uptime, memory, agent type and version, platforms, allowed users, notify chats, debug flags, and config paths.
113
+
114
+ ### `roundhouse tui`
115
+
116
+ Opens the configured agent's interactive TUI, resumed to a gateway chat session. This lets you continue the same conversation from Telegram in your terminal.
117
+
118
+ ```bash
119
+ roundhouse tui # pick from all threads
120
+ roundhouse tui telegram # filter to telegram threads
121
+ roundhouse tui telegram_c12345 # exact thread match
122
+ ```
123
+
65
124
  ## Config
66
125
 
67
- Place `gateway.config.json` in the project root, or use `--config path`:
126
+ Place `gateway.config.json` in `~/.roundhouse/` (created by `roundhouse install`), or in the project root, or use `--config path`:
68
127
 
69
128
  ```json
70
129
  {
@@ -93,7 +152,12 @@ Without a config file, defaults are used with env vars (`TELEGRAM_BOT_TOKEN`, `B
93
152
  | `agent.sessionDir` | Override session storage path |
94
153
  | `chat.botUsername` | Bot display name for Chat SDK |
95
154
  | `chat.allowedUsers` | Telegram usernames / user IDs allowed (empty = allow all) |
155
+ | `chat.notifyChatIds` | Telegram chat IDs to notify on startup (env: `NOTIFY_CHAT_IDS`) |
96
156
  | `chat.adapters.telegram` | `{ "mode": "polling" \| "webhook" \| "auto" }` |
157
+ | `voice.stt.enabled` | Enable automatic voice transcription (default: off unless configured) |
158
+ | `voice.stt.autoInstall` | Auto-install whisper via pip3 if missing (default: false) |
159
+ | `voice.stt.chain` | STT provider chain, e.g. `["whisper"]` |
160
+ | `voice.stt.providers.whisper` | `{ "model": "small", "timeoutMs": 30000 }` |
97
161
 
98
162
  Secrets stay in env vars: `TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`, etc.
99
163
 
@@ -102,11 +166,237 @@ Secrets stay in env vars: `TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`, etc.
102
166
  Sessions are stored at `~/.pi/agent/gateway-sessions/<thread>/`. Resume from CLI:
103
167
 
104
168
  ```bash
169
+ # Via roundhouse (recommended — auto-discovers sessions)
170
+ roundhouse tui
171
+
172
+ # Or directly via pi
105
173
  pi --resume ~/.pi/agent/gateway-sessions/<thread_dir>/<session>.jsonl
106
174
  ```
107
175
 
108
176
  Messages from Telegram/Slack and from the CLI share the same context.
109
177
 
178
+ ## Telegram bot commands
179
+
180
+ Roundhouse automatically registers these commands with Telegram on startup:
181
+
182
+ | Command | Description |
183
+ |---------|-------------|
184
+ | `/new` | Start a fresh conversation (resets the agent session for this chat) |
185
+ | `/compact` | Compact session context to free up tokens |
186
+ | `/verbose` | Toggle tool status messages on/off for this chat |
187
+ | `/status` | Show gateway status: version, agent, model, context usage, uptime, etc. |
188
+ | `/stop` | Stop the current agent run (abort tools, LLM calls, compaction) |
189
+ | `/restart` | Restart the gateway service (requires `allowedUsers` to be configured) |
190
+ | `/doctor` | Run health checks and show system status |
191
+ | `/crons` | Manage scheduled jobs (list, trigger, pause, resume) |
192
+ | `/jobs` | List scheduled jobs (alias for /crons) |
193
+
194
+ These appear in Telegram's `/` command menu automatically.
195
+
196
+ ### `/status` details
197
+
198
+ Shows a rich status view including:
199
+ - Roundhouse and agent versions
200
+ - Current model (from active session or configured default)
201
+ - Context token usage with visual progress bar
202
+ - Active sessions, platforms, uptime, memory
203
+ - Debug flags and allowed users
204
+
205
+ ### `/compact`
206
+
207
+ Manually triggers context compaction for the current chat's session. Shows before/after token counts. Useful when conversations get long and you want to free up context window space without starting a new session.
208
+
209
+ ### `/verbose`
210
+
211
+ Toggles verbose mode for the current chat. When ON, shows tool call status messages (e.g. "⚡ Running `bash`…"). When OFF (default), tool calls execute silently — you only see the agent's text responses. State shown in `/status`.
212
+
213
+ ### `/stop`
214
+
215
+ Aborts the current agent run for this chat — stops any in-progress tool calls, LLM generation, and compaction. The session is preserved; send another message to continue the conversation.
216
+
217
+ ### Follow-up notifications
218
+
219
+ When extensions (e.g. code review) queue follow-up work after the agent responds, the gateway shows:
220
+ - ⏳ "Hold on — waiting for follow-up messages..." (after 2s delay)
221
+ - ✅ "All done — waiting for your input." (when processing completes)
222
+
223
+ Fast operations that complete within 2 seconds show no extra messages.
224
+
225
+ ## File attachments
226
+
227
+ Roundhouse handles voice messages, images, documents, and other file attachments from Telegram:
228
+
229
+ 1. Files are downloaded and saved to `~/.roundhouse/incoming/<thread>/<message>/`
230
+ 2. A structured `AgentMessage` with typed `MessageAttachment[]` metadata is passed to the agent
231
+ 3. The agent receives file paths, MIME types, sizes, and can inspect files with its tools
232
+ 4. Files are marked as untrusted user-provided input
233
+
234
+ ### Limits
235
+
236
+ | Limit | Value |
237
+ |-------|-------|
238
+ | Max file size | 20 MB per file |
239
+ | Max attachments | 5 per message |
240
+ | Filename length | 100 characters (sanitized to ASCII) |
241
+
242
+ Supported types: voice messages, audio, images, video, documents (PDF, etc.)
243
+
244
+ The incoming directory can be overridden with `ROUNDHOUSE_INCOMING_DIR` environment variable.
245
+
246
+ ### How the agent sees attachments
247
+
248
+ The Pi adapter formats attachments as a fenced JSON manifest:
249
+
250
+ ```
251
+ Chat attachments saved locally. Inspect these files with tools before making claims about their contents.
252
+ ```json
253
+ [
254
+ {
255
+ "id": "att_a1b2c3d4",
256
+ "type": "audio",
257
+ "name": "audio.ogg",
258
+ "localPath": "/home/user/.roundhouse/incoming/telegram_c123/1745.../0-audio.ogg",
259
+ "mime": "audio/ogg",
260
+ "sizeBytes": 43520,
261
+ "untrusted": true
262
+ }
263
+ ]
264
+ ```
265
+
266
+ Other agent adapters can format attachments differently — the `AgentMessage.attachments` array provides structured data.
267
+
268
+ ### Voice transcription (STT)
269
+
270
+ Roundhouse can automatically transcribe voice messages using [OpenAI Whisper](https://github.com/openai/whisper) running locally. No cloud services or API keys required.
271
+
272
+ **Setup:**
273
+ ```bash
274
+ pip install openai-whisper
275
+ ```
276
+
277
+ Or set `autoInstall: true` in config to have roundhouse install whisper automatically on first voice message.
278
+
279
+ **Enable in config:**
280
+ ```json
281
+ {
282
+ "voice": {
283
+ "stt": {
284
+ "enabled": true,
285
+ "mode": "on",
286
+ "autoInstall": true,
287
+ "chain": ["whisper"],
288
+ "autoTranscribe": {
289
+ "voiceMessages": true,
290
+ "audioFiles": false,
291
+ "maxDurationSec": 120
292
+ },
293
+ "providers": {
294
+ "whisper": {
295
+ "type": "whisper",
296
+ "model": "small",
297
+ "timeoutMs": 30000
298
+ }
299
+ }
300
+ }
301
+ }
302
+ }
303
+ ```
304
+
305
+ When enabled, voice messages are automatically transcribed before being sent to the agent. The agent sees both the transcript and the raw audio file path:
306
+
307
+ ```json
308
+ {
309
+ "id": "att_a1b2c3d4",
310
+ "type": "audio",
311
+ "localPath": "/home/user/.roundhouse/incoming/.../0-audio.ogg",
312
+ "transcript": {
313
+ "text": "This will also work in Hebrew",
314
+ "language": "hebrew",
315
+ "provider": "whisper-small",
316
+ "approximate": true
317
+ }
318
+ }
319
+ ```
320
+
321
+ For voice-only messages (no typed text), the transcript becomes the message text sent to the agent.
322
+
323
+ Whisper model sizes: `tiny` (fast, English-only reliable), `base`, `small` (recommended — good multilingual), `medium`, `large` (slow but most accurate).
324
+
325
+ Transcripts are always marked `approximate: true` — the agent is instructed to use the raw file if exact wording matters.
326
+
327
+ ## Cron Jobs (Scheduled Tasks)
328
+
329
+ Roundhouse includes a built-in cron scheduler for running agent prompts on a schedule.
330
+
331
+ ### CLI Commands
332
+
333
+ ```
334
+ roundhouse cron add <id> [flags] Create a scheduled job
335
+ roundhouse cron list List all jobs
336
+ roundhouse cron show <id> Show job details + recent runs
337
+ roundhouse cron trigger <id> Run a job now
338
+ roundhouse cron runs <id> Show run history
339
+ roundhouse cron edit <id> [flags] Modify a job
340
+ roundhouse cron pause <id> Disable a job
341
+ roundhouse cron resume <id> Re-enable a job
342
+ roundhouse cron delete <id> Delete a job
343
+ ```
344
+
345
+ ### Schedule Types
346
+
347
+ ```bash
348
+ # Standard cron with timezone
349
+ roundhouse cron add daily-report --cron "0 8 * * *" --tz Asia/Jerusalem --prompt "..."
350
+
351
+ # Fixed interval
352
+ roundhouse cron add health-check --every 6h --prompt "..."
353
+
354
+ # One-shot (relative or absolute)
355
+ roundhouse cron add reminder --at 30m --prompt "..."
356
+ roundhouse cron add meeting --at 2026-04-28T14:00:00 --tz Asia/Jerusalem --prompt "..."
357
+ ```
358
+
359
+ ### Notifications
360
+
361
+ Add `--telegram <chatId>` to send results to Telegram:
362
+
363
+ ```bash
364
+ roundhouse cron add aws-costs --cron "0 8 * * *" --tz Asia/Jerusalem \\
365
+ --prompt "Check current AWS costs and summarize." \\
366
+ --telegram 123456789
367
+ ```
368
+
369
+ ### Telegram Commands
370
+
371
+ | Command | Description |
372
+ |---------|-------------|
373
+ | `/crons` or `/jobs` | List all scheduled jobs |
374
+ | `/crons trigger <id>` | Run a job now |
375
+ | `/crons pause <id>` | Disable a job |
376
+ | `/crons resume <id>` | Re-enable a job |
377
+
378
+ ### Conversational Setup
379
+
380
+ Tell the agent: *"set a cron daily 8am Israel time to give me current AWS costs"* — the agent will run `roundhouse cron add` via its bash tool.
381
+
382
+ ### Heartbeat
383
+
384
+ Edit `~/.roundhouse/HEARTBEAT.md` with recurring tasks. The scheduler reads it every 30 minutes and runs the instructions as an agent prompt. If the file is empty or contains only the default template, no action is taken.
385
+
386
+ ### Config
387
+
388
+ Job configs stored as JSON in `~/.roundhouse/crons/`. State in `~/.roundhouse/cron-state/`. Run history in `~/.roundhouse/cron-runs/`.
389
+
390
+ ## Extensions
391
+
392
+ ### Code review extension
393
+
394
+ The code review extension has moved to its own package: [pi-autoreview](https://github.com/inceptionstack/pi-autoreview). Install it with:
395
+
396
+ ```bash
397
+ pi install git:github.com/inceptionstack/pi-autoreview
398
+ ```
399
+
110
400
  ## Adding a new agent backend
111
401
 
112
402
  1. Create `src/agents/kiro.ts` implementing `AgentAdapter`
@@ -114,14 +404,14 @@ Messages from Telegram/Slack and from the CLI share the same context.
114
404
  3. Set `"agent": { "type": "kiro" }` in config
115
405
 
116
406
  ```typescript
117
- // src/agents/kiro.ts
118
407
  import type { AgentAdapter, AgentAdapterFactory } from "../types";
119
408
 
120
409
  export const createKiroAgentAdapter: AgentAdapterFactory = (config) => {
121
410
  return {
122
411
  name: "kiro",
123
- async prompt(threadId, text) {
124
- // your implementation
412
+ async prompt(threadId, message) {
413
+ // message.text contains user text
414
+ // message.attachments contains saved file metadata
125
415
  return { text: "response" };
126
416
  },
127
417
  async dispose() {},
@@ -134,7 +424,6 @@ export const createKiroAgentAdapter: AgentAdapterFactory = (config) => {
134
424
  Add the Chat SDK adapter package and wire it in `gateway.ts`:
135
425
 
136
426
  ```typescript
137
- // In buildChatAdapters():
138
427
  if (config.slack) {
139
428
  const { createSlackAdapter } = await import("@chat-adapter/slack");
140
429
  adapters.slack = createSlackAdapter();
@@ -150,11 +439,30 @@ No other changes needed — the gateway's unified handler covers all platforms.
150
439
  | `src/index.ts` | Entry point, config loading, startup |
151
440
  | `src/gateway.ts` | Owns Chat SDK, wires events → router → agent |
152
441
  | `src/router.ts` | `AgentRouter` interface + `SingleAgentRouter` |
153
- | `src/types.ts` | Core interfaces: `AgentAdapter`, `AgentRouter`, `GatewayConfig` |
154
- | `src/util.ts` | Pure utilities: `splitMessage`, `isAllowed`, `threadIdToDir` |
442
+ | `src/types.ts` | Core interfaces: `AgentAdapter`, `AgentStreamEvent`, `AgentRouter`, `GatewayConfig` |
443
+ | `src/util.ts` | Pure utilities: `splitMessage`, `isAllowed`, `threadIdToDir`, `startTypingLoop` |
444
+ | `src/cli/cli.ts` | CLI: start, install, tui, update, logs, etc. |
445
+ | `src/cli/doctor.ts` | CLI doctor command |
446
+ | `src/cli/doctor/runner.ts` | Shared doctor runner (CLI + gateway) |
447
+ | `src/cli/doctor/checks/` | Individual health check modules |
448
+ | `src/cron/` | Cron scheduler, runner, store, schedule, template, format |
449
+ | `src/cron/helpers.ts` | Shared cron constants and utilities |
450
+ | `src/notify/telegram.ts` | Shared Telegram Bot API sender |
155
451
  | `src/agents/pi.ts` | Pi agent adapter (persistent sessions via pi SDK) |
156
452
  | `src/agents/registry.ts` | Agent type → factory registry |
157
- | `test/` | Unit tests (vitest, 32 passing) |
453
+ | `src/config.ts` | Shared config loading, defaults, env overrides |
454
+ | `test/` | Unit tests (vitest, 75 passing) |
455
+
456
+ ## CI/CD
457
+
458
+ Tests run on every push/PR. Publishing to npm happens automatically on tag push:
459
+
460
+ ```bash
461
+ npm version patch # bumps version, creates git tag
462
+ git push origin main --tags # triggers publish workflow
463
+ ```
464
+
465
+ Requires `NPM_TOKEN` secret in GitHub repo settings.
158
466
 
159
467
  ## Testing
160
468
 
@@ -162,3 +470,7 @@ No other changes needed — the gateway's unified handler covers all platforms.
162
470
  npm test # run once
163
471
  npm run test:watch # watch mode
164
472
  ```
473
+
474
+ ## License
475
+
476
+ MIT
package/architecture.md CHANGED
@@ -98,12 +98,14 @@ User sends "list files" on Telegram
98
98
  │ 1. Check isAllowed(message.author, allowedUsers) │
99
99
  │ 2. Resolve agent via router.resolve(thread.id) │
100
100
  │ 3. thread.startTyping() │
101
- │ 4. agent.prompt(thread.id, "list files")
101
+ │ 4. Save attachments to ~/.roundhouse/incoming/<thread>/
102
+ │ 5. Build AgentMessage { text, attachments } │
103
+ │ 6. agent.promptStream(thread.id, agentMessage) │
104
+ │ └─▶ Pi adapter formats text + JSON attachment manifest │
102
105
  │ └─▶ Pi SDK creates/resumes session │
103
106
  │ └─▶ LLM processes, tools execute │
104
- │ └─▶ Returns AgentResponse { text: "..." }
105
- 5. splitMessage(response.text, 4000)
106
- │ 6. thread.post(chunk) for each chunk │
107
+ │ └─▶ Streams AgentStreamEvent back
108
+ 7. Stream text deltas via thread.handleStream()
107
109
  └──────────────────────────────────────────────────────────────┘
108
110
 
109
111
 
@@ -113,9 +115,41 @@ User sends "list files" on Telegram
113
115
  ## Key interfaces
114
116
 
115
117
  ```typescript
118
+ interface AgentMessage {
119
+ text: string;
120
+ attachments?: MessageAttachment[];
121
+ }
122
+
123
+ interface MessageAttachment {
124
+ id: string;
125
+ mediaType: "audio" | "image" | "file" | "video";
126
+ name: string;
127
+ localPath: string;
128
+ mime: string;
129
+ sizeBytes: number;
130
+ untrusted: true;
131
+ transcript?: AttachmentTranscript;
132
+ }
133
+
134
+ interface AttachmentTranscript {
135
+ text: string;
136
+ provider: string;
137
+ language?: string;
138
+ confidence?: number;
139
+ approximate: true;
140
+ status: "completed" | "failed";
141
+ error?: string;
142
+ durationMs?: number;
143
+ }
144
+
116
145
  interface AgentAdapter {
117
146
  name: string;
118
- prompt(threadId: string, text: string): Promise<AgentResponse>;
147
+ prompt(threadId: string, message: AgentMessage): Promise<AgentResponse>;
148
+ promptStream?(threadId: string, message: AgentMessage): AsyncIterable<AgentStreamEvent>;
149
+ restart?(threadId: string): Promise<void>;
150
+ compact?(threadId: string): Promise<{ tokensBefore: number; tokensAfter: number | null } | null>;
151
+ abort?(threadId: string): Promise<void>;
152
+ getInfo?(threadId?: string): Record<string, unknown>;
119
153
  dispose(): Promise<void>;
120
154
  }
121
155
 
@@ -142,10 +176,24 @@ gateway.config.json
142
176
  └── chat # Multiple chat inputs
143
177
  ├── botUsername: "my_bot"
144
178
  ├── allowedUsers: [...] # Auth filter (userName or userId)
179
+ ├── notifyChatIds: [...] # Telegram chat IDs for startup notifications
145
180
  └── adapters
146
181
  ├── telegram: { mode: "polling" }
147
182
  ├── slack: { ... } # (future)
148
183
  └── discord: { ... } # (future)
184
+
185
+ └── voice # Optional voice features
186
+ └── stt
187
+ ├── enabled: true
188
+ ├── mode: "on" | "off"
189
+ ├── autoInstall: false # auto-install whisper via pip3
190
+ ├── chain: ["whisper"] # Provider chain (try in order)
191
+ ├── autoTranscribe
192
+ │ ├── voiceMessages: true
193
+ │ ├── audioFiles: false
194
+ │ └── maxDurationSec: 120
195
+ └── providers
196
+ └── whisper: { model: "small", timeoutMs: 30000 }
149
197
  ```
150
198
 
151
199
  Secrets (`TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`) are always env vars, never in config.
@@ -163,6 +211,9 @@ Secrets (`TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`) are always env vars, never i
163
211
  b. Create Chat instance with all adapters
164
212
  c. Wire onDirectMessage / onNewMention / onSubscribedMessage → handle()
165
213
  d. chat.initialize() — starts polling / webhooks
214
+ e. registerBotCommands() — register /new, /restart, /status with Telegram
215
+ f. notifyStartup() — send Telegram notification to configured notifyChatIds
216
+ g. Start CronSchedulerService — loads jobs, catches up missed runs, ticks every 60s
166
217
  7. Running. Ctrl+C → gateway.stop() → router.dispose() → agent.dispose()
167
218
  ```
168
219
 
@@ -202,13 +253,31 @@ The gateway and agent adapters don't change — only the router.
202
253
 
203
254
  ```
204
255
  index.ts
256
+ ├── config.ts (loadConfig, applyEnvOverrides)
205
257
  ├── agents/registry.ts
206
258
  │ └── agents/pi.ts
207
259
  │ └── util.ts (threadIdToDir)
208
260
  ├── router.ts
209
261
  ├── gateway.ts
210
- │ └── util.ts (splitMessage, isAllowed)
211
- └── types.ts (shared interfaces)
262
+ │ └── util.ts (splitMessage, isAllowed, threadIdToDir, generateAttachmentId)
263
+ └── voice/stt-service.ts
264
+ │ └── voice/providers/whisper.ts
265
+ │ └── voice/types.ts
266
+ └── types.ts (shared interfaces, pure types only)
267
+
268
+ cli/cli.ts
269
+ ├── config.ts (DEFAULT_CONFIG, CONFIG_PATH, loadConfig, etc.)
270
+ ├── agents/registry.ts (getAgentSdkPackage)
271
+ ├── cli/doctor.ts → cli/doctor/runner.ts → cli/doctor/checks/*
272
+ ├── cli/cron.ts → cron/store.ts, cron/runner.ts, cron/helpers.ts
273
+ └── (node:fs, node:child_process for daemon management)
274
+
275
+ gateway.ts also imports:
276
+ → cli/doctor/runner.ts for /doctor command
277
+ → cron/scheduler.ts → cron/runner.ts → cron/store.ts
278
+ → cron/helpers.ts, cron/format.ts
279
+ → notify/telegram.ts
212
280
  ```
213
281
 
214
- No circular dependencies. `util.ts` and `types.ts` are leaf modules.
282
+ No circular dependencies. `types.ts` and `config.ts` are pure leaf modules.
283
+ `util.ts` is a leaf module with runtime helpers (`node:crypto` for attachment IDs).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inceptionstack/roundhouse",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Multi-platform chat gateway that routes messages through a configured AI agent",
6
6
  "license": "MIT",
@@ -40,6 +40,8 @@
40
40
  "@chat-adapter/telegram": "latest",
41
41
  "@mariozechner/pi-coding-agent": "latest",
42
42
  "chat": "latest",
43
+ "croner": "^10.0.1",
44
+ "p-queue": "^9.2.0",
43
45
  "tsx": "^4.0.0"
44
46
  },
45
47
  "devDependencies": {