osborn 0.9.104 → 0.9.106

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.
@@ -91,16 +91,19 @@ When a chunk says **`YOU WERE ADDRESSED`**, people in the meeting are WAITING
91
91
  for your voice. Measured failure (2026-08-01): notes-writing inside the reply
92
92
  turn pushed speech→reply past 30 seconds ("very very delayed").
93
93
 
94
- **Mandatory turn shape when addressed:**
95
- 1. **FIRST tool call = the `/canvas say` POST.** One short spoken reply (1–2
96
- sentences, conversational). Nothing runs before it — no Read, no Edit, no
97
- transcript pull, no sub-agent.
94
+ **Mandatory turn shape when addressed (0.9.106+):**
95
+ 1. **Just ANSWER in plain text — your words are spoken into the meeting
96
+ automatically.** The regular voice pipeline is redirected to the meeting
97
+ for addressed turns; do NOT use Bash/curl to speak (that adds a slow tool
98
+ roundtrip and can eat your tool budget). 1–2 short conversational
99
+ sentences, text first, before any tool call.
98
100
  2. **THEN** delegate note-taking / research to the writer/researcher
99
101
  sub-agents in the background as usual.
100
- 3. If you genuinely need a fact before answering, say a holding line FIRST
101
- ("Good question — one second while I check"), then look it up, then follow
102
- up with a second `/canvas say`. Never leave the room in silence while you
103
- work.
102
+ 3. If you genuinely need a fact before answering, SAY a holding line first
103
+ ("Good question — one second while I check"), look it up, then continue in
104
+ plain text. Never leave the room in silence while you work.
105
+ (The `/canvas say` curl remains for NON-addressed cases: when the voice-native
106
+ user says "tell the meeting X", or proactive announcements.)
104
107
 
105
108
  ## Browse requests while CASTING a live stream — drive the ENGINE, not the canvas
106
109
 
@@ -990,6 +990,17 @@ class ClaudeLLMStream extends llm.LLMStream {
990
990
  loadAllSkills(this.#opts.sessionBaseDir || this.#opts.workingDirectory || process.cwd()),
991
991
  ].filter(Boolean).join('\n\n'),
992
992
  canUseTool: async (toolName, input, _options) => {
993
+ // Auto-approve Bash calls to the agent's OWN local API (/canvas,
994
+ // /tts) — how the agent SPEAKS into a meeting. A spoken reply must
995
+ // never wait on a permission dialog. Localhost-only, own endpoints
996
+ // only, no shell chaining — not a general curl allowance.
997
+ if (toolName === 'Bash') {
998
+ const cmd = String(input?.command || '');
999
+ if (/^\s*curl\b[^;&|]*https?:\/\/(localhost|127\.0\.0\.1):\d+\/(canvas|tts)\b/.test(cmd) && !/[;&|]/.test(cmd)) {
1000
+ console.log(`✅ Auto-approved Bash → own API (canvas/tts speak path)`);
1001
+ return { behavior: 'allow', updatedInput: input };
1002
+ }
1003
+ }
993
1004
  // Auto-approve writes to session workspace (but block spec.md and library/ — fast brain manages those)
994
1005
  if (toolName === 'Write' || toolName === 'Edit') {
995
1006
  const filePath = String(input?.file_path || '');
package/dist/index.js CHANGED
@@ -1617,6 +1617,10 @@ async function main() {
1617
1617
  // gone forever, matching the skill-catalog pattern.
1618
1618
  let userRemovedAgents = [];
1619
1619
  let activeMeetingBotId = null; // Recall.ai bot ID if in a meeting
1620
+ // While set (Date.now() < value), the agent's streaming tts_say output is
1621
+ // REDIRECTED into the meeting canvas instead of suppressed — the regular
1622
+ // voice pipeline reused with the meeting as the sink (latency fix).
1623
+ let meetingAddressedUntil = 0;
1620
1624
  let activeMeetingPoller = null; // Transcript poller bound to that bot
1621
1625
  // LIVE meeting transcript → LLM (buffered webhook finals). See recall.on('transcript').
1622
1626
  const meetingTranscriptBuffer = [];
@@ -1628,8 +1632,17 @@ async function main() {
1628
1632
  if (!meetingTranscriptBuffer.length || !currentLLM)
1629
1633
  return;
1630
1634
  const turns = meetingTranscriptBuffer.splice(0); // drain
1635
+ if (addressed) {
1636
+ // REUSE the regular voice pipeline (2026-08-01 latency fix): during an
1637
+ // addressed turn the agent's normal streaming tts_say output is REDIRECTED
1638
+ // to the canvas (see the tts_say handler) — same speak path as regular
1639
+ // mode, sink = meeting. The old prompt made the agent compose a Bash curl
1640
+ // (extra LLM tool roundtrip, ate the 3-call budget, sometimes lost the
1641
+ // reply entirely) — measured as most of the 5-8s reply lag.
1642
+ meetingAddressedUntil = Date.now() + 90_000;
1643
+ }
1631
1644
  const header = addressed
1632
- ? `[MEETING — ${botId}] — YOU WERE ADDRESSED. Respond OUT LOUD into the meeting now: POST http://localhost:${apiPort}/canvas {"kind":"say","text":"..."} with a short, direct reply. Then note it.`
1645
+ ? `[MEETING — ${botId}] — YOU WERE ADDRESSED. Reply now in PLAIN TEXT — your words are spoken into the meeting automatically (do NOT use Bash/curl to speak). One or two short conversational sentences first; then, if needed, delegate notes/research in the background.`
1633
1646
  : `[MEETING — ${botId}]:`;
1634
1647
  // Prepend + consume any interruption context (bot was cut off mid-sentence).
1635
1648
  const interrupt = meetingInterruptContext ? `${meetingInterruptContext}\n` : '';
@@ -2494,6 +2507,15 @@ async function main() {
2494
2507
  // re-captures in the same room → feedback). Set by PipelineDirectLLM.chat()
2495
2508
  // when the turn is a [MEETING —] chunk. Normal user turns are unaffected.
2496
2509
  if (directLLM.suppressMeetingTTS) {
2510
+ // ADDRESSED turn → REDIRECT the normal streaming reply into the meeting
2511
+ // (reuse of the regular speak path; no Bash roundtrip). Silent-observer
2512
+ // turns stay suppressed as before.
2513
+ if (activeMeetingBotId && Date.now() < meetingAddressedUntil) {
2514
+ console.log(`🔊➡️📽️ tts_say → canvas (addressed meeting turn): "${data.text.slice(0, 60)}"`);
2515
+ pushCanvas({ kind: 'say', text: data.text });
2516
+ markMeetingSpeaking(data.text);
2517
+ return;
2518
+ }
2497
2519
  console.log(`🔇 tts_say suppressed (meeting turn — response goes to /canvas, not browser): "${data.text.slice(0, 60)}"`);
2498
2520
  return;
2499
2521
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.104",
3
+ "version": "0.9.106",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {