@usejarvis/brain 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.
Files changed (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,329 @@
1
+ # J.A.R.V.I.S. Communication Layer
2
+
3
+ The communication layer provides multi-channel messaging, WebSocket-based real-time communication, LLM response streaming, and voice I/O capabilities.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ comms/
9
+ ├── websocket.ts # Local WebSocket server using Bun.serve()
10
+ ├── streaming.ts # LLM stream relay to WebSocket clients
11
+ ├── voice.ts # STT/TTS provider interfaces and stubs
12
+ ├── channels/ # Multi-platform messaging adapters
13
+ │ ├── telegram.ts # ✅ Telegram Bot API (fully functional)
14
+ │ ├── whatsapp.ts # 🚧 WhatsApp Business API (stub)
15
+ │ ├── discord.ts # 🚧 Discord Bot (stub)
16
+ │ └── signal.ts # 🚧 Signal CLI (stub)
17
+ └── index.ts # ChannelManager and exports
18
+ ```
19
+
20
+ ## Core Components
21
+
22
+ ### 1. WebSocket Server
23
+
24
+ Real-time bidirectional communication with web clients.
25
+
26
+ ```typescript
27
+ import { WebSocketServer } from './comms';
28
+
29
+ const wsServer = new WebSocketServer(3142);
30
+
31
+ wsServer.setHandler({
32
+ async onMessage(msg) {
33
+ console.log('Received:', msg.type, msg.payload);
34
+ return { type: 'status', payload: 'Acknowledged', timestamp: Date.now() };
35
+ },
36
+ onConnect() {
37
+ console.log('Client connected');
38
+ },
39
+ onDisconnect() {
40
+ console.log('Client disconnected');
41
+ },
42
+ });
43
+
44
+ wsServer.start();
45
+ ```
46
+
47
+ **Endpoints:**
48
+ - `ws://localhost:3142/ws` - WebSocket connection
49
+ - `http://localhost:3142/health` - Health check
50
+ - `http://localhost:3142/` - Server info
51
+
52
+ **Message Types:**
53
+ ```typescript
54
+ type WSMessage = {
55
+ type: 'chat' | 'command' | 'status' | 'stream' | 'error';
56
+ payload: unknown;
57
+ id?: string;
58
+ timestamp: number;
59
+ };
60
+ ```
61
+
62
+ ### 2. Stream Relay
63
+
64
+ Relays LLM streaming responses to connected WebSocket clients in real-time.
65
+
66
+ ```typescript
67
+ import { StreamRelay, WebSocketServer } from './comms';
68
+
69
+ const wsServer = new WebSocketServer();
70
+ wsServer.start();
71
+
72
+ const relay = new StreamRelay(wsServer);
73
+
74
+ // Relay LLM stream to all connected clients
75
+ const stream = llmProvider.generateStream(prompt);
76
+ const fullResponse = await relay.relayStream(stream, 'request-123');
77
+ ```
78
+
79
+ Clients receive incremental updates:
80
+ ```json
81
+ {
82
+ "type": "stream",
83
+ "payload": {
84
+ "text": "chunk of text",
85
+ "requestId": "request-123",
86
+ "accumulated": "full text so far"
87
+ },
88
+ "timestamp": 1708645200000
89
+ }
90
+ ```
91
+
92
+ ### 3. Channel Manager
93
+
94
+ Unified interface for managing multiple messaging platforms.
95
+
96
+ ```typescript
97
+ import { ChannelManager, TelegramAdapter } from './comms';
98
+
99
+ const manager = new ChannelManager();
100
+
101
+ // Register Telegram
102
+ const telegram = new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN!);
103
+ manager.register(telegram);
104
+
105
+ // Set unified message handler
106
+ manager.setHandler(async (message) => {
107
+ console.log(`[${message.channel}] ${message.from}: ${message.text}`);
108
+ return `Received: ${message.text}`;
109
+ });
110
+
111
+ // Connect all channels
112
+ await manager.connectAll();
113
+
114
+ // Check status
115
+ console.log(manager.getStatus()); // { telegram: true }
116
+ ```
117
+
118
+ ### 4. Telegram Adapter (Functional)
119
+
120
+ Full implementation using Telegram Bot API with long polling.
121
+
122
+ ```typescript
123
+ import { TelegramAdapter } from './comms/channels/telegram';
124
+
125
+ const telegram = new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN!);
126
+
127
+ telegram.onMessage(async (message) => {
128
+ console.log(`Message from ${message.from}: ${message.text}`);
129
+ return `Echo: ${message.text}`;
130
+ });
131
+
132
+ await telegram.connect();
133
+
134
+ // Send message
135
+ await telegram.sendMessage('CHAT_ID', 'Hello from J.A.R.V.I.S.!');
136
+ ```
137
+
138
+ **Setup:**
139
+ 1. Create bot via [@BotFather](https://t.me/botfather)
140
+ 2. Get bot token
141
+ 3. Set `TELEGRAM_BOT_TOKEN` environment variable
142
+
143
+ ### 5. Voice I/O (Stubs)
144
+
145
+ Speech-to-Text and Text-to-Speech provider interfaces.
146
+
147
+ ```typescript
148
+ import { WhisperSTT, LocalTTS } from './comms';
149
+
150
+ // STT - requires whisper.cpp setup
151
+ const stt = new WhisperSTT('http://localhost:8080');
152
+ // const text = await stt.transcribe(audioBuffer);
153
+
154
+ // TTS - requires ElevenLabs API or local TTS
155
+ const tts = new LocalTTS({ provider: 'elevenlabs', apiKey: '...' });
156
+ // const audio = await tts.synthesize('Hello world');
157
+ ```
158
+
159
+ ## Message Flow
160
+
161
+ ### Incoming Message Flow
162
+ ```
163
+ Telegram/Discord/etc
164
+
165
+ ChannelAdapter.onMessage()
166
+
167
+ ChannelHandler (unified)
168
+
169
+ Business logic (in daemon)
170
+
171
+ WebSocketServer.broadcast()
172
+
173
+ All connected web clients
174
+ ```
175
+
176
+ ### LLM Streaming Flow
177
+ ```
178
+ LLM Provider stream
179
+
180
+ StreamRelay.relayStream()
181
+
182
+ WebSocketServer.broadcast()
183
+
184
+ Real-time updates to clients
185
+ ```
186
+
187
+ ## Running Examples
188
+
189
+ ### Start WebSocket Server Only
190
+ ```bash
191
+ bun run src/comms/example.ts
192
+ ```
193
+
194
+ ### With Telegram Integration
195
+ ```bash
196
+ export TELEGRAM_BOT_TOKEN="your_bot_token"
197
+ bun run src/comms/example.ts
198
+ ```
199
+
200
+ ### Run Tests
201
+ ```bash
202
+ bun test src/comms/websocket.test.ts
203
+ bun test src/comms/channels.test.ts
204
+ ```
205
+
206
+ ## Environment Variables
207
+
208
+ ```bash
209
+ # Required for Telegram
210
+ TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
211
+
212
+ # Future integrations
213
+ WHATSAPP_PHONE_ID=...
214
+ WHATSAPP_ACCESS_TOKEN=...
215
+ DISCORD_BOT_TOKEN=...
216
+ SIGNAL_PHONE=+1234567890
217
+ ELEVENLABS_API_KEY=...
218
+ ```
219
+
220
+ ## Channel Adapter Interface
221
+
222
+ All channel adapters implement this interface:
223
+
224
+ ```typescript
225
+ interface ChannelAdapter {
226
+ name: string;
227
+ connect(): Promise<void>;
228
+ disconnect(): Promise<void>;
229
+ sendMessage(to: string, text: string): Promise<void>;
230
+ onMessage(handler: ChannelHandler): void;
231
+ isConnected(): boolean;
232
+ }
233
+
234
+ type ChannelMessage = {
235
+ id: string;
236
+ channel: string;
237
+ from: string;
238
+ text: string;
239
+ timestamp: number;
240
+ metadata: Record<string, unknown>;
241
+ };
242
+
243
+ type ChannelHandler = (message: ChannelMessage) => Promise<string>;
244
+ ```
245
+
246
+ ## Future Channel Implementations
247
+
248
+ ### WhatsApp
249
+ - Requires WhatsApp Business API setup
250
+ - Webhook-based architecture
251
+ - See: https://developers.facebook.com/docs/whatsapp/cloud-api
252
+
253
+ ### Discord
254
+ - Use Discord Gateway WebSocket or discord.js
255
+ - Support slash commands
256
+ - See: https://discord.com/developers/docs
257
+
258
+ ### Signal
259
+ - Requires signal-cli in daemon mode
260
+ - Local installation needed
261
+ - See: https://github.com/AsamK/signal-cli
262
+
263
+ ## Integration with J.A.R.V.I.S. Daemon
264
+
265
+ ```typescript
266
+ // In daemon/index.ts
267
+ import { WebSocketServer, ChannelManager, TelegramAdapter, StreamRelay } from './comms';
268
+ import { LLMRouter } from './llm';
269
+
270
+ const wsServer = new WebSocketServer();
271
+ const channels = new ChannelManager();
272
+ const streamRelay = new StreamRelay(wsServer);
273
+ const llmRouter = new LLMRouter();
274
+
275
+ // Unified message handler
276
+ channels.setHandler(async (message) => {
277
+ // Route to appropriate LLM provider
278
+ const stream = await llmRouter.route(message.text, { stream: true });
279
+
280
+ // Relay stream to WebSocket clients
281
+ const response = await streamRelay.relayStream(stream, message.id);
282
+
283
+ // Return response to original channel
284
+ return response;
285
+ });
286
+
287
+ // Start everything
288
+ wsServer.start();
289
+ if (process.env.TELEGRAM_BOT_TOKEN) {
290
+ channels.register(new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN));
291
+ }
292
+ await channels.connectAll();
293
+ ```
294
+
295
+ ## Performance Considerations
296
+
297
+ - **WebSocket**: Bun's native WebSocket support is extremely fast
298
+ - **Telegram Polling**: Uses long polling (30s timeout) to minimize requests
299
+ - **Stream Relay**: Zero-copy broadcast to multiple clients
300
+ - **Memory**: Each WebSocket connection uses ~1KB of memory
301
+
302
+ ## Security Notes
303
+
304
+ - **WebSocket**: Currently no authentication - add JWT or session tokens for production
305
+ - **Channel Tokens**: Store in environment variables, never commit
306
+ - **Rate Limiting**: Implement on message handlers to prevent abuse
307
+ - **Input Validation**: Always sanitize user input before processing
308
+
309
+ ## Troubleshooting
310
+
311
+ ### WebSocket Connection Failed
312
+ - Check port 3142 is not in use: `lsof -i :3142`
313
+ - Verify firewall allows connections
314
+ - Check CORS if connecting from browser
315
+
316
+ ### Telegram Not Receiving Messages
317
+ - Verify bot token is correct
318
+ - Check bot is not blocked
319
+ - Ensure polling is active: check logs for "Starting polling"
320
+ - Test with `/health` endpoint
321
+
322
+ ### Stream Not Broadcasting
323
+ - Verify WebSocket clients are connected: check `/health`
324
+ - Ensure LLM stream implements AsyncIterable<LLMStreamEvent>
325
+ - Check client WebSocket listeners are set up
326
+
327
+ ## API Reference
328
+
329
+ See inline TypeScript documentation in each module for detailed API reference.
@@ -0,0 +1,48 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <!--
5
+ Auth error page — if ?token= is inside the hash (e.g. /#/settings?token=xxx),
6
+ move it into the query string so the server can set the cookie via Set-Cookie + 302.
7
+ -->
8
+ <script>
9
+ (function () {
10
+ var h = location.hash;
11
+ var qi = h.indexOf('?');
12
+ if (qi !== -1) {
13
+ var hp = new URLSearchParams(h.slice(qi));
14
+ var t = hp.get('token');
15
+ if (t) {
16
+ hp.delete('token');
17
+ var cleanHash = h.slice(0, qi);
18
+ var remaining = hp.toString();
19
+ if (remaining) cleanHash += '?' + remaining;
20
+ location.replace(location.pathname + '?token=' + encodeURIComponent(t) + cleanHash);
21
+ }
22
+ }
23
+ })();
24
+ </script>
25
+ <title>Unauthorized</title>
26
+ <style>
27
+ body {
28
+ font-family: system-ui;
29
+ display: flex;
30
+ justify-content: center;
31
+ align-items: center;
32
+ height: 100vh;
33
+ margin: 0;
34
+ background: #111;
35
+ color: #ccc;
36
+ }
37
+ .box { text-align: center }
38
+ h1 { font-size: 4rem; margin: 0; color: #666 }
39
+ p { margin-top: 1rem }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div class="box">
44
+ <h1>401</h1>
45
+ <p>Access requires a valid token.</p>
46
+ </div>
47
+ </body>
48
+ </html>
@@ -0,0 +1,228 @@
1
+ import { Client, GatewayIntentBits, Partials, type Message } from 'discord.js';
2
+ import type { ChannelAdapter, ChannelHandler, ChannelMessage } from './telegram.ts';
3
+ import type { STTProvider } from '../voice.ts';
4
+
5
+ export class DiscordAdapter implements ChannelAdapter {
6
+ name = 'discord';
7
+ private token: string;
8
+ private handler: ChannelHandler | null = null;
9
+ private connected: boolean = false;
10
+ private client: Client | null = null;
11
+ private allowedUsers: string[];
12
+ private guildId: string | null;
13
+ private sttProvider: STTProvider | null;
14
+
15
+ constructor(token: string, opts?: {
16
+ allowedUsers?: string[];
17
+ guildId?: string;
18
+ sttProvider?: STTProvider;
19
+ }) {
20
+ this.token = token;
21
+ this.allowedUsers = opts?.allowedUsers ?? [];
22
+ this.guildId = opts?.guildId ?? null;
23
+ this.sttProvider = opts?.sttProvider ?? null;
24
+ }
25
+
26
+ setSTTProvider(provider: STTProvider): void {
27
+ this.sttProvider = provider;
28
+ }
29
+
30
+ async connect(): Promise<void> {
31
+ if (this.connected) {
32
+ console.warn('[DiscordAdapter] Already connected');
33
+ return;
34
+ }
35
+
36
+ this.client = new Client({
37
+ intents: [
38
+ GatewayIntentBits.Guilds,
39
+ GatewayIntentBits.GuildMessages,
40
+ GatewayIntentBits.DirectMessages,
41
+ GatewayIntentBits.MessageContent,
42
+ ],
43
+ partials: [Partials.Channel],
44
+ });
45
+
46
+ // Wait for ready event
47
+ let loginTimeout: ReturnType<typeof setTimeout> | null = null;
48
+ const readyPromise = new Promise<void>((resolve, reject) => {
49
+ loginTimeout = setTimeout(() => reject(new Error('Discord login timed out')), 30000);
50
+
51
+ this.client!.once('ready', () => {
52
+ clearTimeout(loginTimeout!);
53
+ this.connected = true;
54
+ console.log(`[DiscordAdapter] Connected as: ${this.client!.user?.tag}`);
55
+ resolve();
56
+ });
57
+
58
+ this.client!.once('error', (err) => {
59
+ clearTimeout(loginTimeout!);
60
+ reject(err);
61
+ });
62
+ });
63
+
64
+ // Set up message handler
65
+ this.client.on('messageCreate', async (message: Message) => {
66
+ try {
67
+ await this.processMessage(message);
68
+ } catch (err) {
69
+ console.error('[DiscordAdapter] Unhandled error in processMessage:', err);
70
+ }
71
+ });
72
+
73
+ try {
74
+ await this.client.login(this.token);
75
+ } catch (err) {
76
+ // Clear the ready timeout to prevent unhandled rejection
77
+ if (loginTimeout) clearTimeout(loginTimeout);
78
+ this.client.destroy();
79
+ this.client = null;
80
+ throw err;
81
+ }
82
+ await readyPromise;
83
+ }
84
+
85
+ async disconnect(): Promise<void> {
86
+ if (this.client) {
87
+ this.client.destroy();
88
+ this.client = null;
89
+ }
90
+ this.connected = false;
91
+ console.log('[DiscordAdapter] Disconnected');
92
+ }
93
+
94
+ async sendMessage(channelId: string, text: string): Promise<void> {
95
+ if (!this.client) throw new Error('Discord not connected');
96
+
97
+ const channel = await this.client.channels.fetch(channelId);
98
+ if (!channel || !channel.isTextBased()) {
99
+ throw new Error(`Invalid or non-text channel: ${channelId}`);
100
+ }
101
+
102
+ const chunks = splitMessage(text, 2000);
103
+ for (const chunk of chunks) {
104
+ await (channel as any).send(chunk);
105
+ }
106
+ }
107
+
108
+ onMessage(handler: ChannelHandler): void {
109
+ this.handler = handler;
110
+ }
111
+
112
+ isConnected(): boolean {
113
+ return this.connected;
114
+ }
115
+
116
+ private async processMessage(message: Message): Promise<void> {
117
+ // Ignore bot messages (including our own)
118
+ if (message.author.bot) return;
119
+ if (!this.handler) return;
120
+
121
+ // Security: check allowed users (empty = allow all)
122
+ if (this.allowedUsers.length > 0 && !this.allowedUsers.includes(message.author.id)) {
123
+ return;
124
+ }
125
+
126
+ // Security: check guild restriction
127
+ if (this.guildId && message.guildId && message.guildId !== this.guildId) {
128
+ return;
129
+ }
130
+
131
+ let text = message.content;
132
+
133
+ // Handle audio attachments via STT
134
+ const audioAttachment = message.attachments.find(a =>
135
+ a.contentType?.startsWith('audio/') ||
136
+ a.name?.endsWith('.ogg') ||
137
+ a.name?.endsWith('.mp3') ||
138
+ a.name?.endsWith('.wav') ||
139
+ a.name?.endsWith('.m4a')
140
+ );
141
+
142
+ if (audioAttachment && !text && this.sttProvider) {
143
+ try {
144
+ const resp = await fetch(audioAttachment.url);
145
+ if (!resp.ok) throw new Error(`Failed to download: ${resp.status}`);
146
+ const buffer = Buffer.from(await resp.arrayBuffer());
147
+ text = await this.sttProvider.transcribe(buffer);
148
+ console.log('[DiscordAdapter] Transcribed audio:', text.slice(0, 80));
149
+ } catch (err) {
150
+ console.error('[DiscordAdapter] STT error:', err);
151
+ await message.reply('Failed to transcribe audio. Please send text.');
152
+ return;
153
+ }
154
+ }
155
+
156
+ if (!text) return;
157
+
158
+ const channelMessage: ChannelMessage = {
159
+ id: message.id,
160
+ channel: 'discord',
161
+ from: message.author.username,
162
+ text,
163
+ timestamp: message.createdTimestamp,
164
+ metadata: {
165
+ userId: message.author.id,
166
+ channelId: message.channelId,
167
+ guildId: message.guildId,
168
+ isDM: !message.guildId,
169
+ isVoice: !!audioAttachment,
170
+ },
171
+ };
172
+
173
+ console.log('[DiscordAdapter] Message from', channelMessage.from, ':', text.slice(0, 80));
174
+
175
+ try {
176
+ // Show typing indicator
177
+ if (message.channel.isSendable()) {
178
+ await message.channel.sendTyping();
179
+ }
180
+
181
+ const response = await this.handler(channelMessage);
182
+
183
+ if (response) {
184
+ const chunks = splitMessage(response, 2000);
185
+ for (const chunk of chunks) {
186
+ await message.reply(chunk);
187
+ }
188
+ }
189
+ } catch (err) {
190
+ console.error('[DiscordAdapter] Error handling message:', err);
191
+ try {
192
+ await message.reply('Sorry, I encountered an error processing your message.');
193
+ } catch {
194
+ // Ignore send failure
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ export function splitMessage(text: string, maxLength: number): string[] {
201
+ if (text.length <= maxLength) return [text];
202
+
203
+ const chunks: string[] = [];
204
+ let remaining = text;
205
+
206
+ while (remaining.length > 0) {
207
+ if (remaining.length <= maxLength) {
208
+ chunks.push(remaining);
209
+ break;
210
+ }
211
+
212
+ // Try to split at newline
213
+ let splitIdx = remaining.lastIndexOf('\n', maxLength);
214
+ if (splitIdx < maxLength / 2) {
215
+ // Try space
216
+ splitIdx = remaining.lastIndexOf(' ', maxLength);
217
+ }
218
+ if (splitIdx < maxLength / 2) {
219
+ // Hard split
220
+ splitIdx = maxLength;
221
+ }
222
+
223
+ chunks.push(remaining.slice(0, splitIdx));
224
+ remaining = remaining.slice(splitIdx).trimStart();
225
+ }
226
+
227
+ return chunks;
228
+ }
@@ -0,0 +1,56 @@
1
+ import type { ChannelAdapter, ChannelHandler, ChannelMessage } from './telegram.ts';
2
+
3
+ export class SignalAdapter implements ChannelAdapter {
4
+ name = 'signal';
5
+ private phone: string;
6
+ private handler: ChannelHandler | null = null;
7
+ private connected: boolean = false;
8
+
9
+ constructor(config: { phone: string }) {
10
+ this.phone = config.phone;
11
+ }
12
+
13
+ async connect(): Promise<void> {
14
+ throw new Error(
15
+ 'Signal adapter not yet implemented. Requires signal-cli setup. ' +
16
+ 'Install signal-cli: https://github.com/AsamK/signal-cli'
17
+ );
18
+
19
+ // Future implementation would use signal-cli in daemon mode:
20
+ // 1. Ensure signal-cli is installed and registered with phone number
21
+ // 2. Start signal-cli in daemon mode with D-Bus interface
22
+ // 3. Subscribe to message events via D-Bus
23
+ // 4. Set this.connected = true
24
+ //
25
+ // Example using signal-cli REST API mode:
26
+ // Start signal-cli with: signal-cli -a <PHONE> daemon --http localhost:8080
27
+ // Then connect via HTTP API
28
+ }
29
+
30
+ async disconnect(): Promise<void> {
31
+ this.connected = false;
32
+ }
33
+
34
+ async sendMessage(recipient: string, text: string): Promise<void> {
35
+ throw new Error('Signal adapter not yet implemented.');
36
+
37
+ // Future implementation using signal-cli REST API:
38
+ // await fetch('http://localhost:8080/v2/send', {
39
+ // method: 'POST',
40
+ // headers: { 'Content-Type': 'application/json' },
41
+ // body: JSON.stringify({
42
+ // number: this.phone,
43
+ // recipients: [recipient],
44
+ // message: text,
45
+ // }),
46
+ // });
47
+ }
48
+
49
+ onMessage(handler: ChannelHandler): void {
50
+ this.handler = handler;
51
+ }
52
+
53
+ isConnected(): boolean {
54
+ return this.connected;
55
+ }
56
+ }