osborn 0.9.86 → 0.9.88

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 (2) hide show
  1. package/dist/index.js +75 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -201,6 +201,22 @@ let leaveRoomHook = null;
201
201
  // as the existing fetch-log/save-log flow so we don't ship Supabase credentials
202
202
  // to the Fly machine.
203
203
  let bugReportHook = null;
204
+ const canvasClients = new Set();
205
+ let latestCanvasShow = null; // last 'show' so a reconnecting canvas resyncs its visual
206
+ function pushCanvas(evt) {
207
+ if (evt.kind === 'show')
208
+ latestCanvasShow = evt;
209
+ const line = `data: ${JSON.stringify(evt)}\n\n`;
210
+ for (const res of canvasClients) {
211
+ try {
212
+ res.write(line);
213
+ }
214
+ catch {
215
+ canvasClients.delete(res);
216
+ }
217
+ }
218
+ console.log(`🖼️ canvas ${evt.kind}: ${evt.kind === 'say' ? evt.text.slice(0, 60) : evt.mode} → ${canvasClients.size} client(s)`);
219
+ }
204
220
  function startApiServer(workingDir, port) {
205
221
  const server = createServer(async (req, res) => {
206
222
  // CORS headers for cloud frontend
@@ -476,6 +492,50 @@ function startApiServer(workingDir, port) {
476
492
  console.log('[events] SSE client connected');
477
493
  return;
478
494
  }
495
+ // ── Meeting canvas: SSE stream the Recall webpage subscribes to ──────────
496
+ // The canvas page (frontend /meeting-canvas) opens this as an EventSource.
497
+ // Recall renders that page as the bot's camera+mic, so whatever we push here
498
+ // becomes what the meeting sees + hears. On connect we resync the current
499
+ // visual so a page reload doesn't blank the bot's camera.
500
+ if (req.method === 'GET' && url.pathname === '/canvas-stream') {
501
+ res.writeHead(200, {
502
+ 'Content-Type': 'text/event-stream',
503
+ 'Cache-Control': 'no-cache',
504
+ 'Connection': 'keep-alive',
505
+ 'X-Accel-Buffering': 'no',
506
+ });
507
+ res.write(`: canvas connected ${new Date().toISOString()}\n\n`);
508
+ res.write(`data: ${JSON.stringify(latestCanvasShow ?? { kind: 'show', mode: 'idle' })}\n\n`);
509
+ canvasClients.add(res);
510
+ const hb = setInterval(() => { try {
511
+ res.write(`: ping ${Date.now()}\n\n`);
512
+ }
513
+ catch { } }, 10_000);
514
+ req.on('close', () => { clearInterval(hb); canvasClients.delete(res); console.log('[canvas] client disconnected'); });
515
+ console.log(`[canvas] client connected (${canvasClients.size} total)`);
516
+ return;
517
+ }
518
+ // ── Meeting canvas: control endpoint (director / agent tool) ─────────────
519
+ // POST /canvas { kind:'say', text } | { kind:'show', mode, title?, items?, url?, text? }
520
+ if (req.method === 'POST' && url.pathname === '/canvas') {
521
+ let body = '';
522
+ req.on('data', (c) => { body += c; });
523
+ req.on('end', () => {
524
+ try {
525
+ const evt = JSON.parse(body || '{}');
526
+ if (evt.kind !== 'say' && evt.kind !== 'show')
527
+ throw new Error("kind must be 'say' or 'show'");
528
+ pushCanvas(evt);
529
+ res.writeHead(200, { 'Content-Type': 'application/json' });
530
+ res.end(JSON.stringify({ ok: true, clients: canvasClients.size }));
531
+ }
532
+ catch (e) {
533
+ res.writeHead(400, { 'Content-Type': 'application/json' });
534
+ res.end(JSON.stringify({ error: e.message }));
535
+ }
536
+ });
537
+ return;
538
+ }
479
539
  // GET /sessions/export — stream a gzipped tar of ~/.claude/projects/ AND
480
540
  // ~/.claude/skills/ to the client. Both directories ship in one archive so
481
541
  // a sync covers conversations (projects/) and learned skills together —
@@ -4625,15 +4685,22 @@ async function main() {
4625
4685
  (process.env.FLY_APP_NAME
4626
4686
  ? `https://${process.env.FLY_APP_NAME}.fly.dev`
4627
4687
  : `http://localhost:${apiPort}`);
4628
- // Polling architecture (post-2026-05-22): the bot joins by name
4629
- // only — no output_media webpage, no LiveKit republish, no audio
4630
- // pipeline at all. Recall captures the meeting audio internally
4631
- // and we pull the transcript via its REST API every ~30s.
4688
+ // Transcript: Recall captures meeting audio internally; we get it
4689
+ // live via the realtime webhook (+ REST poll backstop). Output:
4690
+ // the bot casts the meeting canvas as its camera+mic (below), so
4691
+ // it can show visuals and speak into the meeting on demand.
4632
4692
  await sendToFrontend({ type: 'meeting_joining', message: 'Osborn is joining your meeting...' });
4633
- // 0.9.85: optional castUrl — a webpage to display as the bot's
4634
- // camera (live feed / seeded site). Passed from the frontend or
4635
- // env OSBORN_MEETING_CAST_URL for testing.
4636
- const castUrl = data.castUrl || process.env.OSBORN_MEETING_CAST_URL || undefined;
4693
+ // Cast target = the bot's camera+mic webpage (Recall output_media).
4694
+ // DEFAULT is the meeting canvas (frontend /meeting-canvas), pointed
4695
+ // at THIS agent's public URL so it can subscribe to /canvas-stream
4696
+ // and become the bot's face (visuals) + voice (TTS it speaks). An
4697
+ // explicit castUrl from the frontend or OSBORN_MEETING_CAST_URL
4698
+ // overrides it (e.g. to cast a live feed or a seeded site instead).
4699
+ const frontendUrl = (process.env.OSBORN_FRONTEND_URL || 'https://www.voice-native.com').replace(/\/$/, '');
4700
+ const canvasUrl = /^https:\/\//.test(webhookBase)
4701
+ ? `${frontendUrl}/meeting-canvas?agent=${encodeURIComponent(webhookBase)}`
4702
+ : undefined;
4703
+ const castUrl = data.castUrl || process.env.OSBORN_MEETING_CAST_URL || canvasUrl;
4637
4704
  const botId = await recallJoin.joinMeeting(meetingUrl, webhookBase, { castUrl });
4638
4705
  const sessionId = currentLLM?.sessionId || currentResumeSessionId || 'default';
4639
4706
  recallJoin.registerBot(botId, sessionId);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.86",
3
+ "version": "0.9.88",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {