@cogitator-ai/channels 0.6.1 → 0.7.1
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/README.md +110 -48
- package/dist/approvals.d.ts +2 -21
- package/dist/approvals.d.ts.map +1 -1
- package/dist/approvals.js.map +1 -1
- package/dist/assistant-config.d.ts +13 -0
- package/dist/assistant-config.d.ts.map +1 -1
- package/dist/assistant-config.js +24 -3
- package/dist/assistant-config.js.map +1 -1
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +7 -4
- package/dist/gateway.js.map +1 -1
- package/dist/heartbeat.d.ts +20 -0
- package/dist/heartbeat.d.ts.map +1 -1
- package/dist/heartbeat.js +76 -3
- package/dist/heartbeat.js.map +1 -1
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +17 -12
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/middleware/dm-policy.d.ts +1 -0
- package/dist/middleware/dm-policy.d.ts.map +1 -1
- package/dist/middleware/dm-policy.js +2 -1
- package/dist/middleware/dm-policy.js.map +1 -1
- package/dist/paths.d.ts +3 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +11 -0
- package/dist/paths.js.map +1 -0
- package/dist/runtime-builder.d.ts +9 -0
- package/dist/runtime-builder.d.ts.map +1 -1
- package/dist/runtime-builder.js +41 -10
- package/dist/runtime-builder.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Connect Cogitator agents to messaging platforms. Gateway routes messages across channels, handles sessions, streaming, media, and middleware.
|
|
4
4
|
|
|
5
|
+
Full documentation: [cogitator.app/docs/channels/gateway](https://cogitator.app/docs/channels/gateway).
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -11,11 +13,16 @@ pnpm add @cogitator-ai/channels
|
|
|
11
13
|
pnpm add grammy # Telegram
|
|
12
14
|
pnpm add discord.js # Discord
|
|
13
15
|
pnpm add @slack/bolt # Slack
|
|
14
|
-
pnpm add @whiskeysockets/baileys # WhatsApp (+ qrcode-terminal to print the pairing QR)
|
|
16
|
+
pnpm add @whiskeysockets/baileys # WhatsApp, 6.x or 7.x (+ qrcode-terminal to print the pairing QR)
|
|
15
17
|
pnpm add ws # WebChat
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
# RuntimeBuilder (`cogitator up`)
|
|
20
|
+
pnpm add better-sqlite3 # SQLite memory, knowledge graph and core facts (always needed)
|
|
21
|
+
pnpm add pg # memory.adapter: postgres
|
|
17
22
|
```
|
|
18
23
|
|
|
24
|
+
All adapters are optional peer dependencies, loaded only when the channel starts; a missing one fails `start()` with an install hint. RuntimeBuilder capabilities load `@cogitator-ai/browser`, `@cogitator-ai/rag`, `@cogitator-ai/mcp` and `@cogitator-ai/models` (vision detection) on demand. `LocalWhisper` needs `@huggingface/transformers` and `ogg-opus-decoder`, and installs them with `npm install --no-save` when missing.
|
|
25
|
+
|
|
19
26
|
## Quick Start
|
|
20
27
|
|
|
21
28
|
```typescript
|
|
@@ -58,13 +65,14 @@ telegramChannel({
|
|
|
58
65
|
webhook: {
|
|
59
66
|
url: 'https://example.com/telegram',
|
|
60
67
|
port: 8443,
|
|
68
|
+
path: '/telegram', // optional, defaults to the pathname of `url`
|
|
61
69
|
secretToken: process.env.TG_WEBHOOK_SECRET, // verified on every update
|
|
62
70
|
},
|
|
63
71
|
allowedUpdates: ['message'],
|
|
64
72
|
});
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
Supports: text, photos (→ vision), voice and audio (→ STT), video, documents, replies, streaming via drafts/editText, emoji reactions, sending files from buffers or URLs, 4096 char limit.
|
|
75
|
+
Supports: text, photos (→ vision), voice and audio (→ STT), video, documents, replies, streaming via drafts/editText, emoji reactions, sending files from buffers or URLs, 4096 char limit. Docs: [Telegram](https://cogitator.app/docs/channels/telegram).
|
|
68
76
|
|
|
69
77
|
### Discord
|
|
70
78
|
|
|
@@ -79,7 +87,7 @@ discordChannel({
|
|
|
79
87
|
});
|
|
80
88
|
```
|
|
81
89
|
|
|
82
|
-
Supports: DMs, server messages, attachments (images → vision, audio → STT), replies, streaming, reactions, auto-chunking (2000 chars / 17 lines, code-block aware). Long streamed answers span several Discord messages that are edited in place.
|
|
90
|
+
Supports: DMs, server messages, attachments (images → vision, audio → STT), replies, streaming, reactions, auto-chunking (2000 chars / 17 lines, code-block aware). Long streamed answers span several Discord messages that are edited in place. Docs: [Discord](https://cogitator.app/docs/channels/discord).
|
|
83
91
|
|
|
84
92
|
### Slack
|
|
85
93
|
|
|
@@ -98,11 +106,11 @@ import { slackChannel } from '@cogitator-ai/channels';
|
|
|
98
106
|
slackChannel({
|
|
99
107
|
token: process.env.SLACK_BOT_TOKEN!,
|
|
100
108
|
signingSecret: process.env.SLACK_SIGNING_SECRET!,
|
|
101
|
-
appToken: process.env.SLACK_APP_TOKEN!,
|
|
109
|
+
appToken: process.env.SLACK_APP_TOKEN!, // Socket Mode; omit to serve HTTP events on `port` (default 3000)
|
|
102
110
|
});
|
|
103
111
|
```
|
|
104
112
|
|
|
105
|
-
Supports: DMs, channel messages (via @mention, the mention is stripped from the text), threads (replies go to the thread root), file shares, Socket Mode (no public URL), streaming via chat.update, reactions. Set `mentionOnly: true` to ignore channel messages that do not mention the bot when the app also subscribes to `message.channels`. In `RuntimeBuilder`, Socket Mode is on when `SLACK_APP_TOKEN` is set; otherwise Slack runs in HTTP mode on `SLACK_PORT` (default 3000).
|
|
113
|
+
Supports: DMs, channel messages (via @mention, the mention is stripped from the text), threads (replies go to the thread root), file shares, Socket Mode (no public URL), streaming via chat.update, reactions. Set `mentionOnly: true` to ignore channel messages that do not mention the bot when the app also subscribes to `message.channels`. In `RuntimeBuilder`, Socket Mode is on when `SLACK_APP_TOKEN` is set; otherwise Slack runs in HTTP mode on `SLACK_PORT` (default 3000). Docs: [Slack](https://cogitator.app/docs/channels/slack).
|
|
106
114
|
|
|
107
115
|
### WhatsApp
|
|
108
116
|
|
|
@@ -119,7 +127,9 @@ whatsappChannel({
|
|
|
119
127
|
|
|
120
128
|
**First run:** the QR code is printed to the terminal (or handed to `qrCallback`) → scan from WhatsApp → Settings → Linked Devices. Session is saved for future restarts.
|
|
121
129
|
|
|
122
|
-
Supports: 1-on-1 and group chats, images/voice/video/documents with captions, quoted replies, message edits (streaming), typing indicators, auto-reconnect with exponential backoff, WhatsApp-native markdown.
|
|
130
|
+
Supports: 1-on-1 and group chats, images/voice/video/documents with captions, quoted replies, message edits (streaming), typing indicators, auto-reconnect with exponential backoff (stops when WhatsApp logs the device out), WhatsApp-native markdown.
|
|
131
|
+
|
|
132
|
+
Works with Baileys 6.x and 7.x. `userId` is the sender's phone number (the part before `@`). When WhatsApp addresses a person by LID (Baileys 7), the phone number is taken from `remoteJidAlt` / `participantAlt` whenever WhatsApp shares it, so owner lists and per-user memory keep matching. With `cogitator up`, enable it with `channels.whatsapp` in `cogitator.yml` (see [RuntimeBuilder](#runtimebuilder-yaml-assistants)). Docs: [WhatsApp](https://cogitator.app/docs/channels/whatsapp).
|
|
123
133
|
|
|
124
134
|
### WebChat
|
|
125
135
|
|
|
@@ -131,16 +141,16 @@ import { webchatChannel } from '@cogitator-ai/channels';
|
|
|
131
141
|
webchatChannel({
|
|
132
142
|
port: 3100,
|
|
133
143
|
path: '/ws',
|
|
134
|
-
auth: (token) => token === process.env.
|
|
144
|
+
auth: (token) => token === process.env.WEBCHAT_TOKEN,
|
|
135
145
|
maxPayload: 1024 * 1024, // bytes per incoming frame (default 1 MiB)
|
|
136
146
|
});
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
Connect: `ws://localhost:3100/ws?token=YOUR_SECRET` (unauthorized sockets are closed with code 1008). Send `{"text": "Hello!", "id": "optional-client-id"}`.
|
|
149
|
+
Connect: `ws://localhost:3100/ws?token=YOUR_SECRET` (unauthorized sockets get an `{"type":"error","message":"unauthorized"}` frame and are closed with code 1008). Send `{"text": "Hello!", "id": "optional-client-id"}`. Each connection is its own user and chat (`clientId`).
|
|
140
150
|
|
|
141
|
-
Server frames: `connected` (`clientId`), `message` (`id`, `text`, `replyTo`), `edit` (`id`, `text`), `delete` (`id`), `typing`, `file` (`url` or base64 `data`).
|
|
151
|
+
Server frames: `connected` (`clientId`), `message` (`id`, `text`, `replyTo`), `edit` (`id`, `text`), `delete` (`id`), `typing`, `file` (`filename`, `mimeType`, and `url` or base64 `data`).
|
|
142
152
|
|
|
143
|
-
Supports: streaming (via edit frames), typing indicators, token auth, no message limit.
|
|
153
|
+
Supports: streaming (via edit frames), typing indicators, token auth, no message limit. Text only on the way in (no attachments). With `cogitator up`, enable it with `channels.webchat` in `cogitator.yml` plus `WEBCHAT_TOKEN` (see [RuntimeBuilder](#runtimebuilder-yaml-assistants)). Docs: [WebChat](https://cogitator.app/docs/channels/webchat).
|
|
144
154
|
|
|
145
155
|
### Terminal
|
|
146
156
|
|
|
@@ -152,7 +162,7 @@ import { terminalChannel } from '@cogitator-ai/channels';
|
|
|
152
162
|
terminalChannel({ userName: 'Alice', prompt: '> ', onExit: () => shutdown() });
|
|
153
163
|
```
|
|
154
164
|
|
|
155
|
-
Ctrl+C, Ctrl+D, `/quit`, `/exit` and `exit` call `onExit` (default: raise `SIGINT` so the host app shuts down gracefully); `stop()` closes readline without exiting the process.
|
|
165
|
+
Ctrl+C, Ctrl+D, `/quit`, `/exit` and `exit` call `onExit` (default: raise `SIGINT` so the host app shuts down gracefully); `stop()` closes readline without exiting the process. Docs: [Terminal](https://cogitator.app/docs/channels/terminal).
|
|
156
166
|
|
|
157
167
|
## Platform Comparison
|
|
158
168
|
|
|
@@ -166,7 +176,7 @@ Ctrl+C, Ctrl+D, `/quit`, `/exit` and `exit` call `onExit` (default: raise `SIGIN
|
|
|
166
176
|
| Max message length | 4096 | 2000 | 40000 | 65536 | unlimited |
|
|
167
177
|
| Public URL needed | webhook only | no | HTTP only | no | no |
|
|
168
178
|
|
|
169
|
-
Outbound calls (`sendText`, `editText`, `sendFile`, `deleteMessage`) throw when the channel is not started, the target chat
|
|
179
|
+
Outbound calls (`sendText`, `editText`, `sendFile`, `deleteMessage`) throw when the channel is not started, the target chat is unavailable or the platform rejects the request — wrap direct calls in `try/catch`. WebChat is the exception: only `sendText` throws for a disconnected client, the other calls are dropped. `sendText` returns the platform message id (for Discord, the id of the first chunk; continuation chunks are edited and deleted with it).
|
|
170
180
|
|
|
171
181
|
## Gateway
|
|
172
182
|
|
|
@@ -174,7 +184,7 @@ Routes incoming messages to your agent. Handles:
|
|
|
174
184
|
|
|
175
185
|
- Session management (per-user, per-channel) with an optional custom `sessionManager`
|
|
176
186
|
- History compaction with LLM-written summaries
|
|
177
|
-
- Streaming with `StreamBuffer` (message editing, splits at the platform limit without losing text)
|
|
187
|
+
- Streaming with `StreamBuffer` when `stream` is set (message editing, splits at the platform limit without losing text); without `stream` the full answer is sent once the run finishes
|
|
178
188
|
- Typing keep-alive (re-sends typing indicator every 4s)
|
|
179
189
|
- Media processing (images → vision, voice → STT)
|
|
180
190
|
- Middleware pipeline
|
|
@@ -201,11 +211,18 @@ gateway.stats; // { uptime, activeSessions, totalSessions, messagesToday, connec
|
|
|
201
211
|
gateway.getSessions(); // [{ threadId, userName, messageCount, lastActiveAt, active }]
|
|
202
212
|
await gateway.compactThread('telegram:42'); // force compaction of one conversation
|
|
203
213
|
await gateway.injectMessage(msg); // feed a synthetic message (used by the scheduler)
|
|
214
|
+
await gateway.stop();
|
|
204
215
|
```
|
|
205
216
|
|
|
217
|
+
`GatewayConfig.owner` is deprecated and never read: set owners on `ownerCommands({ ownerIds })` and `dmPolicy({ ownerIds })` (see [Middleware](#middleware)).
|
|
218
|
+
|
|
219
|
+
`stream` takes `flushInterval` and `minChunkSize` (ms / chars between edits), plus optional `minInitialChars` (wait for this much text before the first message), `maxMessageChars` (defaults to the platform limit) and `deleteOnAbort` (remove the partial answer when the run is interrupted). Telegram streams through message drafts when available and falls back to edits.
|
|
220
|
+
|
|
221
|
+
Docs: [Gateway](https://cogitator.app/docs/channels/gateway), [Streaming](https://cogitator.app/docs/channels/streaming).
|
|
222
|
+
|
|
206
223
|
## Approvals
|
|
207
224
|
|
|
208
|
-
Tools marked `requiresApproval` (for example `create_tool` from `capabilities.selfTools`) pause the run before they execute. The Gateway turns that pause into a chat conversation:
|
|
225
|
+
Tools marked `requiresApproval` (for example `create_tool` from `capabilities.selfTools`) pause the run before they execute — unless the Cogitator has `guardrails.onToolApproval`, which then decides instead. The Gateway turns that pause into a chat conversation:
|
|
209
226
|
|
|
210
227
|
1. The paused run's text (if any) is sent, followed by a prompt listing each waiting call — tool name, description and compact JSON arguments (cut at 300 characters).
|
|
211
228
|
2. The user replies `approve` / `yes` to run the calls, or `deny` / `no` to refuse, optionally followed by a reason (`no, too risky`) that the agent sees. Replies are case-insensitive and may end with punctuation; `да` / `одобряю` and `нет` / `отклоняю` work out of the box.
|
|
@@ -230,12 +247,16 @@ const gateway = new Gateway({
|
|
|
230
247
|
},
|
|
231
248
|
});
|
|
232
249
|
|
|
233
|
-
hooks.on('approval:requested', ({ threadId, approvals }) =>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
)
|
|
250
|
+
hooks.on('approval:requested', ({ threadId, approvals }) => {
|
|
251
|
+
audit.log(threadId, approvals);
|
|
252
|
+
});
|
|
253
|
+
hooks.on('approval:resolved', ({ threadId, decision, superseded }) => {
|
|
254
|
+
audit.log(threadId, decision, superseded);
|
|
255
|
+
});
|
|
237
256
|
```
|
|
238
257
|
|
|
258
|
+
`ApprovalRequestedEvent` and `ApprovalResolvedEvent` are exported types; `superseded` is `true` when the user sent a new message instead of answering. With `RuntimeBuilder` / `cogitator up`, the words and `notAllowedMessage` can be set in the `approvals` block of `cogitator.yml` (see [RuntimeBuilder](#runtimebuilder-yaml-assistants)). `DEFAULT_APPROVE_WORDS`, `DEFAULT_DENY_WORDS` and `DEFAULT_NOT_ALLOWED_MESSAGE` hold the defaults.
|
|
259
|
+
|
|
239
260
|
`parseApprovalReply(text, words)` and `formatApprovalPrompt(approvals, words)` are exported for custom channels and UIs.
|
|
240
261
|
|
|
241
262
|
## Status Reactions
|
|
@@ -323,13 +344,14 @@ Platform-specific markdown conversion applied automatically (code spans and fenc
|
|
|
323
344
|
```typescript
|
|
324
345
|
import { rateLimit, ownerCommands, DmPolicyMiddleware, autoExtract } from '@cogitator-ai/channels';
|
|
325
346
|
|
|
326
|
-
|
|
347
|
+
// `gateway` is referenced lazily by the command handlers
|
|
348
|
+
|
|
349
|
+
const gateway: Gateway = new Gateway({
|
|
327
350
|
// ...
|
|
328
351
|
middleware: [
|
|
329
352
|
new DmPolicyMiddleware({
|
|
330
353
|
mode: 'pairing',
|
|
331
354
|
ownerIds: { telegram: '123' },
|
|
332
|
-
storePath: '~/.cogitator/dm-allowlist.json',
|
|
333
355
|
groupPolicy: 'open',
|
|
334
356
|
}),
|
|
335
357
|
ownerCommands({
|
|
@@ -353,9 +375,11 @@ const gateway = new Gateway({
|
|
|
353
375
|
- **pairing** — unknown users get a code, owner approves via `/pair CODE`
|
|
354
376
|
- **disabled** — DMs blocked entirely
|
|
355
377
|
|
|
378
|
+
`groupPolicy` (`open` | `allowlist` | `disabled`, with `groupAllowlist`) does the same for group chats. Approved users are persisted to `storePath` (default `~/.cogitator/dm-allowlist.json`; a leading `~` expands to the home directory). `dmPolicy(config)` is the factory form. Docs: [Middleware](https://cogitator.app/docs/channels/middleware).
|
|
379
|
+
|
|
356
380
|
### Command Authorization Levels
|
|
357
381
|
|
|
358
|
-
Commands have 3 access levels: `owner`, `authorized`, `public`. Built-in commands: `/status`, `/sessions`, `/help` (authorized) and `/users`, `/compact`, `/model <name> [@user]`, `/restart` (owner). Each command calls the matching `on*` handler you provide. `/cmd@botname` (Telegram groups) is recognized. Only owners can approve `/pair` codes.
|
|
382
|
+
Commands have 3 access levels: `owner`, `authorized`, `public`. Built-in commands: `/status`, `/sessions`, `/help` (authorized) and `/users`, `/compact`, `/model <name> [@user]`, `/restart` (owner). Each command calls the matching `on*` handler you provide. `/cmd@botname` (Telegram groups) is recognized. Only owners can approve `/pair` codes. Docs: [Owner Commands](https://cogitator.app/docs/channels/owner-commands).
|
|
359
383
|
|
|
360
384
|
### Rate Limit
|
|
361
385
|
|
|
@@ -369,25 +393,40 @@ Subscribe to events across the message lifecycle:
|
|
|
369
393
|
import { createHookRegistry } from '@cogitator-ai/channels';
|
|
370
394
|
|
|
371
395
|
const hooks = createHookRegistry();
|
|
372
|
-
hooks.on('message:received', (e) => console.log('New message:', e));
|
|
373
|
-
hooks.on('agent:after_run', (e) => console.log('Response:', e));
|
|
374
|
-
hooks.on('agent:error', (e) => console.error('Agent failed:', e));
|
|
396
|
+
hooks.on('message:received', (e) => console.log('New message:', e.msg.text));
|
|
397
|
+
hooks.on('agent:after_run', (e) => console.log('Response:', e.output));
|
|
398
|
+
hooks.on('agent:error', (e) => console.error('Agent failed:', e.error.message));
|
|
375
399
|
|
|
376
400
|
const gateway = new Gateway({ /* ... */ hooks });
|
|
377
401
|
```
|
|
378
402
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
403
|
+
Handlers are typed by hook name through `HookPayloads` (exported here and from `@cogitator-ai/types`, which also exports each event type):
|
|
404
|
+
|
|
405
|
+
| Hook | Payload | Fields |
|
|
406
|
+
| -------------------- | ------------------------ | ------------------------------------------------------------------- |
|
|
407
|
+
| `message:received` | `MessageReceivedEvent` | `msg`, `threadId`, `user` |
|
|
408
|
+
| `message:sending` | `MessageSendingEvent` | `msg`, `threadId`, `text`, `channelId` |
|
|
409
|
+
| `message:sent` | `MessageSentEvent` | `msg`, `threadId`, `text`, `messageId` (first chunk) |
|
|
410
|
+
| `agent:before_run` | `AgentBeforeRunEvent` | `msg`, `threadId`, `agent` (agent name) |
|
|
411
|
+
| `agent:after_run` | `AgentAfterRunEvent` | `msg`, `threadId`, `output` |
|
|
412
|
+
| `agent:error` | `AgentErrorEvent` | `msg`, `threadId`, `error` (always an `Error`) |
|
|
413
|
+
| `session:created` | `SessionCreatedEvent` | `session`, `threadId` |
|
|
414
|
+
| `session:compacted` | `SessionCompactedEvent` | `threadId`, `result` (`CompactionResult`) |
|
|
415
|
+
| `stream:started` | `StreamStartedEvent` | `msg`, `threadId` |
|
|
416
|
+
| `stream:finished` | `StreamFinishedEvent` | `msg`, `threadId`, `messageIds` |
|
|
417
|
+
| `approval:requested` | `ApprovalRequestedEvent` | `msg`, `threadId`, `userId`, `approvals` |
|
|
418
|
+
| `approval:resolved` | `ApprovalResolvedEvent` | `msg`, `threadId`, `userId`, `decision`, `approvals?`, `superseded` |
|
|
419
|
+
|
|
420
|
+
A handler typed `HookHandler` (`unknown` payload) is still accepted for any hook. Errors in one handler don't affect others (they are logged). Unsubscribe with `hooks.off(name, handler)`. Docs: [Lifecycle Hooks](https://cogitator.app/docs/channels/gateway#lifecycle-hooks).
|
|
382
421
|
|
|
383
422
|
## Media & STT
|
|
384
423
|
|
|
385
|
-
|
|
424
|
+
Attachments are processed when the Gateway has a `mediaProcessor`; without one only the message text reaches the agent. `RuntimeBuilder` always wires one and picks the STT provider from env vars (highest priority first):
|
|
386
425
|
|
|
387
|
-
1. **Deepgram** —
|
|
388
|
-
2. **Groq** —
|
|
389
|
-
3. **OpenAI** —
|
|
390
|
-
4. **Local Whisper** —
|
|
426
|
+
1. **Deepgram** — `DEEPGRAM_API_KEY` → `DeepgramSttProvider` (`nova-3`)
|
|
427
|
+
2. **Groq** — `GROQ_API_KEY` → `GroqSttProvider` (`whisper-large-v3`)
|
|
428
|
+
3. **OpenAI** — `OPENAI_API_KEY` → `OpenAISttProvider` (`gpt-transcribe`)
|
|
429
|
+
4. **Local Whisper** — no key: `LocalWhisper` (`Xenova/whisper-tiny`, ~75MB, offline). It transcribes only once the model is downloaded; until then the agent gets a `download_stt_model` tool (`createWhisperDownloadTool`) and asks the user before downloading.
|
|
391
430
|
|
|
392
431
|
Images are passed to the LLM as vision input if the model supports it. Attachments that arrive only as URLs (Discord) are downloaded (default cap 25 MiB). Text-like files (`.md`, `.json`, `text/*`, …) are inlined into the prompt; other files and videos are announced to the model.
|
|
393
432
|
|
|
@@ -401,7 +440,7 @@ const media = new MediaProcessor(new LocalWhisper(), () => true, new GroqSttProv
|
|
|
401
440
|
const gateway = new Gateway({ /* ... */ mediaProcessor: media });
|
|
402
441
|
```
|
|
403
442
|
|
|
404
|
-
Local Whisper decodes OGG/Opus and WAV (8/16/24/32-bit, any channel count); use a cloud provider for other formats.
|
|
443
|
+
Local Whisper decodes OGG/Opus and WAV (8/16/24/32-bit, any channel count); use a cloud provider for other formats. Custom providers implement `SttProvider` (`transcribe(buffer, mimeType)`).
|
|
405
444
|
|
|
406
445
|
## Scheduler
|
|
407
446
|
|
|
@@ -421,11 +460,14 @@ const scheduler = new HeartbeatScheduler(store, {
|
|
|
421
460
|
console.log(`Job ${entry.id}: ${status} (${durationMs}ms)`);
|
|
422
461
|
},
|
|
423
462
|
onError: (err, entry) => console.error('scheduler', entry?.id, err.message),
|
|
463
|
+
onClaimLost: (entry) => console.warn('scheduler: claim lost', entry.id),
|
|
424
464
|
});
|
|
425
465
|
scheduler.start();
|
|
426
466
|
```
|
|
427
467
|
|
|
428
|
-
Schedule types: `cron` (recurring, next fire computed with `cron-parser`, honors `timezone`), `recurring` (interval), `fixed` (one-shot). Consecutive failures are carried across reschedules; after `maxRetries` the job is skipped until `enableJob()`.
|
|
468
|
+
Schedule types: `cron` (recurring, next fire computed with `cron-parser`, honors `timezone`), `recurring` (interval), `fixed` (one-shot). Consecutive failures are carried across reschedules; after `maxRetries` the job is skipped until `enableJob()`. A fired job reaches `onFire` as a message on the `channel` / `channelId` / `userId` stored in its `metadata`, and `staggerMs` adds a random delay before the first poll. Docs: [Scheduler](https://cogitator.app/docs/channels/scheduler).
|
|
469
|
+
|
|
470
|
+
With a store that claims timers (`claimTtl` / `renew` / `release`, e.g. the Redis/Postgres timer stores of `@cogitator-ai/workflows`), the claim is renewed right before a task fires and every `claimTtl / 3` while it runs; `onClaimLost(entry)` reports a claim that is gone (the task is skipped before firing, or another worker may fire it too). Claims are released for disabled tasks, tasks over `maxRetries` and overdue tasks not reached before `stop()`.
|
|
429
471
|
|
|
430
472
|
```typescript
|
|
431
473
|
await scheduler.listJobs();
|
|
@@ -436,24 +478,43 @@ await scheduler.cancelJob(id);
|
|
|
436
478
|
|
|
437
479
|
## RuntimeBuilder (YAML assistants)
|
|
438
480
|
|
|
439
|
-
`RuntimeBuilder` turns
|
|
481
|
+
`RuntimeBuilder` turns an assistant config (the shape of `cogitator.yml`) into a running assistant — this is what `cogitator up` uses. It takes the config as written (`AssistantConfigInput`) and applies `AssistantConfigSchema` itself, so a config built in code only sets what it needs (defaults fill `memory.autoExtract`, `memory.knowledgeGraph`, the security policies, …); an invalid config throws when the builder is created.
|
|
440
482
|
|
|
441
483
|
```typescript
|
|
442
|
-
import {
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
484
|
+
import { RuntimeBuilder } from '@cogitator-ai/channels';
|
|
485
|
+
|
|
486
|
+
const runtime = await new RuntimeBuilder(
|
|
487
|
+
{
|
|
488
|
+
name: 'Jarvis',
|
|
489
|
+
personality: 'You are a personal assistant for Alice.',
|
|
490
|
+
llm: { provider: 'google', model: 'gemini-3.8-flash' },
|
|
491
|
+
channels: { telegram: { ownerIds: ['123'] } },
|
|
492
|
+
capabilities: { scheduler: true },
|
|
493
|
+
},
|
|
494
|
+
process.env,
|
|
495
|
+
{
|
|
496
|
+
onRestart: () => process.exit(78), // default behaviour (RESTART_EXIT_CODE)
|
|
497
|
+
hooks, // HookRegistry passed to the gateway
|
|
498
|
+
approvals: { format: (approvals) => `Run ${approvals.map((a) => a.toolName).join(', ')}?` },
|
|
499
|
+
}
|
|
500
|
+
).build();
|
|
448
501
|
await runtime.gateway.start(); // the scheduler (if enabled) is already running
|
|
449
502
|
// ...
|
|
450
503
|
await runtime.cleanup();
|
|
451
504
|
```
|
|
452
505
|
|
|
453
|
-
|
|
506
|
+
`hooks` and `approvals` are passed to the gateway; `approvals` is merged over the config's `approvals` block (`approveWords`, `denyWords`, `notAllowedMessage`) and wins, so use it for what YAML cannot hold, such as a custom `format`.
|
|
507
|
+
|
|
508
|
+
For a parsed YAML file, validate it first with `new RuntimeBuilder(AssistantConfigSchema.parse(yamlObject), process.env)` (the schema is exported, along with the `AssistantConfigInput` / `AssistantConfigOutput` types).
|
|
509
|
+
|
|
510
|
+
- **Channels:** the terminal REPL plus `channels.telegram` (`TG_TOKEN` or `TELEGRAM_TOKEN`), `channels.discord` (`DISCORD_TOKEN`) and `channels.slack` (`SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, optional `SLACK_APP_TOKEN` / `SLACK_PORT`). A configured channel without its token is skipped with a warning. `channels.whatsapp` (`{ ownerIds?, sessionPath? }`; owner ids are phone numbers without `@s.whatsapp.net`, the session defaults to `~/.cogitator/whatsapp-session`) needs `@whiskeysockets/baileys`. `channels.webchat` (`{ port?, path? }`, default `8080` and `/ws`, no `ownerIds`) needs `ws` and starts only with `WEBCHAT_TOKEN` set; clients connect with `?token=<WEBCHAT_TOKEN>`.
|
|
511
|
+
|
|
512
|
+
- **Memory:** `memory.adapter: sqlite` (default, `memory.path`, default `~/.cogitator/memory.db`) or `postgres` (`memory.connectionString`, `DATABASE_URL` or `POSTGRES_URL`; requires `pg`). Core facts always live in the SQLite file. The knowledge graph (`memory.knowledgeGraph`) and auto-extraction (`memory.autoExtract`) are on by default; `memory.compaction.threshold` enables history compaction.
|
|
454
513
|
- **Fresh context:** the gateway builds an agent per message — instructions (current date/time and known user facts) and the `/model` override are resolved each time. `runtime.agent` is the base agent.
|
|
455
514
|
- **Owner commands wired:** `/status`, `/sessions`, `/users`, `/compact`, `/model` (global or per user), `/restart`.
|
|
456
|
-
- **Security:** `fileSystem.paths` is enforced — file tools refuse paths outside the allowed roots (symlinks resolved). Self-config tools (`config_*`, `env_*`) only work for channel owners and the local terminal.
|
|
515
|
+
- **Security:** `fileSystem.paths` is enforced — file tools refuse paths outside the allowed roots (symlinks resolved). Self-config tools (`config_*`, `env_*`) only work for channel owners and the local terminal. `security` sets the DM/group policy, allowlists and command access.
|
|
516
|
+
|
|
517
|
+
Docs: [Wizard Setup](https://cogitator.app/docs/channels/wizard), [Smart Memory](https://cogitator.app/docs/channels/smart-memory).
|
|
457
518
|
|
|
458
519
|
## Environment Variables
|
|
459
520
|
|
|
@@ -462,7 +523,7 @@ await runtime.cleanup();
|
|
|
462
523
|
GOOGLE_API_KEY=... # or ANTHROPIC_API_KEY, OPENAI_API_KEY
|
|
463
524
|
|
|
464
525
|
# Telegram
|
|
465
|
-
TG_TOKEN=7204891735:AAHr...
|
|
526
|
+
TG_TOKEN=7204891735:AAHr... # or TELEGRAM_TOKEN
|
|
466
527
|
|
|
467
528
|
# Discord
|
|
468
529
|
DISCORD_TOKEN=MTI...
|
|
@@ -470,15 +531,16 @@ DISCORD_TOKEN=MTI...
|
|
|
470
531
|
# Slack (3 tokens)
|
|
471
532
|
SLACK_BOT_TOKEN=xoxb-...
|
|
472
533
|
SLACK_SIGNING_SECRET=...
|
|
473
|
-
SLACK_APP_TOKEN=xapp-...
|
|
534
|
+
SLACK_APP_TOKEN=xapp-... # omit for HTTP mode
|
|
535
|
+
SLACK_PORT=3000 # HTTP mode port
|
|
474
536
|
|
|
475
537
|
# WhatsApp — no token, uses QR pairing
|
|
476
538
|
|
|
477
539
|
# Postgres memory (memory.adapter: postgres)
|
|
478
|
-
DATABASE_URL=postgres://user:pass@localhost:5432/cogitator
|
|
540
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/cogitator # or POSTGRES_URL
|
|
479
541
|
|
|
480
542
|
# WebChat
|
|
481
|
-
|
|
543
|
+
WEBCHAT_TOKEN=your-secret # required by channels.webchat in cogitator.yml
|
|
482
544
|
|
|
483
545
|
# STT (optional, for voice messages — pick one)
|
|
484
546
|
DEEPGRAM_API_KEY=... # highest priority
|
package/dist/approvals.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApprovalRequestedEvent, ApprovalResolvedEvent, ToolApprovalDecision, ToolApprovalRequest } from '@cogitator-ai/types';
|
|
2
|
+
export type { ApprovalRequestedEvent, ApprovalResolvedEvent };
|
|
2
3
|
export interface ApprovalReplyWords {
|
|
3
4
|
approveWords: readonly string[];
|
|
4
5
|
denyWords: readonly string[];
|
|
@@ -16,26 +17,6 @@ export interface GatewayApprovalsConfig {
|
|
|
16
17
|
/** Sent when someone other than the user who started the run answers it */
|
|
17
18
|
notAllowedMessage?: string;
|
|
18
19
|
}
|
|
19
|
-
/** Payload of the `approval:requested` hook: a run paused and the chat was asked */
|
|
20
|
-
export interface ApprovalRequestedEvent {
|
|
21
|
-
msg: ChannelMessage;
|
|
22
|
-
threadId: string;
|
|
23
|
-
userId: string;
|
|
24
|
-
approvals: readonly ToolApprovalRequest[];
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Payload of the `approval:resolved` hook. `superseded` is true when the user
|
|
28
|
-
* sent a new message instead of answering, so the runtime declined the calls.
|
|
29
|
-
* `approvals` is missing when the pause predates this process (e.g. a restart).
|
|
30
|
-
*/
|
|
31
|
-
export interface ApprovalResolvedEvent {
|
|
32
|
-
msg: ChannelMessage;
|
|
33
|
-
threadId: string;
|
|
34
|
-
userId: string;
|
|
35
|
-
decision: ToolApprovalDecision;
|
|
36
|
-
approvals?: readonly ToolApprovalRequest[];
|
|
37
|
-
superseded: boolean;
|
|
38
|
-
}
|
|
39
20
|
export declare const DEFAULT_APPROVE_WORDS: readonly string[];
|
|
40
21
|
export declare const DEFAULT_DENY_WORDS: readonly string[];
|
|
41
22
|
export declare const DEFAULT_NOT_ALLOWED_MESSAGE = "Only the person who made this request can approve or deny it.";
|
package/dist/approvals.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,
|
|
1
|
+
{"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,CAAC;AAE9D,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,EAAE,KAAK,EAAE,kBAAkB,KAAK,MAAM,CAAC;IAC1F,oFAAoF;IACpF,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,sFAAsF;IACtF,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAwC,CAAC;AAC5F,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAAsC,CAAC;AACvF,eAAO,MAAM,2BAA2B,kEACyB,CAAC;AAuBlE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,kBAAkB,GACxB,oBAAoB,GAAG,IAAI,CAU7B;AAoBD,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,SAAS,mBAAmB,EAAE,EACzC,KAAK,EAAE,kBAAkB,GACxB,MAAM,CAqBR"}
|
package/dist/approvals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,6 +21,14 @@ export declare const AssistantConfigSchema: z.ZodObject<{
|
|
|
21
21
|
slack: z.ZodOptional<z.ZodObject<{
|
|
22
22
|
ownerIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
23
|
}, z.core.$strip>>;
|
|
24
|
+
whatsapp: z.ZodOptional<z.ZodObject<{
|
|
25
|
+
ownerIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
+
sessionPath: z.ZodOptional<z.ZodString>;
|
|
27
|
+
}, z.core.$strip>>;
|
|
28
|
+
webchat: z.ZodOptional<z.ZodObject<{
|
|
29
|
+
port: z.ZodDefault<z.ZodNumber>;
|
|
30
|
+
path: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
24
32
|
}, z.core.$strip>>>;
|
|
25
33
|
capabilities: z.ZodDefault<z.ZodOptional<z.ZodObject<{
|
|
26
34
|
webSearch: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -80,6 +88,11 @@ export declare const AssistantConfigSchema: z.ZodObject<{
|
|
|
80
88
|
publicCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
81
89
|
}, z.core.$strip>>;
|
|
82
90
|
}, z.core.$strip>>;
|
|
91
|
+
approvals: z.ZodOptional<z.ZodObject<{
|
|
92
|
+
approveWords: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
93
|
+
denyWords: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
94
|
+
notAllowedMessage: z.ZodOptional<z.ZodString>;
|
|
95
|
+
}, z.core.$strip>>;
|
|
83
96
|
stream: z.ZodOptional<z.ZodObject<{
|
|
84
97
|
flushInterval: z.ZodDefault<z.ZodNumber>;
|
|
85
98
|
minChunkSize: z.ZodDefault<z.ZodNumber>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-config.d.ts","sourceRoot":"","sources":["../src/assistant-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"assistant-config.d.ts","sourceRoot":"","sources":["../src/assistant-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA0FxB,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BhC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACzE,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/assistant-config.js
CHANGED
|
@@ -4,11 +4,31 @@ const McpServerSchema = z.object({
|
|
|
4
4
|
args: z.array(z.string()),
|
|
5
5
|
env: z.record(z.string(), z.string()).optional(),
|
|
6
6
|
});
|
|
7
|
+
const OwnerIdsSchema = z.array(z.string()).optional();
|
|
7
8
|
const AssistantChannelsSchema = z.object({
|
|
8
|
-
telegram: z.object({ ownerIds:
|
|
9
|
-
discord: z.object({ ownerIds:
|
|
10
|
-
slack: z.object({ ownerIds:
|
|
9
|
+
telegram: z.object({ ownerIds: OwnerIdsSchema }).optional(),
|
|
10
|
+
discord: z.object({ ownerIds: OwnerIdsSchema }).optional(),
|
|
11
|
+
slack: z.object({ ownerIds: OwnerIdsSchema }).optional(),
|
|
12
|
+
whatsapp: z
|
|
13
|
+
.object({
|
|
14
|
+
ownerIds: OwnerIdsSchema,
|
|
15
|
+
sessionPath: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.optional(),
|
|
18
|
+
webchat: z
|
|
19
|
+
.object({
|
|
20
|
+
port: z.number().int().positive().default(8080),
|
|
21
|
+
path: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
.optional(),
|
|
11
24
|
});
|
|
25
|
+
const ApprovalsSchema = z
|
|
26
|
+
.object({
|
|
27
|
+
approveWords: z.array(z.string()).optional(),
|
|
28
|
+
denyWords: z.array(z.string()).optional(),
|
|
29
|
+
notAllowedMessage: z.string().optional(),
|
|
30
|
+
})
|
|
31
|
+
.optional();
|
|
12
32
|
const AssistantCapabilitiesSchema = z.object({
|
|
13
33
|
webSearch: z.boolean().optional(),
|
|
14
34
|
fileSystem: z.object({ paths: z.array(z.string()) }).optional(),
|
|
@@ -75,6 +95,7 @@ export const AssistantConfigSchema = z.object({
|
|
|
75
95
|
knowledgeGraph: true,
|
|
76
96
|
}),
|
|
77
97
|
security: SecuritySchema,
|
|
98
|
+
approvals: ApprovalsSchema,
|
|
78
99
|
stream: z
|
|
79
100
|
.object({
|
|
80
101
|
flushInterval: z.number().default(600),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assistant-config.js","sourceRoot":"","sources":["../src/assistant-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACzB,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;CACjD,CAAC,CAAC;AAEH,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"assistant-config.js","sourceRoot":"","sources":["../src/assistant-config.ts"],"names":[],"mappings":"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"}
|
package/dist/gateway.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAEZ,cAAc,EAKd,YAAY,EASb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAY9D,OAAO,KAAK,EAIV,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../src/gateway.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAEZ,cAAc,EAKd,YAAY,EASb,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAM3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAY9D,OAAO,KAAK,EAIV,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAgDD,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IAC3C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAY;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+B;IAC9D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmC;IACjE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsE;IAC9F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAAsB;gBAEjC,MAAM,EAAE,iBAAiB;IA4B/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAetB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAW3B,IAAI,KAAK,IAAI,YAAY,CASxB;IAED,WAAW,IAAI,kBAAkB,EAAE;IAM7B,aAAa,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD;;;OAGG;IACG,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC;YAoBvD,eAAe;YAWf,cAAc;YAQd,QAAQ;IAQtB,OAAO,CAAC,kBAAkB;YAQZ,aAAa;YAab,cAAc;YAmBd,kBAAkB;YAkBlB,cAAc;IA4E5B;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;YAUX,UAAU;YA+BV,eAAe;YA8Cf,cAAc;YAed,gBAAgB;YAmBhB,OAAO;YAeP,YAAY;IA6B1B,OAAO,CAAC,eAAe;YAqBT,WAAW;YAkBX,SAAS;YAwBT,SAAS;YA+BT,YAAY;IAuE1B,OAAO,CAAC,WAAW;YAOL,YAAY;IAQ1B,OAAO,CAAC,uBAAuB;IAmB/B,OAAO,CAAC,iBAAiB;CAqB1B"}
|
package/dist/gateway.js
CHANGED
|
@@ -23,6 +23,9 @@ function isScheduled(msg) {
|
|
|
23
23
|
const raw = msg.raw;
|
|
24
24
|
return typeof raw === 'object' && raw !== null && 'scheduled' in raw && raw.scheduled === true;
|
|
25
25
|
}
|
|
26
|
+
function toError(error) {
|
|
27
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
28
|
+
}
|
|
26
29
|
function isResumeRejection(error) {
|
|
27
30
|
return (CogitatorError.isCogitatorError(error) &&
|
|
28
31
|
(error.code === ErrorCode.RUN_NOT_PAUSED || error.code === ErrorCode.THREAD_ACCESS_DENIED));
|
|
@@ -190,7 +193,7 @@ export class Gateway {
|
|
|
190
193
|
catch (error) {
|
|
191
194
|
if (signal?.aborted)
|
|
192
195
|
return;
|
|
193
|
-
this.config.onError?.(
|
|
196
|
+
this.config.onError?.(toError(error), msg);
|
|
194
197
|
}
|
|
195
198
|
finally {
|
|
196
199
|
const remaining = (this.inFlight.get(threadId) ?? 1) - 1;
|
|
@@ -242,7 +245,7 @@ export class Gateway {
|
|
|
242
245
|
await this.compactIfNeeded(threadId, agent);
|
|
243
246
|
}
|
|
244
247
|
catch (error) {
|
|
245
|
-
this.config.onError?.(
|
|
248
|
+
this.config.onError?.(toError(error), msg);
|
|
246
249
|
}
|
|
247
250
|
}
|
|
248
251
|
const tracker = this.config.reactions?.enabled && channel.setReaction
|
|
@@ -446,7 +449,7 @@ export class Gateway {
|
|
|
446
449
|
}
|
|
447
450
|
catch (error) {
|
|
448
451
|
if (!isResumeRejection(error)) {
|
|
449
|
-
await this.hooks?.emit('agent:error', { msg, threadId, error });
|
|
452
|
+
await this.hooks?.emit('agent:error', { msg, threadId, error: toError(error) });
|
|
450
453
|
}
|
|
451
454
|
throw error;
|
|
452
455
|
}
|
|
@@ -482,7 +485,7 @@ export class Gateway {
|
|
|
482
485
|
catch (error) {
|
|
483
486
|
await stream.abort();
|
|
484
487
|
if (!isResumeRejection(error)) {
|
|
485
|
-
await this.hooks?.emit('agent:error', { msg, threadId, error });
|
|
488
|
+
await this.hooks?.emit('agent:error', { msg, threadId, error: toError(error) });
|
|
486
489
|
}
|
|
487
490
|
throw error;
|
|
488
491
|
}
|