osborn 0.9.45 → 0.9.47

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.
@@ -54,9 +54,37 @@ EXPOSE 8741
54
54
 
55
55
  # Entrypoint: credential persistence + onboarding suppression + start
56
56
  COPY <<'ENTRYPOINT' /entrypoint.sh
57
- #!/bin/sh
57
+ #!/bin/bash
58
58
  set -e
59
59
 
60
+ # Persistent log capture for post-disconnect upload to Supabase Storage.
61
+ # Fly Machines has NO REST endpoint for fetching machine logs (the previous
62
+ # implementation hit /v1/apps/{app}/machines/{id}/logs which returns 404 → that
63
+ # 404 error string was getting uploaded as "the log" for every session).
64
+ # Volume-backed /workspace/osborn.log survives reboots and is readable via
65
+ # the documented /exec endpoint (`tail -n 500 /workspace/osborn.log`).
66
+ #
67
+ # We use process substitution to tee output to BOTH the log file AND the
68
+ # original stdout (so `flyctl logs` keeps working for ad-hoc debugging).
69
+ # Requires bash, hence the #!/bin/bash shebang.
70
+ #
71
+ # Size cap: if log grows past 100 MB, keep only the last 50 MB. Prevents
72
+ # disk-fill from long-running retry loops (we saw 17h × ~1 line/min = ~1000
73
+ # lines today, but anything connecting to LiveKit produces orders of
74
+ # magnitude more output).
75
+ LOGFILE=/workspace/osborn.log
76
+ mkdir -p /workspace
77
+ if [ -f "$LOGFILE" ] && [ "$(stat -c%s "$LOGFILE" 2>/dev/null || echo 0)" -gt 104857600 ]; then
78
+ echo "[sandbox] Rotating /workspace/osborn.log (>100MB, keeping last 50MB)"
79
+ tail -c 52428800 "$LOGFILE" > "$LOGFILE.tmp" && mv "$LOGFILE.tmp" "$LOGFILE"
80
+ fi
81
+ echo "[sandbox] === boot at $(date -Iseconds) ===" >> "$LOGFILE"
82
+ # Redirect all subsequent stdout+stderr from this script (and the eventual
83
+ # `exec osborn`) to both the original fd (Fly stdout collector) AND the
84
+ # append-only log file. tee runs as a backgrounded subshell that survives
85
+ # the final exec replacement.
86
+ exec > >(tee -a "$LOGFILE") 2>&1
87
+
60
88
  # Claude credential persistence (volume at /workspace)
61
89
  mkdir -p /workspace/.claude
62
90
  rm -rf /root/.claude
@@ -211,6 +211,19 @@ export class ClaudeLLM extends llm.LLM {
211
211
  mcpServers: this.#mcpServers,
212
212
  voiceMode: opts.voiceMode || 'realtime',
213
213
  skipTTSQueue: opts.skipTTSQueue || false,
214
+ // CRITICAL: the PreCompact / PostCompact hooks call
215
+ // `this.#opts.onCompactionEvent?.(...)` to invoke the bridge to the
216
+ // frontend (chat-bubble + banner). Without including the callback in
217
+ // this whitelisted literal, callers can pass it correctly via opts but
218
+ // it's silently dropped during construction → hooks invoke undefined →
219
+ // no chat bubble appears. This was the real reason the compaction UI
220
+ // never showed up in 0.9.44–0.9.46 despite the wiring at every caller
221
+ // looking right. Confirmed 2026-05-28 by reading the live dist on Fly
222
+ // and seeing PreCompact/PostCompact emoji logs + the SDK iterator
223
+ // marker [COMPACT-SDK-ITER] firing while [COMPACT-AGENT-RX] never did.
224
+ // `onPermissionRequest` is handled separately via its own private field
225
+ // and does NOT need to be in this literal.
226
+ onCompactionEvent: opts.onCompactionEvent,
214
227
  };
215
228
  this.#eventEmitter = opts.eventEmitter || new EventEmitter();
216
229
  console.log('🟠 ClaudeLLM initialized (Research Mode)');
@@ -660,6 +673,28 @@ export class ClaudeLLM extends llm.LLM {
660
673
  }
661
674
  }
662
675
  }
