@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.
- package/LICENSE +153 -0
- package/README.md +278 -0
- package/bin/jarvis.ts +413 -0
- package/package.json +74 -0
- package/scripts/ensure-bun.cjs +8 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +252 -0
- package/src/actions/browser/session.ts +437 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +321 -0
- package/src/actions/tools/builtin.ts +846 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +20 -0
- package/src/actions/tools/registry.ts +171 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +576 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +307 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +297 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +241 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +230 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +580 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +142 -0
- package/src/comms/voice.test.ts +152 -0
- package/src/comms/voice.ts +291 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +473 -0
- package/src/config/README.md +387 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +137 -0
- package/src/config/loader.ts +142 -0
- package/src/config/types.ts +260 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +600 -0
- package/src/daemon/api-routes.ts +2119 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1004 -0
- package/src/daemon/llm-settings.ts +316 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.ts +98 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +788 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +348 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +386 -0
- package/src/llm/gemini.ts +371 -0
- package/src/llm/index.ts +19 -0
- package/src/llm/manager.ts +153 -0
- package/src/llm/ollama.ts +307 -0
- package/src/llm/openai.ts +350 -0
- package/src/llm/provider.test.ts +231 -0
- package/src/llm/provider.ts +60 -0
- package/src/llm/test.ts +87 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +119 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +194 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +190 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +260 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/entities.ts +180 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +126 -0
- package/src/vault/retrieval.ts +227 -0
- package/src/vault/schema.ts +658 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-j75njzc1.css +1199 -0
- package/ui/dist/index-p2zh407q.js +80603 -0
- package/ui/dist/index.html +13 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- 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
|
+
}
|