osborn 0.9.153 → 0.9.155
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 +22 -1
- package/dist/index.js +41 -3
- package/dist/pipeline-direct-llm.d.ts +2 -0
- package/dist/pipeline-direct-llm.js +4 -1
- package/package.json +1 -1
package/Dockerfile.sandbox
CHANGED
|
@@ -45,7 +45,8 @@ RUN apt-get update -qq && \
|
|
|
45
45
|
git \
|
|
46
46
|
make \
|
|
47
47
|
g++ \
|
|
48
|
-
python-is-python3
|
|
48
|
+
python-is-python3 \
|
|
49
|
+
util-linux && \
|
|
49
50
|
rm -rf /var/lib/apt/lists/*
|
|
50
51
|
|
|
51
52
|
ARG OSBORN_VERSION=latest
|
|
@@ -100,6 +101,7 @@ ENV OSBORN_CWD=/workspace
|
|
|
100
101
|
# Caveat: native user modules are tied to the image's Node ABI (currently 22) —
|
|
101
102
|
# a future Node-major image bump would need an `npm rebuild` of volume globals.
|
|
102
103
|
ENV NPM_CONFIG_PREFIX=/workspace/.npm-global
|
|
104
|
+
ENV NPM_CONFIG_CACHE=/tmp/npm-cache
|
|
103
105
|
ENV PATH=/workspace/.npm-global/bin:$PATH
|
|
104
106
|
|
|
105
107
|
WORKDIR /workspace
|
|
@@ -122,6 +124,25 @@ fi
|
|
|
122
124
|
echo "[sandbox-d] === boot at $(date -Iseconds) ===" >> "$LOGFILE"
|
|
123
125
|
exec > >(tee -a "$LOGFILE") 2>&1
|
|
124
126
|
|
|
127
|
+
# === Swap on volume ===
|
|
128
|
+
# 8GB swapfile on /workspace (persistent volume). Created once on first boot,
|
|
129
|
+
# reactivated on every subsequent boot (swapon state is lost across cold starts).
|
|
130
|
+
# Gives 8GB RAM + 8GB swap = 16GB addressable — pressure relief when the dev
|
|
131
|
+
# server or concurrent sub-agents spike memory without killing the voice loop.
|
|
132
|
+
SWAPFILE=/workspace/swapfile
|
|
133
|
+
if [ ! -f "$SWAPFILE" ]; then
|
|
134
|
+
echo "[sandbox-d] Creating 8GB swapfile at $SWAPFILE..."
|
|
135
|
+
fallocate -l 8G "$SWAPFILE" 2>/dev/null || dd if=/dev/zero of="$SWAPFILE" bs=1M count=8192 status=none
|
|
136
|
+
chmod 600 "$SWAPFILE"
|
|
137
|
+
mkswap "$SWAPFILE"
|
|
138
|
+
echo "[sandbox-d] Swapfile created"
|
|
139
|
+
fi
|
|
140
|
+
if swapon "$SWAPFILE" 2>/dev/null; then
|
|
141
|
+
echo "[sandbox-d] Swap active: $(free -h | grep Swap)"
|
|
142
|
+
else
|
|
143
|
+
echo "[sandbox-d] swapon skipped (already active or kernel restriction)"
|
|
144
|
+
fi
|
|
145
|
+
|
|
125
146
|
# Onboarding-suppression JSON
|
|
126
147
|
ONBOARDING_JSON='{"numStartups":10,"installMethod":"npm","autoUpdates":false,"hasCompletedOnboarding":true,"hasTrustDialogAccepted":true,"hasTrustDialogHooksAccepted":true,"hasCompletedProjectOnboarding":true,"hasAcknowledgedCostThreshold":true,"effortCalloutV2Dismissed":true,"theme":"dark","projects":{"/workspace":{"hasTrustDialogAccepted":true,"hasTrustDialogHooksAccepted":true,"hasCompletedProjectOnboarding":true}}}'
|
|
127
148
|
|
package/dist/index.js
CHANGED
|
@@ -1528,6 +1528,37 @@ function startApiServer(workingDir, port) {
|
|
|
1528
1528
|
return;
|
|
1529
1529
|
}
|
|
1530
1530
|
// ────────────────────────────────────────────────────────────────────────
|
|
1531
|
+
// ── Port proxy: /proxy/PORT/* → localhost:PORT/proxy/PORT/* ─────────────
|
|
1532
|
+
// Forwards to a local dev server while PRESERVING the full path so that
|
|
1533
|
+
// basePath-aware apps (e.g. Next.js with basePath: '/proxy/3000') receive
|
|
1534
|
+
// the complete URL and can strip the prefix themselves. No cookie required —
|
|
1535
|
+
// the machine is per-user and Fly's HTTPS is the outer auth boundary.
|
|
1536
|
+
const portProxyMatch = url.pathname.match(/^\/proxy\/(\d+)(\/.*)?$/);
|
|
1537
|
+
if (portProxyMatch) {
|
|
1538
|
+
const targetPort = parseInt(portProxyMatch[1]);
|
|
1539
|
+
if (targetPort > 1024 && targetPort < 65536 && targetPort !== 8300 && targetPort !== parseInt(process.env.OSBORN_API_PORT || '8741', 10)) {
|
|
1540
|
+
const portProxyOptions = {
|
|
1541
|
+
hostname: '127.0.0.1',
|
|
1542
|
+
port: targetPort,
|
|
1543
|
+
path: url.pathname + (url.search || ''),
|
|
1544
|
+
method: req.method,
|
|
1545
|
+
headers: { ...req.headers, host: `127.0.0.1:${targetPort}` },
|
|
1546
|
+
};
|
|
1547
|
+
const portProxyReq = httpRequest(portProxyOptions, (portProxyRes) => {
|
|
1548
|
+
res.writeHead(portProxyRes.statusCode || 502, portProxyRes.headers);
|
|
1549
|
+
portProxyRes.pipe(res);
|
|
1550
|
+
});
|
|
1551
|
+
portProxyReq.on('error', () => {
|
|
1552
|
+
if (!res.headersSent) {
|
|
1553
|
+
res.writeHead(502);
|
|
1554
|
+
res.end('Port proxy error');
|
|
1555
|
+
}
|
|
1556
|
+
});
|
|
1557
|
+
req.pipe(portProxyReq);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
1531
1562
|
// ── IDE reverse proxy (cookie-gated fall-through) ────────────────────────
|
|
1532
1563
|
// Forwards to code-server ONLY when:
|
|
1533
1564
|
// 1. ideProxyEnabled is true (code-server is confirmed ready), AND
|
|
@@ -2709,7 +2740,7 @@ async function main() {
|
|
|
2709
2740
|
const { readSessionHistory } = await import('./session-access.js');
|
|
2710
2741
|
const history = readSessionHistory(sessionId, workingDir, {
|
|
2711
2742
|
lastN: 10,
|
|
2712
|
-
types: ['assistant'],
|
|
2743
|
+
types: ['assistant', 'tool_use'], // tool_use entries also carry Claude's spoken text
|
|
2713
2744
|
});
|
|
2714
2745
|
recentMessages = history
|
|
2715
2746
|
.filter((m) => m.text)
|
|
@@ -2725,8 +2756,14 @@ async function main() {
|
|
|
2725
2756
|
// BEFORE the previous TTS completed, and we may have already suppressed in-flight
|
|
2726
2757
|
// tts_say events that arrived during that overlap).
|
|
2727
2758
|
const carriedSuppressed = lastInterruption?.suppressedText ?? '';
|
|
2728
|
-
lastInterruption = {
|
|
2729
|
-
|
|
2759
|
+
lastInterruption = {
|
|
2760
|
+
spokenText: fullText,
|
|
2761
|
+
recentMessages,
|
|
2762
|
+
suppressedText: carriedSuppressed,
|
|
2763
|
+
fullBlockText: fullBlockText && fullBlockText !== fullText ? fullBlockText : undefined,
|
|
2764
|
+
timestamp: Date.now(),
|
|
2765
|
+
};
|
|
2766
|
+
console.log(`📋 Interruption context stored (text: ${fullText.length} chars, JSONL: ${recentMessages.length} chars, block: ${fullBlockText?.length ?? 0} chars, suppressed carried: ${carriedSuppressed.length} chars)`);
|
|
2730
2767
|
}
|
|
2731
2768
|
/**
|
|
2732
2769
|
* Append text the agent tried to say while the user was speaking, but which we
|
|
@@ -2766,6 +2803,7 @@ async function main() {
|
|
|
2766
2803
|
spokenText: lastInterruption.spokenText,
|
|
2767
2804
|
recentMessages: lastInterruption.recentMessages,
|
|
2768
2805
|
suppressedText: lastInterruption.suppressedText,
|
|
2806
|
+
fullBlockText: lastInterruption.fullBlockText,
|
|
2769
2807
|
};
|
|
2770
2808
|
lastInterruption = null;
|
|
2771
2809
|
return ctx;
|
|
@@ -21,6 +21,8 @@ export interface InterruptionContext {
|
|
|
21
21
|
* and can re-articulate the relevant bits in its next response.
|
|
22
22
|
*/
|
|
23
23
|
suppressedText: string;
|
|
24
|
+
/** Full text of the interrupted TTS block — fallback when JSONL read returns empty */
|
|
25
|
+
fullBlockText?: string;
|
|
24
26
|
}
|
|
25
27
|
export interface PipelineDirectOptions extends ClaudeLLMOptions {
|
|
26
28
|
onFastBrainResult?: (result: FastBrainPanelResult) => void;
|
|
@@ -204,7 +204,10 @@ export class PipelineDirectLLM extends llm.LLM {
|
|
|
204
204
|
`Anything in "Your recent messages" below that appears AFTER the quoted heard text is content the user did not hear. The user has no memory of it.`,
|
|
205
205
|
``,
|
|
206
206
|
`Your recent messages (full untruncated — you wrote these):`,
|
|
207
|
-
interruptCtx.recentMessages
|
|
207
|
+
interruptCtx.recentMessages
|
|
208
|
+
|| (interruptCtx.fullBlockText
|
|
209
|
+
? `(JSONL unavailable — full TTS block text follows)\n"${interruptCtx.fullBlockText}"`
|
|
210
|
+
: '(no recent messages found)'),
|
|
208
211
|
suppressedBlock,
|
|
209
212
|
``,
|
|
210
213
|
`User's message: "${userText}"`,
|