aibroker 0.2.6 → 0.5.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.
Files changed (83) hide show
  1. package/README.md +164 -4
  2. package/dist/adapters/iterm/core.d.ts +2 -0
  3. package/dist/adapters/iterm/core.d.ts.map +1 -1
  4. package/dist/adapters/iterm/core.js +13 -5
  5. package/dist/adapters/iterm/core.js.map +1 -1
  6. package/dist/adapters/kokoro/media.d.ts +2 -1
  7. package/dist/adapters/kokoro/media.d.ts.map +1 -1
  8. package/dist/adapters/kokoro/media.js +51 -3
  9. package/dist/adapters/kokoro/media.js.map +1 -1
  10. package/dist/adapters/pailot/gateway.d.ts +49 -0
  11. package/dist/adapters/pailot/gateway.d.ts.map +1 -0
  12. package/dist/adapters/pailot/gateway.js +502 -0
  13. package/dist/adapters/pailot/gateway.js.map +1 -0
  14. package/dist/backend/api.d.ts +5 -1
  15. package/dist/backend/api.d.ts.map +1 -1
  16. package/dist/backend/api.js +74 -3
  17. package/dist/backend/api.js.map +1 -1
  18. package/dist/core/hybrid.d.ts +5 -0
  19. package/dist/core/hybrid.d.ts.map +1 -1
  20. package/dist/core/hybrid.js +25 -0
  21. package/dist/core/hybrid.js.map +1 -1
  22. package/dist/core/state.d.ts +3 -0
  23. package/dist/core/state.d.ts.map +1 -1
  24. package/dist/core/state.js +4 -0
  25. package/dist/core/state.js.map +1 -1
  26. package/dist/daemon/adapter-registry.d.ts +58 -0
  27. package/dist/daemon/adapter-registry.d.ts.map +1 -0
  28. package/dist/daemon/adapter-registry.js +179 -0
  29. package/dist/daemon/adapter-registry.js.map +1 -0
  30. package/dist/daemon/cli.d.ts +13 -0
  31. package/dist/daemon/cli.d.ts.map +1 -0
  32. package/dist/daemon/cli.js +58 -0
  33. package/dist/daemon/cli.js.map +1 -0
  34. package/dist/daemon/core-handlers.d.ts +24 -0
  35. package/dist/daemon/core-handlers.d.ts.map +1 -0
  36. package/dist/daemon/core-handlers.js +146 -0
  37. package/dist/daemon/core-handlers.js.map +1 -0
  38. package/dist/daemon/create-adapter.d.ts +22 -0
  39. package/dist/daemon/create-adapter.d.ts.map +1 -0
  40. package/dist/daemon/create-adapter.js +152 -0
  41. package/dist/daemon/create-adapter.js.map +1 -0
  42. package/dist/daemon/index.d.ts +12 -0
  43. package/dist/daemon/index.d.ts.map +1 -0
  44. package/dist/daemon/index.js +83 -0
  45. package/dist/daemon/index.js.map +1 -0
  46. package/dist/daemon/pai-projects.d.ts +68 -0
  47. package/dist/daemon/pai-projects.d.ts.map +1 -0
  48. package/dist/daemon/pai-projects.js +174 -0
  49. package/dist/daemon/pai-projects.js.map +1 -0
  50. package/dist/index.d.ts +11 -2
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +9 -1
  53. package/dist/index.js.map +1 -1
  54. package/dist/ipc/client.d.ts +4 -1
  55. package/dist/ipc/client.d.ts.map +1 -1
  56. package/dist/ipc/client.js +10 -1
  57. package/dist/ipc/client.js.map +1 -1
  58. package/dist/types/adapter.d.ts +41 -0
  59. package/dist/types/adapter.d.ts.map +1 -0
  60. package/dist/types/adapter.js +2 -0
  61. package/dist/types/adapter.js.map +1 -0
  62. package/dist/types/backend.d.ts +29 -1
  63. package/dist/types/backend.d.ts.map +1 -1
  64. package/dist/types/broker.d.ts +45 -0
  65. package/dist/types/broker.d.ts.map +1 -0
  66. package/dist/types/broker.js +21 -0
  67. package/dist/types/broker.js.map +1 -0
  68. package/dist/types/index.d.ts +2 -0
  69. package/dist/types/index.d.ts.map +1 -1
  70. package/dist/types/index.js +2 -0
  71. package/dist/types/index.js.map +1 -1
  72. package/package.json +9 -2
  73. package/templates/adapter/ONBOARDING_PROMPT.md +287 -0
  74. package/templates/adapter/README.md.tmpl +98 -0
  75. package/templates/adapter/package.json.tmpl +23 -0
  76. package/templates/adapter/src/watcher/cli.ts.tmpl +12 -0
  77. package/templates/adapter/src/watcher/commands.ts.tmpl +146 -0
  78. package/templates/adapter/src/watcher/connection.ts.tmpl +59 -0
  79. package/templates/adapter/src/watcher/index.ts.tmpl +177 -0
  80. package/templates/adapter/src/watcher/ipc-server.ts.tmpl +226 -0
  81. package/templates/adapter/src/watcher/send.ts.tmpl +62 -0
  82. package/templates/adapter/src/watcher/state.ts.tmpl +39 -0
  83. package/templates/adapter/tsconfig.json.tmpl +14 -0
