osborn 0.9.45 → 0.9.46
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/Dockerfile.sandbox +29 -1
- package/dist/claude-llm.js +22 -0
- package/dist/index.js +24 -3
- package/package.json +1 -1
package/Dockerfile.sandbox
CHANGED
|
@@ -54,9 +54,37 @@ EXPOSE 8741
|
|
|
54
54
|
|
|
55
55
|
# Entrypoint: credential persistence + onboarding suppression + start
|
|
56
56
|
COPY <<'ENTRYPOINT' /entrypoint.sh
|
|
57
|
-
#!/bin/
|
|
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
|
package/dist/claude-llm.js
CHANGED
|
@@ -660,6 +660,28 @@ export class ClaudeLLM extends llm.LLM {
|
|
|
660
660
|
}
|
|
661
661
|
}
|
|
662
662
|
}
|
|
663
|
+
// Compaction signals observed on the SDK iterator (parallel to hook path).
|
|
664
|
+
// The SDK emits TWO message subtypes during compaction independent of
|
|
665
|
+
// hook registration:
|
|
666
|
+
// - type:'system', subtype:'compact_boundary' (with compact_metadata)
|
|
667
|
+
// - type:'system', subtype:'status', status:'compacting' | null
|
|
668
|
+
// We DON'T route these through onCompactionEvent (to avoid duplicate
|
|
669
|
+
// chat bubbles — hooks already do that), but we LOG them. If the hook
|
|
670
|
+
// path ever silently fails, these logs will be the only signal that
|
|
671
|
+
// compaction actually happened — making the failure obvious in fly logs.
|
|
672
|
+
if (msg.type === 'system' && msg.subtype === 'compact_boundary') {
|
|
673
|
+
const meta = msg.compact_metadata || {};
|
|
674
|
+
console.log(`[COMPACT-SDK-ITER] compact_boundary observed: trigger=${meta.trigger ?? '?'} pre_tokens=${meta.pre_tokens ?? '?'} preserved=${meta.preserved_segment ? 'yes' : 'no'}`);
|
|
675
|
+
// Fire onCompactionEvent as a FALLBACK if hooks didn't fire — we
|
|
676
|
+
// detect this by checking whether we've seen 'compaction_started'
|
|
677
|
+
// recently. For now, log only; can wire as fallback if hooks fail.
|
|
678
|
+
}
|
|
679
|
+
if (msg.type === 'system' && msg.subtype === 'status') {
|
|
680
|
+
const status = msg.status;
|
|
681
|
+
if (status === 'compacting' || status === null) {
|
|
682
|
+
console.log(`[COMPACT-SDK-ITER] status change: ${status === 'compacting' ? 'ENTERED compacting' : 'EXITED compacting'} session=${msg.session_id?.substring(0, 8) ?? '?'}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
663
685
|
// Checkpoint capture
|
|
664
686
|
if (msg.type === 'user' && msg.uuid) {
|
|
665
687
|
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
|
|
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
|
|
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) {
|