676
+ // Compaction signals observed on the SDK iterator (parallel to hook path).
677
+ // The SDK emits TWO message subtypes during compaction independent of
678
+ // hook registration:
679
+ // - type:'system', subtype:'compact_boundary' (with compact_metadata)
680
+ // - type:'system', subtype:'status', status:'compacting' | null
681
+ // We DON'T route these through onCompactionEvent (to avoid duplicate
682
+ // chat bubbles — hooks already do that), but we LOG them. If the hook
683
+ // path ever silently fails, these logs will be the only signal that
684
+ // compaction actually happened — making the failure obvious in fly logs.
685
+ if (msg.type === 'system' && msg.subtype === 'compact_boundary') {
686
+ const meta = msg.compact_metadata || {};
687
+ console.log(`[COMPACT-SDK-ITER] compact_boundary observed: trigger=${meta.trigger ?? '?'} pre_tokens=${meta.pre_tokens ?? '?'} preserved=${meta.preserved_segment ? 'yes' : 'no'}`);
688
+ // Fire onCompactionEvent as a FALLBACK if hooks didn't fire — we
689
+ // detect this by checking whether we've seen 'compaction_started'
690
+ // recently. For now, log only; can wire as fallback if hooks fail.
691
+ }
692
+ if (msg.type === 'system' && msg.subtype === 'status') {
693
+ const status = msg.status;
694
+ if (status === 'compacting' || status === null) {
695
+ console.log(`[COMPACT-SDK-ITER] status change: ${status === 'compacting' ? 'ENTERED compacting' : 'EXITED compacting'} session=${msg.session_id?.substring(0, 8) ?? '?'}`);
696
+ }
697
+ }
663
698
  // Checkpoint capture
664
699
  if (msg.type === 'user' && msg.uuid) {
665
700
  callbacks.onCheckpoint(msg.uuid);
package/dist/index.js CHANGED
@@ -1578,31 +1578,52 @@ async function main() {
1578
1578
  // previously skipped this entirely, so compaction events fired into the void
1579
1579
  // in pipeline mode.
1580
1580
  const buildOnCompactionEvent = () => (event) => {
1581
+ // CRITICAL diagnostic — every compaction event MUST appear in the agent
1582
+ // log first. If you don't see [COMPACT-AGENT-RX] for an event type, the
1583
+ // ClaudeLLM hook isn't calling this callback (most likely culprits:
1584
+ // PreCompact/PostCompact hook never registered, or the callback wasn't
1585
+ // passed through createPipelineDirectLLM's opts). If you see RX but no
1586
+ // CHAT-EMIT, the type didn't match the chat-emit branch. If you see both
1587
+ // but the frontend log never shows [COMPACT-FRONTEND], the data channel
1588
+ // dropped the message (room not connected, payload too big, etc.).
1589
+ console.log(`[COMPACT-AGENT-RX] type=${event.type} keys=[${Object.keys(event).filter(k => k !== 'type').join(',')}]`);
1581
1590
  try {
1582
1591
  // Raw event → banner state machine (compaction_started/progress/complete handlers in VoiceRoom.tsx).
1583
1592
  sendToFrontend({ ...event });
1593
+ console.log(`[COMPACT-AGENT-RAW-SENT] type=${event.type}`);
1584
1594
  // Inline chat bubble — reuses the existing claude_output path that's already working.
1585
1595
  if (event.type === 'compaction_started') {
1586
1596
  const triggerLabel = event.trigger ? ` (${event.trigger})` : '';
1597
+ const text = `🧠 _Crystallizing session memory…_${triggerLabel}`;
1587
1598
  sendToFrontend({
1588
1599
  type: 'claude_output',
1589
- text: `🧠 _Crystallizing session memory…_${triggerLabel}`,
1600
+ text,
1590
1601
  agentRole: 'direct',
1591
1602
  });
1603
+ console.log(`[COMPACT-AGENT-CHAT-EMIT] started → "${text.substring(0, 60)}"`);
1592
1604
  }
1593
1605
  else if (event.type === 'compaction_complete') {
1594
1606
  const n = event.skillsWritten ?? 0;
1595
1607
  const names = Array.isArray(event.skillNames) && event.skillNames.length > 0
1596
1608
  ? ` — ${event.skillNames.join(', ')}`
1597
1609
  : '';
1610
+ const text = `🧠 Memory crystallized — ${n} skill${n === 1 ? '' : 's'} updated${names}.`;
1598
1611
  sendToFrontend({
1599
1612
  type: 'claude_output',
1600
- text: `🧠 Memory crystallized — ${n} skill${n === 1 ? '' : 's'} updated${names}.`,
1613
+ text,
1601
1614
  agentRole: 'direct',
1602
1615
  });
1616
+ console.log(`[COMPACT-AGENT-CHAT-EMIT] complete → "${text.substring(0, 80)}"`);
1603
1617
  }
1618
+ else {
1619
+ // progress events don't get a chat bubble (too noisy) — they only feed the banner.
1620
+ // Log at debug level so we can confirm they fired.
1621
+ console.log(`[COMPACT-AGENT-CHAT-SKIP] type=${event.type} (progress events feed the banner only, no inline bubble)`);
1622
+ }
1623
+ }
1624
+ catch (err) {
1625
+ console.error(`[COMPACT-AGENT-ERROR] ${err.message}`);
1604
1626
  }
1605
- catch { /* non-fatal */ }
1606
1627
  };
1607
1628
  // Create DIRECT session (STT + Claude Agent SDK + TTS)
1608
1629
  async function createDirectSession(resumeSessionId, llmOverride) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "osborn",
3
- "version": "0.9.45",
3
+ "version": "0.9.47",
4
4
  "description": "Voice AI coding assistant - local agent that connects to Osborn frontend",
5
5
  "type": "module",
6
6
  "bin": {