@@ -0,0 +1,502 @@
1
+ /**
2
+ * adapters/pailot/gateway.ts — WebSocket gateway for PAILot app connections.
3
+ *
4
+ * Runs alongside any transport's watcher (Whazaa, Telex, etc.). When the
5
+ * PAILot iOS app connects via WebSocket, incoming messages are routed through
6
+ * the same handleMessage() path as the transport's native messages. Outbound
7
+ * messages from Claude are broadcast to all connected clients.
8
+ *
9
+ * The gateway also supports structured commands (sessions, screenshot,
10
+ * navigation keys) so the app can interact with the watcher without
11
+ * going through text-based slash commands.
12
+ */
13
+ import { WebSocketServer, WebSocket } from "ws";
14
+ import { join } from "node:path";
15
+ import { writeFileSync, readFileSync, existsSync, unlinkSync, appendFileSync } from "node:fs";
16
+ import { tmpdir } from "node:os";
17
+ const DEBUG_LOG = "/tmp/pailot-ws-debug.log";
18
+ function dbg(msg) {
19
+ appendFileSync(DEBUG_LOG, `[${new Date().toISOString()}] ${msg}\n`);
20
+ }
21
+ import { randomUUID } from "node:crypto";
22
+ import { promisify } from "node:util";
23
+ import { execFile } from "node:child_process";
24
+ import { log } from "../../core/log.js";
25
+ import { WHISPER_BIN, WHISPER_MODEL } from "../kokoro/media.js";
26
+ import { setMessageSource, activeItermSessionId, setActiveItermSessionId, } from "../../core/state.js";
27
+ import { setItermSessionVar, setItermTabName } from "../iterm/sessions.js";
28
+ import { runAppleScript, sendKeystrokeToSession, sendEscapeSequenceToSession, pasteTextIntoSession, snapshotAllSessions } from "../iterm/core.js";
29
+ import { hybridManager } from "../../core/hybrid.js";
30
+ const WS_PORT = parseInt(process.env.PAILOT_PORT ?? "8765", 10);
31
+ // --- State ---
32
+ let wss = null;
33
+ const clients = new Set();
34
+ // Reference to the screenshot handler — set via setScreenshotHandler()
35
+ // to avoid circular imports (screenshot.ts imports from state.ts which
36
+ // would create a cycle if we imported it here directly).
37
+ let screenshotHandler = null;
38
+ /**
39
+ * Provide the screenshot handler so ws-gateway can trigger screenshots
40
+ * for navigation commands without a circular import.
41
+ */
42
+ export function setScreenshotHandler(handler) {
43
+ screenshotHandler = handler;
44
+ }
45
+ // --- Structured command handling ---
46
+ /** Detect which iTerm2 session is currently focused and sync the hybrid manager to it. */
47
+ function handleSyncCommand(ws) {
48
+ if (!hybridManager) {
49
+ handleSessionsCommand(ws);
50
+ return;
51
+ }
52
+ // Ask iTerm2 which session is focused right now
53
+ const focusedId = runAppleScript(`tell application "iTerm2"
54
+ try
55
+ return id of current session of current tab of current window
56
+ on error
57
+ return ""
58
+ end try
59
+ end tell`)?.trim() ?? "";
60
+ if (focusedId) {
61
+ // Find this session in the hybrid manager and activate it
62
+ const sessions = hybridManager.listSessions();
63
+ const idx = sessions.findIndex(s => s.backendSessionId === focusedId);
64
+ if (idx >= 0) {
65
+ hybridManager.switchToIndex(idx + 1);
66
+ setActiveItermSessionId(focusedId);
67
+ log(`[PAILot] sync: activated focused session "${sessions[idx].name}" (${focusedId.slice(0, 8)}...)`);
68
+ }
69
+ else {
70
+ log(`[PAILot] sync: focused session ${focusedId.slice(0, 8)}... not registered`);
71
+ }
72
+ }
73
+ // Return sessions with updated active state
74
+ handleSessionsCommand(ws);
75
+ }
76
+ function handleSessionsCommand(ws) {
77
+ if (!hybridManager) {
78
+ sendTo(ws, { type: "sessions", sessions: [] });
79
+ return;
80
+ }
81
+ // Prune visual sessions whose iTerm2 tabs have been closed
82
+ const liveSnapshots = snapshotAllSessions();
83
+ const liveIds = new Set(liveSnapshots.map(s => s.id));
84
+ hybridManager.pruneDeadVisualSessions(liveIds);
85
+ const hybridSessions = hybridManager.listSessions();
86
+ const active = hybridManager.activeSession;
87
+ const sessions = hybridSessions.map((s, i) => ({
88
+ index: i + 1,
89
+ name: s.name,
90
+ type: s.kind === "visual" ? "claude" : "claude",
91
+ kind: s.kind,
92
+ isActive: active ? s.id === active.id : false,
93
+ id: s.backendSessionId,
94
+ }));
95
+ const payload = JSON.stringify({ type: "sessions", sessions });
96
+ if (ws.readyState === WebSocket.OPEN) {
97
+ ws.send(payload);
98
+ }
99
+ }
100
+ function handleSwitchCommand(ws, args) {
101
+ const sessionIndex = args.index;
102
+ const sessionId = args.sessionId;
103
+ const newName = args.name;
104
+ if (!hybridManager) {
105
+ sendTo(ws, { type: "error", message: "No session manager" });
106
+ return;
107
+ }
108
+ // Resolve which session to switch to (prefer index, fall back to sessionId lookup)
109
+ let targetIndex;
110
+ if (sessionIndex) {
111
+ targetIndex = sessionIndex;
112
+ }
113
+ else if (sessionId) {
114
+ const sessions = hybridManager.listSessions();
115
+ const idx = sessions.findIndex(s => s.backendSessionId === sessionId);
116
+ if (idx >= 0)
117
+ targetIndex = idx + 1;
118
+ }
119
+ if (!targetIndex) {
120
+ sendTo(ws, { type: "error", message: "Missing session index or ID" });
121
+ return;
122
+ }
123
+ const session = hybridManager.switchToIndex(targetIndex);
124
+ if (!session) {
125
+ sendTo(ws, { type: "error", message: "Session not found — it may have closed." });
126
+ return;
127
+ }
128
+ // For visual sessions, also focus the iTerm2 tab
129
+ if (session.kind === "visual") {
130
+ setActiveItermSessionId(session.backendSessionId);
131
+ const escapedId = session.backendSessionId.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
132
+ runAppleScript(`tell application "iTerm2"
133
+ repeat with aWindow in windows
134
+ repeat with aTab in tabs of aWindow
135
+ repeat with aSession in sessions of aTab
136
+ if id of aSession is "${escapedId}" then
137
+ select aSession
138
+ return "focused"
139
+ end if
140
+ end repeat
141
+ end repeat
142
+ end repeat
143
+ end tell`);
144
+ }
145
+ if (newName) {
146
+ session.name = newName;
147
+ if (session.kind === "visual") {
148
+ setItermSessionVar(session.backendSessionId, newName);
149
+ setItermTabName(session.backendSessionId, newName);
150
+ }
151
+ }
152
+ sendTo(ws, { type: "session_switched", name: session.name, sessionId: session.backendSessionId });
153
+ log(`[PAILot] switched to ${session.kind} session "${session.name}" (${session.id})`);
154
+ }
155
+ function handleRenameCommand(ws, args) {
156
+ const sessionId = args.sessionId;
157
+ const name = args.name;
158
+ if (!sessionId || !name || !hybridManager) {
159
+ sendTo(ws, { type: "error", message: "Missing sessionId or name" });
160
+ return;
161
+ }
162
+ // Find the hybrid session by backendSessionId
163
+ const sessions = hybridManager.listSessions();
164
+ const session = sessions.find(s => s.backendSessionId === sessionId);
165
+ if (session) {
166
+ session.name = name;
167
+ if (session.kind === "visual") {
168
+ setItermSessionVar(sessionId, name);
169
+ setItermTabName(sessionId, name);
170
+ }
171
+ }
172
+ sendTo(ws, { type: "session_renamed", sessionId, name });
173
+ log(`[PAILot] renamed session ${sessionId} to "${name}"`);
174
+ }
175
+ async function handleNavCommand(ws, args) {
176
+ const key = args.key;
177
+ if (!key)
178
+ return;
179
+ // Guard: nav commands only work with visual sessions
180
+ if (hybridManager?.activeSession?.kind === "api") {
181
+ sendTo(ws, { type: "error", message: "Keyboard commands need a visual session." });
182
+ return;
183
+ }
184
+ const targetSession = activeItermSessionId;
185
+ if (!targetSession) {
186
+ sendTo(ws, { type: "error", message: "No active session" });
187
+ return;
188
+ }
189
+ // Map key names to actions
190
+ // sendKeystrokeToSession takes ASCII code: 13=enter, 9=tab, 27=escape
191
+ // sendEscapeSequenceToSession takes ANSI direction char: A=up, B=down, C=right, D=left
192
+ const keyMap = {
193
+ up: () => sendEscapeSequenceToSession(targetSession, "A"),
194
+ down: () => sendEscapeSequenceToSession(targetSession, "B"),
195
+ left: () => sendEscapeSequenceToSession(targetSession, "D"),
196
+ right: () => sendEscapeSequenceToSession(targetSession, "C"),
197
+ enter: () => sendKeystrokeToSession(targetSession, 13),
198
+ tab: () => sendKeystrokeToSession(targetSession, 9),
199
+ escape: () => sendKeystrokeToSession(targetSession, 27),
200
+ "ctrl-c": () => {
201
+ // Send Ctrl+C (ETX, ASCII 3)
202
+ runAppleScript(`tell application "iTerm2"
203
+ repeat with w in windows
204
+ repeat with t in tabs of w
205
+ repeat with s in sessions of t
206
+ if id of s is "${targetSession}" then
207
+ tell s to write text (ASCII character 3)
208
+ return
209
+ end if
210
+ end repeat
211
+ end repeat
212
+ end repeat
213
+ end tell`);
214
+ },
215
+ };
216
+ const action = keyMap[key];
217
+ if (action) {
218
+ action();
219
+ }
220
+ else {
221
+ // Fallback: send as literal text (vi keys like "dd", "0", "G", etc.)
222
+ pasteTextIntoSession(targetSession, key);
223
+ }
224
+ log(`[PAILot] nav: sent ${key} to session ${targetSession.slice(0, 8)}...`);
225
+ // Auto-screenshot after navigation key with a brief delay for render
226
+ if (screenshotHandler) {
227
+ await new Promise((r) => setTimeout(r, 600));
228
+ await triggerScreenshotForPailot();
229
+ }
230
+ }
231
+ async function triggerScreenshotForPailot() {
232
+ if (!screenshotHandler)
233
+ return;
234
+ // Only send to PAILot — this is triggered by PAILot commands
235
+ await screenshotHandler("pailot");
236
+ }
237
+ // --- Helpers ---
238
+ function sendTo(ws, msg) {
239
+ if (ws.readyState === WebSocket.OPEN) {
240
+ ws.send(JSON.stringify(msg));
241
+ }
242
+ }
243
+ function broadcast(msg) {
244
+ if (clients.size === 0)
245
+ return;
246
+ const payload = JSON.stringify(msg);
247
+ for (const ws of clients) {
248
+ if (ws.readyState === WebSocket.OPEN) {
249
+ ws.send(payload);
250
+ }
251
+ }
252
+ }
253
+ // --- Voice message batching ---
254
+ // When multiple voice messages arrive within BATCH_WINDOW_MS, we combine their
255
+ // transcripts into a single onMessage call so Claude sees them as one input.
256
+ const BATCH_WINDOW_MS = 3000;
257
+ let voiceBatchTimer = null;
258
+ let voiceBatchTranscripts = [];
259
+ let voiceBatchOnMessage = null;
260
+ function flushVoiceBatch() {
261
+ if (voiceBatchTranscripts.length === 0)
262
+ return;
263
+ const combined = voiceBatchTranscripts.join(" ");
264
+ const handler = voiceBatchOnMessage;
265
+ voiceBatchTranscripts = [];
266
+ voiceBatchOnMessage = null;
267
+ voiceBatchTimer = null;
268
+ if (handler) {
269
+ log(`[PAILot] Flushing voice batch (${combined.length} chars)`);
270
+ setMessageSource("pailot");
271
+ handler(`[PAILot:voice] ${combined}`, Date.now());
272
+ setMessageSource("whatsapp");
273
+ }
274
+ }
275
+ // --- Voice transcription for PAILot ---
276
+ const execFileAsync = promisify(execFile);
277
+ async function transcribeAndRoute(audioBase64, onMessage) {
278
+ const base = `pailot-voice-${Date.now()}-${randomUUID().slice(0, 8)}`;
279
+ const audioFile = join(tmpdir(), `${base}.m4a`);
280
+ const filesToClean = [
281
+ audioFile,
282
+ join(tmpdir(), `${base}.txt`),
283
+ join(tmpdir(), `${base}.json`),
284
+ join(tmpdir(), `${base}.vtt`),
285
+ join(tmpdir(), `${base}.srt`),
286
+ join(tmpdir(), `${base}.tsv`),
287
+ ];
288
+ try {
289
+ dbg(`transcribeAndRoute: base64 length=${audioBase64.length}`);
290
+ const buffer = Buffer.from(audioBase64, "base64");
291
+ writeFileSync(audioFile, buffer);
292
+ dbg(`Audio saved: ${audioFile} (${buffer.length} bytes)`);
293
+ log(`[PAILot] Voice note saved (${buffer.length} bytes), running Whisper...`);
294
+ await execFileAsync(WHISPER_BIN, [audioFile, "--model", WHISPER_MODEL, "--output_format", "txt", "--output_dir", tmpdir(), "--verbose", "False"], {
295
+ timeout: 120_000,
296
+ env: {
297
+ ...process.env,
298
+ PATH: `/opt/homebrew/bin:/usr/local/bin:${process.env.PATH || "/usr/bin:/bin"}`,
299
+ },
300
+ });
301
+ const txtPath = join(tmpdir(), `${base}.txt`);
302
+ if (!existsSync(txtPath)) {
303
+ log(`[PAILot] Whisper did not produce output`);
304
+ return;
305
+ }
306
+ const transcript = readFileSync(txtPath, "utf-8").trim();
307
+ if (!transcript) {
308
+ log(`[PAILot] Empty transcript`);
309
+ return;
310
+ }
311
+ log(`[PAILot] Transcription: ${transcript.slice(0, 80)}${transcript.length > 80 ? "..." : ""}`);
312
+ // Batch: accumulate transcripts and reset the timer
313
+ voiceBatchTranscripts.push(transcript);
314
+ voiceBatchOnMessage = onMessage;
315
+ if (voiceBatchTimer)
316
+ clearTimeout(voiceBatchTimer);
317
+ voiceBatchTimer = setTimeout(flushVoiceBatch, BATCH_WINDOW_MS);
318
+ }
319
+ catch (err) {
320
+ log(`[PAILot] Whisper transcription failed: ${err}`);
321
+ }
322
+ finally {
323
+ for (const f of filesToClean) {
324
+ try {
325
+ unlinkSync(f);
326
+ }
327
+ catch { /* ignore */ }
328
+ }
329
+ }
330
+ }
331
+ // --- Public API ---
332
+ /**
333
+ * Start the WebSocket gateway.
334
+ * @param onMessage — the same handleMessage(text, timestamp) used by the transport
335
+ */
336
+ export function startWsGateway(onMessage) {
337
+ wss = new WebSocketServer({ port: WS_PORT });
338
+ wss.on("listening", () => {
339
+ log(`WebSocket gateway listening on ws://0.0.0.0:${WS_PORT}`);
340
+ });
341
+ wss.on("connection", (ws, req) => {
342
+ const addr = req.socket.remoteAddress ?? "unknown";
343
+ log(`PAILot client connected from ${addr}`);
344
+ clients.add(ws);
345
+ ws.on("message", (raw) => {
346
+ try {
347
+ const rawStr = raw.toString();
348
+ dbg(`RAW msg (${rawStr.length} chars): type=${JSON.parse(rawStr).type}, hasAudio=${!!JSON.parse(rawStr).audioBase64}, content=${(JSON.parse(rawStr).content ?? "").slice(0, 50)}`);
349
+ const msg = JSON.parse(rawStr);
350
+ // Structured commands from PAILot app
351
+ if (msg.type === "command") {
352
+ const command = msg.command;
353
+ const args = (msg.args ?? {});
354
+ log(`[PAILot] ← command: ${command}`);
355
+ switch (command) {
356
+ case "sessions":
357
+ handleSessionsCommand(ws);
358
+ return;
359
+ case "sync":
360
+ handleSyncCommand(ws);
361
+ return;
362
+ case "switch":
363
+ handleSwitchCommand(ws, args);
364
+ return;
365
+ case "rename":
366
+ handleRenameCommand(ws, args);
367
+ return;
368
+ case "screenshot":
369
+ // For API sessions, send text status instead of screenshot
370
+ if (hybridManager?.activeSession?.kind === "api") {
371
+ const status = hybridManager.formatActiveStatus();
372
+ if (status) {
373
+ sendTo(ws, { type: "text", content: status });
374
+ }
375
+ }
376
+ else {
377
+ triggerScreenshotForPailot().catch((err) => {
378
+ log(`[PAILot] screenshot error: ${err}`);
379
+ });
380
+ }
381
+ return;
382
+ case "nav":
383
+ handleNavCommand(ws, args).catch((err) => {
384
+ log(`[PAILot] nav error: ${err}`);
385
+ });
386
+ return;
387
+ default:
388
+ break;
389
+ }
390
+ }
391
+ // Voice message — transcribe with Whisper then route
392
+ if (msg.type === "voice" && msg.audioBase64) {
393
+ dbg(`Voice message received, audioBase64 length: ${msg.audioBase64.length}`);
394
+ transcribeAndRoute(msg.audioBase64, onMessage).catch((err) => {
395
+ log(`[PAILot] voice transcription error: ${err}`);
396
+ });
397
+ return;
398
+ }
399
+ // Plain text message — route through handleMessage
400
+ const text = msg.content ?? "";
401
+ if (!text.trim())
402
+ return;
403
+ log(`[PAILot] ← ${text.slice(0, 80)}${text.length > 80 ? "..." : ""}`);
404
+ // Set source so commands handler uses [PAILot] prefix
405
+ setMessageSource("pailot");
406
+ onMessage(text, Date.now());
407
+ setMessageSource("whatsapp");
408
+ }
409
+ catch {
410
+ log(`[PAILot] Invalid message from ${addr}`);
411
+ }
412
+ });
413
+ ws.on("close", () => {
414
+ log(`PAILot client disconnected from ${addr}`);
415
+ clients.delete(ws);
416
+ });
417
+ ws.on("error", (err) => {
418
+ log(`[PAILot] WebSocket error: ${err.message}`);
419
+ clients.delete(ws);
420
+ });
421
+ // Welcome
422
+ sendTo(ws, { type: "text", content: "Connected to PAILot gateway." });
423
+ });
424
+ wss.on("error", (err) => {
425
+ log(`WebSocket gateway error: ${err.message}`);
426
+ });
427
+ }
428
+ /**
429
+ * Broadcast a text message to all connected PAILot clients.
430
+ */
431
+ export function broadcastText(text) {
432
+ broadcast({ type: "text", content: text });
433
+ }
434
+ /**
435
+ * Broadcast a voice note to all connected PAILot clients.
436
+ * Converts OGG Opus to M4A (AAC) since iOS can't play OGG natively.
437
+ */
438
+ export async function broadcastVoice(audioBuffer, transcript) {
439
+ let sendBuffer = audioBuffer;
440
+ // Convert OGG Opus → M4A for iOS compatibility
441
+ try {
442
+ const uid = randomUUID().slice(0, 8);
443
+ const oggPath = join(tmpdir(), `pailot-conv-${uid}.ogg`);
444
+ const m4aPath = join(tmpdir(), `pailot-conv-${uid}.m4a`);
445
+ writeFileSync(oggPath, audioBuffer);
446
+ await execFileAsync("/opt/homebrew/bin/ffmpeg", [
447
+ "-y", "-i", oggPath, "-c:a", "aac", "-b:a", "128k", m4aPath,
448
+ ]);
449
+ if (existsSync(m4aPath)) {
450
+ sendBuffer = readFileSync(m4aPath);
451
+ try {
452
+ unlinkSync(oggPath);
453
+ unlinkSync(m4aPath);
454
+ }
455
+ catch { /* ignore */ }
456
+ }
457
+ }
458
+ catch (err) {
459
+ log(`[PAILot] OGG→M4A conversion failed, sending raw: ${err}`);
460
+ }
461
+ broadcast({
462
+ type: "voice",
463
+ content: transcript,
464
+ audioBase64: sendBuffer.toString("base64"),
465
+ });
466
+ }
467
+ /**
468
+ * Broadcast a screenshot/image to all connected PAILot clients.
469
+ */
470
+ export function broadcastImage(imageBuffer, caption) {
471
+ broadcast({
472
+ type: "image",
473
+ imageBase64: imageBuffer.toString("base64"),
474
+ caption: caption ?? "Screenshot",
475
+ });
476
+ }
477
+ /**
478
+ * Broadcast a status change to all connected PAILot clients.
479
+ * Used to signal compaction, reconnection, etc.
480
+ */
481
+ export function broadcastStatus(status) {
482
+ broadcast({ type: "status", status });
483
+ }
484
+ /**
485
+ * Returns true if any PAILot clients are connected.
486
+ */
487
+ export function hasPailotClients() {
488
+ return clients.size > 0;
489
+ }
490
+ /**
491
+ * Stop the WebSocket gateway.
492
+ */
493
+ export function stopWsGateway() {
494
+ if (wss) {
495
+ for (const ws of clients)
496
+ ws.close();
497
+ clients.clear();
498
+ wss.close();
499
+ wss = null;
500
+ }
501
+ }
502
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/adapters/pailot/gateway.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  * Tracks live session status (thinking, tool_running, compacting, etc.)
11
11
  * so consumers can show meaningful status via /ss.
12
12
  */
13
- import type { Backend, APIBackendConfig } from "../types/backend.js";
13
+ import type { Backend, APIBackendConfig, BackendHealth } from "../types/backend.js";
14
14
  /** Live status of a session's current deliver() call */
15
15
  export type SessionState = "idle" | "thinking" | "tool_running" | "compacting" | "done" | "error";
16
16
  /** Live status snapshot for a session */
@@ -83,5 +83,9 @@ export declare class APIBackend implements Backend {
83
83
  formatStatus(): string;
84
84
  private formatState;
85
85
  deliver(message: string, sessionId?: string): Promise<string | undefined>;
86
+ health(): Promise<BackendHealth>;
87
+ private anthropicDeliver;
88
+ private ollamaDeliver;
89
+ private openaiDeliver;
86
90
  }
87
91
  //# sourceMappingURL=api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/backend/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAKrE,wDAAwD;AACxD,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,UAAU,GACV,cAAc,GACd,YAAY,GACZ,MAAM,GACN,OAAO,CAAC;AAEZ,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qCAAqC;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,qBAAa,UAAW,YAAW,OAAO;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,kCAAkC;IAClC,OAAO,CAAC,gBAAgB,CAAc;IACtC,6CAA6C;IAC7C,OAAO,CAAC,cAAc,CAAK;gBAEf,MAAM,EAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAYxD,gCAAgC;IAChC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,gCAAgC;IAChC,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,EAK7B;IAED,8CAA8C;IAC9C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU;IAerD,2DAA2D;IAC3D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAc/B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU/B,0CAA0C;IAC1C,YAAY,IAAI,UAAU,EAAE;IAQ5B,+DAA+D;IAC/D,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAKxD,4DAA4D;IAC5D,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa;IAK5C,yDAAyD;IACzD,YAAY,IAAI,MAAM;IAsCtB,OAAO,CAAC,WAAW;IA2Bb,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;CAmIhF"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/backend/api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAKpF,wDAAwD;AACxD,MAAM,MAAM,YAAY,GACpB,MAAM,GACN,UAAU,GACV,cAAc,GACd,YAAY,GACZ,MAAM,GACN,OAAO,CAAC;AAEZ,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qCAAqC;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wCAAwC;AACxC,MAAM,WAAW,UAAU;IACzB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,yEAAyE;IACzE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,qBAAa,UAAW,YAAW,OAAO;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;IAC/B,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAE1C,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiC;IAC1D,8BAA8B;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,kCAAkC;IAClC,OAAO,CAAC,gBAAgB,CAAc;IACtC,6CAA6C;IAC7C,OAAO,CAAC,cAAc,CAAK;gBAEf,MAAM,EAAE,gBAAgB,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAYxD,gCAAgC;IAChC,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED,gCAAgC;IAChC,IAAI,eAAe,CAAC,EAAE,EAAE,MAAM,EAK7B;IAED,8CAA8C;IAC9C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU;IAerD,2DAA2D;IAC3D,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAc/B,6EAA6E;IAC7E,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAU/B,0CAA0C;IAC1C,YAAY,IAAI,UAAU,EAAE;IAQ5B,+DAA+D;IAC/D,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAKxD,4DAA4D;IAC5D,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa;IAK5C,yDAAyD;IACzD,YAAY,IAAI,MAAM;IAsCtB,OAAO,CAAC,WAAW;IA2Bb,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IASzE,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;YAqBxB,gBAAgB;YA+HhB,aAAa;YAsBb,aAAa;CA0B5B"}
@@ -167,10 +167,33 @@ export class APIBackend {
167
167
  }
168
168
  }
169
169
  async deliver(message, sessionId) {
170
- if (this.provider !== "anthropic") {
171
- log(`APIBackend: provider '${this.provider}' not yet supported, only 'anthropic'`);
172
- return `Error: provider '${this.provider}' is not yet implemented. Use provider 'anthropic'.`;
170
+ switch (this.provider) {
171
+ case "anthropic": return this.anthropicDeliver(message, sessionId);
172
+ case "ollama": return this.ollamaDeliver(message, sessionId);
173
+ case "openai": return this.openaiDeliver(message, sessionId);
174
+ default: throw new Error(`Unknown provider: ${this.provider}`);
173
175
  }
176
+ }
177
+ async health() {
178
+ const activeSessions = this.sessions.size;
179
+ if (this.provider === "ollama") {
180
+ try {
181
+ const baseUrl = this.config.baseUrl ?? "http://localhost:11434";
182
+ const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });
183
+ if (!res.ok) {
184
+ return { status: "degraded", activeSessions, detail: `provider=ollama, /api/tags returned ${res.status}` };
185
+ }
186
+ return { status: "ok", activeSessions, detail: `provider=ollama, model=${this.model}` };
187
+ }
188
+ catch (err) {
189
+ const msg = err instanceof Error ? err.message : String(err);
190
+ return { status: "down", activeSessions, detail: `provider=ollama, unreachable: ${msg}` };
191
+ }
192
+ }
193
+ // For anthropic and openai, no cheap health-check without burning tokens
194
+ return { status: "ok", activeSessions, detail: `provider=${this.provider}, model=${this.model}` };
195
+ }
196
+ async anthropicDeliver(message, sessionId) {
174
197
  // Resolve session — use explicit ID, fall back to active
175
198
  const targetId = sessionId ?? this._activeSessionId;
176
199
  const session = targetId ? this.sessions.get(targetId) : undefined;
@@ -293,5 +316,53 @@ export class APIBackend {
293
316
  return `Error from Claude subprocess: ${msg}`;
294
317
  }
295
318
  }
319
+ async ollamaDeliver(message, _sessionId) {
320
+ const baseUrl = this.config.baseUrl ?? "http://localhost:11434";
321
+ log(`APIBackend: delivering to Ollama ${this.model} at ${baseUrl}`);
322
+ const response = await fetch(`${baseUrl}/api/chat`, {
323
+ method: "POST",
324
+ headers: { "Content-Type": "application/json" },
325
+ body: JSON.stringify({
326
+ model: this.model,
327
+ messages: [{ role: "user", content: message }],
328
+ stream: false,
329
+ }),
330
+ });
331
+ if (!response.ok) {
332
+ throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
333
+ }
334
+ const data = await response.json();
335
+ const msg = data.message;
336
+ const content = typeof msg?.content === "string" ? msg.content : undefined;
337
+ log(`APIBackend: Ollama response (${content?.length ?? 0} chars)`);
338
+ return content;
339
+ }
340
+ async openaiDeliver(message, _sessionId) {
341
+ const apiKey = this.config.apiKey ?? process.env.OPENAI_API_KEY;
342
+ if (!apiKey)
343
+ throw new Error("OpenAI API key not set — provide apiKey in config or set OPENAI_API_KEY");
344
+ const baseUrl = this.config.baseUrl ?? "https://api.openai.com";
345
+ log(`APIBackend: delivering to OpenAI ${this.model}`);
346
+ const response = await fetch(`${baseUrl}/v1/chat/completions`, {
347
+ method: "POST",
348
+ headers: {
349
+ "Content-Type": "application/json",
350
+ "Authorization": `Bearer ${apiKey}`,
351
+ },
352
+ body: JSON.stringify({
353
+ model: this.model,
354
+ messages: [{ role: "user", content: message }],
355
+ }),
356
+ });
357
+ if (!response.ok) {
358
+ throw new Error(`OpenAI error: ${response.status} ${response.statusText}`);
359
+ }
360
+ const data = await response.json();
361
+ const choices = data.choices;
362
+ const content = choices?.[0]?.message?.content;
363
+ const text = typeof content === "string" ? content : undefined;
364
+ log(`APIBackend: OpenAI response (${text?.length ?? 0} chars)`);
365
+ return text;
366
+ }
296
367
  }
297
368
  //# sourceMappingURL=api.js.map